Skip to content
forked from enthus1ast/nimja

typed and compiled template engine inspired by jinja2, twig and onionhammer/nim-templates for Nim.

License

Notifications You must be signed in to change notification settings

lguzzon-NIM/nimja

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nimja Template Engine

typed and compiled template engine inspired by jinja2, twig and onionhammer/nim-templates for Nim.

FEATURES

  • compiled
  • statically typed
  • extends (a master template)
  • control structures (if elif else / for / while)
  • import other templates
  • most nim code is valid in the templates

DOCUMENTATION

MOTIVATING EXAMPLE

server.nim

import asynchttpserver, asyncdispatch
import ../src/parser
import os, random # os and random are later used in the templates, so imported here

type
  User = object
    name: string
    lastname: string
    age: int

proc renderIndex(title: string, users: seq[User]): string =
  ## the `index.nwt` template is transformed to nim code.
  ## so it can access all variables like `title` and `users`
  ## the return variable could be `string` or `Rope` or
  ## anything which has a `&=`(obj: YourObj, str: string) proc.
  compileTemplateFile(getCurrentDir() / "index.nwt")

proc main {.async.} =
  var server = newAsyncHttpServer()

  proc cb(req: Request) {.async.} =

    # in the templates we can later loop trough this sequence
    let users: seq[User] = @[
      User(name: "Katja", lastname: "Kopylevych", age: 32),
      User(name: "David", lastname: "Krause", age: 32),
    ]
    await req.respond(Http200, renderIndex("index", users))

  server.listen Port(8080)
  while true:
    if server.shouldAcceptRequest():
      await server.acceptRequest(cb)
    else:
      poll()

asyncCheck main()
runForever()

index.nwt:

