Cottle Documentation

Library overview

What does it looks like?

Cottle, short for "Compact Object to Text Transform Language" is a lightweight template engine for .NET 4.0 allowing you to transform structured data into any text-based format output such as plain text, HTML or XML. It uses a simple yet easily extensible template language, thus enabling clean separation of document contents and layout.

A simple Cottle template printing an HTML document showing how many messages are in your mailbox could look like this:

Template <html> <body> <h1>Hello, {name}!</h1> <p> {if len(messages) > 0: You have {len(messages)} new message{if len(messages) > 1:s} in your mailbox! |else: You have no new message. } </p> </body> </html>

As you can guess by looking at this code, a Cottle template contains both "raw" text printed directly and commands used to define dynamic sections. Cottle supports most common template engine features, including:

  • Text substitutions through variables,
  • Builtin and used-defined functions,
  • Variables & functions declaration and assignments,
  • Conditional statements (if),
  • Loops (for, while).

Source code is available, feel free to read and contribute!

Download the library

Cottle is available as an installable package on NuGet official website. Just open your extension manager in Visual Studio, search for "Cottle" and install from there.

You can also read, download or contribute to the source code on GitHub.

Getting started

To start using Cottle, first reference the package in your solution (using NuGet or manual install). You'll then need two things:

  • An input template written with Cottle's template language, used to control how your data must be rendered. This template can be contained in a string or streamed from any source compatible with System.IO.TextReader class (text file, memory buffer, network socket...) as shown in the example below.
  • An executable code that reads your input template, create a Cottle.IDocument object from it then render it to an output string or System.IO.TextWriter instance.

Here is a basic yet working sample rendering a plain text template that doesn't contain dynamic code. Copy the "Template" snippet in a "test.cottle" file you put in the same folder your project executable is, then copy the "C# code" snippet somewhere in your program and get it executed. You should see the content of "Result" snippet printed to standard output:

Template This is my input template file (with only literal text for now).
C# code void RenderAndPrintMyTemplate() { using (var stream = new FileStream("test.cottle", FileMode.Open)) { using (var reader = new StreamReader(stream, Encoding.UTF8)) { var document = new SimpleDocument(reader); // throws ParseException on error var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "some_variable", "some value" } // Declare new "some_variable" string variable }); /* TODO: customize rendering if needed */ Console.Write(document.Render(context)); } } }
Result This is my input template file (with only literal text for now).

Next samples in this document will show the same "Template", "C# code" and "Result" snippets. Since the C# code above applies to all following examples it won't be repeated and only parts that should replace the "TODO" comment will appear.

Template language

Literal text and commands

A Cottle template can contain literal text (which will be printed "as is") and special command that will be executed when document is rendered. These commands can either print dynamic content or have side-effects such as controlling rendering flow or set variables. Here is an example using the "echo" command to print content of a variable named "x", we'll assume its value is "53":

Template Value of x is {echo x}.
Result Value of x is 53.

Here we used the echo command which prints the value of its argument to display current value of variable x. We'll see in next section how to pass variables to templates. Command echo is the most commonly used command so a shorter form is available and you can omit the "echo" keyword if the name of the variable you want to print doesn't conflict with another command: {echo 53} is equivalent to {53} in previous example. This form will be used in all following samples.

All commands should appear between special "begin command" and "end command" delimiters "{" (left brace) and "}" (right brace). Contents between those delimiters is recognized as commands which have their own syntax. Character "|" (pipe) is also used as a "continue command" delimiter for some commands.

Since delimiters have a special meaning you must escape them if you want to use them as literal text. Use a backslash character before any deliminer (or another backslash) to escape it:

Template Characters \{, \}, \| and \\ must be escaped.
Result Characters {, }, | and \ must be escaped.

Delimiters can be changed as you'll see in section "delimiters customization".

Sending variables to templates

To send variables so they can be used when a document is rendered you must provide them through a Cottle.IContext instance which is used as a render-time storage. This interface behaves quite like an immutable Dictionary<Cottle.Value, Cottle.Value> where Cottle.Value is a data structure able to store any value Cottle can handle. Key and value pairs within this dictionary are used as variable names and their associated values.

Implicit constructors from some native .NET types to Value type are provided so you usually don't have to explicitly do the conversion yourself but you can browse Cottle.Values namespace to see available types.

Once you assigned variables to a context, pass it to your document's rendering method so you can read them from your template (see "getting started" section for full code):

Template Hello {echo name}, you have no new message.
C# code var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "name", "John"} // implicit conversion, equivalent to 'new StringValue("John")' });
Result Hello John, you have no new message.

Contexts are passed at document render time so they can be changed from one render to another. This allows you to reuse a compiled document multiple times, since compiling a document has an important cost compared to rendering.

Constants, variables and types

