4 // Copyright (C) 2005 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
28 using System
.Collections
;
29 using System
.Reflection
;
32 namespace CommandLineFu
{
34 [AttributeUsage (AttributeTargets
.Field
)]
35 public class OptionAttribute
: Attribute
{
37 public string LongName
;
38 public string Description
= "(no description)";
39 public string ArgDescription
= "arg";
42 internal class Processor
{
46 public Processor (object target_object
)
48 this.target_object
= target_object
;
51 ///////////////////////////////////////////////////////////////////
55 public FieldInfo Field
;
56 public OptionAttribute Option
;
58 private object target_object
;
59 private bool touched
= false;
62 public Pair (object target
)
64 target_object
= target
;
68 get { return Field.Name; }
71 public string OptionName
{
73 if (Option
.LongName
!= null)
74 return "--" + Option
.LongName
;
76 return "-" + Option
.Name
;
80 public string UsageName
{
82 StringBuilder builder
= new StringBuilder ();
83 if (Option
.LongName
!= null) {
84 builder
.Append ("--");
85 builder
.Append (Option
.LongName
);
87 if (Option
.Name
!= null)
88 builder
.Append (", ");
90 if (Option
.Name
!= null) {
92 builder
.Append (Option
.Name
);
94 if (Field
.FieldType
!= typeof (System
.Boolean
)) {
95 builder
.Append (" [");
96 builder
.Append (Option
.ArgDescription
);
99 return builder
.ToString ();
103 public bool Touched
{
104 get { return touched; }
107 public object Value
{
108 get { return Field.GetValue (target_object); }
111 // Return true if value was actually used
112 public bool Set (string value)
116 // Deal with bools first, since they are easy.
117 if (Field
.FieldType
== typeof (System
.Boolean
)) {
118 Field
.SetValue (target_object
, true);
122 object parsed_value
= null;
124 if (Field
.FieldType
== typeof (System
.String
)) {
126 parsed_value
= value;
128 } else if (Field
.FieldType
== typeof (System
.Int32
)) {
131 parsed_value
= System
.Int32
.Parse (value);
132 } catch (Exception ex
) {
133 // parsed_value will still be null, so the
134 // "Couldn't parse..." error will be displayed.
137 } else if (Field
.FieldType
== typeof (System
.Double
)) {
140 parsed_value
= System
.Double
.Parse (value);
141 } catch (Exception ex
) { }
144 if (parsed_value
!= null) {
145 Field
.SetValue (target_object
, parsed_value
);
147 Console
.WriteLine ("Couldn't parse '{0}' as {1}", value, Field
.FieldType
);
154 ArrayList all_pairs
= new ArrayList ();
155 Hashtable by_name
= new Hashtable ();
156 Hashtable by_long_name
= new Hashtable ();
158 public void AddOption (FieldInfo field
, OptionAttribute option
)
160 Pair pair
= new Pair (target_object
);
162 pair
.Option
= option
;
164 all_pairs
.Add (pair
);
166 if (option
.Name
!= null)
167 by_name
[option
.Name
] = pair
;
169 if (option
.LongName
!= null)
170 by_long_name
[option
.LongName
] = pair
;
173 private static bool IsOption (string arg
)
175 return arg
.StartsWith ("-") || arg
.StartsWith ("/");
178 private Pair
ParseOption (string arg
, out string next_arg
)
182 char [] separator_chars
= new char [] { '=', ':' }
;
183 int i
= arg
.IndexOfAny (separator_chars
);
185 next_arg
= arg
.Substring (i
+1);
186 arg
= arg
.Substring (0, i
);
189 string stripped_arg
= null;
190 if (arg
.StartsWith ("/")) {
191 stripped_arg
= arg
.Substring (1);
192 } else if (arg
.StartsWith ("-")) {
194 while (pos
< arg
.Length
&& arg
[pos
] == '-')
196 stripped_arg
= arg
.Substring (pos
);
200 pair
= by_long_name
[stripped_arg
] as Pair
;
202 pair
= by_name
[stripped_arg
] as Pair
;
207 ///////////////////////////////////////////////////////////////////
211 foreach (Pair pair
in all_pairs
) {
212 Console
.WriteLine ("DEBUG {0}: {1}={2} {3}",
216 pair
.Touched
? "" : "(default)");
220 ///////////////////////////////////////////////////////////////////
222 public void SpewVersion ()
224 Console
.WriteLine (CommandLine
.ProgramVersion
!= null ? CommandLine
.ProgramVersion
: "unknown");
227 public void SpewBanner ()
229 if (CommandLine
.ProgramName
== null)
231 Console
.Write (CommandLine
.ProgramName
);
232 if (CommandLine
.ProgramVersion
!= null) {
234 Console
.Write (CommandLine
.ProgramVersion
);
236 if (CommandLine
.ProgramDate
!= null) {
237 Console
.Write (" - ");
238 Console
.Write (CommandLine
.ProgramDate
);
240 Console
.WriteLine ();
242 if (CommandLine
.ProgramCopyright
!= null)
243 Console
.WriteLine (CommandLine
.ProgramCopyright
);
247 public void SpewOptionDocs ()
249 int max_usage_name_len
= 0;
251 // FIXME: This need better formatting, wrapping of
252 // description lines, etc.
253 // It should also be put in a sane order.
255 Console
.WriteLine ("Options:");
257 foreach (Pair pair
in all_pairs
) {
258 int len
= pair
.UsageName
.Length
;
259 if (len
> max_usage_name_len
)
260 max_usage_name_len
= len
;
263 foreach (Pair pair
in all_pairs
) {
264 StringBuilder builder
= new StringBuilder ();
265 string usage_name
= pair
.UsageName
;
266 builder
.Append (" ");
267 builder
.Append (usage_name
);
268 builder
.Append (' ', max_usage_name_len
- usage_name
.Length
);
269 builder
.Append (" ");
270 builder
.Append (pair
.Option
.Description
);
272 Console
.WriteLine (builder
.ToString ());
276 ///////////////////////////////////////////////////////////////////
278 private string [] TheRealWork (string [] args
)
280 ArrayList parameters
= new ArrayList ();
283 bool parameters_only
= false;
284 while (i
< args
.Length
) {
285 string arg
= args
[i
];
288 string next_arg
= null;
292 if (parameters_only
|| ! IsOption (arg
)) {
293 parameters
.Add (arg
);
294 } else if (arg
== "--") {
295 parameters_only
= true;
297 string attached_next_arg
= null;
298 Pair pair
= ParseOption (arg
, out attached_next_arg
);
301 Console
.WriteLine ("Ignoring unknown argument '{0}'", arg
);
302 } else if (attached_next_arg
!= null) {
303 if (! pair
.Set (attached_next_arg
)) {
304 // FIXME: If we didn't use the attached arg, something must be wrong.
305 // Throw an exception?
306 Console
.WriteLine ("FIXME: Didn't use attached arg '{0}' on {1}",
311 if (pair
.Set (next_arg
))
317 // If we ended prematurely, treat everything that is left
319 while (i
< args
.Length
)
320 parameters
.Add (args
[i
]);
322 // Convert the list of parameters to an array and return it.
323 string [] parameter_array
= new string [parameters
.Count
];
324 for (int j
= 0; j
< parameters
.Count
; ++j
)
325 parameter_array
[j
] = parameters
[j
] as string;
326 return parameter_array
;
329 public string [] Process (string [] args
)
331 foreach (string arg
in args
) {
332 // FIXME: These should be displayed in the banner information.
333 if (arg
== "--version") {
336 } else if (arg
== "--help") {
343 string [] parameters
= TheRealWork (args
);
345 if (CommandLine
.Debug
) {
347 for (int i
= 0; i
< parameters
.Length
; ++i
)
348 Console
.WriteLine ("DEBUG Param {0}: {1}", i
, parameters
[i
]);
355 public class CommandLine
{
357 static public bool Debug
= false;
359 static public string ProgramName
= null;
360 static public string ProgramVersion
= null;
361 static public string ProgramDate
= null;
362 static public string ProgramCopyright
= null;
363 static public string ProgramHomePage
= null;
365 static private Processor
BuildProcessor (Type type
, object obj
, BindingFlags flags
)
367 Processor proc
= new Processor (obj
);
369 flags
|= BindingFlags
.NonPublic
;
370 flags
|= BindingFlags
.Public
;
372 FieldInfo
[] field_info_array
= type
.GetFields (flags
);
373 foreach (FieldInfo fi
in field_info_array
) {
375 object [] attributes
= fi
.GetCustomAttributes (true);
376 foreach (object attr
in attributes
) {
377 OptionAttribute option_attr
= attr
as OptionAttribute
;
378 if (option_attr
!= null)
379 proc
.AddOption (fi
, option_attr
);
386 static public string [] Process (object obj
, string [] args
)
388 Processor proc
= BuildProcessor (obj
.GetType (), obj
, BindingFlags
.Instance
);
389 return proc
.Process (args
);
392 static public string [] Process (Type t
, string [] args
)
394 Processor proc
= BuildProcessor (t
, null, BindingFlags
.Static
);
395 return proc
.Process (args
);
403 class CommandLineFu_SampleCode
{
407 Description
="The Foo Option",
408 ArgDescription
="FOOARG")]
409 static private string foo
= "foo_default";
411 [Option (LongName
="bar",
412 Description
="The Bar Option")]
413 static private int bar
= 12345;
415 [Option (LongName
="baz",
416 Description
="The Baz Option")]
417 static private bool baz
= false;
420 Description
="As you might expect, the d option")]
421 static private double d
= 3.14159;
423 static void Main (string [] args
)
425 CommandLine
.Debug
= true;
426 CommandLine
.Process (typeof (CommandLineFu_SampleCode
), args
);