Skip to content

Commit 6c7c3d8

Browse files
authored
Support escaping commas in extra flags (#443)
* Support escaping commas in extra flags
1 parent c3fce0e commit 6c7c3d8

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

save-common/src/commonMain/kotlin/com/saveourtool/save/core/plugin/ExtraFlagsExtractor.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ class ExtraFlagsExtractor(private val generalConfig: GeneralConfig,
2929
* @return [ExtraFlags] or null if no match occurred
3030
*/
3131
internal fun extractExtraFlagsFrom(line: String) = line
32-
.split(",", ", ")
32+
.splitByNonEscaped(',')
33+
.map { it.replace("\\,", ",") }
3334
.associate { part ->
3435
val pair = part.split("=", limit = 2).map {
3536
it.replace("\\=", "=")
@@ -69,6 +70,29 @@ internal fun List<String>.filterAndJoinBy(regex: Regex, ending: Char): List<Stri
6970
}
7071
}
7172

73+
/**
74+
* Split [this] string by [delimiter] unless it's prepended by `\`.
75+
*
76+
* @param delimiter
77+
* @return list of string parts
78+
*/
79+
@Suppress("IDENTIFIER_LENGTH")
80+
internal fun String.splitByNonEscaped(delimiter: Char): List<String> {
81+
val indicesToSplit = mapIndexed { index, c -> index to c }
82+
.filter { (index, c) ->
83+
c == delimiter && (index == 0 || get(index - 1) != '\\')
84+
}
85+
.map { (index, _) -> index }
86+
val result: MutableList<String> = mutableListOf()
87+
var currentOffset = 0
88+
indicesToSplit.forEach {
89+
result.add(substring(currentOffset, it))
90+
currentOffset = it + 1
91+
}
92+
result.add(substring(currentOffset, length))
93+
return result
94+
}
95+
7296
/**
7397
* Substitute placeholders in `this.execFlags` with values from provided arguments
7498
*

save-common/src/commonNonJsTest/kotlin/com/saveourtool/save/core/ExtraFlagsExtractorTest.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ import com.saveourtool.save.core.plugin.ExtraFlagsExtractor
55
import com.saveourtool.save.core.plugin.GeneralConfig
66
import com.saveourtool.save.core.plugin.filterAndJoinBy
77
import com.saveourtool.save.core.plugin.resolvePlaceholdersFrom
8+
import com.saveourtool.save.core.plugin.splitByNonEscaped
89

910
import okio.fakefilesystem.FakeFileSystem
1011

1112
import kotlin.test.Test
1213
import kotlin.test.assertEquals
1314

15+
@Suppress(
16+
"TOO_LONG_FUNCTION",
17+
"WRONG_INDENTATION", // issue in diktat
18+
)
1419
class ExtraFlagsExtractorTest {
1520
@Test
1621
fun `basic test`() {
@@ -26,6 +31,9 @@ class ExtraFlagsExtractorTest {
2631
"Unparseable nonsense" to ExtraFlags.empty,
2732
"args1=--flag --opt,args2=-debug --flag2" to ExtraFlags("--flag --opt", "-debug --flag2"),
2833
"args1=--flag\\=value,args2=--foo=bar" to ExtraFlags("--flag=value", "--foo=bar"),
34+
"args1=option1\\,option2,args2=option3\\,option4" to ExtraFlags("option1,option2", "option3,option4"),
35+
"args1=option1\\,option2" to ExtraFlags("option1,option2", ""),
36+
"args2=option3\\,option4" to ExtraFlags("", "option3,option4"),
2937
)
3038
.forEach { (line, extraFlags) ->
3139
assertEquals(extraFlags, extraFlagsExtractor.extractExtraFlagsFrom(line))
@@ -105,6 +113,52 @@ class ExtraFlagsExtractorTest {
105113
"another-cmd --flag"
106114
)
107115
)
116+
117+
checkMultilineDirectives(
118+
Regex("""// RUN: (.*([^\\]=)?.*)\\?"""),
119+
listOf(
120+
"// RUN: command --flag=option\\,\\",
121+
"// RUN: another-option --another-flag",
122+
"// RUN: another-cmd\\=\\",
123+
"// RUN: --flag=option\\,another-option",
124+
),
125+
listOf(
126+
"command --flag=option\\,another-option --another-flag",
127+
"another-cmd\\=--flag=option\\,another-option"
128+
)
129+
)
130+
}
131+
132+
@Test
133+
fun `test for splitByNonEscaped`() {
134+
assertEquals(
135+
listOf("this string\\, not split"),
136+
"this string\\, not split".splitByNonEscaped(','),
137+
)
138+
assertEquals(
139+
listOf("this string", " but split"),
140+
"this string, but split".splitByNonEscaped(','),
141+
)
142+
assertEquals(
143+
listOf("this string\\, not split", " but here - it's split"),
144+
"this string\\, not split, but here - it's split".splitByNonEscaped(','),
145+
)
146+
assertEquals(
147+
listOf("", ""),
148+
",".splitByNonEscaped(','),
149+
)
150+
assertEquals(
151+
listOf("", "text"),
152+
",text".splitByNonEscaped(','),
153+
)
154+
assertEquals(
155+
listOf("\\,"),
156+
"\\,".splitByNonEscaped(','),
157+
)
158+
assertEquals(
159+
listOf("\\,text"),
160+
"\\,text".splitByNonEscaped(','),
161+
)
108162
}
109163

110164
private fun checkMultilineDirectives(

0 commit comments

Comments
 (0)