com.kamelia.sprinkler.util.Interpolations.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utils Show documentation
Show all versions of utils Show documentation
Sprinkler@utils | Black Kamelia
The newest version!
@file:JvmName("Interpolations")
package com.kamelia.sprinkler.util
import org.intellij.lang.annotations.Language
/**
* Interpolates variables in this string using the given [resolver]. This function replaces all sequence of characters
* placed between two specific sequences of characters defined as start and end delimiters. The value used to replace
* these sequences is resolved using the [resolver] parameter which maps variable names to their values thanks to the
* [context] parameter.
*
* Any sequence that matches the start or end delimiter (encountered after the start delimiter) are considered as such.
* To escape a delimiter, it must be preceded by a backslash (`\`).
*
* This function can be used as follows:
*
* ```kt
* val resolver: VariableResolver = ...
* val context: MyContext = ...
* val result = "Hello {{name}}, you are {{age}} years old".interpolate(context, resolver = resolver)
* ```
*
* There are several overloads of this function that can be used to interpolate variables using different types of
* [context] like a map, a list, a vararg, etc.
*
* **NOTE**: Depending on the implementation of [VariableResolver] used, this function may throw an
* [IllegalArgumentException] if a variable name is invalid for the given [VariableResolver].
*
* @receiver the string to interpolate
* @param context the context to use for resolving the variable
* @param delimiter the delimitation of the variable (defaults to [VariableDelimiter.default])
* @param resolver the [VariableResolver] to use for resolving variable names
* @return the interpolated string
* @throws IllegalArgumentException if a variable name is invalid for the given [VariableResolver]
* @see VariableResolver
* @see VariableDelimiter
*/
@JvmOverloads
fun String.interpolate(
context: T,
delimiter: VariableDelimiter = VariableDelimiter.default,
resolver: VariableResolver,
): String {
// this function is a copy of the kotlin.text.Regex#replace(CharSequence,(MatchResult) -> CharSequence) function
// the code is pasted here to avoid variable capture in a lambda which would be created for each call to this
var match: MatchResult? = delimiter.regex.find(this) ?: return this
var lastStart = 0
val length = length
val sb = StringBuilder(length)
do {
val foundMatch = match!!
sb.append(this, lastStart, foundMatch.range.first)
// lambda instantiation avoided here
val value = resolver.resolve(foundMatch.groupValues[1], context)
sb.append(value)
lastStart = foundMatch.range.last + 1
match = foundMatch.next()
} while (lastStart < length && match != null)
if (lastStart < length) {
sb.append(this, lastStart, length)
}
return sb.toString()
}
/**
* Interpolates variables in this string using the given vararg [args].
*
* Variables are resolved by their index in the given [args]. The variable passed to is parsed as an integer, and the
* value at the corresponding index in the [array][args] is returned.
*
* The following rules apply to variable names:
* - Name must be a **valid integer** ;
* - The **index** specified in the name must be in between **0** and the **number of arguments in [args]** - 1.
*
* Any string that does not conform to these rules is considered invalid, a call to this function with an invalid string
* will result in an [IllegalArgumentException] being thrown.
*
* This function can be used as follows:
*
* ```kt
* val result = "Hello {{0}}, you are {{1}} years old".interpolateIdx("John", 42)
* ```
*
* **NOTE**: The [interpolateIdx] function is an overload of this function that uses the
* [default][VariableDelimiter.default] [VariableDelimiter].
*
* @receiver the string to interpolate
* @param delimiter the delimitation of the variable
* @param args the vararg of values
* @return the interpolated string
* @throws IllegalArgumentException if a variable name does not conform to the rules defined above
* @see VariableResolver.fromArray
*/
fun String.interpolateIdxD(delimiter: VariableDelimiter, vararg args: Any): String =
interpolate(args, delimiter, VariableResolver.fromArray())
/**
* Overload of [String.interpolateIdxD] that uses the [default][VariableDelimiter.default] [VariableDelimiter].
*
* @receiver the string to interpolate
* @param args the vararg of values
* @return the interpolated string
* @throws IllegalArgumentException if a variable name does not conform to the rules defined in [String.interpolateIdx]
* @see VariableResolver.fromArray
* @see interpolateIdxD
*/
fun String.interpolateIdx(vararg args: Any): String = interpolate(args, resolver = VariableResolver.fromArray())
/**
* Interpolates variables in this string using the given vararg [args], converted to an [Iterator].
*
* Variables are resolved in the order they appear in the string, using the [Iterator.next] method to get the next
* value. If the iterator has no more elements, an [IllegalArgumentException] is thrown.
*
* This function can be used as follows:
* ```kt
* val result = "Hello {{}}, {{}}, {{}}".interpolateIdxIt("John", 42, "foo")
* ```
*
* @receiver the string to interpolate
* @param delimiter the delimitation of the variable
* @param args the vararg of values
* @return the interpolated string
* @throws IllegalArgumentException if the iterator has no more elements and a variable is found
* @see VariableResolver.fromIterator
*/
fun String.interpolateItD(delimiter: VariableDelimiter, vararg args: Any): String =
interpolate(args.iterator(), delimiter, VariableResolver.fromIterator())
/**
* Overload of [String.interpolateItD] that uses the [default][VariableDelimiter.default] [VariableDelimiter].
*
* @receiver the string to interpolate
* @param args the vararg of values
* @return the interpolated string
* @throws IllegalArgumentException if the iterator has no more elements and a variable is found
* @see VariableResolver.fromIterator
* @see interpolateItD
*/
fun String.interpolateIt(vararg args: Any): String =
interpolate(args.iterator(), resolver = VariableResolver.fromIterator())
/**
* Interpolates variables in this string using the given map of [args].
*
* Variables are resolved by their name in the given [map][args]. The name of the variable passed to is used as a key in
* the [map][args], and the value associated with that key is returned. If a variable name is unknown, an
* [IllegalArgumentException] is thrown.
*
* It can be used as follows:
*
* ```kt
* val result = "Hello {{name}}, you are {{age}} years old".interpolate(mapOf("name" to "John", "age" to 42))
* ```
*
* @param args the map of values
* @param delimiter the delimitation of the variable (defaults to [VariableDelimiter.default])
* @return the interpolated string
* @throws IllegalArgumentException if a variable name is unknown
* @see VariableResolver.fromMap
*/
@JvmOverloads
fun String.interpolate(
args: Map,
delimiter: VariableDelimiter = VariableDelimiter.default,
): String =
interpolate(args, delimiter, VariableResolver.fromMap())
/**
* Interpolates variables in this string using the given [Pair] array [args]. The array of pairs is converted to a
* [map][Map].
*
* Variables are resolved by their name represented by the [first][Pair.first] value of each pair. The name of the
* variable passed to is used as a key in the map created from the [array][args] and the value associated with that key
* is returned. If a variable name is unknown, an [IllegalArgumentException] is thrown.
*
* Strings must follow the same rules as defined in [String.interpolate].
*
* It can be used as follows:
*
* ```kt
* val result = "Hello {{name}}, you are {{age}} years old".interpolate("name" to "John", "age" to 42)
* ```
*
* @param args the array of pairs
* @param delimiter the delimitation of the variable (defaults to [VariableDelimiter.default])
* @return the interpolated string
* @throws IllegalArgumentException if a variable name is unknown
* @see VariableResolver.fromMap
*/
@JvmOverloads
fun String.interpolate(
vararg args: Pair,
delimiter: VariableDelimiter = VariableDelimiter.default,
): String =
interpolate(args.toMap(), delimiter)
/**
* Interpolates variables in this string using the given list [args].
*
* Variables are resolved by their index in the given [args]. The variable passed to is parsed as an integer, and the
* value at the corresponding index in the [list][args] is returned.
*
* The following rules apply to variable names:
* - Name must be a **valid integer** ;
* - The **index** specified in the name must be in between **0** and the **number of arguments in [args]** - 1.
*
* Any string that does not conform to these rules is considered invalid, a call to this function with an invalid string
* will result in an [IllegalArgumentException] being thrown.
*
* This function can be used as follows:
*
* ```kt
* val args = listOf("John", 42)
* val result = "Hello {{0}}, you are {{1}} years old".interpolate(args)
* ```
*
* @param args the list of values
* @param delimiter the delimitation of the variable (defaults to [VariableDelimiter.default])
* @return the interpolated string
* @throws IllegalArgumentException if a variable name does not conform to the rules defined above
* @see VariableResolver.fromList
*/
@JvmOverloads
fun String.interpolate(
args: List,
delimiter: VariableDelimiter = VariableDelimiter.default,
): String =
interpolate(args, delimiter, VariableResolver.fromList())
/**
* Interpolates variables in this string using the given iterator [args].
*
* Variables are resolved in the order they appear in the string, using the [Iterator.next] method to get the next
* value. If the iterator has no more elements, an [IllegalArgumentException] is thrown.
*
* This function can be used as follows:
* ```kt
* val args = listOf("John", 42).iterator()
* val result = "Hello {{}}, you are {{}} years old".interpolate(args)
* ```
*
* @param args the iterator of values
* @param delimiter the delimitation of the variable (defaults to [VariableDelimiter.default])
* @return the interpolated string
* @throws IllegalArgumentException if the iterator has no more elements and a variable is found
* @see VariableResolver.fromIterator
*/
@JvmOverloads
fun String.interpolate(args: Iterator, delimiter: VariableDelimiter = VariableDelimiter.default): String =
interpolate(args, delimiter, VariableResolver.fromIterator())
/**
* Interface for resolving variables during string interpolation. This interface maps variable names to their values.
*
* @see interpolate
*/
fun interface VariableResolver {
/**
* Returns the value of the variable with the given [name].
*
* Implementations may throw an [IllegalArgumentException] if the variable is unknown, or return a default value.
*
* @param name the name of the variable
* @param context the context to use for resolving the variable
* @return the value of the variable
* @throws IllegalArgumentException if the variable is unknown
*/
fun resolve(name: String, context: T): String
companion object {
/**
* Creates a [VariableResolver] that resolves variables by their index in a list.
*
* The variable passed to [VariableResolver.resolve] is parsed as an integer, and the value at the corresponding
* index in the is returned. If name does not represent a valid integer, or if the index is out of bounds, an
* [IllegalArgumentException] is thrown.
*
* Example:
* ```kt
* val args = listOf("foo", "bar", "baz")
* val resolver = VariableResolver.fromList()
* val result = "Hello {{0}}, {{2}}, {{1}}".interpolate(resolver, args)
* println(result) // prints "Hello foo, baz, bar"
* ```
*
* @return a [VariableResolver] that resolves variables by their index in a list
*/
@JvmStatic
fun fromList(): VariableResolver> =
VariableResolver { name, context ->
val index = requireNotNull(name.toIntOrNull()) { "Expected an integer for variable '$name'" }
require(index >= 0 && index < context.size) {
"Index out of bounds for variable '$name': expected an integer between 0 and ${context.size - 1} (inclusive)"
}
context[index].toString()
}
/**
* Creates a [VariableResolver] that resolves variables by their index in an array.
*
* The variable passed to [VariableResolver.resolve] is parsed as an integer, and the value at the corresponding
* index in the array is returned. If name does not represent a valid integer, or if the index is out of bounds,
* an [IllegalArgumentException] is thrown.
*
* Example:
* ```kt
* val args = arrayOf("foo", "bar", "baz")
* val resolver = VariableResolver.fromArray()
* val result = "Hello {{0}}, {{2}}, {{1}}".interpolate(resolver, args)
* println(result) // prints "Hello foo, baz, bar"
* ```
*
* @return a [VariableResolver] that resolves variables by their index in an array
*/
@JvmStatic
fun fromArray(): VariableResolver> =
VariableResolver { name, context ->
val index = requireNotNull(name.toIntOrNull()) { "Expected an integer for variable '$name'" }
require(index >= 0 && index < context.size) {
"Index out of bounds for variable '$name': expected an integer between 0 and ${context.size - 1} (inclusive)"
}
context[index].toString()
}
/**
* Creates a [VariableResolver] that resolves variables by their name in a map.
*
* The name of the variable passed to [VariableResolver.resolve] is used as a key in the map, and the value
* associated with that key is returned. If a variable
*
* Example:
* ```kt
* val args = mapOf("name" to "John", "age" to 42)
* val resolver = VariableResolver.fromMap()
* val result = "Hello {{name}}, you are {{age}} years old.".interpolate(resolver, args)
* println(result) // prints "Hello John, you are 42 years old."
* ```
*
* @return a [VariableResolver] that resolves variables by their name in a map
*/
@JvmStatic
fun fromMap(): VariableResolver
© 2015 - 2025 Weber Informatics LLC | Privacy Policy