<terp.foreach> - an iterator task for ANT

Using the <terp.foreach> task requires that the ant-terp.jar file be available to ant and that the task be registered via a <taskdef> statement:

<taskdef name="terp.foreach" classname="com.codemesh.terp.ant.Foreach"/>

Please note that you could use any value for the name attribute that you wish; you are not limited to terp.foreach. All our examples and documentation use the terp.foreach name and use a name prefix of terp. to signal that they originate with and rely on the terp framework.

The <terp.foreach> task is an ANT TaskContainer, meaning that it is designed to contain arbitrary ANT tasks. It has the following attributes:

terp.foreach attributes
Name Type Description
if String Optional attribute specifying a boolean terp expression. The expression represents a condition that must be satisfied for the task to be executed.
expression String Required attribute specifying a terp expression, typically declaring or dereferencing a collection or array instance whose members we wish to iterate over.
name String Required attribute specifying the name under which the current element can be referenced.
unless String Optional attribute specifying a boolean terp expression. The expression represents a condition that must not be satisfied for the task to be executed.

In addition to being a task container, and thereby having the ability to contain all types of Tasks, the terp.foreach task supports the following nested elements:

terp.foreach nested elements
Name Attributes Description
<break/> if, unless Breaks the loop if the if condition is met or the unless condition is not met. At least one attribute should be defined, otherwise the break is unconditional.
<continue/> if, unless Skips the nested elements remaining in this iteration of the loop if the if condition is met or the unless condition is not met. At least one attribute should be defined, otherwise the continue is unconditional.

When the <terp.foreach> task is executed, it will iterate over the elements in the collection specified by the expression attribute. The current element is bound to the name specified by the name attribute. Nested tasks can reference the element via that name. The following example illustrates this usage.

<terp.foreach name="var" expression="{'this', 'is', 'a', 'test'}">
    <echo>${var}</echo>
</terp.foreach>

<terp.foreach> tasks can be nested, allowing you to perform tasks on all combinations of multiple dimensions. Consider the following example from a build script that has to build the same binary target for different processor architectures in both debug and release modes using three different compilers specified via their version numbers:

<terp.foreach name="pa" expression="{x86, amd64}">
    <terp.foreach name="debug" expression="{false, true}">
        <terp.foreach name="vers" expression="{13,14,15}">
            <!-- nested statements follow -->
            ...
        </terp.foreach>
    </terp.foreach>
</terp.foreach>

Assuming that all combinations make sense, this will result in 12 (2 x 2 x 3) executions of the nested statements. Each combination has access to the current value of each variable via the pa, debug, and vers properties.

When using the <terp.foreach> task, you might want to familiarize yourself with the syntax for collections or expressions in general.

Break

You can use the <break> element to achieve some additional flow control. For example, to completely abort the iteration when the number 5 is encountered, you could write:

<terp.foreach name="var" expression="{0, 4, 9, 6, 8, 5, 7, 3, 2}">
    <break if="var==5" />
    <echo>${var}</echo>
</terp.foreach>

Break means: stop executing anything related to the loop. We're done here.

Continue

You can use the <continue> element to achieve some additional flow control. For example, to skip the foreach processing for all even numbers in a collection, you could write:

<terp.foreach name="var" expression="{0, 4, 9, 6, 8, 5, 7, 3, 2}">
    <continue if="var%2==0" />
    <echo>${var}</echo>
</terp.foreach>

Continue means: skip the remaining instructions, continue directly with the next element.

<continue> is very useful to check for null values in a collection and not perform any nested tasks in these cases. You might for example want to compile with every compiler that you know and populate a collection with them: {^acc(), ^gcc(), ^msvc(), ^suncc(), ^xlc()}. Chances are that you do not have every compiler installed on one machine. Evaluating this collection will yield a number of null members where no corresponding compiler could be found. Use <continue if="comp==null"/> as a precheck to skip all subsequent tasks in the foreach statement.

Examples

Example 1: Iterate over all C++ header files (nested) in a directory

<property name="include.dir" value="${basedir}/cpp/include"/>
<terp.foreach name="incl" expression="^path(include.dir+'/**/*.h')[file]">
<echo>${incl.name}: ${incl.length()}</echo> </terp.foreach>

In this example, we have the common situation of an ANT property holding a directory name. We now want to echo the names (without path) and file sizes of all include files in that directory or any nested subdirectories. We achieve this using the following strategies:

  • use a ^path with a file pattern to describe the files we're interested in.
    The pattern is the concatenation of the directory name and the wildcard string on which we're matching. Please note that this is a terp expression. In a terp expression you have to use the plus sign to concatenate strings.
  • add a file selector, just to make sure we don't inadvertently include a directory whose name ends in .h
  • Every object referenced by the loop variable incl will be of type java.io.File because we are iterating over the elements of a ^path. This means that you can use all methods and properties defined by the java.io.File type in your expressions or embedded expressions. In our example, we use the name property (java.io.File.getName()) and the the java.io.File.length() method.

Example 2: Use complex file selector to create file list for ANT task

<property name="dir.list" expression="^path('**/*')[dir][this.name=='include'][join(',')]" />

We show this example to illustrate that you don't explicitly need to iterate over the elements of a ^path to do something useful. Here we want to create a comma-separated list of directories named include. We achieve this by selecting all nested files and then chaining two filters and a formatter together. The first filter selects only directories. The second filter selects only items named 'include'. The formatter concatenates them using a comma as the spearator.

There are of course other ways to achieve the same goal (for example using a file pattern of '**/include' ) but this example is meant to demonstrate the use of a complex expression.


Copyright 2006-2014 by Codemesh, Inc., ALL RIGHTS RESERVED

:
<terp.foreach>
codemesh.com home expressions templates ant about us contact us download   

Commandline