Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Globalization;
- using System.Linq;
- using System.Reflection;
- using System.Text.RegularExpressions;
- using ArgumentParser.Arguments;
- using ArgumentParser.Factory;
- namespace ArgumentParser
- {
- public static class Parser
- {
- #region Constants
- internal const String PREFIX_UNIX_LONG = "--";
- internal const String PREFIX_UNIX_SHORT = "-";
- internal const String PREFIX_WINDOWS = "/";
- #if DEBUG
- private const String VERB_PATTERN = @"
- (?<=\s|^)
- (?:
- (?<args>[\-/]+\b.*)|
- (
- (?>"" (?> \\. | [^""] )* "")|
- (?>' (?> \\. | [^'] )* ')|
- (?> \\. | [^\s""'] )+
- )
- (?=\s|$)
- )";
- private const String UNIX_PARAMETERS_PATTERN = @"
- (^|\s)
- (
- (?<prefix>--)
- (
- (?<tag>(?!-)[\w\-]+)\s+(?!-)
- (?<value>
- (
- (?> "" (?> \\. | [^""])* "")|
- (?> ' (?> \\. | [^'])* ')|
- (?> \\. | [^\-""'] )*
- )(?=\s|$)
- )|
- (?<tag>(?!-)[\w\-]+)($|(?=\s))
- )|
- (?<prefix>-)
- (?<tag>\w)\s+
- (?<value>
- (
- (?> "" (?> \\. | [^""])* "")|
- (?> ' (?> \\. | [^'])* ')|
- (?> \\. | [^\-""'] )*
- )(?=\s|$)
- )|
- (?<prefix>-)
- (?<tag>\w)+($|(?=\s))
- )";
- private const String WINDOWS_PARAMETERS_PATTERN = @"
- (^|\s)(?<prefix>/)
- (
- (?<tag>\w+)\s+(?=[^/])
- (?<value>
- (?>"" (?> \\. | [^""])* "")|
- (?> ' (?> \\. | [^'])* ')|
- (?> \\. | [^\s""'] )*
- )|
- (?<tag>\w+)(?!/)
- )";
- #else
- /* Minified version of the constants */
- #endif
- #endregion
- public static IEnumerable<RawParameter> GetRawArguments(String input, ParameterTokenStyle tokenStyle)
- {
- MatchCollection matches = Regex.Matches(
- input: input,
- pattern: tokenStyle == ParameterTokenStyle.POSIX
- ? (UNIX_PARAMETERS_PATTERN)
- : (WINDOWS_PARAMETERS_PATTERN),
- options: RegexOptions.ExplicitCapture |
- RegexOptions.IgnorePatternWhitespace |
- RegexOptions.CultureInvariant |
- RegexOptions.Singleline);
- return matches.OfType<Match>().Select(x =>
- new RawParameter(
- x.Groups["prefix"].Value,
- x.Groups["tag"].Value,
- x.Groups["value"].Value));
- }
- public static void GetParts(ParserOptions options, String input, out String[] verbs, out String arguments)
- {
- var matches = Regex.Matches(
- input: input,
- pattern: VERB_PATTERN,
- options: RegexOptions.IgnorePatternWhitespace |
- RegexOptions.CultureInvariant |
- RegexOptions.Singleline).OfType<Match>().ToArray();
- verbs = matches
- .Where(x => !x.Groups["args"].Success)
- .Select(x => x.Value).ToArray();
- arguments = matches
- .Where(x => x.Groups["args"].Success)
- .Select(x => x.Value)
- .FirstOrDefault();
- }
- public static void Parse(this IParserContext context, String input)
- {
- if (context == null)
- throw new ArgumentNullException("context");
- if (context.Options == null)
- throw new InvalidOperationException("The provided context does not hold a valid options provider.");
- String[] verbNames;
- String args;
- GetParts(context.Options, input, out verbNames, out args);
- if (!verbNames.Any())
- ParseArguments(context, input, context.Options);
- else
- {
- List<String> unmatchedVerbs = verbNames.ToList();
- KeyValuePair<PropertyDescriptor, Verb>? verb = null;
- Object parent = context;
- var enumerator = verbNames.GetEnumerator();
- Object value = parent;
- while (enumerator.MoveNext()
- && (verb = GetVerb(parent = value, (String) enumerator.Current)).HasValue
- && unmatchedVerbs.Remove((String) enumerator.Current))
- {
- value = verb.Value.Key.GetValue(parent);
- if (value == null)
- verb.Value.Key.SetValue(parent, value = Activator.CreateInstance(verb.Value.Key.PropertyType));
- }
- if (!verb.HasValue)
- {
- ((IVerb) parent).Init(unmatchedVerbs.ToArray());
- return;
- }
- // TODO: check for inheritance
- ParseArguments((IVerb) verb.Value.Key.GetValue(parent), input, context.Options);
- }
- }
- private static KeyValuePair<PropertyDescriptor, Verb>? GetVerb(Object instance, String name)
- {
- var entry = TypeDescriptor.GetProperties(instance)
- .OfType<PropertyDescriptor>()
- .Select(x => new
- {
- Attribute = x.Attributes.OfType<VerbAttribute>().SingleOrDefault(a => a.Tag == name),
- Property = x
- })
- .SingleOrDefault(x => x.Attribute != null);
- if (entry == null)
- return null;
- return new KeyValuePair<PropertyDescriptor, Verb>(entry.Property, new Verb(entry.Attribute.Tag, entry.Attribute.Description));
- }
- public static IEnumerable<IPairable> GetArguments(String input, ParserOptions options, params IArgument[] arguments)
- {
- return GetArguments(input, options, arguments as IEnumerable<IArgument>);
- }
- public static IEnumerable<IPairable> GetArguments(String input, ParserOptions options, IEnumerable<IArgument> arguments)
- {
- if (input == null)
- throw new ArgumentNullException("input");
- if (options == null)
- throw new ArgumentNullException("options");
- if (arguments == null)
- throw new ArgumentNullException("arguments");
- MatchCollection matches = Regex.Matches(
- input: input,
- pattern: options.TokenStyle == ParameterTokenStyle.POSIX
- ? (UNIX_PARAMETERS_PATTERN)
- : (WINDOWS_PARAMETERS_PATTERN), // TODO: add support for Windows variants
- options: RegexOptions.ExplicitCapture |
- RegexOptions.IgnorePatternWhitespace |
- RegexOptions.CultureInvariant |
- RegexOptions.Singleline);
- // TODO: migrate the decoupling logic to prevent premature pairing
- var matchElements =
- matches.OfType<Match>().SelectMany(x => x.Groups["tag"].Captures
- .OfType<Capture>()
- .GroupBy(c => c.Value, (c, e) => new RawParameter(x.Groups["prefix"].Value, c, x.Groups["value"].Success ? x.Groups["value"].Value : null, e.Count())))
- .ToArray();
- var pairs = arguments
- .GroupJoin(
- matchElements,
- a => a,
- p => p,
- (a, p) =>
- {
- var parameters = p.ToArray();
- var values = parameters.Select(x => x.Value == null ? null : ParseValue(options, a, x)).ToArray();
- var flag = a as IFlag;
- if (flag != null)
- return GetFlagPair(options, flag, parameters, values);
- return new ParameterPair(a, values);
- },
- options.PairEqualityComparer)
- .GroupBy(x => x.Key, (k, e) => new ParameterGroup(e, k.Prefix, k.Tag)).ToArray();
- if (options.IgnoreUndefinedParameters)
- return pairs;
- // Caveat: Except() conflates duplicate (unrecognized) parameters.
- return pairs.Concat(matchElements.Except(pairs, options.PairEqualityComparer));
- }
- private static FlagPair GetFlagPair(ParserOptions options, IFlag flag, RawParameter[] parameters, Object[] values)
- {
- int count;
- bool aggregateEntries = (flag.Options & FlagOptions.Aggregate) != 0;
- bool aggregateValues = (flag.Options & FlagOptions.AggregateExplicit) != 0;
- if ((flag.Options & FlagOptions.BitField) != 0)
- {
- if (aggregateEntries)
- count = parameters.Aggregate(0, (c, x) =>
- {
- int value;
- if (aggregateValues)
- return c + (Int32.TryParse((String) x.Value, NumberStyles.Integer, options.Culture, out value) ? value : 0); // Count invalid entries as '0'
- return c + (1 << (x.Count - 1));
- });
- else
- count = (1 << ((int?) values.FirstOrDefault(x => x != null)) >> 1) ?? (parameters.Any() ? parameters.First().Count : 0);
- }
- else
- {
- if (aggregateEntries)
- count = parameters.Aggregate(0, (c, x) =>
- {
- int value;
- if (aggregateValues)
- return c + (Int32.TryParse((String) x.Value, NumberStyles.Integer, options.Culture, out value) ? value : 0); // Count invalid entries as '0'
- return c + x.Count;
- });
- else
- count = (int?) values.FirstOrDefault(x => x != null) ?? (parameters.Any() ? parameters.First().Count : 0);
- }
- return new FlagPair(
- argument: flag,
- values: values,
- count: count);
- }
- private static Object ParseValue(ParserOptions options, IArgument argument, RawParameter parameter)
- {
- try
- {
- return options.Detokenize
- ? argument.GetValue(options.Culture, options.Detokenizer == null
- ? Regex.Unescape((String) parameter.Value ?? String.Empty)
- : options.Detokenizer((String) parameter.Value))
- : argument.GetValue(options.Culture, (String) parameter.Value);
- }
- catch (Exception ex)
- {
- if (options.ExceptionHandler == null || !options.ExceptionHandler.Invoke(ex, argument, parameter))
- throw;
- }
- return null;
- }
- public static void ParseArguments(IVerb instance, String input, ParserOptions options)
- {
- if (instance == null)
- throw new ArgumentNullException("instance");
- if (options == null)
- throw new ArgumentNullException("options");
- ParseArguments(TypeDescriptor.GetProperties(instance).OfType<PropertyDescriptor>(), instance, input, options);
- }
- private static void ParseArguments(IEnumerable<PropertyDescriptor> properties, IVerb instance, String input, ParserOptions options)
- {
- var methods = instance.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod);
- var members = methods.Concat<Object>(properties);
- Dictionary<IArgument, Tuple<Object, IOptionAttribute>> arguments;
- switch (options.TokenStyle)
- {
- case ParameterTokenStyle.POSIX:
- arguments = members
- .SelectMany(m => (m is PropertyDescriptor
- ? ((PropertyDescriptor) m).Attributes.OfType<POSIXOptionAttribute>()
- : ((MethodInfo) m).GetCustomAttributes<POSIXOptionAttribute>()).Select(a => new
- {
- Member = m,
- Attribute = a
- }))
- .Where(x => x.Attribute != null)
- .ToDictionary(
- keySelector: x => GetPOSIXArgument(x.Attribute, x.Member),
- elementSelector: x => Tuple.Create(x.Member, (IOptionAttribute) x.Attribute));
- break;
- case ParameterTokenStyle.Windows:
- arguments = members
- .SelectMany(m => (m is PropertyDescriptor
- ? ((PropertyDescriptor) m).Attributes.OfType<WindowsOptionAttribute>()
- : ((MethodInfo) m).GetCustomAttributes<WindowsOptionAttribute>()).Select(a => new
- {
- Member = m,
- Attribute = a
- }))
- .Where(x => x.Attribute != null)
- .ToDictionary(
- keySelector: x => GetWindowsArgument(x.Attribute, x.Member),
- elementSelector: x => Tuple.Create(x.Member, (IOptionAttribute) x.Attribute));
- break;
- default:
- throw new InvalidOperationException("The logic for the provided token style is not defined.");
- }
- var pairs = GetArguments(input, options, arguments.Select(x => x.Key)).ToArray();
- var matches = pairs.OfType<ParameterGroup>()
- .GroupJoin(
- arguments,
- x => x,
- x => x.Key,
- (pair, kv) => new KeyValuePair<Tuple<Object, IOptionAttribute>, ParameterPair[]>(kv.First().Value, pair.ToArray()),
- options.PairEqualityComparer);
- var parameters = pairs.OfType<RawParameter>();
- foreach (var parameter in parameters)
- instance.HandleParameter(parameter);
- BindValues(instance, matches);
- }
- private static IArgument GetWindowsArgument(WindowsOptionAttribute attribute, Object member)
- {
- // TODO: implement Windows flags
- return WindowsArgumentFactory.CreateArgument(
- tag: attribute.Tag,
- description: attribute.Description,
- returnType: member is PropertyDescriptor
- ? ((PropertyDescriptor) member).PropertyType
- : ((MethodInfo) member).GetParameters().First().ParameterType,
- typeConverter: attribute.TypeConverter,
- defaultValue: attribute.DefaultValue);
- }
- private static IArgument GetPOSIXArgument(POSIXOptionAttribute attribute, Object member)
- {
- var descriptor = member as PropertyDescriptor;
- var returnType = descriptor != null
- ? descriptor.PropertyType
- : ((MethodInfo) member).GetParameters().First().ParameterType;
- if (!attribute.IsShort)
- return POSIXArgumentFactory.CreateArgument(
- tag: attribute.Tag,
- description: attribute.Description,
- returnType: returnType,
- typeConverter: attribute.TypeConverter,
- defaultValue: attribute.DefaultValue);
- var flagAttribute = attribute as POSIXFlagAttribute;
- if (flagAttribute != null)
- return POSIXArgumentFactory.CreateFlag(
- tag: flagAttribute.Tag.First(), // The "Tag" property should hold a single character.
- description: flagAttribute.Description,
- returnType: returnType,
- options: flagAttribute.Options,
- typeConverter: flagAttribute.TypeConverter,
- defaultValue: flagAttribute.DefaultValue);
- return POSIXArgumentFactory.CreateArgument(
- tag: attribute.Tag.First(),
- description: attribute.Description,
- returnType: returnType,
- typeConverter: attribute.TypeConverter,
- defaultValue: attribute.DefaultValue);
- }
- private static void BindValues(Object instance, IEnumerable<KeyValuePair<Tuple<Object, IOptionAttribute>, ParameterPair[]>> matches)
- {
- foreach (var binding in matches)
- {
- var attribute = binding.Key.Item2;
- var property = binding.Key.Item1 as PropertyDescriptor;
- var methodInfo = binding.Key.Item1 as MethodInfo;
- foreach (var pair in binding.Value)
- {
- var flagPair = pair as FlagPair;
- if (pair.Matched)
- {
- if (flagPair != null)
- {
- BindFlag(instance, flagPair, property, methodInfo, attribute, pair);
- }
- else
- {
- if (property != null)
- foreach (var value in pair.Values)
- property.SetValue(instance, value);
- else if (attribute.ManualBinding)
- methodInfo.Invoke(instance, new Object[] { null, new BindingEventArgs(pair, attribute) });
- else
- foreach (var value in pair.Values)
- methodInfo.Invoke(instance, new[] { value });
- }
- }
- else if (pair.Argument.DefaultValue != null)
- {
- if (property != null) property.SetValue(instance, pair.Argument.DefaultValue);
- else methodInfo.Invoke(instance, attribute.ManualBinding
- ? new[] { pair.Argument.DefaultValue, new BindingEventArgs(pair, attribute) }
- : new[] { pair.Argument.DefaultValue });
- }
- }
- }
- }
- private static void BindFlag(Object instance, FlagPair flagPair, PropertyDescriptor property, MethodInfo methodInfo, IOptionAttribute attribute, ParameterPair pair)
- {
- bool defaultValue = (flagPair.Argument.DefaultValue as Boolean? ?? false);
- // We're binding a property
- if (property != null)
- {
- if (property.PropertyType == typeof (Boolean))
- property.SetValue(instance, flagPair.Count > 0 ^ defaultValue);
- else if (flagPair.Count > 0)
- property.SetValue(instance, flagPair.Count);
- else
- foreach (var v in flagPair.Values)
- property.SetValue(instance, v);
- }
- else // ...or invoking a method
- {
- if (methodInfo.ReturnType == typeof (Boolean))
- {
- var value = flagPair.Count > 0 ^ defaultValue;
- methodInfo.Invoke(instance, attribute.ManualBinding
- ? new Object[] { value, new BindingEventArgs(pair, attribute) }
- : new[] { (Object) value });
- }
- else if (attribute.ManualBinding)
- methodInfo.Invoke(instance, new Object[] { flagPair.Count, new BindingEventArgs(pair, attribute) });
- else if (flagPair.Count > 0)
- methodInfo.Invoke(instance, new[] { (Object) flagPair.Count });
- else
- foreach (var v in flagPair.Values)
- methodInfo.Invoke(instance, new[] { v });
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement