1 // File: CommandLineOptions.cs
3 // This is a re-usable component to be used when you
4 // need to parse command-line options/parameters.
6 // Separates command line parameters from command line options.
7 // Uses reflection to populate member variables the derived class with the values
10 // An option can start with "-" or "--". On Windows systems, it can start with "/" as well.
12 // I define 3 types of "options":
13 // 1. Boolean options (yes/no values), e.g: /r to recurse
14 // 2. Value options, e.g: /loglevel=3
15 // 2. Parameters: standalone strings like file names
17 // An example to explain:
18 // csc /nologo /t:exe myfile.cs
26 // Please see a short description of the CommandLineOptions class
27 // at http://codeblast.com/~gert/dotnet/sells.html
29 // Gert Lombard (gert@codeblast.com)
30 // James Newkirk (jim@nunit.org)
35 using System
.Reflection
;
36 using System
.Collections
;
43 [AttributeUsage(AttributeTargets
.Field
)]
44 public class OptionAttribute
: Attribute
46 protected object optValue
;
47 protected string optName
;
48 protected string description
;
52 get { return optName; }
53 set { optName = value; }
58 get { return optValue; }
59 set { optValue = value; }
62 public string Description
64 get { return description; }
65 set { description = value; }
70 // The CommandLineOptions members
73 public abstract class CommandLineOptions
75 protected ArrayList parameters
;
76 protected bool isInvalid
= false;
78 private int optionCount
;
79 private ArrayList invalidArguments
= new ArrayList();
80 private bool allowForwardSlash
;
82 public CommandLineOptions( string[] args
)
83 : this( System
.IO
.Path
.DirectorySeparatorChar
!= '/', args
) {}
85 public CommandLineOptions( bool allowForwardSlash
, string[] args
)
87 this.allowForwardSlash
= allowForwardSlash
;
88 optionCount
= Init( args
);
91 public IList InvalidArguments
93 get { return invalidArguments; }
100 return ParameterCount
== 0 && optionCount
== 0;
104 public bool AllowForwardSlash
106 get { return allowForwardSlash; }
109 public int Init(params string[] args
)
113 while (n
< args
.Length
)
115 int pos
= IsOption(args
[n
]);
119 if (GetOption(args
, ref n
, pos
))
122 InvalidOption(args
[Math
.Min(n
, args
.Length
-1)]);
126 if (parameters
== null) parameters
= new ArrayList();
127 parameters
.Add(args
[n
]);
128 if ( !IsValidParameter(args
[n
]) )
129 InvalidOption( args
[n
] );
136 // An option starts with "/", "-" or "--":
137 protected virtual int IsOption(string opt
)
144 else if (opt
.Length
> 2)
146 c
= opt
.ToCharArray(0, 3);
147 if (c
[0] == '-' && c
[1] == '-' && IsOptionNameChar(c
[2])) return 2;
151 c
= opt
.ToCharArray(0, 2);
153 if ((c
[0] == '-' || c
[0] == '/' && AllowForwardSlash
) && IsOptionNameChar(c
[1])) return 1;
157 protected virtual bool IsOptionNameChar(char c
)
159 return Char
.IsLetterOrDigit(c
) || c
== '?';
162 protected virtual void InvalidOption(string name
)
164 invalidArguments
.Add( name
);
168 protected virtual bool IsValidParameter(string param
)
173 protected virtual bool MatchShortName(FieldInfo field
, string name
)
175 object[] atts
= field
.GetCustomAttributes(typeof(OptionAttribute
), true);
176 foreach (OptionAttribute att
in atts
)
178 if (string.Compare(att
.Short
, name
, true) == 0) return true;
183 protected virtual FieldInfo
GetMemberField(string name
)
185 Type t
= this.GetType();
186 FieldInfo
[] fields
= t
.GetFields(BindingFlags
.Instance
|BindingFlags
.Public
);
187 foreach (FieldInfo field
in fields
)
189 if (string.Compare(field
.Name
, name
, true) == 0) return field
;
190 if (MatchShortName(field
, name
)) return field
;
195 protected virtual object GetOptionValue(FieldInfo field
)
197 object[] atts
= field
.GetCustomAttributes(typeof(OptionAttribute
), true);
200 OptionAttribute att
= (OptionAttribute
)atts
[0];
206 protected virtual bool GetOption(string[] args
, ref int index
, int pos
)
210 object cmdLineVal
= null;
211 string opt
= args
[index
].Substring(pos
, args
[index
].Length
-pos
);
212 SplitOptionAndValue(ref opt
, ref cmdLineVal
);
213 FieldInfo field
= GetMemberField(opt
);
216 object value = GetOptionValue(field
);
219 if (field
.FieldType
== typeof(bool))
220 value = true; // default for bool values is true
221 else if(field
.FieldType
== typeof(string))
223 value = cmdLineVal
!= null ? cmdLineVal
: args
[++index
];
224 field
.SetValue(this, Convert
.ChangeType(value, field
.FieldType
));
225 string stringValue
= (string)value;
226 if(stringValue
== null || stringValue
.Length
== 0) return false;
229 else if(field
.FieldType
.IsEnum
)
230 value = Enum
.Parse( field
.FieldType
, (string)cmdLineVal
, true );
232 value = cmdLineVal
!= null ? cmdLineVal
: args
[++index
];
234 field
.SetValue(this, Convert
.ChangeType(value, field
.FieldType
));
240 // Ignore exceptions like type conversion errors.
245 protected virtual void SplitOptionAndValue(ref string opt
, ref object val
)
247 // Look for ":" or "=" separator in the option:
248 int pos
= opt
.IndexOfAny( new char[] { ':', '=' }
);
251 val
= opt
.Substring(pos
+1);
252 opt
= opt
.Substring(0, pos
);
255 // Parameter accessor:
256 public string this[int index
]
260 if (parameters
!= null) return (string)parameters
[index
];
265 public ArrayList Parameters
267 get { return parameters; }
270 public int ParameterCount
274 return parameters
== null ? 0 : parameters
.Count
;
278 public virtual void Help()
280 Console
.WriteLine(GetHelpText());
283 public virtual string GetHelpText()
285 StringBuilder helpText
= new StringBuilder();
287 Type t
= this.GetType();
288 FieldInfo
[] fields
= t
.GetFields(BindingFlags
.Instance
|BindingFlags
.Public
);
289 char optChar
= allowForwardSlash
? '/' : '-';
290 foreach (FieldInfo field
in fields
)
292 object[] atts
= field
.GetCustomAttributes(typeof(OptionAttribute
), true);
295 OptionAttribute att
= (OptionAttribute
)atts
[0];
296 if (att
.Description
!= null)
299 if (att
.Value
== null)
301 if (field
.FieldType
== typeof(float)) valType
= "=FLOAT";
302 else if (field
.FieldType
== typeof(string)) valType
= "=STR";
303 else if (field
.FieldType
!= typeof(bool)) valType
= "=X";
306 helpText
.AppendFormat("{0}{1,-20}\t{2}", optChar
, field
.Name
+valType
, att
.Description
);
307 if (att
.Short
!= null)
308 helpText
.AppendFormat(" (Short format: {0}{1}{2})", optChar
, att
.Short
, valType
);
309 helpText
.Append( Environment
.NewLine
);
313 return helpText
.ToString();