Skip to content

Commit

Permalink
Updated the SpanAdapter signature to better support logging of unsupp…
Browse files Browse the repository at this point in the history
…orted Spans
  • Loading branch information
brianwernick committed Nov 25, 2023
1 parent bf3080c commit 568d84a
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 30 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ plugins {
id "io.github.gradle-nexus.publish-plugin" version "1.1.0"
}

apply from: './gradle/testOutput.gradle'

allprojects {
repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,25 @@ private fun ScreenContent() {

Sample(
text = annotatedStringResource(R.string.annotated_simple_defaults),
description = "These basic html tags (`<b>` & `<i>`) are supported by default",
description = "These basic html tags (`<b>`, `<i>`, & `<u>`) are supported by default",
modifier = Modifier.fillMaxWidth()
)

Sample(
text = annotatedStringResource(
R.string.annotated_colors,
id = R.string.annotated_colors,
annotationHandlers = colorHandlers
),
description = "Adding support for custom annotations is fairly simple with AnnotationHandlers",
description = "Adding support for custom annotations is fairly simple with AnnotationHandlers. `<annotation color=#ffff0000></annotation>` in the Red example.",
modifier = Modifier.fillMaxWidth()
)

Sample(
text = annotatedStringResource(R.string.annotated_with_args, 120, 0.3547128),
text = annotatedStringResource(
id = R.string.annotated_with_args,
120, 0.3547128,
annotationHandlers = colorHandlers
),
description = "Annotations are supported even with string arguments",
modifier = Modifier.fillMaxWidth()
)
Expand All @@ -83,7 +87,12 @@ private fun ScreenContent() {
)

Sample(
text = annotatedPluralStringResource(R.plurals.annotated_with_args, quantity = 12, 12),
text = annotatedPluralStringResource(
id = R.plurals.annotated_with_args,
quantity = 12,
12,
annotationHandlers = colorHandlers
),
description = "Including Plurals with string arguments",
modifier = Modifier.fillMaxWidth()
)
Expand Down
4 changes: 2 additions & 2 deletions demo/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<string name="annotated_simple_defaults">Annotated strings can be <b>Bold</b>, <i>Italic</i>, or <u>Underlined</u></string>
<string name="annotated_colors">Custom AnnotationHandlers can support things like colors, e.g.: <annotation color="#ffff0000"><b>Red</b></annotation>, <annotation color="#ff00ff00"><b>Green</b></annotation>, &amp; <annotation color="#ff0000ff"><b>Blue</b></annotation></string>
<string name="annotated_with_args">Placeholders are also filled with args such as
<annotation color="#ff3fcf7f"><b>Count = %1$d </b></annotation>
and as a <annotation color="#ff3fcf7f"><b>Percent = %2$.4f %%</b></annotation>
<annotation color="#ff3f9faf"><b>Count = %1$d </b></annotation>
and as a <annotation color="#ff3f9faf"><b>Percent = %2$.4f %%</b></annotation>
</string>

<plurals name="annotated_simple_defaults">
Expand Down
80 changes: 80 additions & 0 deletions gradle/testOutput.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import org.gradle.internal.logging.text.StyledTextOutputFactory
import static org.gradle.internal.logging.text.StyledTextOutput.Style

/**
* Handles watching the test output for each gradle module, storing the results until the
* test suite has completed, then prints out the results in a condensed manner.
*
* To include this functionality in the project, you just need to apply this script in
* the root `build.gradle` file. e.g. `apply from: './gradle/testOutput.gradle'`
*/

ext {
moduleTestResults = []
}

allprojects {
// Collects test results which are then printed in buildFinished below
tasks.withType(Test).configureEach {
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1

testLogging {
outputs.upToDateWhen { false }
events "failed", "skipped", "standardOut"
exceptionFormat "full"

afterSuite { desc, result ->
if (!desc.parent) {
moduleTestResults.add([name: project.name, result: result])
}
}
}
}
}

gradle.buildFinished {
def testResults = ext.moduleTestResults
def totals = [success: 0, failed: 0, skipped: 0, total: 0]

testResults.forEach { result ->
totals.success += result.result.successfulTestCount
totals.failed += result.result.failedTestCount
totals.skipped += result.result.skippedTestCount
totals.total += result.result.testCount

printTestResults(
result.name,
result.result.successfulTestCount,
result.result.failedTestCount,
result.result.skippedTestCount,
result.result.testCount
)
}

if (!testResults.isEmpty()) {
printTestResults(
"Project Total",
totals.success,
totals.failed,
totals.skipped,
totals.total
)
}
}

private printTestResults(title, passed, failed, skipped, total) {
def style = Style.Success
if (failed > 0) {
style = Style.Failure
} else if (skipped > 0) {
style = Style.Info
}

def summary = " ${passed} / ${total} passed, ${failed} failed, ${skipped} skipped "
def summaryTitle = " ${title.capitalize()} "
def styledOut = services.get(StyledTextOutputFactory).create("styledTests-out")

styledOut.withStyle(style).println('╔═' + summaryTitle + '' * (summary.length() - summaryTitle.length() - 1) + '')
styledOut.withStyle(style).println('' + summary + '')
styledOut.withStyle(style).println('' + '' * summary.length() + '')
}
Empty file modified gradlew
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.devbrackets.android.annores.text.annotation

import android.text.Annotation
import android.text.ParcelableSpan
import android.text.Spanned
import android.text.SpannedString
import android.util.Log
Expand All @@ -15,6 +17,8 @@ import com.devbrackets.android.annores.text.annotation.handler.FontStyleAnnotati
* Provides the functionality for converting from a [SpannedString] to an [AnnotatedString]
*/
object AnnotatedStringConverter {
private const val LOG_TAG = "AnnotatedStrConverter"

val defaultSpanAdapters = listOf(
StyleSpanAdapter,
AnnotationSpanAdapter,
Expand Down Expand Up @@ -44,14 +48,21 @@ object AnnotatedStringConverter {
): AnnotatedString {
val destination = AnnotatedString.Builder(source.toString())

spanAdapters.forEach { adapter ->
adapter.adapt(source).forEach { annotation ->
source.getSpans(0, source.length, ParcelableSpan::class.java).forEach { span ->
val adapter = spanAdapters.firstOrNull { it.adapts(span) }
val annotations = adapter?.adapt(span, source.getSpanStart(span), source.getSpanEnd(span))

annotations?.forEach { annotation ->
applyAnnotation(
annotation = annotation,
destination = destination,
annotationHandlers = annotationHandlers
)
}

if (annotations == null) {
Log.e(LOG_TAG, "No SpanAdapter found for Span type \"${span::class.simpleName}\"")
}
}

return destination.toAnnotatedString()
Expand Down Expand Up @@ -79,7 +90,14 @@ object AnnotatedStringConverter {
}

if (!handled) {
Log.w("AnnotatedStrConverter", "No AnnotationHandler handles PositionedAnnotation $annotation")
Log.e(LOG_TAG, "No AnnotationHandler found for ${annotation.annotation.displayString()}")
}
}

/**
* Converts the [Annotation] to a string for display in error messages
*/
private fun Annotation.displayString(): String {
return "Annotation(key = $key, value = $value)"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.devbrackets.android.annores.text.annotation.compat.adapter

import android.text.ParcelableSpan
import android.text.Spanned
import android.util.Log
import com.devbrackets.android.annores.text.annotation.compat.adapter.SpanAdapter.PositionedAnnotation
import kotlin.reflect.KClass

Expand All @@ -12,15 +12,23 @@ import kotlin.reflect.KClass
abstract class AbstractSpanAdapter<T: ParcelableSpan>(
protected val spanClass: KClass<T>
): SpanAdapter {
override fun adapt(source: Spanned): List<PositionedAnnotation> {
val spans = source.getSpans(0, source.length, spanClass.java)

return spans.flatMap { span ->
val startIndex = source.getSpanStart(span)
val endIndex = source.getSpanEnd(span)
override fun <S : ParcelableSpan> adapts(span: S): Boolean {
return span::class == spanClass
}

convertSpan(span, startIndex, endIndex)
override fun <S : ParcelableSpan> adapt(
span: S,
startIndex: Int,
endIndex: Int
): List<PositionedAnnotation> {
if (!adapts(span)) {
val methodCallString = "adapt(${span::class.simpleName}, $startIndex, $endIndex)"
Log.e("AbstractSpanAdapter", "$methodCallString was called when adapts() returned false")
return emptyList()
}

@Suppress("UNCHECKED_CAST")
return convertSpan(span as T, startIndex, endIndex)
}

/**
Expand All @@ -37,6 +45,6 @@ abstract class AbstractSpanAdapter<T: ParcelableSpan>(
abstract fun convertSpan(
span: T,
startIndex: Int,
endIndex: Int,
endIndex: Int
): List<PositionedAnnotation>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.devbrackets.android.annores.text.annotation.compat.adapter

import android.text.Annotation
import android.text.ParcelableSpan
import android.text.Spanned

/**
Expand All @@ -10,13 +11,25 @@ import android.text.Spanned
interface SpanAdapter {

/**
* Converts any Span types supported by the implementing [SpanAdapter]
* into [PositionedAnnotation]s.
* Checks if this instance can convert [S] into [PositionedAnnotation]s.
*
* @param source The [Spanned] to look for and adapt any supported Spans with
* @return A list of the Spans that have been adapted to [PositionedAnnotation]s
* @param span The [S] to determine if it can be converted to [PositionedAnnotation]s
* @return `true` if this [SpanAdapter] can convert [S] into [PositionedAnnotation]s
*/
fun adapt(source: Spanned): List<PositionedAnnotation>
fun <S: ParcelableSpan> adapts(span: S): Boolean

/**
* Converts the [span] into a list of [PositionedAnnotation]s. Typically
* this conversion process will result in a list of size 1, however there
* are cases where a single [ParcelableSpan] will be converted into multiple
* [PositionedAnnotation]s.
*
* @param span The [ParcelableSpan] of [S] to convert to [PositionedAnnotation]s
* @param startIndex The start index for the [span]
* @param endIndex The end index for the [span]
* @return A list of [PositionedAnnotation]s that represent the converted [span]
*/
fun <S: ParcelableSpan> adapt(span: S, startIndex: Int, endIndex: Int): List<PositionedAnnotation>

/**
* Provides the information necessary to apply the [annotation]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ interface AnnotationHandler {
/**
* Checks if this instance can apply the styling defined in the [Annotation].
*
* @param annotation The [Annotation] to check if this instance of [AnnotationHandler] can apply
* styling for
*
* @return `true` if this [AnnotationHandler] instance can apply styling for the [annotation]
* @param annotation The [Annotation] to determine if this [AnnotationHandler] can apply styling
* @return `true` if this [AnnotationHandler] instance can apply styling for [annotation]
*/
fun handles(annotation: Annotation): Boolean

Expand All @@ -27,7 +25,6 @@ interface AnnotationHandler {
* @param startIndex The start index (inclusive) in the [builder] to apply styling to
* @param endIndex The end index (inclusive) in the [builder] to apply styling to
* @param builder The [AnnotatedString.Builder] to apply styling from the [annotation] to
*
* @return `true` if the styling was applied to the [builder]
*/
fun apply(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import androidx.compose.ui.text.style.TextDecoration
* * `normal`
* * `bold`
* * `italic`
* * `underline`
* * `strike` (strike through)
*
* e.g.
* ```xml
Expand All @@ -29,7 +31,7 @@ object FontStyleAnnotationHandler: AnnotationHandler {
const val VALUE_ITALIC = "italic"

const val VALUE_UNDERLINE = "underline"
const val VALUE_STRIKE_THROUGH = "strikeThrough"
const val VALUE_STRIKE_THROUGH = "strike"

override fun handles(annotation: Annotation): Boolean {
return annotation.key.equals(KEY, true)
Expand Down

0 comments on commit 568d84a

Please sign in to comment.