makrell

0.1

makrell is a templating engine and macro processor for php.

Usage

makrell API is quite simple. You instantiate a Makrell object (no constructor params required) and call one of the few simple methods. Examples:

Parse the text according to makrell rules and put the result in $text.

require 'makrell.inc.php';
$m = new Makrell;
$text = $m->parse($some_text);

Parse the text in a file and echo the result.

require 'makrell.inc.php';
$m = new Makrell;
echo $m->parse_file('/path/to/file');

Parse the text in a file, execute the result as php code and echo the ouput. Here, 'user_name' is a "template variable" (only visible within the template).

require 'makrell.inc.php';
$m = new Makrell;
$m->set('user_name', 'Linda');
echo $m->render('/path/to/file');

Besides that, there are a couple of utility classes that can be used to build generic regexp-based parsers. For example, here's a simple but working BB code parser.

class BBParser extends PregParser
{
        function parse($text) {
                $this->clip = new PregClipboard;
                $text = $this->kfun($text, array(
                        '~\[code\](.+?)\[/code\]~si' => '.code',
                        '~\[(/?(b|i|u))\]~i'         => '.tag'));
                return $this->clip->paste(htmlspecialchars($text));
        }
        function code($m) {
                return $this->clip->copy('<code>' . htmlspecialchars($m[1]) . '</code>');
        }
        function tag($m) {
                return $this->clip->copy('<' . $m[1] . '>');
        }
}

$input = "
        [b]bold[/b] and [i]italic[/i]
        <script src=evil.js>
        [code] x = a[i] > a[b] [/code]
";

$p = new BBParser;
echo $p->parse($input);

Syntax

The purpose of makrell is to find and replace specific "constructs" (macros and commands) in the text. Everything that doesn't look like a special construct is not parsed and returned as is. This is much like php itself works.

Macros

