public void AddError_ShouldAddErrorToParseResult() { var parser = A.Fake <Parser>(); var commandParser = A.Fake <CommandParser <Command1Options> >(ob => ob.WithArgumentsForConstructor(() => new CommandParser <Command1Options>(parser))); var parseResult = A.Fake <ParseResult>(); var context = new CommandValidatorContext <Command1Options>(commandParser, parseResult); var missingCommandError = new MissingCommandError(); context.AddError(missingCommandError); A.CallTo(() => parseResult.AddError(missingCommandError)).MustHaveHappened(); }
public ParserResult Parse(string line) { var tokens = Tokenizer.Tokenize(line).Select(token => token?.Trim()).Where(token => token != null).ToList(); if (tokens.Count < 1) { return(new ParserError( Settings.Localization.Errors.NoInput, new ArgumentException( @"No argument values were provided so unable to find a command.", nameof(tokens) ) ).AsResult()); } var commandName = tokens[0]; var command = Find(commandName ?? throw new InvalidOperationException()); if (command == null) { return(MissingCommandError.Create(commandName, Settings.Localization.Errors.CommandNotFound).AsResult()); } var positionalArguments = 0; var parsed = new Dictionary <ICommandArgument, ArgumentValues>(); var errors = new List <ParserError>(); for (var tokenIndex = 1; tokenIndex < tokens.Count; ++tokenIndex) { var token = tokens[tokenIndex]; if (token == null) { throw new InvalidOperationException(@"None of the cleaned arguments should be null at this point."); } var canBeShortName = token.StartsWith(Settings.PrefixShort); if (canBeShortName) { var expectedLength = Settings.PrefixShort.Length + 1; var actualLength = token.Contains('=') ? token.IndexOf('=') : token.Length; canBeShortName = expectedLength == actualLength; } var canBeLongName = token.StartsWith(Settings.PrefixLong); if (canBeLongName) { var actualLength = token.Length; var maximumInvalidLength = Settings.PrefixLong.Length + (token.Contains('=') ? 2 : 1); canBeLongName = actualLength > maximumInvalidLength; } var isPositional = false; if (!canBeShortName && !canBeLongName) { if (positionalArguments < command.PositionalArguments.Count) { isPositional = true; } else { errors.Add(new ParserError(Settings.Localization.Errors.BadArgumentFormat.ToString(token))); continue; } } if (canBeShortName && canBeLongName) { errors.Add( new ParserError( Settings.Localization.Errors.IllegalArgumentFormat.ToString( token, Settings.PrefixShort, Settings.PrefixLong ) ) ); continue; } var cleanArgParts = token.Split('='); var cleanArgName = cleanArgParts[0] ?? ""; var cleanArgValue = (cleanArgParts.Length == 2 ? cleanArgParts[1] : null) ?? ""; if (isPositional) { cleanArgValue = cleanArgName; } var argument = isPositional ? command.PositionalArguments[positionalArguments] : canBeShortName?command.FindArgument(cleanArgName[Settings.PrefixShort.Length]) : command.FindArgument(cleanArgName.Substring(Settings.PrefixLong.Length)); if (argument == null) { if (isPositional) { errors.Add( UnhandledArgumentError.Create( command.Name, positionalArguments, cleanArgValue, Settings.Localization.Errors.UnhandledPositionalArgument ) ); } else { errors.Add( UnhandledArgumentError.Create( command.Name, cleanArgName, Settings.Localization.Errors.UnhandledNamedArgument ) ); } continue; } var argumentDisplayName = isPositional ? argument.Name : cleanArgName; var typeName = argument.ValueType.Name; if (Settings.Localization.TypeNames.TryGetValue(typeName, out var localizedType)) { typeName = localizedType; } List <object> values; if (parsed.TryGetValue(argument, out var argumentValues)) { if (!argument.AllowsMultiple) { errors.Add( new ParserError( Settings.Localization.Errors.DuplicateNamedArgument.ToString(argumentDisplayName), false ) ); continue; } values = argumentValues.Values.ToList(); } else { values = new List <object>(); } if (argument.IsFlag) { if (!string.IsNullOrEmpty(cleanArgValue)) { errors.Add( new ParserError( Settings.Localization.Errors.FlagArgumentsIgnoreValue.ToString(argumentDisplayName), false ) ); } values.Add(true); } else if (argument.IsCollection) { if (argument.Delimeter == null) { } else { var defaultValue = argument.ValueTypeDefault; var parsedPartValues = cleanArgValue.Split(new[] { argument.Delimeter }, StringSplitOptions.None) .Select( valuePart => { if (string.IsNullOrEmpty(valuePart)) { return(defaultValue); } if (TryParseArgument( argument.ValueType, defaultValue, valuePart, out var parsedPart )) { if (!argument.IsValueAllowed(parsedPart)) { errors.Add( new ParserError( Settings.Localization.Errors.InvalidArgumentValue.ToString( valuePart, argumentDisplayName ) ) ); } return(parsedPart); } if (argument.ValueType != typeof(object)) { errors.Add( new ParserError( Settings.Localization.Errors.InvalidArgumentValueWithType.ToString( valuePart, argumentDisplayName, typeName ), false ) ); } else { errors.Add( new ParserError( Settings.Localization.Errors.InvalidArgumentValue.ToString( valuePart, argumentDisplayName ), false ) ); } return(defaultValue); } ); values.AddRange(parsedPartValues); } } else { if (!TryParseArgument(argument.ValueType, argument.DefaultValue, cleanArgValue, out var value)) { if (argument.ValueType != typeof(object)) { errors.Add( new ParserError( Settings.Localization.Errors.InvalidArgumentValueWithType.ToString( cleanArgValue, argumentDisplayName, typeName ), true ) ); } else { errors.Add( new ParserError( Settings.Localization.Errors.InvalidArgumentValue.ToString( cleanArgValue, argumentDisplayName ), false ) ); } } if (!argument.IsValueAllowed(value)) { errors.Add( new ParserError( // TODO: DisallowedArgumentValue message instead Settings.Localization.Errors.InvalidArgumentValue.ToString(value, argumentDisplayName) ) ); } values.Add(value); } parsed[argument] = new ArgumentValues(argumentDisplayName, values); if (isPositional) { ++positionalArguments; } } var parserContext = new ParserContext { Command = command, Tokens = tokens.ToImmutableList(), Errors = errors.ToImmutableList(), Parsed = parsed.ToImmutableDictionary() }; var omitted = new List <ICommandArgument>(); var missing = new List <ICommandArgument>(); foreach (var argument in command.Arguments) { if (!argument.IsRequired(parserContext) || parsed.ContainsKey(argument)) { if (!parsed.ContainsKey(argument)) { parsed[argument] = ConstructDefaultArgument(argument, true); omitted.Add(argument); } continue; } if (argument.IsPositional) { errors.Add( MissingArgumentError.Create( Settings.Localization.Errors.MissingPositionalArgument, argument.Name, commandName ) ); } else { errors.Add(MissingArgumentError.Create(Settings.PrefixLong, argument.Name, commandName)); } missing.Add(argument); } return(new ParserResult(command, new ArgumentValuesMap(parsed), errors, missing, omitted)); }