|
// 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.Collections.Generic;
using System.CommandLine.Binding;
using System.CommandLine.Parsing;
using System.CommandLine.Completions;
using System.Linq;
namespace System.CommandLine
{
/// <summary>
/// A symbol defining a value that can be passed on the command line to a <see cref="Command">command</see> or <see cref="Option">option</see>.
/// </summary>
public abstract class Argument : Symbol
{
private ArgumentArity _arity;
private TryConvertArgument? _convertArguments;
private List<Func<CompletionContext, IEnumerable<CompletionItem>>>? _completionSources = null;
private List<Action<ArgumentResult>>? _validators = null;
/// <summary>
/// Initializes a new instance of the Argument class.
/// </summary>
/// <param name="name">The name of the argument. This can be used to look up the parsed value and is displayed in help</param>
protected Argument(string name) : base(name, allowWhitespace: true)
{
}
/// <summary>
/// Gets or sets the arity of the argument.
/// </summary>
public ArgumentArity Arity
{
get
{
if (!_arity.IsNonDefault)
{
_arity = ArgumentArity.Default(this, FirstParent);
}
return _arity;
}
set => _arity = value;
}
/// <summary>
/// Gets or sets a value indicating whether this argument captures all remaining tokens.
/// </summary>
/// <remarks>
/// When set to <see langword="true"/>, once the parser starts filling this argument,
/// all subsequent tokens are consumed as argument values regardless of whether they
/// match known options or commands. This behaves as if <c>--</c> were implicitly
/// inserted before the argument's first value.
/// <para>
/// An argument with this property set to <see langword="true"/> must be the last
/// argument defined on its parent command.
/// </para>
/// </remarks>
public bool CaptureRemainingTokens { get; set; }
/// <summary>
/// Gets or sets the placeholder name shown in usage help for the argument's value.
/// The value will be wrapped in angle brackets (<c><</c> and <c>></c>).
/// </summary>
/// <remarks>
/// If <c>null</c>, the <see cref="Symbol.Name"/> of the argument will be used.
/// </remarks>
/// <example>
/// An argument with <see cref="Symbol.Name"/> of <c>argument</c> and a
/// <see cref="HelpName"/> of <c>Value</c> will be shown in usage help as:
/// <c><Value></c>. If <see cref="HelpName"/> is not set,
/// help output will show: <c><argument></c>.
/// </example>
/// <value>
/// The name to show as the placeholder for the argument's value.
/// </value>
public string? HelpName { get; set; }
internal TryConvertArgument? ConvertArguments
{
get => _convertArguments ??= ArgumentConverter.GetConverter(this);
set => _convertArguments = value;
}
/// <summary>
/// Gets the list of completion sources for the argument.
/// </summary>
public List<Func<CompletionContext, IEnumerable<CompletionItem>>> CompletionSources
{
get
{
if (_completionSources is null)
{
Type? valueType = ValueType;
if (IsBoolean())
{
_completionSources = new ()
{
static _ => new CompletionItem[]
{
new(bool.TrueString),
new(bool.FalseString)
}
};
}
else if (!valueType.IsPrimitive && (valueType.IsEnum || (valueType.TryGetNullableType(out valueType) && valueType.IsEnum)))
{
_completionSources = new()
{
_ => Enum.GetNames(valueType).Select(n => new CompletionItem(n))
};
}
else
{
_completionSources = new();
}
}
return _completionSources;
}
}
/// <summary>
/// Gets or sets the <see cref="Type" /> that the argument's parsed tokens will be converted to.
/// </summary>
public abstract Type ValueType { get; }
/// <summary>
/// Provides a list of argument validators. Validators can be used
/// to provide custom errors based on user input.
/// </summary>
public List<Action<ArgumentResult>> Validators => _validators ??= new ();
internal bool HasValidators => (_validators?.Count ?? 0) > 0;
/// <summary>
/// Gets the default value for the argument.
/// </summary>
/// <returns>Returns the default value for the argument, if defined. Null otherwise.</returns>
public object? GetDefaultValue()
{
var command = Parents.FlattenBreadthFirst(x => x.Parents)
.OfType<Command>()
.FirstOrDefault();
return GetDefaultValue(new ArgumentResult(this, new SymbolResultTree(command), null));
}
internal abstract object? GetDefaultValue(ArgumentResult argumentResult);
/// <summary>
/// Specifies if a default value is defined for the argument.
/// </summary>
public abstract bool HasDefaultValue { get; }
/// <inheritdoc />
public override IEnumerable<CompletionItem> GetCompletions(CompletionContext context)
{
return CompletionSources
.SelectMany(source => source.Invoke(context))
.Distinct()
.OrderBy(c => c.SortText, StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
public override string ToString() => $"{nameof(Argument)}: {Name}";
internal bool IsBoolean() => ValueType == typeof(bool) || ValueType == typeof(bool?);
internal static Argument None { get; } = new NoArgument();
private sealed class NoArgument : Argument
{
internal NoArgument() : base("@none")
{
}
public override Type ValueType { get; } = typeof(void);
internal override object? GetDefaultValue(ArgumentResult argumentResult) => null;
public override bool HasDefaultValue => false;
}
}
}
|