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);
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);
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);