| File: Command.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.Collections; using System.Collections.Generic; using System.CommandLine.Completions; using System.CommandLine.Invocation; using System.CommandLine.Parsing; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Threading; namespace System.CommandLine { /// <summary> /// Represents a specific action that the application performs. /// </summary> /// <remarks> /// Use the Command object for actions that correspond to a specific string (the command name). /// For simple applications that only have one action, see <see cref="RootCommand"/>. /// For example, <c>dotnet run</c> uses <c>run</c> as the command. /// </remarks> public class Command : Symbol, IEnumerable { internal AliasSet? _aliases; private ChildSymbolList<Argument>? _arguments; private ChildSymbolList<Option>? _options; private ChildSymbolList<Command>? _subcommands; private List<Action<CommandResult>>? _validators; /// <summary> /// Initializes a new instance of the Command class. /// </summary> /// <param name="name">The name of the command.</param> /// <param name="description">The description of the command, shown in help.</param> public Command(string name, string? description = null) : base(name) => Description = description; /// <summary> /// Gets the child symbols. /// </summary> public IEnumerable<Symbol> Children { get { foreach (var command in Subcommands) yield return command; foreach (var option in Options) yield return option; foreach (var argument in Arguments) yield return argument; } } /// <summary> /// Gets all of the arguments for the command. /// </summary> public IList<Argument> Arguments => _arguments ??= new(this); internal bool HasArguments => _arguments?.Count > 0 ; /// <summary> /// Gets all of the options for the command. /// </summary> /// <remarks> /// This collection doesn't include options on parent commands where <see cref="Option.Recursive">Option.Recursive</see> is <see langword="true" />. Those options are valid under the current command but don't appear in this collection. /// </remarks> public IList<Option> Options => _options ??= new (this); internal bool HasOptions => _options?.Count > 0; /// <summary> /// Gets all of the subcommands for the command. /// </summary> public IList<Command> Subcommands => _subcommands ??= new(this); internal bool HasSubcommands => _subcommands is not null && _subcommands.Count > 0; /// <summary> /// Gets the validators to the command. Validators can be used /// to create custom validation logic. /// </summary> public List<Action<CommandResult>> Validators => _validators ??= new (); internal bool HasValidators => _validators is not null && _validators.Count > 0; /// <summary> /// Gets the unique set of strings that can be used on the command line to specify the command. /// </summary> /// <remarks>The collection does not contain the <see cref="Symbol.Name"/> of the Command.</remarks> public ICollection<string> Aliases => _aliases ??= new(); /// <summary> /// Gets or sets the <see cref="CommandLineAction"/> for the Command. The handler represents the action /// that will be performed when the Command is invoked. /// </summary> /// <remarks> /// <para>Use one of the <see cref="SetAction(Action{ParseResult})" /> overloads to construct a handler.</para> /// <para>If the handler is not specified, parser errors will be generated for command line input that /// invokes this Command.</para></remarks> public CommandLineAction? Action { get; set; } /// <summary> /// Sets a synchronous action to be run when the command is invoked. /// </summary> public void SetAction(Action<ParseResult> action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } Action = new AnonymousSynchronousCommandLineAction(context => { action(context); return 0; }); } /// <summary> /// Sets a synchronous action to be run when the command is invoked. /// </summary> /// <remarks>The value returned from the <paramref name="action"/> delegate can be used to set the process exit code.</remarks> public void SetAction(Func<ParseResult, int> action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } Action = new AnonymousSynchronousCommandLineAction(action); } /// <summary> /// Sets an asynchronous action to be run when the command is invoked. /// </summary> public void SetAction(Func<ParseResult, CancellationToken, Task> action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } Action = new AnonymousAsynchronousCommandLineAction(async (context, cancellationToken) => { await action(context, cancellationToken); return 0; }); } /// <summary> /// Sets an asynchronous action to be run when the command is invoked. /// </summary> /// <remarks> /// When possible, prefer using the <see cref="SetAction(Func{ParseResult, CancellationToken, Task})"/> overload /// and passing the <see cref="CancellationToken"/> parameter to the async method(s) called by the action. /// </remarks> // Hide from intellisense, it's public to avoid the compiler choosing a sync overload // for an async action (and fire and forget issue described in https://github.com/dotnet/command-line-api/issues/2562). [EditorBrowsable(EditorBrowsableState.Never)] public void SetAction(Func<ParseResult, Task> action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } Action = new AnonymousAsynchronousCommandLineAction(async (context, _) => { await action(context); return 0; }); } /// <summary> /// Sets an asynchronous action to be run when the command is invoked. /// </summary> /// <remarks> /// When possible, prefer using the <see cref="SetAction(Func{ParseResult, CancellationToken, Task{int}})"/> overload /// and passing the <see cref="CancellationToken"/> parameter to the async method(s) called by the action. /// </remarks> // Hide from intellisense, it's public to avoid the compiler choosing a sync overload // for an async action (and fire and forget issue described in https://github.com/dotnet/command-line-api/issues/2562). [EditorBrowsable(EditorBrowsableState.Never)] public void SetAction(Func<ParseResult, Task<int>> action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } Action = new AnonymousAsynchronousCommandLineAction(async (context, _) => { return await action(context); }); } /// <summary> /// Sets an asynchronous action when the command is invoked. /// </summary> /// <remarks>The value returned from the <paramref name="action"/> delegate can be used to set the process exit code.</remarks> public void SetAction(Func<ParseResult, CancellationToken, Task<int>> action) { if (action is null) { throw new ArgumentNullException(nameof(action)); } Action = new AnonymousAsynchronousCommandLineAction(action); } /// <summary> /// Adds a <see cref="Argument"/> to the command. /// </summary> /// <param name="argument">The option to add to the command.</param> public void Add(Argument argument) => Arguments.Add(argument); /// <summary> /// Adds an <see cref="Option"/> to the command. /// </summary> /// <param name="option">The option to add to the command.</param> public void Add(Option option) => Options.Add(option); /// <summary> /// Adds a <see cref="Command"/> to the command. /// </summary> /// <param name="command">The Command to add to the command.</param> public void Add(Command command) => Subcommands.Add(command); /// <summary> /// Gets or sets a value that indicates whether unmatched tokens should be treated as errors. /// </summary> /// <value><see langword="true"/> to fail validation when an extra command or argument is provided; otherwise, <see langword="false"/>.</value> public bool TreatUnmatchedTokensAsErrors { get; set; } = true; /// <inheritdoc /> [DebuggerStepThrough] [EditorBrowsable(EditorBrowsableState.Never)] // hide from intellisense, it's public for C# collection initializer IEnumerator IEnumerable.GetEnumerator() => Children.GetEnumerator(); /// <summary> /// Parses an array strings using the command. /// </summary> /// <param name="args">The string arguments to parse.</param> /// <param name="configuration">The configuration on which the parser's grammar and behaviors are based.</param> /// <returns>A parse result describing the outcome of the parse operation.</returns> public ParseResult Parse(IReadOnlyList<string> args, ParserConfiguration? configuration = null) => CommandLineParser.Parse(this, args, configuration); /// <summary> /// Parses a command line string value using the command. /// </summary> /// <remarks>The command line string input will be split into tokens as if it had been passed on the command line.</remarks> /// <param name="commandLine">A command line string to parse, which can include spaces and quotes equivalent to what can be entered into a terminal.</param> /// <param name="configuration">The configuration on which the parser's grammar and behaviors are based.</param> /// <returns>A parse result describing the outcome of the parse operation.</returns> public ParseResult Parse(string commandLine, ParserConfiguration? configuration = null) => CommandLineParser.Parse(this, commandLine, configuration); /// <inheritdoc /> public override IEnumerable<CompletionItem> GetCompletions(CompletionContext context) { var completions = new List<CompletionItem>(); if (context.WordToComplete is { } textToMatch) { if (HasSubcommands) { var commands = Subcommands; for (int i = 0; i < commands.Count; i++) { AddCompletionsFor(commands[i], commands[i]._aliases); } } if (HasOptions) { var options = Options; for (int i = 0; i < options.Count; i++) { AddCompletionsFor(options[i], options[i]._aliases); } } if (HasArguments) { var arguments = Arguments; for (int i = 0; i < arguments.Count; i++) { var argument = arguments[i]; foreach (var completion in argument.GetCompletions(context)) { if (completion.Label.ContainsCaseInsensitive(textToMatch)) { completions.Add(completion); } } } } SymbolNode? parent = FirstParent; while (parent is not null) { Command parentCommand = (Command)parent.Symbol; if (context.IsEmpty || context.ParseResult.GetResult(parentCommand) is not null) { if (parentCommand.HasOptions) { for (var i = 0; i < parentCommand.Options.Count; i++) { var option = parentCommand.Options[i]; if (option.Recursive) { AddCompletionsFor(option, option._aliases); } } } parent = parent.Symbol.FirstParent; } else { parent = parent.Next; } } } return completions .OrderBy(item => item.SortText.IndexOfCaseInsensitive(context.WordToComplete)) .ThenBy(symbol => symbol.Label, StringComparer.OrdinalIgnoreCase); void AddCompletionsFor(Symbol identifier, AliasSet? aliases) { if (!identifier.Hidden) { if (identifier.Name.ContainsCaseInsensitive(textToMatch)) { completions.Add(new CompletionItem(identifier.Name, CompletionItem.KindKeyword, detail: identifier.Description)); } if (aliases is not null) { foreach (string alias in aliases) { if (alias.ContainsCaseInsensitive(textToMatch)) { completions.Add(new CompletionItem(alias, CompletionItem.KindKeyword, detail: identifier.Description)); } } } } } } internal bool EqualsNameOrAlias(string name) => Name.Equals(name, StringComparison.Ordinal) || (_aliases is not null && _aliases.Contains(name)); } }