Nimja
typed and compiled template engine inspired by jinja2, twig and onionhammer/nim-templates for Nim.
Install / Use
/learn @enthus1ast/NimjaREADME
Nimja Template Engine
<p align="center"> <img style="max-width: 100%" src="https://user-images.githubusercontent.com/13794470/133277541-01de699e-9699-4d8f-b65c-595bc309a1ee.png"> </p>typed and compiled template engine inspired by jinja2, twig and onionhammer/nim-templates for Nim.
Support Nimja / Buy me a coffie 💌
FEATURES
- compiled
- statically typed
- extends (a master template)
- control structures (if elif else / case / for / while)
- import other templates
- most nim code is valid in the templates
- very fast:
# https://github.com/enthus1ast/dekao/blob/master/bench.nim
# nim c --gc:arc -d:release -d:danger -d:lto --opt:speed -r bench.nim
name ................. min time avg time std dv runs
dekao ................ 0.105 ms 0.117 ms ±0.013 x1000
karax ................ 0.126 ms 0.132 ms ±0.008 x1000
htmlgen .............. 0.021 ms 0.023 ms ±0.004 x1000
nimja ................ 0.016 ms 0.017 ms ±0.001 x1000 <--
nimja iterator ....... 0.008 ms 0.009 ms ±0.001 x1000 <--
scf .................. 0.023 ms 0.024 ms ±0.003 x1000
nim-mustache ......... 0.745 ms 0.790 ms ±0.056 x1000
DOCUMENTATION
- this readme
- generated nim docs
- my blogpost about Nimja
MOTIVATING EXAMPLE
- this example is in the example folder
- and a more complete prologue and jester example
- an example howto load templates from a shared library (dll, so)
server.nim
import asynchttpserver, asyncdispatch
import nimja/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.nimja` 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("index.nimja", baseDir = getScriptDir())
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.nimja:
{% extends partials/_master.nimja%}
{#
extends uses the master.nimja template as the "base".
All the `block`s that are defined in the master.nimja 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
extend must be the first token in the template,
only comments `{# Some foo #}` and strings are permitted to come before it.
#}
{% 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}}">{% importnimja "./partials/_user.nimja" %}</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.nimja"
#}
This INDEX was presented by.... {% importnimja "./partials/_user.nimja" %}
{% endblock footer %} {# the 'footer' in endblock is completely optional #}
partials/_master.nimja
{#
This template is later expanded from the index.nimja template.
All blocks are filled by the blocks from index.nimja
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 #}
{% importnimja "partials/_menu.nimja" %}
<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{% endblock %}
<footer>
{% block footer %}{% endblock %}
</footer>
</body>
</html>
partials/_menu.nimja:
<a href="/">index</a>
partials/_user.nimja:
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 &= ss
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 astcompileTemplateFile(path: string)compiles the content of a file to nim astgetScriptDir()returns the path to your current project, on compiletime.
compileTemplateFile
compileTemplateFile transforms the given file into the nim code.
you should use it like so:
import os # for `/`
proc myRenderProc(someParam: string): string =
compileTemplateFile("myFile.html", baseDir = getScriptDir())
echo myRenderProc("test123")
compileTemplateFile can also generate an iterator body, for details look at the
iteratior section.
compileTemplateFile (also compileTemplateString) generates the body of a proc/iterator so it generates
assign calls to a variable. The default is result.
If you want it to use another variable set it in varname
also look at:
- tmplf (inline version of this)
- tmpls
- compileTemplateStr
compileTemplateStr
compileTemplateStr compiles the given string into nim code.
proc myRenderProc(someParam: string): string =
compileTemplateStr("some nimja code {{someParam}}", baseDir = getScriptDir())
echo myRenderProc("test123")
compileTemplateStr can also generate an iterator body, for details look at the
iteratior section.
compileTemplateString (also compileTemplateFile) generates the body of a proc/iterator so it generates
assign calls to a variable. The default is result.
If you want it to use another variable set it in varname
baseDir is needed when you want to import/extend templates!
A context can be supplied to the compileTemplateString (also compileTemplateFile), to override variable names:
block:
type
Rax = object
aa: string
bb: float
var rax = Rax(aa: "aaaa", bb: 13.37)
var foo = 123
proc render(): string =
compileTemplateString("{{node.bb}}{{baa}}", {node: rax, baa: foo})
Please note, currently the context cannot be procs/funcs etc.
also look at:
- tmpls (inline version of this)
- tmplf
- compileTemplateFile
if / elif / else
{% if aa == 1 %}
aa is: one
{% elif aa == 2 %}
aa is: two
{% else %}
aa is something else
{% endif %}
when / elif / else
when is the compile time if statement.
It has the same semantic than if
{% when declared(isDeclared) %}
isDeclared
{% elif true == true %}
true
{% else %}
something else
{% endwhen %}
case / of / else
(Since Nimja 0.8.1)
case has the same semantic as the nim case statement.
Use case for example if you want to make sure that all cases are handled.
If not all cases are covered, an error is generated.
{%- case str -%}
{%- of "foo" -%}
foo
{%- of "baa" -%}
baa
{%- of "baz" -%}
baz
{%- else -%}
nothing
{%- endcase -%}
tmpls / tmplf
Related Skills
node-connect
346.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
346.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
346.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
