removed the old experiments for templating; so we now just create a StringTemplate object that can be converted to html or used with JDBC queries/statements etc
This commit is contained in:
+45
-23
@@ -1,6 +1,6 @@
|
|||||||
package test.kotlin.jdbc.experiment1
|
package test.kotlin.jdbc.experiment1
|
||||||
|
|
||||||
import kotlin.template.experiment1.*
|
import kotlin.template.*
|
||||||
import kotlin.jdbc.*
|
import kotlin.jdbc.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
import kotlin.util.*
|
import kotlin.util.*
|
||||||
@@ -14,34 +14,50 @@ import java.sql.Connection
|
|||||||
import java.sql.PreparedStatement
|
import java.sql.PreparedStatement
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
|
|
||||||
fun DataSource.update(fn: (PreparedStatementBuilder) -> Unit): Int {
|
fun DataSource.update(template: StringTemplate): Int {
|
||||||
return use{ it.update(fn) }
|
return use{ it.update(template) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> DataSource.query(fn: (PreparedStatementBuilder) -> Unit, resultBlock: (ResultSet) -> T): T {
|
fun <T> DataSource.query(template: StringTemplate, resultBlock: (ResultSet) -> T): T {
|
||||||
return use{ it.query(fn, resultBlock) }
|
return use{ it.query(template, resultBlock) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Connection.update(fn: (PreparedStatementBuilder) -> Unit): Int {
|
fun Connection.update(template: StringTemplate): Int {
|
||||||
val preparedStatement = prepare(fn)
|
val preparedStatement = prepare(template)
|
||||||
return preparedStatement.update()
|
return preparedStatement.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun <T> Connection.query(fn: (PreparedStatementBuilder) -> Unit, resultBlock: (ResultSet) -> T): T {
|
fun <T> Connection.query(template: StringTemplate, resultBlock: (ResultSet) -> T): T {
|
||||||
val preparedStatement = prepare(fn)
|
val preparedStatement = prepare(template)
|
||||||
return preparedStatement.query(resultBlock)
|
return preparedStatement.query(resultBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Connection.prepare(fn: (PreparedStatementBuilder) -> Unit): PreparedStatement {
|
fun Connection.prepare(template: StringTemplate): PreparedStatement {
|
||||||
val builder = PreparedStatementBuilder(this)
|
val builder = PreparedStatementBuilder(this)
|
||||||
fn(builder)
|
var constantText = true
|
||||||
|
template.forEach{
|
||||||
|
if (constantText) {
|
||||||
|
if (it == null) {
|
||||||
|
throw IllegalStateException("No constant checks should be null");
|
||||||
|
} else {
|
||||||
|
val text = it.toString()
|
||||||
|
if (text != null) {
|
||||||
|
builder.text(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.expression(it)
|
||||||
|
}
|
||||||
|
constantText = !constantText
|
||||||
|
}
|
||||||
return builder.statement()
|
return builder.statement()
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Binding {
|
trait Binding {
|
||||||
fun bind(statement: PreparedStatement): Unit
|
fun bind(statement: PreparedStatement): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreparedStatementBuilder(val connection: Connection) {
|
class PreparedStatementBuilder(val connection: Connection) {
|
||||||
val sql = StringBuilder()
|
val sql = StringBuilder()
|
||||||
val tasks = arrayList<Binding>()
|
val tasks = arrayList<Binding>()
|
||||||
@@ -52,6 +68,16 @@ class PreparedStatementBuilder(val connection: Connection) {
|
|||||||
sql.append(value)
|
sql.append(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun expression(value: Any?): Unit {
|
||||||
|
if (value is String) {
|
||||||
|
expression(value)
|
||||||
|
} else if (value is Int) {
|
||||||
|
expression(value)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun expression(value: String): Unit {
|
fun expression(value: String): Unit {
|
||||||
sql.append("?")
|
sql.append("?")
|
||||||
tasks.add(object: Binding {
|
tasks.add(object: Binding {
|
||||||
@@ -98,21 +124,17 @@ class JdbcTemplateTest : TestCase() {
|
|||||||
// Mimicks the following code
|
// Mimicks the following code
|
||||||
// dataSource.update("insert into foo (id, name) values ($id, $name)")
|
// dataSource.update("insert into foo (id, name) values ($id, $name)")
|
||||||
|
|
||||||
dataSource.update {
|
// TODO will use a tuple soon
|
||||||
it.text("insert into foo (id, name) values (")
|
dataSource.update(
|
||||||
it.expression(id)
|
StringTemplate(array("insert into foo (id, name) values (", id, ", ", name, ")"))
|
||||||
it.text(", ")
|
)
|
||||||
it.expression(name)
|
|
||||||
it.text(")")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mimicks
|
// Mimicks
|
||||||
// datasource.query("select * from foo where id = $id") { it["name"] }
|
// datasource.query("select * from foo where id = $id") { it.map{ it["name"] } }
|
||||||
|
|
||||||
val names = dataSource.query({
|
val names = dataSource.query(
|
||||||
it.text("select * from foo where id = ")
|
StringTemplate(array("select * from foo where id = ", id))
|
||||||
it.expression(id)
|
) {
|
||||||
}) {
|
|
||||||
it.map{ it["name"] }
|
it.map{ it["name"] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
## Warning experimental APIs
|
|
||||||
|
|
||||||
This package contains a couple of alternative experimental implementations of string templates.
|
|
||||||
|
|
||||||
These are not yet integrated into the language, but are here to compare implementation details,
|
|
||||||
suitability, code size, complexity and efficiency.
|
|
||||||
|
|
||||||
### Aims
|
|
||||||
|
|
||||||
* make it easy to generate various kinds of output such as text, text with internationalisation, HTML/XML, JSON, URL/URLs or make JDBC calls while reusing the same language templates with $ expressions
|
|
||||||
|
|
||||||
### Issues
|
|
||||||
|
|
||||||
* how many objects are created per call?
|
|
||||||
* how extensible & easy to reuse the concept of String Templates in other library features
|
|
||||||
* pass context into the template somehow; so that formatting options (Locales, nullText, JDBC Connection, converters or whatnot) can be customized
|
|
||||||
* avoid where possible lots of "if (value is SomeType)" runtime checks to determine what encoding strategy should be used
|
|
||||||
|
|
||||||
|
|
||||||
#### experiment1
|
|
||||||
|
|
||||||
Here we convert a string template into a function on some kind of builder object where we invoke the text() method for static text and expression() for dynamic expressions
|
|
||||||
|
|
||||||
* [example](https://github.com/JetBrains/kotlin/blob/master/libraries/testlib/test/template/experiment1/HtmlTemplateTest.kt#L17)
|
|
||||||
|
|
||||||
Pro:
|
|
||||||
|
|
||||||
* simple and efficient code is generated - no arrays
|
|
||||||
* no arrays are created for static and expression arguments
|
|
||||||
* static dispatch of encoding functions; no runtime instanceof checks on each argument
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
|
|
||||||
* function may create an inner object/class (though the compiler may get smart enough to inline some of those - or maybe generate a custom function for the whole thing with parameters?)
|
|
||||||
* we can only use this strategy if the template expression is passed to a function and we can detect the function parameter takes a fn: (T) -> Unit; and T has suitable text() and expression() methods.
|
|
||||||
(We can use extension methods to allow regular builder-like classes such as java.lang.StringBuilder to be used to minimise redundant object creation) - so the code generation is a bit more complex
|
|
||||||
|
|
||||||
#### experiment2
|
|
||||||
|
|
||||||
Here we create a single StringTemplate object containing the constant text array and the values array. Then we use functions or extension functions as ways to do other things.
|
|
||||||
|
|
||||||
* [example](https://github.com/JetBrains/kotlin/blob/master/libraries/testlib/test/template/experiment2/HtmlTemplateTest.kt#L13)
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
|
|
||||||
* really simple
|
|
||||||
* no real compiler magic required where the type depends on the context
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
|
|
||||||
* requires runtime [instanceof checks](https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/template/experiment2/Templates.kt#L66) on each expression value
|
|
||||||
* fair bit of object construction per call (2 arrays, a template object, then the string builder and lots of index lookup of arrays
|
|
||||||
|
|
||||||
|
|
||||||
#### experiment 3
|
|
||||||
|
|
||||||
The idea is we create a FooTemplate object with the static constant strings inside; so we can easily cache the immutable stuff on startup. Then we basically use pseudo code like
|
|
||||||
|
|
||||||
val template = StringTemplate("some ", " static ", "text")
|
|
||||||
...
|
|
||||||
val builder = template.builder()
|
|
||||||
builder.expression(foo)
|
|
||||||
builder.expression(bar)
|
|
||||||
val answer = builder.build()
|
|
||||||
|
|
||||||
Then based on the context - e.g. an annotation or a method parameter type or something; we determine which kind of template to create (StringTemplate, HtmlTemplate, JdbcTemplate etc)
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
|
|
||||||
* fairly simple
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
|
|
||||||
* quite a few different types (FooTemplate and FooBuilder)
|
|
||||||
* no way to pass in parameters to the FooBuilder class; for example the JDBC connection, or the Locale / NumberFormats to use for numeric formatting etc
|
|
||||||
* requires runtime instanceof checks on each expression value & array access etc
|
|
||||||
+43
-33
@@ -1,47 +1,57 @@
|
|||||||
package kotlin.template.experiment2
|
package kotlin.template
|
||||||
|
|
||||||
import kotlin.dom.*
|
import kotlin.dom.*
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
|
import com.sun.org.apache.xalan.internal.xsltc.dom.UnionIterator
|
||||||
|
|
||||||
|
// TODO this class should move into the runtime
|
||||||
|
// in jet.StringTemplate
|
||||||
|
class StringTemplate(val values: Array<Any?>) {
|
||||||
|
|
||||||
/**
|
fun toString(): String {
|
||||||
* Creates a string template from some constant string expressions and some dynamic expressions.
|
val out = StringBuilder()
|
||||||
*/
|
forEach{ out.append(it) }
|
||||||
class StringTemplate(val constantText : Array<String>, val expressions : Array<Any?>) {
|
return out.toString() ?: ""
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given string template to a String
|
|
||||||
*/
|
|
||||||
fun toString(): String = toString(ToStringFormatter)
|
|
||||||
|
|
||||||
fun toString(formatter : ValueFormatter): String {
|
|
||||||
val buffer = StringBuilder()
|
|
||||||
append(buffer, formatter)
|
|
||||||
return buffer.toString() ?: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun append(buffer: Appendable, formatter: ValueFormatter): Unit {
|
/**
|
||||||
val expressionSize = expressions.size
|
* Performs the given function on each value in the collection
|
||||||
for (i in 0.upto(constantText.size - 1)) {
|
*/
|
||||||
buffer.append(constantText[i])
|
fun forEach(fn: (Any?) -> Unit): Unit {
|
||||||
if (i < expressionSize) {
|
for (v in values) {
|
||||||
val value = expressions[i]
|
fn(v)
|
||||||
formatter.format(buffer, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given template to HTML with an optional formatter
|
|
||||||
*/
|
|
||||||
fun toHtml(formatter: HtmlFormatter = HtmlFormatter()): String = toString(formatter)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the HTML representation of this template to the given appendable
|
|
||||||
*/
|
|
||||||
fun appendHtml(buffer: Appendable, formatter: HtmlFormatter = HtmlFormatter()): String = toString(formatter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun StringTemplate.toString(formatter : ValueFormatter): String {
|
||||||
|
val buffer = StringBuilder()
|
||||||
|
append(buffer, formatter)
|
||||||
|
return buffer.toString() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StringTemplate.append(out: Appendable, formatter: ValueFormatter): Unit {
|
||||||
|
var constantText = true
|
||||||
|
this.forEach {
|
||||||
|
if (constantText) {
|
||||||
|
if (it == null) {
|
||||||
|
throw IllegalStateException("No constant checks should be null");
|
||||||
|
} else {
|
||||||
|
val text = it.toString()
|
||||||
|
if (text != null) {
|
||||||
|
out.append(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formatter.format(out, it)
|
||||||
|
}
|
||||||
|
constantText = !constantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun StringTemplate.toHtml(formatter : HtmlFormatter = HtmlFormatter()): String = toString(formatter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a formatter of values in a [[StringTemplate]] which understands how to escape
|
* Represents a formatter of values in a [[StringTemplate]] which understands how to escape
|
||||||
* different types for different kinds of language
|
* different types for different kinds of language
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package kotlin.template.experiment1
|
|
||||||
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import kotlin.dom.toXmlString
|
|
||||||
|
|
||||||
import java.lang.Number
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.text.NumberFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
fun StringBuilder.text(value : String) : Unit {
|
|
||||||
this.append(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun StringBuilder.expression(value : Any?) : Unit {
|
|
||||||
this.append(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a [[StringTemplate]] to a String
|
|
||||||
*/
|
|
||||||
// TODO tried toString() but compiler error kicks in
|
|
||||||
fun format(fn : (StringBuilder) -> Unit) : String {
|
|
||||||
val builder = StringBuilder()
|
|
||||||
fn(builder)
|
|
||||||
return builder.toString() ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
abstract class TextBuilder(val output : Appendable) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Converts the given string template to a String
|
|
||||||
|
|
||||||
fun toString(fn: (T) -> Unit): String = toString(ToStringFormatter, fn)
|
|
||||||
|
|
||||||
fun toString(formatter : ValueFormatter, fn: (T) -> Unit): String {
|
|
||||||
fn(this)
|
|
||||||
return output.toString() ?: ""
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun text(value : String) : Unit {
|
|
||||||
output.append(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class I18nBuilder(output : Appendable, val formatter : I18nFormatter) : TextBuilder(output) {
|
|
||||||
|
|
||||||
fun expression(value : Any?) : Unit {
|
|
||||||
escape(formatter.format(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun expression(value : Number) : Unit {
|
|
||||||
escape(formatter.formatNumber(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun expression(value : Date) : Unit {
|
|
||||||
escape(formatter.formatDate(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default does no escaping but inherited classes may escape text
|
|
||||||
*/
|
|
||||||
open fun escape(text : String) : Unit {
|
|
||||||
output.append(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class HtmlBuilder(output : Appendable, formatter : I18nFormatter) : I18nBuilder(output, formatter) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nodes are assumed to already be properly HTML/XML encoded so don't escape
|
|
||||||
*/
|
|
||||||
fun expression(value : Node) : Unit {
|
|
||||||
text(value.toXmlString())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes text using HTML escaping of sensitive tokens
|
|
||||||
*/
|
|
||||||
override fun escape(text : String) : Unit {
|
|
||||||
for (c in text) {
|
|
||||||
if (c == '<') output.append("<")
|
|
||||||
else if (c == '>') output.append(">")
|
|
||||||
else if (c == '&') output.append("&")
|
|
||||||
else if (c == '"') output.append(""")
|
|
||||||
else output.append(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a formatter of values in a [[StringTemplate]] which understands how to escape
|
|
||||||
* different types for different kinds of language
|
|
||||||
*/
|
|
||||||
trait ValueFormatter {
|
|
||||||
fun format(val value : Any?) : String
|
|
||||||
}
|
|
||||||
|
|
||||||
open class ToStringFormatter : ValueFormatter {
|
|
||||||
|
|
||||||
var nullText : String = "null"
|
|
||||||
|
|
||||||
open fun toString() = "ToStringFormatter"
|
|
||||||
|
|
||||||
override fun format(value : Any?): String {
|
|
||||||
return if (value == null) nullText else value.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class I18nFormatter(val locale : Locale = Locale.getDefault().sure()): ToStringFormatter() {
|
|
||||||
|
|
||||||
override fun toString() = "I18nFormatter"
|
|
||||||
|
|
||||||
public var numberFormat : NumberFormat = NumberFormat.getInstance(locale).sure()
|
|
||||||
|
|
||||||
public var dateFormat : DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, locale).sure()
|
|
||||||
|
|
||||||
fun formatNumber(number : Number): String {
|
|
||||||
return numberFormat.format(number) ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun formatDate(date : Date): String {
|
|
||||||
return dateFormat.format(date) ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the internationalised text for this formatter
|
|
||||||
*/
|
|
||||||
fun toString(fn : (I18nBuilder) -> Unit) : String {
|
|
||||||
val buffer = StringBuilder()
|
|
||||||
val builder = I18nBuilder(buffer, this)
|
|
||||||
fn(builder)
|
|
||||||
return buffer.toString() ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the HTML for the given function
|
|
||||||
*/
|
|
||||||
fun toHtml(fn : (HtmlBuilder) -> Unit) : String {
|
|
||||||
val buffer = StringBuilder()
|
|
||||||
val html = HtmlBuilder(buffer, this)
|
|
||||||
fn(html)
|
|
||||||
return buffer.toString() ?: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package kotlin.template.experiment3
|
|
||||||
|
|
||||||
import kotlin.dom.toXmlString
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a string template from a string with $ expressions inside using HTML escaping of expressions.
|
|
||||||
*/
|
|
||||||
// TODO varargs on constructors seems to fail
|
|
||||||
//class HtmlTemplate(vararg val text: String) {
|
|
||||||
class HtmlTemplate(val constantText : Array<String>) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a builder of string expressions which use HTML encoding on expressions
|
|
||||||
*/
|
|
||||||
fun builder() : HtmlTemplateBuilder {
|
|
||||||
// TODO we should allow the caller to pass these in when we create the builder!
|
|
||||||
val options = HtmlTemplateOptions()
|
|
||||||
return HtmlTemplateBuilder(constantText, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HtmlTemplateOptions() {
|
|
||||||
var nullText : String = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
open class HtmlTemplateBuilder(constantText : Array<String>, val options : HtmlTemplateOptions) : StringTemplateBuilder(constantText) {
|
|
||||||
|
|
||||||
override fun expression(expression : Any) : Unit {
|
|
||||||
val text = if (expression != null) expression.toString() ?: "" else options.nullText
|
|
||||||
escape(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the DOM node, no HTML escaping is done as we assume its already escaped
|
|
||||||
*/
|
|
||||||
fun expression(node : Node) : Unit {
|
|
||||||
// no need to escape
|
|
||||||
unescape(node.toXmlString(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the given text escaped properly
|
|
||||||
*/
|
|
||||||
fun escape(text : String): Unit {
|
|
||||||
appendNextConstant()
|
|
||||||
for (c in text) {
|
|
||||||
if (c == '<') buffer.append("<")
|
|
||||||
else if (c == '>') buffer.append(">")
|
|
||||||
else if (c == '&') buffer.append("&")
|
|
||||||
else if (c == '"') buffer.append(""")
|
|
||||||
else buffer.append(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the unescaped text
|
|
||||||
*/
|
|
||||||
fun unescape(text : String) {
|
|
||||||
appendNextConstant()
|
|
||||||
buffer.append(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package kotlin.template.experiment3
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a string template from a string with $ expressions inside.
|
|
||||||
*/
|
|
||||||
// TODO varargs on constructors seems to fail
|
|
||||||
//class StringTemplate(vararg val text: String) {
|
|
||||||
open class StringTemplate(val constantText : Array<String>) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a builder of string expressions
|
|
||||||
*/
|
|
||||||
open fun builder() : StringTemplateBuilder = StringTemplateBuilder(constantText)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to build strings using expressions
|
|
||||||
*/
|
|
||||||
open class StringTemplateBuilder(val constantText : Array<String>){
|
|
||||||
protected val buffer: StringBuilder = StringBuilder()
|
|
||||||
var index : Int = 0
|
|
||||||
|
|
||||||
fun build() : String {
|
|
||||||
while (appendNextConstant()) {}
|
|
||||||
return buffer.toString() ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun expression(expression : Any) {
|
|
||||||
appendNextConstant()
|
|
||||||
buffer.append(expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next static text value from the template
|
|
||||||
*/
|
|
||||||
protected fun appendNextConstant(): Boolean {
|
|
||||||
if (index < constantText.size) {
|
|
||||||
buffer.append(constantText[index++])
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-3
@@ -1,6 +1,6 @@
|
|||||||
package test.template.experiment
|
package test.template
|
||||||
|
|
||||||
import kotlin.template.experiment2.*
|
import kotlin.template.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
@@ -13,7 +13,10 @@ class HtmlTemplateTest : TestCase() {
|
|||||||
// Code generated by the following template expression:
|
// Code generated by the following template expression:
|
||||||
//
|
//
|
||||||
// val actual = "<h1>$foo</h1> <p>hey $bar</p>".toHtml()
|
// val actual = "<h1>$foo</h1> <p>hey $bar</p>".toHtml()
|
||||||
val actual = StringTemplate(array("<h1>", "</h1> <p>hey ", "</p>"), array(foo, bar)).toHtml()
|
|
||||||
|
// TODO will use a tuple soon
|
||||||
|
//val actual = StringTemplate(Tuple3<String,String,String>("<h1>", foo, "</h1> <p>hey ", bar, "</p>")).toHtml()
|
||||||
|
val actual = StringTemplate(array("<h1>", foo, "</h1> <p>hey ", bar, "</p>")).toHtml()
|
||||||
|
|
||||||
assertEquals("<h1>James</h1> <p>hey x > 1</p>", actual)
|
assertEquals("<h1>James</h1> <p>hey x > 1</p>", actual)
|
||||||
}
|
}
|
||||||
+6
-3
@@ -1,6 +1,6 @@
|
|||||||
package test.template.experiment
|
package test.template
|
||||||
|
|
||||||
import kotlin.template.experiment2.*
|
import kotlin.template.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
@@ -12,7 +12,10 @@ class StringTemplateTest : TestCase() {
|
|||||||
// Code generated by the following template expression:
|
// Code generated by the following template expression:
|
||||||
//
|
//
|
||||||
// val actual = "hello $name!".toString()
|
// val actual = "hello $name!".toString()
|
||||||
val actual = StringTemplate(array("hello ", "!"), array(name)).toString()
|
|
||||||
|
// TODO will use a tuple soon
|
||||||
|
//val actual = StringTemplate(Tuple3<String,String,String>("hello ", name, "!"))).toString()
|
||||||
|
val actual = StringTemplate(array("hello ", name, "!")).toString()
|
||||||
|
|
||||||
assertEquals("hello James!", actual)
|
assertEquals("hello James!", actual)
|
||||||
}
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package template.experiment1
|
|
||||||
|
|
||||||
import kotlin.template.experiment1.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.dom.toXmlString
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlTemplateTest : TestCase() {
|
|
||||||
fun testTemplate(): Unit {
|
|
||||||
val foo = "James"
|
|
||||||
val bar = "x > 1"
|
|
||||||
|
|
||||||
val format = I18nFormatter()
|
|
||||||
// Code generated by the following template expression:
|
|
||||||
//
|
|
||||||
// val actual = format.toHtml("<h1>$foo</h1> <p>hey $bar</p>")
|
|
||||||
val actual = format.toHtml{
|
|
||||||
it.text("<h1>")
|
|
||||||
it.expression(foo)
|
|
||||||
it.text("</h1> <p>hey ")
|
|
||||||
it.expression(bar)
|
|
||||||
it.text("</p>")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("<h1>James</h1> <p>hey x > 1</p>", actual)
|
|
||||||
|
|
||||||
println("Generated HTML: $actual")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package template.experiment1
|
|
||||||
|
|
||||||
import kotlin.template.experiment1.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class I18nTemplateTest : TestCase() {
|
|
||||||
fun testDefaultLocale() : Unit {
|
|
||||||
format(I18nFormatter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testFrance() : Unit {
|
|
||||||
format(I18nFormatter(Locale.FRANCE.sure()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testGermany() : Unit {
|
|
||||||
format(I18nFormatter(Locale.GERMANY.sure()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun format(format: I18nFormatter): Unit {
|
|
||||||
val name = "James"
|
|
||||||
// TODO currently numbers cause: java.lang.ClassNotFoundException: jet.Number
|
|
||||||
//val price: Double = 1.99
|
|
||||||
val now = Date()
|
|
||||||
|
|
||||||
|
|
||||||
// Code generated by the following template expression:
|
|
||||||
//
|
|
||||||
// val actual = format.toI18n("hello $name!")
|
|
||||||
val actual = format.toString{
|
|
||||||
it.text("hello ")
|
|
||||||
it.expression(name)
|
|
||||||
/*
|
|
||||||
it.text(" price ")
|
|
||||||
it.expression(price)
|
|
||||||
*/
|
|
||||||
it.text(" date ")
|
|
||||||
it.expression(now)
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Got text: $actual")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package template.experiment1
|
|
||||||
|
|
||||||
import kotlin.template.experiment1.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
|
|
||||||
class StringTemplateTest : TestCase() {
|
|
||||||
fun testTemplate() : Unit {
|
|
||||||
val name = "James"
|
|
||||||
|
|
||||||
// Code generated by the following template expression:
|
|
||||||
//
|
|
||||||
// val actual = format("hello $name!")
|
|
||||||
val actual = format{
|
|
||||||
it.text("hello ")
|
|
||||||
it.expression(name)
|
|
||||||
it.text("!")
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Got text: $actual")
|
|
||||||
assertEquals("hello James!", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package test.template
|
|
||||||
|
|
||||||
import kotlin.template.experiment3.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
|
|
||||||
class HtmlTemplateTest : TestCase() {
|
|
||||||
fun testTemplate(): Unit {
|
|
||||||
val foo = "James"
|
|
||||||
val bar = "x > 1"
|
|
||||||
|
|
||||||
// Code generated by the following template expression:
|
|
||||||
//
|
|
||||||
// val actual = [Html]"<h1>$foo</h1> <p>hey $bar</p>"
|
|
||||||
val builder = HtmlTemplate(array("<h1>", "</h1> <p>hey ", "</p>")).builder()
|
|
||||||
builder.expression(foo)
|
|
||||||
builder.expression(bar)
|
|
||||||
val actual = builder.build()
|
|
||||||
|
|
||||||
assertEquals("<h1>James</h1> <p>hey x > 1</p>", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package test.template
|
|
||||||
|
|
||||||
import kotlin.template.experiment3.*
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
|
|
||||||
class StringTemplateTest : TestCase() {
|
|
||||||
fun testTemplate(): Unit {
|
|
||||||
val name = "James"
|
|
||||||
|
|
||||||
// Code generated by the following template expression:
|
|
||||||
//
|
|
||||||
// val actual = "hello $name!"
|
|
||||||
val builder = StringTemplate(array("hello ", "!")).builder()
|
|
||||||
builder.expression(name)
|
|
||||||
val actual = builder.build()
|
|
||||||
|
|
||||||
assertEquals("hello James!", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package template.experiment4
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.todo
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a string template from some constant string expressions and some dynamic expressions.
|
|
||||||
*/
|
|
||||||
class StringTemplate<T>(val constantText : Array<String>, val fn : (T) -> Unit) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fun StringBuilder.text(value : String) : Unit {
|
|
||||||
this.append(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun StringBuilder.expression(value : Any?) : Unit {
|
|
||||||
this.append(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a [[StringTemplate]] to a String
|
|
||||||
*/
|
|
||||||
fun toString(template : StringTemplate<StringBuilder>) : String {
|
|
||||||
val builder = StringBuilder()
|
|
||||||
template.fn(builder)
|
|
||||||
return builder.toString() ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
class StringTemplateTest : TestCase() {
|
|
||||||
fun testTemplate() : Unit {
|
|
||||||
val name = "James"
|
|
||||||
|
|
||||||
// Code generated by the following template expression:
|
|
||||||
//
|
|
||||||
// val actual = "hello $name!"
|
|
||||||
val template = StringTemplate<StringBuilder>(array("hello ", "!"), { (it : StringBuilder) ->
|
|
||||||
println("About to apply function!")
|
|
||||||
it.text("hello ")
|
|
||||||
it.expression(name)
|
|
||||||
it.text("!")
|
|
||||||
})
|
|
||||||
println("Template has constants ${template.constantText.toList()} and function ${template.fn}")
|
|
||||||
|
|
||||||
todo {
|
|
||||||
val actual = toString(template)
|
|
||||||
|
|
||||||
assertEquals("hello James!", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,7 +32,6 @@ class GenerateSiteTest : TestCase() {
|
|||||||
config.title = "Kotlin API ($version)"
|
config.title = "Kotlin API ($version)"
|
||||||
config.version = version
|
config.version = version
|
||||||
config.ignorePackages.add("org.jetbrains.kotlin")
|
config.ignorePackages.add("org.jetbrains.kotlin")
|
||||||
config.ignorePackages.add("kotlin.template.experiment")
|
|
||||||
|
|
||||||
val compiler = KDocCompiler()
|
val compiler = KDocCompiler()
|
||||||
compiler.exec(System.out, args)
|
compiler.exec(System.out, args)
|
||||||
|
|||||||
Reference in New Issue
Block a user