{"id":13804630,"url":"https://github.com/mmontone/ten","last_synced_at":"2026-03-11T00:41:17.726Z","repository":{"id":54504710,"uuid":"254478055","full_name":"mmontone/ten","owner":"mmontone","description":"The completeness of Djula meets the usability of Eco.","archived":false,"fork":false,"pushed_at":"2024-07-15T16:10:07.000Z","size":80,"stargazers_count":34,"open_issues_count":4,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-11-18T20:51:48.822Z","etag":null,"topics":["common-lisp","html","lisp","web-template"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mmontone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-09T21:04:23.000Z","updated_at":"2024-08-05T18:22:52.000Z","dependencies_parsed_at":"2024-01-14T18:35:42.970Z","dependency_job_id":"a6570c2b-4266-4e59-a74e-05f9ca839ca9","html_url":"https://github.com/mmontone/ten","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Ften","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Ften/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Ften/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Ften/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mmontone","download_url":"https://codeload.github.com/mmontone/ten/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242855901,"owners_count":20196357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["common-lisp","html","lisp","web-template"],"created_at":"2024-08-04T01:00:51.449Z","updated_at":"2026-03-11T00:41:17.696Z","avatar_url":"https://github.com/mmontone.png","language":"Common Lisp","readme":"# TEN\n\n[![Build Status](https://travis-ci.org/mmontone/ten.svg?branch=master)](https://travis-ci.org/mmontone/ten)\n[![Quicklisp](http://quickdocs.org/badge/ten.svg)](http://quickdocs.org/ten/)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n\nYet another template system for Common Lisp.\n\nTEN is a fork of [ECO template system](https://github.com/eudoxia0/eco) by Fernando Borretti.\n\nLike ECO, TEN compiles templates to Lisp code, but has some differences:\n- Two types of tags only. Control and output.\n- Support for templates inheritance.\n- Dot syntax for accessing template data.\n- Convenient syntax for applying filters.\n- Configurable syntax delimiters (planned, not done yet).\n\nMy 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](https://mmontone.github.io/djula/) that makes things easier for web development (inheritance, dot syntax, etc.).\n\n## Usage\n\nA TEN template looks like this:\n\n```jinja\n{% template ex1 () (user enabled \u0026key items) %}\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n\n    {{ user.name | string-capitalize }}\n\n    {% if enabled %}\n    Enabled\n    {% else %}\n    Disabled\n    {% end %}\n\n    {% when items %}\n    \u003cul\u003e\n      {% loop for item in items do %}\n      \u003cli\u003e{{ item }}\u003c/li\u003e\n      {% end %}\n    \u003c/ul\u003e\n    {% end %}\n\n    {% when (not items) %}\n    There are no items\n    {% end %}    \n  \u003c/body\u003e\n\u003c/html\u003e\n{% end %}\n\n```\n\nThese are the types of tags:\n- *Output tags*: `{{ \u003cvar\u003e }}`, becomes `\u003cvar\u003e`, and `{{ \u003cfn\u003e \u0026rest args }}`, that becomes `(fn arg1 arg2 .. argn)`.\n- *Control tags*: `{% \u003cexpr\u003e %} body {% end %}`, becomes `(\u003cexpr\u003e body)`.\n- *Comments tags*: Use `{#` and `#}` to comment out a piece of template.\n  \nControl tags control which parts of the tamplate are rendered; their return value is ignored.\n\nThe value returned by output tags are interpolated into the template. The function called can be any\nLisp function, or another template (because templates are compiled to functions).\n\n For example:\n \n * `{{ user }}` =\u003e `user`\n * `{{ name user }}` =\u003e  `(name user)`\n * `{% when (name user) %} ... {% end %}` =\u003e `(when (name user) ...)`\n \nThe `if` tag is a special case: it supports using an `else` tag to separate the true and\nfalse branches. For example:\n\n```lisp\n{% if posts %}\n  \u003ch1\u003eRecent Posts\u003c/h1\u003e\n  ... loop over posts ...\n{% else %}\n  No recent posts.\n{% end %}\n```\n\nAlso, more [advanced control expressions](https://github.com/mmontone/ten/blob/master/examples/control.html) are possible, like `let`, `case`, `cond`, etc.\n\n## Template definition\n\nTemplates are defined with the following syntax:\n\n```\n{% template name (\u0026rest options) (\u0026rest args) %}\n  ... body ...\n{% end %}\n```\n\nTemplate options are:\n- `:extends` : The template to extend from.\n- `:dot-syntax`: If T, templates are compiled with dot syntax enabled. Dot syntax is implemented via the Lisp library `access`. Default is T.\n- `:package`: The package in which to compile and export the template. By default, templates are compiled and exported in `TEN-TEMPLATES` package.\n- `:export`: When T, export the generated template function. Otherwise, the template is not exported. Default is T.\n- `:escape-html`: Whether to escape html in output tags. Default is T.\n- `:output-whitespace`. Default is T. When NIL, expressions that just spit whitespace are discarded.\n\n## Template compilation\n\nFor manually compiling templates, use `ten:compile-template` function.\n\nBut more useful is to include them in the ASDF system definition of your project.\n\nFirst, add `:ten` as ASDF system definition dependency:\n\n`:defsystem-depends-on (:ten)`\n\nThen, use `:ten-template` in to include the template files:\n\n```lisp\n(:ten-template \"filename\")\n```\n\nThe 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](https://github.com/mmontone/ten/blob/master/ten.examples.asd) for an example.\n\nYou can also compile all the templates in some directory using this ASDF recipe:\n\n```lisp\n:perform (asdf:compile-op :after (o c)\n               (dolist (template (uiop:directory-files (asdf:system-relative-pathname :my-app \"templates/*.ten\")))\n               (uiop:symbol-call :ten 'compile-template template)))\n```\n\nTemplates 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.\n\nIf `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.\n\nWhen developing your project it is useful to be able to compile templates in an interactive way. \nIf you are using Emacs + SLIME, load `ten.el` file.\n\nThen 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.\n\nFor 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`.\n\nIf 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.\n\n## Inheritance\n\nTo make a template inherit from anohter, use the `:extends` option in template definition.\n\nTemplates are organized in `sections`. `sections` are the parts of the templates that are inherited.\n\nUse `{{super}}` inside a `section` to render the parent `section`.\n\nTEN leverages CLOS for implementing template inheritance. Templates are compiled to classes and generic functions `render-template` and `render-section`.\n\nHave a look at some [examples of template inheritance](https://github.com/mmontone/ten/blob/master/examples/inheritance.html).\n\n## Includes\n\nTo 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.\n\nHave a look at [an example](https://github.com/mmontone/ten/blob/master/examples/include.html).\n\n## Dot syntax\n\nWhen dot syntax is enabled (it is, by default), it is possible to conveniently access objects with dot syntax in templates:\n\n`{{ object.key1.key2 }}`\n\nthat gets translated by [access](https://github.com/AccelerationNet/access) library to:\n\n`(access:accesses obj 'key1 'key2)`\n\nHave a look at [an example](https://github.com/mmontone/ten/blob/master/examples/dot-syntax.html).\n\n## Filters\n\nTEN implements some convenient syntax for filters.\n\n`{{ value | func1 arg1 .. argN| func2 arg1 .. argN| .. | funcN arg1 .. argN}}`\n\nFilters are just normal functions that get applied to the value.\n\nFilters are translated to functions application like this:\n\n`(funcN (.. (func2 (func1 value arg1 .. argN) arg1 .. argN))) arg1 .. argN)`\n\nIn general, filter functions are expected to receive the value to be filtered as first parameter. \nBut, 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.\n\nFor example, `string-trim` receives the string to trim as second value, so, to apply it as filter we do:\n\n`{{str | string-trim '(#\\%) _}}`\n\nFilters syntax is completly optional, you can disregard it and just apply functions instead:\n\n`{{ string-trim '(#\\%) (string-capitalize str) }}`\n\nHave a look at some [examples of filters](https://github.com/mmontone/ten/tree/master/examples/filters.html).\n\n## Examples\n\nLoad and have a look at the [examples](https://github.com/mmontone/ten/tree/master/examples).\n\n```lisp\n(require :ten.examples)\n```\n\nExample templates get compiled and exported to `ten/examples` package.\n\n## Troubleshooting\n\n1) 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:\n\n```django\n{% ten:template my-template (:package my-package) %}\n{% ten:section my-section %}\n{% end %}\n{% end %}\n```\n\n2) You can use different implementation and export packages. Qualify the template name with the package name, like:\n```django\n{% ten:template my-templates:my-template (:package my-package) %}\n{{ foo \"bar\" }}\n{% ten:section my-section %}\n{% end %}\n{% end %}\n```\nThat 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.\n\n3) 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](https://github.com/mmontone/ten/blob/master/examples/control.html). \n\n## License\n\nMIT\n","funding_links":[],"categories":["REPLs ##","Interfaces to other package managers"],"sub_categories":["Isomorphic web frameworks"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmontone%2Ften","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmontone%2Ften","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmontone%2Ften/lists"}