Git Product home page Git Product logo

nimja's Introduction

Nimja Template Engine

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

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(getScriptDir() / "index.nimja")

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 #}

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(getScriptDir() / "myFile.html")

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}}")

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

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

compileTemplateStr and compileTemplateFile both need a surrounding proc. tmpls (template str) and tmplf (template file) are a shorthand for these situations where you want to inline a render call.

let leet = 1337
echo tmpls("foo {{leet}}")
echo tmplf(getScriptDir() / "templates" / "myfile.nimja")

A context can be supplied to the template, to override variable names:

block:
  type
    Rax = object
      aa: string
      bb: float
  var rax = Rax(aa: "aaaa", bb: 13.37)
  var foo = 123
  tmpls("{{node.bb}}{{baa}}", {node: rax, baa: foo})

Please note, currently the context cannot be procs/funcs etc.

for

{% for (cnt, elem) in @["foo", "baa", "baz"].pairs() %}
  {{cnt}} -> {{elem}}
{% endfor %}
{% for elem in someObj.someIter() %}
  {# `elem` is accessible from the "some/template.nimja" #}
  {# see importnimja section for more info #}
  {% importnimja "some/template.nimja" %}
{% 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 importnimja section.

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

importnimja

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.nimja:

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

partials/_users.nimja:

<div class="row">
  {% for user in users: %}
    {% importnimja "partials/_user.nimja" %}
  {% 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.nimja

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

child.nimja

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

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

proc renderChild(): string =
  compileTemplateFile(getScriptDir() / "child.nimja")

echo renderChild()

output:

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

scope

A scope has the same semantic as a nim block. Variables declared inside a scope are not visible on the outside. You can use this for code hygiene.

{% scope %}
  {% let httpMethod = "POST" %}
  {{ httpMethod }}
{% endscope %}
{# httpMethod is not accesible any more, you can define it again. #}
{% let httpMethod = "FOO" %}
{{ httpMethod }}

You can break out of a named scope prematurely

{%- scope foo -%}
  foo
  {%- break foo -%}
  baa
{%- endscope -%}

in this case only "foo" is printed.

self variable

Jinja describes them like so, we can do the same:

You can't define multiple {% block %} tags with the same name in the same template. This limitation exists because a block tag works in "both" directions. That is, a block tag doesn't just provide a placeholder to fill - it also defines the content that fills the placeholder in the parent. If there were two similarly-named {% block %} tags in a template, that template's parent wouldn't know which one of the blocks content to use.

If you want to print a block multiple times, you can, however, use the special self variable and call the block with that name:

<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title }}</h1>
{% block body %}{% endblock %}

To change the specialSelf variable name compile with eg.:

nim c -d:specialSelf="blocks." file.nim

procedures (macro)

Procedures can be defined like so:

{% proc foo(): string = %}
  baa
{% endproc %}
{{ foo() }}
{% proc input(name: string, value="", ttype="text"): string = %}
    <input type="{{ ttype }}" value="{{ value }}" name="{{ name }}">
{% endproc %}
{{ input("name", "value", ttype="text") }}

Func's have the same semantic as nim funcs, they are not allowed to have a side effect.

{% func foo(): string = %}
  baa
{% endfunc %}
{{ foo() }}

macro is an alias for proc

{% macro textarea(name, value="", rows=10, cols=40): string = %}
    <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
        }}">{{ value }}</textarea>
{% endmacro %}
{{ textarea("name", "value") }}

for {{func}} {{proc}} and {{macro}} either the {{end}} tag or the {{endfunc}} {{endproc}} {{endmacro}} are valid closing tags.

Importing func/proc/macro from a file

Importing works like any other ordinary Nimja templates with ìmportnimja. Good practice is to define procs with the "whitespacecontrol":

myMacros.nimja

{%- proc foo(): string = %}foo{% end -%}
{%- proc baa(): string = %}baa{% end -%}

myTemplate.nimja

{% importnimja "myMacros.nimja" %}

When a template extends another template, importnimja statements must be in a block they cannot stand on their own. It might be a good idea to import these "library templates" in the extended template (eg.: master.nimja).

Iterator

Expanded template bodies can also be created as an iterator, therefore the generated strings are not concatenated to the result result &= "my string" but are yielded.

This could be used for streaming templates, or to save memory when a big template is rendered and the http server can send data in chunks:

iterator yourIter(yourParams: bool): string =
  compileTemplateString("{%for idx in 0 .. 100%}{{idx}}{%endfor%}", iter = true)

for elem in yourIter(true):
  echo elem

Whitespace Control

###############
{% if true %}
  <li>   {{foo}}   </li>
{% endif %}
###############

is expanded to:

###############

  <li>   FOO   </li>

###############

the nimja template control statements leave their newline and whitespace when rendered. To fix this you can annotate them with "-":

###############
{% if true -%}
  <li>   {{-foo-}}   </li>
{%- endif %}
###############
###############
<li>FOO</li>
###############

Nimjautils

The optional nimjautils module, implements some convenient procedures.

import nimja/nimjautils

Mainly:

Loop variable/iterator

yields a Loop object with every item. Inside the loop body you have access to the following fields. Unlike jinja2 or twig where the loop variable is implicitly bound and available, we must use the loop() iterator explicity.

{% for (loop, row) in rows.loop() %}
    {{ loop.index0 }} {# which elemen (start from 0) #}
    {{ loop.index }} {# which element (start from 1) #}
    {{ loop.revindex0 }} {# which element, counted from the end (last one is 0) #}
    {{ loop.revindex }} {# which element, counted from the end (last one is 1) #}
    {{ loop.length }} {# the length of the seq, (same as mySeq.len()) #}
    {% if loop.first %}The first item{% endif %} {# if this is the first loop iteration #}
    {% if loop.last %}The last item{% endif %} {# if this is the last loop iteration #}
    {% if loop.previtem.isSome() %}{{ loop.previtem.get() }}{% endif %} {# get the item from the last loop iteration #}
    {% if loop.nextitem.isSome() %}{{ loop.nextitem.get() }}{% endif %} {# get the item from the next loop iteration #}
    <li class="{{ loop.cycle(@["odd", "even"]) }}">{{row}}</li>
{% endfor %}

however, the element you iterate over must match the Concept Loopable. #23 This means you can propably not use loop() with an iterator, since they do not have a len() and []

Cycle

within a loop you can cycle through elements:

{% for (loop, row) in rows.loop() %}
    <li class="{{ loop.cycle(@["odd", "even"]) }}">{{ row }}</li>
{% endfor %}

'~' (tilde)

Converts all operands into strings and concatenates them. like: $aa & $bb

{{ "Hello " ~ name ~ "!" }}

would return (assuming name is set to 'Nim') Hello Nim!.

includeRaw

Includes the content of a file literally without any parsing Good for documentation etc..

proc test(): string =
  let path = (getScriptDir() / "tests/basic" / "includeRawT.txt")
  compileTemplateStr("""pre{{ includeRaw(path) }}suf""")

raw strings

to include raw strings, or nimja code itself to a template (for documentation purpose), you could use this construct {{"raw code"}}

proc foo(): string =
  compileTemplateStr("""
    foo {{"{%if true%}baa{%endif%}"}}
  """)

this would then be rendered like so:

foo {%if true%}baa{%endif%}

includeRawStatic

Includes the content of a file literally without any parsing, on compiletime. This means it is included into the executable.

includeStaticAsDataurl

Includes the content of a file on compile time, it is converted to a data url. Eg:

  <img src="{{includeStaticAsDataurl(getScriptDir() / "logo.jpg")}}">

is transformed to:

  <img src="data:image/jpeg;charset=utf-8;base64,/9j/4AAQSkZJRg..."/>

truncate

truncates a string to "num" characters. when the string was truncated it appends the suf to the text. if preserveWords is true it will not cut words in half but the output string could be shorter than num characters.

proc truncate*(str: string, num: Natural, preserveWords = true, suf = "..."): string
let lorem = "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Rem voluptates odio tempore voluptas beatae eum consequatur laudantium totam. Delectus fuga eveniet ab cum nulla aperiam iste ducimus odio fugit voluptas."

proc test(lorem: string): string =
  compileTemplateStr("{{lorem.truncate(65)}}")
assert test(lorem) == "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Rem..."

nl2br

Converts newline to <br>. If keepNL == true, the one \n is replaced by <br>\n thus keeping the newlines.

func nl2br*(str: string, keepNl = true): string =
assert "foo\nbaa".nl2br == "foo<br>\nbaa"

spaceless

Removes unneeded whitespaces between html tags, warning, this is NOT smart. So it will destroy <textarea> and <pre> content!

  check "<foo>\n\nbaa  </foo>".spaceless == "<foo> baa </foo>"
  check "<foo tag='tag tag'>\n\nbaa  </foo>".spaceless == "<foo tag='tag tag'> baa </foo>"
  check "<foo>baa  baz</foo>".spaceless == "<foo>baa baz</foo>"

slugify

converts any string to an url friendly one. Removes any special chars and replaces non ASCII runes to their ASCII representation.

slugify("Lession learned german umlauts: öüä")

will output:

lession-learned-german-umlauts-oua

let allowedCharsInSlug = Letters + Digits
proc slugify*(str: string, sperator = "-", allowedChars = allowedCharsInSlug): string =

shorthand if ?

a shorthand for a condition, this could be used for example to toggle html classes:

proc foo(isDisabled: bool): string =
  compileTemplateStr("""{% ?isDisabled: "disabled" %}""")
check "disabled" == foo(true)
check "" == foo(false)

filter |

a | b is an alias to a.b this is often used in other template engines.

proc foo(): string =
  compileTemplateStr("""{{"foo baa baz" | slugify}}""")
check foo() == "foo-baa-baz"

Want to hack?

if you need more utils in nimjautils, please PR! they should all be quite easy to implement, so they make up a good first issue/pull request!

a good inspiration WHAT to hack is jinja and twig filters.

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 / hot code reloading / dynamic execution is a planned feature. see the Automatic Recompilation / Hot Code Reloading (hcr) section

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

Automatic Recompilation / Hot Code Reloading (hcr)

(Still an experimental feature, help wanted.) Automatic Recompilation enables you to change your templates and without recompiling your application, see the changes lives.

How it works:

Nimja compiles your templates (and template render functions) to a shared library (.so/.dll/.dynlib), then your host application loads this library, then on source code change, the shared library is unloaded from your host, recompiled, and loaded again.

This is normally way faster, than recompiling your whole application.

For this to work, Nimja now contains a small file watcher, you must utilize this tool in your own application.

You also must restructure you application a little bit, all you render functions must be in a separate file, this file is then compiled to a shared lib and loaded by your host.

When you go live later, you can just disable the recompilation, and compile the shared library for release, it should be very fast as well.

Below is a minimal example, a more complete example is in the example folder

Minimal example:

host.nim

# this is the file that eg. implements your webserver and loads
# the templates as a shared lib.
import nimja/hcrutils # Nimja's hot code reloading utilities
import jester, os

# We watch the templates folder for change (and also tmpls.nim implicitly)
var cw = newChangeWatcher(@[getAppDir() / "templates/"])
asyncCheck cw.recompile() # if a change is detected we recompile tmpls.nim

type
    # You must declare the proc definition from your tmpls.nim here as well.
    ProcNoParam = proc (): string {.gcsafe, stdcall.}
    ProcId = proc (id: string): string {.gcsafe, stdcall.}

routes:
  get "/":
    resp dyn(ProcNoParam, "index")

  get "/id/@id":
    resp dyn(ProcId, "detail", @"id")

tmpls.nim

# this file contains you render functions
# is compiled to a shared lib and loaded by your host application
# to keep compilation fast, use this file only for templates.
# this file is also watched by the filewatcher.
# It can also be changed dynamically!
import nimja
import os # for `/`

proc index*(): string {.exportc, dynlib.} =
  var foos =  1351 # change me i'm dynamic :)
  compileTemplateFile(getScriptDir() / "templates/index.nimja")

proc detail*(id: string): string {.exportc, dynlib.} =
  compileTemplateFile(getScriptDir() / "templates/detail.nimja")

templates/

templates/partials/_master.nimja

<head>
  <title>Hello, world!</title>
</head>
<body>
  <h1><a href="/">Nimja dynamic test</a></h1>
  <div>
    {% block content %}{% endblock %}
  </div>
  </body>
</html>

templates/index.nimja

{% extends "templates/partials/_master.nimja" %}
{% block content %}

<h1>Hello, world! {{foos}}</h1>

index

{% for idx in 0..100 %}
  <a href="/id/{{idx}}">{{idx}}</a>
{%- endfor %}

{% endblock %}

templates/detail.nimja

{% extends "templates/partials/_master.nimja" %}
{% block content %}
detail
<a href="/id/{{id}}">{{id}}</a>
{% endblock %}

you can now change any of the templates or the tmpls.nim files. Later if you wanna go live, comment out the

asyncCheck cw.recompile() # if a change is detected we recompile tmpls.nim

line.

Nimja Template VSCode Syntax Color Formatting

If you are using VSCode to develop your nim app, you can still associate nimja template files for color syntax and formating with vscode as an html file. Add this segment to your settings.json in vscode:

  "files.associations": {
    "*.nwt": "html", // Nimja deprecated templates
    "*.nimja": "html", // Nimja new templates
  },

Debugging

nim c -d:dumpNwtAst -r yourfile.nim # <-- dump NwtAst
nim c -d:dumpNwtAstPretty -r yourfile.nim # <-- dump NwtAst as pretty json
nim c -d:nwtCacheOff -r yourfile.nim   # <-- disables the NwtNode cache
nim c -d:noPreallocatedString -r yourfile # <-- do not preallocate the output string
nim c -d:noCondenseStrings -r yourfile.nim # <-- disables string condense see #12
nim c -d:dumpNwtMacro -r yourfile.nim # <-- dump generated Nim macros

Changelog

TODO

  • 0.8.?
    • Added context to importnimja

DONE

  • 0.8.7
    • Removed unused NImport.
    • Error on uneven when blocks.
  • 0.8.6
  • 0.8.5 Nimja exports os, for /
  • 0.8.4 Fixed string escaping.
  • 0.8.3 Added scope and endscope
  • 0.8.2 Readme fixes and tests for break and continue for the for loop
  • 0.8.1 Added case of and endcase
  • 0.8.0
    • Breaking change!
    • Changed context to template syntax context = {foo: baa}
    • Added context to compileTemplateStr and compileTemplateFile
  • 0.7.0 Added context to tmpls and tmplf
  • 0.6.9 Added when compile time if
  • 0.6.8 Added importnimja deprecated importnwt (importnwt is still valid for now)
  • 0.6.7 Removed the ".nwt" extention everywhere, we go with ".nimja" now.
  • 0.6.6 Preallocate the minimal known output length if result is string.
  • 0.6.5 Condense strings of extended templates (less assigns -> better runtime performance).
  • 0.6.1 No codegen for empty string nodes after whitespaceControl.
  • 0.5.6 Added {{endfunc}} {{endproc}} {{endmacro}} for consistency.
  • 0.5.5 Added tmpls and tmplf procs to use inline.
  • 0.5.1 Added self variable, to print blocks multiple times
  • 0.5.0 Added hot code reloading.
  • 0.4.2 Added includeRawStatic and includeStaticAsDataurl

nimja's People

Contributors

adyxax avatar enthus1ast avatar foxoman avatar jiro4989 avatar lguzzon avatar philippmdoerner avatar xaqbr 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

nimja's Issues

Replacement icon

No offense to you or whoever drew the project icon, but I personally find it a bit unsettling. I came up with a new one that you are free to use if you like it better. If not, that's cool too. I have included the original svg in a zip in case you choose to use it.

nimja

nimja.svg.zip

add nim blocks {%scope%} ?

{% scope %}
  {% let formUrl = context.formUrl %}
  {% let httpMethod = "POST" %}
  {% importnimja "resources/components/form.nimja" %}
{% endscope %}

scope should behave like nim blocks

macro / proc definition syntax

jinja does it like this:

{% macro input(name, value="", type="text") %}
    <input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{% endmacro %}

{% macro textarea(name, value="", rows=10, cols=40) %}
    <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
        }}">{{ value|e }}</textarea>
{% endmacro %}

and then be called like this:

<dd>{{ input("username") }}</dd>

this syntax will work and could be used to create nim procedures.

But there also must be a way to support nims : syntax like:

foo:
  baa

this could be as simple as:

{% foo: %}
baa
{% end %}

respectively / alternative to the macro syntax:

{% proc baa(): string = %}
foo
{% end %}

the template body would then be expanded by nimja.

Runtime compilation?

Is it possible to compile these templates at runtime? with a function or something?

lexer breaks when a single `#` is encountered in comment

eg:

import nimja/parser


let nwtNodes = compile("""{#
{% var idx = 0 %}
{%while true%}
  <div class="row m-4">
    {% for title in @["foo", "baa", "baz", "asdf", "afsdfasdfkl" , "asdfasdf"] %}
      <div class="col-4">
        <div class="card" style="width: 18rem;">
          <img src="..." class="card-img-top" alt="...">
          <div class="card-body">
            <h5 class="card-title">{{title}}</h5>
            <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
            <a href="#" class="btn btn-primary">Go somewhere</a>
          </div>
        </div>
      </div>
    {% endfor %}
  </div>
  {% idx.inc %}
  {% if idx > 2 %}{% break %}{% endif %}
{% endwhile %}
#}""")

echo nwtNodes
assert nwtNodes.len == 0

fieldPairs: wrong number of variables

import nimja 

type Model = object
  foo: string
  baa: string

var modelObj = Model(foo: "foo", baa: "baa")
var model = addr modelObj

echo tmpls"""
  {% for (name, val) in fieldPairs(modelObj) %}
    {{name}}
  {% endfor %}
"""

C:\Users\david.choosenim\toolchains\nim-1.6.6\lib\core\macros.nim(540, 5) Error: wrong number of variables

Nested if that evaluates as false breaks subsequent ifs

proc test2(): string = compileTemplateStr("{%if false%}{%if true%}A{%else%}B{%endif%}{%endif%}{%if true%}123{%else%}345{%endif%}")
doAssert test2() == "123" # Result is an empty string

If the initial if statement is true it seems to execute as expected, but if it's false and it contains its own nested if statement, the subsequent root level if statement never gets evaluated.

`self` variable.

to access and print blocks multiple times, see jinja2 documentation:

https://jinja.palletsprojects.com/en/2.10.x/templates/

You can’t define multiple {% block %} tags with the same name in the same template. This limitation exists because a block tag works in “both” directions. That is, a block tag doesn’t just provide a placeholder to fill - it also defines the content that fills the placeholder in the parent. If there were two similarly-named {% block %} tags in a template, that template’s parent wouldn’t know which one of the blocks’ content to use.

If you want to print a block multiple times, you can, however, use the special self variable and call the block with that name:

<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}

Support the `endmacro` keyword for consistency

Hi,

Thank you very much for an amazing work you have done by developing Nimja!

Currently the macro, (proc and func) definitions end with the end keyword which is less consistent with both Nimja itself and Ninja and Twig.

Consider supporting the endmacro and consequently endproc, endfunc keywords for ending corresponding definitions for consistency with both Nimja itself (as all current constructs follow end<construct> pattern e. g. endif, endfor) and Ninja and Twig (as both of them use the endmacro keyword)

Thank you,
Vlad

compileTemplateFile cannot open File

In test.nim I have:

import nimja

proc renderNimjaStr(ss:string, l: seq[string]): string =
  compileTemplateStr(
    """<div><p>{{ss}}</p>{% for (i) in l %}<p>{{i}}</p>{% endfor %}</div>"""
  )

proc renderNimjaFile(ss:string, l: seq[string]): string =
  compileTemplateFile("index.nwt")
  
echo renderNimjaStr("Foo", @["hello", "world"])
echo renderNimjaFile("Foo", @["hello", "world"])

in the same dir i have index.nwt:

<div><p>{{ss}}</p>{% for (i) in l %}<p>{{i}}</p>{% endfor %}</div>

Running nim c -r test.nim I get:

template/generic instantiation of `compileTemplateFile` from here
sharedhelper.nim(9, 15) Error: cannot open file: index.nwt

renderNimjaStr works fine.

show surrounding blocks in error message

On error show surrounding blocks in the error message, so that the erroneous is easier to spot.
Line number is not that easy to do (withouth a lexer rewrite)

show filename in error messages

when the error is in a template string, an extra "filename" argument could be passed to the compileTemplateStr() to know which template string has the error

allow importnimja to overwrite "context"

This feature should enable importnimja to
make variables available to the imported template with a different name.

something like this:

{% importnimja "someTemplate.nimja" record = someList %}

twig syntax:

{% include 'template.html' with {'foo': 'bar'} %}

basically the same as compileTemplateStr and compileTemplateFile

Static string len guessing

If Static string len can be estimated, result size can be set accordingly, saving string reallocations

extends and importnwt use different base path

when curdir is:

C:\Users\david\projects\nimja

then this is valid:

{% importnwt ../../tests/templates/blockMasterWithContent.html %}
{% extends ./tests/templates/blockMasterWithContent.html %}{% block title %}one{%endblock%}{%block mybody%}two{%endblock%}

tested from dynamicAgain branch.

Use nimja without wrapping code in a proc for `result` variable

One thing I've noticed about Nimja is that it assumes there is a result variable defined that is of a string type. It'd be nice if I could use this in places without needing to define a wrapper proc, eg inline with a Jester route definition. In that case, the variable result is already declared by the framework.

I'd like to be able to do

resp compileTemplateStr("some nimja code {{someParam}}")

or

var a = compileTemplateStr("some nimja code {{someParam}}")

in any arbitrary block of code without it relying on a result variable. I think it is possible to add this without any breaking changes, but if it isn't then I believe you'd need some sort of overload.

Include macro definitions from a file

Hi,

What is the idiomatic way to include macros defined in a separate file into the current template?

Use case. I'm using a template that extends a master template and defines blocks from it. I've defined a set of macros in a separate file and I want all the macros to be available in the template. The file structure is

  • master-termplate.nwt defines overall document structure via blocks
  • template.nwt defines specific content for each block
  • macros.nwt defines macros to be used in the template.nwt

Currently I managed to define all macros at the top of the master-termplate.nwt, but I'd like to separate them in a dedicated file macros.nwt and somehow include macros.nwt into the template.nwt. How this can be done?

Thank you very much,
Vlad

combine strings seperated by comments

when strings are only seperated by a comment, combine the string assignment, for less total concatinations in the produced binary.

foo{#comment#}foo{#comment#}foo

will be expanded to three concatinations, but only one is needed here,
requires a second pass over the NwtNodes.

Currently

result &= "foo"
result &= "foo"
result &= "foo"

Should be

result &= "foofoofoo"

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.