
Enhances debugging and runtime information with features like print debugging, Unicode support, regex functions, and file handling. Offers tools for time manipulation and easy access to stack traces.
Please note that this project is now part of the Kommons library.
Below you will find the description of the last release.
Kommons Debug is a Kotlin Multiplatform Library that adds:
Now, Yesterday, and Tomorrow
of/ofOrNull, from/fromOrNull, and parse/parseOrNull
Kommons Debug is hosted on GitHub with releases provided on Maven Central.
Gradle implementation("com.bkahlert.kommons:kommons-debug:0.14.0")
Maven
<dependency>
<groupId>com.bkahlert.kommons</groupId>
<artifactId>kommons-debug</artifactId>
<version>0.14.0</version>
</dependency>Print tracing information and easily cleanup afterward using IntelliJ's code cleanup feature.
data class Foo(val bar: String = "baz") {
private val baz = 42.0
fun compute() // used to demonstrate that trace/inspect return their argument unchanged
}
Foo().trace.compute()
// output: (sample.kt:5) ⟨ Foo(bar=baz) ⟩
Foo().trace("caption").compute()
// output: (sample.kt:8) caption ⟨ Foo(bar=baz) ⟩
Foo().trace("details") { it.bar.reversed() }.compute()
// output: (sample.kt:11) details ⟨ Foo(bar=baz) ⟩ { "zab" }
Foo().inspect.compute()
// output: (sample.kt:14) ⟨ !Foo { baz: !Double 42.0, bar: !String "baz" } ⟩
Foo().inspect("caption").compute()
// output: (sample.kt:17) caption ⟨ !Foo { baz: !Double 42.0, bar: !String "baz" } ⟩
Foo().inspect("details") { it.bar.reversed() }.compute()
// output: (sample.kt:20) details ⟨ !Foo { baz: !Double 42.0, bar: !String "baz" } ⟩ { !String "zab" }The examples above also work in browsers:
Renders any object's type
"string".renderType() // String
class Foo(val bar: Any = "baz")
foo().renderType() // Foo
val lambda: (String) -> Unit = {}
lambda.renderType() // (String)->UnitRenders any object depending on whether its toString() is overridden:
toString() it's simply used.toString() the object is serialized in the form structurally"string".render() // string
class Foo(val bar: Any = "baz")
foo().render() // { bar: "baz" }
foo(foo()).render(typed = true) // Foo { bar: Foo { bar: "baz" } }
foo().asString() // { bar: "baz" }
foo(null).asString(excludeNullValues = false) // { }Renders any object as an emoji.
null.asEmoji() // "❔"
true.asEmoji() // "✅"
false.asEmoji() // "❌"
Now.asEmoji() // "🕝"
Now.asEmoji(Floor) // "🕑"
"other".asEmoji() // "🔣"Contains a map of the object's properties with each entry representing the name and value of a property.
"string".properties // { length: 6 }
class Foo(val bar: Any = "baz")
foo().properties // { bar: "baz" }
foo(foo()).properties // { bar: { bar: "baz" } }Any URL, URI, Path and File can be opened locally using open.
URL("file:///home/john/dev/project/src/jvm/kotlin/packages/source.kt").open()In order to only open the directory containing an above-mentioned resource
locate can be used.
URL("file:///home/john/dev/project/src/jvm/kotlin/packages/source.kt").locate()Reflects the running program and provides:
Program.isDebugging: Returns whether the program is running in debug mode.Program.isIntelliJ: Returns whether the program is running in IntelliJ.Program.onExit: Allows registering callbacks that are invoked when the program exits.Reflects the platform the program runs on (e.g. Platform.JVM)
and provides:
Platform.ansiSupport: Returns to what extent ANSI escape codes are supported.Platform.fileSeparator: Returns the separator used to separate path segments.Access the current stack trace by a simple call to StackTrace.get()
or locate a specific caller using StackTrace.get().findLastKnownCallOrNull.
fun foo(block: () -> StackTraceElement?) = block()
fun bar(block: () -> StackTraceElement?) = block()
foo { bar { StackTrace.findLastKnownCallOrNull("bar") } }?.function // "foo"
foo { bar { StackTrace.findLastKnownCallOrNull(::bar) } }?.function // "foo"Handling user input requires functions to handle Unicode correctly, unless you're not afraid of the following:
"👨🏾🦱".substring(0, 3) // "👨?", skin tone gone, curly hair gone
"👩👩👦👦".substring(1, 7) // "?👩?", wife gone, kids goneDecode any string to a sequence / list of code points using String.asCodePointSequence / String.toCodePointList.
Decode any string to a sequence / list of graphemes using String.asGraphemeSequence / String.toGraphemeList.
Transliterations and transforms can be done using String.transform.
"a".asCodePoint().name // "LATIN SMALL LETTER A"
"a𝕓c̳🔤".toCharArray() // "a", "?", "?", "c", "̳", "?", "?"
"a𝕓c̳🔤".toCodePointList() // "a", "𝕓", "c", "̳", "🫠"
"a𝕓c̳🔤".toGraphemeList() // "a", "𝕓", "c̳", "🫠"
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".length // 27 (= number of Java chars)
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".asText(CodePoint).length // 16 (= number of Unicode code points)
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".asText(Grapheme).length // 6 (= visually perceivable units)
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".truncate(7.characters) // "a\uD835 … 👦"
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".truncate(7.codePoints) // "a𝕓 … 👦"
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".truncate(7.graphemes) // "a𝕓 … 👨🏾🦱👩👩👦👦"
"© А-З Ä-ö-ß".transform("de_DE", "de_DE-ASCII") // "(C) A-Z AE-oe-ss"| UTF-16 | Char (Java, JavaScript, Kotlin, ...) |
Unicode Code Point |
Unicode Grapheme Cluster |
|---|---|---|---|
| \u0061 | a (LATIN SMALL LETTER A) | a | a |
| \uD835 \uDD53 |
𝕓 (MATHEMATICAL DOUBLE-STRUCK SMALL B) | 𝕓 | 𝕓 |
| \uD83E \uDEE0 \uD83C \uDDE9 |
? (HIGH SURROGATES D83E) ? (LOW SURROGATES DEE0) |
🫠 (MELTING FACE EMOJI) | 🫠 |
| \uD83C \uDDE9 |
? (HIGH SURROGATES D83C) ? (LOW SURROGATES DDE9) |
[D] (REGIONAL INDICATOR SYMBOL LETTER D) | 🇩🇪 |
| \uD83C \uDDEA |
? (HIGH SURROGATES D83C) ? (LOW SURROGATES DDEA) |
[E] (REGIONAL INDICATOR SYMBOL LETTER E) | |
| \uD83D \uDC68 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC68) |
👨 (MAN) | 👨🏾🦱 |
| \uD83C \uDFFE |
? (HIGH SURROGATES D83C) ? (LOW SURROGATES DFFE) |
🏾 (EMOJI MODIFIER FITZPATRICK TYPE-5) | |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83E \uDDB1 |
? (HIGH SURROGATES D83E) ? (LOW SURROGATES DDB1) |
🦱 (EMOJI COMPONENT CURLY HAIR) | |
| \uD83D \uDC69 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC69) |
👩 (WOMAN) | 👩👩👦👦 |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83D \uDC69 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC69) |
👩 (WOMAN) | |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83D \uDC66 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC66) |
👦 (BOY) | |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83D \uDC66 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC66) |
👦 (BOY) |
quoted: quotes and escapes an existing stringansiRemoved: removes ANSI escape sequencesspaced/startSpaced/endSpaced: adds a space before and/or after a string if there isn't already onetruncate/truncateStart/truncateEnd: truncates a string to a given lengthtoIdentifier: create an identifier from any string that resembles itrandomString: create a random string"string".quoted // "string"
"""{ bar: "baz" }""".quoted // "{ bar: \"baz\" }"
"""
line 1
"line 2"
""".quoted // "line1\n\"line2\""
"\u001B[1mbold \u001B[34mand blue\u001B[0m".ansiRemoved
// "bold and blue"
"\u001B[34m↗\u001B(B\u001B[m \u001B]8;;https://example.com\u001B\\link\u001B]8;;\u001B\\".ansiRemoved
// "↗ link"
"string".spaced // " string "
"bar".withPrefix("foo") // "foobar"
"foo bar".withPrefix("foo") // "foo bar"
"foo".withSuffix("bar") // "foobar"
"12345678901234567890".truncate() // "123456 … 567890"
"12345678901234567890".truncateStart() // " … 901234567890"
"12345678901234567890".truncateEnd() // "123456789012 … "
"1👋 xy-z".toIdentifier() // "i__xy-z3"
randomString()
// returns "Ax-212kss0-xTzy5" (16 characters by default) Capitalize / decapitalize strings using capitalize/decapitalize or
manipulate the case style using toCasesString or any of its specializations.
"fooBar".capitalize() // "FooBar"
"FooBar".decapitalize() // "fooBar"
"FooBar".toCamelCasedString() // "fooBar"
"FooBar".toPascalCasedString() // "FooBar"
"FooBar".toScreamingSnakeCasedString() // "FOO_BAR"
"FooBar".toKebabCasedString() // "foo-bar"
"FooBar".toTitleCasedString() // "Foo Bar"
enum class FooBar { FooBaz }
FooBar::class.simpleCamelCasedName // "fooBar"
FooBar::class.simplePascalCasedName // "FooBar"
FooBar::class.simpleScreamingSnakeCasedName // "FOO_BAR"
FooBar::class.simpleKebabCasedName // "foo-bar"
FooBar::class.simpleTitleCasedName // "Foo Bar"
FooBar.FooBaz.camelCasedName // "fooBaz"
FooBar.FooBaz.pascalCasedName // "FooBaz"
FooBar.FooBaz.screamingSnakeCasedName // "FOO_BAZ"
FooBar.FooBaz.kebabCasedName // "foo-baz"
FooBar.FooBaz.titleCasedName // "Foo BazEasily check edge-case with a fluent interface as does requireNotNull does:
requireNotEmpty("abc") // passes and returns "abc"
requireNotBlank(" ") // throws IllegalArgumentException
checkNotEmpty("abc") // passes and returns "abc"
checkNotBlank(" ") // throws IllegalStateException
"abc".takeIfNotEmpty() // returns "abc"
" ".takeIfNotBlank() // returns null
"abc".takeUnlessEmpty() // returns "abc"
" ".takeUnlessBlank() // returns nullRegex can be authored as follows:
Regex("foo") + Regex("bar") // Regex("foobar")
Regex("foo") + "bar" // Regex("foobar")
Regex("foo") or Regex("bar") // Regex("foo|bar")
Regex("foo") or "bar" // Regex("foo|bar")
Regex.fromLiteralAlternates( // Regex("\\[foo\\]|bar\\?")
"[foo]", "bar?"
)
Regex("foo").optional() // Regex("(?:foo)?")
Regex("foo").repeatAny() // Regex("(?:foo)*")
Regex("foo").repeatAtLeastOnce() // Regex("(?:foo)+")
Regex("foo").repeat(2, 5) // Regex("(?:foo){2,5}")
Regex("foo").group() // Regex("(?:foo)")
Regex("foo").group("name") // Regex("(?<name>foo)") Find matches easier:
// get group by name
Regex("(?<name>ba.)")
.findAll("foo bar baz")
.mapNotNull { it.groups["name"]?.value } // "bar", "baz"
// get group value by name
Regex("(?<name>ba.)")
.findAll("foo bar baz")
.map { it.groupValue("name") } // "bar", "baz"
// find all values
Regex("(?<name>ba.)")
.findAllValues("foo bar baz") // "bar", "baz"
// match URLs / URIs
Regex.UrlRegex.findAll(/* ... */)
Regex.UriRegex.findAll(/* ... */)Match multiline strings with simple glob patterns:
// matching within lines with wildcard
"foo.bar()".matchesGlob("foo.*") // ✅
// matching across lines with multiline wildcard
"""
foo
.bar()
.baz()
""".matchesGlob(
"""
foo
.**()
""".trimIndent() // ✅
)
"""
foo
.bar()
.baz()
""".matchesGlob(
"""
foo
.*()
""".trimIndent() // ❌ (* doesn't match across lines)
)Alternatively, you can use matchesCurly if you prefer SLF4J / Logback style
wildcards {} and {{}}.
Require or check emptiness of collections and arrays using requireNotEmpty
and checkNotEmpty.
Iterate any type of closed ranges using asIterable.
(-4.2..42.0)
.asIterable { it + 9 }
.map { it.toInt() } // [-4, 4, 13, 22, 31, 40]Now + 2.seconds // 2 seconds in the future
Today - 3.days // 3 days in the past
Yesterday - 2.days // 3 days in the past
Tomorrow + 1.days // the day after tomorrow
Instant.parse("1910-06-22T13:00:00Z") + 5.minutes // 1910-06-22T12:05:00Z
LocalDate.parse("1910-06-22") - 2.days // 1910-06-20
SystemLocations.Temp.createTempFile().age // < 1ms
Now.toMomentString() // "now"
(Now - 12.hours).toMomentString() // "12h ago"
(Now + 3.days).toMomentString() // "in 3d"
(Today - 1.days).toMomentString() // "yesterday"The extension functions
toHexadecimalString()toOctalString()toBinaryString()... are provided for:
ByteByteArrayIntLongUByteUByteArrayUIntULongval byteArray = byteArrayOf(0x00, 0x7f, -0x80, -0x01)
val largeByteArrayOf = byteArrayOf(-0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01)
val veryLargeByteArray = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
byteArray.map { it.toHexadecimalString() } // "00", "7f", "80", "ff"
byteArray.toHexadecimalString() // "007f80ff"
largeByteArrayOf.toHexadecimalString() // "ffffffffffffffffffffffffffffffff"
veryLargeByteArray.toHexadecimalString() // "0100000000000000000000000000000000"
byteArray.map { it.toOctalString() } // "000", "177", "200", "377"
byteArray.toOctalString() // "000177200377"
largeByteArrayOf.toOctalString() // "377377377377377377377377377377377377377377377377"
veryLargeByteArray.toOctalString() // "001000000000000000000000000000000000000000000000000"
byteArray.map { it.toBinaryString() } // "00000000", "01111111", "10000000", "11111111"
byteArray.toBinaryString() // "00000000011111111000000011111111"
largeByteArrayOf.toBinaryString() // "111111111111111111111111111...111111"
veryLargeByteArray.toBinaryString() // "00000001000000000000000000000000000...000000"Further conversions:
Int.toByteArray()Long.toByteArray()UInt.toUByteArray()ULong.toUByteArray()Compute MD5, SHA-1, and SHA-256 checksums for arbitrary files.
val file = SystemLocations.Home / ".gitconfig"
file.md5Checksum()
file.sha1Checksum()
file.sha256Checksum()The Factory interface provides
the factory builders creator, converter, and parser to easily implement
the factory methods of/ofOrNull, from/fromOrNull, and parse/parseOrNull
as shown in the following example:
data class Version(val major: Int, val minor: Int, val patch: Int) {
companion object : Parser<Version> by (parser { // The `parsing` method supports the following outcomes:
it.split('.').let { (major, minor, patch) -> // - return a `Version` instance in case of success
Version(major.toInt(), minor.toInt(), patch.toInt()) // - return `null` a generic ParsingException is thrown.
} // - If you throw an exception it will be wrapped in a ParsingException.
})
}
Version.parseOrNull("1.2.3") // returns Version(1, 2, 3)
Version.parse("invalid") // throws ParsingException: "Failed to parse "invalid" into an instance of Version"Easily access your working directory with SystemLocations.Work,
your home directory with SystemLocations.Home and your system's
temporary directory with SystemLocations.Temp.
Create files with contents in one call using
createTextFilecreateBinaryFilecreateTempTextFilecreateTempBinaryFileSafely read files with
useInputStream, useBufferedInputStream, useReader, and useBufferedReader,
and write files with
useOutputStream, useBufferedOutputStream, useWriter, and useBufferedWriter.
Find the class directory, the source directory or the source file itself of a class.
Foo::class.findClassesDirectoryOrNull() // /home/john/dev/project/build/classes/kotlin/jvm/test
Foo::class.findSourceDirectoryOrNull() // /home/john/dev/project/src/jvmTest/kotlin
Foo::class.findSourceFileOrNull() // /home/john/dev/project/src/jvmTest/kotlin/packages/source.ktAccess class path resources like any other NIO 2 path using the classpath URI scheme.
Paths.get("classpath:dir/to/resource").readText()
Paths.get("classpath:dir/to/resource").readBytes()
Paths.get("classpath:dir/to/resource").copyToDirectory(SystemLocations.Temp)
Paths.get("classpath:dir/to/resource").useBufferedReader { it.readLine() }0.5.scale(+0.5) // = +0.75 (0.5 scaled 50% closer to +1.0)
0.5.scale(-0.5) // = -0.25 (0.5 scaled 50% closer to -1.0)
4.0.scale(+0.5, -10.0..+10.0) // = +7.0 (4.0 scaled 50% closer to +10.0)
4.0.scale(-0.5, -10.0..+10.0) // = -4.0 (4.0 scaled 50% closer to -10.0) Generic either type that can be used as a replacement for Result,
i.e. in cases where the alternative value doesn't necessarily mean failure.
Available methods are:
getLeftOrThrow / getRightOrThrow
getLeftOrElse / getRightOrElse
getLeftOrDefault / getRightOrDefault
fold / mapLeft / mapRight
onLeft / onLeft
toResult / Result.toEither
val foo: Either<Foo, Bar> = Left(Foo)
foo.getLeftOrThrow() // returns Foo
foo.getRighttOrThrow() // throws an exceptionWant to contribute? Awesome! The most basic way to show your support is to star the project, or to raise issues. You can also support this project by making a PayPal donation to ensure this journey continues indefinitely!
Thanks again for your support, it is much appreciated! 🙏
MIT. See LICENSE for more details.
Please note that this project is now part of the Kommons library.
Below you will find the description of the last release.
Kommons Debug is a Kotlin Multiplatform Library that adds:
Now, Yesterday, and Tomorrow
of/ofOrNull, from/fromOrNull, and parse/parseOrNull
Kommons Debug is hosted on GitHub with releases provided on Maven Central.
Gradle implementation("com.bkahlert.kommons:kommons-debug:0.14.0")
Maven
<dependency>
<groupId>com.bkahlert.kommons</groupId>
<artifactId>kommons-debug</artifactId>
<version>0.14.0</version>
</dependency>Print tracing information and easily cleanup afterward using IntelliJ's code cleanup feature.
data class Foo(val bar: String = "baz") {
private val baz = 42.0
fun compute() // used to demonstrate that trace/inspect return their argument unchanged
}
Foo().trace.compute()
// output: (sample.kt:5) ⟨ Foo(bar=baz) ⟩
Foo().trace("caption").compute()
// output: (sample.kt:8) caption ⟨ Foo(bar=baz) ⟩
Foo().trace("details") { it.bar.reversed() }.compute()
// output: (sample.kt:11) details ⟨ Foo(bar=baz) ⟩ { "zab" }
Foo().inspect.compute()
// output: (sample.kt:14) ⟨ !Foo { baz: !Double 42.0, bar: !String "baz" } ⟩
Foo().inspect("caption").compute()
// output: (sample.kt:17) caption ⟨ !Foo { baz: !Double 42.0, bar: !String "baz" } ⟩
Foo().inspect("details") { it.bar.reversed() }.compute()
// output: (sample.kt:20) details ⟨ !Foo { baz: !Double 42.0, bar: !String "baz" } ⟩ { !String "zab" }The examples above also work in browsers:
Renders any object's type
"string".renderType() // String
class Foo(val bar: Any = "baz")
foo().renderType() // Foo
val lambda: (String) -> Unit = {}
lambda.renderType() // (String)->UnitRenders any object depending on whether its toString() is overridden:
toString() it's simply used.toString() the object is serialized in the form structurally"string".render() // string
class Foo(val bar: Any = "baz")
foo().render() // { bar: "baz" }
foo(foo()).render(typed = true) // Foo { bar: Foo { bar: "baz" } }
foo().asString() // { bar: "baz" }
foo(null).asString(excludeNullValues = false) // { }Renders any object as an emoji.
null.asEmoji() // "❔"
true.asEmoji() // "✅"
false.asEmoji() // "❌"
Now.asEmoji() // "🕝"
Now.asEmoji(Floor) // "🕑"
"other".asEmoji() // "🔣"Contains a map of the object's properties with each entry representing the name and value of a property.
"string".properties // { length: 6 }
class Foo(val bar: Any = "baz")
foo().properties // { bar: "baz" }
foo(foo()).properties // { bar: { bar: "baz" } }Any URL, URI, Path and File can be opened locally using open.
URL("file:///home/john/dev/project/src/jvm/kotlin/packages/source.kt").open()In order to only open the directory containing an above-mentioned resource
locate can be used.
URL("file:///home/john/dev/project/src/jvm/kotlin/packages/source.kt").locate()Reflects the running program and provides:
Program.isDebugging: Returns whether the program is running in debug mode.Program.isIntelliJ: Returns whether the program is running in IntelliJ.Program.onExit: Allows registering callbacks that are invoked when the program exits.Reflects the platform the program runs on (e.g. Platform.JVM)
and provides:
Platform.ansiSupport: Returns to what extent ANSI escape codes are supported.Platform.fileSeparator: Returns the separator used to separate path segments.Access the current stack trace by a simple call to StackTrace.get()
or locate a specific caller using StackTrace.get().findLastKnownCallOrNull.
fun foo(block: () -> StackTraceElement?) = block()
fun bar(block: () -> StackTraceElement?) = block()
foo { bar { StackTrace.findLastKnownCallOrNull("bar") } }?.function // "foo"
foo { bar { StackTrace.findLastKnownCallOrNull(::bar) } }?.function // "foo"Handling user input requires functions to handle Unicode correctly, unless you're not afraid of the following:
"👨🏾🦱".substring(0, 3) // "👨?", skin tone gone, curly hair gone
"👩👩👦👦".substring(1, 7) // "?👩?", wife gone, kids goneDecode any string to a sequence / list of code points using String.asCodePointSequence / String.toCodePointList.
Decode any string to a sequence / list of graphemes using String.asGraphemeSequence / String.toGraphemeList.
Transliterations and transforms can be done using String.transform.
"a".asCodePoint().name // "LATIN SMALL LETTER A"
"a𝕓c̳🔤".toCharArray() // "a", "?", "?", "c", "̳", "?", "?"
"a𝕓c̳🔤".toCodePointList() // "a", "𝕓", "c", "̳", "🫠"
"a𝕓c̳🔤".toGraphemeList() // "a", "𝕓", "c̳", "🫠"
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".length // 27 (= number of Java chars)
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".asText(CodePoint).length // 16 (= number of Unicode code points)
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".asText(Grapheme).length // 6 (= visually perceivable units)
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".truncate(7.characters) // "a\uD835 … 👦"
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".truncate(7.codePoints) // "a𝕓 … 👦"
"a𝕓🫠🇩🇪👨🏾🦱👩👩👦👦".truncate(7.graphemes) // "a𝕓 … 👨🏾🦱👩👩👦👦"
"© А-З Ä-ö-ß".transform("de_DE", "de_DE-ASCII") // "(C) A-Z AE-oe-ss"| UTF-16 | Char (Java, JavaScript, Kotlin, ...) |
Unicode Code Point |
Unicode Grapheme Cluster |
|---|---|---|---|
| \u0061 | a (LATIN SMALL LETTER A) | a | a |
| \uD835 \uDD53 |
𝕓 (MATHEMATICAL DOUBLE-STRUCK SMALL B) | 𝕓 | 𝕓 |
| \uD83E \uDEE0 \uD83C \uDDE9 |
? (HIGH SURROGATES D83E) ? (LOW SURROGATES DEE0) |
🫠 (MELTING FACE EMOJI) | 🫠 |
| \uD83C \uDDE9 |
? (HIGH SURROGATES D83C) ? (LOW SURROGATES DDE9) |
[D] (REGIONAL INDICATOR SYMBOL LETTER D) | 🇩🇪 |
| \uD83C \uDDEA |
? (HIGH SURROGATES D83C) ? (LOW SURROGATES DDEA) |
[E] (REGIONAL INDICATOR SYMBOL LETTER E) | |
| \uD83D \uDC68 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC68) |
👨 (MAN) | 👨🏾🦱 |
| \uD83C \uDFFE |
? (HIGH SURROGATES D83C) ? (LOW SURROGATES DFFE) |
🏾 (EMOJI MODIFIER FITZPATRICK TYPE-5) | |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83E \uDDB1 |
? (HIGH SURROGATES D83E) ? (LOW SURROGATES DDB1) |
🦱 (EMOJI COMPONENT CURLY HAIR) | |
| \uD83D \uDC69 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC69) |
👩 (WOMAN) | 👩👩👦👦 |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83D \uDC69 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC69) |
👩 (WOMAN) | |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83D \uDC66 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC66) |
👦 (BOY) | |
| \u200D | [ZWJ] (ZERO WIDTH JOINER) | [ZWJ] (ZERO WIDTH JOINER) | |
| \uD83D \uDC66 |
? (HIGH SURROGATES D83D) ? (LOW SURROGATES DC66) |
👦 (BOY) |
quoted: quotes and escapes an existing stringansiRemoved: removes ANSI escape sequencesspaced/startSpaced/endSpaced: adds a space before and/or after a string if there isn't already onetruncate/truncateStart/truncateEnd: truncates a string to a given lengthtoIdentifier: create an identifier from any string that resembles itrandomString: create a random string"string".quoted // "string"
"""{ bar: "baz" }""".quoted // "{ bar: \"baz\" }"
"""
line 1
"line 2"
""".quoted // "line1\n\"line2\""
"\u001B[1mbold \u001B[34mand blue\u001B[0m".ansiRemoved
// "bold and blue"
"\u001B[34m↗\u001B(B\u001B[m \u001B]8;;https://example.com\u001B\\link\u001B]8;;\u001B\\".ansiRemoved
// "↗ link"
"string".spaced // " string "
"bar".withPrefix("foo") // "foobar"
"foo bar".withPrefix("foo") // "foo bar"
"foo".withSuffix("bar") // "foobar"
"12345678901234567890".truncate() // "123456 … 567890"
"12345678901234567890".truncateStart() // " … 901234567890"
"12345678901234567890".truncateEnd() // "123456789012 … "
"1👋 xy-z".toIdentifier() // "i__xy-z3"
randomString()
// returns "Ax-212kss0-xTzy5" (16 characters by default) Capitalize / decapitalize strings using capitalize/decapitalize or
manipulate the case style using toCasesString or any of its specializations.
"fooBar".capitalize() // "FooBar"
"FooBar".decapitalize() // "fooBar"
"FooBar".toCamelCasedString() // "fooBar"
"FooBar".toPascalCasedString() // "FooBar"
"FooBar".toScreamingSnakeCasedString() // "FOO_BAR"
"FooBar".toKebabCasedString() // "foo-bar"
"FooBar".toTitleCasedString() // "Foo Bar"
enum class FooBar { FooBaz }
FooBar::class.simpleCamelCasedName // "fooBar"
FooBar::class.simplePascalCasedName // "FooBar"
FooBar::class.simpleScreamingSnakeCasedName // "FOO_BAR"
FooBar::class.simpleKebabCasedName // "foo-bar"
FooBar::class.simpleTitleCasedName // "Foo Bar"
FooBar.FooBaz.camelCasedName // "fooBaz"
FooBar.FooBaz.pascalCasedName // "FooBaz"
FooBar.FooBaz.screamingSnakeCasedName // "FOO_BAZ"
FooBar.FooBaz.kebabCasedName // "foo-baz"
FooBar.FooBaz.titleCasedName // "Foo BazEasily check edge-case with a fluent interface as does requireNotNull does:
requireNotEmpty("abc") // passes and returns "abc"
requireNotBlank(" ") // throws IllegalArgumentException
checkNotEmpty("abc") // passes and returns "abc"
checkNotBlank(" ") // throws IllegalStateException
"abc".takeIfNotEmpty() // returns "abc"
" ".takeIfNotBlank() // returns null
"abc".takeUnlessEmpty() // returns "abc"
" ".takeUnlessBlank() // returns nullRegex can be authored as follows:
Regex("foo") + Regex("bar") // Regex("foobar")
Regex("foo") + "bar" // Regex("foobar")
Regex("foo") or Regex("bar") // Regex("foo|bar")
Regex("foo") or "bar" // Regex("foo|bar")
Regex.fromLiteralAlternates( // Regex("\\[foo\\]|bar\\?")
"[foo]", "bar?"
)
Regex("foo").optional() // Regex("(?:foo)?")
Regex("foo").repeatAny() // Regex("(?:foo)*")
Regex("foo").repeatAtLeastOnce() // Regex("(?:foo)+")
Regex("foo").repeat(2, 5) // Regex("(?:foo){2,5}")
Regex("foo").group() // Regex("(?:foo)")
Regex("foo").group("name") // Regex("(?<name>foo)") Find matches easier:
// get group by name
Regex("(?<name>ba.)")
.findAll("foo bar baz")
.mapNotNull { it.groups["name"]?.value } // "bar", "baz"
// get group value by name
Regex("(?<name>ba.)")
.findAll("foo bar baz")
.map { it.groupValue("name") } // "bar", "baz"
// find all values
Regex("(?<name>ba.)")
.findAllValues("foo bar baz") // "bar", "baz"
// match URLs / URIs
Regex.UrlRegex.findAll(/* ... */)
Regex.UriRegex.findAll(/* ... */)Match multiline strings with simple glob patterns:
// matching within lines with wildcard
"foo.bar()".matchesGlob("foo.*") // ✅
// matching across lines with multiline wildcard
"""
foo
.bar()
.baz()
""".matchesGlob(
"""
foo
.**()
""".trimIndent() // ✅
)
"""
foo
.bar()
.baz()
""".matchesGlob(
"""
foo
.*()
""".trimIndent() // ❌ (* doesn't match across lines)
)Alternatively, you can use matchesCurly if you prefer SLF4J / Logback style
wildcards {} and {{}}.
Require or check emptiness of collections and arrays using requireNotEmpty
and checkNotEmpty.
Iterate any type of closed ranges using asIterable.
(-4.2..42.0)
.asIterable { it + 9 }
.map { it.toInt() } // [-4, 4, 13, 22, 31, 40]Now + 2.seconds // 2 seconds in the future
Today - 3.days // 3 days in the past
Yesterday - 2.days // 3 days in the past
Tomorrow + 1.days // the day after tomorrow
Instant.parse("1910-06-22T13:00:00Z") + 5.minutes // 1910-06-22T12:05:00Z
LocalDate.parse("1910-06-22") - 2.days // 1910-06-20
SystemLocations.Temp.createTempFile().age // < 1ms
Now.toMomentString() // "now"
(Now - 12.hours).toMomentString() // "12h ago"
(Now + 3.days).toMomentString() // "in 3d"
(Today - 1.days).toMomentString() // "yesterday"The extension functions
toHexadecimalString()toOctalString()toBinaryString()... are provided for:
ByteByteArrayIntLongUByteUByteArrayUIntULongval byteArray = byteArrayOf(0x00, 0x7f, -0x80, -0x01)
val largeByteArrayOf = byteArrayOf(-0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01, -0x01)
val veryLargeByteArray = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
byteArray.map { it.toHexadecimalString() } // "00", "7f", "80", "ff"
byteArray.toHexadecimalString() // "007f80ff"
largeByteArrayOf.toHexadecimalString() // "ffffffffffffffffffffffffffffffff"
veryLargeByteArray.toHexadecimalString() // "0100000000000000000000000000000000"
byteArray.map { it.toOctalString() } // "000", "177", "200", "377"
byteArray.toOctalString() // "000177200377"
largeByteArrayOf.toOctalString() // "377377377377377377377377377377377377377377377377"
veryLargeByteArray.toOctalString() // "001000000000000000000000000000000000000000000000000"
byteArray.map { it.toBinaryString() } // "00000000", "01111111", "10000000", "11111111"
byteArray.toBinaryString() // "00000000011111111000000011111111"
largeByteArrayOf.toBinaryString() // "111111111111111111111111111...111111"
veryLargeByteArray.toBinaryString() // "00000001000000000000000000000000000...000000"Further conversions:
Int.toByteArray()Long.toByteArray()UInt.toUByteArray()ULong.toUByteArray()Compute MD5, SHA-1, and SHA-256 checksums for arbitrary files.
val file = SystemLocations.Home / ".gitconfig"
file.md5Checksum()
file.sha1Checksum()
file.sha256Checksum()The Factory interface provides
the factory builders creator, converter, and parser to easily implement
the factory methods of/ofOrNull, from/fromOrNull, and parse/parseOrNull
as shown in the following example:
data class Version(val major: Int, val minor: Int, val patch: Int) {
companion object : Parser<Version> by (parser { // The `parsing` method supports the following outcomes:
it.split('.').let { (major, minor, patch) -> // - return a `Version` instance in case of success
Version(major.toInt(), minor.toInt(), patch.toInt()) // - return `null` a generic ParsingException is thrown.
} // - If you throw an exception it will be wrapped in a ParsingException.
})
}
Version.parseOrNull("1.2.3") // returns Version(1, 2, 3)
Version.parse("invalid") // throws ParsingException: "Failed to parse "invalid" into an instance of Version"Easily access your working directory with SystemLocations.Work,
your home directory with SystemLocations.Home and your system's
temporary directory with SystemLocations.Temp.
Create files with contents in one call using
createTextFilecreateBinaryFilecreateTempTextFilecreateTempBinaryFileSafely read files with
useInputStream, useBufferedInputStream, useReader, and useBufferedReader,
and write files with
useOutputStream, useBufferedOutputStream, useWriter, and useBufferedWriter.
Find the class directory, the source directory or the source file itself of a class.
Foo::class.findClassesDirectoryOrNull() // /home/john/dev/project/build/classes/kotlin/jvm/test
Foo::class.findSourceDirectoryOrNull() // /home/john/dev/project/src/jvmTest/kotlin
Foo::class.findSourceFileOrNull() // /home/john/dev/project/src/jvmTest/kotlin/packages/source.ktAccess class path resources like any other NIO 2 path using the classpath URI scheme.
Paths.get("classpath:dir/to/resource").readText()
Paths.get("classpath:dir/to/resource").readBytes()
Paths.get("classpath:dir/to/resource").copyToDirectory(SystemLocations.Temp)
Paths.get("classpath:dir/to/resource").useBufferedReader { it.readLine() }0.5.scale(+0.5) // = +0.75 (0.5 scaled 50% closer to +1.0)
0.5.scale(-0.5) // = -0.25 (0.5 scaled 50% closer to -1.0)
4.0.scale(+0.5, -10.0..+10.0) // = +7.0 (4.0 scaled 50% closer to +10.0)
4.0.scale(-0.5, -10.0..+10.0) // = -4.0 (4.0 scaled 50% closer to -10.0) Generic either type that can be used as a replacement for Result,
i.e. in cases where the alternative value doesn't necessarily mean failure.
Available methods are:
getLeftOrThrow / getRightOrThrow
getLeftOrElse / getRightOrElse
getLeftOrDefault / getRightOrDefault
fold / mapLeft / mapRight
onLeft / onLeft
toResult / Result.toEither
val foo: Either<Foo, Bar> = Left(Foo)
foo.getLeftOrThrow() // returns Foo
foo.getRighttOrThrow() // throws an exceptionWant to contribute? Awesome! The most basic way to show your support is to star the project, or to raise issues. You can also support this project by making a PayPal donation to ensure this journey continues indefinitely!
Thanks again for your support, it is much appreciated! 🙏
MIT. See LICENSE for more details.