Skip to content

Latest commit

 

History

History
401 lines (298 loc) · 10.7 KB

for.adoc

File metadata and controls

401 lines (298 loc) · 10.7 KB

for

since version 1.0.0

The for macro can be used to repeat text with different parameters.

It can be used similar to the for loop in programming languages, but it can also have multiple variables. The actual values can be listed comma separated in the macro or they can also come from some other list holding macro.

Syntax

The two forms of the macro are

Jamal source
{@for variable in (a,b,c,...,d)= content to be repeated
containing variable}

and

Jamal source
{@for (v1,v2,...,vN) in (a|w|...|1,b|q|...|2,c|r|...|5,d|t|...|9)= content to be repeated
containing v1 v2 and v3}

When the values are held by a macro as a list the keyword from is used instead of the keyword in.

Jamal source
{@for variable from MACRO= content to be repeated}

where the macro MACRO should be defined before the for macro.

History

  • since 1.5.0 multi-argument for

  • since 1.6.3 backtick string separator value list

  • since 1.7.3 options between [ and ]

  • since 1.7.8 option evalist

  • since 2.7.0 option join

Description

The macro for uses the content of the macro as a template. The result of the macro is a string, which is the concatenation of the content for each loop. The content can contain the loop variables, and each time the variables are replaced with the values. For example

Jamal source
{@for x in (a,b,c)=it is $x
}

will result in

output
it is $a
it is $b
it is $c

When using multiple variables, the values are separated by the | character as in the example

Jamal source
{@for (x,y,z) in (a|b|c,d|e|f,g|h|i)=it is $x $y $z
}

which will result in

output
it is $a $b $c
it is $d $e $f
it is $g $h $i
Note
The values a|b|c, d|e|f, and g|h|i are string tuples and multiple loop variables do not mean nested loops.

If the values themselves contain commas, or | characters then other strings can also be used to separate the values. The default separator is the comma character, but it can be changed using the option sep or separator. There are other parameter options, (parops) that can be used to control the behavior of the loop.

The options are

  • $forsep, separator, sep can define the separator if it is different from the default, which is , comma. The value is used as a regular expression giving very versatile possibilities.

  • $forsubsep, subseparator, subsep can define the subseparator if it is different from the default, which is | pipe. It is used when there are multiple variables in the loop. Similarly to the separator, the value is used as a regular expression.

  • trimForValues, trim is a boolean paror. If it is present and true, then the values are trimmed, the spaces are removed from the beginning and the end.

  • skipForEmpty, skipEmpty is a boolean parameter. If it is present and true, then the empty values are skipped.

  • lenient is a boolean parameter. If it is present and true, then the number of the values in the value list is not checked against the number of the variables.

  • evaluateValueList, evalist is a boolean parameter. If it is present and true, then the value list is evaluated as a macro before spling it up to values.

  • $forjoin, join is used to join the values when the values are joined together. The default is the empty string.

Examples

The macro:

Jamal source
{@for [sep=:]$a in (a:b:c)=is $a
}

will result

output
is a
is b
is c

Another example:

Jamal source
{@for [trim sep=":"] $a in ( a : b :c )=is >>$a<<
}
{@for [sep=":"] $a in ( a : b :c )=is >>$a<<
}

will result in the output:

output
is >>a<<
is >>b<<
is >>c<<

is >> a <<
is >> b <<
is >>c <<

The number of the actual values separated by | character should be the same as the number of the variables in the for loop. If this is not the case, then the macro evaluation will throw a bad syntax exception. This can be suppressed with the option lenient. If the option lenient is used, then extra values are ignored and missing values are presented as empty strings. Note that this same option controls how user defined macro arguments are paired to the parameters.

Starting with version 1.5.3 you can fine-tune how a for loop treats the empty elements. By default, the empty elements in a for loop value list represent empty strings. The loop body will be rendered with these values replacing the loop variable with an empty string. In a situation like that the use of the option lenient is also necessary if the loop has multiple variables. In that case, and empty value cannot be split into multiple strings. It is one empty string, and if there are multiple variables, then an error will occur unless the option lenient is used. For example

