namespace Mono.GetOptions
{
- [AttributeUsage(AttributeTargets.Assembly, AllowMultiple=true)]
- public class AuthorAttribute : System.Attribute
- {
- public string Name;
- public string SubProject;
-
- public AuthorAttribute(string Name)
- {
- this.Name = Name;
- this.SubProject = null;
- }
-
- public AuthorAttribute(string Name, string SubProject)
- {
- this.Name = Name;
- this.SubProject = SubProject;
- }
- public override string ToString()
- {
- if (SubProject == null)
- return Name;
- else
- return Name + " (" + SubProject + ")";
- }
+ [Flags]
+ public enum OptionsParsingMode
+ {
+ Linux = 1,
+ Windows = 2,
+ Both = 3
}
- enum OptionParameterType
- {
- None,
- Integer,
- Decimal, // look XML Schemas for better names
- String,
- Symbol,
- FilePath,
- FileMask,
- AssemblyName,
- AssemblyFileName,
- AssemblyNameOrFileName
- }
-
- public delegate bool OptionFound(object Value);
-
/// <summary>
- /// Summary description for Class1.
+ /// Option Parsing
/// </summary>
public class OptionList
{
- struct OptionDetails
- {
- public char ShortForm;
- public string LongForm;
- public string ShortDescription;
- public OptionParameterType ParameterType;
- public int MinOccurs;
- public int MaxOccurs; // negative means there is no limit
- public object DefaultValue;
- public OptionFound Dispatcher;
-
- }
+ private Options optionBundle = null;
+ private OptionsParsingMode parsingMode;
+ private bool breakSingleDashManyLettersIntoManyOptions;
+ private bool endOptionProcessingWithDoubleDash;
+
+ private string appExeName;
+ private string appVersion;
+
private string appTitle = "Add a [assembly: AssemblyTitle(\"Here goes the application name\")] to your assembly";
private string appCopyright = "Add a [assembly: AssemblyCopyright(\"(c)200n Here goes the copyright holder name\")] to your assembly";
private string appDescription = "Add a [assembly: AssemblyDescription(\"Here goes the short description\")] to your assembly";
+ private string appAboutDetails = "Add a [assembly: Mono.About(\"Here goes the short about details\")] to your assembly";
+ private string appUsageComplement = "Add a [assembly: Mono.UsageComplement(\"Here goes the usage clause complement\")] to your assembly";
private string[] appAuthors;
- public readonly string usageFormat;
- public readonly string aboutDetails;
+ private ArrayList list = new ArrayList();
+ private ArrayList arguments = new ArrayList();
+ private ArrayList argumentsTail = new ArrayList();
+ private MethodInfo argumentProcessor = null;
+
+ public string Usage
+ {
+ get
+ {
+ return "Usage: " + appExeName + " [options] " + appUsageComplement;
+ }
+ }
- private SortedList list = new SortedList();
+ public string AboutDetails
+ {
+ get
+ {
+ return appAboutDetails;
+ }
+ }
+ #region Assembly Attributes
+
+ Assembly entry;
+
private object[] GetAssemblyAttributes(Type type)
{
- Assembly entry = Assembly.GetEntryAssembly();
return entry.GetCustomAttributes(type, false);
}
object[] result = GetAssemblyAttributes(type);
if ((result != null) && (result.Length > 0))
- var = (string)(string)type.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance, null, result[0], new object [] {}); ;
+ var = (string)type.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance, null, result[0], new object [] {}); ;
+ }
+
+ private void GetAssemblyAttributeValue(Type type, ref string var)
+ {
+ object[] result = GetAssemblyAttributes(type);
+
+ if ((result != null) && (result.Length > 0))
+ var = result[0].ToString();
}
- public OptionList(string aboutDetails, string usageFormat)
+ #endregion
+
+ #region Constructors
+
+ private void AddArgumentProcessor(MemberInfo memberInfo)
{
- this.aboutDetails = aboutDetails;
- this.usageFormat = usageFormat;
+ if (argumentProcessor != null)
+ throw new NotSupportedException("More than one argument processor method found");
+
+ if ((memberInfo.MemberType == MemberTypes.Method && memberInfo is MethodInfo))
+ {
+ if (((MethodInfo)memberInfo).ReturnType.FullName != typeof(void).FullName)
+ throw new NotSupportedException("Argument processor method must return 'void'");
+
+ ParameterInfo[] parameters = ((MethodInfo)memberInfo).GetParameters();
+ if ((parameters == null) || (parameters.Length != 1) || (parameters[0].ParameterType.FullName != typeof(string).FullName))
+ throw new NotSupportedException("Argument processor method must have a string parameter");
+
+ argumentProcessor = (MethodInfo)memberInfo;
+ }
+ else
+ throw new NotSupportedException("Argument processor marked member isn't a method");
+ }
+
+ private void Initialize(Options optionBundle)
+ {
+ if (optionBundle == null)
+ throw new ArgumentNullException("optionBundle");
+
+ entry = Assembly.GetEntryAssembly();
+ appExeName = entry.GetName().Name;
+ appVersion = entry.GetName().Version.ToString();
+
+ this.optionBundle = optionBundle;
+ this.parsingMode = optionBundle.ParsingMode ;
+ this.breakSingleDashManyLettersIntoManyOptions = optionBundle.BreakSingleDashManyLettersIntoManyOptions;
+ this.endOptionProcessingWithDoubleDash = optionBundle.EndOptionProcessingWithDoubleDash;
GetAssemblyAttributeValue(typeof(AssemblyTitleAttribute), "Title", ref appTitle);
GetAssemblyAttributeValue(typeof(AssemblyCopyrightAttribute), "Copyright", ref appCopyright);
GetAssemblyAttributeValue(typeof(AssemblyDescriptionAttribute), "Description", ref appDescription);
+ GetAssemblyAttributeValue(typeof(Mono.AboutAttribute), ref appAboutDetails);
+ GetAssemblyAttributeValue(typeof(Mono.UsageComplementAttribute), ref appUsageComplement);
appAuthors = GetAssemblyAttributeStrings(typeof(AuthorAttribute));
if (appAuthors.Length == 0)
{
appAuthors = new String[1];
appAuthors[0] = "Add one or more [assembly: Mono.GetOptions.Author(\"Here goes the author name\")] to your assembly";
}
- }
- private void AddGenericOption(
- char shortForm,
- string longForm,
- string shortDescription,
- OptionParameterType parameterType,
- int minOccurs,
- int maxOccurs,
- object defaultValue,
- OptionFound dispatcher)
- {
- OptionDetails option = new OptionDetails();
-
- option.ShortForm = shortForm;
- option.LongForm = longForm;
- option.ShortDescription = shortDescription;
- option.ParameterType = parameterType;
- option.MinOccurs = minOccurs;
- option.MaxOccurs = maxOccurs;
- option.DefaultValue = defaultValue;
- option.Dispatcher = dispatcher;
-
- if (shortForm == ' ')
- list.Add(longForm, option);
- else
- list.Add(shortForm.ToString(), option);
+ foreach(MemberInfo mi in optionBundle.GetType().GetMembers())
+ {
+ object[] attribs = mi.GetCustomAttributes(typeof(OptionAttribute), true);
+ if (attribs != null && attribs.Length > 0)
+ list.Add(new OptionDetails(mi, (OptionAttribute)attribs[0], optionBundle));
+ else
+ {
+ attribs = mi.GetCustomAttributes(typeof(ArgumentProcessorAttribute), true);
+ if (attribs != null && attribs.Length > 0)
+ AddArgumentProcessor(mi);
+ }
+ }
}
- public void AddAbout(char shortForm, string longForm, string shortDescription)
+ public OptionList(Options optionBundle)
{
- AddGenericOption(shortForm, longForm, shortDescription, OptionParameterType.None, 0, 1, null, new OptionFound(DoAbout));
+ Initialize(optionBundle);
}
- public void AddBooleanSwitch(char shortForm, string longForm, string shortDescription, bool defaultValue, OptionFound switcher)
- {
- AddGenericOption(shortForm, longForm, shortDescription, OptionParameterType.None, 0, 1, defaultValue, switcher);
- }
+ #endregion
+
+ #region Prebuilt Options
- public void ShowAbout()
+ private void ShowTitleLines()
{
- Console.WriteLine(appTitle + " - " + appCopyright);
+ Console.WriteLine(appTitle + " " + appVersion + " - " + appCopyright);
Console.WriteLine(appDescription);
Console.WriteLine();
- Console.WriteLine(aboutDetails);
+ }
+
+ private void ShowAbout()
+ {
+ ShowTitleLines();
+ Console.WriteLine(appAboutDetails);
Console.WriteLine();
Console.WriteLine("Authors:");
foreach(string s in appAuthors)
Console.WriteLine ("\t" + s);
}
- private bool DoAbout(object nothing)
+ private void ShowHelp()
{
- ShowAbout();
- return true;
+ ShowTitleLines();
+ Console.WriteLine(Usage);
+ Console.WriteLine("Options:");
+ foreach (OptionDetails option in list)
+ Console.WriteLine(option);
+ }
+
+ private void ShowUsage()
+ {
+ Console.WriteLine(Usage);
+ Console.Write("Short Options: ");
+ foreach (OptionDetails option in list)
+ Console.Write((option.ShortForm != ' ') ? option.ShortForm.ToString() : "");
+ Console.WriteLine();
+
}
- public void ShowUsage()
+ private void ShowUsage(string errorMessage)
{
- Console.WriteLine(appTitle + " - " + appCopyright);
- Console.Write("Usage: ");
- Console.WriteLine(usageFormat);
- // TODO: list registered options here
- foreach (DictionaryEntry option in list)
- Console.WriteLine(option.Value.ToString());
+ Console.WriteLine("ERROR: " + errorMessage.TrimEnd());
+ ShowUsage();
}
- public void ShowUsage(string errorMessage)
+ internal WhatToDoNext DoUsage()
{
- Console.WriteLine(errorMessage);
ShowUsage();
+ return WhatToDoNext.AbandonProgram;
}
- public void ProcessArgs(string[] args)
+ internal WhatToDoNext DoAbout()
{
ShowAbout();
+ return WhatToDoNext.GoAhead;
+ }
+
+ internal WhatToDoNext DoHelp()
+ {
+ ShowHelp();
+ return WhatToDoNext.AbandonProgram;
+ }
+
+ #endregion
+
+ #region Arguments Processing
+
+ public string[] ExpandResponseFiles(string[] args)
+ {
+ ArrayList result = new ArrayList();
+
+ foreach(string arg in args)
+ {
+ if (arg.StartsWith("@"))
+ {
+ try
+ {
+ StreamReader tr = new StreamReader(arg.Substring(1));
+ string line;
+ while ((line = tr.ReadLine()) != null)
+ {
+ result.AddRange(line.Split());
+ }
+ tr.Close();
+ }
+ catch (FileNotFoundException exception)
+ {
+ Console.WriteLine("Could not find response file: " + arg.Substring(1));
+ continue;
+ }
+ catch (Exception exception)
+ {
+ Console.WriteLine("Error trying to read response file: " + arg.Substring(1));
+ Console.WriteLine(exception.Message);
+ continue;
+ }
+ }
+ else
+ result.Add(arg);
+ }
+
+ return (string[])result.ToArray(typeof(string));
+ }
+
+ public string[] NormalizeArgs(string[] args)
+ {
+ bool ParsingOptions = true;
+ ArrayList result = new ArrayList();
+
+ foreach(string arg in ExpandResponseFiles(args))
+ {
+ if (arg.Length > 0)
+ {
+ if (ParsingOptions)
+ {
+ if (endOptionProcessingWithDoubleDash && (arg == "--"))
+ {
+ ParsingOptions = false;
+ continue;
+ }
+
+ if (arg[0] == '/')
+ {
+ if ((parsingMode & OptionsParsingMode.Windows) > 0)
+ {
+ string newArg = '-' + arg.TrimStart('/');
+ result.AddRange(newArg.Split(':'));
+ continue;
+ }
+ }
+
+ if ((parsingMode & OptionsParsingMode.Linux) > 0)
+ {
+ if ((arg[0] == '-') && (arg[1] != '-'))
+ {
+ if (breakSingleDashManyLettersIntoManyOptions)
+ {
+ foreach(char c in arg.Substring(1)) // many single-letter options
+ result.Add("-" + c); // expand into individualized options
+ }
+ else
+ result.Add(arg);
+ continue;
+ }
+
+ if (arg.StartsWith("--"))
+ {
+ result.AddRange(arg.Split('=')); // put in the same form of one-letter options with a parameter
+ continue;
+ }
+ }
+ }
+ else
+ {
+ argumentsTail.Add(arg);
+ continue;
+ }
+
+ // if nothing else matches then it get here
+ result.Add(arg);
+ }
+ }
+
+ return (string[])result.ToArray(typeof(string));
}
+
+ public string[] ProcessArgs(string[] args)
+ {
+ string arg;
+ string nextArg;
+ bool OptionWasProcessed;
+
+ list.Sort();
+
+ args = NormalizeArgs(args);
+
+ try
+ {
+ int argc = args.Length;
+ for (int i = 0; i < argc; i++)
+ {
+ arg = args[i];
+ if (i+1 < argc)
+ nextArg = args[i+1];
+ else
+ nextArg = null;
+
+ OptionWasProcessed = false;
+
+ if (arg.StartsWith("-"))
+ {
+ foreach(OptionDetails option in list)
+ {
+ OptionProcessingResult result = option.ProcessArgument(arg, nextArg);
+ if (result != OptionProcessingResult.NotThisOption)
+ {
+ OptionWasProcessed = true;
+ if (result == OptionProcessingResult.OptionConsumedParameter)
+ i++;
+ break;
+ }
+ }
+ }
+
+ if (!OptionWasProcessed)
+ {
+ if (OptionDetails.Verbose)
+ Console.WriteLine("Could not find what to do with [" + arg + "] followed by [" + nextArg + "]");
+
+ arguments.Add(arg);
+ }
+ }
+
+ foreach(OptionDetails option in list)
+ option.TransferValues();
+
+ foreach(string argument in argumentsTail)
+ arguments.Add(argument);
+
+ if (argumentProcessor == null)
+ return (string[])arguments.ToArray(typeof(string));
+
+ foreach(string argument in arguments)
+ argumentProcessor.Invoke(optionBundle, new object[] { argument });
+ }
+ catch (Exception ex)
+ {
+ ShowUsage(ex.Message);
+ System.Environment.Exit(1);
+ }
+
+ return null;
+ }
+
+ #endregion
+
}
}