2002-10-20 Rafael Teixeira <rafaelteixeirabr@hotmail.com>
[mono.git] / mcs / class / Mono.GetOptions / OptionList.cs
1 //
2 // OptionList.cs
3 //
4 // Author: Rafael Teixeira (rafaelteixeirabr@hotmail.com) (original)
5 // Author: Dean Scarff (D_Scarff@AIMINGFORSPAMFREEmsn.com) (modifications)
6 //
7 // (C) 2002 Rafael Teixeira
8 // Modifications (C) 2002 Dean Scarff, distributed under the Gnu General Public License Version 2 or later
9 //
10
11 using System;
12 using System.Collections;
13 using System.IO;
14 using System.Reflection;
15
16 namespace Mono.GetOptions
17 {
18
19         [Flags]
20         public enum OptionsParsingMode 
21         { 
22                 Linux   = 1, 
23                 Windows = 2,
24                 Both    = 3
25         }
26
27         /// <summary>
28         /// Option Parsing
29         /// </summary>
30         public class OptionList
31         {
32         
33                 private Options optionBundle = null;
34                 private OptionsParsingMode parsingMode;
35                 private bool endOptionProcessingWithDoubleDash;
36                 
37                 private string appExeName;
38                 private string appVersion;
39
40                 private string appTitle = "Add a [assembly: AssemblyTitle(\"Here goes the application name\")] to your assembly";
41                 private string appCopyright = "Add a [assembly: AssemblyCopyright(\"(c)200n Here goes the copyright holder name\")] to your assembly";
42                 private string appDescription = "Add a [assembly: AssemblyDescription(\"Here goes the short description\")] to your assembly";
43                 private string appAboutDetails = "Add a [assembly: Mono.About(\"Here goes the short about details\")] to your assembly";
44                 private string appUsageComplement = "Add a [assembly: Mono.UsageComplement(\"Here goes the usage clause complement\")] to your assembly";
45                 private string[] appAuthors;
46  
47                 private ArrayList list = new ArrayList();
48                 private ArrayList arguments = new ArrayList();
49                 private ArrayList argumentsTail = new ArrayList();
50                 private MethodInfo argumentProcessor = null;
51
52                 public string Usage
53                 {
54                         get
55                         {
56                                 return "Usage: " + appExeName + " [options] " + appUsageComplement;
57                         }
58                 }
59
60                 public string AboutDetails
61                 {
62                         get
63                         {
64                                 return appAboutDetails;
65                         }
66                 }
67
68                 #region Assembly Attributes
69
70                 Assembly entry;
71                 
72                 private object[] GetAssemblyAttributes(Type type)
73                 {
74                         return entry.GetCustomAttributes(type, false);
75                 }
76                         
77                 private string[] GetAssemblyAttributeStrings(Type type)
78                 {
79                         object[] result = GetAssemblyAttributes(type);
80                         
81                         if ((result == null) || (result.Length == 0))
82                                 return new string[0];
83
84                         int i = 0;
85                         string[] var = new string[result.Length];
86
87                         foreach(object o in result)
88                                 var[i++] = o.ToString(); 
89
90                         return var;
91                 }
92
93                 private void GetAssemblyAttributeValue(Type type, string propertyName, ref string var)
94                 {
95                         object[] result = GetAssemblyAttributes(type);
96                         
97                         if ((result != null) && (result.Length > 0))
98                                 var = (string)type.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance, null, result[0], new object [] {}); ;
99                 }
100
101                 private void GetAssemblyAttributeValue(Type type, ref string var)
102                 {
103                         object[] result = GetAssemblyAttributes(type);
104                         
105                         if ((result != null) && (result.Length > 0))
106                                 var = result[0].ToString();
107                 }
108
109                 #endregion
110
111                 #region Constructors
112
113                 private void AddArgumentProcessor(MemberInfo memberInfo)
114                 {
115                         if (argumentProcessor != null)
116                                 throw new NotSupportedException("More than one argument processor method found");
117
118                         if ((memberInfo.MemberType == MemberTypes.Method && memberInfo is MethodInfo))
119                         {
120                                 if (((MethodInfo)memberInfo).ReturnType.FullName != typeof(void).FullName)
121                                         throw new NotSupportedException("Argument processor method must return 'void'");
122
123                                 ParameterInfo[] parameters = ((MethodInfo)memberInfo).GetParameters();
124                                 if ((parameters == null) || (parameters.Length != 1) || (parameters[0].ParameterType.FullName != typeof(string).FullName))
125                                         throw new NotSupportedException("Argument processor method must have a string parameter");
126                                 
127                                 argumentProcessor = (MethodInfo)memberInfo; 
128                         }
129                         else
130                                 throw new NotSupportedException("Argument processor marked member isn't a method");
131                 }
132
133                 private void Initialize(Options optionBundle)
134                 {
135                         if (optionBundle == null)
136                                 throw new ArgumentNullException("optionBundle");
137
138                         entry = Assembly.GetEntryAssembly();
139                         appExeName = entry.GetName().Name;
140                         appVersion = entry.GetName().Version.ToString();
141
142                         this.optionBundle = optionBundle; 
143                         this.parsingMode = optionBundle.ParsingMode ;
144                         this.endOptionProcessingWithDoubleDash = optionBundle.EndOptionProcessingWithDoubleDash;
145
146                         GetAssemblyAttributeValue(typeof(AssemblyTitleAttribute), "Title", ref appTitle);
147                         GetAssemblyAttributeValue(typeof(AssemblyCopyrightAttribute), "Copyright", ref appCopyright);
148                         GetAssemblyAttributeValue(typeof(AssemblyDescriptionAttribute), "Description", ref appDescription);
149                         GetAssemblyAttributeValue(typeof(Mono.AboutAttribute), ref appAboutDetails);
150                         GetAssemblyAttributeValue(typeof(Mono.UsageComplementAttribute), ref appUsageComplement);
151                         appAuthors = GetAssemblyAttributeStrings(typeof(AuthorAttribute));
152                         if (appAuthors.Length == 0)
153                         {
154                                 appAuthors = new String[1];
155                                 appAuthors[0] = "Add one or more [assembly: Mono.GetOptions.Author(\"Here goes the author name\")] to your assembly";
156                         }
157
158                         foreach(MemberInfo mi in optionBundle.GetType().GetMembers())
159                         {
160                                 object[] attribs = mi.GetCustomAttributes(typeof(OptionAttribute), true);
161                                 if (attribs != null && attribs.Length > 0)
162                                         list.Add(new OptionDetails(mi, (OptionAttribute)attribs[0], optionBundle));
163                                 else
164                                 {
165                                         attribs = mi.GetCustomAttributes(typeof(ArgumentProcessorAttribute), true);
166                                         if (attribs != null && attribs.Length > 0)
167                                                 AddArgumentProcessor(mi);
168                                 }
169                         }
170                 }
171
172                 public OptionList(Options optionBundle)
173                 {
174                         Initialize(optionBundle);
175                 }
176
177                 #endregion
178
179                 #region Prebuilt Options
180
181                 private void ShowTitleLines()
182                 {
183                         Console.WriteLine(appTitle + "  " + appVersion + " - " + appCopyright); 
184                         Console.WriteLine(appDescription); 
185                         Console.WriteLine();
186                 }
187
188                 private void ShowAbout()
189                 {
190                         ShowTitleLines();
191                         Console.WriteLine(appAboutDetails); 
192                         Console.WriteLine();
193                         Console.WriteLine("Authors:");
194                         foreach(string s in appAuthors)
195                                 Console.WriteLine ("\t" + s);
196                 }
197
198                 private void ShowHelp()
199                 {
200                         ShowTitleLines();
201                         Console.WriteLine(Usage);
202                         Console.WriteLine("Options:");
203                         foreach (OptionDetails option in list)
204                                 Console.WriteLine(option);
205                 }
206
207                 private void ShowUsage()
208                 {
209                         Console.WriteLine(Usage);
210                         Console.Write("Short Options: ");
211                         foreach (OptionDetails option in list)
212                                 Console.Write((option.ShortForm != ' ') ? option.ShortForm.ToString() : "");
213                         Console.WriteLine();
214                         
215                 }
216
217                 private void ShowUsage(string errorMessage)
218                 {
219                         Console.WriteLine("ERROR: " + errorMessage.TrimEnd());
220                         ShowUsage();
221                 }
222
223                 internal WhatToDoNext DoUsage()
224                 {
225                         ShowUsage();
226                         return WhatToDoNext.AbandonProgram;
227                 }
228
229                 internal WhatToDoNext DoAbout()
230                 {
231                         ShowAbout();
232                         return WhatToDoNext.GoAhead;
233                 }
234
235                 internal WhatToDoNext DoHelp()
236                 {
237                         ShowHelp();
238                         return WhatToDoNext.AbandonProgram;
239                 }
240
241                 #endregion
242
243                 #region Arguments Processing
244
245                 public string[] NormalizeArgs(string[] args)
246                 {
247                         bool ParsingOptions = true;
248                         ArrayList result = new ArrayList();
249
250                         foreach(string arg in args)
251                         {
252                                 if (ParsingOptions)
253                                 {
254                                         if (endOptionProcessingWithDoubleDash && (arg == "--"))
255                                         {
256                                                 ParsingOptions = false;
257                                                 continue;
258                                         }
259
260                                         if ((parsingMode & OptionsParsingMode.Windows) > 0)
261                                         {
262                                                 if ((arg.Length == 2) && (arg[0] == '/')) // Windows options only come in this fashion
263                                                 {
264                                                         result.Add("-" + arg[1]); // translate to Linux style
265                                                         continue;
266                                                 }
267                                         }
268
269                                         if ((parsingMode & OptionsParsingMode.Linux) > 0)
270                                         {
271                                                 if ((arg[0] == '-') && (arg[1] != '-'))
272                                                 {
273                                                         foreach(char c in arg.Substring(1)) // many single-letter options
274                                                                 result.Add("-" + c); // expand into individualized options
275                                                         continue;
276                                                 }
277
278                                                 if (arg.StartsWith("--"))
279                                                 {
280                                                         result.AddRange(arg.Split('='));  // put in the same form of one-letter options with a parameter
281                                                         continue;
282                                                 }
283                                         }
284                                 }
285                                 else
286                                 {
287                                         argumentsTail.Add(arg);
288                                         continue;
289                                 }
290
291                                 // if nothing else matches then it get here
292                                 result.Add(arg);
293                         }
294
295                         return (string[])result.ToArray(typeof(string));
296                 }
297
298                 public string[] ProcessArgs(string[] args)
299                 {
300                         string arg;
301                         string nextArg;
302                         bool OptionWasProcessed;
303
304                         list.Sort();
305
306                         args = NormalizeArgs(args);
307
308                         try
309                         {
310                                 int argc = args.Length;
311                                 for(int i = 0; i < argc; i++)
312                                 {
313                                         arg =  args[i];
314                                         if (i+1 < argc)
315                                         {
316                                                 nextArg = args[i+1];
317                                                 if (nextArg.StartsWith("-"))
318                                                         nextArg = null;
319                                         }
320                                         else
321                                                 nextArg = null;
322
323                                         OptionWasProcessed = false;
324
325                                         if (arg.StartsWith("-"))
326                                         {
327                                                 foreach(OptionDetails option in list)
328                                                 {
329                                                         if (option.ProcessArgument(arg, nextArg))
330                                                         {
331                                                                 OptionWasProcessed = true;
332                                                                 if (nextArg != null)
333                                                                         i++;
334                                                                 break;
335                                                         }
336                                                 }
337                                         }
338
339                                         if (!OptionWasProcessed)
340                                                 arguments.Add(arg); 
341                                 }
342
343                                 foreach(OptionDetails option in list)
344                                         option.TransferValues(); 
345
346                                 foreach(string argument in argumentsTail)
347                                         arguments.Add(argument);
348
349                                 if (argumentProcessor == null)
350                                         return (string[])arguments.ToArray(typeof(string));
351                         
352                                 foreach(string argument in arguments)
353                                         argumentProcessor.Invoke(optionBundle, new object[] { argument });  
354                         }
355                         catch (Exception ex)
356                         {
357                                 ShowUsage(ex.Message);
358                                 System.Environment.Exit(1);
359                         }
360
361                         return null;
362                 }
363                 
364                 #endregion
365
366         }
367 }