You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by David Dabbs <dd...@zefer.com> on 2000/12/14 12:34:26 UTC

Proposal task

Hello,

I'm planning to use Ant and related tools on several upcoming projects.
Having need of a more general-purpose value substitution option (beyond the
PropertyFile task) I've implemented a task called <macro>. Here is an
annotated markup sample:

<!--
    template and outfile are mandatory
    template is asserted to exist
    substitution syntax is identical Ant's, ${name}
-->
<macro template="d:\dev\jakarta-ant\dev.properties.in"
outfile="d:\dev\jakarta-ant\dev.properties.out">

    <source order="1" url="props://" />              <!-- props:// means use
the project.getProiperties() Hashtable -->

    <source order="1" url="props://user" />        <!-- props:// means use
the project.getUserProiperties() Hashtable -->

    <source url="file://d:\dev\jakarta-ant\dev.properties.in" />     <!--
file:// means use the file specified after the protocol -->

    <!--
         no other protocols are supported, but http:// and ftp:// could
easily be added, if desired
         order is not implemented, as I have yet to test to see if tag
loading order is deterministic
    -->
</macro>

Beyond writing a new task, modifications to the replaceProperties() method
in ProjectHelper() were required. If you want to review the code I've
attached is the most important snippet that (re)implements Ant's variable
substitution core. The prior comment said "// XXX need to replace this code
with something better." I hope this fits that description.

For what it's worth, before <macro> I extended the <PropertyFile> task to
support bulk reading property entries from a file. Here is the syntax:

  <propertyfile file="d:\dev\jakarta-ant\dev.properties.in" comment="Test
properties">
     <entry file="d:\dev\jakarta-ant\prod.properties" />
  </propertyfile>

Let me know if these are of interest to the project. Thanks.

David Dabbs
ZEFER/Chicago
http://www.zefer.com/



    /*
        given an arbitrary buffer, replaces properties values line-by-line
        into a result String buffer

        Note: should probably use a Writer (buffer or other io channel)
    */
    public static String replacePropertiesBuffer( Project project, String
buffer, Hashtable keys )
    throws BuildException
    {
        int lineno=1,eolpos, preveol=0;
        int eollen = EOL.length();
        int buflen = 0;
        try {
            buflen = buffer.length();
        } catch (NullPointerException npe) {
            throw new BuildException("Syntax error in prop: null buffer
passed");
        }

        StringBuffer sb=new StringBuffer(buflen);   // at least as large as
incoming buf

        try {
            while ( (eolpos=buffer.indexOf( EOL, preveol )) >= 0 ) {
                String line = buffer.substring( preveol, eolpos );
                preveol=eolpos+eollen;
                sb.append(replaceProperties( project, line,
keys ) ).append(EOL);
                lineno++;
            }
            // process remainder
            String line = buffer.substring(preveol);
            sb.append( replaceProperties( project, line, keys ) );
        } catch (BuildException be) {
            throw new BuildException( be.getMessage() + " at line " + lineno
+ " ");
        }
        return sb.toString();
    }

    /*
        given a string buffer (line), replaces properties values
line-by-line
        into a result String buffer

        Is forgiving about embedded name whitespace (spaces only) e.g. ${
foo }
        Detects boundary conditions such as:
          * empty variable ${}, ${spaces}
          * partial variable ${
          * $ alone
          * all at end of line/buffer as well

    */
    public static String replaceProperties(Project project, String value,
Hashtable keys )
    throws BuildException
    {
        // XXX use Map instead of proj, it's too heavy

        int valueLen=0;
        try {
            valueLen = value.length();
        } catch (NullPointerException npe) {
            throw new BuildException("Syntax error in prop: null template
buffer passed");
        }

        StringBuffer sb=new StringBuffer(valueLen);
        int prev=0;
        int pos;

        while ( (pos=value.indexOf( "$", prev )) >= 0 ) {

            sb.append( value.substring( prev, pos ) );

            char nextCh;
            try {
                nextCh = value.charAt( pos + 1 );
            } catch (StringIndexOutOfBoundsException oob ) {
                // $ at end-of-buffer
                sb.append('$');
                prev = pos + 1;
                continue;
            }
            if (nextCh != '{' ) {
                sb.append( "$" );
                sb.append( nextCh );
                prev=pos+2; // XXX
                continue;
            }

            /*
              look for '}'
            */
            int term = pos + 2;
            char ch;

            try {
                while ((ch = value.charAt(term++)) != '}') {
                }
            } catch (StringIndexOutOfBoundsException oob) {
                throw new BuildException("Syntax error in prop: cannot find
'}' before end-of-line");
            }

            // there's a closing - extract the name
            String n=value.substring( pos+2, term-1 );
            n = n.trim();

            if (n==null || n.length()==0) {
                throw new BuildException("Syntax error in prop: found empty
macro ${ }");
            }
            if (!keys.containsKey(n)) {
                project.log("Property ${" + n + "} has not been set",
Project.MSG_WARN);
                sb.append( "${"+n+"}" );
            } else {
                sb.append((String) keys.get(n));
            }
            prev=term;
        }
        if ( prev < valueLen ) sb.append( value.substring( prev ) );

        return sb.toString();
    }