[Mono-dev] Program Option Parsing Library

Jonathan Pryor jonpryor at vt.edu
Fri Jan 11 22:12:48 EST 2008


On Fri, 2008-01-11 at 14:06 -0800, Jay Logue wrote:
> Jonathan Pryor wrote: 
> > I guess I should create a new delegate type:
> > 
> > 	delegate string OptionLocalizer (string format, string[] args);
> > 
> > Other possible type names appreciated (and I don't like Rafael's
> > TranslateIt name. ;-)
> >   
> I like this approach.  Would it be used for option descriptions as
> well?  

Yes.  This is done in the next drop (attached).  The conversion delegate
is provided as an optional constructor parameter; if not specified, it
defaults to string.Format().

> > This is also a good idea, and suffers the same problem as providing the
> > actual option used: the callback doesn't have that information, and thus
> > can't provide it in the OptionException.
> >   
> I faced this exact issue in a configuration system I wrote, except
> there the problem was exacerbated by the need to have file and line
> information in the exception (plus it was based on .NET 1.1, so no
> anon delegates to make things prettier). I  solved it by requiring the
> use of a context object.
> 
> How about this.  Create a delegate that takes the necessary context
> information, e.g.:
> 
>     delegate void OptionAction<T>(T v, string optionName);
> 
> and use this as the argument to the workhorse Add<T> method:
> 
>     public Options Add<T> (string options, string description,
> OptionAction<T> action)
> 
> then create an overloaded Add<T> that accepts an Action<T> and calls
> the workhorse method:
> 
>     public Options Add<T>(string options, string description,
> Action<T> action)
>     {
>         return Add(options, description, delegate(T v, string
> optionName) { action(v); });
>     }
> 
> That way the user can make use of the context when they want too, but
> the type conversion code has access to it all the time.

I opted for a different approach, as the one you described would require
an additional parameter for each additional piece of context
information.  (I can't think of too many now, but no sense not being
more future-resilient if we can be.)

So the universal Action<string> delegate is now an
Action<string,OptionContext> delegate, and the generic delegate is
Action<T,OptionContext>.  OptionContext has properties for OptionName,
OptionIndex, and OptionValue (which is technically redundant as the
value is *also* provided as the first argument, and I don't care).

I opted for a two-argument form of Action`2 as this keeps the simple
case simple; usually you won't need OptionContext, so if everything were
an Action<OptionContext> we'd need:

	string s;
	var p = new Options () {
	  { "s=", v => s = v.OptionValue },
	};

and I thought all the (required) `.OptionValue's everywhere would just
be unnecessary visual noise that should be avoided.

> > Foo isn't a stand-in for the real type, it *is* the real type.  Just as
> > `int' is the real type, not System.ComponentModel.Int32Converter (the
> > actual type that does the string->int conversion).
>
> What about the case where you want an option that accepts standard
> binary abbreviations, e.g. 1K for 1024?  The type of the value is
> clearly an integer, but I need a custom parser to handle the syntax.

Options isn't the be-all end-all parser; it's intended to be flexible,
so use the flexibility it has: stick to strings. :-)

	int n = 0;
	var p = new Options () {
	  { "n=", v => n = SpecialConversionRoutine (v) },
	};

SpecialConversionRoutine(string) can handle the 1K parsing.
TypeConverter support is ~trivial and makes for a nice syntax (you ask
for an int, you get an int); what you're suggesting is extra complexity
for what I consider minimal gain, especially since there's a simple
alternate solution.

> That said, if you solve the problem of user thrown exceptions having
> access to the parsing context, then I can just handle this myself in
> the Action code.

Indeed:
	var p = new Options () {
	  { "n=", (v, c) => { Console.WriteLine (c.OptionName); } },
	};

One final note: due to the above Action change, I'm using Action`2,
which is specific to .NET 3.5.  Localization is via Func`3, which is
specific to .NET 3.5.  So how to maintain .NET 2.0 support?

	#if !LINQ
		public delegate TResult Func<T1,T2,TResult>(T1 a, T2 b);
		public delegate void Action<T1,T2>(T1 a, T2 b);
	#endif

If LINQ is defined, we use the .NET 3.5 types; if it isn't defined, we
assume .NET 2.0 and declare a local version of those types.

What this currently means is that you can't stick this into a
stand-alone assembly and link against it; you should instead compile it
into your assembly and set the #defines appropriately.

Maybe this isn't the best approach, maybe it is; I'm not sure.

If LINQ is defined you also get a (for fun) Options.Parse()
implementation that uses LINQ syntax.  It's 11 lines shorter, and has so
many nested ternary operators I'm not sure it's more readable.  But it's
fun. :-)

 - Jon

-------------- next part --------------
A non-text attachment was scrubbed...
Name: Options.cs
Type: text/x-csharp
Size: 24121 bytes
Desc: not available
Url : http://lists.ximian.com/pipermail/mono-devel-list/attachments/20080111/82d0d2d7/attachment.bin 


More information about the Mono-devel-list mailing list