Cottle supports immutable values which can either be declared as constants in templates or set in contexts you pass when rendering a document. Values have a type which can be one of the following:

  • Boolean (value is either true or false) ;
  • Number (equivalent to .NET's decimal) ;
  • String (sequence of character) ;
  • Map (associative key/value container) ;
  • Void (value is empty or undefined)

Map values are associative tables that contain multiple children values stored as key/value pairs. Values within a map can be accessed directly by their key, using either dotted or subscript notation:

Template You can use either {mymap.f1} or {mymap["f2"]} notations for map values.
C# code var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "mymap", new Dictionary<Value, Value> { // implicit conversion to MapValue { "f1", "dotted" }, { "f2", "subscript" } }} };
Result You can use either dotted or subscript notations for map values.

Please note the quotes used in subscript notation. Trying to access value of {mymap[f2]} will result in a very different behavior, since it will search for the value whose key is the value of f2 (which hasn't be defined), leading to an undefined result. It is valid to have a map in which two or more keys are equal, but you will only be able to access the last one when using direct access. However, iteration through the map's elements, as we'll see later, will show you its entire content.

Implicit constructors on Cottle.Value class allow you to convert most .NET standard types into a Cottle value instance. To get an empty value your from C# code use the Cottle.Values.VoidValue.Instance static property.

You can also declare constant values in your templates with following constructs:

Template {echo 17.42} {echo "Constant string"} {echo 'String with single quotes'} {echo ["key1": "value1", "key2": "value2"]} {echo ["constant", "map", "with", "numeric", "keys"]}

Keys can be ommited in constant maps declaration, numeric keys starting at index 0 are generated in this case. Also remember that both keys and values can be of any value type (numbers, strings, other nested maps...). There is no way to write a constant boolean value in a template ; you can write your own functions if you really need this feature, but numeric values 0 and 1 are equivalent except in very special cases.

Expression operators

Cottle supports the same mathematical and logical operators found in most programming langages. Here they are sorted by decreasing precedence order:

  • +, - and !: unary plus, minus and logical "not" operator ;
  • *, / and %: binary multiplication, division and modulo operators ;
  • + and -: binary addition and subtraction operators ;
  • <, <=, =, !=, >= and >: binary logical comparison operators ;
  • && and ||: binary "and" and "or" logical operators.

You can also use ( and ) to group sub-expressions and change natural precedence order. Here are some example of valid expressions:

Template {1 + 2 * 3} {(1 + 2) * 3} {!(x < 1 || x > 9)} {value / 2 >= -10} {"aaa" < "aab"}

Mathematical operators (+, -, *, / and %) only accept numeric operands and return a numeric value. Logical operators work on any type of operand in a similar way the cmp standard function does.

Calling builtin functions

Functions in Cottle are special values that can be invoked like in most programming languages. As they are values, they are set in a context, and a special context class is available so you can start with a predefined set of standard functions when rendering your documents. To use them, call Context.CreateBuiltin method and pass result to document's rendering method.

If you don't want to use standard functions you can get a blank context by calling Context.CreateCustom.

Here is how to call a function:

Template You have {len(messages)} new message(s) in your inbox.
C# code var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "messages", new Dictionary<Value,Value> { { 0, "message #0" }, { 1, "message #1" }, { 2, "message #2" } }} });
Result You have 3 new message(s) in your inbox.

All builtin functions are described in paragraph "builtin functions". For all following samples in this document we'll assume that builtin functions are available from rendered templates.

Conditional statements

You can write conditional statements by using the if command which uses a value as a predicate to check whether its body should be printed or not. Predicate is verified if value, once converted to a boolean type, is true. Any empty value, empty string, zero number of empty map will be converted to false while any other value will be converted to true.

Predicate must be followed by ":" (colon) character and body as a literal text (which can also include commands), then the end delimiter ("}" unless you changed it).

Template {if 1: A condition on a numeric value is true if the value is non-zero. } {if 0: see? }
Result A condition on a numeric value is true if the value is non-zero.

The if command also supports optional elif (else if) and else blocks that behave like in any other programming language. Replace the "end command" special delimiter by "continue command" one ("|" unless you changed it) to end the if body then use either else or elif followed by a predicate, then a ":" (colon) character and another body as a literal text. Last block must be ended by the "end command" special delimiter:

Template {if test: Variable "test" is true! |else: Variable "test" is false! } {if len(items) > 2: There are more than two items in map ({len(items)}, actually). } {if x < 0: X is negative. |elif x > 0: X is positive. |else: X equals zero. }
Context { "items" = new Dictionary<Value,Value> { { 0, "item #0" }, { 1, "item #1" }, { 2, "item #2" } }}, { "test", 42 }, { "x", -3 }
Result Variable "test" is true! There are more than two items in map (3, actually). X is negative.

Map values enumeration

Pairs contained within a map value can be enumerated with the for command, that will print its body for each key/value pair it found. An alternative syntax can be used if you only want to iterate on values and don't care about keys. The for command also supports an optional empty block printed when the map you tried to enumerate doesn't contain any key/value pair.

Syntax of the for keyword and its optional empty block is similar to the one used by the if command:

Template {for index, text in messages: Message #{index + 1}: {text} |empty: No messages to display. } Tags: {for tag in tags:{tag} }
Context { "messages", new Dictionary<Value,Value> { { 0, "Hi, this is a sample message!" }, { 1, "Hi, me again!" }, { 2, "Hi, guess what?" } }}, { "tags", new Value[] { "action", "horror", "fantastic" }}
Result Message #1: Hi, this is a sample message! Message #2: Hi, me again! Message #3: Hi, guess what? Tags: action horror fantastic

Commenting your templates

You can use the _ (underscore) command to add comments to your template. This command can be followed by any literal text and won't print any result when template is rendered.

Template {_ This is a comment that will be ignored when rendering the template} Hello, World!
Result Hello, World!

Variable assignments

You can assign variables while template is being rendered by using the set command.

Variable assignment allows you improve performance by storing evaluation results (such as function calls) that are going to be used several times, or code complex template mechanism by combining the if, for and set commands.

Template {set nb_msgs to len(messages)} {if nb_msgs > 0: You have {nb_msgs} new message{if nb_msgs > 1:s} in your mailbox! |else: You have no new message. } {set nb_long to 0} {for message in messages: {if len(message) > 20: {set nb_long to nb_long + 1} } } {nb_long} message{if nb_long > 1:s is|else: are} more than 20 characters long.
Context { "messages", new Dictionary<Value,Value> { { 0, "Hi, this is a sample message!" }, { 1, "Hi, me again!" }, { 2, "Hi, guess what?" } }}
Result You have 3 new messages in your mailbox! 1 message is more than 20 characters long.

More advanced use of variable assignments are explained later in this document.

Conditional loops

The while command evaluates its predicate argument and continues executing its body until predicate is false. Be sure to check for a condition that will become false after a finite number of iterations, or rendering of your template will never complete.

Template {set password to ""} {set nb to rand(8, 16)} {set i to 0} {while i < nb: {set ascii to rand(62)} {if ascii < 10: {set password to cat(password, char(ascii + ord("0")))} |elif ascii < 36: {set password to cat(password, char(ascii + ord("A") - 10))} |else: {set password to cat(password, char(ascii + ord("a") - 36))} } {set i to i + 1} } Your random password is: {password}.
Result Your random password is: 2PIBnodftxz8jAX.

When possible, prefer the use of the for command over while.

Configuration options

Dynamic compiled documents

Code sample from the "getting started" section used a SimpleDocument object to convert template into a renderable document. This class uses a simple tree evaluator to provide decent performance and light construction cost. In some cases you may want to benefit from better rendering performance at the cost of a heavier construction impact.

This is where DynamicDocument may come in handy: this alternative document uses dynamic IL generation to compile your template into executable code directly:

C# code document = new DynamicDocument(myTemplate); // throws ParseException on error

You should use DynamicDocument only if you render your documents way more often than you compile them, otherwise you would probably put too much pressure on .NET's garbage collector (bench and compare both variants to select the one which better suits your needs).

Also note that performance gain increases with the number of commands you use in your templates ; almost plain-text templates won't render faster with a dynamic document.

Using custom configuration

When creating a new Cottle document, you can pass it an optional Cottle.ISetting object which contains configuration parameters. If this parameter is missing then a default instance is used. You can use builtin Cottle.Settings.CustomSetting object to override any configuration parameter you want, or implement the interface yourself for more advanced uses:

C# code void RenderAndPrintMyTemplate() { using (Stream stream = new FileStream("test.cottle", FileMode.Open)) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { var document = new SimpleDocument(reader, setting); // throws ParseException on error var setting = new CustomSetting(); /* TODO: update setting properties */ var context = Context.CreateBuiltin(new Dictionary<Value, Value> { /* TODO: declare some variables */ }); Console.Write(document.Render(context)); } } }

Configuration parameters can be updated where the first "TODO" comment is located. This code will be used for detailed usage of specific parameters in following sections.

Raw text trimming

Cottle's default behavior for raw text is to render it without any modification. While this gives you a perfect character-level control of how a template is rendered, it may prevent you from writing clean indented code for target formats where white spaces are either ignored or collapsed, such as HTML.

For that reason, you can change the way raw text is transformed by Cottle before it is rendered through the use of "trimmers". Some default trimmers are bundled with the library but you can write your own if needed. Available trimmers are:

  • CollapseBlankCharacters: replace all sequences of white characters (spaces, line breaks, etc.) by a single space. This can be handy for targetting languages that ignore consecutive whitespaces such as HTML or XML.
  • LeadingAndTrailingBlankCharacters: remove all leading and trailing blank characters from raw text blocks.
  • FirstAndLastBlankLines: remove first and last empty lines from raw text blocks except for blocks that contain only blank characters.

Here are some simple templates and their rendered results using different cleaners. Unlike other examples in this document, exact white spaces are preserved in results.

CollapseBlankCharacters:

Template <ul> {for s in ["First", "Second", "Third"]: <li> {s} </li> } </ul>
C# code setting.Trimmer = Trimmers.CollapseBlankCharacters;
Result <ul> <li> First </li> <li> Second </li> <li> Third </li> </ul>

LeadingAndTrailingBlankCharacters:

Template {'white'} {'spaces '} around raw blocks are going to be {' collapsed'} .
C# code setting.Trimmer = Trimmers.LeadingAndTrailingBlankCharacters;
Result whitespaces around raw blocks are going to be collapsed.

FirstAndLastBlankLines:

Template This is a line of text with {'inline'} Cottle code. {for s in ["normal", "exceptional", "elite"]: - Length of string "{s}" is {len(s)} character(s). }
C# code setting.Trimmer = Trimmers.FirstAndLastBlankLines;
Result This is a line of text with inline Cottle code. - Length of string "normal" is 6 character(s). - Length of string "exceptional" is 11 character(s). - Length of string "elite" is 5 character(s).

If you want to write your own trimmer, assign a method taking a string as its only argument and returning the "trimmed" text. This method will be called on for each raw text block from input templates should return the text you want to appear when printed document is rendered.

Delimiters customization

When using default configuration, Cottle uses { character to start a code block, | to continue it (before an else statement for example), and } to end it. These characters may not be a good choice if you want to write a template that makes extensive literal use of them, for example if you're writing a JavaScript template, because you would have to escape every {, } and | to avoid Cottle seeing them as block delimiters.

To workaround this problem you can change default block delimiters when creating a new document. Any string can be used as a delimiter as long as it doesn't conflict with a valid Cottle expression (e.g. [, + or <). Make sure at least the first character of your delimiters doesn't cause any amiguity when choosing them.

Template Block delimiters are now {{block_begin}}, {{block_continue}} and {{block_end}}.
C# code setting.BlockBegin = "{{"; setting.BlockContinue = "||"; setting.BlockEnd = "}}"; var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "block_begin", "double left brace (" + setting.BlockBegin + ")" }, { "block_continue", "double pipe (" + setting.BlockContinue + ")" }, { "block_end", "double right brace (" + setting.BlockEnd + ")" } });
Result Block delimiters are now double left brace ({{), double pipe (||) and double right brace (}}).

Disable code optimizer

Cottle performs various optimizations on your code when converting a template into document to ensure faster rendering. You may want to disable these optimizations for some reasons, e.g. when debugging, or if you want to speed up document construction (but slow down rendering):

C# code setting.Optimize = false;

Turning optimization off is not recommended however.

Advanced topics

Debug value contents

When your template doesn't render as you would expect, the dump command can help you identify issues by showing value as an explicit human readable string. Undefined values, for example, won't print anything when passed through the echo command, but the dump command will show them as <void>.

Template {dump "string"} {dump 42} {dump unknown(3)} {dump [856, "hello", "x": 17]}
Result "string" 42 <void> [0: 856, 1: "hello", "x": 17]

Understanding value types

Every value has a type in Cottle, even if you usually don't have to worry about it. Functions that expect arguments of specific types will try to cast them silently or fallback to empty values if they can't. In some cases you may have to force a cast yourself to get desired result, for example when accessing values from a map:

Template {set map to ["first", "second", "third"]} {set key to "1"} {dump map[key]}
Result <void>

You could have expected this template to display "second", but Cottle actually searches for the map value associated to key "1" (as a string), not key 1 (as a number). These are two different values and storing two different values for keys "1" and 1 in a map is perfectly valid, hence no automatic cast can be performed for you.

In this example, you can explicitely change the type of key to a number by using the cast builtin function.

Workaround immutability

Variables in Cottle are immutable, meaning it's not possible to replace portion of a string or assign some value to a map subscript. If you want to append, replace or erase a value in a map, you'll have to build a new one where you inject, filter out or replace desired value. Builtin functions "cat", "slice", "union", "except" can be used to easily achieve such operations:

Template {set my_string to "Modify me if you can"} {set my_string to cat("I", slice(my_string, 16), ".")} {dump my_string} {set my_array to [4, 8, 50, 90, 23, 42]} {set my_array to cat(slice(my_array, 0, 2), slice(my_array, 4))} {set my_array to cat(slice(my_array, 0, 2), [15, 16], slice(my_array, 2))} {dump my_array} {set my_hash to ["delete_me": "TODO: delete this value", "let_me": "I shouldn't be touched"]} {set my_hash to union(my_hash, ["append_me": "I'm here!"])} {set my_hash to except(my_hash, ["delete_me": 0])} {dump my_hash}
Result "I can." [0: 4, 1: 8, 0: 15, 1: 16, 4: 23, 5: 42] ["let_me": "I shouldn't be touched", "append_me": "I'm here!"]

Functions declaration

Cottle allows you to declare functions directly in your template code so you can factor code as you would do with any other programming language. To declare a function and assign it to a variable use the same set you used for regular values assignments with a slightly different syntax. Function arguments must be specified between parenthesis right after the variable name that should receive the function, and the to keyword must be followed by a ":" (semicolon) character then function body declaration.

Functions can return a value that can be used in any expression or stored in a variable. To make a function halt and return a value, use the return command within its body:

Template {set factorial(n) to: {if n > 1: {return n * factorial(n - 1)} |else: {return 1} } } Factorial 1 = {factorial(1)} Factorial 3 = {factorial(3)} Factorial 8 = {factorial(8)} {set hanoi_rec(n, from, by, to) to: {set n to n - 1} {if n > 0: {hanoi_rec(n, from, to, by)}} Move one disk from {from} to {to} {if n > 0: {hanoi_rec(n, by, from, to)}} } {set hanoi(n) to: {hanoi_rec(n, "A", "B", "C")} } {hanoi(3)}
Result Factorial 1 = 1 Factorial 3 = 6 Factorial 8 = 40320 Move one disk from A to C Move one disk from A to B Move one disk from C to B Move one disk from A to C Move one disk from B to A Move one disk from B to C Move one disk from A to C

You can see in this example that returning a value and printing text are two very different things. Raw text within function body is printed each time the function is called, or more precisely each time its enclosing block is executed (that means it won't print if contained in an if command that fails to pass, for example).

The value returned by the function won't be printed unless you explicitly require it by using the echo command (e.g. something like {factorial(8)}). If a function doesn't use any return command it returns an undefined value, that's why the call to {hanoi(3)} in the sample above does not print anything more than the raw text blocks it contains.

Assigned variables scope

When writing complex templates using nested or recursive functions, you may have to take care of variable scopes to avoid potential issues. A scope is the local evaluation context of any function or command having a body. When assigning a value to a variable as described in paragraph "Variables assignment", all variables belong to the same top-level scope. Consider this template:

Template {set depth(item) to: {set res to 0} {for child in item: {set res_child to depth(child) + 1} {set res to max(res, res_child)} } {return res} } {depth([["1.1", "1.2", ["1.3.1", "1.3.2"]], "2", "3", ["4.1", "4.2"]])}

The depth function is expected to return the level of the deepest element in a value that contains nested maps. Of course it could be written in a much more efficient way without using non necessary temporary variables, but it would hide the problem we want to illustrate. If you try to execute this code you'll notice it returns 2 where 3 would have been expected.

Here is the explanation: when using the set method to assign a value to variable res, it always uses the same res instance. The depth function recursively calls itself but overwrite the unique res variable each time it tries to store a value in it, and therefore fails to store the actual deepest level as it should.

To solve this issue, the res variable needs to be local to function depth so that each invocation uses its own res instance. This can be achieved by using the declare command that creates a variable in current scope. Our previous example can then be fixed by declaring a new res variable inside body of function depth, so that every subsequent reference to res resolves to our local instance:

Template {set depth(item) to: {declare res} {set res to 0} {for child in item: {set res_child to depth(child) + 1} {set res to max(res, res_child)} } {return res} } {depth([["1.1", "1.2", ["1.3.1", "1.3.2"]], "2", "3", ["4.1", "4.2"]])}

You could even optimize the first set command away by assigning a value to res during declaration ; the declare command allows you to assign functions or values to variables by using the same kind of syntax than the set command, the only difference being than "to" should be replaced by "as":

Template {declare res as 0}

The same command can also be used to declare functions:

Template {declare depth(item) as: ... }

Note that the set command can also be used without argument, and assigns variable an empty value.

Custom .NET methods

If you need new features or improved performance, you can assign your own .NET methods to template variables. That's actually what Cottle does when you use Context.CreateBuiltin method: a set of internal Cottle methods is added to your context, and you can have a look at the source code to see how these methods work.

To pass a method in a context, wrap it within a NativeFunction object which can then be stored in a FunctionValue (an implicit constructor is also available), then add it to your store as you would do for any value:

Template Testing custom "repeat" function: {repeat("a", 15)} {repeat("oh", 7)} {repeat("-", 10)}
C# code // Implicit construction of Value from a NativeFunction instance var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "repeat", new NativeFunction(arguments => { var builder = new StringBuilder(); for (var i = 0; i < arguments[1].AsNumber; ++i) builder.Append(arguments[0].AsString); return builder.ToString(); }, 2)} };
Result Testing custom "repeat" function: aaaaaaaaaaaaaaa ohohohohohohoh ----------

Your custom .NET method can take up to three arguments but only the first one is mandatory:

  • The IReadOnlyList<Value> list of arguments passed to the function,
  • The IStore storage with all variables currently defined in the document,
  • A TextWriter stream writer opened on document rendering output.

Arguments are instances of the Value class, which can hold data of any type known by Cottle. You can access their AsBoolean, AsFunction, AsNumber and AsString properties to get data of desired type (automatic type conversion will be performed if needed). You can also use the Type property to know which data type a Value instance is actually holding. The Fields property contains all key/value pairs.

In most cases you won't use store and output text writer, but may need them if you want to run a function evaluation within your code. The text writer instance can however be used if you want your function to print something.

The NativeFunction constructors also accept 2 more optional arguments used to indicate how many arguments are expected for your function. You can either ignore those arguments (and check yourself how many values your function received), specify an exact number of expected arguments, or specify min/max boundaries (-1 as a max boundary means "unlimited"). When specifying expected arguments count, Cottle will perform this check for you each time your function is invoked and ignore the call on error.

Your custom function is expected to return a Value object. Remember that implicit constructors are available, so returning a compatible .NET native type is valid too.

Lazy value evaluation

In some cases, you may want to inject to your template big and/or complex values that may or may not be needed at rendering, depending on other parameters. In such configurations, it may be better to avoid injecting the entire value in your context if there is chances it won't be used, and use lazy evaluation instead.

Lazy evaluation allows you to inject a value with a "resolver" callback which will be called only the first time value is accessed, or not called at all if value is not used for rendering. Lazy values are just another specialized version of the Value object and are built from any resolver callback that returns a Value instance:

Template {if is_admin: Administration log: {log} }
C# code var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "is_admin", user.IsAdmin }, { "log", () => log.BuildComplexLogValue() } // implicit conversion to LazyValue }); document.Render(context, Console.Out);

In this example, method log.BuildComplexLogValue won't be called unless is_admin value is true.

Inspect through reflection

Instead of converting complex object hierarchies to Cottle values, you can have the library do it for you by using .NET reflection. Keep in mind that reflection is significantly slower than creating Cottle values manually, but as it's a lazy mechanism it may be a good choice if you have complex objects and don't know in advance which fields might be used in your templates.

To use reflection, create a new ReflectionValue from any .NET object instance, and pass it to your context. Its fields and properties will be accessible like if it were a Cottle map:

Template Your image has a size of {image.Width}x{image.Height} pixels. {for key, value in image: {key} = {value} }
C# code var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "image", new ReflectionValue(new Bitmap(50, 50)) } });
Result Your image has a size of 50x50 pixels. nativeImage = Tag = PhysicalDimension = Size = Width = 50 Height = 50 HorizontalResolution = 96 VerticalResolution = 96 Flags = 2 RawFormat = PixelFormat = Palette = FrameDimensionsList = PropertyIdList = PropertyItems =

By default, a ReflectionValue will read all instance members, both public and private. If you want to change this behavior, specify your BindingFlags as a second parameter to ReflectionValue constructor.

Builtin functions

Numeric and mathematical

abs(numeric)

Return the absolute value of given numeric value.

Template {abs(-3)} {abs(5)}
Result 3 5

add(a, b)

Return the sum of two numeric values (equivalent to + operator).

Template {add(3, 7)}
Result 10

ceil(x)

Returns the smallest integer greater than or equal to decimal value x.

Template {ceil(2.7)}
Result 3

cos(x)

Get the cosine of angle x in radians.

Template {cos(-1.57)}
Result 0.000796326710733263

div(a, b)

Return the numeric value of a divided by the numeric value of b (equivalent to / operator).

Template {div(5, 2)}
Result 2.5

floor(x)

Returns the largest integer less than or equal to decimal value x.

Template {floor(2.7)}
Result 2

max(a[, b[, c, ...]])

Return the highest numeric value among given ones.

Template {max(7, 5)} {max(6, 8, 5, 7, 1, 2)}
Result 7 8

min(a[, b[, c, ...]])

Return the lowest numeric value among given ones.

Template {min(9, 3)} {min(6, 8, 5, 7, 1, 2)}
Result 3 1

mod(a, b)

Return the value of the first numeric argument modulo the second one (equivalent to % operator).

Template {mod(7, 3)}
Result 1

mul(a, b)

Return the numeric value of a times b (equivalent to * operator).

Template {mul(3, 4)}
Result 12

pow(x, y)

Get specified number x raised to the power y.

Template {pow(2, 10)}
Result 1024

rand([a[, b]])

Get a pseudo-random numeric value between 0 and 2.147.483.647 inclusive. If numeric a value is specified, return a pseudo-random numeric value between 0 and a exclusive. If both numeric values a and b are specified, return a pseudo-random numeric value between a inclusive and b exclusive.

Template {rand()} {rand(1, 7)}
Result 542180393 5

round(x[, y])

Rounds a decimal value to a specified number of fractional digits y, or to the nearest integral value if y is not specified.

Template {round(1.57)} {round(1.57, 1)}
Result 2 1.6

sin(x)

Get the sine of angle x in radians.

Template {sin(1.57)}
Result 0.999999682931835

sub(a, b)

Return the numeric value of a minus b (equivalent to - operator).

Template {sub(3, 5)}
Result -2

String and map processing

cat(a, b, ...)

Concatenate all input values into a single one. This function works on both strings and maps (keys are not preserved when used with maps).

Template {cat("Hello, ", "World!")} {for v in cat([1, 2], [3]): {v}}
Result Hello, World! 1 2 3

char(int)

Get a 1-character string from its unicode numeric value. See Unicode on Wikipedia for details.

Template {char(97)}
Result a

find(source, search[, start])

Find index of given search value in a map or sub-string in a string. Returns 0-based index of match if found, or -1 otherwise. Search starts at index 0 unless start argument is specified.

Template {find([89, 3, 572, 35, 7], 35)} {find("hello, world!", "o", 5)} {find("abc", "d")}
Result 3 8 -1

format(value, format[, culture])

Convert any value to a string using given formatting from format string expression. Format should use syntax "str" or "t:str" where "t" indicates the type of the formatter to use and "str" is the associated .NET format string. Available formatter types are:

  • a: automatic (default, used if "t" is omitted)
  • b: System.Boolean
  • d or du: System.DateTime (UTC)
  • dl: System.DateTime (local)
  • i: System.Int64
  • n: System.Decimal
  • s: System.String
Format string depends on the type of formatter selected, see help about String.Format method for more information about format strings.
Template {format(1339936496, "d:yyyy-MM-dd HH:mm:ss")} {format(0.165, "n:p2", "fr-FR")} {format(1, "b:n2")}
Result 2012-06-17 12:34:56 16,50 % True

Formatters use current culture, unless a culture name is specified in the culture argument. See National Language Support API on MSDN for a list of valid culture names.

lcase(string)

Return a lowercase conversion of given string value.

Template {lcase("Mixed Case String"}
Result mixed case string

len(a)

Return the length or given value, that is number pairs if value was a map, or number of characters if value was a string.

Template {len("Hello!")} {len([17, 22, 391, 44])}
Result 6 4

match(string, pattern)

Match string against given regular expression pattern. If match is successful, a map containing full match followed by captured groups is returned, otherwise result is an empty value. See .NET Framework Regular Expressions for more information.

Template {dump match("abc123", "^[a-z]+([0-9]+)$")} {dump match("xyz", "^[a-z]+([0-9]+)$")}
Result [0: "abc123", 1: "123"] <void>

ord(char)

Get the numeric unicode value of the first character of given string. See Unicode on Wikipedia for details.

Template {ord("a")}
Result 97

slice(value, index[, count])

Extact sub-string from a string or elements from a map (keys are not preserved when used with maps). count items or characters are extracted from given 0-based numeric index. If no count argument is specified, all elements starting from given index are extracted.

Template {for v in slice([68, 657, 54, 3, 12, 9], 3, 2): {v}} {slice("abchello", 4)}
Result 3 12 hello

split(input, separator)

Split string input according to given string separator separator. Result is an map where pair values contain split sub-strings.

Template {dump split("2011/01/01", "/")}
Result [0: "2011", 1: "01", 2: "01"]

token(string, search, index[, replace])

Either return the n-th section of a string delimited by separator substring search if no replace argument is provided, or replace this section by replace else. This function can be used as a faster alteranative to combined split/slice/join calls in some cases.

Template {token("First.Second.Third", ".", 1)} {token("A//B//C//D", "//", 2)} {token("XX-??-ZZ", "-", 1, "YY")} {token("1;2;3", ";", 3, "4")}
Result Second C XX-YY-ZZ 1;2;3;4

ucase(string)

Return an uppercase conversion of given string value.

Template {ucase("Mixed Case String"}
Result MIXED CASE STRING

Comparison and boolean tests

and(a, b, ...)

Perform logical "and" between given boolean values, i.e. return "true" if all arguments are true (equivalent to && operator).

Template {and(2 < 3, 5 > 1)}
Result true

cmp(a, b)

Compare a against b, and return -1 if a is lower than b, 0 if they're equal, or 1 else. When used on numeric values, the cmp function uses numerical order. When used on strings, it uses alphabetical order. When used on maps, it first compare their lengths then compare their values if lengths are equal. Two values of different types are always different.

Template {cmp("abc", "bcd")} {cmp(9, 6)} {cmp([2, 4], [2, 4])}
Result -1 1 0

default(test, fallback)

Return test if it evaluates to true or fallback otherwise.

Template {set x to 3} {dump default(x, "invisible")} {dump default(y, "visible")}
Result 3 "visible"

eq(a, b, ...)

Return true if all arguments are equal, or false otherwise (equivalent to = operator). See cmp common function for more information about comparisons.

Template {eq(7, 7)} {eq(1, 4)} {eq("test", "test")}
Result true false true

ge(a, b)

Return true if the numeric value of a is greater than or equal to the numeric value of b, false otherwise (equivalent to >= operator).

Template {ge(7, 3)} {ge(2, 2)} {ge("abc", "abx")}
Result true true false

gt(a, b)

Return true if the numeric value of a is greater than the numeric value of b, false otherwise (equivalent to > operator).

Template {gt(7, 3)} {gt(2, 2)} {gt("abc", "abx")}
Result true false false

has(map, key)

Return true if given map has a value associated to given key, or false otherwise.

Template {has(["name": "Paul", "age": 37, "sex": "M"], "age")}
Result true

le(a, b)

Return true if the numeric value of a is lower than or equal to the numeric value of b, false otherwise (equivalent to <= operator).

Template {le(3, 7)} {le(2, 2)} {le("abc", "abx")}
Result true true true

lt(a, b)

Return true if the numeric value of a is lower than the numeric value of b, false otherwise (equivalent to < operator).

Template {lt(3, 7)} {lt(2, 2)} {lt("abc", "abx")}
Result true false true

ne(a, b, ...)

Return true if all arguments are not equal, or false otherwise (equivalent to != operator).

Template {ne(7, 7)} {ne(1, 4)} {ne("test", "test")}
Result false true false

not(a)

Perform logical "not" on given boolean value, i.e return true if value was false or false if it was true (equivalent to ! operator).

Template {not(1 = 2)}
Result true

or(a, b, ...)

Perform logical "or" between given boolean values, i.e. return "true" if at least one argument is true (equivalent to || operator).

Template {or(2 = 3, 5 > 1)}
Result true

when(test[, true[, false]])

Return true if test evaluates to true, or false otherwise (or an empty value if false is missing).

Template {set x to 3} {set y to 0} {dump when(x, "x is true", "x is false")} {dump when(y, "y is true", "y is false")}
Result "x is true" "y is false"

xor(a, b, ...)

Perform logical "xor" between given boolean values, i.e. return "true" if exactly one argument is true and all the others are false.

Template {xor(2 < 3, 1 = 2)}
Result true

Functional programming

cross(map1, map2, ...)

Return a map containing all pairs from map1 having a key that also exists in map2 and all following maps. Output pair values will always be taken from map1.

Template {dump cross([1: "a", 2: "b", 3: "c"], [1: "x", 3: "y"])}
Result [1: "a", 3: "c"]

except(map1, map2, ...)

Return a map containing all pairs from map1 having a key that does not exist in map2 and any of following maps. This function can also be used to remove a single pair from a map (if you are sure that it's key is not used by any other pair, otherwise all pairs using that key would be removed also).

Template {dump except([1: "a", 2: "b", 3: "c"], [2: "x", 4: "y"])}
Result [1: "a", 3: "c"]

filter(map, predicate[, a, b, ...])

Return a map containing all pairs having a value that satisfies given predicate. Function predicate is invoked for each value from map with this value as its first argument, and pair is added to output map if predicate result evaluates to true.

Optional arguments can be specified when calling filter and will be passed to each invocation of predicate as second, third, forth argument and so on.

Template {declare multiple_of(x, y) as: {return x % y = 0}} {for v in filter([1, 6, 7, 4, 9, 5, 0], multiple_of, 3): {v}}
Result 6 9 0

flip(map)

Return a map were pairs are created by swapping each key and value pair from input map. Using resulting map with the for command will still iterate through each pair even if there was duplicates, but only the last occurrence of each duplicate can be accessed using map subscript operator ([]).

Template {dump flip([1: "hello,", 2: "world!"])} {dump flip(["a": 0, "b": 0])}
Result ["hello,": 1, "world!": 2] [0: "a", 0: "b"]

join(map[, string])

Concatenate all values from given map pairs, using given string as a separator (or empty string if no separator is provided).

Template {join(["2011", "01", "01"], "/")}
Result 2011/01/01

map(source, modifier[, a, b, ...])

Return a map where values are built by applying given modifier to map values, while preserving keys. Function modifier is invoked for each value in source with this value as its first argument.

Optional arguments can be specified when calling map and will be passed to each invocation of modifier as second, third, forth argument and so on.

Template {declare square(x) as: {return x * x}} {for v in map([1, 2, 3, 4], square): {v}} {dump map(["a": 1, "b": 7, "c": 4, "d": 5, "e": 3, "f": 2, "g": 6], lt, 4)}
Result 1 4 9 16 ["a": 1, "b": 0, "c": 0, "d": 0, "e": 1, "f": 1, "g": 0]

range([start, ]stop[, step])

Generate a map where value of the i-th pair is start + step * i and last value is lower (or higher if step is a negative integer) than stop. Default base index is 0 if the start argument is omitted, and default value for step is 1 if start < stop or -1 otherwise.

Template {for v in range(5): {v}} {for v in range(2, 20, 3): {v}}
Result 0 1 2 3 4 2 5 8 11 14 17

sort(map[, callback])

Return a sorted copy of given map. First argument is the input map, and will be sorted using natural order (numerical or alphabetical, depending on value types) by default. You can specify a second argument as comparison delegate, that should accept two arguments and return -1 if the first should be placed "before" the second, 0 if they are equal, or 1 otherwise.

Template {set shuffled to ["in", "order", "elements" "natural"]} {for item in sort(shuffled): {item} } {declare by_length(a, b) as: {return cmp(len(b), len(a))} } {set shuffled to ["by their", "are sorted", "length", "these strings"]} {for item in sort(shuffled, by_length): {item} }
Result elements in natural order these strings are sorted by their length

union(map1, map2, ...)

Return a map containing all pairs from input maps, but without duplicating any key. If a key exists more than once in all input maps, the last one will overwrite any previous pair using it.

Template {dump union([1: "a", 2: "b"], [2: "x", 3: "c"], [4: "d"])}
Result [1: "a", 2: "x", 3: "c", 4: "d"]

zip(a, b)

Combine given maps to create a new one. The n-th pair in result map will use the n-th value from first map as its key and the n-th value of second map as its value. If the two maps don't have the same length, exceeding elements from the longest map are ignored.

Template {set a to ["key1", "key2", "key3"]} {set b to ["value1", "value2", "value3"]} {dump zip(a, b)}
Result ["key1": "value1", "key2": "value2", "key3": "value3"]

Type and indirection

call(func, map)

Call function func with values from given map as arguments (keys are not used).

Template {call(cat, ["Hello", ", ", "World", "!"])} {call(max, [3, 8, 2, 7])}
Result Hello, World! 8

cast(value, type)

Get value converted to requested scalar type. Type must be a string value specifying desired type:

  • "b" or "boolean": convert to boolean value
  • "n" or "number": convert to numeric value
  • "s" or "string": convert to string value
Template {dump cast("2", "n") = 2} {dump ["value for key 0"][cast("0", "n")]} {dump cast("some string", "b")}
Result <true> "value for key 0" <true>

defined(value)

Check whether value is defined or not. Note this is different than checking whether a value evaluates to true or not: integer 0 is equivalent to false when used as a boolean expression, yet is defined. This function is mostly useful for testing if a variable has been assigned a value.

Template {dump defined(undefined)} {set a to 0} {dump defined(a)}
Result <false> <true>

type(value)

Retrieve type of given value as a string. Possible return values are "boolean", "function", "map", "number", "string" or "void".

Template {dump type(15)} {dump type("test")}
Result "number" "string"

Obsolete functions

include(path[, map[, map, ...]])

This function is obsolete as it is too specific and has insufficient customization support, and therefore is not available by default in builtin functions. You should consider reimplementing your own "include" function if you need a similar feature.

Parse document from a template file and render it with variables extracted from given maps. Keys from input maps will be used as variable names associated to their respective values. For this example, we'll assume that a file named "article.cottle" is available in current directory and contains this template code:

Template "article.cottle":
<div class="box"> <div class="title">{a.title}</div> <div class="text">{a.text}</div> </div>

Now, we want to include and render this template file in our global rendering:

Template {for article in articles: {include("article.cottle", ["a": article])} }
C# code var context = Context.CreateBuiltin(new Dictionary<Value, Value> { { "articles", new Value[] { new Dictionary<Value, Value> { {"title", "First article"}, {"text", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."} }, new Dictionary<Value, Value> { {"title", "Second article"}, {"text", "Cras vitae erat dui, commodo gravida purus."} } }} });
Result <div class="box"> <div class="title">First article</div> <div class="text">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div> </div> <div class="box"> <div class="title">Second article</div> <div class="text">Cras vitae erat dui, commodo gravida purus.</div> </div>

By using return keyword in your included template, you can pass a value to the calling template (it will be available as the result of the call to function include).

For performance reasons, included templates are stored in an internal cache which keeps parsed versions of the last 256 included documents. If you want to change cache size, use the CommonFunctions.IncludeCacheSize property. You can also disable the cache by setting the CommonFunctions.IncludeCacheEnabled to false ; be sure to set it back to true before deploying your code to a production environment.

Reference

Change log

Cottle version numbers use format A.B.C.D where each digit get incremented for different reasons:

  • A changes when code base is entirely rewritten from scratch with no compatibility with previous version ;
  • B changes when backwards incompatible changes are introduced to the API ;
  • C changes when significant new features are added to the API while preserving backward compatibility ;
  • D changes when a bugfix, patch or any other minor change is released.

Here is a summary of backward-incompatible changes introduced in previous versions:

  • 1.4.* => 1.5.*:
    • IStore replaced by immutable IContext interface for rendering documents. Since the former extends the later, migration should only imply recompiling without any code change.
    • Cottle function delegates now receive a IReadOnlyList<Value> instead of their mutable equivalent.
    • Method Save from DynamicDocument can only be used in the .NET Framework version, not the .NET Standard one.
  • 1.3.* => 1.4.*:
    • Cottle now requires .NET 4.0 or above.
  • 1.2.* => 1.3.*:
    • Removed deprecated code (flagged as "obsolete" in previous versions).
  • 1.1.* => 1.2.*:
    • IScope replaced by similar IStore interface (they mostly differ by the return type of their "Set" method which made this impossible to change without breaking the API).
    • Callback argument of constructors for NativeFunction are not compatible with IScope to avoid ambiguous statements.
  • 1.0.* => 1.1.*:
    • LexerConfig must be replaced by CustomSetting object to change configuration.
    • FieldMap has been replaced by multiple implementations of the new IMap interface.
    • Two values with different types are always different, even if casts could have made them equal (i.e. removed automatic casts when comparing values).
    • Common functions cross and except now preserve duplicated keys.

Greetings

  • Huge thanks to Zerosquare for the lovely project icon!

Contact

Contact me by e-mail: cottle [at] mirari [dot] fr

Remi Caput, 2012