Rename methods in Regex. Add matchEntire method to match entire string against regex.

This commit is contained in:
Ilya Gorbunov
2015-10-14 07:44:19 +03:00
parent d860f335a3
commit d1d865aa0f
7 changed files with 80 additions and 26 deletions
+20 -2
View File
@@ -70,11 +70,29 @@ public class Regex(pattern: String, options: Set<RegexOption>) {
* @param startIndex An index to start search with, by default 0. Must be not less than zero and not greater than `input.length()` * @param startIndex An index to start search with, by default 0. Must be not less than zero and not greater than `input.length()`
* @return An instance of [MatchResult] if match was found or `null` otherwise. * @return An instance of [MatchResult] if match was found or `null` otherwise.
*/ */
public fun match(input: CharSequence, startIndex: Int = 0): MatchResult? = nativePattern.findNext(input.toString(), startIndex) public fun find(input: CharSequence, startIndex: Int = 0): MatchResult? = nativePattern.findNext(input.toString(), startIndex)
@Deprecated("Use find() instead.", ReplaceWith("find(input, startIndex)"))
public fun match(input: CharSequence, startIndex: Int = 0): MatchResult? = find(input, startIndex)
/** Returns a sequence of all occurrences of a regular expression within the [input] string, beginning at the specified [startIndex]. /** Returns a sequence of all occurrences of a regular expression within the [input] string, beginning at the specified [startIndex].
*/ */
public fun matchAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult> = sequence({ match(input, startIndex) }, { match -> match.next() }) public fun findAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult> = sequence({ find(input, startIndex) }, { match -> match.next() })
@Deprecated("Use findAll() instead.", ReplaceWith("findAll(input, startIndex)"))
public fun matchAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult> = findAll(input, startIndex)
/**
* Attempts to match the entire [input] CharSequence against the pattern.
*
* @return An instance of [MatchResult] if the entire input matches or `null` otherwise.
*/
public fun matchEntire(input: CharSequence): MatchResult? {
if (pattern.startsWith('^') && pattern.endsWith('$'))
return find(input)
else
return Regex("^${pattern.trimStart('^').trimEnd('$')}$", options).find(input)
}
/** /**
* Replaces all occurrences of this regular expression in the specified [input] string with specified [replacement] expression. * Replaces all occurrences of this regular expression in the specified [input] string with specified [replacement] expression.
@@ -123,12 +123,25 @@ public class Regex internal constructor(private val nativePattern: Pattern) {
* @param startIndex An index to start search with, by default 0. Must be not less than zero and not greater than `input.length()` * @param startIndex An index to start search with, by default 0. Must be not less than zero and not greater than `input.length()`
* @return An instance of [MatchResult] if match was found or `null` otherwise. * @return An instance of [MatchResult] if match was found or `null` otherwise.
*/ */
public fun match(input: CharSequence, startIndex: Int = 0): MatchResult? = nativePattern.matcher(input).findNext(startIndex, input) public fun find(input: CharSequence, startIndex: Int = 0): MatchResult? = nativePattern.matcher(input).findNext(startIndex, input)
@Deprecated("Use find() instead.", ReplaceWith("find(input, startIndex)"))
public fun match(input: CharSequence, startIndex: Int = 0): MatchResult? = find(input, startIndex)
/** /**
* Returns a sequence of all occurrences of a regular expression within the [input] string, beginning at the specified [startIndex]. * Returns a sequence of all occurrences of a regular expression within the [input] string, beginning at the specified [startIndex].
*/ */
public fun matchAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult> = sequence({ match(input, startIndex) }, { match -> match.next() }) public fun findAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult> = sequence({ find(input, startIndex) }, { match -> match.next() })
@Deprecated("Use findAll() instead.", ReplaceWith("findAll(input, startIndex)"))
public fun matchAll(input: CharSequence, startIndex: Int = 0): Sequence<MatchResult> = findAll(input, startIndex)
/**
* Attempts to match the entire [input] CharSequence against the pattern.
*
* @return An instance of [MatchResult] if the entire input matches or `null` otherwise.
*/
public fun matchEntire(input: CharSequence): MatchResult? = nativePattern.matcher(input).matchEntire(input)
/** /**
* Replaces all occurrences of this regular expression in the specified [input] string with specified [replacement] expression. * Replaces all occurrences of this regular expression in the specified [input] string with specified [replacement] expression.
@@ -143,8 +156,7 @@ public class Regex internal constructor(private val nativePattern: Pattern) {
* replacement for that match. * replacement for that match.
*/ */
public inline fun replace(input: CharSequence, transform: (MatchResult) -> String): String { public inline fun replace(input: CharSequence, transform: (MatchResult) -> String): String {
var match = match(input) var match: MatchResult? = find(input) ?: return input.toString()
if (match == null) return input.toString()
var lastStart = 0 var lastStart = 0
val length = input.length() val length = input.length()
@@ -212,6 +224,10 @@ private fun Matcher.findNext(from: Int, input: CharSequence): MatchResult? {
return if (!find(from)) null else MatcherMatchResult(this, input) return if (!find(from)) null else MatcherMatchResult(this, input)
} }
private fun Matcher.matchEntire(input: CharSequence): MatchResult? {
return if (!matches()) null else MatcherMatchResult(this, input)
}
private class MatcherMatchResult(private val matcher: Matcher, private val input: CharSequence) : MatchResult { private class MatcherMatchResult(private val matcher: Matcher, private val input: CharSequence) : MatchResult {
private val matchResult = matcher.toMatchResult() private val matchResult = matcher.toMatchResult()
override val range: IntRange override val range: IntRange
+4 -4
View File
@@ -1,17 +1,17 @@
package test.text package test.text
import kotlin.test.* import kotlin.test.*
import org.junit.Test as test import org.junit.Test
class RegexJVMTest { class RegexJVMTest {
@test fun matchGroups() { @Test fun matchGroups() {
val input = "1a 2b 3c" val input = "1a 2b 3c"
val regex = "(\\d)(\\w)".toRegex() val regex = "(\\d)(\\w)".toRegex()
val matches = regex.matchAll(input).toList() val matches = regex.findAll(input).toList()
assertTrue(matches.all { it.groups.size() == 3 }) assertTrue(matches.all { it.groups.size == 3 })
val m1 = matches[0] val m1 = matches[0]
assertEquals("1a", m1.groups[0]?.value) assertEquals("1a", m1.groups[0]?.value)
assertEquals(0..1, m1.groups[0]?.range) assertEquals(0..1, m1.groups[0]?.range)
+33 -13
View File
@@ -16,7 +16,7 @@ class RegexTest {
assertTrue(p.hasMatch(input)) assertTrue(p.hasMatch(input))
val first = p.match(input) val first = p.find(input)
assertTrue(first != null); first!! assertTrue(first != null); first!!
assertEquals("123", first.value) assertEquals("123", first.value)
@@ -26,7 +26,7 @@ class RegexTest {
assertEquals("456", second1.value) assertEquals("456", second1.value)
assertEquals(second1.value, second2.value) assertEquals(second1.value, second2.value)
assertEquals("56", p.match(input, startIndex = 5)?.value) assertEquals("56", p.find(input, startIndex = 5)?.value)
val last = second1.next()!! val last = second1.next()!!
assertEquals("789", last.value) assertEquals("789", last.value)
@@ -44,12 +44,12 @@ class RegexTest {
val input = "123 456 789" val input = "123 456 789"
val pattern = "\\d+".toRegex() val pattern = "\\d+".toRegex()
val matches = pattern.matchAll(input) val matches = pattern.findAll(input)
val values = matches.map { it.value } val values = matches.map { it.value }
val expected = listOf("123", "456", "789") val expected = listOf("123", "456", "789")
assertEquals(expected, values.toList()) assertEquals(expected, values.toList())
assertEquals(expected, values.toList(), "running match sequence second time") assertEquals(expected, values.toList(), "running match sequence second time")
assertEquals(expected.drop(1), pattern.matchAll(input, startIndex = 3).map { it.value }.toList()) assertEquals(expected.drop(1), pattern.findAll(input, startIndex = 3).map { it.value }.toList())
assertEquals(listOf(0..2, 4..6, 8..10), matches.map { it.range }.toList()) assertEquals(listOf(0..2, 4..6, 8..10), matches.map { it.range }.toList())
} }
@@ -57,18 +57,18 @@ class RegexTest {
@test fun matchAllSequence() { @test fun matchAllSequence() {
val input = "test" val input = "test"
val pattern = ".*".toRegex() val pattern = ".*".toRegex()
val matches = pattern.matchAll(input).toList() val matches = pattern.findAll(input).toList()
assertEquals(input, matches[0].value) assertEquals(input, matches[0].value)
assertEquals(input, matches.joinToString("") { it.value }) assertEquals(input, matches.joinToString("") { it.value })
assertEquals(2, matches.size()) assertEquals(2, matches.size)
} }
@test fun matchGroups() { @test fun matchGroups() {
val input = "1a 2b 3c" val input = "1a 2b 3c"
val pattern = "(\\d)(\\w)".toRegex() val pattern = "(\\d)(\\w)".toRegex()
val matches = pattern.matchAll(input).toList() val matches = pattern.findAll(input).toList()
assertTrue(matches.all { it.groups.size() == 3 }) assertTrue(matches.all { it.groups.size == 3 })
val m1 = matches[0] val m1 = matches[0]
assertEquals("1a", m1.groups[0]?.value) assertEquals("1a", m1.groups[0]?.value)
assertEquals("1", m1.groups[1]?.value) assertEquals("1", m1.groups[1]?.value)
@@ -82,23 +82,43 @@ class RegexTest {
@test fun matchOptionalGroup() { @test fun matchOptionalGroup() {
val pattern = "(hi)|(bye)".toRegex(RegexOption.IGNORE_CASE) val pattern = "(hi)|(bye)".toRegex(RegexOption.IGNORE_CASE)
val m1 = pattern.match("Hi!")!! val m1 = pattern.find("Hi!")!!
assertEquals(3, m1.groups.size()) assertEquals(3, m1.groups.size)
assertEquals("Hi", m1.groups[1]?.value) assertEquals("Hi", m1.groups[1]?.value)
assertEquals(null, m1.groups[2]) assertEquals(null, m1.groups[2])
val m2 = pattern.match("bye...")!! val m2 = pattern.find("bye...")!!
assertEquals(3, m2.groups.size()) assertEquals(3, m2.groups.size)
assertEquals(null, m2.groups[1]) assertEquals(null, m2.groups[1])
assertEquals("bye", m2.groups[2]?.value) assertEquals("bye", m2.groups[2]?.value)
} }
@test fun matchMultiline() { @test fun matchMultiline() {
val regex = "^[a-z]*$".toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) val regex = "^[a-z]*$".toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
val matchedValues = regex.matchAll("test\n\nLine").map { it.value }.toList() val matchedValues = regex.findAll("test\n\nLine").map { it.value }.toList()
assertEquals(listOf("test", "", "Line"), matchedValues) assertEquals(listOf("test", "", "Line"), matchedValues)
} }
@test fun matchEntire() {
val regex = "(\\d)(\\w)".toRegex()
assertNull(regex.matchEntire("1a 2b"))
assertNotNull(regex.matchEntire("3c")) { m ->
assertEquals("3c", m.value)
assertEquals(3, m.groups.size)
assertEquals(listOf("3c", "3", "c"), m.groups.map { it!!.value })
}
}
@test fun matchEntireLazyQuantor() {
val regex = "a+b+?".toRegex()
val input = StringBuilder("aaaabbbb")
assertEquals("aaaab", regex.find(input)!!.value)
assertEquals("aaaabbbb", regex.matchEntire(input)!!.value)
}
@test fun escapeLiteral() { @test fun escapeLiteral() {
val literal = """[-\/\\^$*+?.()|[\]{}]""" val literal = """[-\/\\^$*+?.()|[\]{}]"""
assertTrue(Regex.fromLiteral(literal).matches(literal)) assertTrue(Regex.fromLiteral(literal).matches(literal))
+1 -1
View File
@@ -664,7 +664,7 @@ ${" "}
""".trimIndent() """.trimIndent()
assertEquals(23, deindented.lines().size()) assertEquals(23, deindented.lines().size())
val indents = deindented.lines().map { "^\\s*".toRegex().match(it)!!.value.length } val indents = deindented.lines().map { "^\\s*".toRegex().find(it)!!.value.length }
assertEquals(0, indents.min()) assertEquals(0, indents.min())
assertEquals(42, indents.max()) assertEquals(42, indents.max())
assertEquals(1, deindented.lines().count { it.isEmpty() }) assertEquals(1, deindented.lines().count { it.isEmpty() })
@@ -32,7 +32,7 @@ internal fun getUsedMemoryKb(): Long {
private fun comparableVersionStr(version: String) = private fun comparableVersionStr(version: String) =
"(\\d+)\\.(\\d+).*" "(\\d+)\\.(\\d+).*"
.toRegex() .toRegex()
.match(version) .find(version)
?.groups ?.groups
?.drop(1)?.take(2) ?.drop(1)?.take(2)
// checking if two subexpression groups are found and length of each is >0 and <4 // checking if two subexpression groups are found and length of each is >0 and <4
@@ -64,7 +64,7 @@ class KotlinGradleIT: BaseGradleIT() {
for (i in 1..3) { for (i in 1..3) {
project.build(userVariantArg, "build", options = BaseGradleIT.BuildOptions(withDaemon = true)) { project.build(userVariantArg, "build", options = BaseGradleIT.BuildOptions(withDaemon = true)) {
assertSuccessful() assertSuccessful()
val matches = "\\[PERF\\] Used memory after build: (\\d+) kb \\(([+-]?\\d+) kb\\)".toRegex().match(output) val matches = "\\[PERF\\] Used memory after build: (\\d+) kb \\(([+-]?\\d+) kb\\)".toRegex().find(output)
assert(matches != null && matches.groups.size() == 3, "Used memory after build is not reported by plugin") assert(matches != null && matches.groups.size() == 3, "Used memory after build is not reported by plugin")
val reportedGrowth = matches!!.groups.get(2)!!.value.removePrefix("+").toInt() val reportedGrowth = matches!!.groups.get(2)!!.value.removePrefix("+").toInt()
assert(reportedGrowth <= 700, "Used memory growth $reportedGrowth > 700") assert(reportedGrowth <= 700, "Used memory growth $reportedGrowth > 700")