plat-0.1.0.1: Simple templating library

Safe HaskellSafe-Inferred

Plat

Contents

Description

Plat is a template engine, and, as such, it is designed to help you present your data as simple text with the markup of your choice.

In order to use it, you'll have to do three things.

  • Prepare your data;
  • Create a template to insert this data into; and
  • Render the template with this data.

See the following sections for explanations and examples.

Synopsis

Context

Let's repeat: Plat is a template engine. Therefore, it's impossible to do calculations in the template, even small ones, like counting the lines. You need to have your data prepared before you render the template, and that includes sorting, indexing, etc.

There are different types of data, which are used differently. As for now, there are four types, although in the future there could be more. They are:

  • Strings, that are inserted into the template;
  • Booleans, that are checked during rendering;
  • Arrays, that are iterated over; and
  • Records, that keep other values of different types.

Using one type instead of the other results in a run-time error.

Records and arrays form a hierarchy, the root of which should always be a record. This root becomes a context which the template is rendered with. Again: it can't be a string, or a boolean, or even an array.

Defining strings and booleans is pretty straightforward, usual Haskell Strings and Booleans will do, and Haskell lists are a way to define arrays. As for records, we provide a handy Context monad, which allows us to define records with just one =: operator.

Below is an example of data, prepared to be inserted into a template. It's a simple list of employees in a fictional company named Mac's tools. For each employee we provide his name, numerical index (as we know, it's impossible to implement this numbering in Plat itself), and, optionaly a boolean bad, which, probably, is a Mac's evaluation of this particular employee's performance.

ctx =
    do "name" =: "Mac's tools"
       "staff" =: [
        do "index" =: "1"
           "name" =: "Alice"
        ,
        do "name" =: "Bob"
           "index" =: "2"
           "bad" =: True
        ,
        do "index" =: "3"
           "name" =: "Nicolás"
        ]

Of course, as this is nothing but a simple Haskell value, one can use all power of Haskell to create such data automatically.

data Context a Source

This is the type of contexts. The type 'a' here doesn't matter; it's only present so that it would be possible to use the 'do' syntax.

Instances

(=:) :: ElementC a => String -> a -> Context ()Source

An only Context-specific operator.

'name =: value' tells render that 'value' should be substituted for 'name' in a template.

Context elements

data ContextElement Source

This is a universal type for everything that can be assigned to a field in the context. Normally you won't need this, and this type is subject to change, so, it's internals are hidden. The only way to create values of this type is to use the ec (or the ecs) function. The only way to use them is to insert them into the Context with the =: operator and then render into the template.

One possible use of this type is to create a heterogeneous array. If you want an array in your context, containing, for example, both strings and booleans, you can do it like that: '[ec "foo", ec True]'.

class ElementC c whereSource

This is the class of all types that can be used as values in the context. You can create additional ones, although we strictly discourage that.

Methods

ec :: c -> ContextElementSource

This function is something you should implement yourself, if you decide to create your own instance of ElementC. It converts the given value to the element that could be used in the context. Normally you can just go with the =: operator without calling this function explicitly.

ecs :: [c] -> ContextElementSource

This function is here for the same reason the showList function is in the Show class — to allow Strings, which are lists of characters, to be used differently from other lists.

If you decide to create your own instance of ElementC, there is no need to implement this function, as it has the default implementation.

listValues :: ElementC a => [a] -> [Context ()]Source

This function is here for convenience only. If you have a list of elements that can be inserted in the template, this function gives you an array of records, one for each element of the original list, where each record contains the corresponding element in it's 'item' field. Additionally, it gives you boolean 'first' and 'last' fields, true for the first and last elements of the list respectively, and the 'only' field that is true if and only if the list contains just one element.

Template

Unlike contexts, templates are not created in Haskell. Instead, they are typically held in separate files and written in a special markup language. Of course, there isn't anything to prevent them from being held in strings inside Haskell source code. However, they are compiled into a special opaque data type Template before they are used. This compilation needs to happen just once, unless the template is changed.

All template commands start from the letter '@'. Therefore, all '@'s that should be in the rendering result need to be escaped. In fact, that is one of the commands:

@@
Represent the string "@".

There is a way to insert comments in the template. For now, we only support one-line comments. They won't be present in the result of rendering.

@#
Starts a one-line comment, continued up to the end of the current line.

There is also a command that does nothing.

@.
Does nothing.

It can be used as a separator between a command and some other text. For example, if you want the letter 'a' to follow the output of the command '@foo' without any space between them, you can write '@foo@.a'.

There is one command to insert the string from the context into the template.

@expr
Insert the value of the expression 'expr', which should be a string.

For now, the expression syntax is very simple. Any expression is just a sequence of names separated by dots ('.'). Expressions are evaluated from left to right, starting with the context, used to render the template. Each name is just a name of a field in the current record, and the value of this field replaces that record. For example, if the expression is 'foo.bar.baz', then it's value is the value of the field 'baz' of the record, which is the value of the field 'bar' of another record, which, in turn, is the value if the field 'foo' of the context.

It's a runtime error if the required field doesn't exist or if the current value is not a record. There are two exceptions. First, if the value of 'expr' is boolean, then the expression 'expr.not' is valid and it's value is also boolean, complement to the value of 'expr'. Second, if the value of 'expr' is an array, then the expression 'expr.empty' is, again, boolean, and it is true if and only if the value of 'expr' is an empty array. Of course, neither of this two expressions can be inserted into the template as a string.

There is a command to check for the value of a boolean expression.

@!expr
Checks the value of the expression 'expr', which must be true.

It's a runtime error if the value of 'expr' is not boolean, or if it's false. However, there is a difference between this two variants. If the expression 'expr' is not valid, then the expression 'expr.not' is also not valid, but if 'expr' is false, then 'expr.not' is valid and true.

Of course, checking the boolean value is not very useful, unless you do it between the branching commands.

@{
Starts the list of branches.
@|
Separates branches in the list.
@}
Ends the list of branches.

Each branch is a separate template, which is rendered with the same context as the list of branches itself. Plat chooses the first branch that doesn't fail and uses it instead of the whole list as if there was no failure. It's a runtime error if all branches fail. It's a compile-time error to have unbalanced starting or ending branching command, or to have the separating command not surrounded by them. Here, 'compile-time' means 'when the template is compiled', not when the Haskell source is compiled.

At last, there are two looping commands.

@expr[name
Starts the loop.
@]
Ends the loop.

Here, the value of 'expr' should be an array. Each of it's items, in turn, is added to the context under the name 'name', and the template between these commands is rendered with this new context.

An example:

List of employees at @name:
@staff[person
@person.index@.. @person.name@{@!person.bad (going to be fired)@|@}@
@]@# There won't be an empty line in the result

This is a simple template, which can be used with the data example above. Note that if the employee is marked as 'bad', then there would be a note that he is going to be fired; otherwise, there won't be anything, as the second branch is empty. Of course, you can use several branches, not just two of them.

There is just one quirk here. If the line in the template contains only some control commands (i.e. branching, looping, or checking commands), padded with whitespaces at the beginning and the end, then this line won't be present in the result of rendering. All the whitespace, together with the line break, would be removed. So it's safe, for example, to end the loop on a separate line. On the other hand, if the line contains nothing but whitespace, then it won't be removed.

If you don't want a line to be removed, you can just insert the '@.' command.

data Template Source

Before using the template, it should be compiled, so that compilation would not happen each time the template is used. This is the type of the compiled template. It's internals aren't exported, as they are subject to change in future versions.

templateE :: ByteString -> Either TemplateErr TemplateSource

This is a compilation function. It returns error message if the input string has syntax errors.

template :: ByteString -> TemplateSource

This function is provided for convenience only. It does the same as the templateE function, except that instead of returning an error message it just throws a run-time error with the textual representation of that message.

Template errors

data TemplateErr Source

This is a type of error messages you get when you're trying to compile a syntactically incorrect template. It's quite possible that you'll want to pattern-match on it, so it's internals are completely exposed, althoug it might change in future versions. Note that we also provide a function templateErr, which allows you to display errors with any formatting you like.

Constructors

NoLeftTag TemplateErrRight Pos

There is a command which should follow a specific opening command, like '@{' or '@[', but the latter could not be found.

For example, if your template is 'foo@|', then that's an error you'll get, as '@|' command should be between '@{' and '@}'.

NoRightTag TemplateErrLeft Pos

There is an opening command without a matching closing one.

MismatchedTags TemplateErrLeft Pos TemplateErrRight Pos

There is an opening command and a closing command, and they should match, but they don't.

If, for example, you close a branching, started by '@{' command, with '@]' command, you get this error.

There are two positions in this error message; the first is the position of the opening command, and the second is the position of the closing one.

Instances

data TemplateErrLeft Source

This is the part of an error message, which represents an erroneous opening command.

Constructors

TryLeftTag

There is an error with the '@{' command.

LoopLeftTag

There is an error with the '@[' command.

data TemplateErrRight Source

This is the part of an error message which represents an erroneous command, which should follow some specific opening one.

Constructors

TryRightTag

There is an error with the '@}' command.

OrRightTag

There is an error with the '@|' command.

LoopRightTag

There is an error with the '@]' command.

templateErr :: TemplateErr -> Context ()Source

If you want to present your error to the user for some reason, you can do this with Plat itself. You'll need to create a template, as only you can know for sure how this error message should be rendered, but the context is here. It gives you "left" (respectively "right") field if there is the problem with the opening (respectively closing or separating) command, which is also a record with "loop", "try" and "or" boolean fields (the latter being absent in the "left" record) whose values depend on the command in question, and a "pos" field indicating the position of this command. It's possible to have both "left" and "right" fields present, if there are an opening and closing commands that don't match; obviously, in this case both "left" and "right" fields would have there own "pos" subfields.

The position(s) is presented using the posContext function.

Rendering

Now, when you have a Context and a Template, it's time to bring them together.

With the examples given above, the result of rendering would be the following:

List of employees at Mac's tools:
1. Alice
2. Bob (going to be fired)
3. Nicolás

render :: Template -> Context a -> Either RenderErr ByteStringSource

This is the rendering functions. It substitutes values from the given Context into the Template, returning result as ByteString or indicating a failure.

Rendering errors

data RenderErr Source

This type represents a rendering error. Note that we provide a renderErr function which can be used if you want to present an error to the user.

This type internals are exposed for you to pattern-match on them, although they can change in the future versions.

Constructors

ErrorType ContextType ContextType Pos [ByteString]

The expression evaluates to something of the wrong type.

Here, first type is the actual one, while the second is the expected one.

ErrorField ContextType Pos [ByteString]

The expression can't be evaluated, since there is no field with the given name in the record.

ErrorOption (Pos, Pos) [RenderErr]

There are several options in the template, but all of them failed for some reason.

Error messages for all options are listed here.

ErrorCheck Pos [ByteString]

Template asks to check for some boolean expression, which happened to be false.

Instances

data ContextType Source

This is an auxiliary type, which indicates what is the type we expected, or what is the actual type we've got.

Instances

renderErr :: RenderErr -> Context ()Source

This function is similar to templateErr. It allows you to show the rendering error to the user. The context it generates always has a value named "pos", indicating the position where an error occured; if multiple branches failed, there is also a value "endpos", which indicates the position where the list of branches ends. Positions are presented with the posContext function.

Other field depend on the error. For a type mismatch, there are values "expected" and "actual", which are the strings 'string', 'boolean', 'array' or 'record', depending on what type the value should have and what type it actually has; there is also the "expr" field, which presents the list of names in the failing expression with the function listValues.

If the required field wasn't found in the record, then there is the value "field", which is a list of names in the failing expression, and the value "type", which shows what type that expression should have.

If there were multiple branches, all of them failing, then, apart from "pos" and "endpos" fields, there is a list of errors, one for each of the branches. These errors are presented using the renderErr function recursively.

Finally, if the boolean value was checked and happened to be false, there would be the field "check", which, again, is a list of names in the expression, which evaluated to false.

Position

type Pos = (LineNumber, CharNumber)Source

This type indicates some position in the template.

posContext :: Pos -> Context ()Source

This function is used by Plat to display the position. It gives you the record with two fields, "line" and "column", indicating the line number and the column number respectively.