// NAnt - A .NET build tool
// Copyright (C) 2002-2003 Scott Hernandez
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Scott Hernandez (ScottHernandez@hotmail.com)
using System;
using System.Globalization;
using System.IO;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using NAnt.Core.Util;
namespace NAnt.Core.Tasks {
///
/// Loops over a set of items.
///
///
///
/// Can loop over files in directory, lines in a file, etc.
///
///
/// The property value is stored before the loop is done, and restored
/// when the loop is finished.
///
///
/// The property is returned to its normal value once it is used. Read-only
/// parameters cannot be overridden in this loop.
///
///
///
/// Loops over the files in c:\.
///
///
///
///
/// ]]>
///
///
///
/// Loops over all files in the project directory.
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
///
/// Loops over the folders in c:\.
///
///
///
///
/// ]]>
///
///
///
/// Loops over all folders in the project directory.
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
///
/// Loops over a list.
///
///
///
///
/// ]]>
///
///
///
///
/// Loops over lines in the file properties.csv, where each line
/// is of the format name,value.
///
///
///
///
///
/// ]]>
///
///
[TaskName("foreach")]
public class LoopTask : TaskContainer {
public enum LoopItem {
File = 1,
Folder = 2,
String = 3,
Line = 4
}
public enum LoopTrim {
///
/// Do not remove any white space characters.
///
None = 0,
///
/// Remove all white space characters from the end of the current
/// item.
///
End = 1,
///
/// Remove all white space characters from the beginning of the
/// current item.
///
Start = 2,
///
/// Remove all white space characters from the beginning and end of
/// the current item.
///
Both = 3
}
#region Private Instance Fields
private string _prop;
private string[] _props;
private LoopItem _loopItem;
private LoopTrim _loopTrim = LoopTrim.None;
private string _inAttribute;
private string _delim;
private InElement _inElement;
private TaskContainer _doStuff;
#endregion Private Instance Fields
#region Public Instance Properties
///
/// The NAnt property name(s) that should be used for the current
/// iterated item.
///
///
/// If specifying multiple properties, separate them with a comma.
///
[TaskAttribute("property", Required=true)]
[StringValidator(AllowEmpty=false)]
public string Property {
get { return _prop; }
set {
_prop = value;
_props = _prop.Split(',');
foreach (string prop in _props) {
if (Properties.IsReadOnlyProperty(prop)) {
throw new BuildException("Property is readonly! :" + prop, Location);
}
}
}
}
///
/// The type of iteration that should be done.
///
[TaskAttribute("item", Required=true)]
public LoopItem ItemType {
get { return _loopItem; }
set { _loopItem = value; }
}
///
/// The type of whitespace trimming that should be done. The default
/// is .
///
[TaskAttribute("trim")]
public LoopTrim TrimType {
get { return _loopTrim;}
set { _loopTrim = value; }
}
///
/// The source of the iteration.
///
[TaskAttribute("in", Required=false)]
public string Source {
get { return _inAttribute;}
set { _inAttribute = StringUtils.ConvertEmptyToNull(value); }
}
///
/// The deliminator char.
///
[TaskAttribute("delim")]
public string Delimiter {
get { return _delim; }
set {
if (value == null || value.Length == 0) {
_delim = null;
} else {
_delim = value;
}
}
}
///
/// Stuff to operate in. Just like the
/// attribute, but supports more complicated things like a
/// and such.
///
/// Please remove the attribute if you
/// are using this element.
///
///
[BuildElement("in")]
public InElement InElement {
get { return _inElement; }
set { _inElement = value; }
}
///
/// Tasks to execute for each matching item.
///
[BuildElement("do")]
public TaskContainer StuffToDo {
get { return _doStuff; }
set { _doStuff = value; }
}
#endregion Public Instance Properties
#region Override implementation of TaskContainer
protected override void ExecuteTask() {
string[] oldPropVals = new string[_props.Length];
// Save all of the old property values
for (int nIndex = 0; nIndex < oldPropVals.Length; nIndex++) {
oldPropVals[nIndex] = Properties[_props[nIndex]];
}
try {
switch (ItemType) {
case LoopItem.File:
if (StringUtils.IsNullOrEmpty(Source) && InElement == null) {
throw new BuildException("Invalid foreach", Location, new ArgumentException("Nothing to work with...!", "in"));
}
if (!StringUtils.IsNullOrEmpty(Source)) {
// resolve to full path
Source = Project.GetFullPath(Source);
// ensure directory exists
if (!Directory.Exists(Source)) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1134"),
Source), Location);
}
if (_props.Length != 1) {
throw new BuildException(@"Only one property is valid for item=""File""", Location);
}
DirectoryInfo dirInfo = new DirectoryInfo(Source);
FileInfo[] files = dirInfo.GetFiles();
foreach (FileInfo file in files) {
DoWork(file.FullName);
}
} else {
if (StuffToDo == null) {
throw new BuildException("Must use with .", Location);
}
foreach (string file in InElement.Items.FileNames) {
DoWork(file);
}
}
break;
case LoopItem.Folder:
if (StringUtils.IsNullOrEmpty(Source) && InElement == null) {
throw new BuildException("Invalid foreach", Location, new ArgumentException("Nothing to work with...!", "in"));
}
if (_props.Length != 1) {
throw new BuildException(@"Only one property is valid for item=""Folder""", Location);
}
if (!StringUtils.IsNullOrEmpty(Source)) {
// resolve to full path
Source = Project.GetFullPath(Source);
// ensure directory exists
if (!Directory.Exists(Source)) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1134"),
Source), Location);
}
DirectoryInfo dirInfo = new DirectoryInfo(Source);
DirectoryInfo[] dirs = dirInfo.GetDirectories();
foreach (DirectoryInfo dir in dirs) {
DoWork(dir.FullName);
}
} else {
if (StuffToDo == null) {
throw new BuildException("Must use with .", Location);
}
foreach (string dir in InElement.Items.DirectoryNames) {
DoWork(dir);
}
}
break;
case LoopItem.Line:
if (StringUtils.IsNullOrEmpty(Source) && InElement == null) {
throw new BuildException("Invalid foreach", Location, new ArgumentException("Nothing to work with...!", "in"));
}
if (_props.Length > 1 && Delimiter == null) {
throw new BuildException("Delimiter(s) must be specified if multiple properties are specified", Location);
}
if (!StringUtils.IsNullOrEmpty(Source)) {
// resolve to full path
Source = Project.GetFullPath(Source);
// ensure file exists
if (!File.Exists(Source)) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1133"),
Source), Location);
}
DoWorkOnFileLines(Source);
} else {
if (StuffToDo == null) {
throw new BuildException("Must use with .", Location);
}
foreach (string file in InElement.Items.FileNames) {
DoWorkOnFileLines(file);
}
}
break;
case LoopItem.String:
if (StringUtils.IsNullOrEmpty(Source)) {
return;
}
if (_props.Length > 1) {
throw new BuildException(@"Only one property may be specified for item=""String""", Location);
}
if (Delimiter == null) {
throw new BuildException(@"Delimiter must be specified for item=""String""", Location);
}
string[] items = Source.Split(Delimiter.ToCharArray());
foreach (string s in items) {
DoWork(s);
}
break;
}
} finally {
// Restore all of the old property values
for (int nIndex = 0; nIndex < oldPropVals.Length; nIndex++) {
Properties[_props[nIndex]] = oldPropVals[nIndex];
}
}
}
protected override void ExecuteChildTasks() {
if (StuffToDo == null) {
base.ExecuteChildTasks();
} else {
StuffToDo.Execute();
}
}
#endregion Override implementation of TaskContainer
#region Protected Instance Methods
protected virtual void DoWork(params string[] propVals) {
for (int nIndex = 0; nIndex < propVals.Length; nIndex++) {
string propValue = propVals[nIndex];
if (nIndex >= _props.Length) {
throw new BuildException("Too many items on line", Location);
}
switch (TrimType) {
case LoopTrim.Both:
propValue = propValue.Trim();
break;
case LoopTrim.Start:
propValue = propValue.TrimStart();
break;
case LoopTrim.End:
propValue = propValue.TrimEnd();
break;
}
Properties[_props[nIndex]] = propValue;
}
base.ExecuteTask();
}
#endregion Protected Instance Methods
#region Private Instance Methods
private void DoWorkOnFileLines(string filename) {
using (StreamReader sr = File.OpenText(filename)) {
while (true) {
string line = sr.ReadLine();
if (line == null) {
break;
}
if (Delimiter == null) {
DoWork(line);
} else {
DoWork(line.Split(Delimiter.ToCharArray()));
}
}
}
}
#endregion Private Instance Methods
}
public class InElement : Element {
#region Private Instance Fields
private FileSet _items = null;
#endregion Private Instance Fields
#region Public Instance Properties
[BuildElement("items")]
public FileSet Items {
get { return _items;}
set { _items = value; }
}
#endregion Public Instance Properties
}
}