From 62d6bcaa6d9eb9d96b62d0375d819137208c1efc Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 4 Nov 2015 06:04:31 +0300 Subject: [PATCH] Provide overloads both for Strings and CharSequences of removePrefix, removeSuffix, removeSurronding, trim* and pad*, replaceRange & removeRange --- libraries/stdlib/src/kotlin/text/Strings.kt | 263 +++++++++++++----- .../src/kotlin/text/StringsDeprecated.kt | 220 ++------------- libraries/stdlib/test/text/StringTest.kt | 179 +++++++----- 3 files changed, 325 insertions(+), 337 deletions(-) diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index 102a391fab0..84f6171a0e8 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -9,9 +9,9 @@ import kotlin.text.MatchResult import kotlin.text.Regex /** - * Returns a string with leading and trailing characters matching the [predicate] trimmed. + * Returns a sub sequence of this char sequence having leading and trailing characters matching the [predicate] trimmed. */ -inline public fun CharSequence.trim(predicate: (Char) -> Boolean): String { +inline public fun CharSequence.trim(predicate: (Char) -> Boolean): CharSequence { var startIndex = 0 var endIndex = length() - 1 var startFound = false @@ -34,24 +34,36 @@ inline public fun CharSequence.trim(predicate: (Char) -> Boolean): String { } } - return substring(startIndex, endIndex + 1) + return subSequence(startIndex, endIndex + 1) } /** - * Returns a string with leading characters matching the [predicate] trimmed. + * Returns a string with leading and trailing characters matching the [predicate] trimmed. */ -inline public fun CharSequence.trimStart(predicate: (Char) -> Boolean): String { +inline public fun String.trim(predicate: (Char) -> Boolean): String + = (this as CharSequence).trim(predicate).toString() + +/** + * Returns a sub sequence of this char sequence having leading characters matching the [predicate] trimmed. + */ +inline public fun CharSequence.trimStart(predicate: (Char) -> Boolean): CharSequence { for (index in this.indices) if (!predicate(this[index])) - return substring(index) + return subSequence(index, length) return "" } /** - * Returns a string with trailing characters matching the [predicate] trimmed. + * Returns a string with leading characters matching the [predicate] trimmed. */ -inline public fun CharSequence.trimEnd(predicate: (Char) -> Boolean): String { +inline public fun String.trimStart(predicate: (Char) -> Boolean): String + = (this as CharSequence).trimStart(predicate).toString() + +/** + * Returns a sub sequence of this char sequence having trailing characters matching the [predicate] trimmed. + */ +inline public fun CharSequence.trimEnd(predicate: (Char) -> Boolean): CharSequence { for (index in this.indices.reversed()) if (!predicate(this[index])) return substring(0, index + 1) @@ -60,77 +72,137 @@ inline public fun CharSequence.trimEnd(predicate: (Char) -> Boolean): String { } /** - * Returns a string with leading and trailing characters in the [chars] array trimmed. + * Returns a string with trailing characters matching the [predicate] trimmed. */ -public fun CharSequence.trim(vararg chars: Char): String = trim { it in chars } +inline public fun String.trimEnd(predicate: (Char) -> Boolean): String + = (this as CharSequence).trimEnd(predicate).toString() /** - * Returns a string with leading and trailing characters in the [chars] array trimmed. + * Returns a sub sequence of this char sequence having leading and trailing characters from the [chars] array trimmed. */ -public fun CharSequence.trimStart(vararg chars: Char): String = trimStart { it in chars } +public fun CharSequence.trim(vararg chars: Char): CharSequence = trim { it in chars } /** - * Returns a string with trailing characters in the [chars] array trimmed. + * Returns a string with leading and trailing characters from the [chars] array trimmed. */ -public fun CharSequence.trimEnd(vararg chars: Char): String = trimEnd { it in chars } +public fun String.trim(vararg chars: Char): String = trim { it in chars } + +/** + * Returns a sub sequence of this char sequence having leading and trailing characters from the [chars] array trimmed. + */ +public fun CharSequence.trimStart(vararg chars: Char): CharSequence = trimStart { it in chars } + +/** + * Returns a string with leading and trailing characters from the [chars] array trimmed. + */ +public fun String.trimStart(vararg chars: Char): String = trimStart { it in chars } + +/** + * Returns a sub sequence of this char sequence having trailing characters from the [chars] array trimmed. + */ +public fun CharSequence.trimEnd(vararg chars: Char): CharSequence = trimEnd { it in chars } + +/** + * Returns a string with trailing characters from the [chars] array trimmed. + */ +public fun String.trimEnd(vararg chars: Char): String = trimEnd { it in chars } + +/** + * Returns a sub sequence of this char sequence having leading and trailing whitespace trimmed. + */ +public fun CharSequence.trim(): CharSequence = trim { it.isWhitespace() } /** * Returns a string with leading and trailing whitespace trimmed. */ -public fun CharSequence.trim(): String = trim { it.isWhitespace() } +public fun String.trim(): String = (this as CharSequence).trim().toString() + +/** + * Returns a sub sequence of this char sequence having leading whitespace removed. + */ +public fun CharSequence.trimStart(): CharSequence = trimStart { it.isWhitespace() } /** * Returns a string with leading whitespace removed. */ -public fun CharSequence.trimStart(): String = trimStart { it.isWhitespace() } +public fun String.trimStart(): String = (this as CharSequence).trimStart().toString() + +/** + * Returns a sub sequence of this char sequence having trailing whitespace removed. + */ +public fun CharSequence.trimEnd(): CharSequence = trimEnd { it.isWhitespace() } /** * Returns a string with trailing whitespace removed. */ -public fun CharSequence.trimEnd(): String = trimEnd { it.isWhitespace() } +public fun String.trimEnd(): String = (this as CharSequence).trimEnd().toString() /** - * Left pad a String with a specified character or space. + * Returns a char sequence with content of this char sequence padded at the beginning + * to the specified [length] with the specified character or space. * * @param length the desired string length. * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. * as are necessary to reach that length. */ -public fun CharSequence.padStart(length: Int, padChar: Char = ' '): String { +public fun CharSequence.padStart(length: Int, padChar: Char = ' '): CharSequence { if (length < 0) - throw IllegalArgumentException("String length $length is less than zero.") - if (length <= this.length()) - return this.toString() + throw IllegalArgumentException("Desired length $length is less than zero.") + if (length <= this.length) + return this.subSequence(0, this.length) val sb = StringBuilder(length) - for (i in 1..(length - this.length())) + for (i in 1..(length - this.length)) sb.append(padChar) sb.append(this) - return sb.toString() + return sb } /** - * Right pad a String with a specified character or space. + * Pads the string to the specified [length] at the beginning with the specified character or space. * * @param length the desired string length. * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. * as are necessary to reach that length. */ -public fun CharSequence.padEnd(length: Int, padChar: Char = ' '): String { +public fun String.padStart(length: Int, padChar: Char = ' '): String + = (this as CharSequence).padStart(length, padChar).toString() + +/** + * Returns a char sequence with content of this char sequence padded at the end + * to the specified [length] with the specified character or space. + * + * @param length the desired string length. + * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. + * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. + * as are necessary to reach that length. + */ +public fun CharSequence.padEnd(length: Int, padChar: Char = ' '): CharSequence { if (length < 0) - throw IllegalArgumentException("String length $length is less than zero.") - if (length <= this.length()) - return this.toString() + throw IllegalArgumentException("Desired length $length is less than zero.") + if (length <= this.length) + return this.subSequence(0, this.length) val sb = StringBuilder(length) sb.append(this) - for (i in 1..(length - this.length())) + for (i in 1..(length - this.length)) sb.append(padChar) - return sb.toString() + return sb } +/** + * Pads the string to the specified [length] at the end with the specified character or space. + * + * @param length the desired string length. + * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. + * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. + * as are necessary to reach that length. + */ +public fun String.padEnd(length: Int, padChar: Char = ' '): String + = (this as CharSequence).padEnd(length, padChar).toString() + /** * Returns `true` if this nullable char sequence is either `null` or empty. */ @@ -204,24 +276,6 @@ public fun CharSequence.hasSurrogatePairAt(index: Int): Boolean { && this[index + 1].isLowSurrogate() } -/** - * Returns a subsequence obtained by taking the characters at the given [indices] in this sequence. - */ -public fun CharSequence.slice(indices: Iterable): CharSequence { - val sb = StringBuilder() - for (i in indices) { - sb.append(get(i)) - } - return sb.toString() -} - -/** - * Returns a subsequence of this sequence specified by given [range]. - */ -public fun CharSequence.slice(range: IntRange): CharSequence { - return subSequence(range.start, range.end + 1) // inclusive -} - /** * Returns a substring specified by the given [range]. */ @@ -345,97 +399,172 @@ public fun String.substringAfterLast(delimiter: String, missingDelimiterValue: S } /** - * Returns a string with content of this char sequence where its part at the given range + * Returns a char sequence with content of this char sequence where its part at the given range * is replaced with the [replacement] char sequence. * @param firstIndex the index of the first character to be replaced. * @param lastIndex the index of the first character after the replacement to keep in the string. */ -public fun CharSequence.replaceRange(firstIndex: Int, lastIndex: Int, replacement: CharSequence): String { +public fun CharSequence.replaceRange(firstIndex: Int, lastIndex: Int, replacement: CharSequence): CharSequence { if (lastIndex < firstIndex) throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") val sb = StringBuilder() sb.append(this, 0, firstIndex) sb.append(replacement) sb.append(this, lastIndex, length()) - return sb.toString() + return sb } /** - * Returns a string with content of this char sequence where its part at the given [range] + * Replaces the part of the string at the given range with the [replacement] char sequence. + * @param firstIndex the index of the first character to be replaced. + * @param lastIndex the index of the first character after the replacement to keep in the string. + */ +public fun String.replaceRange(firstIndex: Int, lastIndex: Int, replacement: CharSequence): String + = (this as CharSequence).replaceRange(firstIndex, lastIndex, replacement).toString() + +/** + * Returns a char sequence with content of this char sequence where its part at the given [range] * is replaced with the [replacement] char sequence. * * The end index of the [range] is included in the part to be replaced. */ -public fun CharSequence.replaceRange(range: IntRange, replacement: CharSequence): String = replaceRange(range.start, range.end + 1, replacement) +public fun CharSequence.replaceRange(range: IntRange, replacement: CharSequence): CharSequence + = replaceRange(range.start, range.end + 1, replacement) /** - * Returns a string with content of this char sequence where its part at the given range is removed. + * Replace the part of string at the given [range] with the [replacement] string. + * + * The end index of the [range] is included in the part to be replaced. + */ +public fun String.replaceRange(range: IntRange, replacement: CharSequence): String + = replaceRange(range.start, range.end + 1, replacement) + +/** + * Returns a char sequence with content of this char sequence where its part at the given range is removed. * * @param firstIndex the index of the first character to be removed. * @param lastIndex the index of the first character after the removed part to keep in the string. * * [lastIndex] is not included in the removed part. */ -public fun CharSequence.removeRange(firstIndex: Int, lastIndex: Int): String { +public fun CharSequence.removeRange(firstIndex: Int, lastIndex: Int): CharSequence { if (lastIndex < firstIndex) throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") if (lastIndex == firstIndex) - return this.toString() + return this.subSequence(0, length) val sb = StringBuilder(length() - (lastIndex - firstIndex)) sb.append(this, 0, firstIndex) sb.append(this, lastIndex, length()) - return sb.toString() + return sb } /** - * Returns a string with content of this char sequence where its part at the given [range] is removed. + * Removes the part of a string at a given range. + * @param firstIndex the index of the first character to be removed. + * @param lastIndex the index of the first character after the removed part to keep in the string. + * + * [lastIndex] is not included in the removed part. + */ +public fun String.removeRange(firstIndex: Int, lastIndex: Int): String + = (this as CharSequence).removeRange(firstIndex, lastIndex).toString() + +/** + * Returns a char sequence with content of this char sequence where its part at the given [range] is removed. * * The end index of the [range] is included in the removed part. */ -public fun CharSequence.removeRange(range: Range): String = removeRange(range.start, range.end + 1) +public fun CharSequence.removeRange(range: IntRange): CharSequence = removeRange(range.start, range.end + 1) + +/** + * Removes the part of a string at the given [range]. + * + * The end index of the [range] is included in the removed part. + */ +public fun String.removeRange(range: IntRange): String = removeRange(range.start, range.end + 1) + +/** + * If this char sequence starts with the given [prefix], returns a new char sequence + * with the prefix removed. Otherwise, returns a new char sequence with the same characters. + */ +public fun CharSequence.removePrefix(prefix: CharSequence): CharSequence { + if (startsWith(prefix)) { + return subSequence(prefix.length, length) + } + return subSequence(0, length) +} /** * If this string starts with the given [prefix], returns a copy of this string * with the prefix removed. Otherwise, returns this string. */ -public fun String.removePrefix(prefix: String): String { +public fun String.removePrefix(prefix: CharSequence): String { if (startsWith(prefix)) { - return substring(prefix.length()) + return substring(prefix.length) } return this } +/** + * If this char sequence ends with the given [suffix], returns a new char sequence + * with the suffix removed. Otherwise, returns a new char sequence with the same characters. + */ +public fun CharSequence.removeSuffix(suffix: CharSequence): CharSequence { + if (endsWith(suffix)) { + return subSequence(0, length - suffix.length) + } + return subSequence(0, length) +} + /** * If this string ends with the given [suffix], returns a copy of this string * with the suffix removed. Otherwise, returns this string. */ -public fun String.removeSuffix(suffix: String): String { +public fun String.removeSuffix(suffix: CharSequence): String { if (endsWith(suffix)) { - return substring(0, length() - suffix.length()) + return substring(0, length - suffix.length) } return this } +/** + * When this char sequence starts with the given [prefix] and ends with the given [suffix], + * returns a new char sequence having both the given [prefix] and [suffix] removed. + * Otherwise returns a new char sequence with the same characters. + */ +public fun CharSequence.removeSurrounding(prefix: CharSequence, suffix: CharSequence): CharSequence { + if (startsWith(prefix) && endsWith(suffix)) { + return subSequence(prefix.length, length - suffix.length) + } + return subSequence(0, length) +} + /** * Removes from a string both the given [prefix] and [suffix] if and only if * it starts with the [prefix] and ends with the [suffix]. * Otherwise returns this string unchanged. */ -public fun String.removeSurrounding(prefix: String, suffix: String): String { +public fun String.removeSurrounding(prefix: CharSequence, suffix: CharSequence): String { if (startsWith(prefix) && endsWith(suffix)) { - return substring(prefix.length(), length() - suffix.length()) + return substring(prefix.length, length - suffix.length) } return this } +/** + * When this char sequence starts with and ends with the given [delimiter], + * returns a new char sequence having this [delimiter] removed both from the start and end. + * Otherwise returns a new char sequence with the same characters. + */ +public fun CharSequence.removeSurrounding(delimiter: CharSequence): CharSequence = removeSurrounding(delimiter, delimiter) + /** * Removes the given [delimiter] string from both the start and the end of this string * if and only if it starts with and ends with the [delimiter]. * Otherwise returns this string unchanged. */ -public fun String.removeSurrounding(delimiter: String): String = removeSurrounding(delimiter, delimiter) +public fun String.removeSurrounding(delimiter: CharSequence): String = removeSurrounding(delimiter, delimiter) /** * Replace part of string before the first occurrence of given delimiter with the [replacement] string. diff --git a/libraries/stdlib/src/kotlin/text/StringsDeprecated.kt b/libraries/stdlib/src/kotlin/text/StringsDeprecated.kt index f2d02a11236..95be0ee6097 100644 --- a/libraries/stdlib/src/kotlin/text/StringsDeprecated.kt +++ b/libraries/stdlib/src/kotlin/text/StringsDeprecated.kt @@ -8,140 +8,6 @@ import java.util.NoSuchElementException import kotlin.text.MatchResult import kotlin.text.Regex -/** - * Returns the string with leading and trailing characters matching the [predicate] trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -inline public fun String.trim(predicate: (Char) -> Boolean): String { - var startIndex = 0 - var endIndex = length() - 1 - var startFound = false - - while (startIndex <= endIndex) { - val index = if (!startFound) startIndex else endIndex - val match = predicate(this[index]) - - if (!startFound) { - if (!match) - startFound = true - else - startIndex += 1 - } - else { - if (!match) - break - else - endIndex -= 1 - } - } - - return substring(startIndex, endIndex + 1) -} - -/** - * Returns the string with leading characters matching the [predicate] trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -inline public fun String.trimStart(predicate: (Char) -> Boolean): String { - for (index in this.indices) - if (!predicate(this[index])) - return substring(index) - - return "" -} - -/** - * Returns the string with trailing characters matching the [predicate] trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -inline public fun String.trimEnd(predicate: (Char) -> Boolean): String { - for (index in this.indices.reversed()) - if (!predicate(this[index])) - return substring(0, index + 1) - - return "" -} - -/** - * Returns the string with leading and trailing characters in the [chars] array trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.trim(vararg chars: Char): String = trim { it in chars } - -/** - * Returns the string with leading and trailing characters in the [chars] array trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.trimStart(vararg chars: Char): String = trimStart { it in chars } - -/** - * Returns the string with trailing characters in the [chars] array trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.trimEnd(vararg chars: Char): String = trimEnd { it in chars } - -/** - * Returns a string with leading and trailing whitespace trimmed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.trim(): String = trim { it.isWhitespace() } - -/** - * Returns a string with leading whitespace removed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.trimStart(): String = trimStart { it.isWhitespace() } - -/** - * Returns a string with trailing whitespace removed. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.trimEnd(): String = trimEnd { it.isWhitespace() } - -/** - * Left pad a String with a specified character or space. - * - * @param length the desired string length. - * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. - * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. - * as are necessary to reach that length. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.padStart(length: Int, padChar: Char = ' '): String { - if (length < 0) - throw IllegalArgumentException("String length $length is less than zero.") - if (length <= this.length()) - return this - - val sb = StringBuilder(length) - for (i in 1..(length - this.length())) - sb.append(padChar) - sb.append(this) - return sb.toString() -} - -/** - * Right pad a String with a specified character or space. - * - * @param length the desired string length. - * @param padChar the character to pad string with, if it has length less than the [length] specified. Space is used by default. - * @returns Returns a string, of length at least [length], consisting of string prepended with [padChar] as many times. - * as are necessary to reach that length. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.padEnd(length: Int, padChar: Char = ' '): String { - if (length < 0) - throw IllegalArgumentException("String length $length is less than zero.") - if (length <= this.length()) - return this - - val sb = StringBuilder(length) - sb.append(this) - for (i in 1..(length - this.length())) - sb.append(padChar) - return sb.toString() -} - /** Returns `true` if the string is not `null` and not empty */ @Deprecated("Use !isNullOrEmpty() or isNullOrEmpty().not() for nullable strings.", ReplaceWith("this != null && this.isNotEmpty()"), DeprecationLevel.ERROR) @kotlin.jvm.JvmName("isNotEmptyNullable") @@ -273,69 +139,7 @@ public fun String.substringAfterLast(delimiter: String, missingDelimiterValue: S val index = lastIndexOf(delimiter) return if (index == -1) missingDelimiterValue else substring(index + delimiter.length(), length()) } -*/ -/** - * Replaces the part of the string at the given range with the [replacement] string. - * @param firstIndex the index of the first character to be replaced. - * @param lastIndex the index of the first character after the replacement to keep in the string. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.replaceRange(firstIndex: Int, lastIndex: Int, replacement: String): String { - if (lastIndex < firstIndex) - throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") - val sb = StringBuilder() - sb.append(this, 0, firstIndex) - sb.append(replacement) - sb.append(this, lastIndex, length()) - return sb.toString() -} - -/** - * Replace the part of string at the given [range] with the [replacement] string. - * - * The end index of the [range] is included in the part to be replaced. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.replaceRange(range: IntRange, replacement: String): String = replaceRange(range.start, range.end + 1, replacement) - -/** - * Removes the part of a string at a given range. - * @param firstIndex the index of the first character to be removed. - * @param lastIndex the index of the first character after the removed part to keep in the string. - * - * [lastIndex] is not included in the removed part. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.removeRange(firstIndex: Int, lastIndex: Int): String { - if (lastIndex < firstIndex) - throw IndexOutOfBoundsException("Last index ($lastIndex) is less than first index ($firstIndex)") - - if (lastIndex == firstIndex) - return this - - val sb = StringBuilder(length() - (lastIndex - firstIndex)) - sb.append(this, 0, firstIndex) - sb.append(this, lastIndex, length()) - return sb.toString() -} - -/** - * Removes the part of a string at the given [range]. - * - * The end index of the [range] is included in the removed part. - */ -@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) -public fun String.removeRange(range: IntRange): String = removeRange(range.start, range.end + 1) - -/* - -/** - * Removes the given [delimiter] string from both the start and the end of this string - * if and only if it starts with and ends with the [delimiter]. - * Otherwise returns this string unchanged. - */ -public fun String.removeSurrounding(delimiter: String): String = removeSurrounding(delimiter, delimiter) /** * Replace part of string before the first occurrence of given delimiter with the [replacement] string. @@ -410,6 +214,30 @@ public fun String.replaceBeforeLast(delimiter: String, replacement: String, miss } */ + +@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) +public fun String.replaceRange(firstIndex: Int, lastIndex: Int, replacement: String): String + = replaceRange(firstIndex, lastIndex, replacement) + +@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) +public fun String.replaceRange(range: IntRange, replacement: String): String + = replaceRange(range.start, range.end + 1, replacement) + + +@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) +public fun String.removePrefix(prefix: String): String = removePrefix(prefix) + +@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) +public fun String.removeSuffix(suffix: String): String = removeSuffix(suffix) + +@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) +public fun String.removeSurrounding(prefix: String, suffix: String): String = removeSurrounding(prefix, suffix) + +@Deprecated("Provided for binary compatibility", level = DeprecationLevel.HIDDEN) +public fun String.removeSurrounding(delimiter: String): String = removeSurrounding(delimiter, delimiter) + + + /** * Returns a new string obtained by replacing each substring of this string that matches the given regular expression * with the given [replacement]. diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index a1b6c044f11..e5bf5d29b65 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -25,8 +25,8 @@ fun withTwoCharSequenceArgs(f: ((String) -> CharSequence, (String) -> CharSequen f(arg1Builder, arg2Builder) } -fun assertContentEquals(expected: String, actual: CharSequence) { - assertEquals(expected, actual.toString()) +fun assertContentEquals(expected: String, actual: CharSequence, message: String? = null) { + assertEquals(expected, actual.toString(), message) } // helper predicates available on both platforms @@ -236,8 +236,8 @@ class StringTest { val s = arg1("sample text") val replacement = arg2("??") - assertEquals("sa??e text", s.replaceRange(2, 5, replacement)) - assertEquals("sa?? text", s.replaceRange(2..5, replacement)) + assertContentEquals("sa??e text", s.replaceRange(2, 5, replacement)) + assertContentEquals("sa?? text", s.replaceRange(2..5, replacement)) assertFails { s.replaceRange(5..2, replacement) } @@ -246,21 +246,21 @@ class StringTest { } // symmetry with indices - assertEquals(replacement.toString(), s.replaceRange(s.indices, replacement)) + assertContentEquals(replacement.toString(), s.replaceRange(s.indices, replacement)) } @test fun removeRange() = withOneCharSequenceArg("sample text") { s -> - assertEquals("sae text", s.removeRange(2, 5)) - assertEquals("sa text", s.removeRange(2..5)) + assertContentEquals("sae text", s.removeRange(2, 5)) + assertContentEquals("sa text", s.removeRange(2..5)) - assertEquals(s.toString(), s.removeRange(2,2)) + assertContentEquals(s.toString(), s.removeRange(2,2)) // symmetry with indices - assertEquals("", s.removeRange(s.indices)) + assertContentEquals("", s.removeRange(s.indices)) // symmetry with replaceRange - assertEquals(s.replaceRange(2, 5, ""), s.removeRange(2, 5)) - assertEquals(s.replaceRange(2..5, ""), s.removeRange(2..5)) + assertContentEquals(s.toString().replaceRange(2, 5, ""), s.removeRange(2, 5)) + assertContentEquals(s.toString().replaceRange(2..5, ""), s.removeRange(2..5)) } @test fun substringDelimited() { @@ -318,43 +318,43 @@ class StringTest { } @test fun trimStart() = withOneCharSequenceArg { arg1 -> - fun String.trimStart(): String = arg1(this).trimStart() - assertEquals("", "".trimStart()) - assertEquals("a", "a".trimStart()) - assertEquals("a", " a".trimStart()) - assertEquals("a", " a".trimStart()) - assertEquals("a ", " a ".trimStart()) - assertEquals("a b", " a b".trimStart()) - assertEquals("a b ", " a b ".trimStart()) - assertEquals("a", " \u00A0 a".trimStart()) + fun String.trimStartCS(): CharSequence = arg1(this).trimStart() + assertContentEquals("", "".trimStartCS()) + assertContentEquals("a", "a".trimStartCS()) + assertContentEquals("a", " a".trimStartCS()) + assertContentEquals("a", " a".trimStartCS()) + assertContentEquals("a ", " a ".trimStartCS()) + assertContentEquals("a b", " a b".trimStartCS()) + assertContentEquals("a b ", " a b ".trimStartCS()) + assertContentEquals("a", " \u00A0 a".trimStartCS()) - assertEquals("a", "\ta".trimStart()) - assertEquals("a", "\t\ta".trimStart()) - assertEquals("a", "\ra".trimStart()) - assertEquals("a", "\na".trimStart()) + assertContentEquals("a", "\ta".trimStartCS()) + assertContentEquals("a", "\t\ta".trimStartCS()) + assertContentEquals("a", "\ra".trimStartCS()) + assertContentEquals("a", "\na".trimStartCS()) - assertEquals("a=", arg1("-=-=a=").trimStart('-','=')) - assertEquals("123a", arg1("ab123a").trimStart { it < '0' || it > '9' }) // TODO: Use !it.isDigit when available in JS + assertContentEquals("a=", arg1("-=-=a=").trimStart('-','=')) + assertContentEquals("123a", arg1("ab123a").trimStart { !it.isAsciiDigit() }) } @test fun trimEnd() = withOneCharSequenceArg { arg1 -> - fun String.trimEnd(): String = arg1(this).trimEnd() - assertEquals("", "".trimEnd()) - assertEquals("a", "a".trimEnd()) - assertEquals("a", "a ".trimEnd()) - assertEquals("a", "a ".trimEnd()) - assertEquals(" a", " a ".trimEnd()) - assertEquals("a b", "a b ".trimEnd()) - assertEquals(" a b", " a b ".trimEnd()) - assertEquals("a", "a \u00A0 ".trimEnd()) + fun String.trimEndCS(): CharSequence = arg1(this).trimEnd() + assertContentEquals("", "".trimEndCS()) + assertContentEquals("a", "a".trimEndCS()) + assertContentEquals("a", "a ".trimEndCS()) + assertContentEquals("a", "a ".trimEndCS()) + assertContentEquals(" a", " a ".trimEndCS()) + assertContentEquals("a b", "a b ".trimEndCS()) + assertContentEquals(" a b", " a b ".trimEndCS()) + assertContentEquals("a", "a \u00A0 ".trimEndCS()) - assertEquals("a", "a\t".trimEnd()) - assertEquals("a", "a\t\t".trimEnd()) - assertEquals("a", "a\r".trimEnd()) - assertEquals("a", "a\n".trimEnd()) + assertContentEquals("a", "a\t".trimEndCS()) + assertContentEquals("a", "a\t\t".trimEndCS()) + assertContentEquals("a", "a\r".trimEndCS()) + assertContentEquals("a", "a\n".trimEndCS()) - assertEquals("=a", arg1("=a=-=-").trimEnd('-','=')) - assertEquals("ab123", arg1("ab123a").trimEnd { it < '0' || it > '9' }) // TODO: Use !it.isDigit when available in JS + assertContentEquals("=a", arg1("=a=-=-").trimEnd('-','=')) + assertContentEquals("ab123", arg1("ab123a").trimEnd { !it.isAsciiDigit() }) } @test fun trimStartAndEnd() = withOneCharSequenceArg { arg1 -> @@ -369,9 +369,9 @@ class StringTest { " \u00A0 a \u00A0 " ) - for (example in examples.map { arg1(it) }) { - assertEquals(example.trim(), example.trimEnd().trimStart()) - assertEquals(example.trim(), example.trimStart().trimEnd()) + for ((source, example) in examples.map { it to arg1(it) }) { + assertContentEquals(source.trimEnd().trimStart(), example.trim()) + assertContentEquals(source.trimStart().trimEnd(), example.trim()) } val examplesForPredicate = arrayOf("123", @@ -379,57 +379,88 @@ class StringTest { ) val trimChars = charArrayOf('-', '=') - val trimPredicate = { it: Char -> it < '0' || it > '9' } // TODO: Use !it.isDigit when available in JS - for (example in examplesForPredicate.map { arg1(it) }) { - assertEquals(example.trimStart(*trimChars).trimEnd(*trimChars), example.trim(*trimChars)) - assertEquals(example.trimStart(trimPredicate).trimEnd(trimPredicate), example.trim(trimPredicate)) + val trimPredicate = { it: Char -> !it.isAsciiDigit() } + for ((source, example) in examplesForPredicate.map { it to arg1(it) }) { + assertContentEquals(source.trimStart(*trimChars).trimEnd(*trimChars), example.trim(*trimChars)) + assertContentEquals(source.trimStart(trimPredicate).trimEnd(trimPredicate), example.trim(trimPredicate)) } } @test fun padStart() = withOneCharSequenceArg { arg1 -> - fun String.padStart(length: Int): String = arg1(this).padStart(length) - assertEquals("s", "s".padStart(0)) - assertEquals("s", "s".padStart(1)) - assertEquals(" ", "".padStart(2)) - assertEquals("--s", arg1("s").padStart(3, '-')) + val s = arg1("s") + assertContentEquals("s", s.padStart(0)) + assertContentEquals("s", s.padStart(1)) + assertContentEquals("--s", s.padStart(3, '-')) + assertContentEquals(" ", arg1("").padStart(2)) assertFails { - "s".padStart(-1) + s.padStart(-1) } } @test fun padEnd() = withOneCharSequenceArg { arg1 -> - fun String.padEnd(length: Int): String = arg1(this).padEnd(length) - assertEquals("s", "s".padEnd(0)) - assertEquals("s", "s".padEnd(1)) - assertEquals(" ", "".padEnd(2)) - assertEquals("s--", arg1("s").padEnd(3, '-')) + val s = arg1("s") + assertContentEquals("s", s.padEnd(0)) + assertContentEquals("s", s.padEnd(1)) + assertContentEquals("s--", s.padEnd(3, '-')) + assertContentEquals(" ", arg1("").padEnd(2)) assertFails { - "s".padEnd(-1) + s.padEnd(-1) } } - @test fun removePrefix() { - assertEquals("fix", "prefix".removePrefix("pre"), "Removes prefix") - assertEquals("prefix", "preprefix".removePrefix("pre"), "Removes prefix once") - assertEquals("sample", "sample".removePrefix("value")) + @test fun removePrefix() = withOneCharSequenceArg("pre") { prefix -> + assertEquals("fix", "prefix".removePrefix(prefix), "Removes prefix") + assertEquals("prefix", "preprefix".removePrefix(prefix), "Removes prefix once") + assertEquals("sample", "sample".removePrefix(prefix)) assertEquals("sample", "sample".removePrefix("")) } - @test fun removeSuffix() { - assertEquals("suf", "suffix".removeSuffix("fix"), "Removes suffix") - assertEquals("suffix", "suffixfix".removeSuffix("fix"), "Removes suffix once") - assertEquals("sample", "sample".removeSuffix("value")) + @test fun removeSuffix() = withOneCharSequenceArg("fix") { suffix -> + assertEquals("suf", "suffix".removeSuffix(suffix), "Removes suffix") + assertEquals("suffix", "suffixfix".removeSuffix(suffix), "Removes suffix once") + assertEquals("sample", "sample".removeSuffix(suffix)) assertEquals("sample", "sample".removeSuffix("")) } - @test fun removeSurrounding() { - assertEquals("value", "".removeSurrounding("<", ">")) - assertEquals("", "<>".removeSurrounding("<", ">"), "Removes surrounding once") - assertEquals(""), "Only removes surrounding when both prefix and suffix present") - assertEquals("value>", "value>".removeSurrounding("<", ">"), "Only removes surrounding when both prefix and suffix present") - assertEquals("value", "value".removeSurrounding("<", ">")) + @test fun removeSurrounding() = withOneCharSequenceArg { arg1 -> + val pre = arg1("<") + val post = arg1(">") + assertEquals("value", "".removeSurrounding(pre, post)) + assertEquals("", "<>".removeSurrounding(pre, post), "Removes surrounding once") + assertEquals("", "value>".removeSurrounding(pre, post), "Only removes surrounding when both prefix and suffix present") + assertEquals("value", "value".removeSurrounding(pre, post)) } + @test fun removePrefixCharSequence() = withTwoCharSequenceArgs { arg1, arg2 -> + fun String.removePrefix(prefix: String) = arg1(this).removePrefix(arg2(prefix)) + val prefix = "pre" + + assertContentEquals("fix", "prefix".removePrefix(prefix), "Removes prefix") + assertContentEquals("prefix", "preprefix".removePrefix(prefix), "Removes prefix once") + assertContentEquals("sample", "sample".removePrefix(prefix)) + assertContentEquals("sample", "sample".removePrefix("")) + } + + @test fun removeSuffixCharSequence() = withTwoCharSequenceArgs { arg1, arg2 -> + fun String.removeSuffix(suffix: String) = arg1(this).removeSuffix(arg2(suffix)) + val suffix = "fix" + + assertContentEquals("suf", "suffix".removeSuffix(suffix), "Removes suffix") + assertContentEquals("suffix", "suffixfix".removeSuffix(suffix), "Removes suffix once") + assertContentEquals("sample", "sample".removeSuffix(suffix)) + assertContentEquals("sample", "sample".removeSuffix("")) + } + + @test fun removeSurroundingCharSequence() = withTwoCharSequenceArgs { arg1, arg2 -> + fun String.removeSurrounding(prefix: String, postfix: String) = arg1(this).removeSurrounding(arg2(prefix), arg2(postfix)) + + assertContentEquals("value", "".removeSurrounding("<", ">")) + assertContentEquals("", "<>".removeSurrounding("<", ">"), "Removes surrounding once") + assertContentEquals(""), "Only removes surrounding when both prefix and suffix present") + assertContentEquals("value>", "value>".removeSurrounding("<", ">"), "Only removes surrounding when both prefix and suffix present") + assertContentEquals("value", "value".removeSurrounding("<", ">")) + } /* // unit test commented out until rangesDelimitiedBy would become public