Skip to content

Instantly share code, notes, and snippets.

@garrydzeng
Last active December 20, 2015 07:17
Show Gist options
  • Save garrydzeng/7155a5dc298c9f0416a5 to your computer and use it in GitHub Desktop.
Save garrydzeng/7155a5dc298c9f0416a5 to your computer and use it in GitHub Desktop.
CommandOption is a callback-based program option parser.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security;
using System.Text.RegularExpressions;
using System.Linq;
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.0")]
[assembly: AssemblyTitle("CommandOption is a callback-based program option parser.")]
[assembly: AssemblyProduct("CommandOption")]
namespace CommandOption {
// Option prototype, defined as interface...
public interface IOption {
bool IsMark { get; }
string Description { get; }
string Name { get; }
bool IsRequired { get; }
bool IsHelp { get; }
void Process(string argument);
}
[Serializable]
public class OptionException : Exception {
public string OptionName {
protected set;
get;
}
public OptionException() { }
public OptionException(string message, string optionName)
: base(message) {
OptionName = optionName;
}
public OptionException(string message, string optionName, Exception innerException)
: base(message, innerException) {
OptionName = optionName;
}
protected OptionException(SerializationInfo info, StreamingContext context)
: base(info, context) {
OptionName = info.GetString("OptionName");
}
[SecurityCritical]
public override void GetObjectData(SerializationInfo info, StreamingContext context) {
base.GetObjectData(info, context);
info.AddValue("OptionName", OptionName);
}
}
public class OptionSet : List<IOption> {
public OptionSet() { }
public OptionSet(IEnumerable<IOption> collection) : base(collection) { }
public OptionSet(int capacity) : base(capacity) { }
// get help...
public virtual string Help {
get {
throw new NotImplementedException();
}
}
public void Add<T>(string name, Action<T> callback, bool isRequired = true, bool isMark = false, bool isHelp = false) {
Add(new CommonOption<T>(
Name: name,
Callback: callback,
IsMark: isMark,
IsRequired: isRequired,
IsHelp: isHelp
));
}
public void Add<T>(string name, string description, Action<T> callback, bool isRequired = true, bool isMark = false, bool isHelp = false) {
Add(new CommonOption<T>(
Name: name,
Description: description,
Callback: callback,
IsMark: isMark,
IsRequired: isRequired,
IsHelp: isHelp
));
}
public void Add(string name, Action<string> callback, bool isRequired = true, bool isMark = false, bool isHelp = false) {
Add(new CommonOption(
Name: name,
Callback: callback,
IsMark: isMark,
IsRequired: isRequired,
IsHelp: isHelp
));
}
public void Add(string name, string description, Action<string> callback, bool isRequired = true, bool isMark = false, bool isHelp = false) {
Add(new CommonOption(
Name: name,
Description: description,
Callback: callback,
IsMark: isMark,
IsRequired: isRequired,
IsHelp: isHelp
));
}
static readonly Regex pattern = new Regex("^(--|-|/)([^:=]+)([:=](.*))?$");
string filter(string value) {
return string.IsNullOrEmpty(value) ? null : value;
}
IDictionary<string, string> ParseArguments(string[] arguments) {
Match match;
Dictionary<string, string> parsed = new Dictionary<string, string>();
GroupCollection group;
// Ignore invalid...
foreach (string argument in arguments) {
match = pattern.Match(argument);
if (match.Success) {
group = match.Groups;
parsed[group[2].Value] = filter(group[4].Value);
}
}
return parsed;
}
public virtual void Parse(string[] arguments) {
IDictionary<string, string> dictionary = ParseArguments(arguments);
IEnumerable<IOption> options = this
.Where(n => n.IsHelp);
// Try to find help argument.
// if we found,
// process they only.
if (!options.Any(n => dictionary.ContainsKey(n.Name))) {
options = this;
}
bool exists;
string value = null;
foreach (IOption option in options) {
exists = dictionary.ContainsKey(option.Name);
// check key...
if (option.IsRequired && !exists) {
throw new OptionException(
string.Format("Missing required '{0}' option.", option.Name),
option.Name
);
}
// try get value...
value = exists ?
dictionary[option.Name] :
null;
if (option.IsRequired && value == null) {
throw new OptionException(
string.Format("Missing required value for option '{0}'.", option.Name),
option.Name
);
}
if (option.IsMark && value != null) {
throw new OptionException(
string.Format("Flag '{0}' can't has value.", option.Name),
option.Name
);
}
// call the Action when:
// 1. current option is non-help option.
// 2. current option passed.
if (!option.IsHelp || exists) {
option.Process(value);
}
}
}
}
public class CommonOption<T> : IOption {
public bool IsMark { get; protected set; }
public string Description { get; protected set; }
public string Name { get; protected set; }
public bool IsRequired { get; protected set; }
public bool IsHelp { get; protected set; }
protected Action<T> Callback;
public CommonOption(string Name, Action<T> Callback, string Description = null, bool IsRequired = true, bool IsMark = false, bool IsHelp = false) {
if (Name == null || Name.Length == 0) {
throw new ArgumentException(
"Option name can't be null or empty.",
"Name"
);
}
if (Callback == null) {
throw new ArgumentNullException("Callback");
}
this.Callback = Callback;
this.Name = Name;
this.Description = Description;
this.IsMark = IsMark;
this.IsRequired = IsRequired;
this.IsHelp = IsHelp;
}
protected T Parse(string argument) {
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
try {
return argument != null ?
(T)converter.ConvertFromString(argument) :
default(T);
}
catch (Exception exception) {
string format = "Could not convert string '{0}' to type {1} for option '{2}'.";
throw new OptionException(
string.Format(format, argument, typeof(T).Name, Name),
Name,
exception
);
}
}
public void Process(string argument) {
Callback(Parse(argument));
}
}
public class CommonOption : IOption {
public bool IsMark { get; protected set; }
public string Description { get; protected set; }
public string Name { get; protected set; }
public bool IsRequired { get; protected set; }
public bool IsHelp { get; protected set; }
protected Action<string> Callback;
public CommonOption(string Name, Action<string> Callback, string Description = null, bool IsRequired = true, bool IsMark = false, bool IsHelp = false) {
if (Name == null || Name.Length == 0) {
throw new ArgumentException(
"Option name can't be null or empty.",
"Name"
);
}
if (Callback == null) {
throw new ArgumentNullException("Callback");
}
this.Callback = Callback;
this.Name = Name;
this.Description = Description;
this.IsMark = IsMark;
this.IsRequired = IsRequired;
this.IsHelp = IsHelp;
}
public void Process(string argument) {
Callback(argument);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment