Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,27 @@ import org.jetbrains.compose.resources.plural.PluralCategory
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

private val SimpleStringFormatRegex = Regex("""%(\d+)\$[ds]""")
internal fun String.replaceWithArgs(args: List<String>) = SimpleStringFormatRegex.replace(this) { matchResult ->
args[matchResult.groupValues[1].toInt() - 1]
private val SimpleStringFormatRegex = Regex("""%(?:([1-9]\d*)\$)?[ds]""")
internal fun String.replaceWithArgs(args: List<String>): String {
if (!SimpleStringFormatRegex.containsMatchIn(this)) return this

return SimpleStringFormatRegex.replace(this) { match ->
val placeholderNumber = match.groups[1]?.value?.toIntOrNull()
val index = when {
placeholderNumber != null -> placeholderNumber - 1
args.size == 1 -> 0
else -> {
throw IllegalArgumentException(
"Formatting failed: Non-positional placeholder '${match.value}' is ambiguous when multiple arguments are provided in \"$this\""
)
}
}
args.getOrElse(index) {
throw IllegalArgumentException(
"Formatting failed: Placeholder '${match.value}' at position ${index + 1} is out of bounds. Only ${args.size} argument(s) provided for format string \"$this\""
)
}
}
}

internal sealed interface StringItem {
Expand All @@ -23,7 +41,7 @@ internal fun dropStringItemsCache() {

internal suspend fun getStringItem(
resourceItem: ResourceItem,
resourceReader: ResourceReader
resourceReader: ResourceReader,
): StringItem = stringItemsCache.getOrLoad(
key = "${resourceItem.path}/${resourceItem.offset}-${resourceItem.size}"
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,13 @@ class StringFormatTest {
val template = "Hello %1\$s, %2\$s!"
val args = listOf("Alice")

assertFailsWith<IndexOutOfBoundsException> {
val exception = assertFailsWith<IllegalArgumentException> {
template.replaceWithArgs(args)
}
assertEquals(
"Formatting failed: Placeholder '%2\$s' at position 2 is out of bounds. Only 1 argument(s) provided for format string \"Hello %1\$s, %2\$s!\"",
exception.message
)
}

@Test
Expand All @@ -72,9 +76,13 @@ class StringFormatTest {
val template = "Hello %1\$s, you have %3\$s messages"
val args = listOf("Alice")

assertFailsWith<IndexOutOfBoundsException> {
val exception = assertFailsWith<IllegalArgumentException> {
template.replaceWithArgs(args)
}
assertEquals(
"Formatting failed: Placeholder '%3\$s' at position 3 is out of bounds. Only 1 argument(s) provided for format string \"Hello %1\$s, you have %3\$s messages\"",
exception.message
)
}

@Test
Expand Down Expand Up @@ -142,9 +150,13 @@ class StringFormatTest {
val args = listOf("Alice")

// An exception should be thrown because the second argument (%2$d) is missing
assertFailsWith<IndexOutOfBoundsException> {
val exception = assertFailsWith<IllegalArgumentException> {
template.replaceWithArgs(args)
}
assertEquals(
"Formatting failed: Placeholder '%2\$d' at position 2 is out of bounds. Only 1 argument(s) provided for format string \"Hello %1\$s, you have %2\$d messages!\"",
exception.message
)
}

@Test
Expand All @@ -154,9 +166,13 @@ class StringFormatTest {
val args = listOf("Alice", "1")

// The template has a %3$s placeholder, but there is no third argument
assertFailsWith<IndexOutOfBoundsException> {
val exception = assertFailsWith<IllegalArgumentException> {
template.replaceWithArgs(args)
}
assertEquals(
"Formatting failed: Placeholder '%3\$s' at position 3 is out of bounds. Only 2 argument(s) provided for format string \"Hello %1\$s, your rank is %3\$s\"",
exception.message
)
}

@Test
Expand All @@ -180,4 +196,44 @@ class StringFormatTest {
// Only the first argument should be used, ignoring the rest
assertEquals("Hello Alice!", result)
}
}

@Test
fun `replaceWithArgs handle single argument format`() {
val template = "Hello %s!"
val args = listOf("Alice")

val result = template.replaceWithArgs(args)

assertEquals("Hello Alice!", result)
}

@Test
fun `replaceWithArgs handle multiple placeholders for single argument`() {
val template = "%s and %s are best friends!"
val args = listOf("Alice")

val result = template.replaceWithArgs(args)

assertEquals("Alice and Alice are best friends!", result)
}

@Test
fun `replaceWithArgs throw exception when multiple different arguments with single placeholder format`() {
val template = "Hello %s, you have %d new messages!"
val args = listOf("Alice", "15")

assertFailsWith<IllegalArgumentException> {
template.replaceWithArgs(args)
}
}

@Test
fun `replaceWithArgs throw exception when mixing single and multiple placeholders format`() {
val template = "Hello %1\$s, you have %s new messages!"
val args = listOf("Alice", "15")

assertFailsWith<IllegalArgumentException> {
template.replaceWithArgs(args)
}
}
}