GithubHelp home page GithubHelp logo

koreander's Introduction

Version Build Status Coverage Status

Koreander

Koreander is a HTML template engine for Kotlin with a clean elegant haml inspired syntax.

Quick Example

Code

data class ViewModel(val name: String)

val viewModel = ViewModel("world")
val input = File("input.kor")
val output = Koreander().render(input, viewModel)

input.kor

%html
    %body
        .content Hello ${name.capitalized()}!

->

<html>
    <body>
        <div class="content">Hello World!</div>
    </body>
</html>

Introduction

Koreander is a HTML template engine for Kotlin. Html tags are defined by an indent based syntax that is similar to pug (JavaScript), jade4j (Java) or slim/haml (Ruby). Templates are executed in the context of a view model class from where the properties and methods can be accessed in pure Kotlin code.

By combining the simplicity of Kotlin and HAML, the Koreander template syntax is simply beautiful. Koreander templates are also type-safe!! and have excellent performance due to JVM compilation.

Installation

Using Koreander is as simple as adding a few lines to your packaging tool. For Spring integration see below.

Gradle

repositories {
    maven {
        url  "https://dl.bintray.com/lukasjapan/de.cvguy.kotlin"
    }
}

dependencies {
    implementation 'de.cvguy.kotlin:koreander:0.1.0'
}

Maven

TBA

Usage

An introduction of how to use the Koreander template engine.

Code

Just create the view model (instance) and pass it to the render function of a Koreander instance along with the template. The template can be passed as String, File, URL or Input Stream.

import de.cvguy.kotlin.koreander.Koreander

data class Beer(
        val name: String,
        val manufacturer: String,
        val alc: Double
)

data class ViewModel(
        val title: String,
        val beers: List<Beer>
)

fun main(args: Array<String>) {
    val koreander = Koreander()

    val input = File("input.kor").readText()

    val viewModel = ViewModel(
            "Japanese Beers",
            listOf(
                    Beer("Asahi Super Dry", "Asahi Breweries Ltd ", 0.05),
                    Beer("Kirin Ichiban Shibori", "Kirin Brewery Company, Limited", 0.05),
                    Beer("Yebisu", "Sapporo Breweries Ltd.", 0.05),
                    Beer("Sapporo Black Label", "Sapporo Breweries Ltd.", 0.05),
                    Beer("The Premium Malts", "Suntory", 0.055),
                    Beer("Kirin Lager", "Kirin Brewery Company, Limited", 0.049)
            )
    )

    val output = koreander.render(input, viewModel)

    println(output)
}

Template Syntax Summarization

Explanation in detail about the Koreander template syntax can be found below.

Here are the main points summarized to get you started:

  • HTML tags are expressed by a % and are closed automatically
    • %tag<tag></tag>
    • %tag content<tag>content</tag>
  • Lines with deeper indent are included inside the next less deep tag
%outertag
    %innertag content

<outertag>
    <innertag>content</innertag>
