Can I send Ant 'replace' task output to a new file? - ant

The Ant replace task does an in-place replacement without creating a new file.
The below snippet replaces tokens in any of the '*.xml' files with the corresponding values from the 'my.properties' file.
<replace dir="${projects.prj.dir}/config"
replacefilterfile="${projects.prj.dir}/my.properties"
includes="*.xml" summary="true" />
I want those files that had their tokens replaced to be created named after a pattern (e.g.) '*.xml.filtered', and keep the original files.
Is this possible in Ant with some smart combination of tasks?

There are a couple of ways to get close to what you want without copying to a temporary directory and copying back.
Filtersets
If the source files can be changed so that the parts to be replaced can be delimited with begin and end tokens, as in #date# (# is the default token, but it can be changed) then you can use the copy task with a globmapper and a 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 occurrence of #FOO# in a source xml file file be replaced with bar in the target.
Note that the source and target directories are the same, the globmapper means the target files and named with the suffix .filtered. It's possible (and more usual) to copy files into a different target directory)
Filterchains
If the source file can't be changed to add begin and end tokens, a possible alternative would be to use a filterchain with one or more replacestring filters instead of the 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 behaviour of the replace task. Unfortunately this way means you need to include all your replacements in the build file itself, you can't have them in a separate properties file.
In both cases the copy task will only copy source files that are newer than the target files, so unnecessary work won't be done.
Copy then replace
A third possibility (that has just occured to me whilst writing up the other two) would be to perform the copy first to the renamed files, then run the replace task specifying 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 might be the closest solution to the original requirement. The downside is that the replace task will be run each time on the renamed files. This could be a problem for some replacement patterns (admittedly they would be odd ones like foo=foofoo, but they would be okay with the first two methods) and you will be doing unnecessary work when the dependencies don't change.

The replace task doesn't observe dependencies, instead it carries out the replacement by writing a temporary file for each input file. If the temporary file is the same as the input file, it is discarded. A temporary file that differs from the input file is renamed to replace that input. This means all the files are processed, even if none of them need be - hence it can be inefficient.
The original solution to this question was to carry out a copy-replace-copy. The second copy isn't needed though, as a mapper can be used in the first. In the copy, dependencies can be used to restrict processing to just the files that have changed - by means of a depend selector in an explicit fileset:
<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>
That will restrict the copy fileset to just those files that have changed. An alternative syntax for the mappers is:
<globmapper from="*.xml" to="*.xml.filtered" />
The simplest replace would then be:
<replace dir="${projects.prj.dir}"
replacefilterfile="my.properties"
includes="*.xml.filtered" />
That will still process all the files though, even if none of them need undergo replacements. The replace task has an implicit fileset and can operate on an explicit fileset, but unlike similar tasks the implicit fileset is not optional, hence to take advantage of selectors in an explicit fileset you must make the implicit one 'do nothing' - hence the .dummy file 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>
That will prevent the replace task from needlessly processing files that have previously undergone substitution. It doesn't, however, prevent processing of files that haven't changed and don't need substitution.
Beyond that, I'm not sure there is a way to 'code golf' this problem to reduce the number of steps to one.
There isn't a multiple string replacement filter that can be used in a copy task to achieve the same affect as replace, which is a shame because that feels like it would be the right solution.
One other approach would be to generate the xml for a series of replace string filters and then have Ant execute that. But that will be more complex than the existing solution, and prone to problems with replacement strings that, if pasted into an xml fragment will result in something that can't be parsed.
Yet another approach would be to write a custom task or script task to do the work. If there are many files and the copy-replace solution is judged to be too slow, then this might be the way to go. But again, that approach is less simple than the existing solution.
If the requirement is to minimise the work done in the processing, rather than to come up with the shortest Ant solution, then this approach might do.
Make a fileset containing a list of inputs that have changed.
From that fileset create a comma-separated list of corresponding filtered files.
Carry out the copy on the fileset.
Carry out the replace on the comma-separated list.
A wrinkle here is that the implicit fileset in the replace task will fall back to processing everything if no files have changed. To overcome this we 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" />

Related

Is there a way to make #suffix on task basename in Apache Ant case-insensitive?

