From 64b6f606b3583fd96d8a276d89d2abc168cb2ffa Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:35:44 +0100 Subject: [PATCH 01/41] Update test --- CommandLineParser.Tests/CommandLineParserTests.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 05d5e49..f80d967 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -204,11 +204,17 @@ public void ParseWithCommandTests() public void ParseCommandTests(string[] args, string result1, string result2) { var parser = new CommandLineParser(); + var wait = new ManualResetEvent(false); parser.AddCommand() .Name("-a", "--add") .Required() - .OnExecuting((opt1, opt2) => Assert.Equal(result2, opt2.Message)) + .OnExecuting((opt1, opt2) => + { + wait.Set(); + + Assert.Equal(result2, opt2.Message); + }) .Configure(c => c.Message) .Name("-m", "--message") .Required(); @@ -222,6 +228,8 @@ public void ParseCommandTests(string[] args, string result1, string result2) Assert.False(result.HasErrors); Assert.Equal(result1, result.Result.Message); + + Assert.True(wait.WaitOne(2000)); } [Fact] From 0df818dbde9d043d0419322ef1b7105a9994576a Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:12:11 +0100 Subject: [PATCH 02/41] Rename to description --- .../Abstractions/Command/ICommandBuilder'.cs | 6 +++--- .../Abstractions/Command/ICommandBuilder.cs | 6 +++--- .../Command/ICommandConfigurationBuilder.cs | 4 ++-- .../Abstractions/ICommandLineOption.cs | 2 +- CommandLineParser/Abstractions/IOptionBuilder'.cs | 2 +- CommandLineParser/CommandLineParser.cs | 4 ++-- .../Core/Attributes/DescriptionAttribute.cs | 11 +++++++++++ .../Core/Attributes/HelpTextAttribute.cs | 12 ------------ .../Core/Command/CommandLineCommandBase.cs | 2 +- .../CommandLineCommand`TOption`TCommandOption.cs | 14 +++++++------- CommandLineParser/Core/CommandLineOption.cs | 4 ++-- CommandLineParser/Core/CommandLineOptionBase.cs | 2 +- 12 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 CommandLineParser/Core/Attributes/DescriptionAttribute.cs delete mode 100644 CommandLineParser/Core/Attributes/HelpTextAttribute.cs diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index c1edcb2..7663183 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -28,11 +28,11 @@ public interface ICommandBuilder : ICommandConfigurationBuilde new ICommandBuilder Required(bool required = true); /// - /// Configures the help text for the command + /// Describes the command, used in the usage output. /// - /// True or false + /// description of the command /// - new ICommandBuilder HelpText(string help); + new ICommandBuilder Description(string desc); /// /// Configures the command name diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs index c86f033..45a87b3 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs @@ -20,11 +20,11 @@ public interface ICommandBuilder ICommandBuilder Required(bool required = true); /// - /// Configures the help text for the command + /// Describes the command, used in the usage output. /// - /// True or false + /// description of the command /// - ICommandBuilder HelpText(string help); + ICommandBuilder Description(string description); /// /// Configures the command name diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs index 88c4392..4ed8349 100644 --- a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs @@ -10,11 +10,11 @@ public interface ICommandConfigurationBuilder ICommandConfigurationBuilder Required(bool required = true); /// - /// Configures the help text for the command + /// Configures the description text for the command /// /// True or false /// - ICommandConfigurationBuilder HelpText(string help); + ICommandConfigurationBuilder Description(string description); /// /// Configures the command name diff --git a/CommandLineParser/Abstractions/ICommandLineOption.cs b/CommandLineParser/Abstractions/ICommandLineOption.cs index 98a3271..80f43ba 100644 --- a/CommandLineParser/Abstractions/ICommandLineOption.cs +++ b/CommandLineParser/Abstractions/ICommandLineOption.cs @@ -7,7 +7,7 @@ public interface ICommandLineOption { string ShortName { get; } string LongName { get; } - string HelpText { get; } + string Description { get; } bool IsRequired { get; } bool HasShortName { get; } bool HasLongName { get; } diff --git a/CommandLineParser/Abstractions/IOptionBuilder'.cs b/CommandLineParser/Abstractions/IOptionBuilder'.cs index 939824b..b5fd015 100644 --- a/CommandLineParser/Abstractions/IOptionBuilder'.cs +++ b/CommandLineParser/Abstractions/IOptionBuilder'.cs @@ -17,7 +17,7 @@ public interface IOptionBuilder /// /// /// - IOptionBuilder HelpText(string help); + IOptionBuilder Description(string description); /// /// Specify the default value for this option diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index f08fee8..f51f2f6 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -297,8 +297,8 @@ private void InitialzeModel() case DefaultValueAttribute defaultValue: actions.Add(() => ConfigureInternal(lambda, key).Default(defaultValue.DefaultValue)); break; - case HelpTextAttribute helpText: - actions.Add(() => ConfigureInternal(lambda, key).HelpText(helpText.HelpText)); + case DescriptionAttribute helpText: + actions.Add(() => ConfigureInternal(lambda, key).Description(helpText.Description)); break; case NameAttribute name: actions.Add(() => ConfigureInternal(lambda, key).Name(name.ShortName, name.LongName)); diff --git a/CommandLineParser/Core/Attributes/DescriptionAttribute.cs b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs new file mode 100644 index 0000000..97ec330 --- /dev/null +++ b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace MatthiWare.CommandLine.Core.Attributes +{ + public class DescriptionAttribute : BaseAttribute + { + public string Description { get; private set; } + + public DescriptionAttribute(string helpText) => Description = helpText; + } +} diff --git a/CommandLineParser/Core/Attributes/HelpTextAttribute.cs b/CommandLineParser/Core/Attributes/HelpTextAttribute.cs deleted file mode 100644 index 24e550a..0000000 --- a/CommandLineParser/Core/Attributes/HelpTextAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MatthiWare.CommandLine.Core.Attributes -{ - [Obsolete("Help texts are currently not working.", false)] - public class HelpTextAttribute : BaseAttribute - { - public string HelpText { get; private set; } - - public HelpTextAttribute(string helpText) => HelpText = helpText; - } -} diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 14f8e3c..23c8fa9 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -17,7 +17,7 @@ internal abstract class CommandLineCommandBase : public string ShortName { get; protected set; } public string LongName { get; protected set; } - public string HelpText { get; protected set; } + public string Description { get; protected set; } public bool IsRequired { get; protected set; } public bool HasShortName => ShortName != null; public bool HasLongName => LongName != null; diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 551ed31..c44e812 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -14,8 +14,8 @@ namespace MatthiWare.CommandLine.Core.Command internal class CommandLineCommand : CommandLineCommandBase, ICommandBuilder, - ICommandConfigurationBuilder, ICommandBuilder, + ICommandConfigurationBuilder, IOptionConfigurator where TOption : class where TCommandOption : class, new() @@ -134,9 +134,9 @@ ICommandBuilder ICommandBuilder.Required(bool required) return this; } - ICommandBuilder ICommandBuilder.HelpText(string help) + ICommandBuilder ICommandBuilder.Description(string description) { - HelpText = help; + Description = description; return this; } @@ -170,9 +170,9 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder.Required(bool required return this; } - ICommandConfigurationBuilder ICommandConfigurationBuilder.HelpText(string help) + ICommandConfigurationBuilder ICommandConfigurationBuilder.Description(string description) { - HelpText = help; + Description = description; return this; } @@ -199,9 +199,9 @@ public ICommandBuilder InvokeCommand(bool invoke) return this; } - ICommandBuilder ICommandBuilder.HelpText(string help) + ICommandBuilder ICommandBuilder.Description(string help) { - HelpText = help; + Description = help; return this; } diff --git a/CommandLineParser/Core/CommandLineOption.cs b/CommandLineParser/Core/CommandLineOption.cs index f5706e3..675b22d 100644 --- a/CommandLineParser/Core/CommandLineOption.cs +++ b/CommandLineParser/Core/CommandLineOption.cs @@ -63,9 +63,9 @@ IOptionBuilder IOptionBuilder.Default(object defaultValue) return this; } - IOptionBuilder IOptionBuilder.HelpText(string help) + IOptionBuilder IOptionBuilder.Description(string help) { - HelpText = help; + Description = help; return this; } diff --git a/CommandLineParser/Core/CommandLineOptionBase.cs b/CommandLineParser/Core/CommandLineOptionBase.cs index 4200d36..86dd035 100644 --- a/CommandLineParser/Core/CommandLineOptionBase.cs +++ b/CommandLineParser/Core/CommandLineOptionBase.cs @@ -11,7 +11,7 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption { public string ShortName { get; protected set; } public string LongName { get; protected set; } - public string HelpText { get; protected set; } + public string Description { get; protected set; } public bool IsRequired { get; protected set; } public bool HasDefault { get; protected set; } public bool HasShortName => !string.IsNullOrWhiteSpace(ShortName); From cee860e164281e25b827c30e2168b4969785171e Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:12:45 +0100 Subject: [PATCH 03/41] Fix codefactor issues in tests, renaming. --- CommandLineParser.Tests/Command/CommandTests.cs | 2 -- CommandLineParser.Tests/CommandLineModelTests.cs | 8 ++++---- CommandLineParser.Tests/CommandLineParserTests.cs | 1 - CommandLineParser.Tests/OptionBuilderTest.cs | 4 ++-- .../Parsing/Resolvers/BoolResolverTests.cs | 2 -- .../Parsing/Resolvers/DoubleResolverTests.cs | 3 --- .../Parsing/Resolvers/IntResolverTests.cs | 4 ---- .../Parsing/Resolvers/StringResovlerTests.cs | 2 -- CommandLineParser.Tests/XUnitExtensions.cs | 4 ---- 9 files changed, 6 insertions(+), 24 deletions(-) diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index 006b793..dd4641d 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -8,7 +8,6 @@ namespace MatthiWare.CommandLineParser.Tests.Command { public class CommandTests { - [Fact] public void ConfiguringCommandsIncreasesTotalCommandInParser() { @@ -75,6 +74,5 @@ public override void OnExecute(object options, object commandOptions) base.OnExecute(options, commandOptions); } } - } } diff --git a/CommandLineParser.Tests/CommandLineModelTests.cs b/CommandLineParser.Tests/CommandLineModelTests.cs index 305b91b..e3423b0 100644 --- a/CommandLineParser.Tests/CommandLineModelTests.cs +++ b/CommandLineParser.Tests/CommandLineModelTests.cs @@ -25,7 +25,7 @@ public void TestBasicModel() Assert.True(message.HasDefault); Assert.True(message.IsRequired); - Assert.Equal("Help", message.HelpText); + Assert.Equal("Help", message.Description); } [Fact] @@ -35,7 +35,7 @@ public void TestBasicModelWithOverwritingUsingFluentApi() parser.Configure(_ => _.Message) .Required(false) - .HelpText("Different"); + .Description("Different"); Assert.Equal(1, parser.Options.Count); @@ -51,12 +51,12 @@ public void TestBasicModelWithOverwritingUsingFluentApi() Assert.True(message.HasDefault); Assert.False(message.IsRequired); - Assert.Equal("Different", message.HelpText); + Assert.Equal("Different", message.Description); } private class Model { - [Required, Name("-m", "--message"), DefaultValue("not found"), HelpText("Help")] + [Required, Name("-m", "--message"), DefaultValue("not found"), Description("Help")] public string Message { get; set; } } } diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index f80d967..ce42c5d 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -180,7 +180,6 @@ public void ParseWithCommandTests() wait.Set(); }); - addCmd.Configure(opt => opt.Message) .Name("-m", "--message") .Required(); diff --git a/CommandLineParser.Tests/OptionBuilderTest.cs b/CommandLineParser.Tests/OptionBuilderTest.cs index 9f7fabd..4157e46 100644 --- a/CommandLineParser.Tests/OptionBuilderTest.cs +++ b/CommandLineParser.Tests/OptionBuilderTest.cs @@ -26,7 +26,7 @@ public void OptionBuilderConfiguresOptionCorrectly() builder .Default(sDefault) - .HelpText(sHelp) + .Description(sHelp) .Name(sShort, sLong) .Required(); @@ -36,7 +36,7 @@ public void OptionBuilderConfiguresOptionCorrectly() Assert.True(option.HasLongName); Assert.Equal(sLong, option.LongName); - Assert.Equal(sHelp, option.HelpText); + Assert.Equal(sHelp, option.Description); Assert.True(option.HasShortName); Assert.Equal(sShort, option.ShortName); diff --git a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs index e0e94bd..80c0d1c 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs @@ -6,7 +6,6 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { public class BoolResolverTests { - [Theory] [InlineData("yes")] [InlineData("1")] @@ -32,6 +31,5 @@ public void TestResolveFalse(string input) Assert.False(result); } - } } diff --git a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs index ddd4197..0806537 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs @@ -4,7 +4,6 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { - public class DoubleResolverTests { [Theory] @@ -35,7 +34,5 @@ public void TestResolve(double expected, string value) Assert.Equal(expected, resolver.Resolve(model)); } - - } } diff --git a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs index 6c4590f..2bc2201 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs @@ -4,10 +4,8 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { - public class IntResolverTests { - [Theory] [InlineData(true, "-m", "5")] [InlineData(false, "-m", "false")] @@ -29,7 +27,5 @@ public void TestResolve(int expected, string key, string value) Assert.Equal(expected, resolver.Resolve(model)); } - - } } diff --git a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs index 6423d57..a5e0b3b 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs @@ -6,7 +6,6 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { public class StringResovlerTests { - [Theory] [InlineData(true, "-m", "test")] [InlineData(true, "-m", "my string")] @@ -28,6 +27,5 @@ public void TestResolve(string expected, string key, string value) Assert.Equal(expected, resolver.Resolve(model)); } - } } diff --git a/CommandLineParser.Tests/XUnitExtensions.cs b/CommandLineParser.Tests/XUnitExtensions.cs index 62bb182..61941e4 100644 --- a/CommandLineParser.Tests/XUnitExtensions.cs +++ b/CommandLineParser.Tests/XUnitExtensions.cs @@ -1,14 +1,10 @@ using System; using System.Linq.Expressions; -using Xunit.Sdk; namespace MatthiWare.CommandLineParser.Tests { public static class XUnitExtensions { - public static void Fail(string reason) - => throw new XunitException(reason); - public static LambdaExpression CreateLambda(Expression> expression) { return expression; From 06db974033a0ccec4e6c9da6a4ee6074cc1d3c89 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:16:25 +0100 Subject: [PATCH 04/41] Codefactor fixes --- CommandLineParser/Abstractions/Command/ICommandBuilder'.cs | 1 - CommandLineParser/Core/Exceptions/OptionNotFoundException.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index 7663183..d2a25cf 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -11,7 +11,6 @@ public interface ICommandBuilder : ICommandConfigurationBuilde where TOption : class where TSource : class, new() { - /// /// Configures an option in the model /// diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index e9a926c..722cb69 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -16,8 +16,6 @@ public class OptionNotFoundException : KeyNotFoundException public OptionNotFoundException(ICommandLineOption option) : base($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!") - { - - } + { } } } From 7231239a2a8553f6199d4dfd07ae8ee1885e21ea Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:20:47 +0100 Subject: [PATCH 05/41] Sort usings --- CommandLineParser.Tests/Command/CommandTests.cs | 3 ++- CommandLineParser.Tests/CommandLineModelTests.cs | 1 + CommandLineParser.Tests/CommandLineParserTests.cs | 3 +++ CommandLineParser.Tests/CustomerReportedTests.cs | 6 ++---- CommandLineParser.Tests/OptionBuilderTest.cs | 3 +++ CommandLineParser.Tests/Parsing/ParserResultTest.cs | 3 +++ CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs | 3 +++ .../Parsing/Resolvers/BoolResolverTests.cs | 1 + .../Parsing/Resolvers/DoubleResolverTests.cs | 1 + .../Parsing/Resolvers/EnumResolverTests.cs | 1 + .../Parsing/Resolvers/IntResolverTests.cs | 1 + .../Parsing/Resolvers/StringResovlerTests.cs | 1 + .../Abstractions/Parsing/Command/ICommandParserResult.cs | 2 +- CommandLineParser/CommandLineParser.cs | 4 ++-- CommandLineParser/Core/Attributes/DescriptionAttribute.cs | 4 +--- SampleApp/Program.cs | 1 + 16 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index dd4641d..ff8a516 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -1,7 +1,8 @@ using System.Linq; + using MatthiWare.CommandLine; -using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Command diff --git a/CommandLineParser.Tests/CommandLineModelTests.cs b/CommandLineParser.Tests/CommandLineModelTests.cs index e3423b0..b2248f3 100644 --- a/CommandLineParser.Tests/CommandLineModelTests.cs +++ b/CommandLineParser.Tests/CommandLineModelTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine; using MatthiWare.CommandLine.Core.Attributes; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index ce42c5d..876783e 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -1,11 +1,14 @@ using System.Threading; + using MatthiWare.CommandLine; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core.Attributes; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index a012fa7..9e74ce4 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; -using MatthiWare.CommandLine; +using MatthiWare.CommandLine; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/OptionBuilderTest.cs b/CommandLineParser.Tests/OptionBuilderTest.cs index 4157e46..ab0812b 100644 --- a/CommandLineParser.Tests/OptionBuilderTest.cs +++ b/CommandLineParser.Tests/OptionBuilderTest.cs @@ -1,8 +1,11 @@ using System; + using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/Parsing/ParserResultTest.cs b/CommandLineParser.Tests/Parsing/ParserResultTest.cs index 9f46bb1..9b9e887 100644 --- a/CommandLineParser.Tests/Parsing/ParserResultTest.cs +++ b/CommandLineParser.Tests/Parsing/ParserResultTest.cs @@ -1,7 +1,10 @@ using System; + using MatthiWare.CommandLine.Abstractions.Parsing.Command; using MatthiWare.CommandLine.Core.Parsing; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing diff --git a/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs b/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs index 90ded78..19b9939 100644 --- a/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs +++ b/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs @@ -1,9 +1,12 @@ using System; + using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core.Parsing; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing diff --git a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs index 80c0d1c..05b3264 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs index 0806537..151cdc4 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs index 837482a..8838eba 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs index 2bc2201..dfcb3c6 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs index a5e0b3b..578a573 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs index 47ce800..82d1f1f 100644 --- a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs +++ b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs @@ -24,7 +24,7 @@ public interface ICommandParserResult /// /// Executes the command /// - /// /// + /// /// Result contains exceptions. For more info see and properties. /// void ExecuteCommand(); diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index f51f2f6..e68af88 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -136,8 +136,6 @@ public IParserResult Parse(string[] args) result.MergeResult(errors); - result.MergeResult(m_option); - AutoExecuteCommands(result); return result; @@ -198,6 +196,8 @@ private void ParseOptions(IList errors, ParseResult result, option.Parse(model); } + + result.MergeResult(m_option); } /// diff --git a/CommandLineParser/Core/Attributes/DescriptionAttribute.cs b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs index 97ec330..89e7a87 100644 --- a/CommandLineParser/Core/Attributes/DescriptionAttribute.cs +++ b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs @@ -1,6 +1,4 @@ -using System; - -namespace MatthiWare.CommandLine.Core.Attributes +namespace MatthiWare.CommandLine.Core.Attributes { public class DescriptionAttribute : BaseAttribute { diff --git a/SampleApp/Program.cs b/SampleApp/Program.cs index 86abfa9..120df59 100644 --- a/SampleApp/Program.cs +++ b/SampleApp/Program.cs @@ -1,4 +1,5 @@ using System; + using MatthiWare.CommandLine; namespace SampleApp From 39850a1564f325e5228a04dd3ea1e9a826d90c9e Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 20 Dec 2018 08:59:36 +0100 Subject: [PATCH 06/41] Better error message --- .../Core/Exceptions/OptionNotFoundException.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index 722cb69..6990fd5 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -17,5 +17,18 @@ public class OptionNotFoundException : KeyNotFoundException public OptionNotFoundException(ICommandLineOption option) : base($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!") { } + + private string CreateMessage() + { + bool hasShortName = Option.HasShortName; + bool hasLongName = Option.HasLongName; + bool hasBoth = hasShortName && hasLongName; + + string shortName = hasShortName ? $"'{Option.ShortName}'" : string.Empty; + string longName = hasLongName ? $"'{Option.LongName}'" : string.Empty; + string or = hasBoth ? " or " : string.Empty; + + return $"Required argument {shortName}{or}{longName} not found!"; + } } } From 7217ed00f594c27fd8068aac06c53181c05c9b43 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 21 Dec 2018 09:42:31 +0100 Subject: [PATCH 07/41] Change command to only have a name instead of both short and long name. --- .../Command/CommandTests.cs | 12 ++--- .../CommandLineParserTests.cs | 15 ++++-- .../CustomerReportedTests.cs | 10 ++-- .../Abstractions/Command/ICommandBuilder'.cs | 12 +---- .../Abstractions/Command/ICommandBuilder.cs | 12 +---- .../Command/ICommandConfigurationBuilder.cs | 12 +---- .../Command/ICommandLineCommand.cs | 5 +- CommandLineParser/Abstractions/IArgument.cs | 11 ++++ .../Abstractions/ICommandLineOption.cs | 2 +- .../Abstractions/Parsing/IArgumentManager.cs | 7 +-- .../Abstractions/Usage/IUsageDisplay.cs | 12 +++++ .../Core/Command/CommandLineCommandBase.cs | 32 ++++++++--- ...mmandLineCommand`TOption`TCommandOption.cs | 54 ++++++------------- .../Core/CommandLineOptionBase.cs | 8 ++- .../Exceptions/CommandNotFoundException.cs | 2 +- .../Exceptions/OptionNotFoundException.cs | 12 ++--- .../Core/Parsing/ArgumentManager.cs | 45 +++++++++++----- .../Core/Parsing/ParseResult'.cs | 5 +- SampleApp/Program.cs | 2 + 19 files changed, 150 insertions(+), 120 deletions(-) create mode 100644 CommandLineParser/Abstractions/IArgument.cs create mode 100644 CommandLineParser/Abstractions/Usage/IUsageDisplay.cs diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index ff8a516..070565d 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -19,8 +19,8 @@ public void ConfiguringCommandsIncreasesTotalCommandInParser() Assert.Equal(2, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("x"))); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("y"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("x"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("y"))); } [Fact] @@ -33,8 +33,8 @@ public void AddOptionLessCommand() Assert.Equal(2, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("x"))); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("y"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("x"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("y"))); } [Fact] @@ -46,7 +46,7 @@ public void AddCommandType() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); } [Fact] @@ -58,7 +58,7 @@ public void AddCommandTypeWithGenericOption() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); } private class MyComand : Command diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 876783e..2646fdb 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -112,6 +112,9 @@ public void ParseEnumInArguments(string[] args, bool hasErrors, EnumOption enumO [InlineData(new[] { "app.exe", "-1", "message1", "-2", "-3" }, "message1", "message2", "message3")] [InlineData(new[] { "app.exe", "-1", "-2", "message2", "-3" }, "message1", "message2", "message3")] [InlineData(new[] { "app.exe", "-1", "-2", "-3" }, "message1", "message2", "message3")] + [InlineData(new[] { "-1", "message1", "-2", "-3" }, "message1", "message2", "message3")] + [InlineData(new[] { "-1", "-2", "message2", "-3" }, "message1", "message2", "message3")] + [InlineData(new[] { "-1", "-2", "-3" }, "message1", "message2", "message3")] public void ParseWithDefaults(string[] args, string result1, string result2, string result3) { var parser = new CommandLineParser(); @@ -175,7 +178,7 @@ public void ParseWithCommandTests() .Required(); var addCmd = parser.AddCommand() - .Name("-A", "--Add") + .Name("add") .OnExecuting((opt, cmdOpt) => { Assert.Equal("test", opt.Option1); @@ -187,7 +190,7 @@ public void ParseWithCommandTests() .Name("-m", "--message") .Required(); - var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "--Add", "-m", "my message" }); + var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "add", "-m", "my message" }); Assert.False(parsed.HasErrors); @@ -201,15 +204,17 @@ public void ParseWithCommandTests() } [Theory] - [InlineData(new[] { "app.exe", "--Add", "-m", "message2", "-m", "message1" }, "message1", "message2")] - [InlineData(new[] { "app.exe", "-m", "message1", "--Add", "-m", "message2" }, "message1", "message2")] + [InlineData(new[] { "app.exe", "add", "-m", "message2", "-m", "message1" }, "message1", "message2")] + [InlineData(new[] { "app.exe", "-m", "message1", "add", "-m", "message2" }, "message1", "message2")] + [InlineData(new[] { "add", "-m", "message2", "-m", "message1" }, "message1", "message2")] + [InlineData(new[] { "-m", "message1", "add", "-m", "message2" }, "message1", "message2")] public void ParseCommandTests(string[] args, string result1, string result2) { var parser = new CommandLineParser(); var wait = new ManualResetEvent(false); parser.AddCommand() - .Name("-a", "--add") + .Name("add") .Required() .OnExecuting((opt1, opt2) => { diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index 9e74ce4..2297a00 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -11,9 +11,11 @@ public class CustomerReportedTests /// https://github.com/MatthiWare/CommandLineParser.Core/issues/12 /// [Theory] - [InlineData(true, true)] - [InlineData(false, false)] - public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool outcome) + [InlineData(true, true, false)] + [InlineData(false, false, false)] + [InlineData(true, true, true)] + [InlineData(false, false, true)] + public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool outcome, bool empty) { var parser = new CommandLineParser(); @@ -22,7 +24,7 @@ public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool out .Default(1) .Required(required); - var parsed = parser.Parse(new[] { "app.exe" }); + var parsed = parser.Parse(empty ? new string[] { } : new[] { "app.exe" }); Assert.NotNull(parsed); diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index d2a25cf..621685b 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -36,16 +36,8 @@ public interface ICommandBuilder : ICommandConfigurationBuilde /// /// Configures the command name /// - /// Short name + /// name /// - new ICommandBuilder Name(string shortName); - - /// - /// Configures the command name - /// - /// Short name - /// Long name - /// - new ICommandBuilder Name(string shortName, string longName); + new ICommandBuilder Name(string name); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs index 45a87b3..19aede5 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs @@ -29,17 +29,9 @@ public interface ICommandBuilder /// /// Configures the command name /// - /// Short name + /// name /// - ICommandBuilder Name(string shortName); - - /// - /// Configures the command name - /// - /// Short name - /// Long name - /// - ICommandBuilder Name(string shortName, string longName); + ICommandBuilder Name(string name); /// /// Configures the execution of the command diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs index 4ed8349..03ac92c 100644 --- a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs @@ -19,16 +19,8 @@ public interface ICommandConfigurationBuilder /// /// Configures the command name /// - /// Short name + /// Short name /// - ICommandConfigurationBuilder Name(string shortName); - - /// - /// Configures the command name - /// - /// Short name - /// Long name - /// - ICommandConfigurationBuilder Name(string shortName, string longName); + ICommandConfigurationBuilder Name(string name); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs b/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs index e3a92c0..9723203 100644 --- a/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs +++ b/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs @@ -3,8 +3,11 @@ /// /// Command configuration options /// - public interface ICommandLineCommand : ICommandLineOption + public interface ICommandLineCommand : IArgument { + string Name { get; } + bool IsRequired { get; } + string Description { get; } bool AutoExecute { get; } } } diff --git a/CommandLineParser/Abstractions/IArgument.cs b/CommandLineParser/Abstractions/IArgument.cs new file mode 100644 index 0000000..b55a13b --- /dev/null +++ b/CommandLineParser/Abstractions/IArgument.cs @@ -0,0 +1,11 @@ +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Models; + +namespace MatthiWare.CommandLine.Abstractions +{ + /// + /// Represents an argument + /// , and for more info. + /// + public interface IArgument { } +} diff --git a/CommandLineParser/Abstractions/ICommandLineOption.cs b/CommandLineParser/Abstractions/ICommandLineOption.cs index 80f43ba..6c66cff 100644 --- a/CommandLineParser/Abstractions/ICommandLineOption.cs +++ b/CommandLineParser/Abstractions/ICommandLineOption.cs @@ -3,7 +3,7 @@ /// /// Option configuration options /// - public interface ICommandLineOption + public interface ICommandLineOption : IArgument { string ShortName { get; } string LongName { get; } diff --git a/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs b/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs index b4a4d63..9343b7c 100644 --- a/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs +++ b/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs @@ -1,4 +1,5 @@ -using MatthiWare.CommandLine.Abstractions.Models; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Models; namespace MatthiWare.CommandLine.Abstractions.Parsing { @@ -10,9 +11,9 @@ public interface IArgumentManager /// /// Tries to get the arguments associated to the current option /// - /// the option + /// the argument /// The result arguments /// True if arguments are found, false if not - bool TryGetValue(ICommandLineOption option, out ArgumentModel model); + bool TryGetValue(IArgument argument, out ArgumentModel model); } } diff --git a/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs b/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs new file mode 100644 index 0000000..a150183 --- /dev/null +++ b/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MatthiWare.CommandLine.Abstractions.Usage +{ + public interface IUsageDisplay + { + string ToUsage(); + string ToShortUsage(); + } +} diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 23c8fa9..974d7e0 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -1,31 +1,49 @@ using System.Collections.Generic; - +using System.Text; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Abstractions.Parsing.Command; +using MatthiWare.CommandLine.Abstractions.Usage; namespace MatthiWare.CommandLine.Core.Command { internal abstract class CommandLineCommandBase : ICommandLineCommandParser, - ICommandLineCommand + ICommandLineCommand, + IUsageDisplay { protected readonly List m_options = new List(); public IReadOnlyList Options => m_options.AsReadOnly(); - public string ShortName { get; protected set; } - public string LongName { get; protected set; } + public string Name { get; protected set; } public string Description { get; protected set; } public bool IsRequired { get; protected set; } - public bool HasShortName => ShortName != null; - public bool HasLongName => LongName != null; - public bool HasDefault => false; public bool AutoExecute { get; protected set; } = true; public abstract void Execute(); public abstract ICommandParserResult Parse(IArgumentManager argumentManager); + + public string ToShortUsage() + => $" {Name}\t\t{Description}"; + + public string ToUsage() + { + var sb = new StringBuilder(); + + sb.AppendLine(Description); + + if (m_options.Count == 0) + return sb.ToString(); + + sb.AppendLine("Options: "); + + foreach (var opt in m_options) + sb.AppendLine(opt.ToUsage()); + + return sb.ToString(); + } } } diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index c44e812..9841cc0 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -91,21 +91,6 @@ public ICommandBuilder Required(bool required = true) return this; } - public ICommandBuilder Name(string shortName) - { - ShortName = shortName; - - return this; - } - - public ICommandBuilder Name(string shortName, string longName) - { - ShortName = shortName; - LongName = longName; - - return this; - } - public ICommandBuilder OnExecuting(Action action) { m_executor = action; @@ -141,21 +126,6 @@ ICommandBuilder ICommandBuilder.Description(string description return this; } - ICommandBuilder ICommandBuilder.Name(string shortName) - { - ShortName = shortName; - - return this; - } - - ICommandBuilder ICommandBuilder.Name(string shortName, string longName) - { - ShortName = shortName; - LongName = longName; - - return this; - } - ICommandBuilder ICommandBuilder.OnExecuting(Action action) { m_executor = action; @@ -177,31 +147,37 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder.Description(string des return this; } - ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string shortName) + public ICommandBuilder InvokeCommand(bool invoke) { - ShortName = shortName; + AutoExecute = invoke; return this; } - ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string shortName, string longName) + ICommandBuilder ICommandBuilder.Description(string help) { - ShortName = shortName; - LongName = longName; + Description = help; return this; } - public ICommandBuilder InvokeCommand(bool invoke) + ICommandBuilder ICommandBuilder.Name(string name) { - AutoExecute = invoke; + Name = name; return this; } - ICommandBuilder ICommandBuilder.Description(string help) + ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string name) { - Description = help; + Name = name; + + return this; + } + + ICommandBuilder ICommandBuilder.Name(string name) + { + Name = name; return this; } diff --git a/CommandLineParser/Core/CommandLineOptionBase.cs b/CommandLineParser/Core/CommandLineOptionBase.cs index 86dd035..6616c55 100644 --- a/CommandLineParser/Core/CommandLineOptionBase.cs +++ b/CommandLineParser/Core/CommandLineOptionBase.cs @@ -3,11 +3,12 @@ using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Usage; namespace MatthiWare.CommandLine.Core { [DebuggerDisplay("Cmd Option {ShortName ?? LongName}, Req: {IsRequired}, HasDefault: {HasDefault}")] - internal abstract class CommandLineOptionBase : IParser, ICommandLineOption + internal abstract class CommandLineOptionBase : IParser, ICommandLineOption, IUsageDisplay { public string ShortName { get; protected set; } public string LongName { get; protected set; } @@ -23,6 +24,11 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption public abstract void Parse(ArgumentModel model); + public string ToShortUsage() => ToUsage(); + + public string ToUsage() + => $" {(HasShortName ? ShortName : string.Empty)}{(HasShortName && HasLongName ? "|" : string.Empty)}{(HasLongName ? LongName : string.Empty)}\t\t{Description}"; + public abstract void UseDefault(); } } diff --git a/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs b/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs index f59cbb3..6ff710a 100644 --- a/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs @@ -15,7 +15,7 @@ public class CommandNotFoundException : KeyNotFoundException public ICommandLineCommand Command { get; private set; } public CommandNotFoundException(ICommandLineCommand cmd) - : base($"Required command '{cmd.HasShortName}' or '{cmd.LongName}' not found!") + : base($"Required command '{cmd.Name}' not found!") { Command = cmd; } diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index 6990fd5..863d013 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -15,17 +15,17 @@ public class OptionNotFoundException : KeyNotFoundException public ICommandLineOption Option { get; private set; } public OptionNotFoundException(ICommandLineOption option) - : base($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!") + : base(CreateMessage(option)) { } - private string CreateMessage() + private static string CreateMessage(ICommandLineOption option) { - bool hasShortName = Option.HasShortName; - bool hasLongName = Option.HasLongName; + bool hasShortName = option.HasShortName; + bool hasLongName = option.HasLongName; bool hasBoth = hasShortName && hasLongName; - string shortName = hasShortName ? $"'{Option.ShortName}'" : string.Empty; - string longName = hasLongName ? $"'{Option.LongName}'" : string.Empty; + string shortName = hasShortName ? $"'{option.ShortName}'" : string.Empty; + string longName = hasLongName ? $"'{option.LongName}'" : string.Empty; string or = hasBoth ? " or " : string.Empty; return $"Required argument {shortName}{or}{longName} not found!"; diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index e8434b9..893d442 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -3,6 +3,7 @@ using System.Linq; using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core.Command; @@ -11,12 +12,12 @@ namespace MatthiWare.CommandLine.Core.Parsing { internal class ArgumentManager : IArgumentManager, IDisposable { - private readonly IDictionary arguments; + private readonly IDictionary arguments; private readonly List args; public ArgumentManager(string[] args, ICollection commands, ICollection options) { - arguments = new Dictionary(commands.Count + options.Count); + arguments = new Dictionary(commands.Count + options.Count); this.args = new List(args.Select(arg => new ArgumentValueHolder { @@ -30,7 +31,7 @@ public ArgumentManager(string[] args, ICollection comman foreach (var item in this.args) { - if (item.Option == null) continue; + if (item.ArgModel == null) continue; int nextIndex = item.Index + 1; @@ -42,7 +43,7 @@ public ArgumentManager(string[] args, ICollection comman Value = (argValue?.Used ?? true) ? null : argValue.Argument }; - arguments.Add(item.Option, argModel); + arguments.Add(item.ArgModel, argModel); } } @@ -78,10 +79,10 @@ private void ParseCommands(IEnumerable cmds) } } - private void SetArgumentUsed(int idx, ICommandLineOption option) + private void SetArgumentUsed(int idx, IArgument option) { args[idx].Used = true; - args[idx].Option = option; + args[idx].ArgModel = option; args[idx].Index = idx; } @@ -89,26 +90,44 @@ private void SetArgumentUsed(int idx, ICommandLineOption option) /// Finds the index of the first unused argument /// /// List of arguments to search - /// Option to find + /// Argument model to find /// Search offset /// - private int FindIndex(ICommandLineOption option, int startOffset = 0) - => args.FindIndex(startOffset, arg => !arg.Used && - ((option.HasShortName && string.Equals(option.ShortName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)) || - (option.HasLongName && string.Equals(option.LongName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)))); + private int FindIndex(IArgument model, int startOffset = 0) + { + return args.FindIndex(startOffset, arg => + { + if (arg.Used) return false; + + switch (model) + { + case ICommandLineOption opt: + return (opt.HasShortName && string.Equals(opt.ShortName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)) || + (opt.HasLongName && string.Equals(opt.LongName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)); + case ICommandLineCommand cmd: + return string.Equals(cmd.Name, arg.Argument, StringComparison.InvariantCultureIgnoreCase); + default: + return false; + } + + + }); + } + + public void Dispose() { arguments.Clear(); } - public bool TryGetValue(ICommandLineOption option, out ArgumentModel model) => arguments.TryGetValue(option, out model); + public bool TryGetValue(IArgument argument, out ArgumentModel model) => arguments.TryGetValue(argument, out model); private class ArgumentValueHolder { public string Argument { get; set; } public bool Used { get; set; } - public ICommandLineOption Option { get; set; } + public IArgument ArgModel { get; set; } public int Index { get; set; } } } diff --git a/CommandLineParser/Core/Parsing/ParseResult'.cs b/CommandLineParser/Core/Parsing/ParseResult'.cs index d38023a..8d86cce 100644 --- a/CommandLineParser/Core/Parsing/ParseResult'.cs +++ b/CommandLineParser/Core/Parsing/ParseResult'.cs @@ -11,11 +11,10 @@ internal class ParseResult : IParserResult { private readonly List commandParserResults = new List(); private readonly ICollection exceptions = new List(); - private TResult result; #region Properties - public TResult Result => result; + public TResult Result { get; private set; } public bool HasErrors { get; private set; } = false; @@ -54,7 +53,7 @@ public void MergeResult(ICollection errors) public void MergeResult(TResult result) { - this.result = result; + this.Result = result; } public void ExecuteCommands() diff --git a/SampleApp/Program.cs b/SampleApp/Program.cs index 120df59..bb6d17f 100644 --- a/SampleApp/Program.cs +++ b/SampleApp/Program.cs @@ -8,6 +8,8 @@ class Program { static int Main(string[] args) { + Console.WriteLine($"args: {string.Join(',', args)}"); + var parser = new CommandLineParser(); // setup From b1a1a1d90201d891a5369106b3f3b717d0431e57 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 21 Dec 2018 09:59:52 +0100 Subject: [PATCH 08/41] SMall refactor of arg manager. --- CommandLineParser/Core/Parsing/ArgumentManager.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index 893d442..cbf9bfb 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -12,12 +12,12 @@ namespace MatthiWare.CommandLine.Core.Parsing { internal class ArgumentManager : IArgumentManager, IDisposable { - private readonly IDictionary arguments; + private readonly IDictionary resultCache; private readonly List args; public ArgumentManager(string[] args, ICollection commands, ICollection options) { - arguments = new Dictionary(commands.Count + options.Count); + resultCache = new Dictionary(commands.Count + options.Count); this.args = new List(args.Select(arg => new ArgumentValueHolder { @@ -29,6 +29,7 @@ public ArgumentManager(string[] args, ICollection comman ParseOptions(options); + // pre cache results foreach (var item in this.args) { if (item.ArgModel == null) continue; @@ -40,10 +41,11 @@ public ArgumentManager(string[] args, ICollection comman var argModel = new ArgumentModel { Key = item.Argument, + // this checks if the argument is used in an other command/option. Value = (argValue?.Used ?? true) ? null : argValue.Argument }; - arguments.Add(item.ArgModel, argModel); + resultCache.Add(item.ArgModel, argModel); } } @@ -116,12 +118,9 @@ private int FindIndex(IArgument model, int startOffset = 0) - public void Dispose() - { - arguments.Clear(); - } + public void Dispose() => args.Clear(); - public bool TryGetValue(IArgument argument, out ArgumentModel model) => arguments.TryGetValue(argument, out model); + public bool TryGetValue(IArgument argument, out ArgumentModel model) => resultCache.TryGetValue(argument, out model); private class ArgumentValueHolder { From b0024858ef3fe1cb8d42ce46c33dfe4256f15898 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 21 Dec 2018 11:41:58 +0100 Subject: [PATCH 09/41] Add convention options --- .../CommandLineModelTests.cs | 2 +- .../CommandLineParserTests.cs | 26 ++++++------- .../CustomerReportedTests.cs | 2 +- CommandLineParser.Tests/OptionBuilderTest.cs | 9 ++++- CommandLineParser/CommandLineParser.cs | 38 ++++++++++++++----- CommandLineParser/CommandLineParserOptions.cs | 11 ++++++ ...mmandLineCommand`TOption`TCommandOption.cs | 6 ++- CommandLineParser/Core/CommandLineOption.cs | 16 ++++---- 8 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 CommandLineParser/CommandLineParserOptions.cs diff --git a/CommandLineParser.Tests/CommandLineModelTests.cs b/CommandLineParser.Tests/CommandLineModelTests.cs index b2248f3..ddceef0 100644 --- a/CommandLineParser.Tests/CommandLineModelTests.cs +++ b/CommandLineParser.Tests/CommandLineModelTests.cs @@ -57,7 +57,7 @@ public void TestBasicModelWithOverwritingUsingFluentApi() private class Model { - [Required, Name("-m", "--message"), DefaultValue("not found"), Description("Help")] + [Required, Name("m", "message"), DefaultValue("not found"), Description("Help")] public string Message { get; set; } } } diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 2646fdb..b982e86 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -74,7 +74,7 @@ public void ParseTests() var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-o") + .Name("o") .Default("Default message") .Required(); @@ -98,7 +98,7 @@ public void ParseEnumInArguments(string[] args, bool hasErrors, EnumOption enumO var parser = new CommandLineParser(); parser.Configure(opt => opt.EnumOption) - .Name("-e") + .Name("e") .Required(); var result = parser.Parse(args); @@ -120,17 +120,17 @@ public void ParseWithDefaults(string[] args, string result1, string result2, str var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-1") + .Name("1") .Default(result1) .Required(); parser.Configure(opt => opt.Option2) - .Name("-2") + .Name("2") .Default(result2) .Required(); parser.Configure(opt => opt.Option3) - .Name("-3") + .Name("3") .Default(result3) .Required(); @@ -158,7 +158,7 @@ public void ParseWithCustomParserInAttributeConfiguredModelTests() var parser = new CommandLineParser(); parser.ArgumentResolverFactory.Register(resolver.Object); - var result = parser.Parse(new[] { "app.exe", "--p", "sample" }); + var result = parser.Parse(new[] { "app.exe", "-p", "sample" }); Assert.False(result.HasErrors); @@ -173,7 +173,7 @@ public void ParseWithCommandTests() var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-o") + .Name("o") .Default("Default message") .Required(); @@ -187,7 +187,7 @@ public void ParseWithCommandTests() }); addCmd.Configure(opt => opt.Message) - .Name("-m", "--message") + .Name("m", "message") .Required(); var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "add", "-m", "my message" }); @@ -223,11 +223,11 @@ public void ParseCommandTests(string[] args, string result1, string result2) Assert.Equal(result2, opt2.Message); }) .Configure(c => c.Message) - .Name("-m", "--message") + .Name("m", "message") .Required(); parser.Configure(opt => opt.Message) - .Name("-m", "--message") + .Name("m", "message") .Required(); var result = parser.Parse(args); @@ -245,12 +245,12 @@ public void ConfigureTests() var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-o", "--opt") + .Name("o", "opt") .Default("Default message") .Required(); parser.Configure(opt => opt.Option2) - .Name("-x", "--xsomething") + .Name("x", "xsomething") .Required(); Assert.Equal(2, parser.Options.Count); @@ -272,7 +272,7 @@ public void ConfigureTests() private class ObjOption { - [Name("--p"), Required] + [Name("p"), Required] public object Param { get; set; } } diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index 2297a00..f91ab57 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -20,7 +20,7 @@ public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool out var parser = new CommandLineParser(); parser.Configure(opt => opt.Test) - .Name("-1") + .Name("1") .Default(1) .Required(required); diff --git a/CommandLineParser.Tests/OptionBuilderTest.cs b/CommandLineParser.Tests/OptionBuilderTest.cs index ab0812b..05d3dbc 100644 --- a/CommandLineParser.Tests/OptionBuilderTest.cs +++ b/CommandLineParser.Tests/OptionBuilderTest.cs @@ -1,5 +1,5 @@ using System; - +using MatthiWare.CommandLine; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core; @@ -19,7 +19,12 @@ public void OptionBuilderConfiguresOptionCorrectly() var resolverFactoryMock = new Mock(); resolverFactoryMock.Setup(_ => _.CreateResolver(It.IsAny())).Returns(resolverMock.Object); - var option = new CommandLineOption(new object(), XUnitExtensions.CreateLambda(o => o.ToString()), resolverFactoryMock.Object); + var option = new CommandLineOption( + new CommandLineParserOptions { PrefixLongOption = string.Empty, PrefixShortOption = string.Empty }, + new object(), + XUnitExtensions.CreateLambda(o => o.ToString()), + resolverFactoryMock.Object); + var builder = option as IOptionBuilder; string sDefault = "default"; diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index e68af88..7ac4907 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -29,6 +29,7 @@ public sealed class CommandLineParser : ICommandLineParser private readonly TOption m_option; private readonly Dictionary m_options; private readonly List m_commands; + private readonly CommandLineParserOptions m_parserOptions; /// /// Read-only collection of options specified @@ -54,7 +55,7 @@ public sealed class CommandLineParser : ICommandLineParser /// Creates a new instance of the commandline parser /// public CommandLineParser() - : this(new DefaultArgumentResolverFactory(), new DefaultContainerResolver()) + : this(new CommandLineParserOptions(), new DefaultArgumentResolverFactory(), new DefaultContainerResolver()) { } /// @@ -62,7 +63,16 @@ public CommandLineParser() /// /// argument resolver to use public CommandLineParser(IArgumentResolverFactory argumentResolverFactory) - : this(argumentResolverFactory, new DefaultContainerResolver()) + : this(new CommandLineParserOptions(), argumentResolverFactory, new DefaultContainerResolver()) + { } + + /// + /// Creates a new instance of the commandline parser + /// + /// options that the parser will use + /// argument resolver to use + public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolverFactory argumentResolverFactory) + : this(parserOptions, argumentResolverFactory, new DefaultContainerResolver()) { } /// @@ -70,7 +80,16 @@ public CommandLineParser(IArgumentResolverFactory argumentResolverFactory) /// /// container resolver to use public CommandLineParser(IContainerResolver containerResolver) - : this(new DefaultArgumentResolverFactory(), containerResolver) + : this(new CommandLineParserOptions(), new DefaultArgumentResolverFactory(), containerResolver) + { } + + /// + /// Creates a new instance of the commandline parser + /// + /// options that the parser will use + /// container resolver to use + public CommandLineParser(CommandLineParserOptions parserOptions, IContainerResolver containerResolver) + : this(parserOptions, new DefaultArgumentResolverFactory(), containerResolver) { } /// @@ -78,8 +97,9 @@ public CommandLineParser(IContainerResolver containerResolver) /// /// argument resolver to use /// container resolver to use - public CommandLineParser(IArgumentResolverFactory argumentResolverFactory, IContainerResolver containerResolver) + public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolverFactory argumentResolverFactory, IContainerResolver containerResolver) { + m_parserOptions = parserOptions; m_option = new TOption(); m_options = new Dictionary(); @@ -109,7 +129,7 @@ private IOptionBuilder ConfigureInternal(LambdaExpression selector, string key) { if (!m_options.ContainsKey(key)) { - var option = new CommandLineOption(m_option, selector, ArgumentResolverFactory); + var option = new CommandLineOption(m_parserOptions, m_option, selector, ArgumentResolverFactory); m_options.Add(key, option); } @@ -207,7 +227,7 @@ private void ParseOptions(IList errors, ParseResult result, /// Builder for the command, public ICommandBuilder AddCommand() where TCommandOption : class, new() { - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); m_commands.Add(command); @@ -223,7 +243,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); cmdConfigurator.OnConfigure(command); @@ -243,7 +263,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); cmdConfigurator.OnConfigure(command); @@ -258,7 +278,7 @@ public void RegisterCommand() /// Builder for the command, public ICommandBuilder AddCommand() { - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); m_commands.Add(command); diff --git a/CommandLineParser/CommandLineParserOptions.cs b/CommandLineParser/CommandLineParserOptions.cs new file mode 100644 index 0000000..2378b00 --- /dev/null +++ b/CommandLineParser/CommandLineParserOptions.cs @@ -0,0 +1,11 @@ +namespace MatthiWare.CommandLine +{ + public class CommandLineParserOptions + { + public string PrefixShortOption { get; set; } = "-"; + public string PrefixLongOption { get; set; } = "--"; + public string HelpOptionName { get; set; } = "help"; + public bool EnableHelpOption { get; set; } = true; + public string AppName { get; set; } + } +} diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 9841cc0..6fdf796 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -23,12 +23,14 @@ internal class CommandLineCommand : private readonly TCommandOption m_commandOption; private readonly TOption m_baseOption; private readonly IArgumentResolverFactory m_resolverFactory; + private readonly CommandLineParserOptions m_parserOptions; private Action m_executor; private Action m_executor2; - public CommandLineCommand(IArgumentResolverFactory resolverFactory, TOption option) + public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, TOption option) { + m_parserOptions = parserOptions; m_commandOption = new TCommandOption(); m_resolverFactory = resolverFactory; @@ -43,7 +45,7 @@ public override void Execute() public IOptionBuilder Configure(Expression> selector) { - var option = new CommandLineOption(m_commandOption, selector, m_resolverFactory); + var option = new CommandLineOption(m_parserOptions, m_commandOption, selector, m_resolverFactory); m_options.Add(option); diff --git a/CommandLineParser/Core/CommandLineOption.cs b/CommandLineParser/Core/CommandLineOption.cs index 675b22d..7508700 100644 --- a/CommandLineParser/Core/CommandLineOption.cs +++ b/CommandLineParser/Core/CommandLineOption.cs @@ -16,14 +16,16 @@ internal class CommandLineOption : private readonly LambdaExpression m_selector; private object m_defaultValue = null; private readonly IArgumentResolverFactory m_resolverFactory; + private readonly CommandLineParserOptions m_parserOptions; private ICommandLineArgumentResolver m_resolver; - public CommandLineOption(object source, LambdaExpression selector, IArgumentResolverFactory resolver) + public CommandLineOption(CommandLineParserOptions parserOptions, object source, LambdaExpression selector, IArgumentResolverFactory resolver) { - this.m_source = source ?? throw new ArgumentNullException(nameof(source)); - this.m_selector = selector ?? throw new ArgumentNullException(nameof(selector)); - this.m_resolverFactory = resolver ?? throw new ArgumentNullException(nameof(resolver)); + m_parserOptions = parserOptions; + m_source = source ?? throw new ArgumentNullException(nameof(source)); + m_selector = selector ?? throw new ArgumentNullException(nameof(selector)); + m_resolverFactory = resolver ?? throw new ArgumentNullException(nameof(resolver)); } public ICommandLineArgumentResolver Resolver @@ -72,8 +74,8 @@ IOptionBuilder IOptionBuilder.Description(string help) IOptionBuilder IOptionBuilder.Name(string shortName, string longName) { - LongName = longName; - ShortName = shortName; + LongName = $"{m_parserOptions.PrefixLongOption}{longName}"; ; + ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; ; return this; } @@ -87,7 +89,7 @@ IOptionBuilder IOptionBuilder.Required(bool required = true) IOptionBuilder IOptionBuilder.Name(string shortName) { - ShortName = shortName; + ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; return this; } From 06de0c544c23b4c802a963b24890cd7a618da329 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Sun, 23 Dec 2018 10:47:48 +0100 Subject: [PATCH 10/41] Update command --- .../Command/CommandTests.cs | 13 ++- .../Usage/UsagePrinterTests.cs | 33 +++++++ .../Abstractions/Command/Command`.cs | 9 ++ .../Command/ICommandConfigurationBuilder`.cs | 39 +++++++++ .../Command/ICommandLineCommandContainer.cs | 23 +++++ .../Command/ICommandLineCommandParser.cs | 5 -- .../Abstractions/ICommandLineParser'.cs | 12 +-- .../Abstractions/Usage/IUsageBuilder.cs | 16 ++++ .../Abstractions/Usage/IUsageDisplay.cs | 12 --- .../Abstractions/Usage/IUsagePrinter.cs | 10 +++ CommandLineParser/CommandLineParser.cs | 38 +++++++- CommandLineParser/CommandLineParserOptions.cs | 9 +- .../Core/Command/CommandLineCommandBase.cs | 27 ++---- ...mmandLineCommand`TOption`TCommandOption.cs | 24 +++++- .../Core/CommandLineOptionBase.cs | 7 +- .../Core/Exceptions/CommandParseException.cs | 6 +- .../Exceptions/OptionNotFoundException.cs | 20 ++--- .../Core/Exceptions/OptionParseException.cs | 17 +++- CommandLineParser/Core/Usage/UsageBuilder.cs | 86 +++++++++++++++++++ CommandLineParser/Core/Usage/UsagePrinter.cs | 33 +++++++ .../Core/Utils/ExtensionMethods.cs | 9 ++ 21 files changed, 371 insertions(+), 77 deletions(-) create mode 100644 CommandLineParser.Tests/Usage/UsagePrinterTests.cs create mode 100644 CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs create mode 100644 CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs create mode 100644 CommandLineParser/Abstractions/Usage/IUsageBuilder.cs delete mode 100644 CommandLineParser/Abstractions/Usage/IUsageDisplay.cs create mode 100644 CommandLineParser/Abstractions/Usage/IUsagePrinter.cs create mode 100644 CommandLineParser/Core/Usage/UsageBuilder.cs create mode 100644 CommandLineParser/Core/Usage/UsagePrinter.cs diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index 070565d..1c33635 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -46,7 +46,7 @@ public void AddCommandType() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("bla"))); } [Fact] @@ -58,16 +58,23 @@ public void AddCommandTypeWithGenericOption() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("bla"))); } private class MyComand : Command { + public override void OnConfigure(ICommandConfigurationBuilder builder) + { + base.OnConfigure(builder); + + builder.Name("bla").Required(); + } + public override void OnConfigure(ICommandConfigurationBuilder builder) { base.OnConfigure(builder); - builder.Name("-bla").Required(); + builder.Name("bla").Required(); } public override void OnExecute(object options, object commandOptions) diff --git a/CommandLineParser.Tests/Usage/UsagePrinterTests.cs b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs new file mode 100644 index 0000000..c3d814d --- /dev/null +++ b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs @@ -0,0 +1,33 @@ +using MatthiWare.CommandLine; +using MatthiWare.CommandLine.Abstractions.Usage; +using MatthiWare.CommandLine.Core.Attributes; +using Moq; +using Xunit; + +namespace MatthiWare.CommandLineParser.Tests.Usage +{ + public class UsagePrinterTests + { + private class UsagePrinterGetsCalledOptions + { + [Name("o"), Required] + public string Option { get; set; } + } + + [Theory] + [InlineData(new string[] { }, true)] + [InlineData(new string[] { "-o", "bla" }, false)] + [InlineData(new string[] { "-xd", "bla" }, true)] + public void UsagePrintGetsCalledInCorrectCases(string[] args, bool called) + { + var printerMock = new Mock(); + var parser = new CommandLineParser(); + + parser.Printer = printerMock.Object; + + parser.Parse(args); + + printerMock.Verify(mock => mock.PrintUsage(), called ? Times.Once() : Times.Never()); + } + } +} diff --git a/CommandLineParser/Abstractions/Command/Command`.cs b/CommandLineParser/Abstractions/Command/Command`.cs index bfe1a85..99d8cb1 100644 --- a/CommandLineParser/Abstractions/Command/Command`.cs +++ b/CommandLineParser/Abstractions/Command/Command`.cs @@ -10,6 +10,15 @@ public abstract class Command : where TOptions : class, new() where TCommandOptions : class, new() { + /// + /// Configures the command + /// for more info. + /// + /// + public virtual void OnConfigure(ICommandConfigurationBuilder builder) + { + base.OnConfigure(builder); + } /// /// Executes the command diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs new file mode 100644 index 0000000..7e59d37 --- /dev/null +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq.Expressions; + +namespace MatthiWare.CommandLine.Abstractions.Command +{ + public interface ICommandConfigurationBuilder + : ICommandConfigurationBuilder + where TSource : class + { + /// + /// Configures an option in the model + /// + /// Type of the property + /// Model property to configure + /// + IOptionBuilder Configure(Expression> selector); + + /// + /// Configures if the command is required + /// + /// True or false + /// + new ICommandConfigurationBuilder Required(bool required = true); + + /// + /// Configures the description text for the command + /// + /// True or false + /// + new ICommandConfigurationBuilder Description(string description); + + /// + /// Configures the command name + /// + /// Short name + /// + new ICommandConfigurationBuilder Name(string name); + } +} \ No newline at end of file diff --git a/CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs b/CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs new file mode 100644 index 0000000..7103b32 --- /dev/null +++ b/CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace MatthiWare.CommandLine.Abstractions.Command +{ + /// + /// Container that holds options and subcommands. + /// + public interface ICommandLineCommandContainer + { + /// + /// Read-only list of available sub-commands + /// to configure or add an command + /// + IReadOnlyList Commands { get; } + + /// + /// Read-only list of available options for this command + /// to configure or add an option + /// + IReadOnlyList Options { get; } + } +} diff --git a/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs b/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs index 95c7a73..5fa0266 100644 --- a/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs +++ b/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs @@ -10,11 +10,6 @@ namespace MatthiWare.CommandLine.Abstractions.Command /// public interface ICommandLineCommandParser { - /// - /// Read-only collection of the options for the command - /// - IReadOnlyList Options { get; } - /// /// Parses the arguments /// diff --git a/CommandLineParser/Abstractions/ICommandLineParser'.cs b/CommandLineParser/Abstractions/ICommandLineParser'.cs index 56dc8b6..b0f234b 100644 --- a/CommandLineParser/Abstractions/ICommandLineParser'.cs +++ b/CommandLineParser/Abstractions/ICommandLineParser'.cs @@ -4,6 +4,7 @@ using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Usage; namespace MatthiWare.CommandLine.Abstractions { @@ -13,16 +14,9 @@ public interface ICommandLineParser #region Properties /// - /// Read-only list of available sub-commands - /// to configure or add an command + /// Tool to print usage info. /// - IReadOnlyList Commands { get; } - - /// - /// Read-only list of available options for this command - /// to configure or add an option - /// - IReadOnlyList Options { get; } + IUsagePrinter Printer { get; set; } /// /// Factory to resolve the argument type diff --git a/CommandLineParser/Abstractions/Usage/IUsageBuilder.cs b/CommandLineParser/Abstractions/Usage/IUsageBuilder.cs new file mode 100644 index 0000000..4a3186f --- /dev/null +++ b/CommandLineParser/Abstractions/Usage/IUsageBuilder.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MatthiWare.CommandLine.Abstractions.Command; + +namespace MatthiWare.CommandLine.Abstractions.Usage +{ + public interface IUsageBuilder + { + void Print(); + void PrintUsage(string name, bool hasOptions, bool hasCommands); + void PrintOptions(IEnumerable options); + void PrintOption(ICommandLineOption option); + void PrintCommandDescriptions(IEnumerable commands); + void PrintCommandDescription(ICommandLineCommand command); + void PrintCommand(string name, ICommandLineCommandContainer container); + } +} diff --git a/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs b/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs deleted file mode 100644 index a150183..0000000 --- a/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MatthiWare.CommandLine.Abstractions.Usage -{ - public interface IUsageDisplay - { - string ToUsage(); - string ToShortUsage(); - } -} diff --git a/CommandLineParser/Abstractions/Usage/IUsagePrinter.cs b/CommandLineParser/Abstractions/Usage/IUsagePrinter.cs new file mode 100644 index 0000000..862a24d --- /dev/null +++ b/CommandLineParser/Abstractions/Usage/IUsagePrinter.cs @@ -0,0 +1,10 @@ +using MatthiWare.CommandLine.Abstractions.Command; + +namespace MatthiWare.CommandLine.Abstractions.Usage +{ + public interface IUsagePrinter + { + void PrintUsage(); + void PrintUsage(ICommandLineCommand command); + } +} diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index 7ac4907..2f09a8c 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -9,11 +9,13 @@ using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Usage; using MatthiWare.CommandLine.Core; using MatthiWare.CommandLine.Core.Attributes; using MatthiWare.CommandLine.Core.Command; using MatthiWare.CommandLine.Core.Exceptions; using MatthiWare.CommandLine.Core.Parsing; +using MatthiWare.CommandLine.Core.Usage; [assembly: InternalsVisibleTo("CommandLineParser.Tests")] @@ -23,7 +25,7 @@ namespace MatthiWare.CommandLine /// Command line parser /// /// Options model - public sealed class CommandLineParser : ICommandLineParser + public sealed class CommandLineParser : ICommandLineParser, ICommandLineCommandContainer where TOption : class, new() { private readonly TOption m_option; @@ -31,6 +33,11 @@ public sealed class CommandLineParser : ICommandLineParser private readonly List m_commands; private readonly CommandLineParserOptions m_parserOptions; + /// + /// Tool to print usage info. + /// + public IUsagePrinter Printer { get; set; } + /// /// Read-only collection of options specified /// @@ -108,6 +115,8 @@ public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolv ArgumentResolverFactory = argumentResolverFactory; ContainerResolver = containerResolver; + Printer = new UsagePrinter(parserOptions, this); + InitialzeModel(); } @@ -158,10 +167,33 @@ public IParserResult Parse(string[] args) AutoExecuteCommands(result); + AutoPrintUsageAndErrors(errors, args.Length > 0); + return result; } - private void AutoExecuteCommands(ParseResult result) + private void AutoPrintUsageAndErrors(ICollection errors, bool argsSuppplied) + { + if (!m_parserOptions.AutoPrintUsageAndErrors) return; + + if (!argsSuppplied) + PrintHelp(); + else if (errors.Count > 0) + PrintErrors(errors); + } + + private void PrintErrors(ICollection errors) + { + foreach (var error in errors) + Console.Error.WriteLine(error); + + PrintHelp(); + } + + private void PrintHelp() + => Printer.PrintUsage(); + + private void AutoExecuteCommands(IParserResult result) { if (result.HasErrors) return; @@ -197,7 +229,7 @@ private void ParseOptions(IList errors, ParseResult result, if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired) { - errors.Add(new OptionNotFoundException(option)); + errors.Add(new OptionNotFoundException(m_parserOptions, option)); continue; } diff --git a/CommandLineParser/CommandLineParserOptions.cs b/CommandLineParser/CommandLineParserOptions.cs index 2378b00..2d02d28 100644 --- a/CommandLineParser/CommandLineParserOptions.cs +++ b/CommandLineParser/CommandLineParserOptions.cs @@ -4,8 +4,15 @@ public class CommandLineParserOptions { public string PrefixShortOption { get; set; } = "-"; public string PrefixLongOption { get; set; } = "--"; - public string HelpOptionName { get; set; } = "help"; + /// + /// Help option name. + /// Accepts both formatted and unformatted help name. + /// If the name is a single string it will use the + /// If the name is split for example h|help it will use the following format |]]> + /// + public string HelpOptionName { get; set; } = "h|help"; public bool EnableHelpOption { get; set; } = true; + public bool AutoPrintUsageAndErrors { get; set; } = true; public string AppName { get; set; } } } diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 974d7e0..6eb83da 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -10,13 +10,16 @@ namespace MatthiWare.CommandLine.Core.Command { internal abstract class CommandLineCommandBase : ICommandLineCommandParser, - ICommandLineCommand, - IUsageDisplay + ICommandLineCommandContainer, + ICommandLineCommand { protected readonly List m_options = new List(); + protected readonly List m_commands = new List(); public IReadOnlyList Options => m_options.AsReadOnly(); + public IReadOnlyList Commands => m_commands.AsReadOnly(); + public string Name { get; protected set; } public string Description { get; protected set; } public bool IsRequired { get; protected set; } @@ -25,25 +28,5 @@ internal abstract class CommandLineCommandBase : public abstract void Execute(); public abstract ICommandParserResult Parse(IArgumentManager argumentManager); - - public string ToShortUsage() - => $" {Name}\t\t{Description}"; - - public string ToUsage() - { - var sb = new StringBuilder(); - - sb.AppendLine(Description); - - if (m_options.Count == 0) - return sb.ToString(); - - sb.AppendLine("Options: "); - - foreach (var opt in m_options) - sb.AppendLine(opt.ToUsage()); - - return sb.ToString(); - } } } diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 6fdf796..29fedb7 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -16,6 +16,7 @@ internal class CommandLineCommand : ICommandBuilder, ICommandBuilder, ICommandConfigurationBuilder, + ICommandConfigurationBuilder, IOptionConfigurator where TOption : class where TCommandOption : class, new() @@ -61,7 +62,7 @@ public override ICommandParserResult Parse(IArgumentManager argumentManager) { if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired) { - errors.Add(new OptionNotFoundException(option)); + errors.Add(new OptionNotFoundException(m_parserOptions, option)); continue; } @@ -183,5 +184,26 @@ ICommandBuilder ICommandBuilder.Name(string name) return this; } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.Required(bool required) + { + IsRequired = required; + + return this; + } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.Description(string description) + { + Description = description; + + return this; + } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string name) + { + Name = name; + + return this; + } } } diff --git a/CommandLineParser/Core/CommandLineOptionBase.cs b/CommandLineParser/Core/CommandLineOptionBase.cs index 6616c55..ccc0c9f 100644 --- a/CommandLineParser/Core/CommandLineOptionBase.cs +++ b/CommandLineParser/Core/CommandLineOptionBase.cs @@ -8,7 +8,7 @@ namespace MatthiWare.CommandLine.Core { [DebuggerDisplay("Cmd Option {ShortName ?? LongName}, Req: {IsRequired}, HasDefault: {HasDefault}")] - internal abstract class CommandLineOptionBase : IParser, ICommandLineOption, IUsageDisplay + internal abstract class CommandLineOptionBase : IParser, ICommandLineOption { public string ShortName { get; protected set; } public string LongName { get; protected set; } @@ -24,11 +24,6 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption, IUs public abstract void Parse(ArgumentModel model); - public string ToShortUsage() => ToUsage(); - - public string ToUsage() - => $" {(HasShortName ? ShortName : string.Empty)}{(HasShortName && HasLongName ? "|" : string.Empty)}{(HasLongName ? LongName : string.Empty)}\t\t{Description}"; - public abstract void UseDefault(); } } diff --git a/CommandLineParser/Core/Exceptions/CommandParseException.cs b/CommandLineParser/Core/Exceptions/CommandParseException.cs index 751aa85..956ad2f 100644 --- a/CommandLineParser/Core/Exceptions/CommandParseException.cs +++ b/CommandLineParser/Core/Exceptions/CommandParseException.cs @@ -12,12 +12,12 @@ public class CommandParseException : Exception /// /// Command that caused the parsing error /// - public ICommandLineCommand Option { get; set; } + public ICommandLineCommand Command { get; set; } - public CommandParseException(ICommandLineCommand option, Exception innerException) + public CommandParseException(ICommandLineCommand command, Exception innerException) : base("", innerException) { - Option = option; + Command = command; } } } diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index 863d013..3131e7c 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -14,21 +14,21 @@ public class OptionNotFoundException : KeyNotFoundException /// public ICommandLineOption Option { get; private set; } - public OptionNotFoundException(ICommandLineOption option) - : base(CreateMessage(option)) + public OptionNotFoundException(CommandLineParserOptions parserOptions, ICommandLineOption option) + : base(CreateMessage(parserOptions, option)) { } - private static string CreateMessage(ICommandLineOption option) + private static string CreateMessage(CommandLineParserOptions parserOptions, ICommandLineOption option) { - bool hasShortName = option.HasShortName; - bool hasLongName = option.HasLongName; - bool hasBoth = hasShortName && hasLongName; + bool hasShort = option.HasShortName; + bool hasLong = option.HasLongName; + bool hasBoth = hasShort && hasLong; - string shortName = hasShortName ? $"'{option.ShortName}'" : string.Empty; - string longName = hasLongName ? $"'{option.LongName}'" : string.Empty; - string or = hasBoth ? " or " : string.Empty; + string hasBothSeperator = hasBoth ? "|" : string.Empty; + string shortName = hasShort ? $"{parserOptions.PrefixShortOption}{option.ShortName}" : string.Empty; + string longName = hasLong ? $"{parserOptions.PrefixLongOption}{option.LongName}" : string.Empty; - return $"Required argument {shortName}{or}{longName} not found!"; + return $"Required argument {shortName}{hasBothSeperator}{longName} not found!"; } } } diff --git a/CommandLineParser/Core/Exceptions/OptionParseException.cs b/CommandLineParser/Core/Exceptions/OptionParseException.cs index 2cf99f4..e67965c 100644 --- a/CommandLineParser/Core/Exceptions/OptionParseException.cs +++ b/CommandLineParser/Core/Exceptions/OptionParseException.cs @@ -7,7 +7,7 @@ namespace MatthiWare.CommandLine.Core.Exceptions { /// /// Indicates that an option was unable to be parsed - /// This could be caused by an missing . + /// This could be caused by an missing . /// public class OptionParseException : Exception { @@ -15,10 +15,23 @@ public class OptionParseException : Exception private ArgumentModel argModel; public OptionParseException(ICommandLineOption option, ArgumentModel argModel) - : base($"Cannot parse option '{argModel.Key}:{argModel.Value ?? "NULL"}'.") + : base(CreateMessage(option, argModel)) { this.option = option; this.argModel = argModel; } + + private static string CreateMessage(ICommandLineOption option, ArgumentModel argModel) + { + bool hasShort = option.HasShortName; + bool hasLong = option.HasLongName; + bool hasBoth = hasShort && hasLong; + + string hasBothSeperator = hasBoth ? "|" : string.Empty; + string shortName = hasShort ? option.ShortName : string.Empty; + string longName = hasLong ? option.LongName : string.Empty; + + return $"Unable to parse option {shortName}{hasBothSeperator}{longName} value '{argModel.Value}' is invalid!"; + } } } diff --git a/CommandLineParser/Core/Usage/UsageBuilder.cs b/CommandLineParser/Core/Usage/UsageBuilder.cs new file mode 100644 index 0000000..dd03a50 --- /dev/null +++ b/CommandLineParser/Core/Usage/UsageBuilder.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Usage; +using MatthiWare.CommandLine.Core.Command; +using MatthiWare.CommandLine.Core.Utils; + +namespace MatthiWare.CommandLine.Core.Usage +{ + internal class UsageBuilder : IUsageBuilder + { + private readonly StringBuilder stringBuilder = new StringBuilder(); + private readonly CommandLineParserOptions parserOptions; + + public UsageBuilder(CommandLineParserOptions parserOptions) + => this.parserOptions = parserOptions; + + public void Print() + { + Console.WriteLine(stringBuilder.ToString()); + stringBuilder.Clear(); + } + + public void PrintUsage(string name, bool hasOptions, bool hasCommands) + { + stringBuilder.AppendLine() + .Append("Usage: ") + .Append(parserOptions.AppName) + .AppendIf(!string.IsNullOrWhiteSpace(parserOptions.AppName), " ") + .Append(name) + .AppendIf(!string.IsNullOrWhiteSpace(name), " ") + .Append(hasOptions ? "[options] " : string.Empty) + .Append(hasCommands ? "[commands]" : string.Empty) + .AppendLine(); + + } + + public void PrintCommand(string name, ICommandLineCommandContainer container) + { + PrintUsage(name, container.Options.Any(), container.Commands.Any()); + + PrintOptions(container.Options); + + PrintCommandDescriptions(container.Commands); + } + + public void PrintCommandDescription(ICommandLineCommand command) + => stringBuilder.AppendLine($"\t{command.Name}\t{command.Description}"); + + public void PrintCommandDescriptions(IEnumerable commands) + { + if (!commands.Any()) return; + + stringBuilder.AppendLine().AppendLine("Commands: "); + + foreach (var cmd in commands) + PrintCommandDescription(cmd); + } + + public void PrintOption(ICommandLineOption option) + { + bool hasShort = option.HasShortName; + bool hasLong = option.HasLongName; + bool hasBoth = hasShort && hasLong; + + string hasBothSeperator = hasBoth ? "|" : string.Empty; + string shortName = hasShort ? $"{parserOptions.PrefixShortOption}{option.ShortName}" : string.Empty; + string longName = hasLong ? $"{parserOptions.PrefixLongOption}{option.LongName}" : string.Empty; + + stringBuilder.AppendLine($"\t{shortName}{hasBothSeperator}{longName}\t{option.Description}"); + } + + public void PrintOptions(IEnumerable options) + { + if (!options.Any()) return; + + stringBuilder.AppendLine().AppendLine("Options: "); + + foreach (var opt in options) + PrintOption(opt); + } + } +} diff --git a/CommandLineParser/Core/Usage/UsagePrinter.cs b/CommandLineParser/Core/Usage/UsagePrinter.cs new file mode 100644 index 0000000..cba8b0c --- /dev/null +++ b/CommandLineParser/Core/Usage/UsagePrinter.cs @@ -0,0 +1,33 @@ +using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Usage; + +namespace MatthiWare.CommandLine.Core.Usage +{ + internal class UsagePrinter : IUsagePrinter + { + private readonly CommandLineParserOptions m_parserOptions; + private readonly IUsageBuilder m_usageBuilder; + private readonly ICommandLineCommandContainer m_commandContainer; + + public UsagePrinter(CommandLineParserOptions parserOptions, ICommandLineCommandContainer container) + { + m_parserOptions = parserOptions; + m_commandContainer = container; + + m_usageBuilder = new UsageBuilder(parserOptions); + } + + public void PrintUsage() + { + m_usageBuilder.PrintCommand(string.Empty, m_commandContainer); + m_usageBuilder.Print(); + } + + public void PrintUsage(ICommandLineCommand command) + { + m_usageBuilder.PrintCommand(command.Name, (ICommandLineCommandContainer)command); + m_usageBuilder.Print(); + } + } +} diff --git a/CommandLineParser/Core/Utils/ExtensionMethods.cs b/CommandLineParser/Core/Utils/ExtensionMethods.cs index dc1408c..4478195 100644 --- a/CommandLineParser/Core/Utils/ExtensionMethods.cs +++ b/CommandLineParser/Core/Utils/ExtensionMethods.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace MatthiWare.CommandLine.Core.Utils { @@ -28,5 +29,13 @@ public static bool IsAssignableToGenericType(this Type self, Type genericType) return IsAssignableToGenericType(baseType, genericType); } + + public static StringBuilder AppendIf(this StringBuilder self, bool contition, string text) + { + if (contition) + self.Append(text); + + return self; + } } } From 2c508e5e0ca9be7542d0cd6fbd54a4bee331baba Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Sun, 23 Dec 2018 11:29:10 +0100 Subject: [PATCH 11/41] Generic command builder exposed wrong return type. --- .../Abstractions/Command/ICommandBuilder'.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index 621685b..09e370c 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -39,5 +39,13 @@ public interface ICommandBuilder : ICommandConfigurationBuilde /// name /// new ICommandBuilder Name(string name); + + /// + /// Configures how the command should be invoked. + /// Default behavior is to auto invoke the command. + /// + /// True if the command executor will be invoked (default), false if you want to invoke manually. + /// + new ICommandBuilder InvokeCommand(bool invoke); } } From afbe0e0adc42ab15a6b07db4dd7887faf1f13e1b Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Tue, 25 Dec 2018 09:34:58 +0100 Subject: [PATCH 12/41] Add subcommand test --- .../Command/SubCommandTests.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 CommandLineParser.Tests/Command/SubCommandTests.cs diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs new file mode 100644 index 0000000..6622177 --- /dev/null +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Threading; +using MatthiWare.CommandLine; +using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Core.Attributes; +using Xunit; + +namespace MatthiWare.CommandLineParser.Tests.Command +{ + public class SubCommandTests + { + [Fact] + public void TestSubCommandWorksCorrectly() + { + var lock1 = new ManualResetEventSlim(); + var lock2 = new ManualResetEventSlim(); + + var containerResolver = new CustomInstantiator(lock1, lock2); + + var parser = new CommandLineParser(containerResolver); + + var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15" }); + + Assert.False(result.HasErrors); + + Assert.True(lock1.Wait(1000), "MainCommand didn't execute in time."); + Assert.True(lock2.Wait(1000), "SubCommand didn't execute in time."); + } + + private class CustomInstantiator : IContainerResolver + { + private ManualResetEventSlim lock1; + private ManualResetEventSlim lock2; + + public CustomInstantiator(ManualResetEventSlim lock1, ManualResetEventSlim lock2) + { + this.lock1 = lock1; + this.lock2 = lock2; + } + + public T Resolve() + { + if (typeof(T) == typeof(MainCommand)) + return (T)Activator.CreateInstance(typeof(T), lock1); + else if (typeof(T) == typeof(SubCommand)) + return (T)Activator.CreateInstance(typeof(T), lock2); + else + return default; + } + } + } + + public class MainCommand : Command + { + private readonly ManualResetEventSlim locker; + + public MainCommand(ManualResetEventSlim locker) + { + this.locker = locker; + } + + public override void OnConfigure(ICommandConfigurationBuilder builder) + { + builder + .Name("main") + .Required(); + } + + public override void OnExecute(MainModel options, SubModel commandOptions) + { + base.OnExecute(options, commandOptions); + + locker.Set(); + } + } + + public class SubCommand : Command + { + + private readonly ManualResetEventSlim locker; + + public SubCommand(ManualResetEventSlim locker) + { + this.locker = locker; + } + + public override void OnConfigure(ICommandConfigurationBuilder builder) + { + builder + .Name("sub") + .Required(); + } + + public override void OnExecute(MainModel options, SubModel commandOptions) + { + base.OnExecute(options, commandOptions); + + locker.Set(); + } + } + + public class MainModel + { + [Required, Name("b")] + public string Bla { get; set; } + public MainCommand MainCommand { get; set; } + } + + public class SubModel + { + [Required, Name("i")] + public int Item { get; set; } + public SubCommand SubCommand { get; set; } + } +} From f8ef25aa46fe53b3e3ab1e3d87af167e32d91e62 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Tue, 25 Dec 2018 11:28:17 +0100 Subject: [PATCH 13/41] Add subcommands --- .../Parsing/Command/ICommandParserResult.cs | 11 +- CommandLineParser/CommandLineParser.cs | 32 +++- .../Core/Command/CommandLineCommandBase.cs | 6 +- ...mmandLineCommand`TOption`TCommandOption.cs | 160 +++++++++++++++++- .../Core/Command/CommandParserResult.cs | 10 ++ 5 files changed, 204 insertions(+), 15 deletions(-) diff --git a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs index 82d1f1f..eb267a4 100644 --- a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs +++ b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs @@ -1,5 +1,6 @@ using System; - +using System.Collections; +using System.Collections.Generic; using MatthiWare.CommandLine.Abstractions.Command; namespace MatthiWare.CommandLine.Abstractions.Parsing.Command @@ -9,6 +10,14 @@ namespace MatthiWare.CommandLine.Abstractions.Parsing.Command /// public interface ICommandParserResult { + /// + /// Subcommands of the current command + /// + IReadOnlyCollection SubCommands { get; } + + /// + /// The associated command + /// ICommandLineCommand Command { get; } /// diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index 2f09a8c..be6416d 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -9,6 +9,7 @@ using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Parsing.Command; using MatthiWare.CommandLine.Abstractions.Usage; using MatthiWare.CommandLine.Core; using MatthiWare.CommandLine.Core.Attributes; @@ -16,6 +17,7 @@ using MatthiWare.CommandLine.Core.Exceptions; using MatthiWare.CommandLine.Core.Parsing; using MatthiWare.CommandLine.Core.Usage; +using MatthiWare.CommandLine.Core.Utils; [assembly: InternalsVisibleTo("CommandLineParser.Tests")] @@ -197,8 +199,16 @@ private void AutoExecuteCommands(IParserResult result) { if (result.HasErrors) return; - foreach (var cmd in result.CommandResults.Where(r => r.Command.AutoExecute)) + ExecuteCommandParserResults(result.CommandResults.Where(r => r.Command.AutoExecute)); + } + + private void ExecuteCommandParserResults(IEnumerable results) + { + foreach (var cmd in results) cmd.ExecuteCommand(); + + foreach (var cmd in results) + ExecuteCommandParserResults(cmd.SubCommands.Where(sub => sub.Command.AutoExecute)); } private void ParseCommands(IList errors, ParseResult result, IArgumentManager argumentManager) @@ -259,7 +269,7 @@ private void ParseOptions(IList errors, ParseResult result, /// Builder for the command, public ICommandBuilder AddCommand() where TCommandOption : class, new() { - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); m_commands.Add(command); @@ -275,7 +285,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); cmdConfigurator.OnConfigure(command); @@ -295,7 +305,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); cmdConfigurator.OnConfigure(command); @@ -310,7 +320,7 @@ public void RegisterCommand() /// Builder for the command, public ICommandBuilder AddCommand() { - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); m_commands.Add(command); @@ -360,6 +370,18 @@ private void InitialzeModel() if (ignoreSet) continue; // Ignore the configured actions for this option. + if (propInfo.PropertyType.IsAssignableToGenericType(typeof(Command<>))) + { + var genericTypes = propInfo.PropertyType.BaseType.GenericTypeArguments; + var method = GetType().GetMethods().First(m => + { + return (m.Name == nameof(RegisterCommand) && m.IsGenericMethod && m.GetGenericArguments().Length == genericTypes.Length); + }); + var registerCommand = genericTypes.Length > 1 ? method.MakeGenericMethod(propInfo.PropertyType, genericTypes[1]) : method.MakeGenericMethod(propInfo.PropertyType); + + registerCommand.Invoke(this, null); + } + foreach (var action in actions) action(); } diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 6eb83da..fd14ec5 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -13,10 +13,10 @@ internal abstract class CommandLineCommandBase : ICommandLineCommandContainer, ICommandLineCommand { - protected readonly List m_options = new List(); - protected readonly List m_commands = new List(); + protected readonly Dictionary m_options = new Dictionary(); + protected readonly List m_commands = new List(); - public IReadOnlyList Options => m_options.AsReadOnly(); + public IReadOnlyList Options => new ReadOnlyCollectionWrapper(m_options.Values); public IReadOnlyList Commands => m_commands.AsReadOnly(); diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 29fedb7..f518817 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; - +using System.Reflection; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Abstractions.Parsing.Command; +using MatthiWare.CommandLine.Core.Attributes; using MatthiWare.CommandLine.Core.Exceptions; +using MatthiWare.CommandLine.Core.Utils; namespace MatthiWare.CommandLine.Core.Command { @@ -24,18 +27,22 @@ internal class CommandLineCommand : private readonly TCommandOption m_commandOption; private readonly TOption m_baseOption; private readonly IArgumentResolverFactory m_resolverFactory; + private readonly IContainerResolver m_containerResolver; private readonly CommandLineParserOptions m_parserOptions; private Action m_executor; private Action m_executor2; - public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, TOption option) + public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, IContainerResolver containerResolver, TOption option) { m_parserOptions = parserOptions; m_commandOption = new TCommandOption(); + m_containerResolver = containerResolver; m_resolverFactory = resolverFactory; m_baseOption = option; + + InitialzeModel(); } public override void Execute() @@ -46,11 +53,22 @@ public override void Execute() public IOptionBuilder Configure(Expression> selector) { - var option = new CommandLineOption(m_parserOptions, m_commandOption, selector, m_resolverFactory); + var memberInfo = ((MemberExpression)selector.Body).Member; + var key = $"{memberInfo.DeclaringType.FullName}.{memberInfo.Name}"; + + return ConfigureInternal(selector, key); + } + + private IOptionBuilder ConfigureInternal(LambdaExpression selector, string key) + { + if (!m_options.ContainsKey(key)) + { + var option = new CommandLineOption(m_parserOptions, m_commandOption, selector, m_resolverFactory); - m_options.Add(option); + m_options.Add(key, option); + } - return option; + return m_options[key] as IOptionBuilder; } public override ICommandParserResult Parse(IArgumentManager argumentManager) @@ -58,8 +76,27 @@ public override ICommandParserResult Parse(IArgumentManager argumentManager) var result = new CommandParserResult(this); var errors = new List(); - foreach (var option in m_options) + foreach (var cmd in m_commands) { + if (!argumentManager.TryGetValue(cmd, out ArgumentModel model) && cmd.IsRequired) + { + errors.Add(new CommandNotFoundException(cmd)); + + continue; + } + + var cmdParseResult = cmd.Parse(argumentManager); + + if (cmdParseResult.HasErrors) + errors.Add(new CommandParseException(cmd, cmdParseResult.Error)); + + result.MergeResult(cmdParseResult); + } + + foreach (var o in m_options) + { + var option = o.Value; + if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired) { errors.Add(new OptionNotFoundException(m_parserOptions, option)); @@ -205,5 +242,116 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder + /// Initializes the model class with the attributes specified. + /// + private void InitialzeModel() + { + var properties = typeof(TOption).GetProperties(); + + foreach (var propInfo in properties) + { + var attributes = propInfo.GetCustomAttributes(true); + + var lambda = GetLambdaExpression(propInfo, out string key); + + var actions = new List(4); + bool ignoreSet = false; + + foreach (var attribute in attributes) + { + if (ignoreSet) break; + + switch (attribute) + { + // Ignore has been set, skip all the other attributes and DO NOT execute the action list. + case IgnoreAttribute ignore: + ignoreSet = true; + continue; + case RequiredAttribute required: + actions.Add(() => ConfigureInternal(lambda, key).Required(required.Required)); + break; + case DefaultValueAttribute defaultValue: + actions.Add(() => ConfigureInternal(lambda, key).Default(defaultValue.DefaultValue)); + break; + case DescriptionAttribute helpText: + actions.Add(() => ConfigureInternal(lambda, key).Description(helpText.Description)); + break; + case NameAttribute name: + actions.Add(() => ConfigureInternal(lambda, key).Name(name.ShortName, name.LongName)); + break; + } + } + + if (ignoreSet) continue; // Ignore the configured actions for this option. + + if (propInfo.PropertyType.IsAssignableToGenericType(typeof(Command<>))) + { + var genericTypes = propInfo.PropertyType.BaseType.GenericTypeArguments; + var method = GetType().GetMethods().First(m => + { + return (m.Name == nameof(RegisterCommand) && m.IsGenericMethod && m.GetGenericArguments().Length == genericTypes.Length); + }); + var registerCommand = genericTypes.Length > 1 ? method.MakeGenericMethod(propInfo.PropertyType, genericTypes[1]) : method.MakeGenericMethod(propInfo.PropertyType); + + registerCommand.Invoke(this, null); + } + + foreach (var action in actions) + action(); + } + + LambdaExpression GetLambdaExpression(PropertyInfo propInfo, out string key) + { + var entityType = propInfo.DeclaringType; + var propType = propInfo.PropertyType; + var parameter = Expression.Parameter(entityType, entityType.FullName); + var property = Expression.Property(parameter, propInfo); + var funcType = typeof(Func<,>).MakeGenericType(entityType, propType); + + key = $"{entityType.ToString()}.{propInfo.Name}"; + + return Expression.Lambda(funcType, property, parameter); + } + } + + /// + /// Registers a command type + /// + /// Command type, must be inherit + public void RegisterCommand() + where TCommand : Command + { + var cmdConfigurator = m_containerResolver.Resolve(); + + var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_commandOption); + + cmdConfigurator.OnConfigure(command); + + command.OnExecuting(cmdConfigurator.OnExecute); + + m_commands.Add(command); + } + + /// + /// Registers a command type + /// + /// + /// + public void RegisterCommand() + where TCommand : Command + where V : class, new() + { + var cmdConfigurator = m_containerResolver.Resolve(); + + var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_commandOption); + + cmdConfigurator.OnConfigure(command); + + command.OnExecuting((Action)cmdConfigurator.OnExecute); + + m_commands.Add(command); + } } } diff --git a/CommandLineParser/Core/Command/CommandParserResult.cs b/CommandLineParser/Core/Command/CommandParserResult.cs index f9c6289..a2adae6 100644 --- a/CommandLineParser/Core/Command/CommandParserResult.cs +++ b/CommandLineParser/Core/Command/CommandParserResult.cs @@ -10,6 +10,7 @@ namespace MatthiWare.CommandLine.Core.Command internal class CommandParserResult : ICommandParserResult { private readonly CommandLineCommandBase m_cmd; + private readonly List commandParserResults = new List(); public bool HasErrors { get; private set; } @@ -17,6 +18,8 @@ internal class CommandParserResult : ICommandParserResult public ICommandLineCommand Command => m_cmd; + public IReadOnlyCollection SubCommands => commandParserResults; + public CommandParserResult(CommandLineCommandBase command) { m_cmd = command; @@ -33,6 +36,13 @@ public void MergeResult(ICollection errors) errors.First(); } + public void MergeResult(ICommandParserResult result) + { + HasErrors |= result.HasErrors; + + commandParserResults.Add(result); + } + public void ExecuteCommand() => m_cmd.Execute(); } } From 4cb462dc35758aa7ae934652f56d55a81524651d Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 26 Dec 2018 10:02:23 +0100 Subject: [PATCH 14/41] Subcommands fire --- .../Command/SubCommandTests.cs | 17 +++++++++++------ ...CommandLineCommand`TOption`TCommandOption.cs | 10 +++++----- .../Core/Parsing/ArgumentManager.cs | 4 ++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs index 6622177..b858886 100644 --- a/CommandLineParser.Tests/Command/SubCommandTests.cs +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -20,7 +20,7 @@ public void TestSubCommandWorksCorrectly() var parser = new CommandLineParser(containerResolver); - var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15" }); + var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15", "-n", "-1" }); Assert.False(result.HasErrors); @@ -60,7 +60,7 @@ public MainCommand(ManualResetEventSlim locker) this.locker = locker; } - public override void OnConfigure(ICommandConfigurationBuilder builder) + public override void OnConfigure(ICommandConfigurationBuilder builder) { builder .Name("main") @@ -75,9 +75,8 @@ public override void OnExecute(MainModel options, SubModel commandOptions) } } - public class SubCommand : Command + public class SubCommand : Command { - private readonly ManualResetEventSlim locker; public SubCommand(ManualResetEventSlim locker) @@ -85,14 +84,14 @@ public SubCommand(ManualResetEventSlim locker) this.locker = locker; } - public override void OnConfigure(ICommandConfigurationBuilder builder) + public override void OnConfigure(ICommandConfigurationBuilder builder) { builder .Name("sub") .Required(); } - public override void OnExecute(MainModel options, SubModel commandOptions) + public override void OnExecute(MainModel options, SubSubModel commandOptions) { base.OnExecute(options, commandOptions); @@ -113,4 +112,10 @@ public class SubModel public int Item { get; set; } public SubCommand SubCommand { get; set; } } + + public class SubSubModel + { + [Required, Name("n")] + public int Nothing { get; set; } + } } diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index f518817..2f93506 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -21,7 +21,7 @@ internal class CommandLineCommand : ICommandConfigurationBuilder, ICommandConfigurationBuilder, IOptionConfigurator - where TOption : class + where TOption : class, new() where TCommandOption : class, new() { private readonly TCommandOption m_commandOption; @@ -248,7 +248,7 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder private void InitialzeModel() { - var properties = typeof(TOption).GetProperties(); + var properties = typeof(TCommandOption).GetProperties(); foreach (var propInfo in properties) { @@ -340,16 +340,16 @@ public void RegisterCommand() /// /// public void RegisterCommand() - where TCommand : Command + where TCommand : Command where V : class, new() { var cmdConfigurator = m_containerResolver.Resolve(); - var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_commandOption); + var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_baseOption); cmdConfigurator.OnConfigure(command); - command.OnExecuting((Action)cmdConfigurator.OnExecute); + command.OnExecuting((Action)cmdConfigurator.OnExecute); m_commands.Add(command); } diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index cbf9bfb..9ea93f0 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using MatthiWare.CommandLine.Abstractions; @@ -78,6 +79,8 @@ private void ParseCommands(IEnumerable cmds) SetArgumentUsed(optionIdx, option); } + + ParseCommands(cmd.Commands.Cast()); } } @@ -122,6 +125,7 @@ private int FindIndex(IArgument model, int startOffset = 0) public bool TryGetValue(IArgument argument, out ArgumentModel model) => resultCache.TryGetValue(argument, out model); + [DebuggerDisplay("{Argument}, used: {Used}, index: {Index}")] private class ArgumentValueHolder { public string Argument { get; set; } From c9e555fef4c1b878d25a373964ad7fec7e9f0960 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 27 Dec 2018 10:33:07 +0100 Subject: [PATCH 15/41] Improve usage system --- CommandLineParser/CommandLineParser.cs | 9 +++++++-- CommandLineParser/Core/Usage/UsageBuilder.cs | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index be6416d..7a71122 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -34,6 +35,7 @@ public sealed class CommandLineParser : ICommandLineParser, IC private readonly Dictionary m_options; private readonly List m_commands; private readonly CommandLineParserOptions m_parserOptions; + private readonly Process m_currentProcess; /// /// Tool to print usage info. @@ -117,7 +119,10 @@ public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolv ArgumentResolverFactory = argumentResolverFactory; ContainerResolver = containerResolver; - Printer = new UsagePrinter(parserOptions, this); + if (string.IsNullOrWhiteSpace(m_parserOptions.AppName)) + m_parserOptions.AppName = Process.GetCurrentProcess().ProcessName; + + Printer = new UsagePrinter(m_parserOptions, this); InitialzeModel(); } @@ -187,7 +192,7 @@ private void AutoPrintUsageAndErrors(ICollection errors, bool argsSup private void PrintErrors(ICollection errors) { foreach (var error in errors) - Console.Error.WriteLine(error); + Console.Error.WriteLine(error.Message); PrintHelp(); } diff --git a/CommandLineParser/Core/Usage/UsageBuilder.cs b/CommandLineParser/Core/Usage/UsageBuilder.cs index dd03a50..6fe7e5e 100644 --- a/CommandLineParser/Core/Usage/UsageBuilder.cs +++ b/CommandLineParser/Core/Usage/UsageBuilder.cs @@ -48,7 +48,7 @@ public void PrintCommand(string name, ICommandLineCommandContainer container) } public void PrintCommandDescription(ICommandLineCommand command) - => stringBuilder.AppendLine($"\t{command.Name}\t{command.Description}"); + => stringBuilder.AppendLine($" {command.Name}\t\t{command.Description}"); public void PrintCommandDescriptions(IEnumerable commands) { @@ -67,10 +67,10 @@ public void PrintOption(ICommandLineOption option) bool hasBoth = hasShort && hasLong; string hasBothSeperator = hasBoth ? "|" : string.Empty; - string shortName = hasShort ? $"{parserOptions.PrefixShortOption}{option.ShortName}" : string.Empty; - string longName = hasLong ? $"{parserOptions.PrefixLongOption}{option.LongName}" : string.Empty; + string shortName = hasShort ? option.ShortName : string.Empty; + string longName = hasLong ? option.LongName : string.Empty; - stringBuilder.AppendLine($"\t{shortName}{hasBothSeperator}{longName}\t{option.Description}"); + stringBuilder.AppendLine($" {shortName}{hasBothSeperator}{longName}\t{option.Description}"); } public void PrintOptions(IEnumerable options) From 760654e928dd10198ff6038ca5221838993a21bb Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 27 Dec 2018 10:33:37 +0100 Subject: [PATCH 16/41] Imrpove subcommand tests --- CommandLineParser.Tests/Command/SubCommandTests.cs | 4 ++-- CommandLineParser.Tests/Usage/UsagePrinterTests.cs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs index b858886..b228c24 100644 --- a/CommandLineParser.Tests/Command/SubCommandTests.cs +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -30,8 +30,8 @@ public void TestSubCommandWorksCorrectly() private class CustomInstantiator : IContainerResolver { - private ManualResetEventSlim lock1; - private ManualResetEventSlim lock2; + private readonly ManualResetEventSlim lock1; + private readonly ManualResetEventSlim lock2; public CustomInstantiator(ManualResetEventSlim lock1, ManualResetEventSlim lock2) { diff --git a/CommandLineParser.Tests/Usage/UsagePrinterTests.cs b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs index c3d814d..a516198 100644 --- a/CommandLineParser.Tests/Usage/UsagePrinterTests.cs +++ b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs @@ -21,9 +21,11 @@ private class UsagePrinterGetsCalledOptions public void UsagePrintGetsCalledInCorrectCases(string[] args, bool called) { var printerMock = new Mock(); - var parser = new CommandLineParser(); - parser.Printer = printerMock.Object; + var parser = new CommandLineParser + { + Printer = printerMock.Object + }; parser.Parse(args); From 09cab39a2c61a4a414e10d12ab6e4b1195667c29 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 27 Dec 2018 10:33:44 +0100 Subject: [PATCH 17/41] Update sample app. --- SampleApp/Program.cs | 29 ++++++++++++++--------------- SampleApp/SampleApp.csproj | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/SampleApp/Program.cs b/SampleApp/Program.cs index bb6d17f..57ad5a8 100644 --- a/SampleApp/Program.cs +++ b/SampleApp/Program.cs @@ -1,5 +1,4 @@ using System; - using MatthiWare.CommandLine; namespace SampleApp @@ -8,51 +7,51 @@ class Program { static int Main(string[] args) { - Console.WriteLine($"args: {string.Join(',', args)}"); + Console.WriteLine($"args: {string.Join(", ", args)}"); var parser = new CommandLineParser(); // setup parser.Configure(opt => opt.MyInt) - .Name("-i", "--int") + .Name("i", "int") + .Description("Description for -s option, needs a string.") .Required(); parser.Configure(opt => opt.MyString) - .Name("-s", "--string") + .Name("s", "string") + .Description("Description for -s option, needs a string.") .Required(); parser.Configure(opt => opt.MyBool) - .Name("-b", "--bool") + .Name("b", "bool") + .Description("Description for -s option, needs a string.") .Required(); parser.Configure(opt => opt.MyDouble) - .Name("-d", "--double") + .Name("d", "double") + .Description("Description for -s option, needs a string.") .Required(); var startCmd = parser.AddCommand() - .Name("-s", "--start") + .Name("start") + .Description("Start the server command.") .Required() - .OnExecuting(parsedCmdOption => Console.WriteLine($"Starting server using verbose option: {parsedCmdOption.Verbose}")); + .OnExecuting((opt, parsedCmdOption) => Console.WriteLine($"Starting server using verbose option: {parsedCmdOption.Verbose}")); startCmd.Configure(cmd => cmd.Verbose) // configures the command options can also be done using attributes .Required() - .Name("-v", "--verbose"); + .Description("Verbose output [true/false]") + .Name("v", "verbose"); var result = parser.Parse(args); if (result.HasErrors) { - Console.Error.WriteLine(result.Error); Console.ReadKey(); return -1; } - foreach (var cmdResult in result.CommandResults) - { - cmdResult.ExecuteCommand(); // executes the command handler that is configured above. - } - var options = result.Result; Console.WriteLine($"MyInt: {options.MyInt}"); diff --git a/SampleApp/SampleApp.csproj b/SampleApp/SampleApp.csproj index e844bae..e5b8e5c 100644 --- a/SampleApp/SampleApp.csproj +++ b/SampleApp/SampleApp.csproj @@ -6,7 +6,7 @@ - + From 56f087082f4d7e5d1c8490f4d1a76afc38e1535e Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 09:30:38 +0100 Subject: [PATCH 18/41] Improved command system to use correct configure. --- .../Command/CommandTests.cs | 7 ------ .../Abstractions/Command/Command.cs | 5 ++-- .../Abstractions/Command/Command`TOptions.cs | 24 +++++++++++++++++++ ...nd`.cs => Command`TOptions`TCmdOptions.cs} | 7 ++++-- .../Abstractions/Command/ICommandBuilder'.cs | 8 ------- .../Abstractions/Command/ICommandExecutor.cs | 2 ++ .../Abstractions/ICommandLineParser'.cs | 3 +-- CommandLineParser/CommandLineParser.cs | 3 +-- ...mmandLineCommand`TOption`TCommandOption.cs | 17 +++++++++---- 9 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 CommandLineParser/Abstractions/Command/Command`TOptions.cs rename CommandLineParser/Abstractions/Command/{Command`.cs => Command`TOptions`TCmdOptions.cs} (87%) diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index 1c33635..2481f90 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -63,13 +63,6 @@ public void AddCommandTypeWithGenericOption() private class MyComand : Command { - public override void OnConfigure(ICommandConfigurationBuilder builder) - { - base.OnConfigure(builder); - - builder.Name("bla").Required(); - } - public override void OnConfigure(ICommandConfigurationBuilder builder) { base.OnConfigure(builder); diff --git a/CommandLineParser/Abstractions/Command/Command.cs b/CommandLineParser/Abstractions/Command/Command.cs index a411de1..75faad6 100644 --- a/CommandLineParser/Abstractions/Command/Command.cs +++ b/CommandLineParser/Abstractions/Command/Command.cs @@ -4,8 +4,7 @@ /// Defines a command /// /// Base options of the command - public abstract class Command - where TOptions : class, new() + public abstract class Command { /// /// Configures the command @@ -18,6 +17,6 @@ public virtual void OnConfigure(ICommandConfigurationBuilder builder) { } /// Executes the command /// /// Parsed options - public virtual void OnExecute(TOptions options) { } + public virtual void OnExecute() { } } } diff --git a/CommandLineParser/Abstractions/Command/Command`TOptions.cs b/CommandLineParser/Abstractions/Command/Command`TOptions.cs new file mode 100644 index 0000000..942d36f --- /dev/null +++ b/CommandLineParser/Abstractions/Command/Command`TOptions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MatthiWare.CommandLine.Abstractions.Command +{ + /// + /// Defines a command + /// + /// Base options of the command + public abstract class Command + : Command + where TOptions : class, new() + { + /// + /// Executes the command + /// + /// Parsed options + public virtual void OnExecute(TOptions options) + { + OnExecute(); + } + } +} diff --git a/CommandLineParser/Abstractions/Command/Command`.cs b/CommandLineParser/Abstractions/Command/Command`TOptions`TCmdOptions.cs similarity index 87% rename from CommandLineParser/Abstractions/Command/Command`.cs rename to CommandLineParser/Abstractions/Command/Command`TOptions`TCmdOptions.cs index 99d8cb1..3bae08b 100644 --- a/CommandLineParser/Abstractions/Command/Command`.cs +++ b/CommandLineParser/Abstractions/Command/Command`TOptions`TCmdOptions.cs @@ -17,7 +17,7 @@ public abstract class Command : /// public virtual void OnConfigure(ICommandConfigurationBuilder builder) { - base.OnConfigure(builder); + OnConfigure((ICommandConfigurationBuilder)builder); } /// @@ -25,6 +25,9 @@ public virtual void OnConfigure(ICommandConfigurationBuilder bu /// /// /// - public virtual void OnExecute(TOptions options, TCommandOptions commandOptions) { } + public virtual void OnExecute(TOptions options, TCommandOptions commandOptions) + { + OnExecute(options); + } } } diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index 09e370c..621685b 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -39,13 +39,5 @@ public interface ICommandBuilder : ICommandConfigurationBuilde /// name /// new ICommandBuilder Name(string name); - - /// - /// Configures how the command should be invoked. - /// Default behavior is to auto invoke the command. - /// - /// True if the command executor will be invoked (default), false if you want to invoke manually. - /// - new ICommandBuilder InvokeCommand(bool invoke); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandExecutor.cs b/CommandLineParser/Abstractions/Command/ICommandExecutor.cs index 4a48dec..44aaa14 100644 --- a/CommandLineParser/Abstractions/Command/ICommandExecutor.cs +++ b/CommandLineParser/Abstractions/Command/ICommandExecutor.cs @@ -14,6 +14,8 @@ public interface ICommandExecutor /// ICommandBuilder InvokeCommand(bool invoke); + ICommandBuilder OnExecuting(Action action); + ICommandBuilder OnExecuting(Action action); ICommandBuilder OnExecuting(Action action); diff --git a/CommandLineParser/Abstractions/ICommandLineParser'.cs b/CommandLineParser/Abstractions/ICommandLineParser'.cs index b0f234b..c40ea21 100644 --- a/CommandLineParser/Abstractions/ICommandLineParser'.cs +++ b/CommandLineParser/Abstractions/ICommandLineParser'.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; - using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Abstractions.Usage; @@ -71,7 +70,7 @@ ICommandBuilder AddCommand() /// /// The command void RegisterCommand() - where TCommand : Command; + where TCommand : Command.Command; /// /// Registers a new command diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index 7a71122..8164d87 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -35,7 +35,6 @@ public sealed class CommandLineParser : ICommandLineParser, IC private readonly Dictionary m_options; private readonly List m_commands; private readonly CommandLineParserOptions m_parserOptions; - private readonly Process m_currentProcess; /// /// Tool to print usage info. @@ -286,7 +285,7 @@ private void ParseOptions(IList errors, ParseResult result, /// /// Command type, must be inherit public void RegisterCommand() - where TCommand : Command + where TCommand : Command { var cmdConfigurator = ContainerResolver.Resolve(); diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 2f93506..d249d7f 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -30,7 +30,8 @@ internal class CommandLineCommand : private readonly IContainerResolver m_containerResolver; private readonly CommandLineParserOptions m_parserOptions; - private Action m_executor; + private Action m_executor; + private Action m_executor1; private Action m_executor2; public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, IContainerResolver containerResolver, TOption option) @@ -48,7 +49,8 @@ public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResol public override void Execute() { m_executor2?.Invoke(m_baseOption, m_commandOption); - m_executor?.Invoke(m_baseOption); + m_executor1?.Invoke(m_baseOption); + m_executor?.Invoke(); } public IOptionBuilder Configure(Expression> selector) @@ -132,6 +134,13 @@ public ICommandBuilder Required(bool required = true) } public ICommandBuilder OnExecuting(Action action) + { + m_executor1 = action; + + return this; + } + + public ICommandBuilder OnExecuting(Action action) { m_executor = action; @@ -168,7 +177,7 @@ ICommandBuilder ICommandBuilder.Description(string description ICommandBuilder ICommandBuilder.OnExecuting(Action action) { - m_executor = action; + m_executor1 = action; return this; } @@ -329,7 +338,7 @@ public void RegisterCommand() cmdConfigurator.OnConfigure(command); - command.OnExecuting(cmdConfigurator.OnExecute); + command.OnExecuting((Action)cmdConfigurator.OnExecute); m_commands.Add(command); } From b1f883ea50784e72f7b0461747358261d7239abf Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 09:48:44 +0100 Subject: [PATCH 19/41] Fix codefactor issues. --- CommandLineParser/Core/CommandLineOption.cs | 4 ++-- CommandLineParser/Core/Parsing/ArgumentManager.cs | 4 ---- CommandLineParser/Core/Usage/UsageBuilder.cs | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CommandLineParser/Core/CommandLineOption.cs b/CommandLineParser/Core/CommandLineOption.cs index 7508700..a525fe3 100644 --- a/CommandLineParser/Core/CommandLineOption.cs +++ b/CommandLineParser/Core/CommandLineOption.cs @@ -74,8 +74,8 @@ IOptionBuilder IOptionBuilder.Description(string help) IOptionBuilder IOptionBuilder.Name(string shortName, string longName) { - LongName = $"{m_parserOptions.PrefixLongOption}{longName}"; ; - ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; ; + LongName = $"{m_parserOptions.PrefixLongOption}{longName}"; + ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; return this; } diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index 9ea93f0..d045789 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -114,13 +114,9 @@ private int FindIndex(IArgument model, int startOffset = 0) default: return false; } - - }); } - - public void Dispose() => args.Clear(); public bool TryGetValue(IArgument argument, out ArgumentModel model) => resultCache.TryGetValue(argument, out model); diff --git a/CommandLineParser/Core/Usage/UsageBuilder.cs b/CommandLineParser/Core/Usage/UsageBuilder.cs index 6fe7e5e..6dc7414 100644 --- a/CommandLineParser/Core/Usage/UsageBuilder.cs +++ b/CommandLineParser/Core/Usage/UsageBuilder.cs @@ -35,7 +35,6 @@ public void PrintUsage(string name, bool hasOptions, bool hasCommands) .Append(hasOptions ? "[options] " : string.Empty) .Append(hasCommands ? "[commands]" : string.Empty) .AppendLine(); - } public void PrintCommand(string name, ICommandLineCommandContainer container) From f2441f515be85d9d59acb34807c48cc37ade6220 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 09:59:01 +0100 Subject: [PATCH 20/41] Improve dotnet publish output path. --- build.cake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index e0a8ce2..70d4848 100644 --- a/build.cake +++ b/build.cake @@ -12,6 +12,7 @@ var configuration = Argument("configuration", "Release"); var project = "CommandLineParser"; var solution = $"./{project}.sln"; +var cmdParserProject = $"./{project}/{project}.csproj"; var nuspecFile = $"./{project}/{project}.nuspec"; var tests = $"./{project}.Tests/{project}.Tests.csproj"; var publishPath = MakeAbsolute(Directory("./output")); @@ -60,7 +61,7 @@ Task("Publish") .IsDependentOn("Test") .IsDependentOn("Clean-Publish") .Does( () => { - DotNetCorePublish(solution, + DotNetCorePublish(cmdParserProject, new DotNetCorePublishSettings { NoRestore = true, From 9c159bb2e7392bde9d9593c0a6663e2ae40562f7 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 11:47:01 +0100 Subject: [PATCH 21/41] Add codecoverage (#21) * Add codecoverage * Add coverlet tool * add codecov --- .../CommandLineParser.Tests.csproj | 4 +++ build.cake | 33 +++++++++++++++---- codecov.yml | 26 +++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 codecov.yml diff --git a/CommandLineParser.Tests/CommandLineParser.Tests.csproj b/CommandLineParser.Tests/CommandLineParser.Tests.csproj index e50b9d1..8cd64d0 100644 --- a/CommandLineParser.Tests/CommandLineParser.Tests.csproj +++ b/CommandLineParser.Tests/CommandLineParser.Tests.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers + diff --git a/build.cake b/build.cake index 70d4848..21fa3b9 100644 --- a/build.cake +++ b/build.cake @@ -1,4 +1,7 @@ #tool "nuget:?package=xunit.runner.console&version=2.2.0" +#tool nuget:?package=Codecov +#addin nuget:?package=Cake.Codecov +#addin nuget:?package=Cake.Coverlet /////////////////////////////////////////////////////////////////////////////// // ARGUMENTS /////////////////////////////////////////////////////////////////////////////// @@ -17,6 +20,7 @@ var nuspecFile = $"./{project}/{project}.nuspec"; var tests = $"./{project}.Tests/{project}.Tests.csproj"; var publishPath = MakeAbsolute(Directory("./output")); var nugetPackageDir = MakeAbsolute(Directory("./nuget")); +var codeCoverageOutput = MakeAbsolute(Directory("./code-coverage/")); /////////////////////////////////////////////////////////////////////////////// // TASKS @@ -49,12 +53,29 @@ Task("Test") .IsDependentOn("Build") .Does( () => { + var coverletSettings = new CoverletSettings { + CollectCoverage = true, + CoverletOutputDirectory = codeCoverageOutput, + CoverletOutputFormat = CoverletOutputFormat.opencover, + CoverletOutputName = $"coverage.xml" + }; + DotNetCoreTest(tests, - new DotNetCoreTestSettings { - NoBuild = true, - NoRestore = true, - Configuration = configuration - }); + new DotNetCoreTestSettings { + NoBuild = true, + NoRestore = true, + Configuration = configuration + }, coverletSettings); + + Information("Print all files: "); + foreach(var file in GetFiles($".\\**\\**\\**\\*.*")) + { + Information(file.FullPath); + } + + + // Upload a coverage report. + Codecov($"{codeCoverageOutput}\\coverage.xml"); }); Task("Publish") @@ -78,7 +99,7 @@ Task("Publish-NuGet") { BasePath = publishPath, OutputDirectory = nugetPackageDir, - IncludeReferencedProjects = true, + IncludeReferencedProjects = false, Properties = new Dictionary { diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..68ec52b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,26 @@ +codecov: + notify: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "25...75" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header, diff, changes, sunburst, uncovered" + behavior: default + require_changes: no \ No newline at end of file From 2898f1444890e1d5e116fd7387f4887b54621529 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:35:44 +0100 Subject: [PATCH 22/41] Update test --- CommandLineParser.Tests/CommandLineParserTests.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 05d5e49..f80d967 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -204,11 +204,17 @@ public void ParseWithCommandTests() public void ParseCommandTests(string[] args, string result1, string result2) { var parser = new CommandLineParser(); + var wait = new ManualResetEvent(false); parser.AddCommand() .Name("-a", "--add") .Required() - .OnExecuting((opt1, opt2) => Assert.Equal(result2, opt2.Message)) + .OnExecuting((opt1, opt2) => + { + wait.Set(); + + Assert.Equal(result2, opt2.Message); + }) .Configure(c => c.Message) .Name("-m", "--message") .Required(); @@ -222,6 +228,8 @@ public void ParseCommandTests(string[] args, string result1, string result2) Assert.False(result.HasErrors); Assert.Equal(result1, result.Result.Message); + + Assert.True(wait.WaitOne(2000)); } [Fact] From b8e92bdbf8bcfee57b0d853526e6bf40f8189086 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:12:11 +0100 Subject: [PATCH 23/41] Rename to description --- .../Abstractions/Command/ICommandBuilder'.cs | 6 +++--- .../Abstractions/Command/ICommandBuilder.cs | 6 +++--- .../Command/ICommandConfigurationBuilder.cs | 4 ++-- .../Abstractions/ICommandLineOption.cs | 2 +- CommandLineParser/Abstractions/IOptionBuilder'.cs | 2 +- CommandLineParser/CommandLineParser.cs | 4 ++-- .../Core/Attributes/DescriptionAttribute.cs | 11 +++++++++++ .../Core/Attributes/HelpTextAttribute.cs | 12 ------------ .../Core/Command/CommandLineCommandBase.cs | 2 +- .../CommandLineCommand`TOption`TCommandOption.cs | 14 +++++++------- CommandLineParser/Core/CommandLineOption.cs | 4 ++-- CommandLineParser/Core/CommandLineOptionBase.cs | 2 +- 12 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 CommandLineParser/Core/Attributes/DescriptionAttribute.cs delete mode 100644 CommandLineParser/Core/Attributes/HelpTextAttribute.cs diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index c1edcb2..7663183 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -28,11 +28,11 @@ public interface ICommandBuilder : ICommandConfigurationBuilde new ICommandBuilder Required(bool required = true); /// - /// Configures the help text for the command + /// Describes the command, used in the usage output. /// - /// True or false + /// description of the command /// - new ICommandBuilder HelpText(string help); + new ICommandBuilder Description(string desc); /// /// Configures the command name diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs index c86f033..45a87b3 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs @@ -20,11 +20,11 @@ public interface ICommandBuilder ICommandBuilder Required(bool required = true); /// - /// Configures the help text for the command + /// Describes the command, used in the usage output. /// - /// True or false + /// description of the command /// - ICommandBuilder HelpText(string help); + ICommandBuilder Description(string description); /// /// Configures the command name diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs index 88c4392..4ed8349 100644 --- a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs @@ -10,11 +10,11 @@ public interface ICommandConfigurationBuilder ICommandConfigurationBuilder Required(bool required = true); /// - /// Configures the help text for the command + /// Configures the description text for the command /// /// True or false /// - ICommandConfigurationBuilder HelpText(string help); + ICommandConfigurationBuilder Description(string description); /// /// Configures the command name diff --git a/CommandLineParser/Abstractions/ICommandLineOption.cs b/CommandLineParser/Abstractions/ICommandLineOption.cs index 98a3271..80f43ba 100644 --- a/CommandLineParser/Abstractions/ICommandLineOption.cs +++ b/CommandLineParser/Abstractions/ICommandLineOption.cs @@ -7,7 +7,7 @@ public interface ICommandLineOption { string ShortName { get; } string LongName { get; } - string HelpText { get; } + string Description { get; } bool IsRequired { get; } bool HasShortName { get; } bool HasLongName { get; } diff --git a/CommandLineParser/Abstractions/IOptionBuilder'.cs b/CommandLineParser/Abstractions/IOptionBuilder'.cs index 939824b..b5fd015 100644 --- a/CommandLineParser/Abstractions/IOptionBuilder'.cs +++ b/CommandLineParser/Abstractions/IOptionBuilder'.cs @@ -17,7 +17,7 @@ public interface IOptionBuilder /// /// /// - IOptionBuilder HelpText(string help); + IOptionBuilder Description(string description); /// /// Specify the default value for this option diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index f08fee8..f51f2f6 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -297,8 +297,8 @@ private void InitialzeModel() case DefaultValueAttribute defaultValue: actions.Add(() => ConfigureInternal(lambda, key).Default(defaultValue.DefaultValue)); break; - case HelpTextAttribute helpText: - actions.Add(() => ConfigureInternal(lambda, key).HelpText(helpText.HelpText)); + case DescriptionAttribute helpText: + actions.Add(() => ConfigureInternal(lambda, key).Description(helpText.Description)); break; case NameAttribute name: actions.Add(() => ConfigureInternal(lambda, key).Name(name.ShortName, name.LongName)); diff --git a/CommandLineParser/Core/Attributes/DescriptionAttribute.cs b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs new file mode 100644 index 0000000..97ec330 --- /dev/null +++ b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace MatthiWare.CommandLine.Core.Attributes +{ + public class DescriptionAttribute : BaseAttribute + { + public string Description { get; private set; } + + public DescriptionAttribute(string helpText) => Description = helpText; + } +} diff --git a/CommandLineParser/Core/Attributes/HelpTextAttribute.cs b/CommandLineParser/Core/Attributes/HelpTextAttribute.cs deleted file mode 100644 index 24e550a..0000000 --- a/CommandLineParser/Core/Attributes/HelpTextAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MatthiWare.CommandLine.Core.Attributes -{ - [Obsolete("Help texts are currently not working.", false)] - public class HelpTextAttribute : BaseAttribute - { - public string HelpText { get; private set; } - - public HelpTextAttribute(string helpText) => HelpText = helpText; - } -} diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 14f8e3c..23c8fa9 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -17,7 +17,7 @@ internal abstract class CommandLineCommandBase : public string ShortName { get; protected set; } public string LongName { get; protected set; } - public string HelpText { get; protected set; } + public string Description { get; protected set; } public bool IsRequired { get; protected set; } public bool HasShortName => ShortName != null; public bool HasLongName => LongName != null; diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 551ed31..c44e812 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -14,8 +14,8 @@ namespace MatthiWare.CommandLine.Core.Command internal class CommandLineCommand : CommandLineCommandBase, ICommandBuilder, - ICommandConfigurationBuilder, ICommandBuilder, + ICommandConfigurationBuilder, IOptionConfigurator where TOption : class where TCommandOption : class, new() @@ -134,9 +134,9 @@ ICommandBuilder ICommandBuilder.Required(bool required) return this; } - ICommandBuilder ICommandBuilder.HelpText(string help) + ICommandBuilder ICommandBuilder.Description(string description) { - HelpText = help; + Description = description; return this; } @@ -170,9 +170,9 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder.Required(bool required return this; } - ICommandConfigurationBuilder ICommandConfigurationBuilder.HelpText(string help) + ICommandConfigurationBuilder ICommandConfigurationBuilder.Description(string description) { - HelpText = help; + Description = description; return this; } @@ -199,9 +199,9 @@ public ICommandBuilder InvokeCommand(bool invoke) return this; } - ICommandBuilder ICommandBuilder.HelpText(string help) + ICommandBuilder ICommandBuilder.Description(string help) { - HelpText = help; + Description = help; return this; } diff --git a/CommandLineParser/Core/CommandLineOption.cs b/CommandLineParser/Core/CommandLineOption.cs index f5706e3..675b22d 100644 --- a/CommandLineParser/Core/CommandLineOption.cs +++ b/CommandLineParser/Core/CommandLineOption.cs @@ -63,9 +63,9 @@ IOptionBuilder IOptionBuilder.Default(object defaultValue) return this; } - IOptionBuilder IOptionBuilder.HelpText(string help) + IOptionBuilder IOptionBuilder.Description(string help) { - HelpText = help; + Description = help; return this; } diff --git a/CommandLineParser/Core/CommandLineOptionBase.cs b/CommandLineParser/Core/CommandLineOptionBase.cs index 4200d36..86dd035 100644 --- a/CommandLineParser/Core/CommandLineOptionBase.cs +++ b/CommandLineParser/Core/CommandLineOptionBase.cs @@ -11,7 +11,7 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption { public string ShortName { get; protected set; } public string LongName { get; protected set; } - public string HelpText { get; protected set; } + public string Description { get; protected set; } public bool IsRequired { get; protected set; } public bool HasDefault { get; protected set; } public bool HasShortName => !string.IsNullOrWhiteSpace(ShortName); From 7a0e625e94239857200ec113fc19f0e43727dd3b Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:12:45 +0100 Subject: [PATCH 24/41] Fix codefactor issues in tests, renaming. --- CommandLineParser.Tests/Command/CommandTests.cs | 2 -- CommandLineParser.Tests/CommandLineModelTests.cs | 8 ++++---- CommandLineParser.Tests/CommandLineParserTests.cs | 1 - CommandLineParser.Tests/OptionBuilderTest.cs | 4 ++-- .../Parsing/Resolvers/BoolResolverTests.cs | 2 -- .../Parsing/Resolvers/DoubleResolverTests.cs | 3 --- .../Parsing/Resolvers/IntResolverTests.cs | 4 ---- .../Parsing/Resolvers/StringResovlerTests.cs | 2 -- CommandLineParser.Tests/XUnitExtensions.cs | 4 ---- 9 files changed, 6 insertions(+), 24 deletions(-) diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index 006b793..dd4641d 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -8,7 +8,6 @@ namespace MatthiWare.CommandLineParser.Tests.Command { public class CommandTests { - [Fact] public void ConfiguringCommandsIncreasesTotalCommandInParser() { @@ -75,6 +74,5 @@ public override void OnExecute(object options, object commandOptions) base.OnExecute(options, commandOptions); } } - } } diff --git a/CommandLineParser.Tests/CommandLineModelTests.cs b/CommandLineParser.Tests/CommandLineModelTests.cs index 305b91b..e3423b0 100644 --- a/CommandLineParser.Tests/CommandLineModelTests.cs +++ b/CommandLineParser.Tests/CommandLineModelTests.cs @@ -25,7 +25,7 @@ public void TestBasicModel() Assert.True(message.HasDefault); Assert.True(message.IsRequired); - Assert.Equal("Help", message.HelpText); + Assert.Equal("Help", message.Description); } [Fact] @@ -35,7 +35,7 @@ public void TestBasicModelWithOverwritingUsingFluentApi() parser.Configure(_ => _.Message) .Required(false) - .HelpText("Different"); + .Description("Different"); Assert.Equal(1, parser.Options.Count); @@ -51,12 +51,12 @@ public void TestBasicModelWithOverwritingUsingFluentApi() Assert.True(message.HasDefault); Assert.False(message.IsRequired); - Assert.Equal("Different", message.HelpText); + Assert.Equal("Different", message.Description); } private class Model { - [Required, Name("-m", "--message"), DefaultValue("not found"), HelpText("Help")] + [Required, Name("-m", "--message"), DefaultValue("not found"), Description("Help")] public string Message { get; set; } } } diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index f80d967..ce42c5d 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -180,7 +180,6 @@ public void ParseWithCommandTests() wait.Set(); }); - addCmd.Configure(opt => opt.Message) .Name("-m", "--message") .Required(); diff --git a/CommandLineParser.Tests/OptionBuilderTest.cs b/CommandLineParser.Tests/OptionBuilderTest.cs index 9f7fabd..4157e46 100644 --- a/CommandLineParser.Tests/OptionBuilderTest.cs +++ b/CommandLineParser.Tests/OptionBuilderTest.cs @@ -26,7 +26,7 @@ public void OptionBuilderConfiguresOptionCorrectly() builder .Default(sDefault) - .HelpText(sHelp) + .Description(sHelp) .Name(sShort, sLong) .Required(); @@ -36,7 +36,7 @@ public void OptionBuilderConfiguresOptionCorrectly() Assert.True(option.HasLongName); Assert.Equal(sLong, option.LongName); - Assert.Equal(sHelp, option.HelpText); + Assert.Equal(sHelp, option.Description); Assert.True(option.HasShortName); Assert.Equal(sShort, option.ShortName); diff --git a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs index e0e94bd..80c0d1c 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs @@ -6,7 +6,6 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { public class BoolResolverTests { - [Theory] [InlineData("yes")] [InlineData("1")] @@ -32,6 +31,5 @@ public void TestResolveFalse(string input) Assert.False(result); } - } } diff --git a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs index ddd4197..0806537 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs @@ -4,7 +4,6 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { - public class DoubleResolverTests { [Theory] @@ -35,7 +34,5 @@ public void TestResolve(double expected, string value) Assert.Equal(expected, resolver.Resolve(model)); } - - } } diff --git a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs index 6c4590f..2bc2201 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs @@ -4,10 +4,8 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { - public class IntResolverTests { - [Theory] [InlineData(true, "-m", "5")] [InlineData(false, "-m", "false")] @@ -29,7 +27,5 @@ public void TestResolve(int expected, string key, string value) Assert.Equal(expected, resolver.Resolve(model)); } - - } } diff --git a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs index 6423d57..a5e0b3b 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs @@ -6,7 +6,6 @@ namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers { public class StringResovlerTests { - [Theory] [InlineData(true, "-m", "test")] [InlineData(true, "-m", "my string")] @@ -28,6 +27,5 @@ public void TestResolve(string expected, string key, string value) Assert.Equal(expected, resolver.Resolve(model)); } - } } diff --git a/CommandLineParser.Tests/XUnitExtensions.cs b/CommandLineParser.Tests/XUnitExtensions.cs index 62bb182..61941e4 100644 --- a/CommandLineParser.Tests/XUnitExtensions.cs +++ b/CommandLineParser.Tests/XUnitExtensions.cs @@ -1,14 +1,10 @@ using System; using System.Linq.Expressions; -using Xunit.Sdk; namespace MatthiWare.CommandLineParser.Tests { public static class XUnitExtensions { - public static void Fail(string reason) - => throw new XunitException(reason); - public static LambdaExpression CreateLambda(Expression> expression) { return expression; From 45c363e5f3fc1c55d7d59ddddce61758c7e2024f Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:16:25 +0100 Subject: [PATCH 25/41] Codefactor fixes --- CommandLineParser/Abstractions/Command/ICommandBuilder'.cs | 1 - CommandLineParser/Core/Exceptions/OptionNotFoundException.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index 7663183..d2a25cf 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -11,7 +11,6 @@ public interface ICommandBuilder : ICommandConfigurationBuilde where TOption : class where TSource : class, new() { - /// /// Configures an option in the model /// diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index e9a926c..722cb69 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -16,8 +16,6 @@ public class OptionNotFoundException : KeyNotFoundException public OptionNotFoundException(ICommandLineOption option) : base($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!") - { - - } + { } } } From 43572b32359d9b7bc2cdd2f499e662a49503928d Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 19 Dec 2018 08:20:47 +0100 Subject: [PATCH 26/41] Sort usings --- CommandLineParser.Tests/Command/CommandTests.cs | 3 ++- CommandLineParser.Tests/CommandLineModelTests.cs | 1 + CommandLineParser.Tests/CommandLineParserTests.cs | 3 +++ CommandLineParser.Tests/CustomerReportedTests.cs | 6 ++---- CommandLineParser.Tests/OptionBuilderTest.cs | 3 +++ CommandLineParser.Tests/Parsing/ParserResultTest.cs | 3 +++ CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs | 3 +++ .../Parsing/Resolvers/BoolResolverTests.cs | 1 + .../Parsing/Resolvers/DoubleResolverTests.cs | 1 + .../Parsing/Resolvers/EnumResolverTests.cs | 1 + .../Parsing/Resolvers/IntResolverTests.cs | 1 + .../Parsing/Resolvers/StringResovlerTests.cs | 1 + .../Abstractions/Parsing/Command/ICommandParserResult.cs | 2 +- CommandLineParser/CommandLineParser.cs | 4 ++-- CommandLineParser/Core/Attributes/DescriptionAttribute.cs | 4 +--- SampleApp/Program.cs | 1 + 16 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index dd4641d..ff8a516 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -1,7 +1,8 @@ using System.Linq; + using MatthiWare.CommandLine; -using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Command diff --git a/CommandLineParser.Tests/CommandLineModelTests.cs b/CommandLineParser.Tests/CommandLineModelTests.cs index e3423b0..b2248f3 100644 --- a/CommandLineParser.Tests/CommandLineModelTests.cs +++ b/CommandLineParser.Tests/CommandLineModelTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine; using MatthiWare.CommandLine.Core.Attributes; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index ce42c5d..876783e 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -1,11 +1,14 @@ using System.Threading; + using MatthiWare.CommandLine; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core.Attributes; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index a012fa7..9e74ce4 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; -using MatthiWare.CommandLine; +using MatthiWare.CommandLine; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/OptionBuilderTest.cs b/CommandLineParser.Tests/OptionBuilderTest.cs index 4157e46..ab0812b 100644 --- a/CommandLineParser.Tests/OptionBuilderTest.cs +++ b/CommandLineParser.Tests/OptionBuilderTest.cs @@ -1,8 +1,11 @@ using System; + using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests diff --git a/CommandLineParser.Tests/Parsing/ParserResultTest.cs b/CommandLineParser.Tests/Parsing/ParserResultTest.cs index 9f46bb1..9b9e887 100644 --- a/CommandLineParser.Tests/Parsing/ParserResultTest.cs +++ b/CommandLineParser.Tests/Parsing/ParserResultTest.cs @@ -1,7 +1,10 @@ using System; + using MatthiWare.CommandLine.Abstractions.Parsing.Command; using MatthiWare.CommandLine.Core.Parsing; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing diff --git a/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs b/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs index 90ded78..19b9939 100644 --- a/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs +++ b/CommandLineParser.Tests/Parsing/ResolverFactoryTest.cs @@ -1,9 +1,12 @@ using System; + using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core.Parsing; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Moq; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing diff --git a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs index 80c0d1c..05b3264 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/BoolResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs index 0806537..151cdc4 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/DoubleResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs index 837482a..8838eba 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/EnumResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs index 2bc2201..dfcb3c6 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/IntResolverTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs index a5e0b3b..578a573 100644 --- a/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs +++ b/CommandLineParser.Tests/Parsing/Resolvers/StringResovlerTests.cs @@ -1,5 +1,6 @@ using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Core.Parsing.Resolvers; + using Xunit; namespace MatthiWare.CommandLineParser.Tests.Parsing.Resolvers diff --git a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs index 47ce800..82d1f1f 100644 --- a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs +++ b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs @@ -24,7 +24,7 @@ public interface ICommandParserResult /// /// Executes the command /// - /// /// + /// /// Result contains exceptions. For more info see and properties. /// void ExecuteCommand(); diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index f51f2f6..e68af88 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -136,8 +136,6 @@ public IParserResult Parse(string[] args) result.MergeResult(errors); - result.MergeResult(m_option); - AutoExecuteCommands(result); return result; @@ -198,6 +196,8 @@ private void ParseOptions(IList errors, ParseResult result, option.Parse(model); } + + result.MergeResult(m_option); } /// diff --git a/CommandLineParser/Core/Attributes/DescriptionAttribute.cs b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs index 97ec330..89e7a87 100644 --- a/CommandLineParser/Core/Attributes/DescriptionAttribute.cs +++ b/CommandLineParser/Core/Attributes/DescriptionAttribute.cs @@ -1,6 +1,4 @@ -using System; - -namespace MatthiWare.CommandLine.Core.Attributes +namespace MatthiWare.CommandLine.Core.Attributes { public class DescriptionAttribute : BaseAttribute { diff --git a/SampleApp/Program.cs b/SampleApp/Program.cs index 86abfa9..120df59 100644 --- a/SampleApp/Program.cs +++ b/SampleApp/Program.cs @@ -1,4 +1,5 @@ using System; + using MatthiWare.CommandLine; namespace SampleApp From 93384e880cea09ce859bf54db7056e6702097896 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 20 Dec 2018 08:59:36 +0100 Subject: [PATCH 27/41] Better error message --- .../Core/Exceptions/OptionNotFoundException.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index 722cb69..6990fd5 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -17,5 +17,18 @@ public class OptionNotFoundException : KeyNotFoundException public OptionNotFoundException(ICommandLineOption option) : base($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!") { } + + private string CreateMessage() + { + bool hasShortName = Option.HasShortName; + bool hasLongName = Option.HasLongName; + bool hasBoth = hasShortName && hasLongName; + + string shortName = hasShortName ? $"'{Option.ShortName}'" : string.Empty; + string longName = hasLongName ? $"'{Option.LongName}'" : string.Empty; + string or = hasBoth ? " or " : string.Empty; + + return $"Required argument {shortName}{or}{longName} not found!"; + } } } From b9867497cd29a1eb226aa6a323366f729d3b4475 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 21 Dec 2018 09:42:31 +0100 Subject: [PATCH 28/41] Change command to only have a name instead of both short and long name. --- .../Command/CommandTests.cs | 12 ++--- .../CommandLineParserTests.cs | 15 ++++-- .../CustomerReportedTests.cs | 10 ++-- .../Abstractions/Command/ICommandBuilder'.cs | 12 +---- .../Abstractions/Command/ICommandBuilder.cs | 12 +---- .../Command/ICommandConfigurationBuilder.cs | 12 +---- .../Command/ICommandLineCommand.cs | 5 +- CommandLineParser/Abstractions/IArgument.cs | 11 ++++ .../Abstractions/ICommandLineOption.cs | 2 +- .../Abstractions/Parsing/IArgumentManager.cs | 7 +-- .../Abstractions/Usage/IUsageDisplay.cs | 12 +++++ .../Core/Command/CommandLineCommandBase.cs | 32 ++++++++--- ...mmandLineCommand`TOption`TCommandOption.cs | 54 ++++++------------- .../Core/CommandLineOptionBase.cs | 8 ++- .../Exceptions/CommandNotFoundException.cs | 2 +- .../Exceptions/OptionNotFoundException.cs | 12 ++--- .../Core/Parsing/ArgumentManager.cs | 45 +++++++++++----- .../Core/Parsing/ParseResult'.cs | 5 +- SampleApp/Program.cs | 2 + 19 files changed, 150 insertions(+), 120 deletions(-) create mode 100644 CommandLineParser/Abstractions/IArgument.cs create mode 100644 CommandLineParser/Abstractions/Usage/IUsageDisplay.cs diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index ff8a516..070565d 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -19,8 +19,8 @@ public void ConfiguringCommandsIncreasesTotalCommandInParser() Assert.Equal(2, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("x"))); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("y"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("x"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("y"))); } [Fact] @@ -33,8 +33,8 @@ public void AddOptionLessCommand() Assert.Equal(2, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("x"))); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("y"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("x"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("y"))); } [Fact] @@ -46,7 +46,7 @@ public void AddCommandType() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); } [Fact] @@ -58,7 +58,7 @@ public void AddCommandTypeWithGenericOption() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.ShortName.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); } private class MyComand : Command diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 876783e..2646fdb 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -112,6 +112,9 @@ public void ParseEnumInArguments(string[] args, bool hasErrors, EnumOption enumO [InlineData(new[] { "app.exe", "-1", "message1", "-2", "-3" }, "message1", "message2", "message3")] [InlineData(new[] { "app.exe", "-1", "-2", "message2", "-3" }, "message1", "message2", "message3")] [InlineData(new[] { "app.exe", "-1", "-2", "-3" }, "message1", "message2", "message3")] + [InlineData(new[] { "-1", "message1", "-2", "-3" }, "message1", "message2", "message3")] + [InlineData(new[] { "-1", "-2", "message2", "-3" }, "message1", "message2", "message3")] + [InlineData(new[] { "-1", "-2", "-3" }, "message1", "message2", "message3")] public void ParseWithDefaults(string[] args, string result1, string result2, string result3) { var parser = new CommandLineParser(); @@ -175,7 +178,7 @@ public void ParseWithCommandTests() .Required(); var addCmd = parser.AddCommand() - .Name("-A", "--Add") + .Name("add") .OnExecuting((opt, cmdOpt) => { Assert.Equal("test", opt.Option1); @@ -187,7 +190,7 @@ public void ParseWithCommandTests() .Name("-m", "--message") .Required(); - var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "--Add", "-m", "my message" }); + var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "add", "-m", "my message" }); Assert.False(parsed.HasErrors); @@ -201,15 +204,17 @@ public void ParseWithCommandTests() } [Theory] - [InlineData(new[] { "app.exe", "--Add", "-m", "message2", "-m", "message1" }, "message1", "message2")] - [InlineData(new[] { "app.exe", "-m", "message1", "--Add", "-m", "message2" }, "message1", "message2")] + [InlineData(new[] { "app.exe", "add", "-m", "message2", "-m", "message1" }, "message1", "message2")] + [InlineData(new[] { "app.exe", "-m", "message1", "add", "-m", "message2" }, "message1", "message2")] + [InlineData(new[] { "add", "-m", "message2", "-m", "message1" }, "message1", "message2")] + [InlineData(new[] { "-m", "message1", "add", "-m", "message2" }, "message1", "message2")] public void ParseCommandTests(string[] args, string result1, string result2) { var parser = new CommandLineParser(); var wait = new ManualResetEvent(false); parser.AddCommand() - .Name("-a", "--add") + .Name("add") .Required() .OnExecuting((opt1, opt2) => { diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index 9e74ce4..2297a00 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -11,9 +11,11 @@ public class CustomerReportedTests /// https://github.com/MatthiWare/CommandLineParser.Core/issues/12 /// [Theory] - [InlineData(true, true)] - [InlineData(false, false)] - public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool outcome) + [InlineData(true, true, false)] + [InlineData(false, false, false)] + [InlineData(true, true, true)] + [InlineData(false, false, true)] + public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool outcome, bool empty) { var parser = new CommandLineParser(); @@ -22,7 +24,7 @@ public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool out .Default(1) .Required(required); - var parsed = parser.Parse(new[] { "app.exe" }); + var parsed = parser.Parse(empty ? new string[] { } : new[] { "app.exe" }); Assert.NotNull(parsed); diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index d2a25cf..621685b 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -36,16 +36,8 @@ public interface ICommandBuilder : ICommandConfigurationBuilde /// /// Configures the command name /// - /// Short name + /// name /// - new ICommandBuilder Name(string shortName); - - /// - /// Configures the command name - /// - /// Short name - /// Long name - /// - new ICommandBuilder Name(string shortName, string longName); + new ICommandBuilder Name(string name); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs index 45a87b3..19aede5 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder.cs @@ -29,17 +29,9 @@ public interface ICommandBuilder /// /// Configures the command name /// - /// Short name + /// name /// - ICommandBuilder Name(string shortName); - - /// - /// Configures the command name - /// - /// Short name - /// Long name - /// - ICommandBuilder Name(string shortName, string longName); + ICommandBuilder Name(string name); /// /// Configures the execution of the command diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs index 4ed8349..03ac92c 100644 --- a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder.cs @@ -19,16 +19,8 @@ public interface ICommandConfigurationBuilder /// /// Configures the command name /// - /// Short name + /// Short name /// - ICommandConfigurationBuilder Name(string shortName); - - /// - /// Configures the command name - /// - /// Short name - /// Long name - /// - ICommandConfigurationBuilder Name(string shortName, string longName); + ICommandConfigurationBuilder Name(string name); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs b/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs index e3a92c0..9723203 100644 --- a/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs +++ b/CommandLineParser/Abstractions/Command/ICommandLineCommand.cs @@ -3,8 +3,11 @@ /// /// Command configuration options /// - public interface ICommandLineCommand : ICommandLineOption + public interface ICommandLineCommand : IArgument { + string Name { get; } + bool IsRequired { get; } + string Description { get; } bool AutoExecute { get; } } } diff --git a/CommandLineParser/Abstractions/IArgument.cs b/CommandLineParser/Abstractions/IArgument.cs new file mode 100644 index 0000000..b55a13b --- /dev/null +++ b/CommandLineParser/Abstractions/IArgument.cs @@ -0,0 +1,11 @@ +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Models; + +namespace MatthiWare.CommandLine.Abstractions +{ + /// + /// Represents an argument + /// , and for more info. + /// + public interface IArgument { } +} diff --git a/CommandLineParser/Abstractions/ICommandLineOption.cs b/CommandLineParser/Abstractions/ICommandLineOption.cs index 80f43ba..6c66cff 100644 --- a/CommandLineParser/Abstractions/ICommandLineOption.cs +++ b/CommandLineParser/Abstractions/ICommandLineOption.cs @@ -3,7 +3,7 @@ /// /// Option configuration options /// - public interface ICommandLineOption + public interface ICommandLineOption : IArgument { string ShortName { get; } string LongName { get; } diff --git a/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs b/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs index b4a4d63..9343b7c 100644 --- a/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs +++ b/CommandLineParser/Abstractions/Parsing/IArgumentManager.cs @@ -1,4 +1,5 @@ -using MatthiWare.CommandLine.Abstractions.Models; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Models; namespace MatthiWare.CommandLine.Abstractions.Parsing { @@ -10,9 +11,9 @@ public interface IArgumentManager /// /// Tries to get the arguments associated to the current option /// - /// the option + /// the argument /// The result arguments /// True if arguments are found, false if not - bool TryGetValue(ICommandLineOption option, out ArgumentModel model); + bool TryGetValue(IArgument argument, out ArgumentModel model); } } diff --git a/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs b/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs new file mode 100644 index 0000000..a150183 --- /dev/null +++ b/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MatthiWare.CommandLine.Abstractions.Usage +{ + public interface IUsageDisplay + { + string ToUsage(); + string ToShortUsage(); + } +} diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 23c8fa9..974d7e0 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -1,31 +1,49 @@ using System.Collections.Generic; - +using System.Text; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Abstractions.Parsing.Command; +using MatthiWare.CommandLine.Abstractions.Usage; namespace MatthiWare.CommandLine.Core.Command { internal abstract class CommandLineCommandBase : ICommandLineCommandParser, - ICommandLineCommand + ICommandLineCommand, + IUsageDisplay { protected readonly List m_options = new List(); public IReadOnlyList Options => m_options.AsReadOnly(); - public string ShortName { get; protected set; } - public string LongName { get; protected set; } + public string Name { get; protected set; } public string Description { get; protected set; } public bool IsRequired { get; protected set; } - public bool HasShortName => ShortName != null; - public bool HasLongName => LongName != null; - public bool HasDefault => false; public bool AutoExecute { get; protected set; } = true; public abstract void Execute(); public abstract ICommandParserResult Parse(IArgumentManager argumentManager); + + public string ToShortUsage() + => $" {Name}\t\t{Description}"; + + public string ToUsage() + { + var sb = new StringBuilder(); + + sb.AppendLine(Description); + + if (m_options.Count == 0) + return sb.ToString(); + + sb.AppendLine("Options: "); + + foreach (var opt in m_options) + sb.AppendLine(opt.ToUsage()); + + return sb.ToString(); + } } } diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index c44e812..9841cc0 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -91,21 +91,6 @@ public ICommandBuilder Required(bool required = true) return this; } - public ICommandBuilder Name(string shortName) - { - ShortName = shortName; - - return this; - } - - public ICommandBuilder Name(string shortName, string longName) - { - ShortName = shortName; - LongName = longName; - - return this; - } - public ICommandBuilder OnExecuting(Action action) { m_executor = action; @@ -141,21 +126,6 @@ ICommandBuilder ICommandBuilder.Description(string description return this; } - ICommandBuilder ICommandBuilder.Name(string shortName) - { - ShortName = shortName; - - return this; - } - - ICommandBuilder ICommandBuilder.Name(string shortName, string longName) - { - ShortName = shortName; - LongName = longName; - - return this; - } - ICommandBuilder ICommandBuilder.OnExecuting(Action action) { m_executor = action; @@ -177,31 +147,37 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder.Description(string des return this; } - ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string shortName) + public ICommandBuilder InvokeCommand(bool invoke) { - ShortName = shortName; + AutoExecute = invoke; return this; } - ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string shortName, string longName) + ICommandBuilder ICommandBuilder.Description(string help) { - ShortName = shortName; - LongName = longName; + Description = help; return this; } - public ICommandBuilder InvokeCommand(bool invoke) + ICommandBuilder ICommandBuilder.Name(string name) { - AutoExecute = invoke; + Name = name; return this; } - ICommandBuilder ICommandBuilder.Description(string help) + ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string name) { - Description = help; + Name = name; + + return this; + } + + ICommandBuilder ICommandBuilder.Name(string name) + { + Name = name; return this; } diff --git a/CommandLineParser/Core/CommandLineOptionBase.cs b/CommandLineParser/Core/CommandLineOptionBase.cs index 86dd035..6616c55 100644 --- a/CommandLineParser/Core/CommandLineOptionBase.cs +++ b/CommandLineParser/Core/CommandLineOptionBase.cs @@ -3,11 +3,12 @@ using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Usage; namespace MatthiWare.CommandLine.Core { [DebuggerDisplay("Cmd Option {ShortName ?? LongName}, Req: {IsRequired}, HasDefault: {HasDefault}")] - internal abstract class CommandLineOptionBase : IParser, ICommandLineOption + internal abstract class CommandLineOptionBase : IParser, ICommandLineOption, IUsageDisplay { public string ShortName { get; protected set; } public string LongName { get; protected set; } @@ -23,6 +24,11 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption public abstract void Parse(ArgumentModel model); + public string ToShortUsage() => ToUsage(); + + public string ToUsage() + => $" {(HasShortName ? ShortName : string.Empty)}{(HasShortName && HasLongName ? "|" : string.Empty)}{(HasLongName ? LongName : string.Empty)}\t\t{Description}"; + public abstract void UseDefault(); } } diff --git a/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs b/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs index f59cbb3..6ff710a 100644 --- a/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/CommandNotFoundException.cs @@ -15,7 +15,7 @@ public class CommandNotFoundException : KeyNotFoundException public ICommandLineCommand Command { get; private set; } public CommandNotFoundException(ICommandLineCommand cmd) - : base($"Required command '{cmd.HasShortName}' or '{cmd.LongName}' not found!") + : base($"Required command '{cmd.Name}' not found!") { Command = cmd; } diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index 6990fd5..863d013 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -15,17 +15,17 @@ public class OptionNotFoundException : KeyNotFoundException public ICommandLineOption Option { get; private set; } public OptionNotFoundException(ICommandLineOption option) - : base($"Required argument '{option.HasShortName}' or '{option.LongName}' not found!") + : base(CreateMessage(option)) { } - private string CreateMessage() + private static string CreateMessage(ICommandLineOption option) { - bool hasShortName = Option.HasShortName; - bool hasLongName = Option.HasLongName; + bool hasShortName = option.HasShortName; + bool hasLongName = option.HasLongName; bool hasBoth = hasShortName && hasLongName; - string shortName = hasShortName ? $"'{Option.ShortName}'" : string.Empty; - string longName = hasLongName ? $"'{Option.LongName}'" : string.Empty; + string shortName = hasShortName ? $"'{option.ShortName}'" : string.Empty; + string longName = hasLongName ? $"'{option.LongName}'" : string.Empty; string or = hasBoth ? " or " : string.Empty; return $"Required argument {shortName}{or}{longName} not found!"; diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index e8434b9..893d442 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -3,6 +3,7 @@ using System.Linq; using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core.Command; @@ -11,12 +12,12 @@ namespace MatthiWare.CommandLine.Core.Parsing { internal class ArgumentManager : IArgumentManager, IDisposable { - private readonly IDictionary arguments; + private readonly IDictionary arguments; private readonly List args; public ArgumentManager(string[] args, ICollection commands, ICollection options) { - arguments = new Dictionary(commands.Count + options.Count); + arguments = new Dictionary(commands.Count + options.Count); this.args = new List(args.Select(arg => new ArgumentValueHolder { @@ -30,7 +31,7 @@ public ArgumentManager(string[] args, ICollection comman foreach (var item in this.args) { - if (item.Option == null) continue; + if (item.ArgModel == null) continue; int nextIndex = item.Index + 1; @@ -42,7 +43,7 @@ public ArgumentManager(string[] args, ICollection comman Value = (argValue?.Used ?? true) ? null : argValue.Argument }; - arguments.Add(item.Option, argModel); + arguments.Add(item.ArgModel, argModel); } } @@ -78,10 +79,10 @@ private void ParseCommands(IEnumerable cmds) } } - private void SetArgumentUsed(int idx, ICommandLineOption option) + private void SetArgumentUsed(int idx, IArgument option) { args[idx].Used = true; - args[idx].Option = option; + args[idx].ArgModel = option; args[idx].Index = idx; } @@ -89,26 +90,44 @@ private void SetArgumentUsed(int idx, ICommandLineOption option) /// Finds the index of the first unused argument /// /// List of arguments to search - /// Option to find + /// Argument model to find /// Search offset /// - private int FindIndex(ICommandLineOption option, int startOffset = 0) - => args.FindIndex(startOffset, arg => !arg.Used && - ((option.HasShortName && string.Equals(option.ShortName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)) || - (option.HasLongName && string.Equals(option.LongName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)))); + private int FindIndex(IArgument model, int startOffset = 0) + { + return args.FindIndex(startOffset, arg => + { + if (arg.Used) return false; + + switch (model) + { + case ICommandLineOption opt: + return (opt.HasShortName && string.Equals(opt.ShortName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)) || + (opt.HasLongName && string.Equals(opt.LongName, arg.Argument, StringComparison.InvariantCultureIgnoreCase)); + case ICommandLineCommand cmd: + return string.Equals(cmd.Name, arg.Argument, StringComparison.InvariantCultureIgnoreCase); + default: + return false; + } + + + }); + } + + public void Dispose() { arguments.Clear(); } - public bool TryGetValue(ICommandLineOption option, out ArgumentModel model) => arguments.TryGetValue(option, out model); + public bool TryGetValue(IArgument argument, out ArgumentModel model) => arguments.TryGetValue(argument, out model); private class ArgumentValueHolder { public string Argument { get; set; } public bool Used { get; set; } - public ICommandLineOption Option { get; set; } + public IArgument ArgModel { get; set; } public int Index { get; set; } } } diff --git a/CommandLineParser/Core/Parsing/ParseResult'.cs b/CommandLineParser/Core/Parsing/ParseResult'.cs index d38023a..8d86cce 100644 --- a/CommandLineParser/Core/Parsing/ParseResult'.cs +++ b/CommandLineParser/Core/Parsing/ParseResult'.cs @@ -11,11 +11,10 @@ internal class ParseResult : IParserResult { private readonly List commandParserResults = new List(); private readonly ICollection exceptions = new List(); - private TResult result; #region Properties - public TResult Result => result; + public TResult Result { get; private set; } public bool HasErrors { get; private set; } = false; @@ -54,7 +53,7 @@ public void MergeResult(ICollection errors) public void MergeResult(TResult result) { - this.result = result; + this.Result = result; } public void ExecuteCommands() diff --git a/SampleApp/Program.cs b/SampleApp/Program.cs index 120df59..bb6d17f 100644 --- a/SampleApp/Program.cs +++ b/SampleApp/Program.cs @@ -8,6 +8,8 @@ class Program { static int Main(string[] args) { + Console.WriteLine($"args: {string.Join(',', args)}"); + var parser = new CommandLineParser(); // setup From a1f71b41512f64ec2dd10e746b0588d4ab52f495 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 21 Dec 2018 09:59:52 +0100 Subject: [PATCH 29/41] SMall refactor of arg manager. --- CommandLineParser/Core/Parsing/ArgumentManager.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index 893d442..cbf9bfb 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -12,12 +12,12 @@ namespace MatthiWare.CommandLine.Core.Parsing { internal class ArgumentManager : IArgumentManager, IDisposable { - private readonly IDictionary arguments; + private readonly IDictionary resultCache; private readonly List args; public ArgumentManager(string[] args, ICollection commands, ICollection options) { - arguments = new Dictionary(commands.Count + options.Count); + resultCache = new Dictionary(commands.Count + options.Count); this.args = new List(args.Select(arg => new ArgumentValueHolder { @@ -29,6 +29,7 @@ public ArgumentManager(string[] args, ICollection comman ParseOptions(options); + // pre cache results foreach (var item in this.args) { if (item.ArgModel == null) continue; @@ -40,10 +41,11 @@ public ArgumentManager(string[] args, ICollection comman var argModel = new ArgumentModel { Key = item.Argument, + // this checks if the argument is used in an other command/option. Value = (argValue?.Used ?? true) ? null : argValue.Argument }; - arguments.Add(item.ArgModel, argModel); + resultCache.Add(item.ArgModel, argModel); } } @@ -116,12 +118,9 @@ private int FindIndex(IArgument model, int startOffset = 0) - public void Dispose() - { - arguments.Clear(); - } + public void Dispose() => args.Clear(); - public bool TryGetValue(IArgument argument, out ArgumentModel model) => arguments.TryGetValue(argument, out model); + public bool TryGetValue(IArgument argument, out ArgumentModel model) => resultCache.TryGetValue(argument, out model); private class ArgumentValueHolder { From 144476ac2e5abd2de5ea40f377e56f67fdc9391b Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 21 Dec 2018 11:41:58 +0100 Subject: [PATCH 30/41] Add convention options --- .../CommandLineModelTests.cs | 2 +- .../CommandLineParserTests.cs | 26 ++++++------- .../CustomerReportedTests.cs | 2 +- CommandLineParser.Tests/OptionBuilderTest.cs | 9 ++++- CommandLineParser/CommandLineParser.cs | 38 ++++++++++++++----- CommandLineParser/CommandLineParserOptions.cs | 11 ++++++ ...mmandLineCommand`TOption`TCommandOption.cs | 6 ++- CommandLineParser/Core/CommandLineOption.cs | 16 ++++---- 8 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 CommandLineParser/CommandLineParserOptions.cs diff --git a/CommandLineParser.Tests/CommandLineModelTests.cs b/CommandLineParser.Tests/CommandLineModelTests.cs index b2248f3..ddceef0 100644 --- a/CommandLineParser.Tests/CommandLineModelTests.cs +++ b/CommandLineParser.Tests/CommandLineModelTests.cs @@ -57,7 +57,7 @@ public void TestBasicModelWithOverwritingUsingFluentApi() private class Model { - [Required, Name("-m", "--message"), DefaultValue("not found"), Description("Help")] + [Required, Name("m", "message"), DefaultValue("not found"), Description("Help")] public string Message { get; set; } } } diff --git a/CommandLineParser.Tests/CommandLineParserTests.cs b/CommandLineParser.Tests/CommandLineParserTests.cs index 2646fdb..b982e86 100644 --- a/CommandLineParser.Tests/CommandLineParserTests.cs +++ b/CommandLineParser.Tests/CommandLineParserTests.cs @@ -74,7 +74,7 @@ public void ParseTests() var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-o") + .Name("o") .Default("Default message") .Required(); @@ -98,7 +98,7 @@ public void ParseEnumInArguments(string[] args, bool hasErrors, EnumOption enumO var parser = new CommandLineParser(); parser.Configure(opt => opt.EnumOption) - .Name("-e") + .Name("e") .Required(); var result = parser.Parse(args); @@ -120,17 +120,17 @@ public void ParseWithDefaults(string[] args, string result1, string result2, str var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-1") + .Name("1") .Default(result1) .Required(); parser.Configure(opt => opt.Option2) - .Name("-2") + .Name("2") .Default(result2) .Required(); parser.Configure(opt => opt.Option3) - .Name("-3") + .Name("3") .Default(result3) .Required(); @@ -158,7 +158,7 @@ public void ParseWithCustomParserInAttributeConfiguredModelTests() var parser = new CommandLineParser(); parser.ArgumentResolverFactory.Register(resolver.Object); - var result = parser.Parse(new[] { "app.exe", "--p", "sample" }); + var result = parser.Parse(new[] { "app.exe", "-p", "sample" }); Assert.False(result.HasErrors); @@ -173,7 +173,7 @@ public void ParseWithCommandTests() var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-o") + .Name("o") .Default("Default message") .Required(); @@ -187,7 +187,7 @@ public void ParseWithCommandTests() }); addCmd.Configure(opt => opt.Message) - .Name("-m", "--message") + .Name("m", "message") .Required(); var parsed = parser.Parse(new string[] { "app.exe", "-o", "test", "add", "-m", "my message" }); @@ -223,11 +223,11 @@ public void ParseCommandTests(string[] args, string result1, string result2) Assert.Equal(result2, opt2.Message); }) .Configure(c => c.Message) - .Name("-m", "--message") + .Name("m", "message") .Required(); parser.Configure(opt => opt.Message) - .Name("-m", "--message") + .Name("m", "message") .Required(); var result = parser.Parse(args); @@ -245,12 +245,12 @@ public void ConfigureTests() var parser = new CommandLineParser(); parser.Configure(opt => opt.Option1) - .Name("-o", "--opt") + .Name("o", "opt") .Default("Default message") .Required(); parser.Configure(opt => opt.Option2) - .Name("-x", "--xsomething") + .Name("x", "xsomething") .Required(); Assert.Equal(2, parser.Options.Count); @@ -272,7 +272,7 @@ public void ConfigureTests() private class ObjOption { - [Name("--p"), Required] + [Name("p"), Required] public object Param { get; set; } } diff --git a/CommandLineParser.Tests/CustomerReportedTests.cs b/CommandLineParser.Tests/CustomerReportedTests.cs index 2297a00..f91ab57 100644 --- a/CommandLineParser.Tests/CustomerReportedTests.cs +++ b/CommandLineParser.Tests/CustomerReportedTests.cs @@ -20,7 +20,7 @@ public void NoCommandLineArgumentsCrashesParser_Issue_12(bool required, bool out var parser = new CommandLineParser(); parser.Configure(opt => opt.Test) - .Name("-1") + .Name("1") .Default(1) .Required(required); diff --git a/CommandLineParser.Tests/OptionBuilderTest.cs b/CommandLineParser.Tests/OptionBuilderTest.cs index ab0812b..05d3dbc 100644 --- a/CommandLineParser.Tests/OptionBuilderTest.cs +++ b/CommandLineParser.Tests/OptionBuilderTest.cs @@ -1,5 +1,5 @@ using System; - +using MatthiWare.CommandLine; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Core; @@ -19,7 +19,12 @@ public void OptionBuilderConfiguresOptionCorrectly() var resolverFactoryMock = new Mock(); resolverFactoryMock.Setup(_ => _.CreateResolver(It.IsAny())).Returns(resolverMock.Object); - var option = new CommandLineOption(new object(), XUnitExtensions.CreateLambda(o => o.ToString()), resolverFactoryMock.Object); + var option = new CommandLineOption( + new CommandLineParserOptions { PrefixLongOption = string.Empty, PrefixShortOption = string.Empty }, + new object(), + XUnitExtensions.CreateLambda(o => o.ToString()), + resolverFactoryMock.Object); + var builder = option as IOptionBuilder; string sDefault = "default"; diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index e68af88..7ac4907 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -29,6 +29,7 @@ public sealed class CommandLineParser : ICommandLineParser private readonly TOption m_option; private readonly Dictionary m_options; private readonly List m_commands; + private readonly CommandLineParserOptions m_parserOptions; /// /// Read-only collection of options specified @@ -54,7 +55,7 @@ public sealed class CommandLineParser : ICommandLineParser /// Creates a new instance of the commandline parser /// public CommandLineParser() - : this(new DefaultArgumentResolverFactory(), new DefaultContainerResolver()) + : this(new CommandLineParserOptions(), new DefaultArgumentResolverFactory(), new DefaultContainerResolver()) { } /// @@ -62,7 +63,16 @@ public CommandLineParser() /// /// argument resolver to use public CommandLineParser(IArgumentResolverFactory argumentResolverFactory) - : this(argumentResolverFactory, new DefaultContainerResolver()) + : this(new CommandLineParserOptions(), argumentResolverFactory, new DefaultContainerResolver()) + { } + + /// + /// Creates a new instance of the commandline parser + /// + /// options that the parser will use + /// argument resolver to use + public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolverFactory argumentResolverFactory) + : this(parserOptions, argumentResolverFactory, new DefaultContainerResolver()) { } /// @@ -70,7 +80,16 @@ public CommandLineParser(IArgumentResolverFactory argumentResolverFactory) /// /// container resolver to use public CommandLineParser(IContainerResolver containerResolver) - : this(new DefaultArgumentResolverFactory(), containerResolver) + : this(new CommandLineParserOptions(), new DefaultArgumentResolverFactory(), containerResolver) + { } + + /// + /// Creates a new instance of the commandline parser + /// + /// options that the parser will use + /// container resolver to use + public CommandLineParser(CommandLineParserOptions parserOptions, IContainerResolver containerResolver) + : this(parserOptions, new DefaultArgumentResolverFactory(), containerResolver) { } /// @@ -78,8 +97,9 @@ public CommandLineParser(IContainerResolver containerResolver) /// /// argument resolver to use /// container resolver to use - public CommandLineParser(IArgumentResolverFactory argumentResolverFactory, IContainerResolver containerResolver) + public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolverFactory argumentResolverFactory, IContainerResolver containerResolver) { + m_parserOptions = parserOptions; m_option = new TOption(); m_options = new Dictionary(); @@ -109,7 +129,7 @@ private IOptionBuilder ConfigureInternal(LambdaExpression selector, string key) { if (!m_options.ContainsKey(key)) { - var option = new CommandLineOption(m_option, selector, ArgumentResolverFactory); + var option = new CommandLineOption(m_parserOptions, m_option, selector, ArgumentResolverFactory); m_options.Add(key, option); } @@ -207,7 +227,7 @@ private void ParseOptions(IList errors, ParseResult result, /// Builder for the command, public ICommandBuilder AddCommand() where TCommandOption : class, new() { - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); m_commands.Add(command); @@ -223,7 +243,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); cmdConfigurator.OnConfigure(command); @@ -243,7 +263,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); cmdConfigurator.OnConfigure(command); @@ -258,7 +278,7 @@ public void RegisterCommand() /// Builder for the command, public ICommandBuilder AddCommand() { - var command = new CommandLineCommand(ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); m_commands.Add(command); diff --git a/CommandLineParser/CommandLineParserOptions.cs b/CommandLineParser/CommandLineParserOptions.cs new file mode 100644 index 0000000..2378b00 --- /dev/null +++ b/CommandLineParser/CommandLineParserOptions.cs @@ -0,0 +1,11 @@ +namespace MatthiWare.CommandLine +{ + public class CommandLineParserOptions + { + public string PrefixShortOption { get; set; } = "-"; + public string PrefixLongOption { get; set; } = "--"; + public string HelpOptionName { get; set; } = "help"; + public bool EnableHelpOption { get; set; } = true; + public string AppName { get; set; } + } +} diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 9841cc0..6fdf796 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -23,12 +23,14 @@ internal class CommandLineCommand : private readonly TCommandOption m_commandOption; private readonly TOption m_baseOption; private readonly IArgumentResolverFactory m_resolverFactory; + private readonly CommandLineParserOptions m_parserOptions; private Action m_executor; private Action m_executor2; - public CommandLineCommand(IArgumentResolverFactory resolverFactory, TOption option) + public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, TOption option) { + m_parserOptions = parserOptions; m_commandOption = new TCommandOption(); m_resolverFactory = resolverFactory; @@ -43,7 +45,7 @@ public override void Execute() public IOptionBuilder Configure(Expression> selector) { - var option = new CommandLineOption(m_commandOption, selector, m_resolverFactory); + var option = new CommandLineOption(m_parserOptions, m_commandOption, selector, m_resolverFactory); m_options.Add(option); diff --git a/CommandLineParser/Core/CommandLineOption.cs b/CommandLineParser/Core/CommandLineOption.cs index 675b22d..7508700 100644 --- a/CommandLineParser/Core/CommandLineOption.cs +++ b/CommandLineParser/Core/CommandLineOption.cs @@ -16,14 +16,16 @@ internal class CommandLineOption : private readonly LambdaExpression m_selector; private object m_defaultValue = null; private readonly IArgumentResolverFactory m_resolverFactory; + private readonly CommandLineParserOptions m_parserOptions; private ICommandLineArgumentResolver m_resolver; - public CommandLineOption(object source, LambdaExpression selector, IArgumentResolverFactory resolver) + public CommandLineOption(CommandLineParserOptions parserOptions, object source, LambdaExpression selector, IArgumentResolverFactory resolver) { - this.m_source = source ?? throw new ArgumentNullException(nameof(source)); - this.m_selector = selector ?? throw new ArgumentNullException(nameof(selector)); - this.m_resolverFactory = resolver ?? throw new ArgumentNullException(nameof(resolver)); + m_parserOptions = parserOptions; + m_source = source ?? throw new ArgumentNullException(nameof(source)); + m_selector = selector ?? throw new ArgumentNullException(nameof(selector)); + m_resolverFactory = resolver ?? throw new ArgumentNullException(nameof(resolver)); } public ICommandLineArgumentResolver Resolver @@ -72,8 +74,8 @@ IOptionBuilder IOptionBuilder.Description(string help) IOptionBuilder IOptionBuilder.Name(string shortName, string longName) { - LongName = longName; - ShortName = shortName; + LongName = $"{m_parserOptions.PrefixLongOption}{longName}"; ; + ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; ; return this; } @@ -87,7 +89,7 @@ IOptionBuilder IOptionBuilder.Required(bool required = true) IOptionBuilder IOptionBuilder.Name(string shortName) { - ShortName = shortName; + ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; return this; } From f646c5b10e632c793e0993af9ded9a75b2a90eae Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Sun, 23 Dec 2018 10:47:48 +0100 Subject: [PATCH 31/41] Update command --- .../Command/CommandTests.cs | 13 ++- .../Usage/UsagePrinterTests.cs | 33 +++++++ .../Abstractions/Command/Command`.cs | 9 ++ .../Command/ICommandConfigurationBuilder`.cs | 39 +++++++++ .../Command/ICommandLineCommandContainer.cs | 23 +++++ .../Command/ICommandLineCommandParser.cs | 5 -- .../Abstractions/ICommandLineParser'.cs | 12 +-- .../Abstractions/Usage/IUsageBuilder.cs | 16 ++++ .../Abstractions/Usage/IUsageDisplay.cs | 12 --- .../Abstractions/Usage/IUsagePrinter.cs | 10 +++ CommandLineParser/CommandLineParser.cs | 38 +++++++- CommandLineParser/CommandLineParserOptions.cs | 9 +- .../Core/Command/CommandLineCommandBase.cs | 27 ++---- ...mmandLineCommand`TOption`TCommandOption.cs | 24 +++++- .../Core/CommandLineOptionBase.cs | 7 +- .../Core/Exceptions/CommandParseException.cs | 6 +- .../Exceptions/OptionNotFoundException.cs | 20 ++--- .../Core/Exceptions/OptionParseException.cs | 17 +++- CommandLineParser/Core/Usage/UsageBuilder.cs | 86 +++++++++++++++++++ CommandLineParser/Core/Usage/UsagePrinter.cs | 33 +++++++ .../Core/Utils/ExtensionMethods.cs | 9 ++ 21 files changed, 371 insertions(+), 77 deletions(-) create mode 100644 CommandLineParser.Tests/Usage/UsagePrinterTests.cs create mode 100644 CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs create mode 100644 CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs create mode 100644 CommandLineParser/Abstractions/Usage/IUsageBuilder.cs delete mode 100644 CommandLineParser/Abstractions/Usage/IUsageDisplay.cs create mode 100644 CommandLineParser/Abstractions/Usage/IUsagePrinter.cs create mode 100644 CommandLineParser/Core/Usage/UsageBuilder.cs create mode 100644 CommandLineParser/Core/Usage/UsagePrinter.cs diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index 070565d..1c33635 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -46,7 +46,7 @@ public void AddCommandType() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("bla"))); } [Fact] @@ -58,16 +58,23 @@ public void AddCommandTypeWithGenericOption() Assert.Equal(1, parser.Commands.Count); - Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("-bla"))); + Assert.NotNull(parser.Commands.First(cmd => cmd.Name.Equals("bla"))); } private class MyComand : Command { + public override void OnConfigure(ICommandConfigurationBuilder builder) + { + base.OnConfigure(builder); + + builder.Name("bla").Required(); + } + public override void OnConfigure(ICommandConfigurationBuilder builder) { base.OnConfigure(builder); - builder.Name("-bla").Required(); + builder.Name("bla").Required(); } public override void OnExecute(object options, object commandOptions) diff --git a/CommandLineParser.Tests/Usage/UsagePrinterTests.cs b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs new file mode 100644 index 0000000..c3d814d --- /dev/null +++ b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs @@ -0,0 +1,33 @@ +using MatthiWare.CommandLine; +using MatthiWare.CommandLine.Abstractions.Usage; +using MatthiWare.CommandLine.Core.Attributes; +using Moq; +using Xunit; + +namespace MatthiWare.CommandLineParser.Tests.Usage +{ + public class UsagePrinterTests + { + private class UsagePrinterGetsCalledOptions + { + [Name("o"), Required] + public string Option { get; set; } + } + + [Theory] + [InlineData(new string[] { }, true)] + [InlineData(new string[] { "-o", "bla" }, false)] + [InlineData(new string[] { "-xd", "bla" }, true)] + public void UsagePrintGetsCalledInCorrectCases(string[] args, bool called) + { + var printerMock = new Mock(); + var parser = new CommandLineParser(); + + parser.Printer = printerMock.Object; + + parser.Parse(args); + + printerMock.Verify(mock => mock.PrintUsage(), called ? Times.Once() : Times.Never()); + } + } +} diff --git a/CommandLineParser/Abstractions/Command/Command`.cs b/CommandLineParser/Abstractions/Command/Command`.cs index bfe1a85..99d8cb1 100644 --- a/CommandLineParser/Abstractions/Command/Command`.cs +++ b/CommandLineParser/Abstractions/Command/Command`.cs @@ -10,6 +10,15 @@ public abstract class Command : where TOptions : class, new() where TCommandOptions : class, new() { + /// + /// Configures the command + /// for more info. + /// + /// + public virtual void OnConfigure(ICommandConfigurationBuilder builder) + { + base.OnConfigure(builder); + } /// /// Executes the command diff --git a/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs new file mode 100644 index 0000000..7e59d37 --- /dev/null +++ b/CommandLineParser/Abstractions/Command/ICommandConfigurationBuilder`.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq.Expressions; + +namespace MatthiWare.CommandLine.Abstractions.Command +{ + public interface ICommandConfigurationBuilder + : ICommandConfigurationBuilder + where TSource : class + { + /// + /// Configures an option in the model + /// + /// Type of the property + /// Model property to configure + /// + IOptionBuilder Configure(Expression> selector); + + /// + /// Configures if the command is required + /// + /// True or false + /// + new ICommandConfigurationBuilder Required(bool required = true); + + /// + /// Configures the description text for the command + /// + /// True or false + /// + new ICommandConfigurationBuilder Description(string description); + + /// + /// Configures the command name + /// + /// Short name + /// + new ICommandConfigurationBuilder Name(string name); + } +} \ No newline at end of file diff --git a/CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs b/CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs new file mode 100644 index 0000000..7103b32 --- /dev/null +++ b/CommandLineParser/Abstractions/Command/ICommandLineCommandContainer.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace MatthiWare.CommandLine.Abstractions.Command +{ + /// + /// Container that holds options and subcommands. + /// + public interface ICommandLineCommandContainer + { + /// + /// Read-only list of available sub-commands + /// to configure or add an command + /// + IReadOnlyList Commands { get; } + + /// + /// Read-only list of available options for this command + /// to configure or add an option + /// + IReadOnlyList Options { get; } + } +} diff --git a/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs b/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs index 95c7a73..5fa0266 100644 --- a/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs +++ b/CommandLineParser/Abstractions/Command/ICommandLineCommandParser.cs @@ -10,11 +10,6 @@ namespace MatthiWare.CommandLine.Abstractions.Command /// public interface ICommandLineCommandParser { - /// - /// Read-only collection of the options for the command - /// - IReadOnlyList Options { get; } - /// /// Parses the arguments /// diff --git a/CommandLineParser/Abstractions/ICommandLineParser'.cs b/CommandLineParser/Abstractions/ICommandLineParser'.cs index 56dc8b6..b0f234b 100644 --- a/CommandLineParser/Abstractions/ICommandLineParser'.cs +++ b/CommandLineParser/Abstractions/ICommandLineParser'.cs @@ -4,6 +4,7 @@ using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Usage; namespace MatthiWare.CommandLine.Abstractions { @@ -13,16 +14,9 @@ public interface ICommandLineParser #region Properties /// - /// Read-only list of available sub-commands - /// to configure or add an command + /// Tool to print usage info. /// - IReadOnlyList Commands { get; } - - /// - /// Read-only list of available options for this command - /// to configure or add an option - /// - IReadOnlyList Options { get; } + IUsagePrinter Printer { get; set; } /// /// Factory to resolve the argument type diff --git a/CommandLineParser/Abstractions/Usage/IUsageBuilder.cs b/CommandLineParser/Abstractions/Usage/IUsageBuilder.cs new file mode 100644 index 0000000..4a3186f --- /dev/null +++ b/CommandLineParser/Abstractions/Usage/IUsageBuilder.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MatthiWare.CommandLine.Abstractions.Command; + +namespace MatthiWare.CommandLine.Abstractions.Usage +{ + public interface IUsageBuilder + { + void Print(); + void PrintUsage(string name, bool hasOptions, bool hasCommands); + void PrintOptions(IEnumerable options); + void PrintOption(ICommandLineOption option); + void PrintCommandDescriptions(IEnumerable commands); + void PrintCommandDescription(ICommandLineCommand command); + void PrintCommand(string name, ICommandLineCommandContainer container); + } +} diff --git a/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs b/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs deleted file mode 100644 index a150183..0000000 --- a/CommandLineParser/Abstractions/Usage/IUsageDisplay.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MatthiWare.CommandLine.Abstractions.Usage -{ - public interface IUsageDisplay - { - string ToUsage(); - string ToShortUsage(); - } -} diff --git a/CommandLineParser/Abstractions/Usage/IUsagePrinter.cs b/CommandLineParser/Abstractions/Usage/IUsagePrinter.cs new file mode 100644 index 0000000..862a24d --- /dev/null +++ b/CommandLineParser/Abstractions/Usage/IUsagePrinter.cs @@ -0,0 +1,10 @@ +using MatthiWare.CommandLine.Abstractions.Command; + +namespace MatthiWare.CommandLine.Abstractions.Usage +{ + public interface IUsagePrinter + { + void PrintUsage(); + void PrintUsage(ICommandLineCommand command); + } +} diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index 7ac4907..2f09a8c 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -9,11 +9,13 @@ using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Usage; using MatthiWare.CommandLine.Core; using MatthiWare.CommandLine.Core.Attributes; using MatthiWare.CommandLine.Core.Command; using MatthiWare.CommandLine.Core.Exceptions; using MatthiWare.CommandLine.Core.Parsing; +using MatthiWare.CommandLine.Core.Usage; [assembly: InternalsVisibleTo("CommandLineParser.Tests")] @@ -23,7 +25,7 @@ namespace MatthiWare.CommandLine /// Command line parser /// /// Options model - public sealed class CommandLineParser : ICommandLineParser + public sealed class CommandLineParser : ICommandLineParser, ICommandLineCommandContainer where TOption : class, new() { private readonly TOption m_option; @@ -31,6 +33,11 @@ public sealed class CommandLineParser : ICommandLineParser private readonly List m_commands; private readonly CommandLineParserOptions m_parserOptions; + /// + /// Tool to print usage info. + /// + public IUsagePrinter Printer { get; set; } + /// /// Read-only collection of options specified /// @@ -108,6 +115,8 @@ public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolv ArgumentResolverFactory = argumentResolverFactory; ContainerResolver = containerResolver; + Printer = new UsagePrinter(parserOptions, this); + InitialzeModel(); } @@ -158,10 +167,33 @@ public IParserResult Parse(string[] args) AutoExecuteCommands(result); + AutoPrintUsageAndErrors(errors, args.Length > 0); + return result; } - private void AutoExecuteCommands(ParseResult result) + private void AutoPrintUsageAndErrors(ICollection errors, bool argsSuppplied) + { + if (!m_parserOptions.AutoPrintUsageAndErrors) return; + + if (!argsSuppplied) + PrintHelp(); + else if (errors.Count > 0) + PrintErrors(errors); + } + + private void PrintErrors(ICollection errors) + { + foreach (var error in errors) + Console.Error.WriteLine(error); + + PrintHelp(); + } + + private void PrintHelp() + => Printer.PrintUsage(); + + private void AutoExecuteCommands(IParserResult result) { if (result.HasErrors) return; @@ -197,7 +229,7 @@ private void ParseOptions(IList errors, ParseResult result, if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired) { - errors.Add(new OptionNotFoundException(option)); + errors.Add(new OptionNotFoundException(m_parserOptions, option)); continue; } diff --git a/CommandLineParser/CommandLineParserOptions.cs b/CommandLineParser/CommandLineParserOptions.cs index 2378b00..2d02d28 100644 --- a/CommandLineParser/CommandLineParserOptions.cs +++ b/CommandLineParser/CommandLineParserOptions.cs @@ -4,8 +4,15 @@ public class CommandLineParserOptions { public string PrefixShortOption { get; set; } = "-"; public string PrefixLongOption { get; set; } = "--"; - public string HelpOptionName { get; set; } = "help"; + /// + /// Help option name. + /// Accepts both formatted and unformatted help name. + /// If the name is a single string it will use the + /// If the name is split for example h|help it will use the following format |]]> + /// + public string HelpOptionName { get; set; } = "h|help"; public bool EnableHelpOption { get; set; } = true; + public bool AutoPrintUsageAndErrors { get; set; } = true; public string AppName { get; set; } } } diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 974d7e0..6eb83da 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -10,13 +10,16 @@ namespace MatthiWare.CommandLine.Core.Command { internal abstract class CommandLineCommandBase : ICommandLineCommandParser, - ICommandLineCommand, - IUsageDisplay + ICommandLineCommandContainer, + ICommandLineCommand { protected readonly List m_options = new List(); + protected readonly List m_commands = new List(); public IReadOnlyList Options => m_options.AsReadOnly(); + public IReadOnlyList Commands => m_commands.AsReadOnly(); + public string Name { get; protected set; } public string Description { get; protected set; } public bool IsRequired { get; protected set; } @@ -25,25 +28,5 @@ internal abstract class CommandLineCommandBase : public abstract void Execute(); public abstract ICommandParserResult Parse(IArgumentManager argumentManager); - - public string ToShortUsage() - => $" {Name}\t\t{Description}"; - - public string ToUsage() - { - var sb = new StringBuilder(); - - sb.AppendLine(Description); - - if (m_options.Count == 0) - return sb.ToString(); - - sb.AppendLine("Options: "); - - foreach (var opt in m_options) - sb.AppendLine(opt.ToUsage()); - - return sb.ToString(); - } } } diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 6fdf796..29fedb7 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -16,6 +16,7 @@ internal class CommandLineCommand : ICommandBuilder, ICommandBuilder, ICommandConfigurationBuilder, + ICommandConfigurationBuilder, IOptionConfigurator where TOption : class where TCommandOption : class, new() @@ -61,7 +62,7 @@ public override ICommandParserResult Parse(IArgumentManager argumentManager) { if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired) { - errors.Add(new OptionNotFoundException(option)); + errors.Add(new OptionNotFoundException(m_parserOptions, option)); continue; } @@ -183,5 +184,26 @@ ICommandBuilder ICommandBuilder.Name(string name) return this; } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.Required(bool required) + { + IsRequired = required; + + return this; + } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.Description(string description) + { + Description = description; + + return this; + } + + ICommandConfigurationBuilder ICommandConfigurationBuilder.Name(string name) + { + Name = name; + + return this; + } } } diff --git a/CommandLineParser/Core/CommandLineOptionBase.cs b/CommandLineParser/Core/CommandLineOptionBase.cs index 6616c55..ccc0c9f 100644 --- a/CommandLineParser/Core/CommandLineOptionBase.cs +++ b/CommandLineParser/Core/CommandLineOptionBase.cs @@ -8,7 +8,7 @@ namespace MatthiWare.CommandLine.Core { [DebuggerDisplay("Cmd Option {ShortName ?? LongName}, Req: {IsRequired}, HasDefault: {HasDefault}")] - internal abstract class CommandLineOptionBase : IParser, ICommandLineOption, IUsageDisplay + internal abstract class CommandLineOptionBase : IParser, ICommandLineOption { public string ShortName { get; protected set; } public string LongName { get; protected set; } @@ -24,11 +24,6 @@ internal abstract class CommandLineOptionBase : IParser, ICommandLineOption, IUs public abstract void Parse(ArgumentModel model); - public string ToShortUsage() => ToUsage(); - - public string ToUsage() - => $" {(HasShortName ? ShortName : string.Empty)}{(HasShortName && HasLongName ? "|" : string.Empty)}{(HasLongName ? LongName : string.Empty)}\t\t{Description}"; - public abstract void UseDefault(); } } diff --git a/CommandLineParser/Core/Exceptions/CommandParseException.cs b/CommandLineParser/Core/Exceptions/CommandParseException.cs index 751aa85..956ad2f 100644 --- a/CommandLineParser/Core/Exceptions/CommandParseException.cs +++ b/CommandLineParser/Core/Exceptions/CommandParseException.cs @@ -12,12 +12,12 @@ public class CommandParseException : Exception /// /// Command that caused the parsing error /// - public ICommandLineCommand Option { get; set; } + public ICommandLineCommand Command { get; set; } - public CommandParseException(ICommandLineCommand option, Exception innerException) + public CommandParseException(ICommandLineCommand command, Exception innerException) : base("", innerException) { - Option = option; + Command = command; } } } diff --git a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs index 863d013..3131e7c 100644 --- a/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs +++ b/CommandLineParser/Core/Exceptions/OptionNotFoundException.cs @@ -14,21 +14,21 @@ public class OptionNotFoundException : KeyNotFoundException /// public ICommandLineOption Option { get; private set; } - public OptionNotFoundException(ICommandLineOption option) - : base(CreateMessage(option)) + public OptionNotFoundException(CommandLineParserOptions parserOptions, ICommandLineOption option) + : base(CreateMessage(parserOptions, option)) { } - private static string CreateMessage(ICommandLineOption option) + private static string CreateMessage(CommandLineParserOptions parserOptions, ICommandLineOption option) { - bool hasShortName = option.HasShortName; - bool hasLongName = option.HasLongName; - bool hasBoth = hasShortName && hasLongName; + bool hasShort = option.HasShortName; + bool hasLong = option.HasLongName; + bool hasBoth = hasShort && hasLong; - string shortName = hasShortName ? $"'{option.ShortName}'" : string.Empty; - string longName = hasLongName ? $"'{option.LongName}'" : string.Empty; - string or = hasBoth ? " or " : string.Empty; + string hasBothSeperator = hasBoth ? "|" : string.Empty; + string shortName = hasShort ? $"{parserOptions.PrefixShortOption}{option.ShortName}" : string.Empty; + string longName = hasLong ? $"{parserOptions.PrefixLongOption}{option.LongName}" : string.Empty; - return $"Required argument {shortName}{or}{longName} not found!"; + return $"Required argument {shortName}{hasBothSeperator}{longName} not found!"; } } } diff --git a/CommandLineParser/Core/Exceptions/OptionParseException.cs b/CommandLineParser/Core/Exceptions/OptionParseException.cs index 2cf99f4..e67965c 100644 --- a/CommandLineParser/Core/Exceptions/OptionParseException.cs +++ b/CommandLineParser/Core/Exceptions/OptionParseException.cs @@ -7,7 +7,7 @@ namespace MatthiWare.CommandLine.Core.Exceptions { /// /// Indicates that an option was unable to be parsed - /// This could be caused by an missing . + /// This could be caused by an missing . /// public class OptionParseException : Exception { @@ -15,10 +15,23 @@ public class OptionParseException : Exception private ArgumentModel argModel; public OptionParseException(ICommandLineOption option, ArgumentModel argModel) - : base($"Cannot parse option '{argModel.Key}:{argModel.Value ?? "NULL"}'.") + : base(CreateMessage(option, argModel)) { this.option = option; this.argModel = argModel; } + + private static string CreateMessage(ICommandLineOption option, ArgumentModel argModel) + { + bool hasShort = option.HasShortName; + bool hasLong = option.HasLongName; + bool hasBoth = hasShort && hasLong; + + string hasBothSeperator = hasBoth ? "|" : string.Empty; + string shortName = hasShort ? option.ShortName : string.Empty; + string longName = hasLong ? option.LongName : string.Empty; + + return $"Unable to parse option {shortName}{hasBothSeperator}{longName} value '{argModel.Value}' is invalid!"; + } } } diff --git a/CommandLineParser/Core/Usage/UsageBuilder.cs b/CommandLineParser/Core/Usage/UsageBuilder.cs new file mode 100644 index 0000000..dd03a50 --- /dev/null +++ b/CommandLineParser/Core/Usage/UsageBuilder.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Usage; +using MatthiWare.CommandLine.Core.Command; +using MatthiWare.CommandLine.Core.Utils; + +namespace MatthiWare.CommandLine.Core.Usage +{ + internal class UsageBuilder : IUsageBuilder + { + private readonly StringBuilder stringBuilder = new StringBuilder(); + private readonly CommandLineParserOptions parserOptions; + + public UsageBuilder(CommandLineParserOptions parserOptions) + => this.parserOptions = parserOptions; + + public void Print() + { + Console.WriteLine(stringBuilder.ToString()); + stringBuilder.Clear(); + } + + public void PrintUsage(string name, bool hasOptions, bool hasCommands) + { + stringBuilder.AppendLine() + .Append("Usage: ") + .Append(parserOptions.AppName) + .AppendIf(!string.IsNullOrWhiteSpace(parserOptions.AppName), " ") + .Append(name) + .AppendIf(!string.IsNullOrWhiteSpace(name), " ") + .Append(hasOptions ? "[options] " : string.Empty) + .Append(hasCommands ? "[commands]" : string.Empty) + .AppendLine(); + + } + + public void PrintCommand(string name, ICommandLineCommandContainer container) + { + PrintUsage(name, container.Options.Any(), container.Commands.Any()); + + PrintOptions(container.Options); + + PrintCommandDescriptions(container.Commands); + } + + public void PrintCommandDescription(ICommandLineCommand command) + => stringBuilder.AppendLine($"\t{command.Name}\t{command.Description}"); + + public void PrintCommandDescriptions(IEnumerable commands) + { + if (!commands.Any()) return; + + stringBuilder.AppendLine().AppendLine("Commands: "); + + foreach (var cmd in commands) + PrintCommandDescription(cmd); + } + + public void PrintOption(ICommandLineOption option) + { + bool hasShort = option.HasShortName; + bool hasLong = option.HasLongName; + bool hasBoth = hasShort && hasLong; + + string hasBothSeperator = hasBoth ? "|" : string.Empty; + string shortName = hasShort ? $"{parserOptions.PrefixShortOption}{option.ShortName}" : string.Empty; + string longName = hasLong ? $"{parserOptions.PrefixLongOption}{option.LongName}" : string.Empty; + + stringBuilder.AppendLine($"\t{shortName}{hasBothSeperator}{longName}\t{option.Description}"); + } + + public void PrintOptions(IEnumerable options) + { + if (!options.Any()) return; + + stringBuilder.AppendLine().AppendLine("Options: "); + + foreach (var opt in options) + PrintOption(opt); + } + } +} diff --git a/CommandLineParser/Core/Usage/UsagePrinter.cs b/CommandLineParser/Core/Usage/UsagePrinter.cs new file mode 100644 index 0000000..cba8b0c --- /dev/null +++ b/CommandLineParser/Core/Usage/UsagePrinter.cs @@ -0,0 +1,33 @@ +using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Abstractions.Usage; + +namespace MatthiWare.CommandLine.Core.Usage +{ + internal class UsagePrinter : IUsagePrinter + { + private readonly CommandLineParserOptions m_parserOptions; + private readonly IUsageBuilder m_usageBuilder; + private readonly ICommandLineCommandContainer m_commandContainer; + + public UsagePrinter(CommandLineParserOptions parserOptions, ICommandLineCommandContainer container) + { + m_parserOptions = parserOptions; + m_commandContainer = container; + + m_usageBuilder = new UsageBuilder(parserOptions); + } + + public void PrintUsage() + { + m_usageBuilder.PrintCommand(string.Empty, m_commandContainer); + m_usageBuilder.Print(); + } + + public void PrintUsage(ICommandLineCommand command) + { + m_usageBuilder.PrintCommand(command.Name, (ICommandLineCommandContainer)command); + m_usageBuilder.Print(); + } + } +} diff --git a/CommandLineParser/Core/Utils/ExtensionMethods.cs b/CommandLineParser/Core/Utils/ExtensionMethods.cs index dc1408c..4478195 100644 --- a/CommandLineParser/Core/Utils/ExtensionMethods.cs +++ b/CommandLineParser/Core/Utils/ExtensionMethods.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace MatthiWare.CommandLine.Core.Utils { @@ -28,5 +29,13 @@ public static bool IsAssignableToGenericType(this Type self, Type genericType) return IsAssignableToGenericType(baseType, genericType); } + + public static StringBuilder AppendIf(this StringBuilder self, bool contition, string text) + { + if (contition) + self.Append(text); + + return self; + } } } From 3d69e405eaf6822a57f633401134a291f428a500 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Sun, 23 Dec 2018 11:29:10 +0100 Subject: [PATCH 32/41] Generic command builder exposed wrong return type. --- .../Abstractions/Command/ICommandBuilder'.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index 621685b..09e370c 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -39,5 +39,13 @@ public interface ICommandBuilder : ICommandConfigurationBuilde /// name /// new ICommandBuilder Name(string name); + + /// + /// Configures how the command should be invoked. + /// Default behavior is to auto invoke the command. + /// + /// True if the command executor will be invoked (default), false if you want to invoke manually. + /// + new ICommandBuilder InvokeCommand(bool invoke); } } From 9f7ccf647437d080a0ffe0669ef36ba548908bc6 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Tue, 25 Dec 2018 09:34:58 +0100 Subject: [PATCH 33/41] Add subcommand test --- .../Command/SubCommandTests.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 CommandLineParser.Tests/Command/SubCommandTests.cs diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs new file mode 100644 index 0000000..6622177 --- /dev/null +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Threading; +using MatthiWare.CommandLine; +using MatthiWare.CommandLine.Abstractions; +using MatthiWare.CommandLine.Abstractions.Command; +using MatthiWare.CommandLine.Core.Attributes; +using Xunit; + +namespace MatthiWare.CommandLineParser.Tests.Command +{ + public class SubCommandTests + { + [Fact] + public void TestSubCommandWorksCorrectly() + { + var lock1 = new ManualResetEventSlim(); + var lock2 = new ManualResetEventSlim(); + + var containerResolver = new CustomInstantiator(lock1, lock2); + + var parser = new CommandLineParser(containerResolver); + + var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15" }); + + Assert.False(result.HasErrors); + + Assert.True(lock1.Wait(1000), "MainCommand didn't execute in time."); + Assert.True(lock2.Wait(1000), "SubCommand didn't execute in time."); + } + + private class CustomInstantiator : IContainerResolver + { + private ManualResetEventSlim lock1; + private ManualResetEventSlim lock2; + + public CustomInstantiator(ManualResetEventSlim lock1, ManualResetEventSlim lock2) + { + this.lock1 = lock1; + this.lock2 = lock2; + } + + public T Resolve() + { + if (typeof(T) == typeof(MainCommand)) + return (T)Activator.CreateInstance(typeof(T), lock1); + else if (typeof(T) == typeof(SubCommand)) + return (T)Activator.CreateInstance(typeof(T), lock2); + else + return default; + } + } + } + + public class MainCommand : Command + { + private readonly ManualResetEventSlim locker; + + public MainCommand(ManualResetEventSlim locker) + { + this.locker = locker; + } + + public override void OnConfigure(ICommandConfigurationBuilder builder) + { + builder + .Name("main") + .Required(); + } + + public override void OnExecute(MainModel options, SubModel commandOptions) + { + base.OnExecute(options, commandOptions); + + locker.Set(); + } + } + + public class SubCommand : Command + { + + private readonly ManualResetEventSlim locker; + + public SubCommand(ManualResetEventSlim locker) + { + this.locker = locker; + } + + public override void OnConfigure(ICommandConfigurationBuilder builder) + { + builder + .Name("sub") + .Required(); + } + + public override void OnExecute(MainModel options, SubModel commandOptions) + { + base.OnExecute(options, commandOptions); + + locker.Set(); + } + } + + public class MainModel + { + [Required, Name("b")] + public string Bla { get; set; } + public MainCommand MainCommand { get; set; } + } + + public class SubModel + { + [Required, Name("i")] + public int Item { get; set; } + public SubCommand SubCommand { get; set; } + } +} From 28525f98d15029e152940ea17a60978e85372214 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Tue, 25 Dec 2018 11:28:17 +0100 Subject: [PATCH 34/41] Add subcommands --- .../Parsing/Command/ICommandParserResult.cs | 11 +- CommandLineParser/CommandLineParser.cs | 32 +++- .../Core/Command/CommandLineCommandBase.cs | 6 +- ...mmandLineCommand`TOption`TCommandOption.cs | 160 +++++++++++++++++- .../Core/Command/CommandParserResult.cs | 10 ++ 5 files changed, 204 insertions(+), 15 deletions(-) diff --git a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs index 82d1f1f..eb267a4 100644 --- a/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs +++ b/CommandLineParser/Abstractions/Parsing/Command/ICommandParserResult.cs @@ -1,5 +1,6 @@ using System; - +using System.Collections; +using System.Collections.Generic; using MatthiWare.CommandLine.Abstractions.Command; namespace MatthiWare.CommandLine.Abstractions.Parsing.Command @@ -9,6 +10,14 @@ namespace MatthiWare.CommandLine.Abstractions.Parsing.Command /// public interface ICommandParserResult { + /// + /// Subcommands of the current command + /// + IReadOnlyCollection SubCommands { get; } + + /// + /// The associated command + /// ICommandLineCommand Command { get; } /// diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index 2f09a8c..be6416d 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -9,6 +9,7 @@ using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; +using MatthiWare.CommandLine.Abstractions.Parsing.Command; using MatthiWare.CommandLine.Abstractions.Usage; using MatthiWare.CommandLine.Core; using MatthiWare.CommandLine.Core.Attributes; @@ -16,6 +17,7 @@ using MatthiWare.CommandLine.Core.Exceptions; using MatthiWare.CommandLine.Core.Parsing; using MatthiWare.CommandLine.Core.Usage; +using MatthiWare.CommandLine.Core.Utils; [assembly: InternalsVisibleTo("CommandLineParser.Tests")] @@ -197,8 +199,16 @@ private void AutoExecuteCommands(IParserResult result) { if (result.HasErrors) return; - foreach (var cmd in result.CommandResults.Where(r => r.Command.AutoExecute)) + ExecuteCommandParserResults(result.CommandResults.Where(r => r.Command.AutoExecute)); + } + + private void ExecuteCommandParserResults(IEnumerable results) + { + foreach (var cmd in results) cmd.ExecuteCommand(); + + foreach (var cmd in results) + ExecuteCommandParserResults(cmd.SubCommands.Where(sub => sub.Command.AutoExecute)); } private void ParseCommands(IList errors, ParseResult result, IArgumentManager argumentManager) @@ -259,7 +269,7 @@ private void ParseOptions(IList errors, ParseResult result, /// Builder for the command, public ICommandBuilder AddCommand() where TCommandOption : class, new() { - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); m_commands.Add(command); @@ -275,7 +285,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); cmdConfigurator.OnConfigure(command); @@ -295,7 +305,7 @@ public void RegisterCommand() { var cmdConfigurator = ContainerResolver.Resolve(); - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); cmdConfigurator.OnConfigure(command); @@ -310,7 +320,7 @@ public void RegisterCommand() /// Builder for the command, public ICommandBuilder AddCommand() { - var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, m_option); + var command = new CommandLineCommand(m_parserOptions, ArgumentResolverFactory, ContainerResolver, m_option); m_commands.Add(command); @@ -360,6 +370,18 @@ private void InitialzeModel() if (ignoreSet) continue; // Ignore the configured actions for this option. + if (propInfo.PropertyType.IsAssignableToGenericType(typeof(Command<>))) + { + var genericTypes = propInfo.PropertyType.BaseType.GenericTypeArguments; + var method = GetType().GetMethods().First(m => + { + return (m.Name == nameof(RegisterCommand) && m.IsGenericMethod && m.GetGenericArguments().Length == genericTypes.Length); + }); + var registerCommand = genericTypes.Length > 1 ? method.MakeGenericMethod(propInfo.PropertyType, genericTypes[1]) : method.MakeGenericMethod(propInfo.PropertyType); + + registerCommand.Invoke(this, null); + } + foreach (var action in actions) action(); } diff --git a/CommandLineParser/Core/Command/CommandLineCommandBase.cs b/CommandLineParser/Core/Command/CommandLineCommandBase.cs index 6eb83da..fd14ec5 100644 --- a/CommandLineParser/Core/Command/CommandLineCommandBase.cs +++ b/CommandLineParser/Core/Command/CommandLineCommandBase.cs @@ -13,10 +13,10 @@ internal abstract class CommandLineCommandBase : ICommandLineCommandContainer, ICommandLineCommand { - protected readonly List m_options = new List(); - protected readonly List m_commands = new List(); + protected readonly Dictionary m_options = new Dictionary(); + protected readonly List m_commands = new List(); - public IReadOnlyList Options => m_options.AsReadOnly(); + public IReadOnlyList Options => new ReadOnlyCollectionWrapper(m_options.Values); public IReadOnlyList Commands => m_commands.AsReadOnly(); diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 29fedb7..f518817 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; - +using System.Reflection; using MatthiWare.CommandLine.Abstractions; using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Models; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Abstractions.Parsing.Command; +using MatthiWare.CommandLine.Core.Attributes; using MatthiWare.CommandLine.Core.Exceptions; +using MatthiWare.CommandLine.Core.Utils; namespace MatthiWare.CommandLine.Core.Command { @@ -24,18 +27,22 @@ internal class CommandLineCommand : private readonly TCommandOption m_commandOption; private readonly TOption m_baseOption; private readonly IArgumentResolverFactory m_resolverFactory; + private readonly IContainerResolver m_containerResolver; private readonly CommandLineParserOptions m_parserOptions; private Action m_executor; private Action m_executor2; - public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, TOption option) + public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, IContainerResolver containerResolver, TOption option) { m_parserOptions = parserOptions; m_commandOption = new TCommandOption(); + m_containerResolver = containerResolver; m_resolverFactory = resolverFactory; m_baseOption = option; + + InitialzeModel(); } public override void Execute() @@ -46,11 +53,22 @@ public override void Execute() public IOptionBuilder Configure(Expression> selector) { - var option = new CommandLineOption(m_parserOptions, m_commandOption, selector, m_resolverFactory); + var memberInfo = ((MemberExpression)selector.Body).Member; + var key = $"{memberInfo.DeclaringType.FullName}.{memberInfo.Name}"; + + return ConfigureInternal(selector, key); + } + + private IOptionBuilder ConfigureInternal(LambdaExpression selector, string key) + { + if (!m_options.ContainsKey(key)) + { + var option = new CommandLineOption(m_parserOptions, m_commandOption, selector, m_resolverFactory); - m_options.Add(option); + m_options.Add(key, option); + } - return option; + return m_options[key] as IOptionBuilder; } public override ICommandParserResult Parse(IArgumentManager argumentManager) @@ -58,8 +76,27 @@ public override ICommandParserResult Parse(IArgumentManager argumentManager) var result = new CommandParserResult(this); var errors = new List(); - foreach (var option in m_options) + foreach (var cmd in m_commands) { + if (!argumentManager.TryGetValue(cmd, out ArgumentModel model) && cmd.IsRequired) + { + errors.Add(new CommandNotFoundException(cmd)); + + continue; + } + + var cmdParseResult = cmd.Parse(argumentManager); + + if (cmdParseResult.HasErrors) + errors.Add(new CommandParseException(cmd, cmdParseResult.Error)); + + result.MergeResult(cmdParseResult); + } + + foreach (var o in m_options) + { + var option = o.Value; + if (!argumentManager.TryGetValue(option, out ArgumentModel model) && option.IsRequired) { errors.Add(new OptionNotFoundException(m_parserOptions, option)); @@ -205,5 +242,116 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder + /// Initializes the model class with the attributes specified. + /// + private void InitialzeModel() + { + var properties = typeof(TOption).GetProperties(); + + foreach (var propInfo in properties) + { + var attributes = propInfo.GetCustomAttributes(true); + + var lambda = GetLambdaExpression(propInfo, out string key); + + var actions = new List(4); + bool ignoreSet = false; + + foreach (var attribute in attributes) + { + if (ignoreSet) break; + + switch (attribute) + { + // Ignore has been set, skip all the other attributes and DO NOT execute the action list. + case IgnoreAttribute ignore: + ignoreSet = true; + continue; + case RequiredAttribute required: + actions.Add(() => ConfigureInternal(lambda, key).Required(required.Required)); + break; + case DefaultValueAttribute defaultValue: + actions.Add(() => ConfigureInternal(lambda, key).Default(defaultValue.DefaultValue)); + break; + case DescriptionAttribute helpText: + actions.Add(() => ConfigureInternal(lambda, key).Description(helpText.Description)); + break; + case NameAttribute name: + actions.Add(() => ConfigureInternal(lambda, key).Name(name.ShortName, name.LongName)); + break; + } + } + + if (ignoreSet) continue; // Ignore the configured actions for this option. + + if (propInfo.PropertyType.IsAssignableToGenericType(typeof(Command<>))) + { + var genericTypes = propInfo.PropertyType.BaseType.GenericTypeArguments; + var method = GetType().GetMethods().First(m => + { + return (m.Name == nameof(RegisterCommand) && m.IsGenericMethod && m.GetGenericArguments().Length == genericTypes.Length); + }); + var registerCommand = genericTypes.Length > 1 ? method.MakeGenericMethod(propInfo.PropertyType, genericTypes[1]) : method.MakeGenericMethod(propInfo.PropertyType); + + registerCommand.Invoke(this, null); + } + + foreach (var action in actions) + action(); + } + + LambdaExpression GetLambdaExpression(PropertyInfo propInfo, out string key) + { + var entityType = propInfo.DeclaringType; + var propType = propInfo.PropertyType; + var parameter = Expression.Parameter(entityType, entityType.FullName); + var property = Expression.Property(parameter, propInfo); + var funcType = typeof(Func<,>).MakeGenericType(entityType, propType); + + key = $"{entityType.ToString()}.{propInfo.Name}"; + + return Expression.Lambda(funcType, property, parameter); + } + } + + /// + /// Registers a command type + /// + /// Command type, must be inherit + public void RegisterCommand() + where TCommand : Command + { + var cmdConfigurator = m_containerResolver.Resolve(); + + var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_commandOption); + + cmdConfigurator.OnConfigure(command); + + command.OnExecuting(cmdConfigurator.OnExecute); + + m_commands.Add(command); + } + + /// + /// Registers a command type + /// + /// + /// + public void RegisterCommand() + where TCommand : Command + where V : class, new() + { + var cmdConfigurator = m_containerResolver.Resolve(); + + var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_commandOption); + + cmdConfigurator.OnConfigure(command); + + command.OnExecuting((Action)cmdConfigurator.OnExecute); + + m_commands.Add(command); + } } } diff --git a/CommandLineParser/Core/Command/CommandParserResult.cs b/CommandLineParser/Core/Command/CommandParserResult.cs index f9c6289..a2adae6 100644 --- a/CommandLineParser/Core/Command/CommandParserResult.cs +++ b/CommandLineParser/Core/Command/CommandParserResult.cs @@ -10,6 +10,7 @@ namespace MatthiWare.CommandLine.Core.Command internal class CommandParserResult : ICommandParserResult { private readonly CommandLineCommandBase m_cmd; + private readonly List commandParserResults = new List(); public bool HasErrors { get; private set; } @@ -17,6 +18,8 @@ internal class CommandParserResult : ICommandParserResult public ICommandLineCommand Command => m_cmd; + public IReadOnlyCollection SubCommands => commandParserResults; + public CommandParserResult(CommandLineCommandBase command) { m_cmd = command; @@ -33,6 +36,13 @@ public void MergeResult(ICollection errors) errors.First(); } + public void MergeResult(ICommandParserResult result) + { + HasErrors |= result.HasErrors; + + commandParserResults.Add(result); + } + public void ExecuteCommand() => m_cmd.Execute(); } } From c3c67ebaed06fe98f3dc46bc9d67a6210d59e902 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Wed, 26 Dec 2018 10:02:23 +0100 Subject: [PATCH 35/41] Subcommands fire --- .../Command/SubCommandTests.cs | 17 +++++++++++------ ...CommandLineCommand`TOption`TCommandOption.cs | 10 +++++----- .../Core/Parsing/ArgumentManager.cs | 4 ++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs index 6622177..b858886 100644 --- a/CommandLineParser.Tests/Command/SubCommandTests.cs +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -20,7 +20,7 @@ public void TestSubCommandWorksCorrectly() var parser = new CommandLineParser(containerResolver); - var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15" }); + var result = parser.Parse(new[] { "main", "-b", "something", "sub", "-i", "15", "-n", "-1" }); Assert.False(result.HasErrors); @@ -60,7 +60,7 @@ public MainCommand(ManualResetEventSlim locker) this.locker = locker; } - public override void OnConfigure(ICommandConfigurationBuilder builder) + public override void OnConfigure(ICommandConfigurationBuilder builder) { builder .Name("main") @@ -75,9 +75,8 @@ public override void OnExecute(MainModel options, SubModel commandOptions) } } - public class SubCommand : Command + public class SubCommand : Command { - private readonly ManualResetEventSlim locker; public SubCommand(ManualResetEventSlim locker) @@ -85,14 +84,14 @@ public SubCommand(ManualResetEventSlim locker) this.locker = locker; } - public override void OnConfigure(ICommandConfigurationBuilder builder) + public override void OnConfigure(ICommandConfigurationBuilder builder) { builder .Name("sub") .Required(); } - public override void OnExecute(MainModel options, SubModel commandOptions) + public override void OnExecute(MainModel options, SubSubModel commandOptions) { base.OnExecute(options, commandOptions); @@ -113,4 +112,10 @@ public class SubModel public int Item { get; set; } public SubCommand SubCommand { get; set; } } + + public class SubSubModel + { + [Required, Name("n")] + public int Nothing { get; set; } + } } diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index f518817..2f93506 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -21,7 +21,7 @@ internal class CommandLineCommand : ICommandConfigurationBuilder, ICommandConfigurationBuilder, IOptionConfigurator - where TOption : class + where TOption : class, new() where TCommandOption : class, new() { private readonly TCommandOption m_commandOption; @@ -248,7 +248,7 @@ ICommandConfigurationBuilder ICommandConfigurationBuilder private void InitialzeModel() { - var properties = typeof(TOption).GetProperties(); + var properties = typeof(TCommandOption).GetProperties(); foreach (var propInfo in properties) { @@ -340,16 +340,16 @@ public void RegisterCommand() /// /// public void RegisterCommand() - where TCommand : Command + where TCommand : Command where V : class, new() { var cmdConfigurator = m_containerResolver.Resolve(); - var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_commandOption); + var command = new CommandLineCommand(m_parserOptions, m_resolverFactory, m_containerResolver, m_baseOption); cmdConfigurator.OnConfigure(command); - command.OnExecuting((Action)cmdConfigurator.OnExecute); + command.OnExecuting((Action)cmdConfigurator.OnExecute); m_commands.Add(command); } diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index cbf9bfb..9ea93f0 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using MatthiWare.CommandLine.Abstractions; @@ -78,6 +79,8 @@ private void ParseCommands(IEnumerable cmds) SetArgumentUsed(optionIdx, option); } + + ParseCommands(cmd.Commands.Cast()); } } @@ -122,6 +125,7 @@ private int FindIndex(IArgument model, int startOffset = 0) public bool TryGetValue(IArgument argument, out ArgumentModel model) => resultCache.TryGetValue(argument, out model); + [DebuggerDisplay("{Argument}, used: {Used}, index: {Index}")] private class ArgumentValueHolder { public string Argument { get; set; } From 0134a3cfa6cfb510dfa9210b9609b7677be94f12 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 27 Dec 2018 10:33:07 +0100 Subject: [PATCH 36/41] Improve usage system --- CommandLineParser/CommandLineParser.cs | 9 +++++++-- CommandLineParser/Core/Usage/UsageBuilder.cs | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index be6416d..7a71122 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -34,6 +35,7 @@ public sealed class CommandLineParser : ICommandLineParser, IC private readonly Dictionary m_options; private readonly List m_commands; private readonly CommandLineParserOptions m_parserOptions; + private readonly Process m_currentProcess; /// /// Tool to print usage info. @@ -117,7 +119,10 @@ public CommandLineParser(CommandLineParserOptions parserOptions, IArgumentResolv ArgumentResolverFactory = argumentResolverFactory; ContainerResolver = containerResolver; - Printer = new UsagePrinter(parserOptions, this); + if (string.IsNullOrWhiteSpace(m_parserOptions.AppName)) + m_parserOptions.AppName = Process.GetCurrentProcess().ProcessName; + + Printer = new UsagePrinter(m_parserOptions, this); InitialzeModel(); } @@ -187,7 +192,7 @@ private void AutoPrintUsageAndErrors(ICollection errors, bool argsSup private void PrintErrors(ICollection errors) { foreach (var error in errors) - Console.Error.WriteLine(error); + Console.Error.WriteLine(error.Message); PrintHelp(); } diff --git a/CommandLineParser/Core/Usage/UsageBuilder.cs b/CommandLineParser/Core/Usage/UsageBuilder.cs index dd03a50..6fe7e5e 100644 --- a/CommandLineParser/Core/Usage/UsageBuilder.cs +++ b/CommandLineParser/Core/Usage/UsageBuilder.cs @@ -48,7 +48,7 @@ public void PrintCommand(string name, ICommandLineCommandContainer container) } public void PrintCommandDescription(ICommandLineCommand command) - => stringBuilder.AppendLine($"\t{command.Name}\t{command.Description}"); + => stringBuilder.AppendLine($" {command.Name}\t\t{command.Description}"); public void PrintCommandDescriptions(IEnumerable commands) { @@ -67,10 +67,10 @@ public void PrintOption(ICommandLineOption option) bool hasBoth = hasShort && hasLong; string hasBothSeperator = hasBoth ? "|" : string.Empty; - string shortName = hasShort ? $"{parserOptions.PrefixShortOption}{option.ShortName}" : string.Empty; - string longName = hasLong ? $"{parserOptions.PrefixLongOption}{option.LongName}" : string.Empty; + string shortName = hasShort ? option.ShortName : string.Empty; + string longName = hasLong ? option.LongName : string.Empty; - stringBuilder.AppendLine($"\t{shortName}{hasBothSeperator}{longName}\t{option.Description}"); + stringBuilder.AppendLine($" {shortName}{hasBothSeperator}{longName}\t{option.Description}"); } public void PrintOptions(IEnumerable options) From d2ab84736f9ed92666797cdba3319623e43f38da Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 27 Dec 2018 10:33:37 +0100 Subject: [PATCH 37/41] Imrpove subcommand tests --- CommandLineParser.Tests/Command/SubCommandTests.cs | 4 ++-- CommandLineParser.Tests/Usage/UsagePrinterTests.cs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CommandLineParser.Tests/Command/SubCommandTests.cs b/CommandLineParser.Tests/Command/SubCommandTests.cs index b858886..b228c24 100644 --- a/CommandLineParser.Tests/Command/SubCommandTests.cs +++ b/CommandLineParser.Tests/Command/SubCommandTests.cs @@ -30,8 +30,8 @@ public void TestSubCommandWorksCorrectly() private class CustomInstantiator : IContainerResolver { - private ManualResetEventSlim lock1; - private ManualResetEventSlim lock2; + private readonly ManualResetEventSlim lock1; + private readonly ManualResetEventSlim lock2; public CustomInstantiator(ManualResetEventSlim lock1, ManualResetEventSlim lock2) { diff --git a/CommandLineParser.Tests/Usage/UsagePrinterTests.cs b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs index c3d814d..a516198 100644 --- a/CommandLineParser.Tests/Usage/UsagePrinterTests.cs +++ b/CommandLineParser.Tests/Usage/UsagePrinterTests.cs @@ -21,9 +21,11 @@ private class UsagePrinterGetsCalledOptions public void UsagePrintGetsCalledInCorrectCases(string[] args, bool called) { var printerMock = new Mock(); - var parser = new CommandLineParser(); - parser.Printer = printerMock.Object; + var parser = new CommandLineParser + { + Printer = printerMock.Object + }; parser.Parse(args); From 9d7a3eb8c16089ebe0db47a69fbbdf14b73987da Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Thu, 27 Dec 2018 10:33:44 +0100 Subject: [PATCH 38/41] Update sample app. --- SampleApp/Program.cs | 29 ++++++++++++++--------------- SampleApp/SampleApp.csproj | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/SampleApp/Program.cs b/SampleApp/Program.cs index bb6d17f..57ad5a8 100644 --- a/SampleApp/Program.cs +++ b/SampleApp/Program.cs @@ -1,5 +1,4 @@ using System; - using MatthiWare.CommandLine; namespace SampleApp @@ -8,51 +7,51 @@ class Program { static int Main(string[] args) { - Console.WriteLine($"args: {string.Join(',', args)}"); + Console.WriteLine($"args: {string.Join(", ", args)}"); var parser = new CommandLineParser(); // setup parser.Configure(opt => opt.MyInt) - .Name("-i", "--int") + .Name("i", "int") + .Description("Description for -s option, needs a string.") .Required(); parser.Configure(opt => opt.MyString) - .Name("-s", "--string") + .Name("s", "string") + .Description("Description for -s option, needs a string.") .Required(); parser.Configure(opt => opt.MyBool) - .Name("-b", "--bool") + .Name("b", "bool") + .Description("Description for -s option, needs a string.") .Required(); parser.Configure(opt => opt.MyDouble) - .Name("-d", "--double") + .Name("d", "double") + .Description("Description for -s option, needs a string.") .Required(); var startCmd = parser.AddCommand() - .Name("-s", "--start") + .Name("start") + .Description("Start the server command.") .Required() - .OnExecuting(parsedCmdOption => Console.WriteLine($"Starting server using verbose option: {parsedCmdOption.Verbose}")); + .OnExecuting((opt, parsedCmdOption) => Console.WriteLine($"Starting server using verbose option: {parsedCmdOption.Verbose}")); startCmd.Configure(cmd => cmd.Verbose) // configures the command options can also be done using attributes .Required() - .Name("-v", "--verbose"); + .Description("Verbose output [true/false]") + .Name("v", "verbose"); var result = parser.Parse(args); if (result.HasErrors) { - Console.Error.WriteLine(result.Error); Console.ReadKey(); return -1; } - foreach (var cmdResult in result.CommandResults) - { - cmdResult.ExecuteCommand(); // executes the command handler that is configured above. - } - var options = result.Result; Console.WriteLine($"MyInt: {options.MyInt}"); diff --git a/SampleApp/SampleApp.csproj b/SampleApp/SampleApp.csproj index e844bae..e5b8e5c 100644 --- a/SampleApp/SampleApp.csproj +++ b/SampleApp/SampleApp.csproj @@ -6,7 +6,7 @@ - + From a03c23e772c48f6bd87c258f6a424a5de1142e10 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 09:30:38 +0100 Subject: [PATCH 39/41] Improved command system to use correct configure. --- .../Command/CommandTests.cs | 7 ------ .../Abstractions/Command/Command.cs | 5 ++-- .../Abstractions/Command/Command`TOptions.cs | 24 +++++++++++++++++++ ...nd`.cs => Command`TOptions`TCmdOptions.cs} | 7 ++++-- .../Abstractions/Command/ICommandBuilder'.cs | 8 ------- .../Abstractions/Command/ICommandExecutor.cs | 2 ++ .../Abstractions/ICommandLineParser'.cs | 3 +-- CommandLineParser/CommandLineParser.cs | 3 +-- ...mmandLineCommand`TOption`TCommandOption.cs | 17 +++++++++---- 9 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 CommandLineParser/Abstractions/Command/Command`TOptions.cs rename CommandLineParser/Abstractions/Command/{Command`.cs => Command`TOptions`TCmdOptions.cs} (87%) diff --git a/CommandLineParser.Tests/Command/CommandTests.cs b/CommandLineParser.Tests/Command/CommandTests.cs index 1c33635..2481f90 100644 --- a/CommandLineParser.Tests/Command/CommandTests.cs +++ b/CommandLineParser.Tests/Command/CommandTests.cs @@ -63,13 +63,6 @@ public void AddCommandTypeWithGenericOption() private class MyComand : Command { - public override void OnConfigure(ICommandConfigurationBuilder builder) - { - base.OnConfigure(builder); - - builder.Name("bla").Required(); - } - public override void OnConfigure(ICommandConfigurationBuilder builder) { base.OnConfigure(builder); diff --git a/CommandLineParser/Abstractions/Command/Command.cs b/CommandLineParser/Abstractions/Command/Command.cs index a411de1..75faad6 100644 --- a/CommandLineParser/Abstractions/Command/Command.cs +++ b/CommandLineParser/Abstractions/Command/Command.cs @@ -4,8 +4,7 @@ /// Defines a command /// /// Base options of the command - public abstract class Command - where TOptions : class, new() + public abstract class Command { /// /// Configures the command @@ -18,6 +17,6 @@ public virtual void OnConfigure(ICommandConfigurationBuilder builder) { } /// Executes the command /// /// Parsed options - public virtual void OnExecute(TOptions options) { } + public virtual void OnExecute() { } } } diff --git a/CommandLineParser/Abstractions/Command/Command`TOptions.cs b/CommandLineParser/Abstractions/Command/Command`TOptions.cs new file mode 100644 index 0000000..942d36f --- /dev/null +++ b/CommandLineParser/Abstractions/Command/Command`TOptions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MatthiWare.CommandLine.Abstractions.Command +{ + /// + /// Defines a command + /// + /// Base options of the command + public abstract class Command + : Command + where TOptions : class, new() + { + /// + /// Executes the command + /// + /// Parsed options + public virtual void OnExecute(TOptions options) + { + OnExecute(); + } + } +} diff --git a/CommandLineParser/Abstractions/Command/Command`.cs b/CommandLineParser/Abstractions/Command/Command`TOptions`TCmdOptions.cs similarity index 87% rename from CommandLineParser/Abstractions/Command/Command`.cs rename to CommandLineParser/Abstractions/Command/Command`TOptions`TCmdOptions.cs index 99d8cb1..3bae08b 100644 --- a/CommandLineParser/Abstractions/Command/Command`.cs +++ b/CommandLineParser/Abstractions/Command/Command`TOptions`TCmdOptions.cs @@ -17,7 +17,7 @@ public abstract class Command : /// public virtual void OnConfigure(ICommandConfigurationBuilder builder) { - base.OnConfigure(builder); + OnConfigure((ICommandConfigurationBuilder)builder); } /// @@ -25,6 +25,9 @@ public virtual void OnConfigure(ICommandConfigurationBuilder bu /// /// /// - public virtual void OnExecute(TOptions options, TCommandOptions commandOptions) { } + public virtual void OnExecute(TOptions options, TCommandOptions commandOptions) + { + OnExecute(options); + } } } diff --git a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs index 09e370c..621685b 100644 --- a/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs +++ b/CommandLineParser/Abstractions/Command/ICommandBuilder'.cs @@ -39,13 +39,5 @@ public interface ICommandBuilder : ICommandConfigurationBuilde /// name /// new ICommandBuilder Name(string name); - - /// - /// Configures how the command should be invoked. - /// Default behavior is to auto invoke the command. - /// - /// True if the command executor will be invoked (default), false if you want to invoke manually. - /// - new ICommandBuilder InvokeCommand(bool invoke); } } diff --git a/CommandLineParser/Abstractions/Command/ICommandExecutor.cs b/CommandLineParser/Abstractions/Command/ICommandExecutor.cs index 4a48dec..44aaa14 100644 --- a/CommandLineParser/Abstractions/Command/ICommandExecutor.cs +++ b/CommandLineParser/Abstractions/Command/ICommandExecutor.cs @@ -14,6 +14,8 @@ public interface ICommandExecutor /// ICommandBuilder InvokeCommand(bool invoke); + ICommandBuilder OnExecuting(Action action); + ICommandBuilder OnExecuting(Action action); ICommandBuilder OnExecuting(Action action); diff --git a/CommandLineParser/Abstractions/ICommandLineParser'.cs b/CommandLineParser/Abstractions/ICommandLineParser'.cs index b0f234b..c40ea21 100644 --- a/CommandLineParser/Abstractions/ICommandLineParser'.cs +++ b/CommandLineParser/Abstractions/ICommandLineParser'.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; - using MatthiWare.CommandLine.Abstractions.Command; using MatthiWare.CommandLine.Abstractions.Parsing; using MatthiWare.CommandLine.Abstractions.Usage; @@ -71,7 +70,7 @@ ICommandBuilder AddCommand() /// /// The command void RegisterCommand() - where TCommand : Command; + where TCommand : Command.Command; /// /// Registers a new command diff --git a/CommandLineParser/CommandLineParser.cs b/CommandLineParser/CommandLineParser.cs index 7a71122..8164d87 100644 --- a/CommandLineParser/CommandLineParser.cs +++ b/CommandLineParser/CommandLineParser.cs @@ -35,7 +35,6 @@ public sealed class CommandLineParser : ICommandLineParser, IC private readonly Dictionary m_options; private readonly List m_commands; private readonly CommandLineParserOptions m_parserOptions; - private readonly Process m_currentProcess; /// /// Tool to print usage info. @@ -286,7 +285,7 @@ private void ParseOptions(IList errors, ParseResult result, /// /// Command type, must be inherit public void RegisterCommand() - where TCommand : Command + where TCommand : Command { var cmdConfigurator = ContainerResolver.Resolve(); diff --git a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs index 2f93506..d249d7f 100644 --- a/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs +++ b/CommandLineParser/Core/Command/CommandLineCommand`TOption`TCommandOption.cs @@ -30,7 +30,8 @@ internal class CommandLineCommand : private readonly IContainerResolver m_containerResolver; private readonly CommandLineParserOptions m_parserOptions; - private Action m_executor; + private Action m_executor; + private Action m_executor1; private Action m_executor2; public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResolverFactory resolverFactory, IContainerResolver containerResolver, TOption option) @@ -48,7 +49,8 @@ public CommandLineCommand(CommandLineParserOptions parserOptions, IArgumentResol public override void Execute() { m_executor2?.Invoke(m_baseOption, m_commandOption); - m_executor?.Invoke(m_baseOption); + m_executor1?.Invoke(m_baseOption); + m_executor?.Invoke(); } public IOptionBuilder Configure(Expression> selector) @@ -132,6 +134,13 @@ public ICommandBuilder Required(bool required = true) } public ICommandBuilder OnExecuting(Action action) + { + m_executor1 = action; + + return this; + } + + public ICommandBuilder OnExecuting(Action action) { m_executor = action; @@ -168,7 +177,7 @@ ICommandBuilder ICommandBuilder.Description(string description ICommandBuilder ICommandBuilder.OnExecuting(Action action) { - m_executor = action; + m_executor1 = action; return this; } @@ -329,7 +338,7 @@ public void RegisterCommand() cmdConfigurator.OnConfigure(command); - command.OnExecuting(cmdConfigurator.OnExecute); + command.OnExecuting((Action)cmdConfigurator.OnExecute); m_commands.Add(command); } From 3ad8cede5446905040648abb6ae9f2238ff59d41 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 09:48:44 +0100 Subject: [PATCH 40/41] Fix codefactor issues. --- CommandLineParser/Core/CommandLineOption.cs | 4 ++-- CommandLineParser/Core/Parsing/ArgumentManager.cs | 4 ---- CommandLineParser/Core/Usage/UsageBuilder.cs | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CommandLineParser/Core/CommandLineOption.cs b/CommandLineParser/Core/CommandLineOption.cs index 7508700..a525fe3 100644 --- a/CommandLineParser/Core/CommandLineOption.cs +++ b/CommandLineParser/Core/CommandLineOption.cs @@ -74,8 +74,8 @@ IOptionBuilder IOptionBuilder.Description(string help) IOptionBuilder IOptionBuilder.Name(string shortName, string longName) { - LongName = $"{m_parserOptions.PrefixLongOption}{longName}"; ; - ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; ; + LongName = $"{m_parserOptions.PrefixLongOption}{longName}"; + ShortName = $"{m_parserOptions.PrefixShortOption}{shortName}"; return this; } diff --git a/CommandLineParser/Core/Parsing/ArgumentManager.cs b/CommandLineParser/Core/Parsing/ArgumentManager.cs index 9ea93f0..d045789 100644 --- a/CommandLineParser/Core/Parsing/ArgumentManager.cs +++ b/CommandLineParser/Core/Parsing/ArgumentManager.cs @@ -114,13 +114,9 @@ private int FindIndex(IArgument model, int startOffset = 0) default: return false; } - - }); } - - public void Dispose() => args.Clear(); public bool TryGetValue(IArgument argument, out ArgumentModel model) => resultCache.TryGetValue(argument, out model); diff --git a/CommandLineParser/Core/Usage/UsageBuilder.cs b/CommandLineParser/Core/Usage/UsageBuilder.cs index 6fe7e5e..6dc7414 100644 --- a/CommandLineParser/Core/Usage/UsageBuilder.cs +++ b/CommandLineParser/Core/Usage/UsageBuilder.cs @@ -35,7 +35,6 @@ public void PrintUsage(string name, bool hasOptions, bool hasCommands) .Append(hasOptions ? "[options] " : string.Empty) .Append(hasCommands ? "[commands]" : string.Empty) .AppendLine(); - } public void PrintCommand(string name, ICommandLineCommandContainer container) From 36a029135cd5fdbe8ddf698fdf43c35965a39384 Mon Sep 17 00:00:00 2001 From: Matthias Beerens <3512339+Matthiee@users.noreply.github.com> Date: Fri, 28 Dec 2018 11:55:43 +0100 Subject: [PATCH 41/41] Add comments --- .../Abstractions/Command/ICommandExecutor.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CommandLineParser/Abstractions/Command/ICommandExecutor.cs b/CommandLineParser/Abstractions/Command/ICommandExecutor.cs index 44aaa14..d9becdb 100644 --- a/CommandLineParser/Abstractions/Command/ICommandExecutor.cs +++ b/CommandLineParser/Abstractions/Command/ICommandExecutor.cs @@ -2,6 +2,11 @@ namespace MatthiWare.CommandLine.Abstractions.Command { + /// + /// API for configurion command executions + /// + /// Base option + /// Command option public interface ICommandExecutor where TOption : class where TSource : class, new() @@ -14,10 +19,25 @@ public interface ICommandExecutor /// ICommandBuilder InvokeCommand(bool invoke); + /// + /// Sets the command execute action + /// + /// Action to execute + /// ICommandBuilder OnExecuting(Action action); + /// + /// Sets the command execute action + /// + /// Action to execute + /// ICommandBuilder OnExecuting(Action action); + /// + /// Sets the command execute action + /// + /// Action to execute + /// ICommandBuilder OnExecuting(Action action); } }