From 84ef068f5d91fb51ea0df8e68af2b14634901ce6 Mon Sep 17 00:00:00 2001 From: Sergey Mashkov Date: Wed, 12 Aug 2015 16:04:02 +0300 Subject: [PATCH] #KT-8708 refactor trimIndent, trimMargin, replaceIndent, replaceIndentByMargin, prependIndent --- libraries/stdlib/src/kotlin/text/Indent.kt | 115 ++++++++++++++++++++ libraries/stdlib/src/kotlin/text/Strings.kt | 81 -------------- libraries/stdlib/test/text/StringTest.kt | 14 ++- 3 files changed, 126 insertions(+), 84 deletions(-) create mode 100644 libraries/stdlib/src/kotlin/text/Indent.kt diff --git a/libraries/stdlib/src/kotlin/text/Indent.kt b/libraries/stdlib/src/kotlin/text/Indent.kt new file mode 100644 index 00000000000..578bf86202c --- /dev/null +++ b/libraries/stdlib/src/kotlin/text/Indent.kt @@ -0,0 +1,115 @@ +package kotlin + +/** + * Trims leading whitespace characters followed by [marginPrefix] from every line of a source string and removes + * first and last lines if they are blank (notice difference blank vs empty). Doesn't affect line if it doesn't contain + * [marginPrefix] except first and last blank lines + * + * Do not preserves original line endings + * + * [marginPrefix] should be non-blank string + * + * Example + * ```kotlin + * assertEquals("ABC\n123\n456", """ABC + * |123 + * |456""".trimMargin()) + * ``` + * + * @param marginPrefix characters to be used as a margin delimiter. Default is `|` (pipe character) + * @return the trimmed String + * @see kotlin.isWhitespace + * @since M13 + */ +public fun String.trimMargin(marginPrefix: String = "|"): String = + replaceIndentByMargin("", marginPrefix) + +/** + * Detects indent by [marginPrefix] as it does [trimMargin] and replace it with [newIndent] + */ +public fun String.replaceIndentByMargin(newIndent: String = "", marginPrefix: String = "|"): String { + require(marginPrefix.isNotBlank()) { "marginPrefix should be non blank string but it is '$marginPrefix'" } + val lines = lines() + + return lines.reindent(length() + newIndent.length() * lines.size(), getIndentFunction(newIndent)) { line -> + val firstNonWhitespaceIndex = line.indexOfFirst { !it.isWhitespace() } + + when { + firstNonWhitespaceIndex == -1 -> null + line.startsWith(marginPrefix, firstNonWhitespaceIndex) -> line.substring(firstNonWhitespaceIndex + marginPrefix.length()) + else -> null + } + } +} + +/** + * Detects a common minimal indent of all the input lines, removes it from every line and also removes first and last + * lines if they are blank (notice difference blank vs empty). + * Note that blank lines do not affect detected indent level. + * Please keep in mind that if there are non-blank lines with no leading whitespace characters (no indent at all) then the + * common indent is 0 so this function may do nothing so it is recommended to keep first line empty (will be dropped). + * + * Do not preserves original line endings + * + * Example + * ```kotlin + * assertEquals("ABC\n123\n456", """ + * ABC + * 123 + * 456""".trimMargin()) + * ``` + * + * @return deindented String + * @see kotlin.isBlank + * @since M13 + */ +public fun String.trimIndent(): String = replaceIndent("") + +/** + * Detects a common minimal indent like it does [trimIndent] and replaces it with the specified [newIndent] + * @since M13 + */ +public fun String.replaceIndent(newIndent: String = ""): String { + val lines = lines() + + val minCommonIndent = lines + .filter { it.isNotBlank() } + .map { it.indentWidth() } + .min() ?: 0 + + return lines.reindent(length() + newIndent.length() * lines.size(), getIndentFunction(newIndent)) { line -> line.drop(minCommonIndent) } +} + +/** + * Prepends [indent] to every line of the original string. Does not preserve original line endings + */ +public fun String.prependIndent(indent: String = " "): String = + lineSequence() + .map { + when { + it.isBlank() -> { + when { + it.length() < indent.length() -> indent + else -> it + } + } + else -> indent + it + } + } + .joinToString("\n") + +private fun String.indentWidth(): Int = indexOfFirst { !it.isWhitespace() }.let { if (it == -1) length() else it } + +private fun getIndentFunction(indent: String) = when { + indent.isEmpty() -> { line: String -> line } + else -> { line: String -> "$indent$line" } +} + +private inline fun List.reindent(resultSizeEstimate: Int, indentAddFunction: (String) -> String, indentCutFunction: (String) -> String?) = lastIndex.let { lastLineIndex -> + withIndex() + .dropWhile { it.index == 0 && it.value.isBlank() } + .takeWhile { it.index != lastLineIndex || it.value.isNotBlank() } + .map { indentCutFunction(it.value)?.let { cutted -> indentAddFunction(cutted) } ?: it.value } + .joinTo(StringBuilder(resultSizeEstimate), "\n") + .toString() +} diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index 2889613e8fb..29205c5387b 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -1,6 +1,5 @@ package kotlin -import java.lang import java.util.NoSuchElementException import kotlin.platform.platformName import kotlin.text.MatchResult @@ -792,86 +791,6 @@ public fun String.contains(seq: CharSequence, ignoreCase: Boolean = false): Bool public fun String.contains(char: Char, ignoreCase: Boolean = false): Boolean = indexOf(char, ignoreCase = ignoreCase) >= 0 -private fun getIndentFunction(indent: String) = when { - indent.isEmpty() -> { line: String -> line } - else -> { line: String -> "$indent$line" } -} - -private inline fun List.reindent(length: Int, indentAddFunction: (String) -> String, indentCutFunction: (String) -> String) = lastIndex.let { lastLineIndex -> - withIndex() - .dropWhile { it.index == 0 && it.value.isBlank() } - .takeWhile { it.index != lastLineIndex || it.value.isNotBlank() } - .map { indentCutFunction(it.value) } - .map(indentAddFunction) - .joinTo(StringBuilder(length), "\n") - .toString() -} - -/** - * Trims leading whitespace characters followed by [marginPrefix] from every line of a source string and removes - * first and last lines if they are blank (notice difference blank vs empty). - * Do not preserves original line endings - * - * Example - * ```kotlin - * assertEquals("ABC\n123\n456", """ABC - * |123 - * |456""".trimMargin()) - * ``` - * - * @param marginPrefix characters to be used as a margin delimiter. Default is `|` (pipe character) - * @return the trimmed String - * @see kotlin.isWhitespace - * @since M13 - */ -public fun String.trimMargin(marginPrefix: String = "|", newIndent: String = ""): String { - require(marginPrefix.isNotBlank()) { "marginPrefix should be non blank string but it is '$marginPrefix'" } - - return lines().reindent(length(), getIndentFunction(newIndent)) { line -> - val firstNonWhitespaceIndex = line.indexOfFirst { !it.isWhitespace() } - - when { - firstNonWhitespaceIndex == -1 -> line - line.startsWith(marginPrefix, firstNonWhitespaceIndex) -> line.substring(firstNonWhitespaceIndex + marginPrefix.length()) - else -> line - } - } -} - -/** - * Detects a common minimal indent of all the input lines, removes it from every line and also removes first and last - * lines if they are blank (notice difference blank vs empty). - * Note that blank lines do not affect detected indent level. - * Please keep in mind that if there are non-blank lines with no leading whitespace characters (no indent at all) then the - * common indent is 0 so this function may do nothing so it is recommended to keep first line empty (will be dropped). - * - * Do not preserves original line endings - * - * Example - * ```kotlin - * assertEquals("ABC\n123\n456", """ - * ABC - * 123 - * 456""".trimMargin()) - * ``` - * - * @return deindented String - * @see kotlin.isBlank - * @since M13 - */ -public fun String.trimIndent(newIndent: String = ""): String { - val lines = lines() - - val minCommonIndent = lines - .filter { it.isNotBlank() } - .map { it.indentWidth() } - .min() ?: 0 - - return lines.reindent(length(), getIndentFunction(newIndent)) { line -> line.drop(minCommonIndent) } -} - -private fun String.indentWidth(): Int = indexOfFirst { !it.isWhitespace() }.let { if (it == -1) length() else it } - // rangesDelimitedBy diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index a83122e47a3..9a32cb17e15 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -566,9 +566,9 @@ class StringTest { |123 |456""".trimMargin()) - assertEquals(" ABC\n 123\n 456", """ABC + assertEquals("ABC\n 123\n 456", """ABC |123 - |456""".trimMargin(newIndent = " ")) + |456""".replaceIndentByMargin(newIndent = " ")) assertEquals("ABC \n123\n456", """ABC${" "} |123 @@ -626,7 +626,7 @@ class StringTest { assertEquals(" 123\n 456", """ 123 456 - """.trimIndent(newIndent = " ")) + """.replaceIndent(newIndent = " ")) assertEquals(" 123\n456", """ 123 @@ -668,4 +668,12 @@ ${" "} assertEquals(42, indents.max()) assertEquals(1, deindented.lines().count { it.isEmpty() }) } + + test fun testIndent() { + assertEquals(" ABC\n 123", "ABC\n123".prependIndent(" ")) + assertEquals(" ABC\n \n 123", "ABC\n\n123".prependIndent(" ")) + assertEquals(" ABC\n \n 123", "ABC\n \n123".prependIndent(" ")) + assertEquals(" ABC\n \n 123", "ABC\n \n123".prependIndent(" ")) + assertEquals(" ", "".prependIndent(" ")) + } }