in my build file, I have declared:
<basename property="filename" file="${args.input}" suffix="XML"/>
where ${args.input} is passed in through an Oxygen transformation scenario, ex -Dargs.input="${cfd}\PMC-min.XML"
${filename} returns PMC-min, which is the desired output. I want the file name without any extension.
However, after discovering an error I realized that
<basename property="filename" file="${args.input}" suffix="xml"/>
was returning PMC-min.XML. So #suffix is case-sensitive.
I could change ${args.input} to ${cfd}\PMC-min (which would require other changes to the build file), or just make sure the extension case of ${args.input} matches #suffix in the scenario. But I was wondering if there was a case-insensitive way to retrieve the filename without the extension in ant? (It doesn't seem to matter if the case of the actual file's extension is different, only the parameters have to match).
One way that might work for you is to use a <mappedresources> "collection of one", something like this:
<mappedresources id="converted">
<string value="${args.input}" />
<chainedmapper>
<flattenmapper />
<globmapper casesensitive="false" from="*.xml" to="*" />
</chainedmapper>
</mappedresources>
<echo message="file_name=${toString:converted}" />
The echo is just to illustrate how to reference the resource.
In the above:
the string resource specifies the input value
the chained mapper combines two mapping steps to alter the string:
a flattenmapper removes the directory part
a globmapper removes the suffix, ignoring case.

Using Apache Ant to Delete All But Most Recent 3 Directories

I have a scenario in which there is an "archive" directory that contains various subdirectories. I only want to keep the most recent three subdirectories. So, for example I have:
archive/
subA/ [created 1-May-2018]
subB/ [created 2-May-2018]
subC/ [created 3-May-2018]
subD/ [created 4-May-2018]
subE/ [created 5-May-2018]
In other words, I want to be able to select subA and subB (on the basis of their filesystem dates) and delete them -- including all files and subdirectories within them. I can easily do a Python script that does this, but would prefer a pure-Ant solution.
Based on another StackOverflow question (How to delete all but latest 2 files using Ant), I have tried:
<resources id="deleteDirs">
<allbutlast count="3" >
<sort>
<date />
<resources>
<dirset dir="${dir.archive}" includes="*" />
</resources>
</sort>
</allbutlast>
</resources>
<echo message="Delete directories: ${toString:deleteDirs}" />
<delete verbose="true">
<resources refid="deleteDirs" />
</delete>
However this and any other variations I have come up with do not work. Note, however, that the deleteDirs refid when printed out does show what I want selected, but the delete task quietly ignores it.
In briefly examining the source for the delete task, there are comments to the effect that the delete was at some point refactored to also perform the deprecated deltree task (which apparently cannot wrap around a resource, dirset, or path). So, I am guessing that when deltree was brought into delete it still only works with the form:
<delete dir="DIRNAME"/>
and not when wrapping a resource collection.
Is there a pure Ant way to essentially do:
<deltree dir="${dir.archive}/subA" />
<deltree dir="${dir.archive}/subB" />
However, without hard-coding my selection(s) in deltree tasks and allowing Ant to select all but the most recent three directories based on date.
I got stuck on the same problem. I am shocked that this problem is not solved for long. Apparently task does not work with resource collection of type . The only solution to the problem I could figure out is to use ant-contrib and do the follwing:
<for param="folder">
<path>
<allbutlast count="${to.keep.count}">
<sort>
<dirset dir="${dir.archive}" includes="*"/>
</sort>
</allbutlast>
</path>
<sequential>
<delete dir="#{folder}"/>
</sequential>
</for>
If anybody can suggest a solution without using Ant-Contrib, I will be (and I believe not only me) very very grateful.
Of course the perfect solution would be to make this work as follows:
<delete verbose="true">
<allbutlast count="${to.keep.count}">
<sort>
<dirset dir="${dir.archive}" includes="*"/>
</sort>
</allbutlast>
</delete>
but unfortunately this does not work.

Replace all underscores in filenames using Ant?

