TMPL(3)

NAME
tmpl - templates

LIBRARY
PDEL Library (libpdel, -lpdel)

SYNOPSIS
struct tmpl *
tmpl_create(FILE *input, int *num_errors, const char *mtype);
struct tmpl *
tmpl_create_mmap(const char *path, int *num_errors, const char *mtype);
void
tmpl_destroy(struct tmpl **tmplp);
struct tmpl_ctx *
tmpl_ctx_create(void *arg, const char *mtype, tmpl_handler_t *handler, tmpl_errfmtr_t *errfmtr);
void *
tmpl_ctx_get_arg(struct tmpl_ctx *ctx);
const char *
tmpl_ctx_get_mtype(struct tmpl_ctx *ctx);
const char *
tmpl_ctx_get_var(struct tmpl_ctx *ctx, const char *name);
int
tmpl_ctx_set_var(struct tmpl_ctx *ctx, const char *name, const char *value);
char *
tmpl_list_handler(struct tmpl_ctx *ctx, const struct tmpl_func *userfuncs, u_int uflen, char **errmsgp, int argc, char **argv);
void
tmpl_ctx_destroy(struct tmpl_ctx **ctxp);
int
tmpl_execute(struct tmpl *tmpl, struct tmpl_ctx *ctx, FILE *output, int flags);
int
tmpl_execute_func(struct tmpl_ctx *ctx, FILE *output, char **errmsgp, int argc, char **argv, int flags);
void
tmpl_ctx_reset(struct tmpl_ctx *ctx);

DESCRIPTION

  Overview
The tmpl library supports programmatic generation of output based on input from tmpl template files. Output is generated by parsing and then executing a tmpl file.

The tmpl file simply contains the desired output, with invocations of various tmpl functions, denoted by the “@” character, interspersed. Tmpl functions take zero or more arguments, where each argument can be either a doubly-quoted string or another, nested tmpl function.

When executed, the output of a tmpl file is simply the contents of the file, with each tmpl function replaced by the value returned by that function. Several functions, including control flow constructs, are built-in, and compile-time and run-time user defined functions are supported.

Here is a simple example tmpl input file:

    I'm going to count to three:
    @set("i", "1")
    @while(@le(@get("i"), "3"))
          @get("i")
          @set("i", @add(@get("i"), "1"))
    @endwhile
    Done.

If this template were executed, the output would be:

    I'm going to count to three:
          1
          2
          3
    Done.

While the example above uses only built-in functions, user-defined functions written in C may be invoked in the same way.

Parsing
This section describes the precise rules that govern how tmpl files are parsed.