</outertag>
  • No closing tags for Void Elements
    • %br something<br>something
  • Attributes can be written right after tags
    • %tag with=attribute content<tag with="attribute">content</tag>
  • There are shortcuts for id (#) and class (.) attribute
    • %tag#myid<tag id="myid"></tag>
    • %tag.myclass<tag class="myclass"></tag>
    • %tag#myid.myclass<tag id="myid" class="myclass"></tag>
  • If used, div tags may be omitted
    • #myid<div id="myid"></div>
    • .myclass<div class="myclass"></div>
    • #myid.myclass<div id="myid" class="myclass"></div>
  • Texts are evaluated as Kotlin string templates, therefore Kotlin code can be inserted (almost) anywhere
    • %tag one plus one is ${1 + 1}<tag>one plus one is 2</tag>
    • %tag oneplusone=is${1 + 1}<tag oneplusone="is2"></tag>
  • Code is executed as if it would be inside the view model class
    • %tag content ${functionOfViewModel()}<tag>content xxxxx</tag>
    • %tag content $propertyOfViewModel<tag>content xxxxx</tag>
  • Code only lines can be expressed by a leading -
    • - invokeFunctionOfViewModel()
  • Deeper indented lines after code are passed to the code as a block
%ul
    - $collectionPropertyOfViewModel.forEach
        %li This is ${it}!
    - $collectionPropertyOfViewModel.forEach -> item
        %li This is ${item}!

<ul>
        <li>This is xxxx</li>
        <li>This is xxxx</li>
        <li>This is xxxx</li>
        ...
</ul>
  • Filters can be used for non-html input
:js
    var name = "World"
    console.log("Hello " + name");

<script type="test/javascript">
var name = "World"
console.log("Hello " + name");
</script>

Example Template

Using the code above, a template saved as input.kor ...

!!! 5
%html
    %head
        %link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"
    %body
        .container
            %h1 ${title}
            %table.table.table-striped
                %thead
                    %tr
                        %th Name
                        %th Manufacturer
                        %th Alc. percentage
                %tbody
                    - beers.forEach
                        %tr
                            %td ${it.name}
                            %td ${it.manufacturer}
                            %td ${"%.2f".format(it.alc * 100.0)}%

... will generate the following output:

<!DOCTYPE html>
<html>
    <head>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="container">
            <h1>Japanese Beers</h1>
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Manufacturer</th>
                        <th>Alc. percentage</th>
                    </tr>
                </thead>
                <tbody>
                        <tr>
                            <td>Asahi Super Dry</td>
                            <td>Asahi Breweries Ltd </td>
                            <td>5.00%</td>
                        </tr>
                        <tr>
                            <td>Kirin Ichiban Shibori</td>
                            <td>Kirin Brewery Company, Limited</td>
                            <td>5.00%</td>
                        </tr>
                        <tr>
                            <td>Yebisu</td>
                            <td>Sapporo Breweries Ltd.</td>
                            <td>5.00%</td>
                        </tr>
                        <tr>
                            <td>Sapporo Black Label</td>
                            <td>Sapporo Breweries Ltd.</td>
                            <td>5.00%</td>
                        </tr>
                        <tr>
                            <td>The Premium Malts</td>
                            <td>Suntory</td>
                            <td>5.50%</td>
                        </tr>
                        <tr>
                            <td>Kirin Lager</td>
                            <td>Kirin Brewery Company, Limited</td>
                            <td>4.90%</td>
                        </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

Template Syntax

In depth explanation of the template syntax.

<!DOCTYPE> declaration

A doctype can be specified in the first line of the template. The following can be used:

Identifier Doctype
!!! <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
!!! Strict <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
!!! Frameset <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
!!! 5 <!DOCTYPE html>
!!! 1.1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
!!! Basic <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
!!! Mobile <!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">
!!! RDFa <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">

HTML Tags

TBA

Void Elements

Void elements are not allowed to have content and its closing tag is omitted. Currently, regardless of the doctype the alternate closing notations slash (<br/>) or space-slash (<br />) are not used.

List of void elements:

  • area
  • base
  • br
  • col
  • command
  • embed
  • hr
  • img
  • input
  • keygen
  • link
  • meta
  • param
  • source
  • track
  • wbr

Attributes

TBA

ID shortcut # and class shortcut .

TBA

Text content

TBA

Output =

TBA

Control code -

TBA

Comment /

TBA

Filters

Filters can process custom input and are expected to transform it into valid HTML. To pass input from the template to a filter, start a line or text with a colon followed by the filter identifier.

Input can be passed directly after the identifier on a single line or as a deeper indented block. When passed as a block, the indent is cleaned from the input before being processed by the filter and then added back to the output.

Filter identifiers must be completely in alphabetic lower case letters.

Ex. 1 - Pass input as Line

%html
    %body
        :js alert("Hello World!")

or even

%html
    %body :js alert("Hello World!")

Ex. 2 - Pass input as Block

%html
    %head
        :css
            body {
                color: red;
            }

Build in filters:

Identifier Name Description
unsafehtml Unsafe HTML Filter Passes the input as it is, effectively bypassing HTML escaping.
js Inline Javascript Filter Surrounds the input with a script tag.
css Inline Style Filter Surrounds the input with a style tag.
Custom Filters

Warning: In general, implement custom logic inside the view model rather than using filters.

Implement the KoreanderFilter interface:

class MyCustomFilter : KoreanderFilter {
    override fun filter(input: String): String {
        return "<p>Do something fancy with ${input} here.</p>"
    }
}

Add the filter to the .filters property of the Koreander class:

val koreander = Koreander()
koreander.filters["mycustom"] = MyCustomFilter()

Usage:

%html
    %body :mycustom Process me!

Support

Examples for integration with popular libraries can be found below.

Koreander is currently JVM only.

Spring

Ktor

Syntax Highlighters

TBA

Contributing

TBA?

License

Korander is released under the MIT license.

Authors

  • Lukas Prasuhn

koreander's People

Contributors

frosner avatar lukasjapan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

koreander's Issues

Shortcut notation for assigning classes ignored

Heya Lukas,

Love the syntax, but I've ran into an issue with the shortcut notation for assigning classes.

According to the readme it should be possible to use %tag.myclass → <tag class="myclass"></tag>.
However, when parsing %span.test test span with test class the result is
<span.test>test span with test class</span.test> instead.

Would be awesome if you could try to replicate,
Many thanks in advance and a fantastic holiday season,
fish

template.kor:

!!! 5
%html
  %head
    %title Test title

  %body
    .test
      %p test div with test class
      %span.test test span with test class

app.kt:

data class Model(val name: String = "")

class app {
    fun generateHtml(): String {
        val viewUrl = javaClass.getResource("template.kor")
        return Koreander().render(viewUrl, Model())
    }
}

Unable to create HTML 5 template

1. Reproducable example:

  • Create main.kt and put following code:
    fun main() {
        val kor = javaClass.getResource("/index.kor").readText()
        Koreander().render(kor, Any())
    }
  • Create index.kor in resource directory in src/main folder of project and put the following code:
    !!! 5
    %html
        %head
        %body
            %p.hello Hello World!
    

2. Expected result:

Expected to see the <!DOCTYPE html> declaration instead of the exception.

3. Actual result:

de.cvguy.kotlin.koreander.exception.UnexpectedDocType: Parse error in tba at 0:4 - Unexpexted DocType 5 exception thrown

4. StackTrace:

de.cvguy.kotlin.koreander.exception.UnexpectedDocType: Parse error in tba at 0:4 - Unexpexted DocType 5
	at de.cvguy.kotlin.koreander.parser.KoreanderParseEngine.unshiftDocType(Parser.kt:169)
	at de.cvguy.kotlin.koreander.parser.KoreanderParseEngine.parse(Parser.kt:79)
	at de.cvguy.kotlin.koreander.parser.KoreanderParser.generateScriptCode(Parser.kt:17)
	at de.cvguy.kotlin.koreander.Koreander.compile(Koreander.kt:81)
	at com.github.animeshz.konvironment.http.RoutingKt$module$1$1.invokeSuspend(Routing.kt:27)
	at com.github.animeshz.konvironment.http.RoutingKt$module$1$1.invoke(Routing.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.routing.Routing.executeResult(Routing.kt:147)
	at io.ktor.routing.Routing.interceptor(Routing.kt:34)
	at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:99)
	at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118)
	at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
	at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
	at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161)
	at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
	at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:40)
	at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:111)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
	at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
	at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:30)
	at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:24)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
	at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:56)
	at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:365)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:510)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:518)
	at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:834)