What would be the best way to do this using an Ant task:
Start with a bunch of text files with names like this:
Filename_With_Underscores.txt
Another_Filename_With_Underscores.txt
Yet_Another_Filename_With_Underscores.txt
and turn them into this:
Filename+With+Underscores.txt
Another+Filename+With+Underscores.txt
Yet+Another+Filename+With+Underscores.txt
I actually need to change the identically-named folder that each text file is contained in also, that is, to start with this folder/file structure:
Filename_With_Underscores/Filename_With_Underscores.txt
and turn it into this:
Filename+With+Underscores/Filename+With+Underscores.txt
but I can handle that if I know how to rewrite the text file names.
I would know how to replace all underscores with plus signs in the contents of a file, using replaceregexp, but how do I do this to the folder and file names themselves?
I've used a mapper to rewrite folder and file names in the past, for example:
<target name="dita_wrap" description="Wraps each file in a folder with the same name as the file, copies all to new location">
<copy todir="output" verbose="true">
<fileset dir="source"
includes="*/*.dita" />
<regexpmapper handledirsep="true" from="^(.*)/([^/]*)\.dita$"
to="\1/\2/\2.dita" />
</copy>
</target>
However getting something like that to capture and replace each underscore in a filename, no matter how many underscores there might be, is eluding me. Any advice?
You need a <filtermapper>, for example:
<copy todir="output" verbose="true">
<fileset dir="source" includes="*/*.txt" />
<filtermapper>
<replacestring from="_" to="+"/>
</filtermapper>
</copy>
Works for me:
$> find . -type f
./build.xml
./source/Filename_With_Underscores/Filename_With_Underscores.txt
./output/Filename+With+Underscores/Filename+With+Underscores.txt

How to load a list of files as resources in an Ant task?

I've looked all over the net as to how I can load a list of files that contain spaces and don't yet exist with an Ant task.
I have a file that contains one file path per line, like so:
dir1/dir2/dir with spaces/file1.js
dir1/dir2/dir with spaces/dir3/file2.js
dir1/file1.js
Since the paths have spaces I cannot use:
<filelist files="..." />
The files also don't exist yet, so it seems like I can't use
<fileset>
<includesfile name="..." />
</fileset>
Any ideas would be greatly appreaciated.
You can use a resourcelist for this. For example, if your list of files are in a file called 'files.txt':
<resourcelist id="files">
<file file="files.txt"/>
</resourcelist>
<touch mkdirs="true">
<resources refid="files" />
</touch>
For me this yields:
[touch] Creating .../filelist/dir1/dir2/dir with spaces/file1.js
[touch] Creating .../filelist/dir1/dir2/dir with spaces/dir3/file2.js
[touch] Creating .../filelist/dir1/file1.js
The reason this works is that a <resourcelist> treats each line in the file read as a separate resource, so line separators rather than commas or spaces divide the items.

Apply a diff on multiple files - Ant

My ant project generates an "output" folder which contains the outputs of multiple runs and I would like to diff them all against files located in an "expected" folder (these lasts have the exact the same name as the ones in the output folder). I tried that:
<apply executable="diff">
<fileset dir="${output.dir}" />
<arg value="-u" />
<srcfile />
<arg value="${expected.dir}/" />
<srcfile />
<redirector>
<outputmapper type="merge" to="result.out" />
</redirector>
</apply>
But I have two problems:
First I am not allowed to put two <srclist /> in one <apply>, so how can I use two times the name of the current file treated in one <apply>?
Second I am not able to get the output of my <apply> in one single file. I tried different ways (putting the output argument in the <apply> tag...) but it always seems to only store the last execution of the apply. How can I append the result of each diff in one single file?
And I don't want to have to install anything else (so no ant-contrib with foreach).
Thanks.
This works for me:
<delete file="result.out" />
<apply executable="diff" force="yes" output="result.out" append="true">
<fileset dir="${output.dir}" />
<srcfile />
<targetfile />
<mapper type="glob" from="*" to="${expected.dir}/*" />
</apply>
Some explanation:
The apply task targetfile element lets you specify a second arg that is derived from the srcfile via the mapper.
To get the merged output use the output attribute and set append true. Note that you may need to delete the output file (i.e. truncate it) otherwise on rerun you'll not get a clean slate.
I fiddled with the arg list for diff to suit my OS, instead of passing expected.dir and the target file as successive elements, the mapper merges them into a single full path to the 'right hand' file for the diff.

Resources