Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public System.Collections.Generic.List<System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>>> CompletionSources { get; }
public System.Boolean HasDefaultValue { get; }
public System.String HelpName { get; set; }
public System.Boolean Recursive { get; set; }
public System.Collections.Generic.List<System.Action<System.CommandLine.Parsing.ArgumentResult>> Validators { get; }
public System.Type ValueType { get; }
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
Expand Down
24 changes: 24 additions & 0 deletions src/System.CommandLine.Tests/Help/HelpBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,30 @@ public void Usage_section_for_subcommand_shows_arguments_for_subcommand_and_pare
_console.ToString().Should().Contain(expected);
}

[Fact]
public void Usage_section_for_subcommand_omits_non_recursive_parent_arguments()
{
var inner = new Command("inner", "command help")
{
new Option<string>("-v") { Description = "Sets the verbosity" },
new Argument<string[]>("inner-args")
};
_ = new Command("outer", "command help")
{
inner,
new Argument<string[]>("outer-args") { Recursive = false }
};

_helpBuilder.Write(inner, _console);

var expected =
$"Usage:{NewLine}" +
$"{_indentation}outer inner [<inner-args>...] [options]";

_console.ToString().Should().Contain(expected);
_console.ToString().Should().NotContain("<outer-args>");
}

[Fact]
public void Usage_section_does_not_show_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_not_specified()
{
Expand Down
20 changes: 20 additions & 0 deletions src/System.CommandLine.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,26 @@ public void Arguments_can_match_subcommands()
.BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four");
}

[Fact]
public void Non_recursive_parent_argument_is_unmatched_when_subcommand_is_invoked()
{
var rootArg = new Argument<string>("rootArg") { Recursive = false };
var subcommand = new Command("subcommand");
var root = new RootCommand
{
rootArg,
subcommand
};

var result = root.Parse("value subcommand");

result.CommandResult.Command.Should().BeSameAs(subcommand);
result.UnmatchedTokens.Should().BeEquivalentTo("value");
result.Errors.Should().ContainSingle(e =>
e.Message == LocalizationResources.UnrecognizedCommandOrArgument("value"));
result.GetValue(rootArg).Should().BeNull();
}

[Theory]
[InlineData("-x=-y")]
[InlineData("-x:-y")]
Expand Down
6 changes: 6 additions & 0 deletions src/System.CommandLine/Argument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,11 @@ internal NoArgument() : base("@none")

public override bool HasDefaultValue => false;
}

/// <summary>
/// Gets or sets a value indicating whether this argument is inherited by child commands.
/// </summary>
public bool Recursive { get; set; } = true;

}
}
9 changes: 6 additions & 3 deletions src/System.CommandLine/Help/HelpBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,12 @@ IEnumerable<string> GetUsageParts()

yield return (parentCommand is RootCommand root ? root.HelpName : null) ?? parentCommand.Name;

if (parentCommand.Arguments.Any())
var argumentToDisplay = parentCommand == command
? parentCommand.Arguments.Where(a => !a.Hidden).ToList()
: parentCommand.Arguments.Where(a => a.Recursive && !a.Hidden).ToList();
if (argumentToDisplay.Any())
{
yield return FormatArgumentUsage(parentCommand.Arguments);
yield return FormatArgumentUsage(argumentToDisplay);
}
}

Expand Down Expand Up @@ -168,7 +171,7 @@ private IEnumerable<TwoColumnHelpRow> GetCommandArgumentRows(Command command, He
command
.RecurseWhileNotNull(c => c.Parents.OfType<Command>().FirstOrDefault())
.Reverse()
.SelectMany(cmd => cmd.Arguments.Where(a => !a.Hidden))
.SelectMany(cmd => cmd.Arguments.Where(a => !a.Hidden && (cmd == command || a.Recursive)))
.Select(a => GetTwoColumnRow(a, context))
.Distinct();

Expand Down
33 changes: 31 additions & 2 deletions src/System.CommandLine/Parsing/ParseOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,50 @@ internal ParseResult Parse()
private void ParseSubcommand()
{
Command command = (Command)CurrentToken.Symbol!;
var parentCommandResult = _innermostCommandResult;

_innermostCommandResult = new CommandResult(
command,
CurrentToken,
_symbolResultTree,
_innermostCommandResult);
parentCommandResult);

_symbolResultTree.Add(command, _innermostCommandResult);

MoveNonRecursiveParentArgumentsToUnmatched(parentCommandResult, _innermostCommandResult);

Advance();

ParseCommandChildren();
}

private void MoveNonRecursiveParentArgumentsToUnmatched(CommandResult parent, CommandResult innermost)
{
if (!parent.Command.HasArguments)
{
return;
}

var arguments = parent.Command.Arguments;
for (var i = 0; i < arguments.Count; i++)
{
Argument argument = arguments[i];

if (!argument.Recursive &&
_symbolResultTree.TryGetValue(argument, out SymbolResult? parsedResult) &&
parsedResult is ArgumentResult argumentResult &&
ReferenceEquals(argumentResult.Parent, parent))
{
foreach (var token in argumentResult.Tokens)
{
_symbolResultTree.AddUnmatchedToken(token, innermost, _rootCommandResult);
}

_symbolResultTree.Remove(argument);
}
}
}

private void ParseCommandChildren()
{
int currentArgumentCount = 0;
Expand Down Expand Up @@ -411,7 +441,6 @@ private void ValidateAndAddDefaultResults()
while (currentResult is not null)
{
currentResult.Validate(isInnermostCommand: false);

currentResult = currentResult.Parent as CommandResult;
}

Expand Down