4 // Author: Rafael Teixeira (rafaelteixeirabr@hotmail.com)
6 // (C) 2002 Rafael Teixeira
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
33 using System.Reflection;
36 namespace Mono.GetOptions
42 public class OptionList
45 private Options optionBundle = null;
46 private OptionsParsingMode parsingMode;
47 private bool breakSingleDashManyLettersIntoManyOptions;
48 private bool endOptionProcessingWithDoubleDash;
49 public ErrorReporter ReportError;
51 private string appExeName;
52 private string appVersion;
54 private string appTitle = "Add a [assembly: AssemblyTitle(\"Here goes the application name\")] to your assembly";
55 private string appCopyright = "Add a [assembly: AssemblyCopyright(\"(c)200n Here goes the copyright holder name\")] to your assembly";
56 private string appDescription = "Add a [assembly: AssemblyDescription(\"Here goes the short description\")] to your assembly";
57 private string appAboutDetails = "Add a [assembly: Mono.About(\"Here goes the short about details\")] to your assembly";
58 private string appUsageComplement = "Add a [assembly: Mono.UsageComplement(\"Here goes the usage clause complement\")] to your assembly";
59 private string appAdditionalInfo = null;
60 private string appReportBugsTo = null;
61 private string[] appAuthors;
63 private ArrayList list = new ArrayList();
64 private ArrayList arguments = new ArrayList();
65 private ArrayList argumentsTail = new ArrayList();
66 private MethodInfo argumentProcessor = null;
68 private bool HasSecondLevelHelp = false;
70 internal bool MaybeAnOption(string arg)
72 return ((parsingMode & OptionsParsingMode.Windows) > 0 && arg[0] == '/') ||
73 ((parsingMode & OptionsParsingMode.Linux) > 0 && arg[0] == '-');
79 return "Usage: " + appExeName + " [options] " + appUsageComplement;
83 public string AboutDetails
86 return appAboutDetails;
90 #region Assembly Attributes
94 private object[] GetAssemblyAttributes(Type type)
96 return entry.GetCustomAttributes(type, false);
99 private string[] GetAssemblyAttributeStrings(Type type)
101 object[] result = GetAssemblyAttributes(type);
103 if ((result == null) || (result.Length == 0))
104 return new string[0];
107 string[] var = new string[result.Length];
109 foreach(object o in result)
110 var[i++] = o.ToString();
115 private void GetAssemblyAttributeValue(Type type, string propertyName, ref string var)
117 object[] result = GetAssemblyAttributes(type);
119 if ((result != null) && (result.Length > 0))
120 var = (string)type.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance, null, result[0], new object [] {}); ;
123 private void GetAssemblyAttributeValue(Type type, ref string var)
125 object[] result = GetAssemblyAttributes(type);
127 if ((result != null) && (result.Length > 0))
128 var = result[0].ToString();
131 private void ExtractEntryAssemblyInfo(Type optionsType)
133 entry = optionsType.Assembly;
134 if (entry == this.GetType().Assembly) {
135 entry = Assembly.GetEntryAssembly();
138 appExeName = entry.GetName().Name;
139 appVersion = entry.GetName().Version.ToString();
140 GetAssemblyAttributeValue(typeof(AssemblyTitleAttribute), "Title", ref appTitle);
141 GetAssemblyAttributeValue(typeof(AssemblyCopyrightAttribute), "Copyright", ref appCopyright);
142 GetAssemblyAttributeValue(typeof(AssemblyDescriptionAttribute), "Description", ref appDescription);
143 GetAssemblyAttributeValue(typeof(Mono.AboutAttribute), ref appAboutDetails);
144 GetAssemblyAttributeValue(typeof(Mono.UsageComplementAttribute), ref appUsageComplement);
145 GetAssemblyAttributeValue(typeof(Mono.AdditionalInfoAttribute), ref appAdditionalInfo);
146 GetAssemblyAttributeValue(typeof(Mono.ReportBugsToAttribute), ref appReportBugsTo);
147 appAuthors = GetAssemblyAttributeStrings(typeof(AuthorAttribute));
148 if (appAuthors.Length == 0) {
149 appAuthors = new String[1];
150 appAuthors[0] = "Add one or more [assembly: Mono.Author(\"Here goes the author name\")] to your assembly";
158 private void AddArgumentProcessor(MemberInfo memberInfo)
160 if (argumentProcessor != null)
161 throw new NotSupportedException("More than one argument processor method found");
163 if ((memberInfo.MemberType == MemberTypes.Method && memberInfo is MethodInfo)) {
164 if (((MethodInfo)memberInfo).ReturnType.FullName != typeof(void).FullName)
165 throw new NotSupportedException("Argument processor method must return 'void'");
167 ParameterInfo[] parameters = ((MethodInfo)memberInfo).GetParameters();
168 if ((parameters == null) || (parameters.Length != 1) || (parameters[0].ParameterType.FullName != typeof(string).FullName))
169 throw new NotSupportedException("Argument processor method must have a string parameter");
171 argumentProcessor = (MethodInfo)memberInfo;
174 throw new NotSupportedException("Argument processor marked member isn't a method");
177 public OptionList(Options optionBundle)
179 if (optionBundle == null)
180 throw new ArgumentNullException("optionBundle");
182 Type optionsType = optionBundle.GetType();
183 this.optionBundle = optionBundle;
184 this.parsingMode = optionBundle.ParsingMode ;
185 this.breakSingleDashManyLettersIntoManyOptions = optionBundle.BreakSingleDashManyLettersIntoManyOptions;
186 this.endOptionProcessingWithDoubleDash = optionBundle.EndOptionProcessingWithDoubleDash;
187 this.ReportError = optionBundle.ReportError;
189 ExtractEntryAssemblyInfo(optionsType);
191 foreach(MemberInfo mi in optionsType.GetMembers()) {
192 object[] attribs = mi.GetCustomAttributes(typeof(KillOptionAttribute), true);
193 if (attribs == null || attribs.Length == 0) {
194 attribs = mi.GetCustomAttributes(typeof(OptionAttribute), true);
195 if (attribs != null && attribs.Length > 0) {
196 OptionDetails option = new OptionDetails(mi, (OptionAttribute)attribs[0], optionBundle);
198 HasSecondLevelHelp = HasSecondLevelHelp || option.SecondLevelHelp;
199 } else if (mi.DeclaringType == mi.ReflectedType) { // not inherited
200 attribs = mi.GetCustomAttributes(typeof(ArgumentProcessorAttribute), true);
201 if (attribs != null && attribs.Length > 0)
202 AddArgumentProcessor(mi);
207 if (argumentProcessor == null) // try to find an inherited one
208 foreach(MemberInfo mi in optionsType.GetMembers())
209 if (mi.DeclaringType != mi.ReflectedType) { // inherited
210 object[] attribs = mi.GetCustomAttributes(typeof(ArgumentProcessorAttribute), true);
211 if (attribs != null && attribs.Length > 0)
212 AddArgumentProcessor(mi);
218 #region Prebuilt Options
220 private bool bannerAlreadyShown = false;
222 internal string AdditionalBannerInfo;
224 public void ShowBanner()
226 if (!bannerAlreadyShown) {
227 Console.WriteLine(appTitle + " " + appVersion + " - " + appCopyright);
228 if (AdditionalBannerInfo != null)
229 Console.WriteLine(AdditionalBannerInfo);
231 bannerAlreadyShown = true;
234 private void ShowTitleLines()
237 Console.WriteLine(appDescription);
241 private void ShowAbout()
244 Console.WriteLine(appAboutDetails);
245 Console.Write("Authors: ");
246 Console.WriteLine(string.Join(", ", appAuthors));
249 private void ShowHelp(bool showSecondLevelHelp)
252 Console.WriteLine(Usage);
253 Console.WriteLine("Options:");
254 ArrayList lines = new ArrayList(list.Count);
256 foreach (OptionDetails option in list)
257 if (option.SecondLevelHelp == showSecondLevelHelp) {
258 string[] optionLines = option.ToString().Split('\n');
259 foreach(string line in optionLines) {
260 int pos = line.IndexOf('\t');
267 foreach (string line in lines) {
268 string[] parts = line.Split('\t');
269 Console.Write(parts[0].PadRight(tabSize));
270 Console.WriteLine(parts[1]);
271 if (parts.Length > 2) {
272 string spacer = new string(' ', tabSize);
273 for(int i = 2; i < parts.Length; i++) {
274 Console.Write(spacer);
275 Console.WriteLine(parts[i]);
279 if (appAdditionalInfo != null)
280 Console.WriteLine("\n{0}", appAdditionalInfo);
281 if (appReportBugsTo != null)
282 Console.WriteLine("\nPlease report bugs {0} <{1}>", (appReportBugsTo.IndexOf('@')>0)?"to":"at" , appReportBugsTo);
286 private void ShowUsage()
288 Console.WriteLine(Usage);
289 Console.Write("Short Options: ");
290 foreach (OptionDetails option in list)
291 Console.Write(option.ShortForm.Trim());
296 internal WhatToDoNext DoUsage()
299 return WhatToDoNext.AbandonProgram;
302 internal WhatToDoNext DoAbout()
305 return WhatToDoNext.AbandonProgram;
308 internal WhatToDoNext DoHelp()
311 return WhatToDoNext.AbandonProgram;
314 internal WhatToDoNext DoHelp2()
317 return WhatToDoNext.AbandonProgram;
322 #region Response File Expansion
324 private void processResponseFileLine(string line, ArrayList result, StringBuilder sb)
327 for (int i = 0; i < t; i++) {
329 if (c == '"' || c == '\'') {
331 for (i++; i < t; i++) {
337 } else if (c == ' ') {
339 result.Add(sb.ToString());
347 result.Add(sb.ToString());
352 private void processResponseFile(string filename, ArrayList result)
354 StringBuilder sb = new StringBuilder();
357 using (StreamReader responseFile = new StreamReader(filename)) {
358 while ((line = responseFile.ReadLine()) != null)
359 processResponseFileLine(line, result, sb);
360 responseFile.Close ();
362 } catch (FileNotFoundException) {
363 ReportError(2011, "Unable to find response file '" + filename + "'");
364 } catch (Exception exception) {
365 ReportError(2011, "Unable to open response file '" + filename + "'. " + exception.Message);
369 private ArrayList ExpandResponseFiles(string[] args)
371 ArrayList result = new ArrayList();
372 foreach(string arg in args)
373 if (arg.StartsWith("@"))
374 processResponseFile(arg.Substring(1), result);
382 #region Arguments Processing
385 private static int IndexOfAny(string where, params char[] what)
387 return where.IndexOfAny(what);
390 private string[] NormalizeArgs(string[] args)
392 bool ParsingOptions = true;
393 ArrayList result = new ArrayList();
395 foreach(string arg in ExpandResponseFiles(args)) {
396 if (arg.Length > 0) {
397 if (ParsingOptions) {
398 if (endOptionProcessingWithDoubleDash && (arg == "--")) {
399 ParsingOptions = false;
403 if ((parsingMode & OptionsParsingMode.Linux) > 0 &&
404 arg[0] == '-' && arg.Length > 1 && arg[1] != '-' &&
405 breakSingleDashManyLettersIntoManyOptions) {
406 foreach(char c in arg.Substring(1)) // many single-letter options
407 result.Add("-" + c); // expand into individualized options
411 if (MaybeAnOption(arg)) {
412 int pos = IndexOfAny(arg, ':', '=');
417 result.Add(arg.Substring(0, pos));
418 result.Add(arg.Substring(pos+1));
423 argumentsTail.Add(arg);
427 // if nothing else matches then it get here
432 return (string[])result.ToArray(typeof(string));
435 public string[] ProcessArgs(string[] args)
439 bool OptionWasProcessed;
443 OptionDetails.LinkAlternatesInsideList(list);
445 args = NormalizeArgs(args);
448 int argc = args.Length;
449 for (int i = 0; i < argc; i++) {
456 OptionWasProcessed = false;
458 if (arg.Length > 1 && (arg.StartsWith("-") || arg.StartsWith("/"))) {
459 foreach(OptionDetails option in list) {
460 OptionProcessingResult result = option.ProcessArgument(arg, nextArg);
461 if (result != OptionProcessingResult.NotThisOption) {
462 OptionWasProcessed = true;
463 if (result == OptionProcessingResult.OptionConsumedParameter)
470 if (!OptionWasProcessed)
471 ProcessNonOption(arg);
474 foreach(OptionDetails option in list)
475 option.TransferValues();
477 foreach(string argument in argumentsTail)
478 ProcessNonOption(argument);
480 return (string[])arguments.ToArray(typeof(string));
482 } catch (Exception ex) {
483 System.Console.WriteLine(ex.ToString());
484 System.Environment.Exit(1);
490 private void ProcessNonOption(string argument)
492 if (optionBundle.VerboseParsingOfOptions)
493 Console.WriteLine("argument [" + argument + "]");
494 if (argumentProcessor == null)
495 arguments.Add(argument);
497 argumentProcessor.Invoke(optionBundle, new object[] { argument });