Head and p tags closing incorrectly

Heya Lukas,
The Japanese beers example from the readme seems to be closing the <head> tag incorrectly.
Rather than:

<!DOCTYPE html>
<html>
    <head>
        ...
    </head>
    <body>
        ...
    </body>
</html>

The generated HTML looks like (inspect the payload, not the HTML inspector in the browser):

<!DOCTYPE html>
<html>
    <head>
        ...
    <body>
        ...
    </body>
    </head>
</html>

I've observed a similar issue with %p tags that include a linebreak. For example:

!!! 5
%html
    %body
        %p
            line 1
            line 2
            line 3
        %div

Renders correctly, but introducing a linebreak in the paragraph:

!!! 5
%html
    %body
        %p
            line 1
            %br
            line 2
        %div

Instead results in:

<!DOCTYPE html>
<html>
    <body>
        <p>
            line 1
            <br>
            line 2
        <div></div>
        </p>
    </body>
</html>

Am I nesting the linebreak incorrectly in the paragraph or are there some inconsistencies in how the indentations are read?

Many thanks,
fish

Koreander templates are not rendered via SpringBoot and fat JARs

I am currently using Spring 5 and Koreander as the template engine. I am unfortunately getting a java.lang.ArrayIndexOutOfBoundsException: Index 10912 out of bounds for length 10912] error, at de.cvguy.kotlin.koreander.Koreander.compile(Koreander.kt:62) ~[koreander-0.1-WIP.jar:na].

I am using a fat JAR, which requires the Kotlin compiler to be unpacked at startup, via:

bootJar { requiresUnpack '**/kotlin-compiler-*.jar' }

My Spring application looks like:

@SpringBootApplication class TestApplication { @Bean fun koreanderViewResolver(): ViewResolver = KoreanderViewResolver() }

And the corresponding controller:

@Controller class DemoController { @GetMapping("/demo") fun demo(): String { return "example" } }

I'm using compile "de.cvguy.kotlin:koreander-spring:0.+" via Gradle.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.