Skip to content

Commit

Permalink
fix: Fix parsing issues and unsupported scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Aug 11, 2024
1 parent 385f408 commit d4cfaed
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 64 deletions.
39 changes: 24 additions & 15 deletions docs/1_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,25 +101,34 @@ java -jar revanced-cli.jar patch -b revanced-patches.rvp --set-options "Patch na
```

> [!WARNING]
> The values of options are typed. If you set a value with the wrong type, the patching process will fail.
> The type of the option value can be seen when listing patches with the option `--with-options`.
> Option values are usually typed. If you set a value with the wrong type, the patch can fail.
> Option value types can be seen when listing patches with the option `--with-options`.
>
> Example values:
> Example option values:
>
> String: `string`
> Boolean: `true`, `false`
> Integer: `123`
> Double: `1.0`
> Float: `1.0f`
> Long: `1234567890`, `1L`
> List: `item1,item2,item3`
> - String: `string`
> - Boolean: `true`, `false`
> - Integer: `123`
> - Double: `1.0`
> - Float: `1.0f`
> - Long: `1234567890`, `1L`
> - List: `[item1,item2,item3]`
> - List of type `Any`: `[item1,123,true,1.0]`
> - Empty list of type `Any`: `[]`
> - Typed empty list: `int[]`
> - Typed and nested empty list: `[int[]]`
> - List with null value and two empty strings: `[null,\'\',\"\"]`
>
> In addition to that, you can escape quotes (`\"`, `\'`) and commas (`\,`) to treat values as string literals:
> Quotes and commas escaped in strings (`\"`, `\'`, `\,`) are parsed as part of the string.
> List items are recursively parsed, so you can escape values in lists:
>
> Integer as string: `\'123\'`
> List with an integer, an integer as a string and a string with a comma: `123,\'123\',str\,ing`
>
> Example command with escaped quotes:
> - Escaped integer as a string: `[\'123\']`
> - Escaped boolean as a string: `[\'true\']`
> - Escaped list as a string: `[\'[item1,item2]\']`
> - Escaped null value as a string: `[\'null\']`
> - List with an integer, an integer as a string and a string with a comma, and an escaped list: [`123,\'123\',str\,ing`,`\'[]\'`]
>
> Example command with an escaped integer as a string:
>
> ```bash
> java -jar revanced-cli.jar -b revanced-patches.rvp --set-options "Patch name" -OstringKey=\'1\' input.apk
Expand Down
114 changes: 75 additions & 39 deletions src/main/kotlin/app/revanced/cli/command/PatchCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import picocli.CommandLine.Spec
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.util.*
import java.util.logging.Logger

@CommandLine.Command(
Expand Down Expand Up @@ -407,56 +406,93 @@ class OptionValueConverter : CommandLine.ITypeConverter<Any?> {
override fun convert(value: String?): Any? {
value ?: return null

// Check if the value starts with '[' and ends with ']'
if (value.startsWith("[") && value.endsWith("]")) {
val innerValue = value.substring(1, value.length - 1)

val list = buildList {
var escaped = false
var insideEscape = false

buildString {
for (char in innerValue) {
if (escaped) {
append(char)
escaped = false
} else if (char == '\\') {
escaped = true
} else if (char == '[' && !insideEscape) {
insideEscape = true
append(char)
} else if (char == ']' && insideEscape) {
insideEscape = false
append(char)
} else if (char == ',' && !insideEscape) {
add(toString())
setLength(0)
} else {
append(char)
return when {
value.startsWith("[") && value.endsWith("]") -> {
val innerValue = value.substring(1, value.length - 1)

buildList {
var nestLevel = 0
var insideQuote = false
var escaped = false

val item = buildString {
for (char in innerValue) {
when (char) {
'\\' -> {
if (escaped || nestLevel != 0) {
append(char)
}

escaped = !escaped
}
'"', '\'' -> {
if (!escaped) {
insideQuote = !insideQuote
} else {
escaped = false
}

append(char)
}

'[' -> {
if (!insideQuote) {
nestLevel++
}

append(char)
}

']' -> {
if (!insideQuote) {
nestLevel--

if (nestLevel == -1) {
return value
}
}

append(char)
}

',' -> if (nestLevel == 0) {
if (insideQuote) {
append(char)
} else {
add(convert(toString()))
setLength(0)
}
} else {
append(char)
}

else -> append(char)
}
}
}
add(toString()) // Add the last element
}
}

if (list.size > 1) {
return list.map(::convert)
if (item.isNotEmpty()) {
add(convert(item))
}
}
}

return list
}

return when {
value.startsWith("\"") && value.endsWith("\"") -> value.substring(1, value.length - 1)
value.startsWith("'") && value.endsWith("'") -> value.substring(1, value.length - 1)
value.endsWith("f") -> value.dropLast(1).toFloatOrNull() ?: value
value.endsWith("L") -> value.dropLast(1).toLongOrNull() ?: value
value.endsWith("f") -> value.dropLast(1).toFloat()
value.endsWith("L") -> value.dropLast(1).toLong()
value.equals("true", ignoreCase = true) -> true
value.equals("false", ignoreCase = true) -> false
value.toIntOrNull() != null -> value.toInt()
value.toLongOrNull() != null -> value.toLong()
value.toDoubleOrNull() != null -> value.toDouble()
value.toFloatOrNull() != null -> value.toFloat()
value == "null" -> null
value == "int[]" -> emptyList<Int>()
value == "long[]" -> emptyList<Long>()
value == "double[]" -> emptyList<Double>()
value == "float[]" -> emptyList<Float>()
value == "boolean[]" -> emptyList<Boolean>()
value == "string[]" -> emptyList<String>()
else -> value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ class OptionValueConverterTest {
"string" convertsTo "string" because "Strings should remain the same"
}

@Test
fun `converts to null`() {
"null" convertsTo null because "null should convert to null"
"\"null\"" convertsTo "null" because "Escaped null should convert to a string"
}

@Test
fun `converts to boolean`() {
"true" convertsTo true because "true should convert to a boolean true"
Expand Down Expand Up @@ -46,23 +52,27 @@ class OptionValueConverterTest {
fun `converts lists`() {
"1,2" convertsTo "1,2" because "Lists without square brackets should not be converted to lists"
"[1,2" convertsTo "[1,2" because "Invalid lists should not be converted to lists"
"\\[1,2]" convertsTo "[1,2]" because "Lists with escaped square brackets should not be converted to lists"
"\\[1,2\\]" convertsTo "[1,2\\]" because "Escaping the closing square bracket should be treated as a normal character"
"\"[1,2]\"" convertsTo "[1,2]" because "Lists with escaped square brackets should not be converted to lists"

"[]" convertsTo listOf<String>() because "Empty untyped lists should convert to empty lists of strings"
"int[]" convertsTo listOf<Int>() because "Empty typed lists should convert to lists of the specified type"
"int[][]" convertsTo listOf<List<Int>>() because "Nested typed lists should convert to nested lists of the specified type"
"[]" convertsTo emptyList<Any>() because "Empty untyped lists should convert to empty lists of any"
"int[]" convertsTo emptyList<Int>() because "Empty typed lists should convert to lists of the specified type"
"[[int[]]]" convertsTo listOf(listOf(emptyList<Int>())) because "Nested typed lists should convert to nested lists of the specified type"
"[\"int[]\"]" convertsTo listOf("int[]") because "Lists of escaped empty typed lists should not be converted to lists"

"[1,2,3]" convertsTo listOf(1, 2, 3) because "Lists of integers should convert to lists of integers"
"[[1]]" convertsTo listOf(listOf(1)) because "Nested lists with one element should convert to nested lists"
"[[1,2],[3,4]]" convertsTo listOf(listOf(1, 2), listOf(3, 4)) because "Nested lists should convert to nested lists"
"[1\\,2]" convertsTo listOf("1,2") because "Values in lists should not be split by escaped commas"
"[1\\\\,2]" convertsTo listOf("1\\", "2") because "Values in lists should not be split by escaped escape symbols"
"[[1\\,2]]" convertsTo listOf(listOf("1,2")) because "Values in nested lists should not be split by escaped commas"

"[\"1,2\"]" convertsTo listOf("1,2") because "Values in lists should not be split by commas in strings"
"[[\"1,2\"]]" convertsTo listOf(listOf("1,2")) because "Values in nested lists should not be split by commas in strings"

"[\"\\\"\"]" convertsTo listOf("\"") because "Escaped quotes in strings should be converted to quotes"
"[[\"\\\"\"]]" convertsTo listOf(listOf("\"")) because "Escaped quotes in strings nested in lists should be converted to quotes"
"[.1,.2f,,true,FALSE]" convertsTo listOf(.1, .2f, "", true, false) because "Values in lists should be converted to the correct type"
}

private val convert = OptionValueConverter()::convert

private infix fun String.convertsTo(to: Any) = convert(this) to to
private infix fun Pair<Any?, Any>.because(reason: String) = assert(this.first == this.second) { reason }
private infix fun String.convertsTo(to: Any?) = convert(this) to to
private infix fun Pair<Any?, Any?>.because(reason: String) = assert(this.first == this.second) { reason }
}

0 comments on commit d4cfaed

Please sign in to comment.