Magro is a language with very few syntax elements and most of the functionality will be determined by user defined macros and libraries.
An example script and its output is show below:
import 'html5'
html:
body:
div(id='d1'):
'Some text'
Will generate the following output:
<html><body><div id="d1">Some text</div><body></html>
Macros are the basic unit of the Magro language, they can be defined and called with a set of parameters and an optional body. The result of the call will be appended to the output text. If a call to a macro cannot be resolved, it will simply be ignored.
The name of any macro can be any succession of alphabetic or numeric characters, underscores (‘_’), dots (‘.’) or hyphens (‘-‘)
A macro can be called without parameters:
this_is_a_macro_call
With parameters:
macro( 'parameter' )
With a body parameter, indented from the parent call:
macro:
'body'
anothermacro( 'parameter' ):
body:
anothercall
'more body'
In the last example, the macro call anothercall is the body of the call to the body macro. The body of the anothermacro call is composed of the call to body and the 'more body' string.
In magro, indentation is important as it is used to determine what expressions belong to the body of each call. Note that when a body is used in a macro call, a colon ':' must be written to make it more explicit that this macro call has a body.
The list of parameters is optional for the call and has the following syntax:
( parameter_name = parametervalue, param2 = value2, paramN = valueN )
The parameter name is optional and the parameter value can any sequence of expressions separated by spaces, either of strings, of macro calls, cycles or any combination of them; for example:
div( id='head' index, class='container' ):
'body of the div'
In the example above, the parameter will be assigned a value formed by the concatenation of ``'head' and the result of the call to index.
A function call will try to determine its value in the following order:
- Look up the value in the context the user passes to the parser at invocation. a) Look up a renderer for the type of the found object. b) Use the string representation of the found object.
- Look up the value as a parameter passed to the called macro.
- Look up a macro definition of the same name.
- Use an empty string as result.
A macro can be defined using the following syntax:
def macroname( parameters ):
code_of_the_macro
The code_of_the_macro is a sequence of calls of the form described in the previous section, that will be invoqued when the defined macro is called.
The list of parameters is optional and can be used to define default values to any parameter that the used might ommit from the macro call. At the moment of invocation, the caller might decide not to use all of the declared parameter names or to pass any parameter that is not declared in the macro definition. For example:
some_macro( some_random_param='some_value' )
def some_macro( id, class='default' ):
'Macro value'
In this case, id``will have no value, ``class will have the string 'default' as value, and some_random_param will be passed to the call even if it is not used by the macro definition.
A call to a parameter is actually the same as a macro call, and the rules for determining its value described in the previous section apply to it.
There are a number of symbols that can be used on macro definitions without being explicitly declared or given values by its caller. All these values have the following form:
$name_of_variable
If the implicit variable does not exist, it will be ignored.
All macro declarations can use the following implicit values:
- $
- Represents the body used in the macro call. If no body was used in the call, it is an empty string.
- $all
- Returns an array of all the parameters used during the call in the order they were written.
- $undeclared
- Returns an array with all the parameters used at the macro call, but only if they are not declared in the macro definition.
- $1, $2, ...
- Besides the actual names of the parameters (in case their names were used,) each parameter can be accessed by its position in the macro call, where $1 is the first parameter, $2 the second and so.
More implicit values will be explained in the context they are available.
Special macro declarations can be defined to be invoqued when an object of certain type is encountered. This kind of macros can be defined using the following syntax:
@name_of_the_type( parameters ):
code_of_the_macro
The engine will try to match the object type hierarchy with the name used in the macro definition. If no definition is found, the string representation of the object will be used.
The code_of_the_macro section and parameters definition follows the same rules described in section 4.1.2. Except for the fact of being invoqued when a type match is found, this kind of macros behave just like normal macros.
There is an additional implicit value that will be available in this kind of macros:
- $object
- Contains the reference to the object that triggered the macro invocation.
Strings can be used inside macro calls or as part of parameter value definition. They can be written in three diferent forms:
Normal strings, written between single (‘) or double quotes (“) with no new line inside them:
'This is a normal string' "This is a normal string too"Line strings, beginning with a single or double quote and ending with a new line:
'This is a line string. 'Line strings will have a new line 'appended at the end. "This is another line string.Long strings, written between a pair of three single quotes (‘’‘) or double quotes (“”“) can contain new lines inside them:
'''This is a long string of many lines''' """This is another example of a long string"""
All kinds of strings can contain escape characters using the same set as Python strings. Please refer to the Python documentation for more details on this.
As many template engines, Magro has a special syntax to support loops over a collection of items, a set of parameters or an iterator. The syntax for a loop is as follows:
[lists_of_values]:
expressions_evaluated_for_each_value
Each part has its own rules described below.
Syntax example:
[ optional_key = value, key2 = value2, another_value ]
This is a list of values separated by commas (,) very similar to the parameters passed to a macro call, but with the following exceptions:
1. Values that evaluate to an array, will be expanded and iterated as if they were single values. For example:
[ $all ]: $valueWill iterate over all the parameters passed to the macro call, represented by the implicit variable $all that evaluates to an array containing all the parameters that were used during the macro call.
2. Values that evaluate to an empty string and don’t have a key will be ignored from the loop. There is a way to define an alternative body block by using a single colon (:) at the same level of indentation right after the main block of the loop. This alternate block will be called for those values of the loop that evaluate to an empty string. This feature can be used to implement a simple conditional expression:
[ condition ]: 'This will be printed if condition does not evaluate to empty' : 'This will be printed if condition evaluates to empty'
Arrays expansion can be prevented by enclosing in parentesis the variables that evaluate to an array:
[ firstarray=($all), secondarray=('1','2','3') ]:
[ $value ]:
'A nested loop'
As shown in the example, a set of comma separated expressions can be grouped in an array by enclosing them between parentesis. This applies to parameters passed to a macro call too.
The body of loop expressions is very similar to the body of a macro call, except that it cannot contain macro calls that contain a body. This limitation might change in a future version of the language.
There are some implicit variable names available only inside loop expressions:
- $key
- Returns the value of the key used for the value currently evaluated. If no key was used, this variable will be ignored.
- $value
- Returns the current value of the iteration.
- $index
- Returns the position of the current value of the iteration, starting from 0.
Macro definitions declared in a separate file can be used by importing them using the import clause:
import 'file_name'
The file to import will be searched in order using all the paths declared in the magro.env.path variable.
Only the macro definitions will be imported, and all output generated by calls made inside the imported file will be ignored.
Python expressions can be used inside magro templates by enclosing them inside back-quotes (`). For example:
`range( 1, 10 )`
The result of the expression can be of any type, and can be used as a parameter value, as a single expression, or as part of the body of a macro call.
If a certain module needs to be imported for its use in python expressions, it can be done by enclosing a python import sentence inside back-quotes. For example:
`from random import random`
`random()`
Values passed to the template and parameters used in a macro call are available as variables inside the Python expression. For example:
`from random import random`
randomtext( 'abcde', `10` )
def randomtext( seed, size ):
`''.join( [ seed[int(random()*len(seed))] for i in range(size)] )`
If the variable name is an implicit value or its name has a format incompatible with python syntax for variable names, the function _( varname ) can be used to retrieve such value:
[ '1','2','3' ]: ' value*10=' `int( _('$value') )*10`
It is recommended that Python expressions used inside templates have no collateral effects as that templates should be used only for output presentation.
A template can be passed a dict object to define the values of the symbols used by it. Evaluation of the names used in templates is done as detailed in section 4.1.1.
As sometimes the value of a macro call will evaluate to a Python object, it would be convenient to be able to access the object’s attributes from inside the template. This could be done using a Python expression, but a better approach is supported by using the magro.context.Context class.
The Context is an extension to the dict type that will follow the next sequence when searching for a key contains dots (‘.’) inside it :
- Search for the whole key in the dict.
- Ignore the last part of the key (after the last ‘.’).
- Repeat steps 1 and 2 for each new key until an object is found or the key has no more parts.
- If an object is found, try to evaluate all the attributes in the sequence.
For example:
>>> from magro import Template
>>> from magro.context import Context
>>> template = Template( 'a.imag "i"' )
>>> template.render( Context({'a': 10+5j}) )
u'5.0i'
When an referenced attribute of an object is callable, it will be called without arguments and the returned value will be used as the attribute’s value.
If no object is found a KeyError is raised. When an object is found but the sequence of attribute references is not possible, an AttributeError will be raised.
Commentaries to the code can be written beginning the comment with a '#' character. All text following this character up to the next new line character will be ignored.