Crate sdml_tera

Crate sdml_tera 

Source
Expand description

Provides integration that allows document generation from SDML modules using the Tera template engine.

This package provides a set of rendering functions as well as a set of context functions. By default all render functions will create new context value using the module_to_value function to convert a Module into a context object. However, you may provide your own context to add custom values.

The documentation for the [context] module describes the simplifications made in the creation of the context object(s).

§Example

We wish to produce an output such as the following, a bulleted outline of a module.

# Module `campaign` Outline

* **campaign** (Module)
  * **Name** <- *xsd:string* (Datatype)
  * **CampaignId** <- *xsd:string* (Datatype)
  * **State** (Enum)
    * Running
    * Paused
    * error
  * **Tag** (Structure)
    * key -> *xsd:NMTOKEN*
    * value -> *rdf:langString*
  * **Ad** (Entity)
  * **AdGroup** (Entity)
  * **Campaign** (Entity)
    * identity campaignId -> *CampaignId*
    * name -> *unknown*
    * tag -> *Tag*
    * target -> *TargetCriteria*
  * **AudienceTarget** (Entity)
  * **GeographicTarget** (Entity)
  * **TargetCriteria** (Union)
    * Audience (Audience)
    * Geographic (Geographic)

To do this we create a file named outline.md with the following content.

{% macro member(item) %}
{%- if item.__type == "reference" -%}
*{{ item.type_ref }}*
{% elif item.__type == "definition" -%}
{{ item.name }} -> *{{ item.type_ref }}*
{% endif -%}
{% endmacro member %}

# Module `{{ module.name }}` Outline

* **{{ module.name }}** (Module)
{% for def in module.definitions %}  * **{{ def.name }}**
{%- if def.__type == "datatype" %} <- *{{ def.base_type }}*
{%- endif %} ({{ def.__type | capitalize | replace(from="-", to=" ") }})
{% if def.__type == "entity" -%}
{%- if def.identity %}    * identity {{ self::member(item=def.identity) }}
{%- endif -%}
{%- if def.members -%}
{% for member in def.members %}    * {{ self::member(item=member) }}
{%- endfor -%}
{%- endif -%}
{%- elif def.__type == "enum" -%}
{% for var in def.variants %}    * {{ var.name }}
{% endfor -%}
{% elif def.__type == "event" -%}
{%- if def.members -%}
{% for member in def.members %}    * {{ self::member(item=member) }}
{%- endfor -%}
{%- endif -%}
{% elif def.__type == "structure" -%}
{%- if def.identity %}  * identity {{ self::member(item=def.identity) }}
{%- endif -%}
{%- if def.members -%}
{% for member in def.members %}    * {{ self::member(item=member) }}
{%- endfor -%}
{%- endif -%}
{%- elif def.__type == "union" -%}
{% for var in def.variants %}    * {% if var.rename %}{{ var.rename }} ({{ var.name }})
{%- else %}{{ var.name }}
{%- endif %}
{% endfor -%}
{% endif -%}
{% endfor %}

Once we have finished testing using the sdml-tera tool we can write the following code to render any module with the template above.

use sdml_core::model::modules::Module;
use sdml_core::store::ModuleStore;
use sdml_tera::make_engine_from;
use sdml_tera::render_module;

fn print_module(module: &Module, cache: &impl ModuleStore) {

    let engine = make_engine_from("tests/templates/**/*.md")
        .expect("Could not parse template files");


    let rendered = render_module(&engine, module, None, "outline.md")
        .expect("Issue in template rendering");

    println!("{}", rendered);
}

§Features

This crate also has a binary that allows you to test the development of templates. The tool takes a glob expression for Tera to load templates and a specific template name to use for a specific test. The input/output allows for file read/write and stdin/stdout, or for input you can specify a module name for the standard resolver to find for you.

❯ sdml-tera --help
Simple Domain Modeling Language (SDML) Tera Integration

Usage: sdml-tera [OPTIONS] --template-name <TEMPLATE_NAME> [MODULE]

Arguments:
  [MODULE]  SDML module, loaded using the standard resolver

Options:
  -o, --output <OUTPUT>                File name to write to, or '-' to write to stdout [default: -]
  -i, --input <INPUT>                  Input SDML file name to read from, or '-' to read from stdin [default: -]
  -g, --template-glob <TEMPLATE_GLOB>  [default: templates/**/*.md]
  -n, --template-name <TEMPLATE_NAME>
  -h, --help                           Print help
  -V, --version                        Print version

The error messages produced by the tool are also verbose to help as much as possible to diagnose issues as you develop templates. For example, the following shows the output when a syntax error is found in a template.

An error occurred creating the Tera engine; most likely this is a syntax error in one of your templates.
Error: A template error occurred; source:
* Failed to parse "/Users/simonjo/Projects/sdm-lang/rust-sdml/sdml-tera/tests/templates/module.md"
  --> 35:25
   |
35 | event {{ definition.name$ }} source {{ definition.source }}
   |                         ^---
   |
   = expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)

Functions§

make_engine_from
A local wrapper around the Tera engine creation.
render_module
Render module, with the template in the file template_name, and using engine.
render_module_to
Render module, with the template in the file template_name, and using engine.
render_module_to_file
Render module, with the template in the file template_name, and using engine.
render_modules
Render the set of modules, with the template in the file template_name, and using engine.
render_modules_to
Render the set of modules, with the template in the file template_name, and using engine. If context is not specified a new blank object is created, and in either case a map is created under the key "modules" as a map from module name to module representation.
render_modules_to_file
Render the set of modules, with the template in the file template_name, and using engine.