2002-12-28 Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
[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[] ExpandResponseFiles(string[] args)
246                 {
247                         ArrayList result = new ArrayList();
248
249                         foreach(string arg in args)
250                         {
251                                 if (arg.StartsWith("@"))
252                                 {
253                                         try 
254                                         {
255                                                 StreamReader tr = new StreamReader(arg.Substring(1));
256                                                 string line;
257                                                 while ((line = tr.ReadLine()) != null)
258                                                 {
259                                                         result.AddRange(line.Split());
260                                                 }
261                                                 tr.Close(); 
262                                         }
263                                         catch (FileNotFoundException exception)
264                                         {
265                                                 Console.WriteLine("Could not find response file: " + arg.Substring(1));
266                                                 continue;
267                                         }
268                                         catch (Exception exception)
269                                         {
270                                                 Console.WriteLine("Error trying to read response file: " + arg.Substring(1));
271                                                 Console.WriteLine(exception.Message);
272                                                 continue;
273                                         }
274                                 }
275                                 else
276                                         result.Add(arg);
277                         }
278
279                         return (string[])result.ToArray(typeof(string));
280                 }
281
282                 public string[] NormalizeArgs(string[] args)
283                 {
284                         bool ParsingOptions = true;
285                         ArrayList result = new ArrayList();
286
287                         foreach(string arg in ExpandResponseFiles(args))
288                         {
289                                 if (arg.Length > 0)
290                                 {
291                                         if (ParsingOptions)
292                                         {
293                                                 if (endOptionProcessingWithDoubleDash && (arg == "--"))
294                                                 {
295                                                         ParsingOptions = false;
296                                                         continue;
297                                                 }
298
299                                                 if ((parsingMode & OptionsParsingMode.Windows) > 0)
300                                                 {
301                                                         if ((arg.Length == 2) && (arg[0] == '/')) // Windows options only come in this fashion
302                                                         {
303                                                                 result.Add("-" + arg[1]); // translate to Linux style
304                                                                 continue;
305                                                         }
306                                                 }
307
308                                                 if ((parsingMode & OptionsParsingMode.Linux) > 0)
309                                                 {
310                                                         if ((arg[0] == '-') && (arg[1] != '-'))
311                                                         {
312                                                                 foreach(char c in arg.Substring(1)) // many single-letter options
313                                                                         result.Add("-" + c); // expand into individualized options
314                                                                 continue;
315                                                         }
316
317                                                         if (arg.StartsWith("--"))
318                                                         {
319                                                                 result.AddRange(arg.Split('='));  // put in the same form of one-letter options with a parameter
320                                                                 continue;
321                                                         }
322                                                 }
323                                         }
324                                         else
325                                         {
326                                                 argumentsTail.Add(arg);
327                                                 continue;
328                                         }
329
330                                         // if nothing else matches then it get here
331                                         result.Add(arg);
332                                 }
333                         }
334
335                         return (string[])result.ToArray(typeof(string));
336                 }
337
338                 public string[] ProcessArgs(string[] args)
339                 {
340                         string arg;
341                         string nextArg;
342                         bool OptionWasProcessed;
343
344                         list.Sort();
345
346                         args = NormalizeArgs(args);
347
348                         try
349                         {
350                                 int argc = args.Length;
351                                 for(int i = 0; i < argc; i++)
352                                 {
353                                         arg =  args[i];
354                                         if (i+1 < argc)
355                                         {
356                                                 nextArg = args[i+1];
357                                                 if (nextArg.StartsWith("-"))
358                                                         nextArg = null;
359                                         }
360                                         else
361                                                 nextArg = null;
362
363                                         OptionWasProcessed = false;
364
365                                         if (arg.StartsWith("-"))
366                                         {
367                                                 foreach(OptionDetails option in list)
368                                                 {
369                                                         if (option.ProcessArgument(arg, nextArg))
370                                                         {
371                                                                 OptionWasProcessed = true;
372                                                                 if (nextArg != null)
373                                                                         i++;
374                                                                 break;
375                                                         }
376                                                 }
377                                         }
378
379                                         if (!OptionWasProcessed)
380                                                 arguments.Add(arg); 
381                                 }
382
383                                 foreach(OptionDetails option in list)
384                                         option.TransferValues(); 
385
386                                 foreach(string argument in argumentsTail)
387                                         arguments.Add(argument);
388
389                                 if (argumentProcessor == null)
390                                         return (string[])arguments.ToArray(typeof(string));
391                         
392                                 foreach(string argument in arguments)
393                                         argumentProcessor.Invoke(optionBundle, new object[] { argument });  
394                         }
395                         catch (Exception ex)
396                         {
397                                 ShowUsage(ex.Message);
398                                 System.Environment.Exit(1);
399                         }
400
401                         return null;
402                 }
403                 
404                 #endregion
405
406         }
407 }