/// <summary> /// This method is used by the code that validates the command-line grouping. It is /// called for every iteration of the arguments. /// </summary> /// <param name="previousType">The type of the previously-checked argument.</param> /// <param name="currentType">The type of the currently-checked argument.</param> /// <param name="grouping">The expected arg grouping.</param> private static void VerifyCommandLineGrouping(ArgumentType previousType, ArgumentType currentType, ArgGrouping grouping) { if (grouping == ArgGrouping.DoesNotMatter) { return; } if (previousType == ArgumentType.NotSet || currentType == ArgumentType.NotSet) { return; } if (grouping == ArgGrouping.OptionsAfterArguments && previousType == ArgumentType.Option && currentType == ArgumentType.Argument) { throw new ParserException(ParserException.Codes.OptionsAfterParameters, Messages.OptionsAfterParameters); } if (grouping == ArgGrouping.OptionsBeforeArguments && previousType == ArgumentType.Argument && currentType == ArgumentType.Option) { throw new ParserException(ParserException.Codes.OptionsBeforeParameters, Messages.OptionsBeforeParameters); } }
/// <inheritdoc/> public override ArgGrouping GetGrouping(ArgGrouping specifiedGrouping, IReadOnlyList <Option> options, IReadOnlyList <Argument> arguments) { // If any option has variable number of parameters (i.e. ExpectedParameters = null), // then the groupings is changed to DoesNotMatter. if (specifiedGrouping == ArgGrouping.OptionsBeforeArguments) { bool optionsHaveVariableParameters = options.Any(option => !option.Usage.ExpectedParameters.HasValue); if (optionsHaveVariableParameters) { specifiedGrouping = ArgGrouping.DoesNotMatter; } } // If any option has unlimited parameters, then options must appear after arguments. bool optionsHaveUnlimitedParameters = options.Any(option => option.Usage.MaxParameters == OptionUsage.Unlimited); if (specifiedGrouping != ArgGrouping.OptionsAfterArguments && optionsHaveUnlimitedParameters) { specifiedGrouping = ArgGrouping.OptionsAfterArguments; } return(specifiedGrouping); }
public Parser(Command command, ArgStyle argStyle, ArgGrouping grouping = ArgGrouping.DoesNotMatter) { if (command is null) { throw new ArgumentNullException(nameof(command)); } if (argStyle is null) { throw new ArgumentNullException(nameof(argStyle)); } Command = command; ArgStyle = argStyle; Grouping = grouping; }
/// <summary> /// Identifies all provided tokens as arguments, options and option parameters. /// <para/> /// Option and argument validators are not checked in this phase. Only the arg grouping /// is checked. /// </summary> /// <param name="tokens">All the specified tokens.</param> /// <param name="options"> /// All available options. If any of the tokens match, add its details to this parameter. /// </param> /// <param name="grouping">The expected arg grouping to validate.</param> /// <returns>A collection of all the identified arguments.</returns> public abstract IEnumerable <string> IdentifyTokens(IEnumerable <string> tokens, IReadOnlyList <OptionRun> options, ArgGrouping grouping);
/// <summary> /// Allows the parser style to override the preferred grouping based on its rules for the /// specified options and arguments. /// </summary> /// <param name="specifiedGrouping">The preferred grouping.</param> /// <param name="options">The list of allowed options.</param> /// <param name="arguments">The list of allowed arguments.</param> /// <returns>The final grouping for the specified options and arguments.</returns> public virtual ArgGrouping GetGrouping(ArgGrouping specifiedGrouping, IReadOnlyList <Option> options, IReadOnlyList <Argument> arguments) { return(specifiedGrouping); }
/// <summary> /// Initializes a new instance of the <see cref="ConsoleProgram"/> class with the specified /// name, arg style and grouping. /// </summary> /// <param name="name">The name of the program.</param> /// <param name="argStyle">The expected argument style.</param> /// <param name="grouping">The expected arg grouping.</param> public ConsoleProgram(string name, ArgStyle argStyle, ArgGrouping grouping = ArgGrouping.DoesNotMatter) : base(caseSensitive: false, name) { _argStyle = CreateArgStyle(argStyle); Grouping = grouping; }
/// <inheritdoc/> public override IEnumerable <string> IdentifyTokens(IEnumerable <string> tokens, IReadOnlyList <OptionRun> options, ArgGrouping grouping) { ArgumentType previousType = ArgumentType.NotSet; ArgumentType currentType = ArgumentType.NotSet; foreach (string token in tokens) { VerifyCommandLineGrouping(previousType, currentType, grouping); previousType = currentType; var optionMatch = OptionPattern.Match(token); if (!optionMatch.Success) { currentType = ArgumentType.Argument; yield return(token); } else { currentType = ArgumentType.Option; string specifiedOptionName = optionMatch.Groups[groupnum : 1].Value; // Find the corresponding option run for the specified option name. // If not found, throw a parse exception. OptionRun availableOption = options.SingleOrDefault(or => or.Option.HasName(specifiedOptionName)); if (availableOption is null) { throw new ParserException(ParserException.Codes.InvalidOptionSpecified, string.Format(Messages.InvalidOptionSpecified, specifiedOptionName)); } // Increase the number of occurrences of the option run. availableOption.Occurrences += 1; // If no option parameters are specified, we're done with this option. // Continue the loop to process the next token. if (token.Length == specifiedOptionName.Length + 1) { continue; } // Option name and its parameters must be separated by a colon. // If not, throw a parse exception. if (token[specifiedOptionName.Length + 1] != ':') { throw new ParserException(ParserException.Codes.InvalidOptionParameterSpecifier, string.Format(Messages.InvalidOptionParameterSpecifier, specifiedOptionName)); } // Match any parameters and add to the option run's Parameters property. var parameterMatches = OptionParameterPattern.Matches(token, optionMatch.Length + 1); foreach (Match parameterMatch in parameterMatches) { string value = parameterMatch.Groups[groupnum : 1].Value; if (value.StartsWith(",", StringComparison.OrdinalIgnoreCase)) { value = value.Remove(startIndex: 0, count: 1); } availableOption.Parameters.Add(value); } } } VerifyCommandLineGrouping(previousType, currentType, grouping); }
/// <summary> /// Parses the given set of tokens based on the rules specified by the <see cref="Command"/>. /// </summary> /// <param name="tokens">Token strings to parse.</param> /// <returns>A <see cref="ParseResult" /> instance.</returns> public ParseResult Parse(IEnumerable <string> tokens) { DebugOutput.Write("Tokens passed", tokens); IReadOnlyList <string> tokenList = tokens.ToList(); // Creates a ParseRun instance, which specifies the sequence of commands specified and // the tokens and any options and arguments that apply to the specified commands. ParseRun run = CreateRun(tokenList); // Extract out just the option and argument objects from their respective run collections. // We want to pass these to parser style methods that only need to deal with the Option // and Argument objects and not have to deal with the 'run' aspects. See some of the calls // to protected methods from this method. IReadOnlyList <Option> justOptions = run.Options.Select(o => o.Option).ToList(); IReadOnlyList <Argument> justArguments = run.Arguments.Select(a => a.Argument).ToList(); // Even though the caller can define the grouping, the parser style can override it based // on the available options and arguments. See the UnixArgStyle class for an example. Grouping = ArgStyle.GetGrouping(Grouping, justOptions, justArguments); // Validate all the available options based on the parser style rules. // See the UnixArgStyle for an example. ArgStyle.ValidateDefinedOptions(justOptions); // Identify all tokens as options or arguments. Identified option details are stored in // the Option instance itself. Identified arguments are returned from the method. List <string> specifiedArguments = ArgStyle.IdentifyTokens(run.Tokens, run.Options, Grouping).ToList(); // Get the groups that match the specified options and arguments. IReadOnlyList <int> matchingGroups = GetMatchingGroups(run, specifiedArguments); // If no groups match, we cannot proceed, so throw a parser exception. if (matchingGroups.Count == 0) { throw new ParserException(-1, "The specified arguments and options are invalid."); } // Trim the argument and option runs to those that contain the matcing groups. TrimRunsToMatchingGroups(run, matchingGroups); //TODO: Come back to this later //if (ConfigReader != null) // specifiedArguments = ConfigReader.Run(specifiedArguments, Options); // Process the specified options and arguments, and resolve their values. ProcessOptions(run.Options); ProcessArguments(specifiedArguments, run.Arguments); var parseResult = new ParseResult(run); // Runs the custom validator, if it is assigned. CommandCustomValidator customCommandValidator = parseResult.Command.CustomValidator; string validationError = customCommandValidator?.Invoke(parseResult.Arguments, parseResult.Options); if (validationError != null) { throw new ValidationException(validationError, null, null); } return(parseResult); }
/// <inheritdoc/> public override IEnumerable <string> IdentifyTokens(IEnumerable <string> tokens, IReadOnlyList <OptionRun> options, ArgGrouping grouping) { OptionRun currentOption = null; bool forceArguments = false; foreach (string token in tokens) { if (token == "--") { forceArguments = true; continue; } if (forceArguments) { yield return(token); continue; } Match optionMatch = OptionPattern.Match(token); // If the token is not an option and we are not iterating over the parameters of an // option, then the token is an argument. if (!optionMatch.Success) { // If we're processing an option (currentOption != null), then it's an argument. // Otherwise, it's a parameter on the current option. if (currentOption is null) { yield return(token); } else { currentOption.Parameters.Add(token); } } else { bool isShortOption = optionMatch.Groups[1].Value.Length == 1; string optionName = optionMatch.Groups[2].Value; string parameterValue = optionMatch.Groups[3].Value; bool isParameterSpecified = !string.IsNullOrEmpty(parameterValue); // If multiple short options are specified as a single combined option, then none // of them can have parameters. if (isShortOption && optionName.Length > 1 && isParameterSpecified) { throw new ParserException(-1, $"Cannot specify a parameter for the combined option {optionName}."); } // Get the specified option names. // If a short option name is specified and it has multiple characters, each // character is a short name. string[] optionNames = isShortOption && optionName.Length > 1 ? optionName.Select(c => c.ToString()).ToArray() : new[] { optionName }; // Add each option to its corresponding option run. foreach (string name in optionNames) { OptionRun matchingOption = options.SingleOrDefault(or => or.Option.HasName(name)); if (matchingOption is null) { throw new ParserException(ParserException.Codes.InvalidOptionSpecified, string.Format(Messages.InvalidOptionSpecified, optionName)); } // Increase the number of occurrences of the option matchingOption.Occurrences += 1; } // If only one option was specified (and not a combined short option set), we deal // with the parameters now. if (optionNames.Length == 1) { // We know its option run exists, so use Single here. OptionRun matchingOption = options.Single(or => or.Option.HasName(optionNames[0])); // If the parameter was specified in the same token with a "=" symbol, then // that is the only parameter we can allow. // Add it and proceed with the next token. if (isParameterSpecified) { matchingOption.Parameters.Add(parameterValue); currentOption = null; } // Otherwise, mark the current option as this one and process all subsequent // tokens as parameters until we encounter another option or we've reached the // max limit of parameters for this option. else { currentOption = matchingOption; } } else { currentOption = null; } } // If we're on an option (currentOption != null) and the number of parameters has // reached the maximum allowed, then we can stop handling that option by setting // currentOption to null so that the next arg will be treated as a new option or // argument. if (currentOption != null && currentOption.Parameters.Count >= currentOption.Option.Usage.MaxParameters) { currentOption = null; } } }
public Parser(ParserStyle parserStyle, ArgGrouping grouping = ArgGrouping.DoesNotMatter) { ParserStyle = parserStyle; Grouping = grouping; }