// 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 } }