You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@ant.apache.org by Steve Amerige <St...@sas.com> on 2011/12/09 00:46:14 UTC

Ant For Loop: Looping over Ranges with Step and Over PropertySets

Hi all,

In the recent past, I've asked about implementing a "for" loop in Ant.   I've wanted something that could iterate over number ranges 
or propertysets.  And, I wanted something that didn't depend on Ant-Contrib 1.0b3.  And, I wanted to be able to iterate forwards or 
backwards with a settable step value.

I was able to work out everything except for handling the element.  Enter: Scot Floess.  We got together via e-mail and had a great 
time, and in just a few days hammered out a solution that works!  Many thanks for helping finish the for loop code, Scot!  The 
solution is not perfect in that it uses global properties (the index, key, and value attributes used as ${index}, ${index.key}, and 
${index.value}) instead of params (used as @{param}) in the element body.  But this imperfection is mitigated by the fact that the 
code below works and that by choosing names carefully results in code that is acceptable for production use.  The code below follows 
naming conventions... see if you can guess what they are.

Very importantly, the code below shows how to execute an element.  This opens up many possibilities for coding in Ant!

The most interesting parts of the code below that relate to elements:

  * <__for-internal ...><sequential><body/></sequential></__for-internal>
      o Note that this organization allows us to depend on the sequential task existing and that the body element is the very first
        thing in it.
  * <element name="sequential" classname="org.apache.tools.ant.taskdefs.Sequential"/>
      o We bind the "sequential" element to the Sequential taskdef.
  * Task body = (Task) elements.get("sequential").get(0)
      o And, here we get the 0th object: the body element.
  * body.execute()
      o Now, we can execute the body element.


I will put this code up on a website so that if I fix any bugs, you can grab the latest version.

Please feel free to improve on the code below and please do share with me directly or in this list with your comments and/or 
improvements.  Again, thanks to Scot for his energetic, fun, and useful contributions!

Enjoy!
Steve Amerige
SAS Institute, Deployment Software Development

*Ant For Loop Implementation, Test Code, and Results*

<macrodef name="__for">
<attribute name="index"                   description="index name (stores int values from start..end by step)"/>
<attribute name="start" default="1"       description="starting value"/>
<attribute name="end"   default=""        description="ending value (not applicable for refid)" />
<attribute name="step"  default="1"       description="increment"/>
<attribute name="refid" default=""        description="reference to a set, typically a propertyset"/>
<element   name="body"  implicit="true"   description="for body" />
<sequential>
<if>
<equals arg1="@{refid}" arg2=""/>
<then>
<__for-internal index="@{index}" start="@{start}" end="@{end}" step="@{step}">
<sequential>
<body/>
</sequential>
</__for-internal>
</then>
<else>
<var name="_!string" value=", ${toString:@{refid}}"/>

<propertyset id="_!set">
<propertyset refid="@{refid}"/>
<mapper type="glob" from="*" to="~~*" />
</propertyset>

<propertyregex property="_!list" input=", ${toString:_!set}" regexp="(^~~|, ~~)" replace="|" global="true" override="true" 
defaultValue="" />
<var name="@{index}" value="@{start}"/>
<var name="_!key" value="@{index}.key"/>
<var name="_!value" value="@{index}.value"/>

<for list="${_!list}" delimiter="|" param="foritem">
<sequential>
<propertyregex property="${_!key}" input="@{foritem}" regexp="^([^=]*)=.*" select="\1" override="true" defaultValue="" />
<propertyregex property="${_!value}" input="@{foritem}" regexp="^[^=]*=(.*)" select="\1" override="true" defaultValue="" />
<body/>
<math result="@{index}" operand1="${@{index}}" operand2="@{step}" operation="+" datatype="int"/>
</sequential>
</for>
</else>
</if>
</sequential>
</macrodef>

<scriptdef name="__for-internal" language="groovy">
<attribute name="index"/>
<attribute name="start"/>
<attribute name="end"/>
<attribute name="step"/>

<element name="sequential" classname="org.apache.tools.ant.taskdefs.Sequential"/>
<![CDATA[
         import org.apache.tools.ant.Task

         index = attributes.get("index")
         start = attributes.get("start").toInteger()
         end   = attributes.get("end").toInteger()
         step  = attributes.get("step").toInteger()

         Task body = (Task) elements.get("sequential").get(0)

         for (int i = start; (step > 0) ? (i <= end) : (i >= end); i += step) {
             project.setProperty(index, i.toString())
             body.execute()
         }
     ]]>
</scriptdef>

*Test Code*

<__for index="i" start="1" end="10">
<echo message="i = ${i}"/>
</__for>

<property name="a.b.c.d" value="1"/>
<property name="a.b.c.e" value="2"/>
<property name="a.c.x.x" value="3,4,5"/>
<property name="a.c.x.y" value="3"/>
<property name="a.c.x.z" value="3"/>
<property name="p.q.r" value="4"/>
<property name="p.q.s" value="4"/>
<property name="p.q.t" value="4 />

<propertyset id="r">
<propertyref regex="^a\.(?!b\.).*"/> <!-- begins with "a." not followed by "b." -->
</propertyset>

<__for index="_i" refid="r">
<echo message="_i = ${_i} key='${_i.key}' value='${_i.value}'"/>
</__for>

*Test Results*

test-forloop:
      [echo] i = 1
      [echo] i = 2
      [echo] i = 3
      [echo] i = 4
      [echo] i = 5
      [echo] i = 6
      [echo] i = 7
      [echo] i = 8
      [echo] i = 9
      [echo] i = 10
      [echo] _i = 1 key='a.c.x.x' value='3,4,5'
      [echo] _i = 2 key='a.c.x.y' value='3'
      [echo] _i = 3 key='a.c.x.z' value='3'
BUILD SUCCESSFUL
Total time: 1 second