Jamal source
{#for (k,z) in ()=wukz}

it will not work because the empty string cannot be split into two strings. It results in one empty string when it is split. On the other hand, the following code will work

Jamal source
{@for [lenient] (k,z) in ()=wukz}

and it will result

output
wu

as both k and z are empty strings.

This default behavior can be altered using the option skipForEmpty. If this option is used the for loop will skip the empty values. The previous example with this option:

Jamal source
{@for [skipEmpty] (k,z) in ()=wukz}\

will evaluate to an empty string. Also note that in this case there is no need to use the option lenient. That is because the empty value is skipped and there is no issue splitting it up into a lesser number of values than the number of the loop variables.

The example above contains one loop value, and that loop value is an empty string. There can be multiple empty values in a for loop and empty and non-empty values can be mixed. The option skipForEmpty and the alias skipEmpty works in any of those cases. For example:

Jamal source
{@for [skipEmpty] k in (,)=wuk}

will also result an empty string and

Jamal source
{#for k in (,k)=wuk{@options skipForEmpty}}

will result

output
wuk

Sometimes the values for the for loop come from some macro. In that case the for macro should start with the # character, otherwise the macro will not be evaluated to the list of values. For example:

Jamal source
{@define list=x,y,z}{@for z in ({list})={@define z=zz}}{?x}{?y}{?z}

will result

output
{@define {list}={list}{list}}

That is because the content of the macro for is not evaluated before the for loop is executed as we used the @ character. It is also to note that the result of the for loop is not evaluated. We will have to attend to that later. First we have to solve the issue that the macro list is not evaluated. To do that, we need to use the # character in front of the for loop.

Jamal source
{@define list=x,y,z}{#for z in ({list})={@define z=zz}}{?x}{?y}{?z}

will result an empty string:

output

The reason is that the content of the for macro is evaluated before executing the macro itself. That way the macro reference {list} will become x,y,z, but the same time the part, which is after the = is also evaluated. The evaluation will define the macro z to be zz, but this macro is within the scope of the for macro. As soon as the for macro execution is finished the definition of z is lost. What we want is to protect the body of the for macro from evaluation before for the macro is executed, and we want it to execute after.

Jamal source
{@define list=x,y,z}{!#for z in ({list})={@ident {@define z=zz}}}{?x}{?y}{?z}

will result

output
xxyyzz

The macro {@ident …​} is evaluated, and its result is the content of the macro, and it is not evaluated further before the evaluation of the macro for. The macro for gets evaluated and, then the output is evaluated because the macro is preceeded with the ! character, which is a shorthand for the core built-in macro eval. This evaluation defines x, y and z.

Because the case that we want to evaluate the list part of the for loop but not the body part is so common there is an option that helps with this. The option evaluateValueList (alias evalist) instructs the macro for to evaluate the value list before iterating through it.

Jamal source
{@define list=x,y,z}{!@for [evaluateValueList] z in ({list})={@define z=zz}}{?x}{?y}{?z}

will result

output
xxyyzz

In version 2.5.0 and later you can use a bare macro name between the parentheses, as

Jamal source
{@define list=x,y,z}{!@for [evaluateValueList] z in (list)={@define z=zz}}{?x}{?y}{?z}

will result the same output:

output
xxyyzz

We still need the ! character in front of the for but we could get rid of the ident macro and the extra level of nesting.

Note

The use of evalist and using # along with ident is not exactly the same. Using # will evaluate the part not protected by ident before the for macro evaluates its input. The option evalist tells the macro to evaluate the string it has already found that time between the opening ( and closing ).

The consequence is that using evalist you can have a list that contains the ) character. The end of the list was already determined when the evaluation starts. Using # in front of the macro identifier will cause problem if the list contains the ) character.

In situations like that you can use the special list separator that we discuss in the next paragraph.

Sometimes you may need to do a for loop over values that contain the ) character. With the conventional form of the for macro it was not possible, because the first ) character terminates the list of the values. Jamal 1.6.3 introduced a new, backward compatible format for the for macro.

Instead of the ( and ) characters it is possible to use an arbitrary string to denote the end of the values. When the first character after the keyword in (after optional spaces) is the backtick character, then the string till the next backtick character will be used to denote the end of the values. The starting and ending backtick should also be part of the string closing the values.

For example, the following

Jamal source
{@for x in `END`a),b),c),d)`END`=x }

will result

output
a) b) c) d)

Note that this alternative format can only be used for the values list and not for the variables. The variables of the for loop should always be listed between ( and ) characters.