Can I send the output of the Ant 'replace' task to a new file?

The Ant replace task performs an in-place replacement without creating a new file.

Below the fragment replaces the tokens in any of the * .xml files with the corresponding values ​​from the file 'my.properties'.

 <replace dir="${projects.prj.dir}/config" replacefilterfile="${projects.prj.dir}/my.properties" includes="*.xml" summary="true" /> 

I want the files with their tokens replaced to be created with the name after the template (for example) '* .xml.filtered' and save the source files.

Is this possible in Ant with some clever combination of tasks?

+6
replace ant
source share
2 answers

There are several ways to get closer to what you want without copying to a temporary directory and copying.

Filtersets

If the source files can be changed so that the parts to be replaced can be separated using start and end tokens, as in @ date@ ( @ is the default token, but it can be changed), then you can use copy task with globmapper and filterset :

 <copy todir="config"> <fileset dir="config" includes="*.xml" /> <globmapper from="*.xml" to="*.xml.filtered" /> <filterset filtersfile="replace.properties" /> </copy> 

If replace.properties contains FOO = bar , then any @ FOO@ event in the source xml file must be replaced with bar in the target.

Note that the source and destination directories are the same; globmapper means destination files and names with the suffix .filtered . It is possible (and more common) to copy files to another destination directory)

Filterchains

If the source file cannot be modified to add begin and end tokens, an alternative would be to use a filterchain with one or more replacestring filters instead of filterset :

 <copy todir="config"> <fileset dir="config" includes="*.xml" /> <globmapper from="*.xml" to="*.xml.filtered" /> <filterchain> <tokenfilter> <replacestring from="foo" to="bar" /> <!-- extra replacestring elements here as required --> </tokenfilter> </filterchain> </copy> 

This will replace any occurrence of foo with bar anywhere in the file, which is more like the behavior of the replace task. Unfortunately, this method means that you need to include all your replacements in the assembly file itself, you cannot have them in a separate properties file.

In both cases, the copy task will only copy the source files, which are newer than the target files, so unnecessary work will not be performed.

Copy, then replace

The third possibility (which just happened to me when writing the other two) was to first make a copy in the renamed files, and then run the replace task with the renamed files:

 <copy todir="config"> <fileset dir="config" includes="*.xml" /> <globmapper from="*.xml" to="*.xml.filtered" /> </copy> <replace dir="config" replacefilterfile="replace.properties" summary="true" includes="*.xml.filtered" /> 

This may be the closest solution to the initial requirement. The downside is that the replace task will be executed every time you rename files. This might be a problem for some replacement patterns (admittedly, they would be odd, like foo=foofoo , but they would be fine with the first two methods), and you will do unnecessary work if the dependencies do not change.

+7
source share

The replace task does not take into account dependencies; instead, it performs the replacement by writing a temporary file for each input file. If the temporary file matches the input file, it is discarded. A temporary file that is different from the input file is renamed to replace this input. This means that all files are processed, even if none of them are needed - therefore, it can be inefficient.

The original solution to this issue was to execute a copy-replace copy. The second copy is not needed, since in the first case you can use a cartographer. In a copy, dependencies can be used to limit processing to only modified files - using the depend selector in an explicit set:

 <copy todir="${projects.prj.dir}"> <fileset dir="${projects.prj.dir}"> <include name="*.xml" /> <depend targetdir="${projects.prj.dir}"> <mapper type="glob" from="*.xml" to="*.xml.filtered" /> </depend> </fileset> <mapper type="glob" from="*.xml" to="*.xml.filtered" /> </copy> 

This will limit the set of copies to only those files that have been modified. Alternative syntax for mappers:

 <globmapper from="*.xml" to="*.xml.filtered" /> 

The simplest replacement would be the following:

 <replace dir="${projects.prj.dir}" replacefilterfile="my.properties" includes="*.xml.filtered" /> 

This will still process all the files, even if none of them needs to be replaced. The replace task has an implicit file set and can work with an explicit file set, but unlike similar tasks, an implicit file set is not optional, therefore, to use selectors in an explicit file set, you must do an implicit "do nothing" - hence .dummy here:

 <replace dir="${projects.prj.dir}" replacefilterfile="my.properties"> includes=".dummy" /> <fileset dir="${projects.prj.dir}" includes="*.xml.filtered"> <not> <different targetdir="${projects.prj.dir}"> <globmapper from="*.xml.filtered" to="*.xml" /> </different> </not> </fileset> </replace> 

This will prevent unnecessary processing of the replace task by processing files that were previously replaced. However, it does not prevent the processing of files that have not been modified and do not need to be substituted.

Other than that, I'm not sure there is a way to “encode golf” with this problem in order to reduce the number of steps to one. There is no multi-line filter that can be used in the copy task to achieve the same effect as replace , which is a shame because it seems like it would be the right solution.

Another approach would be to generate xml for a series of filters replace string , and then execute Ant. But it will be more complicated than the existing solution, and prone to problems with replacing strings, which, if inserted into the xml fragment, will lead to something that cannot be parsed.

Another approach would be to create a custom task or script task to do this work. If there are many files, and the solution to replace the copy is considered too slow, then this may be the way to go. But again, this approach is less simple than the existing solution.

If the requirement is to minimize the work done during processing, instead of coming up with the shortest Ant solution, this approach can do.

  • Make a set of files containing a list of inputs that have changed.
  • From this set of files, create a comma-separated list of filters.
  • Make a copy on the fileset.
  • Replace in a comma separated list.

The wrinkle here is that the implicit set of files in the replacement task will return to processing everything if the files have not changed. To overcome this, insert a dummy file name.

 <fileset id="changed" dir="${projects.prj.dir}" includes="*.xml"> <depend targetdir="${projects.prj.dir}"> <globmapper from="*.xml" to="*.xml.filtered" /> </depend> </fileset> <pathconvert property="replace.includes" refid="changed"> <map from=".xml" to=".xml.filtered" /> </pathconvert> <copy todir="${projects.prj.dir}" preservelastmodified="true"> <fileset refid="changed" /> <globmapper from="*.xml" to="*.xml.filtered" /> </copy> <replace dir="${projects.prj.dir}" replacefilterfile="my.properties" includes=".dummy,${replace.includes}" summary="true" /> 
+1
source share

All Articles