2003-09-04 Sebastien Pouliot <spouliot@videotron.ca>
[mono.git] / mcs / class / Mono.GetOptions / OptionList.cs
index 40751e656a79471f4365ea3a1faba2b898caa1db..3fbb3326214d4019c1e84fa2564a054ec561e731 100644 (file)
@@ -13,80 +13,63 @@ using System.Reflection;
 
 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);
                }
                        
@@ -111,99 +94,322 @@ namespace Mono.GetOptions
                        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
+
        }
 }