From db76e28c6da37521b3d5e7d68bf11bc4fea5b0dd Mon Sep 17 00:00:00 2001 From: James Strachan Date: Fri, 23 Dec 2011 13:40:03 +0000 Subject: [PATCH] added a little experimental spike of a text and markup based template library for internal DSLs for templating (which external DSLs like Jade / Razor / Velocity / Erb / JSP style) could layer on top of. Mails to follow shortly :) --- templatelib/src/TemplateCore.kt | 59 +++++++++ templatelib/src/TemplateHtml.kt | 119 ++++++++++++++++++ templatelib/src/TemplateJavaIo.kt | 34 +++++ templatelib/templatelib.iml | 15 +++ templatelib/test/TemplateCoreTest.kt | 54 ++++++++ templatelib/test/TemplateHtmlTest.kt | 75 +++++++++++ .../test/std/template/TemplateTestAll.java | 13 ++ 7 files changed, 369 insertions(+) create mode 100644 templatelib/src/TemplateCore.kt create mode 100644 templatelib/src/TemplateHtml.kt create mode 100644 templatelib/src/TemplateJavaIo.kt create mode 100644 templatelib/templatelib.iml create mode 100644 templatelib/test/TemplateCoreTest.kt create mode 100644 templatelib/test/TemplateHtmlTest.kt create mode 100644 templatelib/test/std/template/TemplateTestAll.java diff --git a/templatelib/src/TemplateCore.kt b/templatelib/src/TemplateCore.kt new file mode 100644 index 00000000000..883c4b1b665 --- /dev/null +++ b/templatelib/src/TemplateCore.kt @@ -0,0 +1,59 @@ +namespace std.template + +import std.io.* + +/** + * Represents a generic API to templates which should be usable + * in JavaScript in a browser or on the server side stand alone or in a web app etc. + * + * To make things easier to implement in JS this namespace won't refer to any java.io or servlet + * stuff + */ +trait Template { + var printer: Printer + + /** Renders the template to the output printer */ + fun render(): Unit +} + +/** + * Represents the output of a template which is a stream of objects + * usually strings which then write to some underlying output stream + * such as a java.io.Writer on the server side. + * + * We abstract away java.io.* APIs here to make the JS implementation simpler + */ +trait Printer { + fun print(value: Any): Unit + //fun print(text: String): Unit +} + +class NullPrinter() : Printer { + //override fun print(text: String) = none() + override fun print(value: Any) { + throw UnsupportedOperationException("No Printer defined on the Template") + } +} + +/** + * Base class for template implementations to hold any helpful behaviour + */ +abstract class TemplateSupport : Template { +} + +/** + * Base class for templates generating text output + * by printing values to some underlying Printer which + * will typically output to a String, File, stream etc. + */ +abstract class TextTemplate() : TemplateSupport, Printer { + override var printer: Printer = NullPrinter() + + fun String.plus(): Unit { + printer.print(this) + } + + //override fun print(value: String) = printer.print(value) + + override fun print(value: Any) = printer.print(value) +} diff --git a/templatelib/src/TemplateHtml.kt b/templatelib/src/TemplateHtml.kt new file mode 100644 index 00000000000..3edf78f2929 --- /dev/null +++ b/templatelib/src/TemplateHtml.kt @@ -0,0 +1,119 @@ +namespace std.template.html + +import std.* +import std.template.* +import std.io.* +import std.util.* +import java.io.* +import java.util.* + + +trait Factory { + fun create() : T +} + +abstract class Element + +class TextElement(val text : String) : Element + +abstract class Tag(val name : String) : Element { + val children = ArrayList() + val attributes = HashMap() + + protected fun initTag(init : T.()-> Unit) : T + where class object T : Factory { + val tag = T.create() + tag.init() + children.add(tag) + return tag + } +} + +abstract class TagWithText(name : String) : Tag(name) { + fun String.plus() { + children.add(TextElement(this)) + } +} + +class HTML() : TagWithText("html") { + class object : Factory { + override fun create() = HTML() + } + + fun head(init : Head.()-> Unit) = initTag(init) + + fun body(init : Body.()-> Unit) = initTag(init) +} + +class Head() : TagWithText("head") { + class object : Factory { + override fun create() = Head() + } + + fun title(init : Title.()-> Unit) = initTag(init) +} + +class Title() : TagWithText("title") + +abstract class BodyTag(name : String) : TagWithText(name) { +} + +class Body() : BodyTag("body") { + class object : Factory { + override fun create() = Body() + } + + fun b(init : B.()-> Unit) = initTag(init) + fun p(init : P.()-> Unit) = initTag(init) + fun h1(init : H1.()-> Unit) = initTag(init) + + fun a(href : String) { + a(href) {} + } + + fun a(href : String, init : A.()-> Unit) { + val a = initTag(init) + a.href = href + } +} + +class B() : BodyTag("b") +class P() : BodyTag("p") +class H1() : BodyTag("h1") + +class A() : BodyTag("a") { + var href: String? = null + /* + var href : String + get() = attributes["href"] + set(value) { attributes["href"] = value } + */ +} + +fun body(init: Body.()-> Unit): Body { + val elem = Body() + elem.init() + return elem +} + +fun html(init : HTML.()-> Unit) : HTML { + val html = HTML() + html.init() + return html +} + +/** + * Base class for templates which generate markup (XML or HTML for example) + * which have additional helper methods for escaping markup etc + */ +abstract class MarkupTemplate() : TemplateSupport { + + override fun render() { + val markup = markup() + print(markup) + } + + /** Returns the markup to be rendered */ + abstract fun markup(): Iterable +} + diff --git a/templatelib/src/TemplateJavaIo.kt b/templatelib/src/TemplateJavaIo.kt new file mode 100644 index 00000000000..a6316884f3c --- /dev/null +++ b/templatelib/src/TemplateJavaIo.kt @@ -0,0 +1,34 @@ +// Server side Java IO code to avoid coupling +// the core template code to java.* for easier JS porting +namespace std.template.io + +import std.template.* +import java.io.Writer +import java.io.OutputStream +import java.io.OutputStreamWriter +import java.io.PrintStream +import java.io.StringWriter + +/** + * A Printer implementation which uses a Writer + */ +class WriterPrinter(val writer: Writer) : Printer { + + override fun print(value: Any) { + // TODO should be using a formatter to do the conversion + writer.write(value.toString()) + } +} + +fun Template.renderToText(): String { + val buffer = StringWriter() + renderTo(buffer) + return buffer.toString().sure() +} + +fun Template.renderTo(writer: Writer): Unit { + this.printer = WriterPrinter(writer) + this.render() +} + +fun Template.renderTo(os: OutputStream): Unit = renderTo(OutputStreamWriter(os)) diff --git a/templatelib/templatelib.iml b/templatelib/templatelib.iml new file mode 100644 index 00000000000..148c33735b9 --- /dev/null +++ b/templatelib/templatelib.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/templatelib/test/TemplateCoreTest.kt b/templatelib/test/TemplateCoreTest.kt new file mode 100644 index 00000000000..8c2c7c037b8 --- /dev/null +++ b/templatelib/test/TemplateCoreTest.kt @@ -0,0 +1,54 @@ +namespace std.template + +import std.* +import std.template.io.* +import std.io.* +import std.util.* +import std.test.* +import java.util.* + +class EmailTemplate(var name: String = "James", var time: Date = Date()) : TextTemplate() { + override fun render() { + print("Hello there $name and how are you? Today is $time. Kotlin rocks") + } +} + +/** + TODO compile error + +class MoreDryTemplate(var name: String = "James", var time: Date = Date()) : TextTemplate() { + override fun render() { + +"Hey there $name and how are you? Today is $time. Kotlin rocks" + } +} +*/ + +class TemplateCoreTest() : TestSupport() { + fun testDefaultValues() { + val text = EmailTemplate().renderToText() + assert { + println(text) + text.startsWith("Hello there James") + } + } + + fun testDifferentValues() { + val text = EmailTemplate("Andrey").renderToText() + assert { + println(text) + text.startsWith("Hello there Andrey") + } + } + + /* + TODO compile error + + fun testMoreDryTemplate() { + val text = MoreDryTemplate("Alex").renderToText() + assert { + println(text) + text.startsWith("Hey there Alex") + } + } + */ +} \ No newline at end of file diff --git a/templatelib/test/TemplateHtmlTest.kt b/templatelib/test/TemplateHtmlTest.kt new file mode 100644 index 00000000000..0167e7685e7 --- /dev/null +++ b/templatelib/test/TemplateHtmlTest.kt @@ -0,0 +1,75 @@ +namespace std.template.html + +import std.* +import std.template.* +import std.template.io.* +import std.io.* +import std.util.* +import std.test.* +import java.util.* + +/* +fun result(args : List) = + html { + head { + title {+"XML encoding with Kotlin"} + } + body { + h1 {+"XML encoding with Kotlin"} + p {+"this format can be used as an alternative markup to XML"} + + // an element with attributes and text content + a(href = "http://jetbrains.com/kotlin") {+"Kotlin"} + + // mixed content + p { + +"This is some" + b {+"mixed"} + +"text. For more see the" + a(href = "http://jetbrains.com/kotlin") {+"Kotlin"} + +"project" + } + p {+"some text"} + + // content generated by + p { + for (arg in args) + +arg + } + } + } + */ + +/* +val justBody = body { + +"Hello world" + } +*/ + +class TemplateHtmlTest() : TestSupport() { + fun testNoneCompileYet() { + } + +/* + fun testHtmlFUnction() { + val text = result(arrayList("a", "b", "c")) + println(text) + } + + fun testJustBody() { + println(justBody) + } + + fun testEmbeddedFunction() { + val e = html { + head { + title {+"XML encoding with Kotlin"} + } + body { + a("http://jetbrains.com/kotlin") + } + } + println(e) + } +*/ +} \ No newline at end of file diff --git a/templatelib/test/std/template/TemplateTestAll.java b/templatelib/test/std/template/TemplateTestAll.java new file mode 100644 index 00000000000..08e1e34d138 --- /dev/null +++ b/templatelib/test/std/template/TemplateTestAll.java @@ -0,0 +1,13 @@ +package std.template; + +import std.template.html.*; + +import junit.framework.TestSuite; + +/** + */ +public class TemplateTestAll { + public static TestSuite suite() { + return new TestSuite(TemplateCoreTest.class, TemplateHtmlTest.class); + } +}