A tmpl file is parsed by scanning for special function calls. A function call is an at sign ('@') followed by a contiguous sequence of letters, digits, and underscores, followed by matching parentheses containing zero or more function arguments. The text between functions calls is ignored (it doesn't even have to be text).

Function arguments may be either other nested function calls (the argument to the outer function is the result of the inner function invocation), or constant literal strings in double quotes (the argument is the value of the string). Therefore, all function arguments begin with either an at sign or a double quote character. Function arguments are separated by commas and may have surrounding whitespace, which is ignored.

Constant literal strings are enclosed in double quotes and respect the usual C backslash escapes.

Built-in functions (see below) that take zero arguments do not require parentheses, but parentheses may be included for separation purposes.

A parsed tmpl file is represented by a struct tmpl.

Execution

The parsing of a tmpl file and its execution are separate steps. Once a tmpl file has been parsed, it may be executed several times. Execution requires a context, represented by a struct tmpl_ctx, and generates output which is written to an output stream.

When the template is executed, the text between function calls is copied to the output stream without modification, while the function calls are replaced with their values, which are strings. Functions are evaluated as they are encountered during execution.

The tmpl library includes several built-in functions, including special control flow functions that control input processing. The user code may also implement custom functions.

User functions may return NULL and set errno to indicate an error; they may also set an error string (if no error string is set, strerror(3) is used to generate one). When such an error occurs, the function does not return at all. Instead, an error message is propagated up to the outer-most function call. The error message is reformatted by an optional user-supplied error formatter function, and then the formatted message is written to the output stream.

Built-in Functions
The built-in functions are listed below. In these definitions, the numeric value of a string is the result of parsing it with strtol(3), and a string is considered "true" if it has a numeric value other than zero.

Control flow

    @while(x) ... @endwhile

        The text in between is repeated as long as the argument supplied to @while() is true.

    @loop(x) ... @endloop

        The text in between is repeated N times, where N is the numerical value of the argument passed to @loop().

    @loopindex(x)

        Takes zero or one argument; returns the loop index (counting from zero) of the loop that is N loops out from the innermost containing loop, where N is the numerical value of the argument, or -1 if no such loop exists. If the argument is omitted it is assumed to be zero.

    @if(x) ... [ @elif(y) ... ] [ @else ... ] @endif

        Conditional execution depending on the truth value of the argument to @if(). Zero or more @elif() blocks may be followed by zero or one @else block. An @endif is always required.

    @break()

        Break out of the innermost enclosing @loop or @while.

    @continue()

        Continue with the next iteration of the nearest enclosing @loop or @while.

    @return()

        Return from within a run-time function.

    @eval(x)

        Parses the argument as a template, executes it, and returns the resulting output.

Run-time variables and functions

    @set(name, value)

        Sets the run-time variable named by the first argument to have the value equal to the second argument. All run-time variables are global and exist as long as the associated execution context exists.

    @get(name)

        Returns the value of the run-time variable named by the first argument, or the empty string if the variable is not set.

    @define(name) ... @enddef

        Defines a run-time function. The text in between is executed whenever @name(...) is invoked. During this execution, the variables argc and arg0, arg1, ... are set to the function argument count and arguments, respectively; arg0 is always equal to the name of the function. All run-time functions are global and exist as long as the associated execution context exists.

    @invoke()

        Invokes a function. The function to be invoked and its arguments are described by the run-time variables argc and arg0, arg1, ... as above. So arg0 is the function name and arg0, arg1, ... are the function arguments. @invoke() itself does not take any arguments.

Evaluators

    @equal(x, y)

        Returns "1" if x and y are identical, otherwise "0".

    @not(x)

        Returns "1" if x is false, otherwise "0".

    @and(...)

        Returns "1" if all of the arguments are true, otherwise "0".

    @or(...)

        Returns "1" if any of the arguments is true, otherwise "0".

    @add(...)

        Returns the sum of the arguments.

    @sub(x, ...)

        Returns the second and subsequent arguments subtracted from the first.

    @mul(...)

        Returns the product of the arguments.

    @div(x, y)

        Returns the first argument divided by the second.

    @mod(x, y)

        Returns the first argument modulo the second.

    @lt(x, y)

        Returns "1" if the first argument is less than the second, otherwise "0".

    @le(x, y)

        Returns "1" if the first argument is less than or equal to the second, otherwise "0".

    @gt(x, y)

        Returns "1" if the first argument is greater than the second, otherwise "0".

    @ge(x, y)

        Returns "1" if the first argument is greater than or equal to the second, otherwise "0".

String functions

    @cat(...)

        Returns the concatenation of all of the arguments.

    @@()

        Returns "@".

    @error(arg)

        Returns the argument formatted using the caller-supplied error formatter.

    @htmlencode(arg)

        Encodes the argument with HTML escapes and returns the result.

    @urlencode(arg)

        Encodes the argument with URL escapes and returns the result.

I/O functions

    @flush()

        Flushes the output stream.

    @output(arg)

        Outputs the argument directly to the output stream. That is, if this function is invoked from within a user-defined function, the argument goes directly to the template output rather than being concatenated to the return value of the function.

API
tmpl_create() parses input from input and creates and returns a new template object, which uses typed_mem(3) type mtype. If num_errors is not NULL, then the number of parse errors detected is stored in *num_errors. A parse error is an occurrence of the “@” character that is not the beginning of a well-formed tmpl function invocation.

tmpl_create_mmap() parses the contents of the file named path, using mmap(2) internally to avoid having to store the entire file in memory. This results in less memory being used; however, if the file's contents are changed then subsequent invocations of tmpl_execute() may give garbled output.

tmpl_destroy() destroys a template object. Upon return, *tmplp will be set to NULL. If *tmplp is already NULL when tmpl_destroy() is invoked, nothing happens.

tmpl_ctx_create() creates a new template execution context. mtype is the typed_mem(3) type used not only for the execution context object itself, but also for all strings generated during execution. In particular, all strings returned by user functions must be stored in buffers allocated with this type. The arg is a user cookie ignored by the tmpl functions. The parameters arg and mtype may be retrieved with tmpl_ctx_get_arg() and tmpl_ctx_get_mtype(), respectively.

handler and errfmtr point to functions having these types:

    typedef char *tmpl_handler_t(struct tmpl_ctx *ctx, char **errmsgp,
                      int argc, char **argv);
    typedef char *tmpl_errfmtr_t(struct tmpl_ctx *ctx, const char *errmsg);

handler() returns the result of invoking the function described by argc and argv as a '\0'-terminated string allocated with the typed_mem(3) type mtype. The first argument is always the function name, and subsequent arguments are the (evaluated) arguments passed to the function. Therefore, argc is always at least one.

handler() may indicate an error by returning NULL, in which case it should either set errno appropriately or else set *errmsgp to point to an error message (which should also be allocated with typed_mem(3) type mtype); *errmsgp will be NULL when handler() is invoked.

The error formatter function errfmtr() is optional. It should return an error string allocated with typed_mem(3) type mtype and formatted appropriately for the template output (e.g., in HTML). The errmsg is the original, unformatted error string returned by the function handler; if the handler did not return an explicit error message, strerror(errno) is used for errmsg.

tmpl_list_handler() may be useful for implementing handler() when there is a fixed list of user functions. The userfuncs parameter points to a length uflen array of struct tmpl_func:

    struct tmpl_func {
        const char      *name;      /* function name, null to end list */
        u_int           min_args;   /* min # args (not counting name) */
        u_int           max_args;   /* max # args (not counting name) */
        tmpl_handler_t  *handler;   /* handler for function */
    };

Each entry in the array describes a user function. The function called name accepts at least min_args and at most max_args parameters, and is implemented by the handler. The array must be sorted lexicographically by name. tmpl_list_handler() finds the function named by argv[0] using a binary search of the array and invokes its handler() with the supplied arguments. In the case of an error, tmpl_list_handler() prepends the returned error string with the offending function call and arguments.

tmpl_ctx_set_var() and tmpl_ctx_get_var() may be used to set and retrieve variables associated with ctx.

tmpl_ctx_destroy() destroys a template context. Upon return, *ctxp will be set to NULL. If *ctxp is already NULL when tmpl_ctx_destroy() is invoked, nothing happens.

tmpl_execute() executes the parsed template tmpl using the execution context ctx, and writes the output to output. flags may contain any of the following values OR'd together:

    TMPL_SKIP_NL_WHITE    Skip newline plus whitespace

TMPL_SKIP_NL_WHITE causes any inter-function occurrences of a newline followed by whitespace to be ignored. This often generates more intuitive output. The example template given previously would generate this output if TMPL_SKIP_NL_WHITE were specified:

    I'm going to count to three:
    123
    Done.

tmpl_execute_func() can be used to execute a single function defined in a template context. This includes non-control flow built-in functions, user functions, and run-time functions. ctx, output, and flags are as with tmpl_execute(). The function and arguments are described by argc and argv, and errmsgp must point to a char * error message pointer.

tmpl_ctx_reset() resets an execution context to its initial state. This causes any runtime variables and functions defined during a previous execution using ctx to be forgotten.

RETURN VALUES
tmpl_create(), tmpl_ctx_create(), and tmpl_execute() return NULL or -1 to indicate an error, with errno set appropriately.

tmpl_execute_func() returns -1 if there was an error. In the case of a system error, *errmsgp will be set to NULL and errno will be set appropriately; otherwise, *errmsgp will point to an appropriate error message allocated with typed_mem(3) type mtype, which the caller must eventually free.

SEE ALSO
http_servlet_tmpl(3), libpdel(3), strtol(3), typed_mem(3)

IMPLEMENTATION NOTES
Here are two common sources of bugs:

-   User functions returning constant strings or strings allocated with the wrong typed_mem(3) type.
-   Not sorting the user function array given to tmpl_list_handler().

HISTORY
The PDEL library was developed at Packet Design, LLC. http://www.packetdesign.com/

AUTHORS
Archie Cobbs <archie@packetdesign.com>

BUGS
tmpl_create(), tmpl_execute(), and tmpl_execute_func() may leak small amounts of memory if the thread is canceled.