SkillAgentSearch skills...

Nimja

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

Install / Use

/learn @enthus1ast/Nimja
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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

test

  • 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

MOTIVATING EXAMPLE

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 ast
  • compileTemplateFile(path: string) compiles the content of a file to nim ast
  • getScriptDir() 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:

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:

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

View on GitHub
GitHub Stars216
CategoryDevelopment
Updated9d ago
Forks15

Languages

Nim

Security Score

100/100

Audited on Mar 24, 2026

No findings