{% extends partials/_master.nwt%}
{#
  extends uses the master.nwt template as the "base".
  All the `block`s that are defined in the master.nwt are filled
  with blocks from this template.

  If the templates extends another, all content HAVE TO be in a block.

  blocks can have arbitrary names

  currently the extends must be on the FIRST LINE!
#}


{% block content %}
  {# A random loop to show off. #}
  {# Data is defined here for demo purpose, but could come frome database etc.. #}
  <h1>Random links</h1>
  {% const links = [
    (title: "google", target: "https://google.de"),
    (title: "fefe", target: "https://blog.fefe.de")]
  %}
  {% for (ii, item) in links.pairs() %}
    {{ii}} <a href="{{item.target}}">This is a link to: {{item.title}}</a><br>
  {% endfor %}

  <h1>Members</h1>
    {# `users` was a param to the `renderIndex` proc #}
    {% for (idx, user) in users.pairs %}
        <a href="/users/{{idx}}">{% importnwt "./partials/_user.nwt" %}</a><br>
    {% endfor %}
{% endblock %}

{% block footer %}
  {#
    we can call arbitraty nim code in the templates.
    Here we pick a random user from users.
  #}
  {% var user = users.sample() %}

  {#
    imported templates have access to all variables declared in the parent.
    So `user` is usable in "./partials/user.nwt"
  #}
  This INDEX was presented by.... {% importnwt "./partials/_user.nwt" %}
{% endblock footer %} {# the 'footer' in endblock is completely optional #}

master.nwt

{#

  This template is later expanded from the index.nwt template.
  All blocks are filled by the blocks from index.nwt

  Variables are also useable.
 #}
<html>
<head>
  <title>{{title}}</title>
</head>
<body>

<style>
body {
  background-color: aqua;
  color: red;
}
</style>

{# The master can declare a variable that is later visible in the child template #}
{% var aVarFromMaster = "aVarFromMaster" %}

{# We import templates to keep the master small #}
{% importnwt "partials/_menu.nwt" %}

<h1>{{title}}</h1>

{# This block is filled from the child templates #}
{%block content%}{%endblock%}


{#
  If the block contains content and is NOT overwritten later.
  The content from the master is rendered
#}
{% block onlyMasterBlock %}Only Master Block (does it work yet?){% endblock %}

<footer>
  {% block footer %}{% endblock %}
</footer>

</body>
</html>

partials/_menu.nwt:

<a href="/">index</a>

partials/_user.nwt:

User: {{user.name}} {{user.lastname}} age: {{user.age}}

Basic Syntax

  • {{ myObj.myVar }} --transformed-to---> $(myObj.myVar)
  • {% myExpression.inc() %} --transformed-to---> myExpression.inc()
  • {# a comment #}

How?

nimja transforms templates to nim code on compilation, so you can write arbitrary nim code.

proc foo(ss: string, ii: int): string =
  compileTemplateStr(
    """example{% if ii == 1%}{{ss}}{%endif%}{% var myvar = 1 %}{% myvar.inc %}"""
  )

is transformed to:

proc foo(ss: string; ii: int): string =
  result &= "example"
  if ii == 1:
    result &= "one"
  var myvar = 1
  inc(myvar, 1)

this means you have the full power of nim in your templates.

USAGE

there are only three relevant procedures:

  • compileTemplateStr(str: string) compiles a template string to nim ast
  • compileTemplateFile(path: string) compiles the content of a file to nim ast
  • getScriptDir() returns the path to your current project, on compiletime.

if / elif / else

{% if aa == 1 %}
  aa is: one
{% elif aa == 2 %}
  aa is: two
{% else %}
  aa is something else
{% endif %}

for

{% for (cnt, elem) in @["foo", "baa", "baz"].pairs() %}
  {{cnt}} -> {{elem}}
{% endfor %}

while

{% while isTrue() %}
  still true
{% endwhile %}
{% var idx = 0 %}
{% while idx < 10 %}
  still true
  {% idx.inc %}
{% endwhile %}

comments

{# single line comment #}
{#
  multi
  line
  comment
#}
{# {% var idx = 0 %} #}

"to string" / output

declare your own $ before you call compileTemplateStr() or compileTemplateFile() for your custom objects. For complex types it is recommend to use the method described in the importnwt section.

{{myVar}}
{{someProc()}}

importnwt

import the content of another template. The imported template has access to the parents variables. So it's a valid strategy to have a "partial" template that for example can render an object or a defined type. Then include the template wherever you need it:

best practice is to have a partials folder, and every partial template begins with an underscore "_" all templates are partial that do not extend another template and therefore can be included.

This way you create reusable template blocks to use all over your webpage.

partials/_user.nwt:

<div class="col-3">
  <h2>{{user.name}}</h2>
  <ul>
    <li>Age: {{user.age}}</li>
    <li>Lastname: {{user.lastname}}</li>
  </ul>
</div>

partials/_users.nwt:

<div class="row">
  {% for user in users: %}
    {% importnwt "partials/_user.nwt" %}
  {% endfor %}
</div>

extends

a child template can extend a master template. So that placeholder blocks in the master are filled with content from the child.

partials/_master.nwt

<html>
<body>
A lot of boilerplate
{% block content %}{% endblock %}
<hr>
{% block footer %}{% endblock %}
</body>
</html>

child.nwt

{% extends "partials/_master.nwt" %}
{% block content %}I AM CONTENT{% endblock %}
{% block footer %}...The footer..{% endblock %}

if the child.nwt is compiled then rendered like so:

proc renderChild(): string =
  compileTemplateFile("child.nwt")

echo renderChild()

output:

<html>
<body>
A lot of boilerplate
I AM CONTENT
<hr>
...The footer..
</body>
</html>

Compile / Use

This is a COMPILED template engine. This means you must recompile your application for every change you do in the templates!

Automatic recompilation and/or hot code reloading is a planned feature.

nim c -r yourfile.nim

sometimes, nim does not catch changes to template files. Then compile with "-f" (force)

nim c -f -r  yourfile.nim

About

typed and compiled template engine inspired by jinja2, twig and onionhammer/nim-templates for Nim.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Nim 96.3%
  • HTML 3.7%