| File: Parsing\ArgumentResult.cs | Web Access |
| Project: src\command-line-api\src\System.CommandLine\System.CommandLine.csproj (System.CommandLine) |
// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.CommandLine.Binding; using System.Linq; namespace System.CommandLine.Parsing { /// <summary> /// Represents a result produced when parsing an <see cref="Argument"/>. /// </summary> public sealed class ArgumentResult : SymbolResult { private ArgumentConversionResult? _conversionResult; private bool _validatorsHaveBeenRun; private bool _onlyTakeHasBeenCalled; internal ArgumentResult( Argument argument, SymbolResultTree symbolResultTree, SymbolResult? parent) : base(symbolResultTree, parent) { Argument = argument ?? throw new ArgumentNullException(nameof(argument)); } /// <summary> /// Gets the argument to which the result applies. /// </summary> public Argument Argument { get; } internal bool ArgumentLimitReached => Argument.Arity.MaximumNumberOfValues == (_tokens?.Count ?? 0); public bool Implicit { get; private set; } internal ArgumentConversionResult GetArgumentConversionResult() { if (_conversionResult is not null) { if (!_validatorsHaveBeenRun && Argument.HasValidators) { // GetValueOrDefault was called before GetArgumentConversionResult, // which cached a conversion result without running validators. // Run them now so that validators like AcceptExistingOnly() are not skipped. _validatorsHaveBeenRun = true; for (var i = 0; i < Argument.Validators.Count; i++) { Argument.Validators[i](this); } } return _conversionResult; } return _conversionResult = ValidateAndConvert(useValidators: true); } /// <summary> /// Gets the parsed value or the default value for <see cref="Argument"/>. /// </summary> /// <returns>The parsed value or the default value for <see cref="Argument"/>.</returns> public T GetValueOrDefault<T>() => (_conversionResult ??= ValidateAndConvert(useValidators: false)) .ConvertIfNeeded(typeof(T)) .GetValueOrDefault<T>(); /// <summary> /// Specifies the maximum number of tokens to consume for the argument. Remaining tokens are passed on and can be consumed by later arguments, or will otherwise be added to <see cref="ParseResult.UnmatchedTokens"/>. /// </summary> /// <param name="numberOfTokens">The number of tokens to take. The rest are passed on.</param> /// <exception cref="ArgumentOutOfRangeException"><c>numberOfTokens - Value</c> must be at least 1.</exception> /// <exception cref="InvalidOperationException">This method is called more than once.</exception> /// <exception cref="NotSupportedException">An Option has a CustomParser delegate that receives an ArgumentResult as a parameter and calls the OnlyTake method of that ArgumentResult.</exception> public void OnlyTake(int numberOfTokens) { if (numberOfTokens < 0) { throw new ArgumentOutOfRangeException(nameof(numberOfTokens), numberOfTokens, "Value must be at least 1."); } if (_onlyTakeHasBeenCalled) { throw new InvalidOperationException($"{nameof(OnlyTake)} can only be called once."); } if (Parent is OptionResult) { throw new NotSupportedException($"{nameof(OnlyTake)} is supported only for a {nameof(Command)}-owned {nameof(ArgumentResult)}"); } _onlyTakeHasBeenCalled = true; if (_tokens is null || numberOfTokens >= _tokens.Count) { return; } CommandResult parent = (CommandResult)Parent!; var arguments = parent.Command.Arguments; int argumentIndex = arguments.IndexOf(Argument); int nextArgumentIndex = argumentIndex + 1; int tokensToPass = _tokens.Count - numberOfTokens; while (tokensToPass > 0 && nextArgumentIndex < arguments.Count) { Argument nextArgument = parent.Command.Arguments[nextArgumentIndex]; ArgumentResult nextArgumentResult; if (SymbolResultTree.TryGetValue(nextArgument, out SymbolResult? symbolResult)) { nextArgumentResult = (ArgumentResult)symbolResult; } else { // it might have not been parsed yet or due too few arguments, so we add it now nextArgumentResult = new ArgumentResult(nextArgument, SymbolResultTree, Parent); SymbolResultTree.Add(nextArgument, nextArgumentResult); } while (!nextArgumentResult.ArgumentLimitReached && tokensToPass > 0) { Token toPass = _tokens[numberOfTokens]; _tokens.RemoveAt(numberOfTokens); nextArgumentResult.AddToken(toPass); --tokensToPass; } nextArgumentIndex++; } CommandResult rootCommand = parent; // When_tokens_are_passed_on_by_custom_parser_on_last_argument_then_they_become_unmatched_tokens while (tokensToPass > 0) { Token unmatched = _tokens[numberOfTokens]; _tokens.RemoveAt(numberOfTokens); SymbolResultTree.AddUnmatchedToken(unmatched, parent, rootCommand); --tokensToPass; } } /// <inheritdoc/> public override string ToString() => $"{nameof(ArgumentResult)} {Argument.Name}: {string.Join(" ", Tokens.Select(t => $"<{t.Value}>"))}"; /// <inheritdoc/> public override void AddError(string errorMessage) { SymbolResultTree.AddError(new ParseError(errorMessage, AppliesToPublicSymbolResult)); _conversionResult = ArgumentConversionResult.Failure(this, errorMessage, ArgumentConversionResultType.Failed); } private ArgumentConversionResult ValidateAndConvert(bool useValidators) { if (!ArgumentArity.Validate(this, out ArgumentConversionResult? arityFailure)) { return ReportErrorIfNeeded(arityFailure); } // There is nothing that stops user-defined Validator from calling ArgumentResult.GetValueOrDefault. // In such cases, we can't call the validators again, as it would create infinite recursion. // GetArgumentConversionResult => ValidateAndConvert => Validator // => GetValueOrDefault => ValidateAndConvert (again) if (useValidators && Argument.HasValidators) { _validatorsHaveBeenRun = true; for (var i = 0; i < Argument.Validators.Count; i++) { Argument.Validators[i](this); } // validator provided by the user might report an error, which sets _conversionResult if (_conversionResult is not null) { return _conversionResult; } } if (Argument.HasDefaultValue && Parent!.UseDefaultValueFor(this)) { var defaultValue = Argument.GetDefaultValue(this); Implicit = true; // default value factory provided by the user might report an error, which sets _conversionResult if (_conversionResult is not null) { return _conversionResult; } return ArgumentConversionResult.Success(this, defaultValue); } if (Argument.ConvertArguments is null) { return Argument.Arity.MaximumNumberOfValues switch { 1 when _tokens is null => ArgumentConversionResult.None(this), 1 when _tokens is not null => ArgumentConversionResult.Success(this, _tokens[0]), _ => ArgumentConversionResult.Success(this, Tokens) }; } var success = Argument.ConvertArguments(this, out var value); // default value factory provided by the user might report an error, which sets _conversionResult if (_conversionResult is not null) { return _conversionResult; } if (value is ArgumentConversionResult conversionResult) { return ReportErrorIfNeeded(conversionResult); } if (success) { return ArgumentConversionResult.Success(this, value); } return ReportErrorIfNeeded( ArgumentConversionResult.ArgumentConversionCannotParse( this, Argument.ValueType, Tokens.Count > 0 ? Tokens[0].Value : "")); ArgumentConversionResult ReportErrorIfNeeded(ArgumentConversionResult result) { if (result.Result >= ArgumentConversionResultType.Failed) { SymbolResultTree.AddError(new ParseError(result.ErrorMessage!, AppliesToPublicSymbolResult)); } return result; } } /// <summary> /// Since Option.Argument is an internal implementation detail, this ArgumentResult applies to the OptionResult in public API if the parent is an OptionResult. /// </summary> private SymbolResult AppliesToPublicSymbolResult => Parent is OptionResult optionResult ? optionResult : this; } }