diff --git a/src/dotnet/ArgumentForwardingExtensions.cs b/src/dotnet/ArgumentForwardingExtensions.cs index fd47ea8e9..5e49a4c99 100644 --- a/src/dotnet/ArgumentForwardingExtensions.cs +++ b/src/dotnet/ArgumentForwardingExtensions.cs @@ -7,11 +7,14 @@ namespace Microsoft.DotNet.Cli { public static class ArgumentForwardingExtensions { + public static ArgumentsRule Forward( + this ArgumentsRule rule) => + rule.MaterializeAs(o => new ForwardedArgument(o.Arguments.SingleOrDefault())); + public static ArgumentsRule ForwardAs( this ArgumentsRule rule, string template) => - rule.MaterializeAs(o => - new ForwardedArgument(string.Format(template, o.Arguments.Single()))); + rule.MaterializeAs(o => new ForwardedArgument(template)); public static ArgumentsRule ForwardAs( this ArgumentsRule rule, diff --git a/src/dotnet/CommonOptions.cs b/src/dotnet/CommonOptions.cs index a39027bc1..c0e6d08d7 100644 --- a/src/dotnet/CommonOptions.cs +++ b/src/dotnet/CommonOptions.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Tools.Common; @@ -22,7 +23,7 @@ namespace Microsoft.DotNet.Cli "m", "minimal", "n", "normal", "d", "detailed") - .ForwardAs("/verbosity:{0}")); + .ForwardAs(o => $"/verbosity:{o.Arguments.Single()}")); public static ArgumentsRule DefaultToCurrentDirectory(this ArgumentsRule rule) => rule.With(defaultValue: () => PathUtility.EnsureTrailingSlash(Directory.GetCurrentDirectory())); diff --git a/src/dotnet/commands/dotnet-build/BuildCommandParser.cs b/src/dotnet/commands/dotnet-build/BuildCommandParser.cs index 9e9a27c1a..77f393a41 100644 --- a/src/dotnet/commands/dotnet-build/BuildCommandParser.cs +++ b/src/dotnet/commands/dotnet-build/BuildCommandParser.cs @@ -1,6 +1,7 @@ // 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.Linq; using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli @@ -12,37 +13,37 @@ namespace Microsoft.DotNet.Cli "build", ".NET Builder", Accept.ZeroOrOneArgument - .ForwardAs("{0}"), + .Forward(), CommonOptions.HelpOption(), Create.Option( "-o|--output", "Output directory in which to place built artifacts.", Accept.ExactlyOneArgument .With(name: "OUTPUT_DIR") - .ForwardAs("/p:OutputPath={0}")), + .ForwardAs(o => $"/p:OutputPath={o.Arguments.Single()}")), Create.Option( "-f|--framework", "Target framework to build for. The target framework has to be specified in the project file.", Accept.AnyOneOf(Suggest.TargetFrameworksFromProjectFile) - .ForwardAs("/p:TargetFramework={0}")), + .ForwardAs(o => $"/p:TargetFramework={o.Arguments.Single()}")), Create.Option( "-r|--runtime", "Target runtime to build for. The default is to build a portable application.", Accept.AnyOneOf(Suggest.RunTimesFromProjectFile) - .ForwardAs("/p:RuntimeIdentifier={0}")), + .ForwardAs(o => $"/p:RuntimeIdentifier={o.Arguments.Single()}")), Create.Option( "-c|--configuration", "Configuration to use for building the project. Default for most projects is \"Debug\".", Accept.ExactlyOneArgument .With(name: "CONFIGURATION") .WithSuggestionsFrom("DEBUG", "RELEASE") - .ForwardAs("/p:Configuration={0}")), + .ForwardAs(o => $"/p:Configuration={o.Arguments.Single()}")), Create.Option( "--version-suffix", "Defines the value for the $(VersionSuffix) property in the project", Accept.ExactlyOneArgument .With(name: "VERSION_SUFFIX") - .ForwardAs("/p:VersionSuffix={0}")), + .ForwardAs(o => $"/p:VersionSuffix={o.Arguments.Single()}")), Create.Option( "--no-incremental", "Disables incremental build."), diff --git a/src/dotnet/commands/dotnet-complete/CompleteCommand.cs b/src/dotnet/commands/dotnet-complete/CompleteCommand.cs index 444ecfe08..a8375687a 100644 --- a/src/dotnet/commands/dotnet-complete/CompleteCommand.cs +++ b/src/dotnet/commands/dotnet-complete/CompleteCommand.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Cli // parse the arguments var result = parser.ParseFrom("dotnet complete", args); - var complete = result["complete"]; + var complete = result["dotnet"]["complete"]; var suggestions = Suggestions(complete); diff --git a/src/dotnet/commands/dotnet-complete/CompleteCommandParser.cs b/src/dotnet/commands/dotnet-complete/CompleteCommandParser.cs index c42db5fc1..e79951fcb 100644 --- a/src/dotnet/commands/dotnet-complete/CompleteCommandParser.cs +++ b/src/dotnet/commands/dotnet-complete/CompleteCommandParser.cs @@ -15,7 +15,6 @@ namespace Microsoft.DotNet.Cli .With(name: "path"), Create.Option("--position", "", Accept.ExactlyOneArgument - .With(name: "command") - .MaterializeAs(o => int.Parse(o.Arguments.Single())))); + .With(name: "command"))); } } \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-publish/PublishCommandParser.cs b/src/dotnet/commands/dotnet-publish/PublishCommandParser.cs index 2d8cac21b..01ae347c0 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommandParser.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommandParser.cs @@ -1,6 +1,7 @@ // 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.Linq; using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli @@ -8,38 +9,39 @@ namespace Microsoft.DotNet.Cli internal static class PublishCommandParser { public static Command Publish() => - Create.Command("publish", - ".NET Publisher", - Accept.ZeroOrMoreArguments, - CommonOptions.HelpOption(), - Create.Option("-f|--framework", - "Target framework to publish for. The target framework has to be specified in the project file.", - Accept.WithSuggestionsFrom(_ => Suggest.TargetFrameworksFromProjectFile()) - .With(name: "FRAMEWORK") - .ForwardAs("/p:TargetFramework={0}")), - Create.Option("-r|--runtime", - "Publish the project for a given runtime. This is used when creating self-contained deployment. Default is to publish a framework-dependent app.", - Accept.WithSuggestionsFrom(_ => Suggest.RunTimesFromProjectFile()) - .With(name: "RUNTIME_IDENTIFIER") - .ForwardAs("/p:RuntimeIdentifier={0}")), - Create.Option("-o|--output", - "Output directory in which to place the published artifacts.", - Accept.ExactlyOneArgument - .With(name: "OUTPUT_DIR") - .ForwardAs("/p:PublishDir={0}")), - Create.Option("-c|--configuration", "Configuration to use for building the project. Default for most projects is \"Debug\".", - Accept.ExactlyOneArgument - .With(name: "CONFIGURATION") - .WithSuggestionsFrom("DEBUG", "RELEASE") - .ForwardAs("/p:Configuration={0}")), - Create.Option("--version-suffix", "Defines the value for the $(VersionSuffix) property in the project.", - Accept.ExactlyOneArgument - .With(name: "VERSION_SUFFIX") - .ForwardAs("/p:VersionSuffix={0}")), - Create.Option("--filter", "The XML file that contains the list of packages to be excluded from publish step.", - Accept.ExactlyOneArgument - .With(name: "PROFILE_XML") - .ForwardAs("/p:FilterProjectFiles={0}")), - CommonOptions.VerbosityOption()); + Create.Command( + "publish", + ".NET Publisher", + Accept.ZeroOrMoreArguments, + CommonOptions.HelpOption(), + Create.Option("-f|--framework", + "Target framework to publish for. The target framework has to be specified in the project file.", + Accept.WithSuggestionsFrom(_ => Suggest.TargetFrameworksFromProjectFile()) + .With(name: "FRAMEWORK") + .ForwardAs(o => $"/p:TargetFramework={o.Arguments.Single()}")), + Create.Option("-r|--runtime", + "Publish the project for a given runtime. This is used when creating self-contained deployment. Default is to publish a framework-dependent app.", + Accept.WithSuggestionsFrom(_ => Suggest.RunTimesFromProjectFile()) + .With(name: "RUNTIME_IDENTIFIER") + .ForwardAs(o => $"/p:RuntimeIdentifier={o.Arguments.Single()}")), + Create.Option("-o|--output", + "Output directory in which to place the published artifacts.", + Accept.ExactlyOneArgument + .With(name: "OUTPUT_DIR") + .ForwardAs(o => $"/p:PublishDir={o.Arguments.Single()}")), + Create.Option("-c|--configuration", "Configuration to use for building the project. Default for most projects is \"Debug\".", + Accept.ExactlyOneArgument + .With(name: "CONFIGURATION") + .WithSuggestionsFrom("DEBUG", "RELEASE") + .ForwardAs(o => $"/p:Configuration={o.Arguments.Single()}")), + Create.Option("--version-suffix", "Defines the value for the $(VersionSuffix) property in the project.", + Accept.ExactlyOneArgument + .With(name: "VERSION_SUFFIX") + .ForwardAs(o => $"/p:VersionSuffix={o.Arguments.Single()}")), + Create.Option("--filter", "The XML file that contains the list of packages to be excluded from publish step.", + Accept.ExactlyOneArgument + .With(name: "PROFILE_XML") + .ForwardAs(o => $"/p:FilterProjectFiles={o.Arguments.Single()}")), + CommonOptions.VerbosityOption()); } } \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-restore/RestoreCommandParser.cs b/src/dotnet/commands/dotnet-restore/RestoreCommandParser.cs index 62ab2f870..39ae995cb 100644 --- a/src/dotnet/commands/dotnet-restore/RestoreCommandParser.cs +++ b/src/dotnet/commands/dotnet-restore/RestoreCommandParser.cs @@ -1,6 +1,7 @@ // 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.Linq; using Microsoft.DotNet.Cli.CommandLine; namespace Microsoft.DotNet.Cli @@ -31,7 +32,7 @@ namespace Microsoft.DotNet.Cli "Directory to install packages in.", Accept.ExactlyOneArgument .With(name: "PACKAGES_DIRECTORY") - .ForwardAs("/p:RestorePackagesPath={0}")), + .ForwardAs(o => $"/p:RestorePackagesPath={o.Arguments.Single()}")), Create.Option( "--disable-parallel", "Disables restoring multiple projects in parallel.", @@ -42,7 +43,7 @@ namespace Microsoft.DotNet.Cli "The NuGet configuration file to use.", Accept.ExactlyOneArgument .With(name: "FILE") - .ForwardAs("/p:RestoreConfigFile={0}")), + .ForwardAs(o => $"/p:RestoreConfigFile={o.Arguments.Single()}")), Create.Option( "--no-cache", "Do not cache packages and http requests.", diff --git a/test/dotnet-msbuild.Tests/GivenDotnetRestoreInvocation.cs b/test/dotnet-msbuild.Tests/GivenDotnetRestoreInvocation.cs index 282549125..c900a6d48 100644 --- a/test/dotnet-msbuild.Tests/GivenDotnetRestoreInvocation.cs +++ b/test/dotnet-msbuild.Tests/GivenDotnetRestoreInvocation.cs @@ -26,8 +26,8 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests [InlineData(new string[] { "--no-cache" }, "/p:RestoreNoCache=true")] [InlineData(new string[] { "--ignore-failed-sources" }, "/p:RestoreIgnoreFailedSources=true")] [InlineData(new string[] { "--no-dependencies" }, "/p:RestoreRecursive=false")] - [InlineData(new string[] { "-v", "" }, @"/verbosity:")] - [InlineData(new string[] { "--verbosity", "" }, @"/verbosity:")] + [InlineData(new string[] { "-v", "minimal" }, @"/verbosity:minimal")] + [InlineData(new string[] { "--verbosity", "minimal" }, @"/verbosity:minimal")] public void MsbuildInvocationIsCorrect(string[] args, string expectedAdditionalArgs) { expectedAdditionalArgs = (string.IsNullOrEmpty(expectedAdditionalArgs) ? "" : $" {expectedAdditionalArgs}"); diff --git a/test/dotnet.Tests/ParserTests/ArgumentForwardingExtensionsTests.cs b/test/dotnet.Tests/ParserTests/ArgumentForwardingExtensionsTests.cs index 8763f4c20..c40b07888 100644 --- a/test/dotnet.Tests/ParserTests/ArgumentForwardingExtensionsTests.cs +++ b/test/dotnet.Tests/ParserTests/ArgumentForwardingExtensionsTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using FluentAssertions; +using System.Linq; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.CommandLine; using Xunit; @@ -13,28 +14,31 @@ namespace Microsoft.DotNet.Tests.ParserTests public class ArgumentForwardingExtensionsTests { [Fact] - public void An_outgoing_command_line_can_be_generated_based_on_a_parse_result() + public void AnOutgoingCommandLineCanBeGeneratedBasedOnAParseResult() { var command = Command("the-command", "", - Option("-o|--one", "", - ZeroOrOneArgument.ForwardAs("/i:{0}")), - Option("-t|--two", "", - ZeroOrOneArgument.ForwardAs("/s:{0}"))); + Option("-o|--one", "", + ZeroOrOneArgument + .ForwardAs(o => $"/i:{o.Arguments.Single()}")), + Option("-t|--two", "", + NoArguments + .ForwardAs("/s:true"))); - var result = command.Parse("the-command -t argument-two-value -o 123"); + var result = command.Parse("the-command -t -o 123"); result["the-command"] .OptionValuesToBeForwarded() .Should() - .BeEquivalentTo("/i:123", "/s:argument-two-value"); + .BeEquivalentTo("/i:123", "/s:true"); } [Fact] public void MultipleArgumentsCanBeJoinedWhenForwarding() { var command = Command("the-command", "", - Option("-x", "", - ZeroOrMoreArguments.ForwardAs(o => $"/x:{string.Join("&", o.Arguments)}"))); + Option("-x", "", + ZeroOrMoreArguments + .ForwardAs(o => $"/x:{string.Join("&", o.Arguments)}"))); var result = command.Parse("the-command -x one -x two"); @@ -43,5 +47,21 @@ namespace Microsoft.DotNet.Tests.ParserTests .Should() .BeEquivalentTo("/x:one&two"); } + + [Fact] + public void AnArgumentCanBeForwardedAsIs() + { + var command = Command("the-command", "", + Option("-x", "", + ZeroOrMoreArguments + .Forward())); + + var result = command.Parse("the-command -x one"); + + result["the-command"] + .OptionValuesToBeForwarded() + .Should() + .BeEquivalentTo("one"); + } } } \ No newline at end of file diff --git a/test/dotnet.Tests/ParserTests/RestoreParserTests.cs b/test/dotnet.Tests/ParserTests/RestoreParserTests.cs index 44733f907..cb3128cc0 100644 --- a/test/dotnet.Tests/ParserTests/RestoreParserTests.cs +++ b/test/dotnet.Tests/ParserTests/RestoreParserTests.cs @@ -22,13 +22,13 @@ namespace Microsoft.DotNet.Tests.ParserTests [Fact] public void RestoreCapturesArgumentsToForwardToMSBuildWhenTargetIsSpecified() { - var parser = Parser.Instance["dotnet"]["restore"]; + var parser = Parser.Instance; - var result = parser.Parse(@".\some.csproj --packages c:\.nuget\packages /p:SkipInvalidConfigurations=true"); + var result = parser.Parse(@"dotnet restore .\some.csproj --packages c:\.nuget\packages /p:SkipInvalidConfigurations=true"); output.WriteLine(result.Diagram()); - result["restore"] + result["dotnet"]["restore"] .Arguments .Should() .BeEquivalentTo(@".\some.csproj", @"/p:SkipInvalidConfigurations=true"); @@ -37,13 +37,13 @@ namespace Microsoft.DotNet.Tests.ParserTests [Fact] public void RestoreCapturesArgumentsToForwardToMSBuildWhenTargetIsNotSpecified() { - var parser = Parser.Instance["dotnet"]["restore"]; + var parser = Parser.Instance; - var result = parser.Parse(@"--packages c:\.nuget\packages /p:SkipInvalidConfigurations=true"); + var result = parser.Parse(@"dotnet restore --packages c:\.nuget\packages /p:SkipInvalidConfigurations=true"); output.WriteLine(result.Diagram()); - result["restore"] + result["dotnet"]["restore"] .Arguments .Should() .BeEquivalentTo(@"/p:SkipInvalidConfigurations=true");