// NAnt - A .NET build tool
// Copyright (C) 2001 Gerry Shaw
//
// 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
//
// Gert Driesen (gert.driesen@ardatis.com)
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
namespace NAnt.Core.Util {
///
/// Commandline parser.
///
public class CommandLineParser {
#region Public Instance Constructors
///
/// Initializes a new instance of the class
/// using possible arguments deducted from the specific .
///
/// The from which the possible command-line arguments should be retrieved.
/// A value indicating whether or not a response file is able to be used.
/// is a null reference.
public CommandLineParser(Type argumentSpecification, bool supportsResponseFile ) {
if (argumentSpecification == null) {
throw new ArgumentNullException("argumentSpecification");
}
_argumentCollection = new CommandLineArgumentCollection();
foreach (PropertyInfo propertyInfo in argumentSpecification.GetProperties(BindingFlags.Instance | BindingFlags.Public)) {
if (propertyInfo.CanWrite || typeof(ICollection).IsAssignableFrom(propertyInfo.PropertyType)) {
CommandLineArgumentAttribute attribute = GetCommandLineAttribute(propertyInfo);
if (attribute is DefaultCommandLineArgumentAttribute) {
Debug.Assert(_defaultArgument == null);
_defaultArgument = new CommandLineArgument(attribute, propertyInfo);
} else if (attribute != null) {
_argumentCollection.Add(new CommandLineArgument(attribute, propertyInfo));
}
}
}
_argumentSpecification = argumentSpecification;
_supportsResponseFile = supportsResponseFile;
}
#endregion Public Instance Constructors
#region Public Instance Properties
///
/// Gets a logo banner using version and copyright attributes defined on the
/// or the
/// .
///
///
/// A logo banner.
///
public virtual string LogoBanner {
get {
string productName;
string informationalVersion;
Version assemblyVersion;
string configurationInformation = null;
string copyrightInformation = null;
string companyInformation = null;
DateTime releaseDate;
Assembly assembly = Assembly.GetEntryAssembly();
if (assembly == null) {
assembly = Assembly.GetCallingAssembly();
}
// get product name
object[] productAttributes = assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false);
if (productAttributes.Length > 0) {
AssemblyProductAttribute productAttribute = (AssemblyProductAttribute) productAttributes[0];
productName = productAttribute.Product;
} else {
productName = assembly.GetName().Name;
}
// get informational version
object[] informationalVersionAttributes = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
if (informationalVersionAttributes.Length > 0) {
AssemblyInformationalVersionAttribute informationalVersionAttribute = (AssemblyInformationalVersionAttribute) informationalVersionAttributes[0];
informationalVersion = informationalVersionAttribute.InformationalVersion;
} else {
FileVersionInfo info = FileVersionInfo.GetVersionInfo(assembly.Location);
informationalVersion = info.FileMajorPart + "." + info.FileMinorPart + "." + info.FileBuildPart + "." + info.FilePrivatePart;
}
// get assembly version
assemblyVersion = assembly.GetName().Version;
// determine release date using build number of assembly
// version (specified as number of days passed since 1/1/2000)
releaseDate = new DateTime(2000, 1, 1).AddDays(assemblyVersion.Build);
// get configuration information
object[] configurationAttributes = assembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false);
if (configurationAttributes.Length > 0) {
AssemblyConfigurationAttribute configurationAttribute = (AssemblyConfigurationAttribute) configurationAttributes[0];
configurationInformation = configurationAttribute.Configuration;
}
// get copyright information
object[] copyrightAttributes = assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
if (copyrightAttributes.Length > 0) {
AssemblyCopyrightAttribute copyrightAttribute = (AssemblyCopyrightAttribute) copyrightAttributes[0];
copyrightInformation = copyrightAttribute.Copyright;
}
// get company information
object[] companyAttributes = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
if (companyAttributes.Length > 0) {
AssemblyCompanyAttribute companyAttribute = (AssemblyCompanyAttribute) companyAttributes[0];
companyInformation = companyAttribute.Company;
}
StringBuilder logoBanner = new StringBuilder();
logoBanner.AppendFormat(CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_BuildBanner"), productName,
informationalVersion, assemblyVersion.ToString(4),
configurationInformation, releaseDate.ToShortDateString());
// output copyright information
if (!StringUtils.IsNullOrEmpty(copyrightInformation)) {
logoBanner.Append(Environment.NewLine);
logoBanner.Append(copyrightInformation);
}
// output company information
if (!StringUtils.IsNullOrEmpty(companyInformation)) {
logoBanner.Append(Environment.NewLine);
logoBanner.Append(companyInformation);
}
return logoBanner.ToString();
}
}
///
/// Gets the usage instructions.
///
/// The usage instructions.
public virtual string Usage {
get {
StringBuilder helpText = new StringBuilder();
Assembly assembly = Assembly.GetEntryAssembly();
if (assembly == null) {
assembly = Assembly.GetCallingAssembly();
}
// Add usage instructions to helptext
if (helpText.Length > 0) {
helpText.Append(Environment.NewLine);
}
helpText.Append("Usage : " + assembly.GetName().Name + " [options]");
if (_defaultArgument != null) {
helpText.Append(" <" + _defaultArgument.LongName + ">");
if (_defaultArgument.AllowMultiple) {
helpText.Append(" <" + _defaultArgument.LongName + ">");
helpText.Append(" ...");
}
}
helpText.Append(Environment.NewLine);
// Add options to helptext
helpText.Append("Options : ");
helpText.Append(Environment.NewLine);
helpText.Append(Environment.NewLine);
foreach (CommandLineArgument argument in _argumentCollection) {
string valType = "";
if (argument.ValueType == typeof(string)) {
valType = ":";
} else if (argument.ValueType == typeof(bool)) {
valType = "[+|-]";
} else if (argument.ValueType == typeof(FileInfo)) {
valType = ":";
} else if (argument.ValueType == typeof(int)) {
valType = ":";
} else if (argument.IsNameValueCollection) {
valType = ":=";
} else {
valType = ":" + argument.ValueType.FullName;
}
string optionName = argument.LongName;
if (argument.ShortName != null) {
if (argument.LongName.StartsWith(argument.ShortName)) {
optionName = optionName.Insert(argument.ShortName.Length, "[") + "]";
}
helpText.AppendFormat(CultureInfo.InvariantCulture, " -{0,-30}{1}", optionName + valType, argument.Description);
if (!optionName.StartsWith(argument.ShortName)) {
helpText.AppendFormat(CultureInfo.InvariantCulture, " (Short format: /{0})", argument.ShortName);
}
} else {
helpText.AppendFormat(CultureInfo.InvariantCulture, " -{0,-30}{1}", optionName + valType, argument.Description);
}
helpText.Append(Environment.NewLine);
}
if (_supportsResponseFile) {
helpText.AppendFormat(CultureInfo.InvariantCulture, " {0,-31}{1}",
"@",
"Insert command-line settings from a text file.");
helpText.Append(Environment.NewLine);
}
return helpText.ToString();
}
}
///
/// Gets a value indicating whether no arguments were specified on the
/// command line.
///
public bool NoArgs {
get {
foreach(CommandLineArgument argument in _argumentCollection) {
if (argument.SeenValue) {
return true;
}
}
if (_defaultArgument != null) {
return _defaultArgument.SeenValue;
}
return false;
}
}
#endregion Public Instance Properties
#region Public Instance Methods
///
/// Parses an argument list.
///
/// The arguments to parse.
/// The destination object on which properties will be set corresponding to the specified arguments.
/// is a null reference.
/// The of does not match the argument specification that was used to initialize the parser.
public void Parse(string[] args, object destination) {
if (destination == null) {
throw new ArgumentNullException("destination");
}
if (!_argumentSpecification.IsAssignableFrom(destination.GetType())) {
throw new ArgumentException("Type of destination does not match type of argument specification.");
}
ParseArgumentList(args);
// check for missing required arguments
foreach (CommandLineArgument arg in _argumentCollection) {
arg.Finish(destination);
}
if (_defaultArgument != null) {
_defaultArgument.Finish(destination);
}
}
#endregion Public Instance Methods
#region Private Instance Methods
///
/// Splits a string and removes any empty strings from the
/// result. Same functionality as the
/// public string[] Split(char[] separator, StringSplitOptions options)
/// method in .Net 2.0. Replace with that call when 2.0 is standard.
///
///
///
/// the array of strings
string[] SplitStringNoNulls(string sourceString, char[] delimiters) {
string[] basicSplit = sourceString.Split(delimiters);
bool containsNulls = false;
if ( basicSplit.Length == 0) {
return basicSplit;
}
StringCollection intermediateResult = new StringCollection();
foreach(string item in basicSplit) {
if (item.Length != 0 ) {
intermediateResult.Add(item);
} else {
containsNulls = true;
}
}
// only make the extra copy of there are nulls.
if (containsNulls) {
string[] result = new string[intermediateResult.Count];
intermediateResult.CopyTo(result, 0);
return result;
} else {
return basicSplit;
}
}
///
/// Read a response file and parse the arguments as usual.
///
///
private void ProcessResponseFile(string file){
// throw excaption if file doesn't exist
StringCollection argsCol = new StringCollection();
char[] whitespaceChars = new char[]{' ', '\t'};
using (StreamReader sr = new StreamReader(file)) {
String line;
// Read and concat lines from the file until the end of
// the file is reached.
while ((line = sr.ReadLine()) != null) {
line = line.Trim(whitespaceChars);
if ( ! line.StartsWith("#")) {
argsCol.AddRange( SplitStringNoNulls(line, whitespaceChars ));
}
}
string[] args = new string[argsCol.Count];
argsCol.CopyTo( args, 0 );
//parse as a regular argument list.
ParseArgumentList( args );
}
}
///
/// Parse the argument list using the
///
///
private void ParseArgumentList(string[] args) {
if (args != null) {
foreach (string argument in args) {
if (argument.Length > 0) {
switch (argument[0]) {
case '-':
case '/':
int endIndex = argument.IndexOfAny(new char[] {':', '+', '-'}, 1);
string option = argument.Substring(1, endIndex == -1 ? argument.Length - 1 : endIndex - 1);
string optionArgument;
if (option.Length + 1 == argument.Length) {
optionArgument = null;
} else if (argument.Length > 1 + option.Length && argument[1 + option.Length] == ':') {
optionArgument = argument.Substring(option.Length + 2);
} else {
optionArgument = argument.Substring(option.Length + 1);
}
CommandLineArgument arg = _argumentCollection[option];
if (arg == null) {
throw new CommandLineArgumentException(string.Format(CultureInfo.InvariantCulture,
"Unknown argument '{0}'", argument));
} else {
// check if argument is obsolete
Attribute[] attribs = (Attribute[]) arg.Property.GetCustomAttributes(
typeof(ObsoleteAttribute), false);
if (attribs.Length > 0) {
ObsoleteAttribute obsoleteAttrib = (ObsoleteAttribute) attribs[0];
string message = string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1177"), option,
obsoleteAttrib.Message);
if (obsoleteAttrib.IsError) {
throw new CommandLineArgumentException(message);
} else {
Console.WriteLine(string.Empty);
Console.WriteLine("Warning: " + message);
Console.WriteLine(string.Empty);
}
}
if (arg.IsExclusive && args.Length > 1) {
throw new CommandLineArgumentException(string.Format(CultureInfo.InvariantCulture,
"Commandline argument '-{0}' cannot be combined with other arguments.",
arg.LongName));
} else {
arg.SetValue(optionArgument);
}
}
break;
case '@':
if ( _supportsResponseFile ) {
string responseFile = argument.Substring(1, argument.Length - 1 );
if ( ! File.Exists(responseFile)) {
throw new CommandLineArgumentException(string.Format(CultureInfo.InvariantCulture,
"unable to open response file \"{0}\"", responseFile ));
}
// load file and parse it.
ProcessResponseFile(responseFile);
break;
}
continue;
default:
if (_defaultArgument != null) {
_defaultArgument.SetValue(argument);
} else {
throw new CommandLineArgumentException(string.Format(CultureInfo.InvariantCulture,
"Unknown argument '{0}'", argument));
}
break;
}
}
}
}
}
#endregion Private Instance Methods
#region Private Static Methods
///
/// Returns the that's applied
/// on the specified property.
///
/// The property of which applied should be returned.
///
/// The that's applied to the
/// , or a null reference if none was applied.
///
private static CommandLineArgumentAttribute GetCommandLineAttribute(PropertyInfo propertyInfo) {
object[] attributes = propertyInfo.GetCustomAttributes(typeof(CommandLineArgumentAttribute), false);
if (attributes.Length == 1)
return (CommandLineArgumentAttribute) attributes[0];
Debug.Assert(attributes.Length == 0);
return null;
}
#endregion Private Static Methods
#region Private Instance Fields
private CommandLineArgumentCollection _argumentCollection;
private CommandLineArgument _defaultArgument;
private Type _argumentSpecification;
private bool _supportsResponseFile = false;
#endregion Private Instance Fields
}
}