Code Monkey home page Code Monkey logo

ten's Introduction

TEN

Build Status Quicklisp MIT License

Yet another template system for Common Lisp.

TEN is a fork of ECO template system by Fernando Borretti.

Like ECO, TEN compiles templates to Lisp code, but has some differences:

  • Two types of tags only. Control and output.
  • Support for templates inheritance.
  • Dot syntax for accessing template data.
  • Convenient syntax for applying filters.
  • Configurable syntax delimiters (planned, not done yet).

My reasons for writing yet another template system for Common Lisp is to combine the simplicity and usability of ECO (the whole Lisp language at your disposal for writing the templates), with features available in more complex template systems like Djula that makes things easier for web development (inheritance, dot syntax, etc.).

Usage

A TEN template looks like this:

{% template ex1 () (user enabled &key items) %}
<html>
  <head>
  </head>
  <body>

    {{ user.name | string-capitalize }}

    {% if enabled %}
    Enabled
    {% else %}
    Disabled
    {% end %}

    {% when items %}
    <ul>
      {% loop for item in items do %}
      <li>{{ item }}</li>
      {% end %}
    </ul>
    {% end %}

    {% when (not items) %}
    There are no items
    {% end %}    
  </body>
</html>
{% end %}

These are the types of tags:

  • Output tags: {{ <var> }}, becomes <var>, and {{ <fn> &rest args }}, that becomes (fn arg1 arg2 .. argn).
  • Control tags: {% <expr> %} body {% end %}, becomes (<expr> body).
  • Comments tags: Use {# and #} to comment out a piece of template.

Control tags control which parts of the tamplate are rendered; their return value is ignored.

The value returned by output tags are interpolated into the template. The function called can be any Lisp function, or another template (because templates are compiled to functions).

For example:

  • {{ user }} => user
  • {{ name user }} => (name user)
  • {% when (name user) %} ... {% end %} => (when (name user) ...)

The if tag is a special case: it supports using an else tag to separate the true and false branches. For example:

{% if posts %}
  <h1>Recent Posts</h1>
  ... loop over posts ...
{% else %}
  No recent posts.
{% end %}

Also, more advanced control expressions are possible, like let, case, cond, etc.

Template definition

Templates are defined with the following syntax:

{% template name (&rest options) (&rest args) %}
  ... body ...
{% end %}

Template options are:

  • :extends : The template to extend from.
  • :dot-syntax: If T, templates are compiled with dot syntax enabled. Dot syntax is implemented via the Lisp library access. Default is T.
  • :package: The package in which to compile and export the template. By default, templates are compiled and exported in TEN-TEMPLATES package.
  • :export: When T, export the generated template function. Otherwise, the template is not exported. Default is T.
  • :escape-html: Whether to escape html in output tags. Default is T.
  • :output-whitespace. Default is T. When NIL, expressions that just spit whitespace are discarded.

Template compilation

For manually compiling templates, use ten:compile-template function.

But more useful is to include them in the ASDF system definition of your project.

First, add :ten as ASDF system definition dependency:

:defsystem-depends-on (:ten)

Then, use :ten-template in to include the template files:

(:ten-template "filename")

The default file extension is "ten", but another can be specified via the :file-extension option; and the template package can be specified with the :package option. Look at ten.examples ASDF system for an example.

You can also compile all the templates in some directory using this ASDF recipe:

:perform (asdf:compile-op :after (o c)
               (dolist (template (uiop:directory-files (asdf:system-relative-pathname :my-app "templates/*.ten")))
               (uiop:symbol-call :ten 'compile-template template)))

Templates are compiled into functions and exported in the indicated package. The default package is ten-templates, but that can be changed from either the ASDF system definition, the ten:compile-template parameters, or the {% template %} options.

If CREATE-STREAM-WRITING-FUNCTION option is enabled, then two functions are compiled, the default one, that compiles to a function that takes template arguments and renders the template to a string. And a function that renders the template to the *TEMPLATE-OUTPUT* stream. So, for a template named my-template, a my-template function that renders to a string is created, and a my-template* function that renders to *TEMPLATE-OUTPUT* stream is created.

When developing your project it is useful to be able to compile templates in an interactive way. If you are using Emacs + SLIME, load ten.el file.

Then use M-X ten-compile-template when on the template buffer to compile templates. Note that you may want to have :package option specified in the template so that it gets compiled into the correct package.

For debugging, you can inspect the expanded template using ten:expand-template function. In Emacs, go to template buffer an do M-x ten-expand-template.

If you enable ten minor mode, template compilation gets conveniently bound to C-c C-c, and template expansion to C-c RET. Best is to automatically enable the minor mode for template files adding something like (add-hook 'web-mode-hook 'ten-mode) to your .emacs initialization file.

Inheritance

To make a template inherit from anohter, use the :extends option in template definition.

Templates are organized in sections. sections are the parts of the templates that are inherited.

Use {{super}} inside a section to render the parent section.

TEN leverages CLOS for implementing template inheritance. Templates are compiled to classes and generic functions render-template and render-section.

Have a look at some examples of template inheritance.

Includes

To include other templates, just use the output tag with the name of the included template. Remember that templates are compiled to functions; just call those functions from the template to include them.

Have a look at an example.

Dot syntax

When dot syntax is enabled (it is, by default), it is possible to conveniently access objects with dot syntax in templates:

{{ object.key1.key2 }}

that gets translated by access library to:

(access:accesses obj 'key1 'key2)

Have a look at an example.

Filters

TEN implements some convenient syntax for filters.

{{ value | func1 arg1 .. argN| func2 arg1 .. argN| .. | funcN arg1 .. argN}}

Filters are just normal functions that get applied to the value.

Filters are translated to functions application like this:

(funcN (.. (func2 (func1 value arg1 .. argN) arg1 .. argN))) arg1 .. argN)

In general, filter functions are expected to receive the value to be filtered as first parameter. But, for several Lisp functions that's not the case. In those cases, it is possible to use _ to indicate where the filter function should receive the value.

For example, string-trim receives the string to trim as second value, so, to apply it as filter we do:

{{str | string-trim '(#\%) _}}

Filters syntax is completly optional, you can disregard it and just apply functions instead:

{{ string-trim '(#\%) (string-capitalize str) }}

Have a look at some examples of filters.

Examples

Load and have a look at the examples.

(require :ten.examples)

Example templates get compiled and exported to ten/examples package.

Troubleshooting

  1. When specifying a template package other than ten-templates, if the package specified doesn't :use ten or ten-template packages, then you may run into problems trying to compile your templates. That may be because the template and section macros are not found in the specified package. In that case, make sure to prefix your template and section declarations with ten:, like:
{% ten:template my-template (:package my-package) %}
{% ten:section my-section %}
{% end %}
{% end %}
  1. You can use different implementation and export packages. Qualify the template name with the package name, like:
{% ten:template my-templates:my-template (:package my-package) %}
{{ foo "bar" }}
{% ten:section my-section %}
{% end %}
{% end %}

That will define MY-TEMPLATE on the MY-TEMPLATES package, but use MY-PACKAGE in its source (the {{ foo "bar" }} expression expands to to (MY-PACKAGE::FOO "bar")). Note that you need to previously define a package MY-TEMPLATE that exports MY-TEMPLATE. Or otherwise use MY-TEMPLATE::MY-TEMPLATE as template name.

  1. Some "complex" expressions, like cond and case, require that you turn :output-whitespace to NIL. Otherwise, template compilation puts write-string expressions right in the middle of the case and cond bodies. Have a look at this template.

License

MIT

ten's People

Contributors

mmontone 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

Watchers

 avatar  avatar  avatar  avatar  avatar

ten's Issues

Feature request: Control insertion of newlines

Hi

It would be really helpful if it was possible to control how the }} and %} delimiters inserted newlines, possibly like Jinja does using {%- and -%}. I am using Ten for text emails (ie no HTML) and controlling newlines with actual newlines make the template very difficult to read.

Option to avoid exporting templates

Perhaps it goes against the design of ten, but I don't like that template functions are automatically exported from whatever package is designated via :package. For one thing, it makes it so that defpackage complains about missing exports, and for another, it prevents (except via dirty unexport tricks) having internal symbols which you might want in a template group for eg helper templates meant for internal inheritance or some such.

simple template compilation error

I'm getting error on compiling simple template:

Template:

{% template base () (locale) %}
<html {% if locale %}lang="{{ locale }}"{% end %}>
{% end %}

Error:

  too many elements in
    (IF TEST.TEN::LOCALE
        (WRITE-STRING "lang=\"" TEN/TEMPLATE:%TEN-STREAM)
        (MULTIPLE-VALUE-BIND (#1=#:OUT1514 #2=#:RAW1515)
            TEST.TEN::LOCALE
          (IF #2#
              (PRINC #1# TEN/TEMPLATE:%TEN-STREAM)
              (WRITE-STRING # TEN/TEMPLATE:%TEN-STREAM)))
        (WRITE-STRING "\"" TEN/TEMPLATE:%TEN-STREAM))
  to satisfy lambda list
    (IF PREDICATE
        ARM1
        &OPTIONAL
        ARM2):
  between 3 and 4 expected, but got 5

But if i delete closing quote of attribute "lang", then it compiles:

{% template base () (locale) %}
<html {% if locale %}lang="{{ locale }}{% end %}>
{% end %}

problem with dot-syntax: collision

{% template page () (data) %}
PAGE-TEXT
{% end %}

{% template problem () (data) %}
{{ data.page }}
{% end %}

if i call template function problem like this:

(ten-templates:problem '(:id 1 :page "test"))

i get this response:

"
PAGE-TEXT
"
T

It happens because of collision between the template name "page" and data list ":page" item.

Empty sections cause compiler warnings

If there is an empty section which is not overridden, e.g. {% section footer %}{% end %} it causes a compiler warning like

In (TEN/COMPILER::RENDER-SECTION ((EQL 'TEN-TEMPLATES::FOOTER) TEN-TEMPLATES::BASE T)) inside an anonymous lambda form: Unused lexical variable TEN/TEMPLATE:%TEN-STREAM

Adding whitespace in the section removes the warning. {% section footer %} {% end %}

Empty sections should be possible without causing compiler warnings.

:output-whitespace

After set this option :output-whitespace T, how to still keep some required space?

Examples of live sites using ten

Hello,
I would like to start using ten instead of Jekyll. Would it be possible to have a list of some live sites using Ten, perhaps even some repositories, where beginners could learn from more complex examples that the nice ones already present here?

Thank you for ten, it is very nice.

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.