private bool TryEvaluateBundle(string arg, out ArgumentEvaluationResult result) { if (arg == null) { throw new ArgumentNullException(nameof(arg)); } var match = BundledOptionRegex.Match(arg); result = match.Success && match.AreGroupsSuccessful(flag, bundle) ? new ArgumentEvaluationResult(match.GetGroupValue(flag), match.GetGroupValue(bundle)) : ArgumentEvaluationResult.Failed; if (!(result.IsValid || result.IsBundled)) { return(false); } /* We could interpolate each of the characters then evaluate Contains and Select, * but this works as well, and only Takes what we want. */ foreach (var y in result.Name.TakeWhile(x => Contains($"{x}")).Select(x => $"{x}")) { result.Options.Add(Tuple.Create(y, this[y])); } result.Value = result.Name.Length == result.Options.Count ? null : result.Name.Substring(result.Options.Count); return(true); }
private bool TryEvaluateArgument(string arg, out ArgumentEvaluationResult result) { if (arg == null) { throw new ArgumentNullException(nameof(arg)); } var match = ValueOptionRegex.Match(arg); result = match.Success && match.AreGroupsSuccessful(flag, name) ? new ArgumentEvaluationResult(match.GetGroupValue(flag), match.GetGroupValue(name) , match.GetGroupValueOrDefault(roo).ToOptionValueType(), match.GetGroupValueOrDefault(val)) : ArgumentEvaluationResult.Failed; if (!result.IsValid) { return(false); } // Remember, here we are evaluating the COMMAND LINE ARGUMENT itself. result.Options.Add(Tuple.Create( result.Name , Contains(result.Name) ? this[result.Name] : GetDefaultOption() )); return(true); }
/// <summary> /// Visits a fleshed out <paramref name="context"/> upon each of the selected the /// <see cref="ArgumentEvaluationResult.Options"/>, rounding out elements such as /// <see cref="OptionContext.Option"/> itself, <see cref="OptionContext.OptionName"/>, /// and any <see cref="OptionContext.Parameters"/> that may be required. We will expect /// that <see cref="OptionContext.OptionIndex"/> will have been designated by virtual of /// the calling Arguments loop. /// </summary> /// <param name="context">The Current Context, at this stage we are expected to convey /// the Option(s), the actual Option Name used to trigger the invocation, as contrasted /// with a Option Prototype, and any further Option parameters Values. Once that is /// complete we may then trigger the Option Visitation, in which case we literally visit /// the Current Context upon the current Option of interest.</param> /// <param name="parts">Given the Parts evaluated as either Bundled or Conventional.</param> /// <param name="args">We expect to be handed the current view of the Command Line /// Arguments starting from the Current, Zero-based Argument. Any Option Parameters /// that require relay would either be parsed from the Current Argument, or subsequent /// One based Arguments.</param> /// <param name="count">Report the Count of the number of Arguments actually consumed /// by this Dispatch invocation, not including the Current argument.</param> /// <param name="processed">Captures all of the Processed Options.</param> /// <returns></returns> private bool TryDispatchOptionVisit(OptionContext context, ref ArgumentEvaluationResult parts , IReadOnlyList <string> args, out int count, out IEnumerable <Option> processed) { count = 0; // ReSharper disable once RedundantEmptyObjectOrCollectionInitializer var processedOptions = new Dictionary <Guid, Option> { }; // ReSharper disable once RedundantAssignment bool IsBooleanType(Type targetType) => BooleanTypes.Any(t => targetType == t); bool IsNotBooleanType(Type targetType) => BooleanTypes.All(t => targetType != t); string RenderEnableBoolean(bool enable) => $"{enable}".ToLower(); bool VerifyIsNeitherBundleNorArgument(params int[] indexes) { var max = indexes.Max(); return(args.Count > max && indexes.All(i => !(TryEvaluateBundle(args[i], out _) || TryEvaluateArgument(args[i], out _)))); } // ReSharper disable once IdentifierTypo bool TryUnbundleKeyValuePair(string s, char[] separators, out string[] results) => (results = s.Split(separators)).Length == 2; // Key is more of a Key at this point than a Name, used to lookup the Option. foreach (var(key, option) in parts.Options) { // Accounting for Parts.Value, Boolean Flags, is transparent with this approach. var currentCount = 0; /* Having closed the loop with this bookkeeping, then we can focus our undivided * attention on Dispatching the correct Option Parameters to the next Option. */ context.Option = option; context.OptionName = $"{parts.Flag}{key}"; context.Parameters.Clear(); /* TODO: TBD: the dispatch heuristics are a more relax here, just simply because each * individual Option may require a different set of parameters, but we still want to * signal which actual arguments were consumed by either the dispatched bundled value, * or the next value(s) in sequence, etc. */ // Determine which other Parameters we should be able to furnish the next Dispatch. switch (context.Option) { // No-op in this case, we have what we came here for. case ISimpleActionOption _: break; case IActionOption _ when parts.HasValue && !parts.EnableBoolean.HasValue: context.Parameters.Add(parts.Value); break; case IActionOption _ when parts.EnableBoolean.HasValue && !parts.HasValue: context.Parameters.Add(RenderEnableBoolean(parts.EnableBoolean.Value)); break; case IActionOption _ when !(parts.HasValue || parts.EnableBoolean.HasValue) && VerifyIsNeitherBundleNorArgument(1): context.Parameters.Add(args[++currentCount]); break; case IKeyValueActionOption _ when parts.HasValue && !parts.EnableBoolean.HasValue && VerifyIsNeitherBundleNorArgument(1): context.Parameters.Add(parts.Value); context.Parameters.Add(args[++currentCount]); break; case IKeyValueActionOption _ when parts.HasValue && !parts.EnableBoolean.HasValue && context.HasOption && TryUnbundleKeyValuePair(parts.Value, context.Option?.Separators?.ToArray(), out var xy): context.Parameters.Add(xy[0]); context.Parameters.Add(xy[1]); break; // Always assume the Boolean Shorthand is the Value member of the Key Value Pair. case IKeyValueActionOption o when parts.HasValue && parts.EnableBoolean.HasValue && o.TryVerifyKeyValueActionOptionTypes(valueTypeCallback: IsBooleanType) : context.Parameters.Add(parts.Value); context.Parameters.Add(RenderEnableBoolean(parts.EnableBoolean.Value)); break; // After handling the specific leading edge use case, then we may significantly reduce subsequent case load. case IKeyValueActionOption o when !parts.HasValue && parts.EnableBoolean.HasValue && o.TryVerifyKeyValueActionOptionTypes(IsNotBooleanType, IsBooleanType) && VerifyIsNeitherBundleNorArgument(1): context.Parameters.Add(args[++currentCount]); context.Parameters.Add(RenderEnableBoolean(parts.EnableBoolean.Value)); break; case IKeyValueActionOption _ when !(parts.HasValue || parts.EnableBoolean.HasValue) && VerifyIsNeitherBundleNorArgument(1, 2): context.Parameters.Add(args[++currentCount]); context.Parameters.Add(args[++currentCount]); break; } // Which culminates in an Option Visit. context.Visit(); // There is nothing further to Report when we encounter the Default Option. if (context.Option is IDefaultOption) { continue; } // Relay the Processed Options via the Context driven view. processedOptions[context.Option.Id] = context.Option; // Pull the Maximum Count forward to be reconciled by the Caller. count = Max(count, currentCount); } return((processed = processedOptions.Values.ToArray()).Any()); }