Bravo

From HaskellWiki


What is Bravo?[edit]

Bravo is a general-purpose text template library inspired by the PHP template engine Smarty and the Haskell template library chunks, providing the parsing and generation of templates at compile time. Templates can be read from strings or files and for each a new record data type is created, allowing convenient access to all template variables in a type-safe manner.

All releases of Bravo are available on Hackage; there's no darcs repository for Bravo at the moment.

Features[edit]

  • Static template processing: All templates are read, parsed and processed at compile time, so no extra file access or error handling at runtime is necessary. If the application is supposed to be released in binary form, it's not necessary to ship the template files with the application.
  • Multiple templates per file: Some template libraries require the user the create a file for each template, what may be very annoying for a lot of small templates. Bravo allows the user to define multiple templates per file with arbitrary comments between them. This means template documentation can be embedded directly into the template files.
  • Conditional template evaluation: Bravo provides conditional template splices (see below), allowing to check conditions at runtime. This removes misplaced logic from the program source code and leads to a cleaner separation of program logic and layout. Beyond that, it helps to decrease the number of templates.
  • Embedding of Haskell expressions: Bravo allows the user to embed arbitrary Haskell expressions combined with template variables.
  • Customized data type generation: For the data type creation by default the following naming scheme is used:
    • For a given template name "name", the created data type/constructor name is "TplName".
    • For a template "name" and a template variable "var" a record field "nameVar" is created.
This behaviour can be changed by the mkTemplatesWithOptions and mkTemplatesFromFileWithOptions functions, that allow you to customize this naming scheme.

Comparison to other template systems[edit]

HStringTemplate[edit]

The main difference between HStringTemplate and Bravo is that Bravo parses the templates at compile time, while HStringTemplate does at runtime. Besides that, HStringTemplate provides several methods for rendering different data types in different styles. This can also be accomplished in Bravo by writing the appropriate rendering function in your Haskell code and using it in the template.

chunks[edit]

chunks and Bravo share the basic idea of parsing templates and generating new data types at compile time. However, Bravo uses a richer syntax allowing conditional template evaluation and the use of Haskell expressions. Moreover chunks doesn't provide a customization of the generated data types. In contrast with that, chunks allows nesting of templates which is not provided by Bravo.

Usage[edit]

Bravo templates can be read from strings via the mkTemplates, or from files via the mkTemplatesFromFile Template Haskell functions, so you need to enable the TemplateHaskell language extension when using Bravo in your Haskell application. Each read string or file can contain multiple templates and additional comments. A single template is delimitated by an opening splice {{ tpl name }} and a closing splice {{ endtpl }} where name is an identifier starting with a lowercase letter. Characters before and after these splices are considered to be template comments and therefore are ignored. Between these delimiters multiple inner splices are allowed, which are:

  • Normal text, i.e. character sequences not including {{ or }}.
  • Comment splices {{- comment text -}}. These splices are only for documentary purposes and will not occur in the produced template text.
  • Expression splices {{: expression }}. Here expression can be an arbitrary Haskell expression that does not require any language extensions to be parsed. The expression itself is not evaluated at compile time but rather at runtime and the result of evaluation will be included in the produced template text. Note that the evaluated expression must be of type String, otherwise compile errors will occur in the produced declarations. Additionally, template variables of the form $name or $(name) can be used within an expression.
  • Conditional splices {{ if condition_1 }} ... [ {{ elseif condition_2 }} ... ]* [ {{ else }} ... ] {{ endif }} where each ... stands for an arbitrary number of inner splices. Multiple elseif splices and/or a single else splice are optional. condition_n are Haskell expressions similar to expression in expression splices, except that they have to evaluate to a value of type Bool. All conditions will be evaluated in sequence and if one condition evaluates to True, the subsequent template splices are added to the resulting template text; all other inner splices are discarded.

After successful parsing, a new record data type with a single data constructor and a corresponding instance of class Show is created for each template. Also each used template variable is transformed to a new record field of the data constructor. When parsed with default options, the simple template

    {{tpl simple}}{{:$name}}'s favourite song is `{{:$song}}'{{endtpl}}

will be translated to the record data type

    data TplSimple = TplSimple {
                        simpleName :: String,
                        simpleSong :: String
                     }.

To customize the created data types, the mkTemplatesWithOptions or mkTemplatesFromFileWithOptions functions can be used. The passed value of type TplOptions contains a tplMkName function to create the data type/contructior name and the tplMkFieldName function to create the field names. Additionally, it contains a tplModifyText function that is applied to each template text splice (see below), allowing e.g. the stripping of extra whitespace or other post-processing.

Finally, use the show function on a value of the created data type to convert it into a string.

Security concerns[edit]

Bravo allows the template writer to use arbitrary Haskell functions, even the use of unsafePerformIO is permitted. You may think this is a huge security problem – no, it's not! Using Haskell's module system will fix this problem, as you can see in the following example from the Bravo package:

The template file:

{{tpl safe}}
    {{: "Hello" ++ " world!" }}
{{endtpl}}

{{tpl unsafe}}
    {{: unsafePerformIO (putStrLn "Doing some very bad stuff ..." >> return "Hello world!") }}
{{endtpl}}

The templates module:

{-# LANGUAGE TemplateHaskell #-}
module Example02Templates (
        TplSafe (..),
        TplUnsafe (..)
    )
where

import Text.Bravo

$(mkTemplatesFromFile "Example02.tpl")

The main module:

module Main (main) where

import Text.Bravo

import Example02Templates

main :: IO ()
main = return ()

This program won't compile, since unsafePerformIO is not visible in the templates module, whereas e.g. putStrLn is. All Prelude functions can be hidden, but at least Show, show and concat have to be imported to make Bravo work properly.

Future work[edit]

  • Performance: Processing huge template files (> 1MB) sometimes fails. Usually a single template is not that large, so large files can be split and this should not be a big problem. Nevertheless, maybe this could be fixed by the use of attoparsec.
  • Input encoding: Support for different input encodings is not yet implemented.
  • Custom template splice delimiters: {{ and }} as delimiters are suitable in most cases. One goal of future releases is to remove these fixed delimiters and provide a wide range of possible delimiters.
  • Processing of lists with section splices: Sometimes it is necessary to process a list of values of type A in the same way, leading to format a template for each value, concatenate all the produced strings and pass the result to the variable of a second template. A convenient way to handle this would be a `section splice', that is mapped on a variable with a list of values (see also Smarty sections).
  • Caching: Caching of evaluated templates is not provided at the moment.

Known issues[edit]

Bravo depends on haskell-src-meta, where the last stable version 0.0.6 on Hackage doesn't support TH >= 2.4. This is a problem since GHC 6.12 ships with TH 2.4. Though, there's a patched version that supports TH 2.4 available from this darcs repository. Bravo presumes haskell-src-meta >= 0.1.0 to be compatible with TH >= 2.4.