A macro definition consists of a key and a body. The body is enclosed in two or more curly braces and can be multiline. The key must start on the new line (leading whitespaces are ignored) and the opening {{ must be one the same line.

ok {{ body }}

        ok {{ body }}
        
ok {{ body }} BAD {{ body }} 

ok {{
        more
        lines
}}

ok {{{{{{ weird }}}}}}

BAD
{{ foo }}

Each time makrell encounters a macro key somewhere in the text, it is replaced with the body. This is called macro expansion.

name {{ Linda }} <---- definition

Hi, name!        <---- expansion

// Hi, Linda!     <---- result

Keys don't have to be identifiers or be in any fixed format.

<main>  {{ <h1> }}
...     {{ &hellip; }}
50%     {{ a half }}

Macro replacements are always global, that is, macro key is expanded even if its definition is textually below it. If there are two or more macro definitions with the same key, the textually later one overrides the earlier one.

Parameters

Macro parameter is a word (only lowercase latin letters are allowed) prepended with a '@'. Parameters can be used both in macro key and body. When macro is expanded, matched substrings are captured in the key parameters and then inserted into the body.

bold(@text)  {{ <b>@text</b> }}

bold(foo) bold(bar)

// Result:
// <b>foo</b> <b>bar</b>

Parameters can be placed anywhere in the macro and there is no rules on how they should be delimited. makrell performs a "non-greedy" search, that is, a parameter matches as little symbols as possible to make the whole macro match. Examples:

--@text {{ <h2>@text</h2> }}

--Hi there

// Result:
// <h2>Hi there</h2>

list @x,@y,@z {{ 
        - @x
        - @y
        - @z
}}

list foo,bar,baz

// Result:
//      - foo
//      - bar
//      - baz

compare @a to @b {{ strcmp("@b", "@a") }}

compare apples to oranges

// Result:
// strcmp("oranges", "apples")

If none or too few parameters are given, the macro is not expanded and copied literally to the output.

pair @a  @b {{ [@a @b] }}

pair 10 20 
pair foobar 

// Result:
// [10 20] 
// pair foobar

Types

By default, a parameter matches all characters except newlines. To limit matching to specific strings typed parameters can be used. The syntax is "@name:type" where type is a builtin or user defined type name. Builtin types are

int     -  integer
string  -  php string (in single or double quotes, with escaping)
id      -  php identifier
alpha   -  only letters
alnum   -  only letters or numbers
ns      -  non-whitespace
nobr    -  anything except linebreak
br      -  linebreaks
any     -  anything

In the following example, the first string in parenthesis is expanded because it matches the definition (identifier required). The second string doesn't match and is left as is.

(@var:id)     {{ <? echo $@var; ?> }}

(count) (just some text)

// Result:
// <?php echo $count; ?> (just some text)

Nesting

Macro definitions can be nested:

foo {{
        A
        bar {{ xyz }}
        B
}}

This defines two macros "bar" (with the body "xyz") and "foo" (whose body would be "A B" because the parser replaces macro definitions with empty strings).

Inner macros are normally parsed first, but you can change this by giving an outer macro more priority. Macro priority is designated by the number of braces around the body - the more braces are used, the higher is the priority. With nesting and priority you can define dynamic macros, i.e. the macros whose definitions are generated on the fly by expanding other macros.

design in @color {{{
        h1_@color(@text)   {{ <h1 style='color:@color'>@text</h1> }}
        span_@color(@text) {{ <span style='color:@color'>@text</span> }}
}}}

// The following will generate a series of macros h1_red(@text), span_red(@text) etc.

design in red

Dynamic macros even allow you to change basic makrell syntax (braces) for your own taste:

<macro name="@key">@body:any</macro> {{{ 
        @key {{@body}} 
}}}

// from now on, macros can be defined like this

<macro name="name">Linda</macro>

Commands

Along with macros, there is another special construct called command. The syntax of the command is "{{ verb body }}". When makrell encounters a command, it executes the code bound to this verb (it's actually just a method with the name command_verb). The following commands are built-in:

{{ include file }}      - replaces the command with the content of the file
{{ quote text }}        - ignores makrell rules in the text
{{ type name regexp }}  - defines a new parameter type

Examples:

{{ include foobar }}

{{ quote 
        This text should be preserved as is.
        Please no macro processing here.
}}

{{ type url www\.[\w\/.?&#]+ }}

New commands can be easily added by extending Makrell class.

class MyMakrell extends Makrell {
        function command_up($body) { 
                return strtoupper($body);
        }
}

...

{{ up ibm }}

// Result: IBM

It's important to understand that commands are executed when the template is being parsed ("at compile time"), not when it's being evaluated ("run time").

If there is no verb or it is unknown, the whole command is ignored. You can use this to comment out chunks of text.

{{ this will be ignored 
        (as long as there is no method command_this defined)
}}

{{* this will be always ignored }}

Inheritance

Macros in included files are taken into account when target file is expanded. We can use this to achieve macro "virtualization" which is much like how inheritance in OOP languages work. Consider the following

// file: hello
sayhello {{ <h1>hello_text</h1> }}

Here, "hello_text" has no definition, that is, it's just a placeholder. Once we define it

// file: german

{{ include hello }}

hello_text {{ Willkommen }}

the result of "sayhello" changes, although we haven't change the base file directly.

{{ include german }}
sayhello
// Result: <h1>Willkommen</h1>

To continue the analogy with a progamming language, in this example "hello_text" is a virtual method, and "german" is a class that extends the base "hello".

Template virtualization provides an easy way of building complex structures using step-by-step "refinements".

// file: website

<title>the_title</title>
<body>the_content</body>

// file: company

{{ include website }}

the_title {{ Company: company_subtitle }}
the_content {{
        <h1>Company</h1>
        company_content
}}

// file: company-about

{{ include company }}

company_subtitle {{ About us }}
company_content {{
        <h2>About us</h2>
        Hi there!
}}

Each file "extends" its parent file and provides more and more specific content to it, but the basic structure remains the same for all files.


Generated on Tue May 20 13:52:05 2008 for makrell by  doxygen 1.5.5