added a few experimental alternatives implementations of string templates for string/html/jdbc etc for KT-1565 seems there's a few ways we could do it...
This commit is contained in:
@@ -8,11 +8,7 @@ import java.sql.*
|
|||||||
fun <T> Connection.statement(block: (Statement) -> T): T {
|
fun <T> Connection.statement(block: (Statement) -> T): T {
|
||||||
val statement = createStatement()
|
val statement = createStatement()
|
||||||
if (statement != null) {
|
if (statement != null) {
|
||||||
try {
|
return statement.use(block)
|
||||||
return block(statement)
|
|
||||||
} finally {
|
|
||||||
statement.close()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw IllegalStateException("No Statement returned from $this")
|
throw IllegalStateException("No Statement returned from $this")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package kotlin.jdbc
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to process a statement on this collection
|
||||||
|
*/
|
||||||
|
import java.sql.PreparedStatement
|
||||||
|
import java.sql.ResultSet
|
||||||
|
|
||||||
|
fun PreparedStatement.update(): Int {
|
||||||
|
try {
|
||||||
|
return this.executeUpdate()
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun <T> PreparedStatement.query(block: (ResultSet) -> T): T {
|
||||||
|
try {
|
||||||
|
val resultSet = this.executeQuery()
|
||||||
|
if (resultSet == null) {
|
||||||
|
throw IllegalStateException("No ResultSet returned from $this")
|
||||||
|
} else {
|
||||||
|
return block(resultSet)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package kotlin.jdbc
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to process a statement on this collection
|
||||||
|
*/
|
||||||
|
import java.sql.Statement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the statement with the given block then closes the statement
|
||||||
|
*/
|
||||||
|
fun <T> Statement.use(block : (Statement) -> T) : T {
|
||||||
|
try {
|
||||||
|
return block(this)
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,38 @@
|
|||||||
package test.kotlin.jdbc
|
package test.kotlin.jdbc
|
||||||
|
|
||||||
import kotlin.jdbc.*
|
import kotlin.jdbc. *
|
||||||
import kotlin.test.*
|
import kotlin.test. *
|
||||||
|
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
import org.h2.jdbcx.JdbcDataSource
|
import org.h2.jdbcx.JdbcDataSource
|
||||||
import javax.sql.DataSource
|
import javax.sql.DataSource
|
||||||
import org.h2.jdbcx.JdbcConnectionPool
|
import org.h2.jdbcx.JdbcConnectionPool
|
||||||
|
|
||||||
fun createDataSource(): DataSource {
|
val dataSource = createDataSource()
|
||||||
val pool = JdbcConnectionPool.create("jdbc:h2:mem:KotlinJdbcTest;DB_CLOSE_DELAY=-1", "user", "password")
|
|
||||||
if (pool == null) {
|
fun createDataSource() : DataSource {
|
||||||
|
val dataSource = JdbcConnectionPool.create("jdbc:h2:mem:KotlinJdbcTest;DB_CLOSE_DELAY=-1", "user", "password")
|
||||||
|
if (dataSource == null) {
|
||||||
throw IllegalStateException("No DataSource created")
|
throw IllegalStateException("No DataSource created")
|
||||||
} else {
|
} else {
|
||||||
return pool
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JdbcTest : TestCase() {
|
|
||||||
val dataSource = createDataSource()
|
|
||||||
|
|
||||||
fun testDatabase() {
|
|
||||||
dataSource.update("create table foo (id int primary key, name varchar(100))")
|
dataSource.update("create table foo (id int primary key, name varchar(100))")
|
||||||
assertEquals(1, dataSource.update("insert into foo (id, name) values (1, 'James')"))
|
assertEquals(1, dataSource.update("insert into foo (id, name) values (1, 'James')"))
|
||||||
assertEquals(1, dataSource.update("insert into foo (id, name) values (2, 'Andrey')"))
|
assertEquals(1, dataSource.update("insert into foo (id, name) values (2, 'Andrey')"))
|
||||||
|
|
||||||
// lets query using integer lookups
|
return dataSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JdbcTest : TestCase() {
|
||||||
|
fun testQueryWithIndexColumnAccess() {
|
||||||
dataSource.query("select * from foo") {
|
dataSource.query("select * from foo") {
|
||||||
for (row in it) {
|
for (row in it) {
|
||||||
println("id ${row[1]} and name: ${row[2]}")
|
println("id ${row[1]} and name: ${row[2]}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testQueryWithNamedColumnAccess() {
|
||||||
// query using names
|
// query using names
|
||||||
dataSource.query("select * from foo") {
|
dataSource.query("select * from foo") {
|
||||||
for (row in it) {
|
for (row in it) {
|
||||||
|
|||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
package test.kotlin.jdbc.experiment1
|
||||||
|
|
||||||
|
import kotlin.template.experiment1.*
|
||||||
|
import kotlin.jdbc.*
|
||||||
|
import kotlin.test.*
|
||||||
|
import kotlin.util.arrayList
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
import test.kotlin.jdbc.createDataSource
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.sql.PreparedStatement
|
||||||
|
import java.sql.ResultSet
|
||||||
|
|
||||||
|
fun DataSource.update(fn: (PreparedStatementBuilder) -> Unit): Int {
|
||||||
|
return use{ it.update(fn) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> DataSource.query(fn: (PreparedStatementBuilder) -> Unit, resultBlock: (ResultSet) -> T): T {
|
||||||
|
return use{ it.query(fn, resultBlock) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Connection.update(fn: (PreparedStatementBuilder) -> Unit): Int {
|
||||||
|
val preparedStatement = prepare(fn)
|
||||||
|
return preparedStatement.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun <T> Connection.query(fn: (PreparedStatementBuilder) -> Unit, resultBlock: (ResultSet) -> T): T {
|
||||||
|
val preparedStatement = prepare(fn)
|
||||||
|
return preparedStatement.query(resultBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Connection.prepare(fn: (PreparedStatementBuilder) -> Unit): PreparedStatement {
|
||||||
|
val builder = PreparedStatementBuilder(this)
|
||||||
|
fn(builder)
|
||||||
|
return builder.statement()
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Binding {
|
||||||
|
fun bind(statement: PreparedStatement): Unit
|
||||||
|
}
|
||||||
|
class PreparedStatementBuilder(val connection: Connection) {
|
||||||
|
val sql = StringBuilder()
|
||||||
|
val tasks = arrayList<Binding>()
|
||||||
|
private var parameterIndex = 0
|
||||||
|
|
||||||
|
fun text(value: String): Unit {
|
||||||
|
// we assume all text is escaped already
|
||||||
|
sql.append(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expression(value: String): Unit {
|
||||||
|
sql.append("?")
|
||||||
|
tasks.add(object: Binding {
|
||||||
|
override fun bind(statement : PreparedStatement) {
|
||||||
|
statement.setString(nextParameterIndex(), value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO bind other kinds!
|
||||||
|
|
||||||
|
fun statement(): PreparedStatement {
|
||||||
|
val answer = connection.prepareStatement(sql.toString())
|
||||||
|
if (answer == null) {
|
||||||
|
throw IllegalStateException("No PreparedStatement returned from $connection")
|
||||||
|
} else {
|
||||||
|
for (task in tasks) {
|
||||||
|
task.bind(answer)
|
||||||
|
}
|
||||||
|
return answer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun nextParameterIndex(): Int = ++parameterIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class JdbcTemplateTest : TestCase() {
|
||||||
|
//val dataSource = createDataSource()
|
||||||
|
|
||||||
|
fun testTemplateInsert() {
|
||||||
|
val id = 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
dataSource.update {
|
||||||
|
it.text("select * from foo where id = ")
|
||||||
|
it.expression(id)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
== 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Pros:
|
||||||
|
|
||||||
|
* really simple
|
||||||
|
* no real compiler magic required where the type depends on the context
|
||||||
|
|
||||||
|
Cons:
|
||||||
|
|
||||||
|
* requires runtime instanceof checks 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
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package kotlin.template.experiment1
|
||||||
|
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import kotlin.dom.toXmlString
|
||||||
|
|
||||||
|
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<T>(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class HtmlBuilder(output: Appendable) : TextBuilder<HtmlBuilder>(output) {
|
||||||
|
var nullText : String = ""
|
||||||
|
|
||||||
|
fun expression(value: Node): Unit {
|
||||||
|
text(value.toXmlString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expression(value: Any?): Unit {
|
||||||
|
if (value == null) {
|
||||||
|
output.append(nullText)
|
||||||
|
} else {
|
||||||
|
escape(value.toString() ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(buffer : Appendable, val value : Any?) : Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
object ToStringFormatter : ValueFormatter {
|
||||||
|
|
||||||
|
fun toString() = "ToStringFormatter"
|
||||||
|
|
||||||
|
override fun format(buffer : Appendable, value : Any?) {
|
||||||
|
val text = if (value == null) "null" else value.toString()
|
||||||
|
buffer.append(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toHtml(fn: (HtmlBuilder) -> Unit): String {
|
||||||
|
val buffer = StringBuilder()
|
||||||
|
val html = HtmlBuilder(buffer)
|
||||||
|
fn(html)
|
||||||
|
return buffer.toString() ?: ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package kotlin.template.experiment2
|
||||||
|
|
||||||
|
import kotlin.dom.*
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a string template from some constant string expressions and some dynamic expressions.
|
||||||
|
*/
|
||||||
|
class StringTemplate(val constantText : Array<String>, val expressions : Array<Any?>) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
for (i in 0.upto(constantText.size - 1)) {
|
||||||
|
buffer.append(constantText[i])
|
||||||
|
if (i < expressionSize) {
|
||||||
|
val value = expressions[i]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a formatter of values in a [[StringTemplate]] which understands how to escape
|
||||||
|
* different types for different kinds of language
|
||||||
|
*/
|
||||||
|
trait ValueFormatter {
|
||||||
|
fun format(buffer : Appendable, val value : Any?) : Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
object ToStringFormatter : ValueFormatter {
|
||||||
|
|
||||||
|
fun toString() = "ToStringFormatter"
|
||||||
|
|
||||||
|
override fun format(buffer : Appendable, value : Any?) {
|
||||||
|
val text = if (value == null) "null" else value.toString()
|
||||||
|
buffer.append(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HtmlFormatter : ValueFormatter {
|
||||||
|
var nullText : String = ""
|
||||||
|
|
||||||
|
override fun format(buffer : Appendable, value : Any?) {
|
||||||
|
if (value == null) {
|
||||||
|
buffer.append(nullText)
|
||||||
|
} else if (value is StringTemplate) {
|
||||||
|
value.append(buffer, this)
|
||||||
|
} else if (value is Node) {
|
||||||
|
buffer.append(value.toXmlString())
|
||||||
|
} else {
|
||||||
|
escape(buffer, value.toString() ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun escape(buffer : Appendable, text : String) : Unit {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlin.template
|
package kotlin.template.experiment3
|
||||||
|
|
||||||
import kotlin.dom.toXmlString
|
import kotlin.dom.toXmlString
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlin.template
|
package kotlin.template.experiment3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a string template from a string with $ expressions inside.
|
* Creates a string template from a string with $ expressions inside.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
// Code generated by the following template expression:
|
||||||
|
//
|
||||||
|
// val actual = toHtml("<h1>$foo</h1> <p>hey $bar</p>")
|
||||||
|
val actual = 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package test.template.experiment
|
||||||
|
|
||||||
|
import kotlin.template.experiment2.*
|
||||||
|
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 = "<h1>$foo</h1> <p>hey $bar</p>".toHtml()
|
||||||
|
val actual = StringTemplate(array("<h1>", "</h1> <p>hey ", "</p>"), array(foo, bar)).toHtml()
|
||||||
|
|
||||||
|
assertEquals("<h1>James</h1> <p>hey x > 1</p>", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package test.template.experiment
|
||||||
|
|
||||||
|
import kotlin.template.experiment2.*
|
||||||
|
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!".toString()
|
||||||
|
val actual = StringTemplate(array("hello ", "!"), array(name)).toString()
|
||||||
|
|
||||||
|
assertEquals("hello James!", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-2
@@ -1,9 +1,10 @@
|
|||||||
package test.template
|
package test.template
|
||||||
|
|
||||||
import junit.framework.TestCase
|
import kotlin.template.experiment3.*
|
||||||
import kotlin.template.*
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
class HtmlTemplateTest : TestCase() {
|
class HtmlTemplateTest : TestCase() {
|
||||||
fun testTemplate(): Unit {
|
fun testTemplate(): Unit {
|
||||||
val foo = "James"
|
val foo = "James"
|
||||||
+3
-2
@@ -1,9 +1,10 @@
|
|||||||
package test.template
|
package test.template
|
||||||
|
|
||||||
import junit.framework.TestCase
|
import kotlin.template.experiment3.*
|
||||||
import kotlin.template.StringTemplate
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
class StringTemplateTest : TestCase() {
|
class StringTemplateTest : TestCase() {
|
||||||
fun testTemplate(): Unit {
|
fun testTemplate(): Unit {
|
||||||
val name = "James"
|
val name = "James"
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user