Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/shared-core-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
- 'libs/encryption/**'
- 'libs/reporting/**'
- 'libs/analytics-events/**'
- 'libs/text-format/**'
- 'build-logic/convention/src/main/kotlin/AnalyticsCatalogueConventionPlugin.kt'
- 'build-logic/convention/src/main/kotlin/com/getcode/buildlogic/analytics/**'
- 'gradle/libs.versions.toml'
Expand Down Expand Up @@ -78,7 +79,8 @@ jobs:
:libs:encryption:mnemonic:iosSimulatorArm64Test \
:libs:encryption:sha256:iosSimulatorArm64Test \
:libs:encryption:sha512:iosSimulatorArm64Test \
:libs:reporting:iosSimulatorArm64Test
:libs:reporting:iosSimulatorArm64Test \
:libs:text-format:iosSimulatorArm64Test

# The facade suite has to run against the Kotlin in this checkout, not against the last
# published release, or a facade written for an unreleased Kotlin change is untested until
Expand Down
2 changes: 2 additions & 0 deletions kmp/shared-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ kotlin {
isStatic = true
export(project(":libs:codes:kikcode"))
export(project(":libs:encryption:base58"))
export(project(":libs:text-format"))
export(project(":libs:encryption:sha256"))
export(project(":libs:encryption:sha512"))
export(project(":libs:encryption:hmac"))
Expand All @@ -56,6 +57,7 @@ kotlin {
dependencies {
api(project(":libs:codes:kikcode"))
api(project(":libs:encryption:base58"))
api(project(":libs:text-format"))
api(project(":libs:encryption:sha256"))
api(project(":libs:encryption:sha512"))
api(project(":libs:encryption:hmac"))
Expand Down
43 changes: 43 additions & 0 deletions libs/text-format/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
alias(libs.plugins.flipcash.kmp.library)
alias(libs.plugins.flipcash.kmp.test.fixtures)
}

// Compiles `src/commonTest/resources` into a generated `TestFixtures.kt` on `commonTest`, readable
// from every target -- see the `flipcash.kmp.test.fixtures` convention plugin.
testFixtures {
packageName = "com.flipcash.libs.textformat"
}

kotlin {
android {
namespace = "com.flipcash.libs.textformat"
compileSdk {
version = release(libs.versions.android.compileSdk.get().toInt()) {
minorApiLevel = libs.versions.android.compileSdkMinor.get().toInt()
}
}
minSdk = 29
withHostTest {}
}

iosArm64()
iosSimulatorArm64()
iosX64()
macosArm64()
macosX64()

sourceSets {
commonMain {
// Pure Kotlin -- no external dependencies needed.
}
commonTest {
dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.serialization.json)
// Hashes the copied fixture so a stale copy fails loudly.
implementation(libs.kotlincrypto.hash.sha2)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.flipcash.libs.textformat

/** What a detected range is. The parser never detects these itself; callers pass them in. */
enum class RangeKind { Link, Mention }

/**
* A link or mention that detection found in the raw text. [start] and [end] are UTF-16 code unit
* offsets (Kotlin `String` indices), half-open. A marker inside a range is part of it and never
* opens or closes a style.
*/
data class ProtectedRange(
val start: Int,
val end: Int,
val kind: RangeKind,
)

/** The styles the parser produces. */
enum class FormatStyle { Bold, Italic, Strike, Code, CodeBlock, Quote, Bullet, Numbered }

/** A [style] over [FormattedText.display], UTF-16 code unit offsets, half-open. */
data class StyledSpan(
val start: Int,
val end: Int,
val style: FormatStyle,
)

/**
* An input range moved to [FormattedText.display] offsets. A masked link `[text](url)` becomes a
* [RangeKind.Link] over `text` with the URL as [target]. A range without a [target] opens its own
* text.
*/
data class DisplayRange(
val start: Int,
val end: Int,
val kind: RangeKind,
val target: String? = null,
)

/**
* The parser's result. [display] is the raw text minus the consumed markers. [spans] are
* normalized per style (touching or overlapping runs merged) and ordered by start, then longest
* first, then style name. [displayRanges] are ordered by start, end, kind, then target.
*/
data class FormattedText(
val display: String,
val spans: List<StyledSpan>,
val displayRanges: List<DisplayRange>,
)
Loading
Loading