Skip to content

Instantly share code, notes, and snippets.

@zaphar
Created November 17, 2014 20:11
Show Gist options
  • Save zaphar/6c62eb593be2579bfe63 to your computer and use it in GitHub Desktop.
Save zaphar/6c62eb593be2579bfe63 to your computer and use it in GitHub Desktop.
C# command line parsing library that doesn't suck.
// Heavily inspired by golang's flags library https://golang.org/pkg/flag/
using System;
using System.Linq;
using System.Collections.Generic;
namespace mzhs.flags
{
/// <summary>
/// The interface a CommandLine Flag must support to be used by the command line parser.
/// </summary>
public interface FlagValue
{
/// <summary>
/// Returns a string version of the command line flag's current value and description.
/// </summary>
string ToString();
/// <summary>
/// Set the specified flag from the provided string value.
/// </summary>
/// <returns> true if the flag was successfully set from the string, false otherwise.</returns>
bool Set(string flag);
/// <summary>
/// This method tells the parser if the method is boolean in it's value.
/// It's a convenience to make parsing bool flags that have no value to set easier.
/// </summary>
bool IsBool { get; }
}
/// <summary>
/// A Typed FlagValue.
/// </summary>
public interface FlagValue<T> : FlagValue
{
/// <summary>
/// Gets the current value for this FlagValue.
/// </summary>
T Value { get; }
}
/// <summary>
/// Flags Command Line Parsing library for C#.
/// </summary>
public class Flags
{
private List<string> _args = new List<string>();
private Dictionary<string, FlagValue> _flags = new Dictionary<string, FlagValue>();
private bool _HasErrors;
private string _ErrorText = "";
/// <summary>
/// Add your own Custom FlagValue handler for flag parsing.
/// </summary>
public FlagValue<T> Add<T>(string name, FlagValue<T> val)
{
_flags[name] = val;
return val;
}
/// <summary>
/// Add an Integer command line flag to be parsed.
/// </summary>
public FlagValue<int> AddIntFlag(string name, int defaultValue, string description="")
{
return Add(name, new Val<int>(defaultValue,
(s) => {
int i;
bool result = int.TryParse(s, out i);
return Tuple.Create(result, i);
}, description: description));
}
/// <summary>
/// Adds a boolean command line flag to be parsed.
/// </summary>
/// <param name="description">Description.</param>
public FlagValue<bool> AddBoolFlag(string name, bool defaultValue, string description="")
{
return Add(name, new Val<bool>(defaultValue,
(s) => {
bool b;
bool result = bool.TryParse(s, out b);
return Tuple.Create(result, b);
}, description: description, isBool: true));
}
/// <summary>
/// Add a string command line flag to be parsed.
/// </summary>
public FlagValue<string> AddStringFlag(string name, string defaultValue, string description="")
{
return Add(name, new Val<string>(defaultValue,
(s) => {
return Tuple.Create(true, s);
}, description: description));
}
/// <summary>
/// Parse a set of command line args.
/// </summary>
/// <remarks>
/// This method will stop at the first error encountered while parsing flags.
/// Check the HasErrors property to see if there was an error while parsing.
/// If HasErrors is true then the ErrorText property will contain the error message.
/// </remarks>
public void Parse(string[] args)
{
for (var i = 0; i < args.Length; i++)
{
var flag = args[i];
if (flag[0] == '-')
{
var start = 1;
if (flag[1] == '-') { start = 2; }
flag = args[i].Substring(start);
}
else {
_args.Add(flag);
continue;
}
string val = "";
bool hadEquals = false;
if (flag.Contains("="))
{
hadEquals = true;
var results = flag.Split(new char[] { '=' }, 2);
flag = results[0];
val = results[1];
}
else if (args.Length > (i+1))
{
val = args[i + 1];
}
if (_flags.ContainsKey(flag))
{
var fv = _flags[flag];
if (val != "" && hadEquals)
{
fv.Set(val);
}
else if (fv.IsBool)
{
fv.Set(bool.TrueString);
}
else if (val != "")
{
if (!fv.Set(val))
{
_ErrorText = "\"" + val + "\" is not a valid " + flag + ".";
_HasErrors = true;
}
i++;
}
else
{
// This would be a parse error.
_ErrorText = flag + " expects an argument.";
_HasErrors = true;
}
}
else
{
// This is an unparsed arg.
_args.Add(args[i]);
}
if (_HasErrors)
{
break;
}
}
}
/// <summary>
/// Returns the text of the first error encountered while parsing.
/// </summary>
public string ErrorText
{
get { return _ErrorText; }
}
/// <summary>
/// Returns true if the parser encountered an Error while parsing.
/// </summary>
public bool HasErrors
{
get { return _HasErrors; }
}
/// <summary>
/// Returns any left over arguments not parsed by the command line parser.
/// </summary>
public List<string> Args
{
get { return _args; }
}
public List<string> HelpText
{
get
{
var help = new List<string>();
foreach (var kv in _flags)
{
help.Add("-" + kv.Key + " " + kv.Value.ToString());
}
return help;
}
}
}
public class Val<T> : FlagValue<T>
{
private T _value;
private string _description = "";
private Func<string, Tuple<bool,T>> _parseFn;
public Val(T defaultValue, Func<string, Tuple<bool, T>> parseFn, string description="", bool isBool=false)
{
_value = defaultValue;
_description = description;
IsBool = isBool;
_parseFn = parseFn;
}
public bool Set(string value)
{
var tpl = _parseFn(value);
_value = tpl.Item1 ? tpl.Item2 : _value;
return tpl.Item1;
}
public T Value
{
get { return _value; }
}
public bool IsBool
{
get;
private set;
}
public override string ToString()
{
return "[" + _value + "]" + (_description.IsEmpty() ? "" : " - " + _description);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment