ekt/src/main/kotlin/net/shadowfacts/ekt/EKT.kt

115 lines
3.0 KiB
Kotlin
Raw Normal View History

2017-08-04 19:38:25 +00:00
package net.shadowfacts.ekt
import java.io.File
import javax.script.ScriptContext
import javax.script.ScriptEngineManager
2017-08-05 18:11:19 +00:00
import javax.script.SimpleScriptContext
2017-08-04 19:38:25 +00:00
/**
* @author shadowfacts
*/
object EKT {
2017-08-04 20:56:49 +00:00
private val startControlCodes: Map<String, (String) -> String> = mapOf(
":" to { s -> s },
"=" to { s -> ")" + s },
"#" to { s -> "*/" + s }
)
private val endControlCodes: Map<String, (String) -> String> = mapOf(
":" to { s -> s },
"=" to { s -> s + "echo(" },
"#" to { s -> s + "/*" }
)
private val startStringRegex = Regex("(?:^|[^\\\\])([:=#])]")
2017-08-04 20:56:49 +00:00
private val endStringRegex = Regex("\\[([:=#])")
2017-08-04 19:38:25 +00:00
private val scriptPrefix = """
val _result = StringBuilder()
fun echo(s: Any) { _result.append(s) }
"""
private val scriptSuffix = """
_result.toString()
"""
private val manager by lazy {
ScriptEngineManager()
}
2017-08-05 18:11:19 +00:00
private val engine by lazy {
manager.getEngineByExtension("kts")
}
2017-08-05 17:41:24 +00:00
fun render(template: String, dumpGeneratedScript: File? = null, dataProvider: DataProviderContext.() -> Unit): String {
2017-08-04 19:38:25 +00:00
@Suppress("NAME_SHADOWING")
var template = template
template = template.replace("$", "\${'$'}")
2017-08-04 20:56:49 +00:00
template = ":]$template[:"
2017-08-04 19:38:25 +00:00
template = template.replace(startStringRegex, {
2017-08-04 20:56:49 +00:00
val c = it.groups[1]!!.value
if (c in startControlCodes) {
startControlCodes[c]!!("\necho(\"\"\"")
} else {
throw RuntimeException("Unknown control code: [$c")
}
2017-08-04 19:38:25 +00:00
})
template = template.replace(endStringRegex, {
2017-08-04 20:56:49 +00:00
val c = it.groups[1]!!.value
if (c in endControlCodes) {
endControlCodes[c]!!("\"\"\")\n")
} else {
throw RuntimeException("Unknown control code: $c]")
}
2017-08-04 19:38:25 +00:00
})
val script = scriptPrefix + template + scriptSuffix
2017-08-04 20:58:17 +00:00
dumpGeneratedScript?.apply {
if (!exists()) createNewFile()
writeText(script)
2017-08-04 19:38:25 +00:00
}
2017-08-05 17:41:24 +00:00
val data = DataProviderContext()
data.dataProvider()
return eval(script, data.map) as String
2017-08-04 19:38:25 +00:00
}
2017-08-05 17:41:24 +00:00
fun render(template: File, dumpGeneratedScript: File? = null, dataProvider: DataProviderContext.() -> Unit): String {
return render(template.readText(), dumpGeneratedScript, dataProvider)
2017-08-04 19:38:25 +00:00
}
2017-08-05 17:41:24 +00:00
internal fun eval(script: String, data: Map<String, TypedValue> = mapOf()): Any? {
2017-08-05 18:11:19 +00:00
engine.context = SimpleScriptContext()
2017-08-04 19:38:25 +00:00
val bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE)
bindings.putAll(data)
// Hack to allow data to be accessed by name from template instead of via bindings map
val unwrapBindings = data.keys.map {
2017-08-05 17:41:24 +00:00
val type = data[it]!!.type
"val $it = (bindings[\"$it\"] as net.shadowfacts.ekt.EKT.TypedValue).value as $type"
2017-08-04 19:38:25 +00:00
}.joinToString("\n")
engine.eval(unwrapBindings)
return engine.eval(script)
}
2017-08-05 17:41:24 +00:00
class DataProviderContext {
internal val map = mutableMapOf<String, TypedValue>()
infix fun String.to(value: Any) {
if (value is TypedValue) {
map[this] = value
} else {
map[this] = TypedValue(value, value::class.qualifiedName!!)
}
}
infix fun Any.asType(type: String): TypedValue {
return TypedValue(this, type)
}
}
data class TypedValue(val value: Any, val type: String)
2017-08-04 19:38:25 +00:00
}