Tuesday, January 11, 2011

Using F# 2.0 Powerpack ArgParser from C# -- (ii)

Having resolved the issue of why the "--" separator wasn't triggering (PowerShell was swallowing it before it got to my code) that was stumping me in the previous post, I put together a little fluent wrapper to make this a little easier to consume from C#. The code that isolates the F# types looks like this:

namespace Tinesware.ArgParser
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.FSharp.Core;
using Microsoft.FSharp.Text;
/// <summary>
/// Assembles the various command line options and then calls the parser
/// </summary>
public class Options
{
/// <summary>
/// Option values
/// </summary>
private List<ArgInfo> options = new List<ArgInfo>();
/// <summary>
/// Callbacks for boolean values
/// </summary>
private List<Flag> flags = new List<Flag>();
/// <summary>
/// Initializes a new instance of the Options class.
/// </summary>
public Options()
{
}
/// <summary>
/// An option with no argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Unit(string option, Action callback, string usage)
{
var type = ArgType.Unit(FuncConvert.ToFSharpFunc<Unit>(x => callback()));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An option with a string argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options String(string option, Action<string> callback, string usage)
{
var type = ArgType.String(FuncConvert.ToFSharpFunc<string>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An option with an integer argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Int(string option, Action<int> callback, string usage)
{
var type = ArgType.Int(FuncConvert.ToFSharpFunc<int>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An option with a double precision argument
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for this option</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Float(string option, Action<double> callback, string usage)
{
var type = ArgType.Float(FuncConvert.ToFSharpFunc<double>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An end-of-arguments argument, usually "--"
/// </summary>
/// <param name="option">The option string, usually "--"</param>
/// <param name="callback">The callback for the remaining arguments</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Rest(string option, Action<string> callback, string usage)
{
var type = ArgType.Rest(FuncConvert.ToFSharpFunc<string>(callback));
this.options.Add(new ArgInfo(option, type, usage));
return this;
}
/// <summary>
/// An argument indicating true
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for the remaining arguments</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Set(string option, Action<bool> callback, string usage)
{
var flag = new Flag
{
Reference = new FSharpRef<bool>(false),
Callback = callback
};
var type = ArgType.Set(flag.Reference);
this.options.Add(new ArgInfo(option, type, usage));
this.flags.Add(flag);
return this;
}
/// <summary>
/// An argument indicating false
/// </summary>
/// <param name="option">The option string</param>
/// <param name="callback">The callback for the remaining arguments</param>
/// <param name="usage">Usage comment</param>
/// <returns>This object (fluent interface)</returns>
public Options Clear(string option, Action<bool> callback, string usage)
{
var flag = new Flag
{
Reference = new FSharpRef<bool>(true),
Callback = callback
};
var type = ArgType.Set(flag.Reference);
this.options.Add(new ArgInfo(option, type, usage));
this.flags.Add(flag);
return this;
}
/// <summary>
/// Parse the environment command line with no default handler or usage comment
/// </summary>
public void Parse()
{
this.Parse(null, null);
}
/// <summary>
/// Parse the environment command line with no default handler
/// </summary>
/// <param name="usage">usage comment</param>
public void Parse(string usage)
{
this.Parse(null, usage);
}
/// <summary>
/// Parse the environment command line with no usage comment
/// </summary>
/// <param name="defaultHandler">default handler for unmatched arguments</param>
public void Parse(Action<string> defaultHandler)
{
this.Parse(defaultHandler, null);
}
/// <summary>
/// Parse the environment command line
/// </summary>
/// <param name="defaultHandler">default handler for unmatched arguments</param>
/// <param name="usage">usage comment</param>
public void Parse(Action<string> defaultHandler, string usage)
{
var temp = usage ?? string.Empty;
var theUsage = string.IsNullOrEmpty(temp.Trim()) ?
FSharpOption<string>.None :
new FSharpOption<string>(usage);
var theDefault = defaultHandler == null ?
FSharpOption<FSharpFunc<string, Unit>>.None :
new FSharpOption<FSharpFunc<string, Unit>>(
FuncConvert.ToFSharpFunc<string>(defaultHandler));
ArgParser.Parse(this.options, theDefault, theUsage);
this.flags.ForEach(x => x.Callback(x.Reference.Value));
}
/// <summary>
/// Report the command usage
/// </summary>
public void Usage()
{
this.Usage(null);
}
/// <summary>
/// Report the command usage
/// </summary>
/// <param name="remark">Initial comment</param>
public void Usage(string remark)
{
var temp = remark ?? string.Empty;
var theUsage = string.IsNullOrEmpty(temp.Trim()) ?
FSharpOption<string>.None :
new FSharpOption<string>(remark);
ArgParser.Usage(this.options, theUsage);
}
/// <summary>
/// Binds a ref bool and a callback
/// </summary>
private struct Flag
{
/// <summary>
/// A reference cell with a boolean value
/// </summary>
public FSharpRef<bool> Reference;
/// <summary>
/// A post-parse callback
/// </summary>
public Action<bool> Callback;
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

The calling code, for the same example as before looks like

namespace fsgetopt
{
class Program
{
static void Main(string[] args)
{
Action<string> compile = (s => Console.WriteLine("Compiling {0}...", s));
var outputName = "a.out";
var verbose = false;
var warningLevel = 0;
var rest = new List<string>();
var options = new Tinesware.ArgParser.Options().
String("-o",
x => outputName = x,
"Name of the output").
Set("-v",
x => verbose = x,
"Display additional information").
Int("--warn",
x => warningLevel = x,
"Set warning level").
Rest("--",
x => rest.Add(x),
"Stop parsing command line");
options.Parse(compile, "Usage options are:");
Console.WriteLine("outputName = {0}", outputName);
Console.WriteLine("Verbose = {0}", verbose.Value);
Console.WriteLine("Warning level = {0}", warningLevel);
Console.WriteLine("rest = {0}", rest.Count);
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

which is much clearer.

Now edited for FxCop.

No comments :