Skip to content

Instantly share code, notes, and snippets.

@glureau
Created April 10, 2024 16:10
Show Gist options
  • Save glureau/f968330faae4d3c21daf6610c35fd3f4 to your computer and use it in GitHub Desktop.
Save glureau/f968330faae4d3c21daf6610c35fd3f4 to your computer and use it in GitHub Desktop.
Kotlin Regex generator for semver/maven versioning style (<major>.<minor>.<patch>)
data class Group(val matches: Boolean, val testedVersions: MutableList<String>) {
val range: String get() = "[${testedVersions.first()} - ${testedVersions.last()}]"
}
fun main() {
val major = 2
val minor = 62
val patch = 103
println("Input version: $major.$minor.$patch")
val regexStr = "^" + higherVersionRegex(major, minor, patch)
println("Regex: $regexStr")
println("")
val regex = Regex(regexStr)
verify(regex, false)
verify(regex, true)
}
private fun verify(regex: Regex, isRegister: Boolean) {
println("---------------------")
println("Verifying regex: ${regex.pattern} on ${if (isRegister) "REGISTER" else "non-register"} versions")
val bruteforceMax = 250
println("Brute forcing all versions... (from 0.0.0 to $bruteforceMax.$bruteforceMax.$bruteforceMax)")
val groups = mutableListOf<Group>()
var versionCount = 0
bruteforceVersions(maxNumber = bruteforceMax)
.map { if (isRegister) "$it-reg" else it }
.forEach { testVersion ->
val matches = regex.matchAt(testVersion, 0)
val lastGroup = groups.lastOrNull()
val isMatch = matches != null && matches.range.start == 0
if (lastGroup != null && lastGroup.matches == isMatch) {
lastGroup.testedVersions.add(testVersion)
} else {
groups.add(Group(isMatch, mutableListOf(testVersion)))
}
versionCount++
}
println("Bruteforce Done!")
println("$versionCount versions tested against the regex.")
println("")
println("Groups:")
groups.forEach {
println("${it.range} - ${if (it.matches) "ENABLED" else "DISABLED"} (${it.testedVersions.size} versions)")
}
println("")
if (groups.size > 2) {
println("Warning: More than 2 groups detected. This is not expected!")
}
}
fun bruteforceVersions(maxNumber: Int) = generateSequence("0.0.0") {
val (major, minor, patch) = it.split('.').map { it.toInt() }
var newPatch = patch + 1
var newMinor = if (newPatch > maxNumber) {
newPatch = 0
minor + 1
} else minor
val newMajor = if (newMinor > maxNumber) {
newMinor = 0
major + 1
} else major
if (newMajor > maxNumber) return@generateSequence null // Stop the sequence
"$newMajor.$newMinor.$newPatch"
}
fun higherVersionRegex(major: Int, minor: Int, patch: Int): String {
val possibilities = buildList<String> {
add("$major\\.$minor\\.${higherNumberRegex(patch)}")
add("$major\\.${higherNumberRegex(minor + 1)}\\.[0-9]+")
add("${higherNumberRegex(major + 1)}\\.[0-9]+\\.[0-9]+")
}
return possibilities.joinToString(separator = "|", prefix = "(", postfix = ")")
}
fun higherNumberRegex(input: Int): String {
val inputStr = input.toString()
val inputStrLength = inputStr.length
val possibilities = buildList {
(1..inputStrLength).forEach { index ->
add(
inputStr.replaceRange(
inputStrLength - index, inputStrLength,
"[${inputStr[inputStrLength - index] + if (index == 1) 0 else 1}-9]" + "[0-9]".repeat(index - 1)
)
)
}
add("[0-9]{${inputStrLength + 1},}")
}
return possibilities.joinToString(separator = "|", prefix = "(", postfix = ")")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment