From 0889665ae2c76aeaeddde4740c6a0f2728159028 Mon Sep 17 00:00:00 2001 From: Tomas Arci Kouba Date: Tue, 9 Oct 2018 00:10:43 +0200 Subject: [PATCH 001/198] Localizable HelpText with infrastructure backend class and tests --- src/CommandLine/BaseAttribute.cs | 23 ++++++-- src/CommandLine/CommandLine.csproj | 13 ++++- .../LocalizableAttributeProperty.cs | 58 +++++++++++++++++++ .../CommandLine.Tests.Properties.csproj | 32 +++++----- .../CommandLine.Tests.csproj | 37 +++++++----- .../CommandLine.Tests/Fakes/ResourceFakes.cs | 31 ++++++++++ .../Unit/BaseAttributeTests.cs | 39 +++++++++++-- 7 files changed, 193 insertions(+), 40 deletions(-) create mode 100644 src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs create mode 100644 tests/CommandLine.Tests/Fakes/ResourceFakes.cs diff --git a/src/CommandLine/BaseAttribute.cs b/src/CommandLine/BaseAttribute.cs index e390e88c..a2bad2d7 100644 --- a/src/CommandLine/BaseAttribute.cs +++ b/src/CommandLine/BaseAttribute.cs @@ -12,8 +12,9 @@ public abstract class BaseAttribute : Attribute private int min; private int max; private object @default; - private string helpText; + private Infrastructure.LocalizableAttributeProperty helpText; private string metaValue; + private Type resourceType; /// /// Initializes a new instance of the class. @@ -22,8 +23,9 @@ protected internal BaseAttribute() { min = -1; max = -1; - helpText = string.Empty; + helpText = new Infrastructure.LocalizableAttributeProperty(nameof(HelpText)); metaValue = string.Empty; + resourceType = null; } /// @@ -90,7 +92,7 @@ public object Default /// public string HelpText { - get { return helpText; } + get { return helpText.Value; } set { if (value == null) @@ -98,7 +100,7 @@ public string HelpText throw new ArgumentNullException("value"); } - helpText = value; + helpText.Value = value; } } @@ -127,5 +129,18 @@ public bool Hidden get; set; } + + /// + /// Gets or sets the that contains the resources for . + /// + public Type ResourceType + { + get => resourceType; + set + { + resourceType = + helpText.ResourceType = value; + } + } } } diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 8f829c9f..1f179fc5 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -84,6 +84,7 @@ + @@ -169,8 +170,16 @@ - - + + + + ..\..\packages\FSharp.Core\lib\net40\FSharp.Core.dll + True + True + + + + ..\..\packages\FSharp.Core\lib\portable-net45+netcore45\FSharp.Core.dll diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs new file mode 100644 index 00000000..0a462df1 --- /dev/null +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace CommandLine.Infrastructure +{ + internal class LocalizableAttributeProperty + { + private string _propertyName; + private string _value; + private Type _type; + private PropertyInfo _localizationPropertyInfo; + + public LocalizableAttributeProperty(string propertyName) + { + _propertyName = propertyName; + } + + public string Value + { + get => GetLocalizedValue(); + set + { + _localizationPropertyInfo = null; + _value = value; + } + } + + public Type ResourceType + { + set + { + _localizationPropertyInfo = null; + _type = value; + } + } + + private string GetLocalizedValue() + { + if (String.IsNullOrEmpty(_value) || _type == null) + return _value; + if (_localizationPropertyInfo == null) + { + // Static class IsAbstract + if (!_type.IsVisible) + throw new ArgumentException("Invalid resource type", _propertyName); + PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); + if (propertyInfo == null || !propertyInfo.CanRead || propertyInfo.PropertyType != typeof(string)) + throw new ArgumentException("Invalid resource property name", _propertyName); + _localizationPropertyInfo = propertyInfo; + } + return (string)_localizationPropertyInfo.GetValue(null, null); + } + } + +} diff --git a/tests/CommandLine.Tests.Properties/CommandLine.Tests.Properties.csproj b/tests/CommandLine.Tests.Properties/CommandLine.Tests.Properties.csproj index cb2b6b96..9940f4c6 100644 --- a/tests/CommandLine.Tests.Properties/CommandLine.Tests.Properties.csproj +++ b/tests/CommandLine.Tests.Properties/CommandLine.Tests.Properties.csproj @@ -58,12 +58,12 @@ - + <__paket__xunit_runner_visualstudio_props>net20\xunit.runner.visualstudio - + <__paket__xunit_runner_visualstudio_props>portable-net45+win8+wp8+wpa81\xunit.runner.visualstudio @@ -94,7 +94,7 @@ <__paket__xunit_core_props>Xamarin.iOS\xunit.core - + <__paket__xunit_core_props>monoandroid\xunit.core @@ -109,7 +109,7 @@ <__paket__xunit_core_props>portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core - + <__paket__xunit_core_props>portable-win81+wpa81\xunit.core <__paket__xunit_core_targets>portable-win81+wpa81\xunit.core @@ -137,7 +137,7 @@ --> - + ..\..\packages\FluentAssertions\lib\monoandroid\FluentAssertions.Core.dll @@ -169,7 +169,7 @@ - + ..\..\packages\FluentAssertions\lib\net45\FluentAssertions.dll @@ -183,7 +183,7 @@ - + ..\..\packages\FluentAssertions\lib\portable-net40+sl5+win8+wp8+wpa81\FluentAssertions.dll @@ -197,7 +197,7 @@ - + ..\..\packages\FluentAssertions\lib\portable-win81+wpa81\FluentAssertions.dll @@ -255,7 +255,7 @@ - + ..\..\packages\FsCheck\lib\net45\FsCheck.dll @@ -264,7 +264,7 @@ - + ..\..\packages\FsCheck\lib\portable-net45+netcore45\FsCheck.dll @@ -302,7 +302,7 @@ - + ..\..\packages\FSharp.Core\lib\net40\FSharp.Core.dll @@ -311,7 +311,7 @@ - + ..\..\packages\FSharp.Core\lib\portable-net45+netcore45\FSharp.Core.dll @@ -349,7 +349,7 @@ - + ..\..\packages\xunit.abstractions\lib\net35\xunit.abstractions.dll @@ -358,7 +358,7 @@ - + ..\..\packages\xunit.abstractions\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.abstractions.dll @@ -369,7 +369,7 @@ - + ..\..\packages\xunit.assert\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll @@ -380,7 +380,7 @@ - + ..\..\packages\xunit.extensibility.core\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll diff --git a/tests/CommandLine.Tests/CommandLine.Tests.csproj b/tests/CommandLine.Tests/CommandLine.Tests.csproj index f96b4e2e..1d53d813 100644 --- a/tests/CommandLine.Tests/CommandLine.Tests.csproj +++ b/tests/CommandLine.Tests/CommandLine.Tests.csproj @@ -73,6 +73,7 @@ + @@ -143,12 +144,12 @@ - + <__paket__xunit_runner_visualstudio_props>net20\xunit.runner.visualstudio - + <__paket__xunit_runner_visualstudio_props>portable-net45+win8+wp8+wpa81\xunit.runner.visualstudio @@ -179,7 +180,7 @@ <__paket__xunit_core_props>Xamarin.iOS\xunit.core - + <__paket__xunit_core_props>monoandroid\xunit.core @@ -194,7 +195,7 @@ <__paket__xunit_core_props>portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core - + <__paket__xunit_core_props>portable-win81+wpa81\xunit.core <__paket__xunit_core_targets>portable-win81+wpa81\xunit.core @@ -220,7 +221,7 @@ --> - + ..\..\packages\FluentAssertions\lib\monoandroid\FluentAssertions.Core.dll @@ -252,7 +253,7 @@ - + ..\..\packages\FluentAssertions\lib\net45\FluentAssertions.dll @@ -266,7 +267,7 @@ - + ..\..\packages\FluentAssertions\lib\portable-net40+sl5+win8+wp8+wpa81\FluentAssertions.dll @@ -280,7 +281,7 @@ - + ..\..\packages\FluentAssertions\lib\portable-win81+wpa81\FluentAssertions.dll @@ -347,8 +348,16 @@ - - + + + + ..\..\packages\FSharp.Core\lib\net40\FSharp.Core.dll + True + True + + + + ..\..\packages\FSharp.Core\lib\portable-net45+netcore45\FSharp.Core.dll @@ -386,7 +395,7 @@ - + ..\..\packages\xunit.abstractions\lib\net35\xunit.abstractions.dll @@ -395,7 +404,7 @@ - + ..\..\packages\xunit.abstractions\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.abstractions.dll @@ -406,7 +415,7 @@ - + ..\..\packages\xunit.assert\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll @@ -417,7 +426,7 @@ - + ..\..\packages\xunit.extensibility.core\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll diff --git a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs new file mode 100644 index 00000000..52e44b29 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CommandLine.Tests.Fakes +{ + public static class StaticResource + { + public static string HelpText { get => "Localized HelpText"; } + } + + public class NonStaticResource + { + public static string HelpText { get => "Localized HelpText"; } + public static string WriteOnlyText { set => value?.ToString(); } + private static string PrivateHelpText { get => "Localized HelpText"; } + } + + public class NonStaticResource_WithNonStaticProperty + { + public string HelpText { get => "Localized HelpText"; } + } + + internal class InternalResource + { + public static string HelpText { get => "Localized HelpText"; } + } + +} diff --git a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs index 79d20c7b..9a2a3ae0 100644 --- a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace CommandLine.Tests.Unit @@ -20,6 +16,40 @@ public static void Default(object defaultValue) Assert.Equal(defaultValue, baseAttribute.Default); } + [Theory] + [InlineData("", null, "")] + [InlineData("", typeof(Fakes.StaticResource), "")] + [InlineData("Help text", null, "Help text")] + [InlineData("HelpText", typeof(Fakes.StaticResource), "Localized HelpText")] + [InlineData("HelpText", typeof(Fakes.NonStaticResource), "Localized HelpText")] + public static void HelpText(string helpText, Type resourceType, string expected) + { + TestBaseAttribute baseAttribute = new TestBaseAttribute() + { + HelpText = helpText, + ResourceType = resourceType + }; + Assert.Equal(expected, baseAttribute.HelpText); + } + + [Theory] + [InlineData("HelpText", typeof(Fakes.NonStaticResource_WithNonStaticProperty))] + [InlineData("WriteOnlyText", typeof(Fakes.NonStaticResource))] + [InlineData("PrivateOnlyText", typeof(Fakes.NonStaticResource))] + [InlineData("HelpText", typeof(Fakes.InternalResource))] + public void ThrowsHelpText(string helpText, Type resourceType) + { + TestBaseAttribute baseAttribute = new TestBaseAttribute() + { + HelpText = helpText, + ResourceType = resourceType + }; + + // Verify exception + Assert.Throws(() => baseAttribute.HelpText.ToString()); + } + + private class TestBaseAttribute : BaseAttribute { public TestBaseAttribute() @@ -27,5 +57,6 @@ public TestBaseAttribute() // Do nothing } } + } } From 8eeff04e0b5db36027b3e414923c6427bd37c9fb Mon Sep 17 00:00:00 2001 From: Tomas Arci Kouba Date: Tue, 9 Oct 2018 00:27:32 +0200 Subject: [PATCH 002/198] Fixed expression bodied properties --- src/CommandLine/BaseAttribute.cs | 2 +- src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/BaseAttribute.cs b/src/CommandLine/BaseAttribute.cs index a2bad2d7..bf179d78 100644 --- a/src/CommandLine/BaseAttribute.cs +++ b/src/CommandLine/BaseAttribute.cs @@ -135,7 +135,7 @@ public bool Hidden /// public Type ResourceType { - get => resourceType; + get { return resourceType; } set { resourceType = diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index 0a462df1..25c2ae4d 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -20,7 +20,7 @@ public LocalizableAttributeProperty(string propertyName) public string Value { - get => GetLocalizedValue(); + get { return GetLocalizedValue(); } set { _localizationPropertyInfo = null; From 589869dd6273ca545f8c73c5a3ef120f26ccc1fc Mon Sep 17 00:00:00 2001 From: Tomas Arci Kouba Date: Tue, 9 Oct 2018 00:53:23 +0200 Subject: [PATCH 003/198] Fakes without expression bodied properties --- tests/CommandLine.Tests/Fakes/ResourceFakes.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs index 52e44b29..917d51bf 100644 --- a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs +++ b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs @@ -8,24 +8,24 @@ namespace CommandLine.Tests.Fakes { public static class StaticResource { - public static string HelpText { get => "Localized HelpText"; } + public static string HelpText { get { return "Localized HelpText"; } } } public class NonStaticResource { - public static string HelpText { get => "Localized HelpText"; } - public static string WriteOnlyText { set => value?.ToString(); } - private static string PrivateHelpText { get => "Localized HelpText"; } + public static string HelpText { get { return "Localized HelpText"; } } + public static string WriteOnlyText { set { value?.ToString(); } } + private static string PrivateHelpText { get { return "Localized HelpText"; } } } public class NonStaticResource_WithNonStaticProperty { - public string HelpText { get => "Localized HelpText"; } + public string HelpText { get { return "Localized HelpText"; } } } internal class InternalResource { - public static string HelpText { get => "Localized HelpText"; } + public static string HelpText { get { return "Localized HelpText"; } } } } From d45f14fd87d930e48916a35f56bb5095f84fe5af Mon Sep 17 00:00:00 2001 From: Tomas Arci Kouba Date: Tue, 9 Oct 2018 00:55:17 +0200 Subject: [PATCH 004/198] Refaktoring tests, removed initializers --- .../Unit/BaseAttributeTests.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs index 9a2a3ae0..72cf81b4 100644 --- a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs @@ -24,11 +24,10 @@ public static void Default(object defaultValue) [InlineData("HelpText", typeof(Fakes.NonStaticResource), "Localized HelpText")] public static void HelpText(string helpText, Type resourceType, string expected) { - TestBaseAttribute baseAttribute = new TestBaseAttribute() - { - HelpText = helpText, - ResourceType = resourceType - }; + TestBaseAttribute baseAttribute = new TestBaseAttribute(); + baseAttribute.HelpText = helpText; + baseAttribute.ResourceType = resourceType; + Assert.Equal(expected, baseAttribute.HelpText); } @@ -39,11 +38,9 @@ public static void HelpText(string helpText, Type resourceType, string expected) [InlineData("HelpText", typeof(Fakes.InternalResource))] public void ThrowsHelpText(string helpText, Type resourceType) { - TestBaseAttribute baseAttribute = new TestBaseAttribute() - { - HelpText = helpText, - ResourceType = resourceType - }; + TestBaseAttribute baseAttribute = new TestBaseAttribute(); + baseAttribute.HelpText = helpText; + baseAttribute.ResourceType = resourceType; // Verify exception Assert.Throws(() => baseAttribute.HelpText.ToString()); From 0881d1b7e1277859a67fe93350f907c9ec377119 Mon Sep 17 00:00:00 2001 From: Tomas Arci Kouba Date: Wed, 31 Oct 2018 17:38:06 +0100 Subject: [PATCH 005/198] netstandard 1.5 fix --- .../Infrastructure/LocalizableAttributeProperty.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index 25c2ae4d..3e622918 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -43,8 +43,12 @@ private string GetLocalizedValue() return _value; if (_localizationPropertyInfo == null) { - // Static class IsAbstract + // Static class IsAbstract +#if NETSTANDARD1_5 + if (!_type.GetTypeInfo().IsVisible) +#else if (!_type.IsVisible) +#endif throw new ArgumentException("Invalid resource type", _propertyName); PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); if (propertyInfo == null || !propertyInfo.CanRead || propertyInfo.PropertyType != typeof(string)) From 2da82095b5a737af03c6c60c64d21b0459a38635 Mon Sep 17 00:00:00 2001 From: "John D. Kane" Date: Sat, 1 Dec 2018 16:52:53 -0500 Subject: [PATCH 006/198] Remove duplication by delegating calls to CreateDefaultImmutableInstance(Type[]) to the overloaded function CreateDefaultImmutableInstance(Type, Type[]) which has the same code and logic. --- src/CommandLine/Infrastructure/ReflectionHelper.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/CommandLine/Infrastructure/ReflectionHelper.cs b/src/CommandLine/Infrastructure/ReflectionHelper.cs index c054f83c..c7443fec 100644 --- a/src/CommandLine/Infrastructure/ReflectionHelper.cs +++ b/src/CommandLine/Infrastructure/ReflectionHelper.cs @@ -84,11 +84,8 @@ public static bool IsFSharpOptionType(Type type) public static T CreateDefaultImmutableInstance(Type[] constructorTypes) { - var t = typeof(T); - var ctor = t.GetTypeInfo().GetConstructor(constructorTypes); - var values = (from prms in ctor.GetParameters() - select prms.ParameterType.CreateDefaultForImmutable()).ToArray(); - return (T)ctor.Invoke(values); + var t = typeof(T); + return (T)CreateDefaultImmutableInstance(t, constructorTypes); } public static object CreateDefaultImmutableInstance(Type type, Type[] constructorTypes) From a0c130ef31399bf6002f31ecc5ba3c7bc03fdd77 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Thu, 10 Jan 2019 05:11:27 +0200 Subject: [PATCH 007/198] add support for net40 and net45 --- demo/ReadText.Demo/ReadText.Demo.csproj | 50 ++----------------- demo/ReadText.Demo/ReadText.Demo.sln | 15 ++++-- src/CommandLine/CommandLine.csproj | 5 +- .../Infrastructure/ReflectionHelper.cs | 5 ++ src/CommandLine/IntrospectionExtensions.cs | 20 ++++++++ .../CommandLine.Tests.csproj | 7 +++ 6 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 src/CommandLine/IntrospectionExtensions.cs diff --git a/demo/ReadText.Demo/ReadText.Demo.csproj b/demo/ReadText.Demo/ReadText.Demo.csproj index f07c8801..71f16965 100644 --- a/demo/ReadText.Demo/ReadText.Demo.csproj +++ b/demo/ReadText.Demo/ReadText.Demo.csproj @@ -1,52 +1,10 @@ - - + - Debug - AnyCPU - 12.0.0 - 2.0 - {F9D3B288-1A73-4C91-8ED7-11ED1704B817} Exe - ReadText.Demo - ReadText.Demo + net40;net45;net461;netcoreapp2.1;netcoreapp2.0 +false - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - true - - - full - true - bin\Release - prompt - 4 - true - - - - packages\CommandLineParser.2.1.1-beta\lib\net40\CommandLine.dll - - - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - Designer - + - \ No newline at end of file diff --git a/demo/ReadText.Demo/ReadText.Demo.sln b/demo/ReadText.Demo/ReadText.Demo.sln index 1cac367d..cafe0089 100644 --- a/demo/ReadText.Demo/ReadText.Demo.sln +++ b/demo/ReadText.Demo/ReadText.Demo.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.106 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReadText.Demo", "ReadText.Demo.csproj", "{F9D3B288-1A73-4C91-8ED7-11ED1704B817}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadText.Demo", "ReadText.Demo.csproj", "{F9D3B288-1A73-4C91-8ED7-11ED1704B817}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLine", "..\..\src\CommandLine\CommandLine.csproj", "{A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,8 +17,15 @@ Global {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Debug|Any CPU.Build.0 = Debug|Any CPU {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Release|Any CPU.ActiveCfg = Release|Any CPU {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Release|Any CPU.Build.0 = Release|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FF14CDF0-EF51-448B-918C-47CD369568DF} + EndGlobalSection EndGlobal diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index ba3bc243..019b44a3 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -3,7 +3,7 @@ CommandLine Library - netstandard2.0 + netstandard2.0;net40;net45;net461 $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC $(DefineConstants);SKIP_FSHARP true @@ -14,7 +14,7 @@ gsscoder;nemec;ericnewton76 Command Line Parser Library $(VersionSuffix) - 2.3.0 + 2.5.0 Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors @@ -22,6 +22,7 @@ https://github.com/gsscoder/commandline https://raw.githubusercontent.com/commandlineparser/commandline/master/art/CommandLine20.png command line;commandline;argument;option;parser;parsing;library;syntax;shell + true diff --git a/src/CommandLine/Infrastructure/ReflectionHelper.cs b/src/CommandLine/Infrastructure/ReflectionHelper.cs index 52bf8991..a8284180 100644 --- a/src/CommandLine/Infrastructure/ReflectionHelper.cs +++ b/src/CommandLine/Infrastructure/ReflectionHelper.cs @@ -52,7 +52,12 @@ public static Maybe GetAttribute() } var assembly = GetExecutingOrEntryAssembly(); + +#if NET40 + var attributes = assembly.GetCustomAttributes(typeof(TAttribute), false); +#else var attributes = assembly.GetCustomAttributes().ToArray(); +#endif return attributes.Length > 0 ? Maybe.Just((TAttribute)attributes[0]) diff --git a/src/CommandLine/IntrospectionExtensions.cs b/src/CommandLine/IntrospectionExtensions.cs new file mode 100644 index 00000000..8e4c64ea --- /dev/null +++ b/src/CommandLine/IntrospectionExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CommandLine +{ +#if NET40 + + internal static class IntrospectionExtensions + { + public static Type GetTypeInfo(this Type type) + { + return type; + } + } +#endif +} + diff --git a/tests/CommandLine.Tests/CommandLine.Tests.csproj b/tests/CommandLine.Tests/CommandLine.Tests.csproj index 0c28967a..274a3e38 100644 --- a/tests/CommandLine.Tests/CommandLine.Tests.csproj +++ b/tests/CommandLine.Tests/CommandLine.Tests.csproj @@ -7,6 +7,11 @@ $(DefineConstants);SKIP_FSHARP ..\..\CommandLine.snk true + gsscoder;nemec;ericnewton76 + Command Line Parser Library + $(VersionSuffix) + 2.5.0 + Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors @@ -24,6 +29,8 @@ + + \ No newline at end of file From dae308144a0ed37613eea505a6273b3744322f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Wed, 16 Jan 2019 11:06:13 +0100 Subject: [PATCH 008/198] Added async versions of WithParsed extension methods --- src/CommandLine/ParserResultExtensions.cs | 58 ++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index 66201dbb..c1bd35a7 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace CommandLine { @@ -28,6 +29,24 @@ public static ParserResult WithParsed(this ParserResult result, Action< return result; } + /// + /// Executes asynchronously if contains + /// parsed values. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + await action(parsed.Value); + } + return result; + } + + /// /// Executes if parsed values are of . /// @@ -48,6 +67,26 @@ public static ParserResult WithParsed(this ParserResult resul return result; } + /// + /// Executes asynchronously if parsed values are of . + /// + /// Type of the target instance built with parsed value. + /// An verb result instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + if (parsed.Value is T) + { + await action((T)parsed.Value); + } + } + return result; + } + + /// /// Executes if lacks /// parsed values and contains errors. @@ -57,11 +96,28 @@ public static ParserResult WithParsed(this ParserResult resul /// The delegate to execute. /// The same instance. public static ParserResult WithNotParsed(this ParserResult result, Action> action) + { + if (result is NotParsed notParsed) + { + action(notParsed.Errors); + } + return result; + } + + /// + /// Executes asynchronously if lacks + /// parsed values and contains errors. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The delegate to execute. + /// The same instance as a instance. + public static async Task> WithNotParsedAsync(this ParserResult result, Func, Task> action) { var notParsed = result as NotParsed; if (notParsed != null) { - action(notParsed.Errors); + await action(notParsed.Errors); } return result; } From 8f18fcb5da4c323c24d31e396afd516dd50a1f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Wed, 16 Jan 2019 16:12:12 +0100 Subject: [PATCH 009/198] Added async versions of MapResult extension methods --- src/CommandLine/ParserResultExtensions.cs | 1508 +++++++++++++++++++-- 1 file changed, 1376 insertions(+), 132 deletions(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index c1bd35a7..d551bc29 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -143,6 +143,26 @@ public static TResult MapResult(this ParserResult res return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// Type of the target instance built with parsed value. + /// The type of the new value. + /// An instance. + /// Async lambda executed on successful parsing. + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + return parsedFunc(parsed.Value); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -168,6 +188,30 @@ public static TResult MapResult(this ParserResult result, return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -200,6 +244,37 @@ public static TResult MapResult(this ParserResult resul return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) + { + return parsedFunc2((T2)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -239,6 +314,44 @@ public static TResult MapResult(this ParserResult r return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -285,6 +398,51 @@ public static TResult MapResult(this ParserResult)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -338,6 +496,58 @@ public static TResult MapResult(this ParserResult)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -398,6 +608,65 @@ public static TResult MapResult(this ParserResu return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -465,6 +734,72 @@ public static TResult MapResult(this Parser return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -540,7 +875,7 @@ public static TResult MapResult(this Pa } /// - /// Provides a way to transform result data into another value. + /// Provides a way to asynchronously transform result data into another value. /// /// First verb type. /// Second verb type. @@ -550,70 +885,62 @@ public static TResult MapResult(this Pa /// Sixth verb type. /// Seventh verb type. /// Eighth verb type. - /// Ninth verb type. /// /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func, TResult> notParsedFunc) + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func, Task> notParsedFunc) { - var parsed = result as Parsed; - if (parsed != null) + if (result is Parsed parsed) { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) + if (parsed.Value is T1 t1) { - return parsedFunc2((T2)parsed.Value); + return parsedFunc1(t1); } - if (parsed.Value is T3) + if (parsed.Value is T2 t2) { - return parsedFunc3((T3)parsed.Value); + return parsedFunc2(t2); } - if (parsed.Value is T4) + if (parsed.Value is T3 t3) { - return parsedFunc4((T4)parsed.Value); + return parsedFunc3(t3); } - if (parsed.Value is T5) + if (parsed.Value is T4 t4) { - return parsedFunc5((T5)parsed.Value); + return parsedFunc4(t4); } - if (parsed.Value is T6) + if (parsed.Value is T5 t5) { - return parsedFunc6((T6)parsed.Value); + return parsedFunc5(t5); } - if (parsed.Value is T7) + if (parsed.Value is T6 t6) { - return parsedFunc7((T7)parsed.Value); + return parsedFunc6(t6); } - if (parsed.Value is T8) + if (parsed.Value is T7 t7) { - return parsedFunc8((T8)parsed.Value); + return parsedFunc7(t7); } - if (parsed.Value is T9) + if (parsed.Value is T8 t8) { - return parsedFunc9((T9)parsed.Value); + return parsedFunc8(t8); } throw new InvalidOperationException(); } @@ -632,7 +959,6 @@ public static TResult MapResult(thi /// Seventh verb type. /// Eighth verb type. /// Ninth verb type. - /// Tenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -644,10 +970,9 @@ public static TResult MapResult(thi /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -657,7 +982,6 @@ public static TResult MapResult parsedFunc7, Func parsedFunc8, Func parsedFunc9, - Func parsedFunc10, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -699,17 +1023,13 @@ public static TResult MapResult)result).Errors); } /// - /// Provides a way to transform result data into another value. + /// Provides a way to asynchronously transform result data into another value. /// /// First verb type. /// Second verb type. @@ -720,14 +1040,269 @@ public static TResult MapResultSeventh verb type. /// Eighth verb type. /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. /// /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// + /// The result in verb scenario. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. + /// The new value. + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func parsedFunc10, + Func, TResult> notParsedFunc) + { + var parsed = result as Parsed; + if (parsed != null) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) + { + return parsedFunc2((T2)parsed.Value); + } + if (parsed.Value is T3) + { + return parsedFunc3((T3)parsed.Value); + } + if (parsed.Value is T4) + { + return parsedFunc4((T4)parsed.Value); + } + if (parsed.Value is T5) + { + return parsedFunc5((T5)parsed.Value); + } + if (parsed.Value is T6) + { + return parsedFunc6((T6)parsed.Value); + } + if (parsed.Value is T7) + { + return parsedFunc7((T7)parsed.Value); + } + if (parsed.Value is T8) + { + return parsedFunc8((T8)parsed.Value); + } + if (parsed.Value is T9) + { + return parsedFunc9((T9)parsed.Value); + } + if (parsed.Value is T10) + { + return parsedFunc10((T10)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// + /// The result in verb scenario. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . @@ -774,29 +1349,326 @@ public static TResult MapResult)result).Errors); + } + + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// + /// The result in verb scenario. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. + /// The new value. + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func parsedFunc10, + Func parsedFunc11, + Func parsedFunc12, + Func, TResult> notParsedFunc) + { + var parsed = result as Parsed; + if (parsed != null) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) + { + return parsedFunc2((T2)parsed.Value); + } + if (parsed.Value is T3) + { + return parsedFunc3((T3)parsed.Value); + } + if (parsed.Value is T4) + { + return parsedFunc4((T4)parsed.Value); + } + if (parsed.Value is T5) + { + return parsedFunc5((T5)parsed.Value); + } + if (parsed.Value is T6) + { + return parsedFunc6((T6)parsed.Value); + } + if (parsed.Value is T7) + { + return parsedFunc7((T7)parsed.Value); + } + if (parsed.Value is T8) + { + return parsedFunc8((T8)parsed.Value); + } + if (parsed.Value is T9) + { + return parsedFunc9((T9)parsed.Value); + } + if (parsed.Value is T10) + { + return parsedFunc10((T10)parsed.Value); + } + if (parsed.Value is T11) + { + return parsedFunc11((T11)parsed.Value); + } + if (parsed.Value is T12) + { + return parsedFunc12((T12)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) { - return parsedFunc6((T6)parsed.Value); + return parsedFunc7(t7); } - if (parsed.Value is T7) + if (parsed.Value is T8 t8) { - return parsedFunc7((T7)parsed.Value); + return parsedFunc8(t8); } - if (parsed.Value is T8) + if (parsed.Value is T9 t9) { - return parsedFunc8((T8)parsed.Value); + return parsedFunc9(t9); } - if (parsed.Value is T9) + if (parsed.Value is T10 t10) { - return parsedFunc9((T9)parsed.Value); + return parsedFunc10(t10); } - if (parsed.Value is T10) + if (parsed.Value is T11 t11) { - return parsedFunc10((T10)parsed.Value); + return parsedFunc11(t11); } - if (parsed.Value is T11) + if (parsed.Value is T12 t12) { - return parsedFunc11((T11)parsed.Value); + return parsedFunc12(t12); } throw new InvalidOperationException(); } @@ -818,6 +1690,7 @@ public static TResult MapResultTenth verb type. /// Eleventh verb type. /// Twelfth verb type. + /// Thirteenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -832,9 +1705,10 @@ public static TResult MapResultLambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -847,6 +1721,7 @@ public static TResult MapResult parsedFunc10, Func parsedFunc11, Func parsedFunc12, + Func parsedFunc13, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -900,13 +1775,17 @@ public static TResult MapResult)result).Errors); } /// - /// Provides a way to transform result data into another value. + /// Provides a way to asynchronously result data into another value. /// /// First verb type. /// Second verb type. @@ -923,97 +1802,95 @@ public static TResult MapResultThirteenth verb type. /// /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func parsedFunc10, - Func parsedFunc11, - Func parsedFunc12, - Func parsedFunc13, - Func, TResult> notParsedFunc) + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func, Task> notParsedFunc) { - var parsed = result as Parsed; - if (parsed != null) + if (result is Parsed parsed) { - if (parsed.Value is T1) + if (parsed.Value is T1 t1) { - return parsedFunc1((T1)parsed.Value); + return parsedFunc1(t1); } - if (parsed.Value is T2) + if (parsed.Value is T2 t2) { - return parsedFunc2((T2)parsed.Value); + return parsedFunc2(t2); } - if (parsed.Value is T3) + if (parsed.Value is T3 t3) { - return parsedFunc3((T3)parsed.Value); + return parsedFunc3(t3); } - if (parsed.Value is T4) + if (parsed.Value is T4 t4) { - return parsedFunc4((T4)parsed.Value); + return parsedFunc4(t4); } - if (parsed.Value is T5) + if (parsed.Value is T5 t5) { - return parsedFunc5((T5)parsed.Value); + return parsedFunc5(t5); } - if (parsed.Value is T6) + if (parsed.Value is T6 t6) { - return parsedFunc6((T6)parsed.Value); + return parsedFunc6(t6); } - if (parsed.Value is T7) + if (parsed.Value is T7 t7) { - return parsedFunc7((T7)parsed.Value); + return parsedFunc7(t7); } - if (parsed.Value is T8) + if (parsed.Value is T8 t8) { - return parsedFunc8((T8)parsed.Value); + return parsedFunc8(t8); } - if (parsed.Value is T9) + if (parsed.Value is T9 t9) { - return parsedFunc9((T9)parsed.Value); + return parsedFunc9(t9); } - if (parsed.Value is T10) + if (parsed.Value is T10 t10) { - return parsedFunc10((T10)parsed.Value); + return parsedFunc10(t10); } - if (parsed.Value is T11) + if (parsed.Value is T11 t11) { - return parsedFunc11((T11)parsed.Value); + return parsedFunc11(t11); } - if (parsed.Value is T12) + if (parsed.Value is T12 t12) { - return parsedFunc12((T12)parsed.Value); + return parsedFunc12(t12); } - if (parsed.Value is T13) + if (parsed.Value is T13 t13) { - return parsedFunc13((T13)parsed.Value); + return parsedFunc13(t13); } throw new InvalidOperationException(); } return notParsedFunc(((NotParsed)result).Errors); } - /// /// Provides a way to transform result data into another value. /// @@ -1130,6 +2007,121 @@ public static TResult MapResult)result).Errors); } + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// Thirteenth verb type. + /// Fourteenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func> parsedFunc14, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + if (parsed.Value is T12 t12) + { + return parsedFunc12(t12); + } + if (parsed.Value is T13 t13) + { + return parsedFunc13(t13); + } + if (parsed.Value is T14 t14) + { + return parsedFunc14(t14); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -1253,6 +2245,128 @@ public static TResult MapResult)result).Errors); } + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// Thirteenth verb type. + /// Fourteenth verb type. + /// Fifteenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func> parsedFunc14, + Func> parsedFunc15, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + if (parsed.Value is T12 t12) + { + return parsedFunc12(t12); + } + if (parsed.Value is T13 t13) + { + return parsedFunc13(t13); + } + if (parsed.Value is T14 t14) + { + return parsedFunc14(t14); + } + if (parsed.Value is T15 t15) + { + return parsedFunc15(t15); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -1382,5 +2496,135 @@ public static TResult MapResult)result).Errors); } + + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// Thirteenth verb type. + /// Fourteenth verb type. + /// Fifteenth verb type. + /// Sixteenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func> parsedFunc14, + Func> parsedFunc15, + Func> parsedFunc16, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + if (parsed.Value is T12 t12) + { + return parsedFunc12(t12); + } + if (parsed.Value is T13 t13) + { + return parsedFunc13(t13); + } + if (parsed.Value is T14 t14) + { + return parsedFunc14(t14); + } + if (parsed.Value is T15 t15) + { + return parsedFunc15(t15); + } + if (parsed.Value is T16 t16) + { + return parsedFunc16(t16); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + } } From 04f7505d33a51fe3019e4b7581ddf87148945444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Wed, 16 Jan 2019 16:45:22 +0100 Subject: [PATCH 010/198] Added unit tests for async extension methods --- .../Unit/ParserResultExtensionsTests.cs | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs index 97755940..f992996a 100644 --- a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs @@ -6,6 +6,7 @@ using CommandLine.Tests.Fakes; using Xunit; using FluentAssertions; +using System.Threading.Tasks; namespace CommandLine.Tests.Unit { @@ -21,6 +22,16 @@ public static void Invoke_parsed_lambda_when_parsed() "value".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_parsed_lambda_when_parsedAsync() + { + var expected = string.Empty; + await Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }) + .WithParsedAsync(opts => Task.Run(() => expected = opts.StringValue)); + + "value".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_parsed_lambda_when_parsed_for_verbs() { @@ -34,6 +45,20 @@ public static void Invoke_parsed_lambda_when_parsed_for_verbs() "https://value.org/user/file.git".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_parsed_lambda_when_parsed_for_verbsAsync() + { + var expected = string.Empty; + var parsedArguments = Parser.Default.ParseArguments( + new[] { "clone", "https://value.org/user/file.git" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong1")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong2")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.Urls.First())); + + "https://value.org/user/file.git".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_not_parsed_lambda_when_not_parsed() { @@ -44,6 +69,16 @@ public static void Invoke_not_parsed_lambda_when_not_parsed() "changed".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_not_parsed_lambda_when_not_parsedAsync() + { + var expected = "a default"; + await Parser.Default.ParseArguments(new[] { "-i", "aaa" }) + .WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "changed".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_not_parsed_lambda_when_parsed_for_verbs() { @@ -57,6 +92,20 @@ public static void Invoke_not_parsed_lambda_when_parsed_for_verbs() "changed".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_not_parsed_lambda_when_parsed_for_verbsAsync() + { + var expected = "a default"; + var parsedArguments = Parser.Default.ParseArguments(new[] { "undefined", "-xyz" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong1")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong2")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong3")); + await parsedArguments.WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "changed".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_proper_lambda_when_parsed() { @@ -68,6 +117,18 @@ public static void Invoke_proper_lambda_when_parsed() "value".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_proper_lambda_when_parsedAsync() + { + var expected = string.Empty; + var parsedArguments = Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.StringValue)); + await parsedArguments.WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "value".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_proper_lambda_when_not_parsed() { @@ -79,6 +140,18 @@ public static void Invoke_proper_lambda_when_not_parsed() "changed".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_proper_lambda_when_not_parsedAsync() + { + var expected = "a default"; + var parsedArguments = Parser.Default.ParseArguments(new[] { "-i", "aaa" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.StringValue)); + await parsedArguments.WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "changed".Should().BeEquivalentTo(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code() { @@ -88,6 +161,15 @@ public static void Turn_sucessful_parsing_into_exit_code() 0.Should().Be(expected); } + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_codeAsync() + { + var expected = await Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }) + .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); + + 0.Should().Be(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_verbs() { @@ -102,6 +184,20 @@ public static void Turn_sucessful_parsing_into_exit_code_for_verbs() 2.Should().Be(expected); } + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_code_for_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "clone", "https://value.org/user/file.git" }) + .MapResultAsync( + (Add_Verb opts) => Task.FromResult(0), + (Commit_Verb opts) => Task.FromResult(1), + (Clone_Verb opts) => Task.FromResult(2), + errs => Task.FromResult(3)); + + 2.Should().Be(expected); + } + [Fact] public static void Turn_failed_parsing_into_exit_code() { @@ -111,6 +207,15 @@ public static void Turn_failed_parsing_into_exit_code() (-1).Should().Be(expected); } + [Fact] + public static async Task Turn_failed_parsing_into_exit_codeAsync() + { + var expected = await Parser.Default.ParseArguments(new[] { "-i", "aaa" }) + .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); + + (-1).Should().Be(expected); + } + [Fact] public static void Turn_failed_parsing_into_exit_code_for_verbs() { @@ -125,6 +230,20 @@ public static void Turn_failed_parsing_into_exit_code_for_verbs() 3.Should().Be(expected); } + [Fact] + public static async Task Turn_failed_parsing_into_exit_code_for_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "undefined", "-xyz" }) + .MapResultAsync( + (Add_Verb opts) => Task.FromResult(0), + (Commit_Verb opts) => Task.FromResult(1), + (Clone_Verb opts) => Task.FromResult(2), + errs => Task.FromResult(3)); + + 3.Should().Be(expected); + } + [Fact] public static void Invoke_parsed_lambda_when_parsed_for_base_verbs() { @@ -139,6 +258,21 @@ public static void Invoke_parsed_lambda_when_parsed_for_base_verbs() "dummy.bin".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_parsed_lambda_when_parsed_for_base_verbsAsync() + { + var expected = string.Empty; + var parsedArguments = Parser.Default.ParseArguments( + new[] { "derivedadd", "dummy.bin" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong1")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong2")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong3")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.FileName)); + + "dummy.bin".Should().BeEquivalentTo(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_single_base_verbs() { @@ -151,6 +285,18 @@ public static void Turn_sucessful_parsing_into_exit_code_for_single_base_verbs() 1.Should().Be(expected); } + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_code_for_single_base_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "derivedadd", "dummy.bin" }) + .MapResultAsync( + (Base_Class_For_Verb opts) => Task.FromResult(1), + errs => Task.FromResult(2)); + + 1.Should().Be(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs() { @@ -166,5 +312,21 @@ public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs 4.Should().Be(expected); } + + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "derivedadd", "dummy.bin" }) + .MapResultAsync( + (Add_Verb opts) => Task.FromResult(0), + (Commit_Verb opts) => Task.FromResult(1), + (Clone_Verb opts) => Task.FromResult(2), + (Base_Class_For_Verb opts) => Task.FromResult(4), + (Derived_Verb opts) => Task.FromResult(3), + errs => Task.FromResult(5)); + + 4.Should().Be(expected); + } } } From be60128a2128a341bad5cc4ddc3b7e7e324752d8 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Thu, 17 Jan 2019 22:12:51 -0500 Subject: [PATCH 011/198] fixed #391 --- CommandLine.sln | 9 +++++++-- src/CommandLine/CommandLine.csproj | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CommandLine.sln b/CommandLine.sln index 06356a14..102837f1 100644 --- a/CommandLine.sln +++ b/CommandLine.sln @@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27703.2042 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine", "src\CommandLine\CommandLine.csproj", "{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLine", "src\CommandLine\CommandLine.csproj", "{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine.Tests", "tests\CommandLine.Tests\CommandLine.Tests.csproj", "{0A15C4D2-B3E9-43AB-8155-1B39F7AC8A5E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLine.Tests", "tests\CommandLine.Tests\CommandLine.Tests.csproj", "{0A15C4D2-B3E9-43AB-8155-1B39F7AC8A5E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1361E8B1-D0E1-493E-B8C1-7380A7B7C472}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,6 +27,9 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0A15C4D2-B3E9-43AB-8155-1B39F7AC8A5E} = {1361E8B1-D0E1-493E-B8C1-7380A7B7C472} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5B5A476C-82FB-49FB-B592-5224D9005186} EndGlobalSection diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index ba3bc243..08b5b983 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -19,7 +19,7 @@ Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors https://raw.githubusercontent.com/gsscoder/commandline/master/doc/LICENSE - https://github.com/gsscoder/commandline + https://github.com/commandlineparser/commandline https://raw.githubusercontent.com/commandlineparser/commandline/master/art/CommandLine20.png command line;commandline;argument;option;parser;parsing;library;syntax;shell From ebc6f3716519ebaa5337794e52ea13bfdc24b1af Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Fri, 18 Jan 2019 00:32:53 -0500 Subject: [PATCH 012/198] readability campaign: shift anon functions into actual named functions --- src/CommandLine/Core/InstanceBuilder.cs | 109 +++++++++++++------ src/CommandLine/Core/ReflectionExtensions.cs | 13 ++- 2 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 82c29ea8..a1fdd9f2 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -82,44 +82,19 @@ public static ParserResult Build( var specPropsWithValue = optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memorize(); - var setPropertyErrors = new List(); + var setPropertyErrors = new List(); - Func buildMutable = () => + //build the instance, determining if the type is mutable or not. + T instance; + if(typeInfo.IsMutable() == true) { - var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance()); - setPropertyErrors.AddRange(mutable.SetProperties(specPropsWithValue, sp => sp.Value.IsJust(), sp => sp.Value.FromJustOrFail())); - setPropertyErrors.AddRange(mutable.SetProperties( - specPropsWithValue, - sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(), - sp => sp.Specification.DefaultValue.FromJustOrFail())); - setPropertyErrors.AddRange(mutable.SetProperties( - specPropsWithValue, - sp => - sp.Value.IsNothing() && sp.Specification.TargetType == TargetType.Sequence - && sp.Specification.DefaultValue.MatchNothing(), - sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray())); - return mutable; - }; - - Func buildImmutable = () => + instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors); + } + else { - var ctor = typeInfo.GetTypeInfo().GetConstructor((from sp in specProps select sp.Property.PropertyType).ToArray()); - var values = (from prms in ctor.GetParameters() - join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv - from sp in spv.DefaultIfEmpty() - select - sp == null - ? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase)) - .Property.PropertyType.GetDefaultValue() - : sp.Value.GetValueOrDefault( - sp.Specification.DefaultValue.GetValueOrDefault( - sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray(); - var immutable = (T)ctor.Invoke(values); - return immutable; - }; - - var instance = typeInfo.IsMutable() ? buildMutable() : buildImmutable(); - + instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors); + } + var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens)); var allErrors = @@ -150,5 +125,67 @@ from sp in spv.DefaultIfEmpty() return result; } + + private static T BuildMutable(Maybe> factory, IEnumerable specPropsWithValue, List setPropertyErrors ) + { + var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance()); + + setPropertyErrors.AddRange( + mutable.SetProperties( + specPropsWithValue, + sp => sp.Value.IsJust(), + sp => sp.Value.FromJustOrFail() + ) + ); + + setPropertyErrors.AddRange( + mutable.SetProperties( + specPropsWithValue, + sp => sp.Value.IsNothing() && sp.Specification.DefaultValue.IsJust(), + sp => sp.Specification.DefaultValue.FromJustOrFail() + ) + ); + + setPropertyErrors.AddRange( + mutable.SetProperties( + specPropsWithValue, + sp => sp.Value.IsNothing() + && sp.Specification.TargetType == TargetType.Sequence + && sp.Specification.DefaultValue.MatchNothing(), + sp => sp.Property.PropertyType.GetTypeInfo().GetGenericArguments().Single().CreateEmptyArray() + ) + ); + + return mutable; + } + + private static T BuildImmutable(Type typeInfo, Maybe> factory, IEnumerable specProps, IEnumerable specPropsWithValue, List setPropertyErrors) + { + var ctor = typeInfo.GetTypeInfo().GetConstructor( + specProps.Select(sp => sp.Property.PropertyType).ToArray() + ); + + if(ctor == null) + { + throw new InvalidOperationException($"Type appears to be immutable, but no constructor found for type {typeInfo.FullName} to accept values."); + } + + var values = + (from prms in ctor.GetParameters() + join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv + from sp in spv.DefaultIfEmpty() + select + sp == null + ? specProps.First(s => String.Equals(s.Property.Name, prms.Name, StringComparison.CurrentCultureIgnoreCase)) + .Property.PropertyType.GetDefaultValue() + : sp.Value.GetValueOrDefault( + sp.Specification.DefaultValue.GetValueOrDefault( + sp.Specification.ConversionType.CreateDefaultForImmutable()))).ToArray(); + + var immutable = (T)ctor.Invoke(values); + + return immutable; + } + } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index c82ebefc..cd75e07e 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -122,12 +122,13 @@ public static object GetDefaultValue(this Type type) public static bool IsMutable(this Type type) { - Func isMutable = () => { - var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite); - var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any(); - return props || fields; - }; - return type != typeof(object) ? isMutable() : true; + if(type == typeof(object)) + return true; + + var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite); + var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any(); + + return props || fields; } public static object CreateDefaultForImmutable(this Type type) From d9efdac38848e007e93da76305b322d03875193e Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Fri, 18 Jan 2019 00:34:03 -0500 Subject: [PATCH 013/198] added test for invalid situation where options class has properties with no setter and no valid constructor --- .../Unit/Core/InstanceBuilderTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index fe2d9ec6..c150d23a 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1133,6 +1133,21 @@ public void Parse_TimeSpan() // Teardown } + [Fact] + public void OptionClass_IsImmutable_HasNoCtor() + { + Action act = () => InvokeBuild(new string[] { "Test" }, false, false); + + act.Should().Throw(); + } + + private class ValueWithNoSetterOptions + { + [Value(0, MetaName = "Test", Default = 0)] + public int TestValue { get; } + } + + public static IEnumerable RequiredValueStringData { get From 5f4c11d4ceea14040950b8edb0ae408d915f9d3e Mon Sep 17 00:00:00 2001 From: Sascha Falk Date: Fri, 25 Jan 2019 07:16:48 +0100 Subject: [PATCH 014/198] Added explicit support for .NET 4.6.1 and .NET Core 2.0. Eliminates the need for interfacing libraries reducing the footprint of an application built with the CommandLine library. --- src/CommandLine/CommandLine.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 46cd3c1c..cd16cc54 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -3,7 +3,7 @@ CommandLine Library - netstandard2.0 + netstandard2.0; net461; netcoreapp2.0 $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC $(DefineConstants);SKIP_FSHARP true From cdfe65538cb70ac215ddc917a84d0c28375e68cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Fri, 25 Jan 2019 12:51:05 +0100 Subject: [PATCH 015/198] Reset attribute overrides when the test is disposed This unclutters all tests that need to reset the attribute overrides --- .../Unit/Text/HelpTextTests.cs | 121 ++++++++---------- 1 file changed, 51 insertions(+), 70 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 0c6e5022..6ef21ac0 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -2,13 +2,10 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Reflection; -using CommandLine.Core; using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; -using CommandLine.Tests.Unit.Infrastructure; using CommandLine.Text; using FluentAssertions; using Xunit; @@ -16,8 +13,13 @@ namespace CommandLine.Tests.Unit.Text { - public class HelpTextTests + public class HelpTextTests : IDisposable { + public void Dispose() + { + ReflectionHelper.SetAttributeOverride(null); + } + [Fact] public void Create_empty_instance() { @@ -573,91 +575,70 @@ public void Default_set_to_sequence_should_be_properly_printed() [Fact] public void AutoBuild_when_no_assembly_attributes() { - try - { - string expectedCopyright = "Copyright (C) 1 author"; + string expectedCopyright = "Copyright (C) 1 author"; - ReflectionHelper.SetAttributeOverride(new Attribute[0]); + ReflectionHelper.SetAttributeOverride(new Attribute[0]); - ParserResult fakeResult = new NotParsed( - TypeInfo.Create(typeof (Simple_Options)), new Error[0]); - bool onErrorCalled = false; - HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => - { - onErrorCalled = true; - return ht; - }, ex => ex); - - onErrorCalled.Should().BeTrue(); - actualResult.Copyright.Should().Be(expectedCopyright); - } - finally + ParserResult fakeResult = new NotParsed( + TypeInfo.Create(typeof (Simple_Options)), new Error[0]); + bool onErrorCalled = false; + HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => { - ReflectionHelper.SetAttributeOverride(null); - } + onErrorCalled = true; + return ht; + }, ex => ex); + + onErrorCalled.Should().BeTrue(); + actualResult.Copyright.Should().Be(expectedCopyright); } [Fact] public void AutoBuild_with_assembly_title_and_version_attributes_only() { - try - { - string expectedTitle = "Title"; - string expectedVersion = "1.2.3.4"; + string expectedTitle = "Title"; + string expectedVersion = "1.2.3.4"; - ReflectionHelper.SetAttributeOverride(new Attribute[] - { - new AssemblyTitleAttribute(expectedTitle), - new AssemblyInformationalVersionAttribute(expectedVersion) - }); - - ParserResult fakeResult = new NotParsed( - TypeInfo.Create(typeof (Simple_Options)), new Error[0]); - bool onErrorCalled = false; - HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => - { - onErrorCalled = true; - return ht; - }, ex => ex); - - onErrorCalled.Should().BeTrue(); - actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion)); - } - finally + ReflectionHelper.SetAttributeOverride(new Attribute[] + { + new AssemblyTitleAttribute(expectedTitle), + new AssemblyInformationalVersionAttribute(expectedVersion) + }); + + ParserResult fakeResult = new NotParsed( + TypeInfo.Create(typeof (Simple_Options)), new Error[0]); + bool onErrorCalled = false; + HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => { - ReflectionHelper.SetAttributeOverride(null); - } + onErrorCalled = true; + return ht; + }, ex => ex); + + onErrorCalled.Should().BeTrue(); + actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion)); } [Fact] public void AutoBuild_with_assembly_company_attribute_only() { - try - { - string expectedCompany = "Company"; + string expectedCompany = "Company"; - ReflectionHelper.SetAttributeOverride(new Attribute[] - { - new AssemblyCompanyAttribute(expectedCompany) - }); + ReflectionHelper.SetAttributeOverride(new Attribute[] + { + new AssemblyCompanyAttribute(expectedCompany) + }); - ParserResult fakeResult = new NotParsed( - TypeInfo.Create(typeof (Simple_Options)), new Error[0]); - bool onErrorCalled = false; - HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => - { - onErrorCalled = true; - return ht; - }, ex => ex); - - onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic - actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany)); - } - finally + ParserResult fakeResult = new NotParsed( + TypeInfo.Create(typeof (Simple_Options)), new Error[0]); + bool onErrorCalled = false; + HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => { - ReflectionHelper.SetAttributeOverride(null); - } + onErrorCalled = true; + return ht; + }, ex => ex); + + onErrorCalled.Should().BeFalse(); // Other attributes have fallback logic + actualResult.Copyright.Should().Be(string.Format("Copyright (C) {0} {1}", DateTime.Now.Year, expectedCompany)); } [Fact] From 678528659cb2f33137b9b7c1c0457024d8d22341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20M=C3=BCller?= Date: Sat, 26 Jan 2019 12:26:22 +0100 Subject: [PATCH 016/198] Adjusts link to 'verb commands' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f11109e..91727097 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Supports `--help`, `--version`, `version` and `help [verb]` by default. - Map to sequences (via `IEnumerable` and similar) and scalar types, including Enums and `Nullable`. - You can also map to every type with a constructor that accepts a string (like `System.Uri`). -- Define [verb commands](https://github.com/commandlineparser/commandline/wiki#verbs) similar to `git commit -a`. +- Define [verb commands](https://github.com/commandlineparser/commandline/wiki/Verbs) similar to `git commit -a`. - Unparsing support: `CommandLine.Parser.Default.FormatCommandLine(T options)`. - CommandLineParser.FSharp package is F#-friendly with support for `option<'a>`, see [demo](https://github.com/commandlineparser/commandline/blob/master/demo/fsharp-demo.fsx). _NOTE: This is a separate NuGet package._ - Most of features applies with a [CoC](http://en.wikipedia.org/wiki/Convention_over_configuration) philosophy. From 0dac5acb2644db7e444e67e82d4ebacfc570192f Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Fri, 1 Feb 2019 12:46:36 -0500 Subject: [PATCH 017/198] add specific image to make sure Visual Studio 2017 environment used --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index a3b73034..c7979afb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,8 @@ #version should be only changed with RELEASE eminent, see RELEASE.md version: 2.4.{build} +image: Visual Studio 2017 + clone_depth: 1 pull_requests: do_not_increment_build_number: true From 6d2dc12f0c31600ba7c00a7385982d9487b63f79 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Fri, 1 Feb 2019 12:54:02 -0500 Subject: [PATCH 018/198] fixed bad merge of Error.cs in build --- src/CommandLine/Error.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index b1bc3e94..90ee26d8 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -64,7 +64,8 @@ public enum ErrorType /// /// Value of type. /// - SetValueExceptionError + SetValueExceptionError, + VersionRequestedError, /// /// Value of type. From 030eb8d431c0c56b62277c654d0951308e873960 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Fri, 1 Feb 2019 13:01:01 -0500 Subject: [PATCH 019/198] @moh-hassan helped fix FSharp side of build --- src/CommandLine/CommandLine.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 46e6d82d..3764bcb6 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -14,7 +14,7 @@ gsscoder;nemec;ericnewton76 Command Line Parser Library $(VersionSuffix) - 2.4.0 + 0.0.0 Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors @@ -35,7 +35,7 @@ - + From 371b61a267418fb6a94c924100ac007c6ebf5fe9 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Fri, 1 Feb 2019 20:46:07 +0200 Subject: [PATCH 020/198] fix Fsharp in net40 --- src/CommandLine/CommandLine.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 019b44a3..94c76586 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -3,7 +3,7 @@ CommandLine Library - netstandard2.0;net40;net45;net461 + netstandard2.0;net40;net45;net461;netcoreapp2.0 $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC $(DefineConstants);SKIP_FSHARP true @@ -14,7 +14,7 @@ gsscoder;nemec;ericnewton76 Command Line Parser Library $(VersionSuffix) - 2.5.0 + 0.0.0 Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors @@ -36,7 +36,7 @@ - + From 41f3fbae82df15fcee87727f0e561cd50aed4b51 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Thu, 4 Apr 2019 12:22:12 -0400 Subject: [PATCH 021/198] ignore Visual Studio Code editor data ignore .vscode/ --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 55f900a1..d45e4df4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,6 @@ artifacts/* *.DotSettings.user # Visual Studio 2015 cache/options directory .vs/ +.vscode/ -[R|r]elease/** +[R|r]elease/** From f23916f4285caab386bb58ea8f5233921aafb145 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Thu, 4 Apr 2019 12:24:33 -0400 Subject: [PATCH 022/198] replace invalid signature key replace CommandLine.snk update strong name in assembly: InternalsVisibleTo attribute --- CommandLine.snk | Bin 595 -> 596 bytes src/CommandLine/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CommandLine.snk b/CommandLine.snk index 96087a731df6b418f70cfe6df405afeb53af9025..4dbcd829b2c2129e561f0cdb0d1022ccf2759472 100644 GIT binary patch delta 587 zcmV-R0<`_p1k?nO6n|~jz}K(eIfu6#IXaR(XL9J!4UtAaNytDx7&ypp8eOwdC0HX# zX@voJz9@eTiH^584`Htb3Ek0@x@X?vqt5DfAcL%cQA?%}(WGG59>GWmTbRG!g1=M` zHR|%e1|&tfF4KOvsJSDlxu$3Nr3qrA*f>7W6ZeuhjV&Rza(}#AR7R6~jrYBrIvUiY z6hvspK1CSyAOK&BUHz1<$x^h{DKi{=cGWOm)8X3Duus>980vZ}wD=nzbo1WKis$0j zT9}9mQ_VJSzR;dStJAIKoF7ct|Kwvc`_qZXu?i=m38v1yIt~q^YCiFXfB{KNvLs;& zEve*LQmy*z$bX#FH`R6;X9>8a7$rv~k_S&twm!_&vo@`q$=hIi;h%{Q{`-PE^OO~q z(lTO0W`t=))=%*yJruxLoH?5G?2DYW|3XF)Kaxh2+gMvLtbyJhr_gobvPaj(_8heR z5pue`#SeU45&xx~e{>!Iaae*-fRvR4_lZNVS-os^8-Ej$B|&6Ce#Xw}Np}&FXEW^E zrXtyv+u&r*@6g6aSKPQH#n@j*XID+u4WEhcV_mJ-J41W4TK2)fRV(e%q3m$IK@~sD z7s+o$SF51VW{@5s=1jRa#;saMgIc_((NollY Zx|ufn`J!2YF%PUKv~Ht~O@F7Z7=|*ZCa3@a delta 586 zcmV-Q0=50r1k(hN6n_=#b#c~~z;$AHF!@G_X%IWZS7b(e+syn3Q1bickv`%x)z{BE zytr`vLguKNSzz+GKvhPVu0vD2Ya^gx`v<6kYi+V$6dn=DR; z3xyNavf()^Du2$m5!)`rG9pe90Qr(rr`&n6Hg7mO{we0^4%`~!U@yK2eSDpk_-g}d zug^~rk5qh2!QC@_&G;YFDY*E$El3}nIWf4gGLr+t%X1n9fovdLPQ^+})N#3*cyRmr zyuWpl43ire`CXHd2*=^2iP(&JCOBMEBmKCT-q@SNpMR$_HvZf*?{z*zU_|RnE=c)7 z^V$RgVKR}ENDOzv39QEk^RC$p!FH1XR(Z&`kQ4TD$zc`WS@J%T$3?$HvCG>S27yE; zSVgj8M^qyZKp0P%+@LP&0%1ZGa Date: Thu, 4 Apr 2019 17:04:43 -0400 Subject: [PATCH 023/198] test usage property with custom attribute define custom attribute class specify attribute for fake Options_With_Usage_Attribute.Examples property --- tests/CommandLine.Tests/Fakes/CustomAttribute.cs | 4 ++++ tests/CommandLine.Tests/Fakes/Help_Fakes.cs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 tests/CommandLine.Tests/Fakes/CustomAttribute.cs diff --git a/tests/CommandLine.Tests/Fakes/CustomAttribute.cs b/tests/CommandLine.Tests/Fakes/CustomAttribute.cs new file mode 100644 index 00000000..845fb2dd --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/CustomAttribute.cs @@ -0,0 +1,4 @@ +using System; + +[AttributeUsage(AttributeTargets.All)] +class CustomAttribute: Attribute {} \ No newline at end of file diff --git a/tests/CommandLine.Tests/Fakes/Help_Fakes.cs b/tests/CommandLine.Tests/Fakes/Help_Fakes.cs index cceb5331..4b9fe83e 100644 --- a/tests/CommandLine.Tests/Fakes/Help_Fakes.cs +++ b/tests/CommandLine.Tests/Fakes/Help_Fakes.cs @@ -79,7 +79,9 @@ class Options_With_Usage_Attribute [Option("secert-option", Hidden = true, HelpText = "This is a description for a secert hidden option that should never be visibile to the user via help text.")] public string SecertOption { get; set; } - [Usage(ApplicationAlias = "mono testapp.exe")] + [ Custom + , Usage(ApplicationAlias = "mono testapp.exe") + ] public static IEnumerable Examples { get From 6a01d6c7b74b6a50aa5c9a5f5cea574c867ba795 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Tue, 9 Apr 2019 21:28:32 +0200 Subject: [PATCH 024/198] Resolve the null EntryAssembly Issues #389 #424 --- Directory.Build.props | 8 ++ src/CommandLine/CommandLine.csproj | 3 +- .../Infrastructure/ReflectionHelper.cs | 4 +- .../CommandLine.Tests.csproj | 8 +- tests/CommandLine.Tests/Unit/Issue389Tests.cs | 78 +++++++++++++++++++ 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 Directory.Build.props create mode 100644 tests/CommandLine.Tests/Unit/Issue389Tests.cs diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..c074efed --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,8 @@ + + + CS1591;8002;NU5125 + + + $(DefineConstants);NETFRAMEWORK + + \ No newline at end of file diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 94c76586..5b0ea0e0 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -3,7 +3,7 @@ CommandLine Library - netstandard2.0;net40;net45;net461;netcoreapp2.0 + netstandard2.0;net40;net45 $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC $(DefineConstants);SKIP_FSHARP true @@ -23,6 +23,7 @@ https://raw.githubusercontent.com/commandlineparser/commandline/master/art/CommandLine20.png command line;commandline;argument;option;parser;parsing;library;syntax;shell true + 7.3 diff --git a/src/CommandLine/Infrastructure/ReflectionHelper.cs b/src/CommandLine/Infrastructure/ReflectionHelper.cs index a8284180..50bea517 100644 --- a/src/CommandLine/Infrastructure/ReflectionHelper.cs +++ b/src/CommandLine/Infrastructure/ReflectionHelper.cs @@ -101,7 +101,9 @@ public static object CreateDefaultImmutableInstance(Type type, Type[] constructo private static Assembly GetExecutingOrEntryAssembly() { - return Assembly.GetEntryAssembly(); + //resolve issues of null EntryAssembly in Xunit Test #392,424,389 + //return Assembly.GetEntryAssembly(); + return Assembly.GetEntryAssembly()??Assembly.GetCallingAssembly(); } } } \ No newline at end of file diff --git a/tests/CommandLine.Tests/CommandLine.Tests.csproj b/tests/CommandLine.Tests/CommandLine.Tests.csproj index 274a3e38..6d1851a9 100644 --- a/tests/CommandLine.Tests/CommandLine.Tests.csproj +++ b/tests/CommandLine.Tests/CommandLine.Tests.csproj @@ -2,7 +2,7 @@ Library - netcoreapp2.0 + net461;netcoreapp2.0 $(DefineConstants);PLATFORM_DOTNET $(DefineConstants);SKIP_FSHARP ..\..\CommandLine.snk @@ -13,7 +13,6 @@ 2.5.0 Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors - @@ -29,8 +28,9 @@ - + + - + \ No newline at end of file diff --git a/tests/CommandLine.Tests/Unit/Issue389Tests.cs b/tests/CommandLine.Tests/Unit/Issue389Tests.cs new file mode 100644 index 00000000..ef4f6169 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue389Tests.cs @@ -0,0 +1,78 @@ +using CommandLine.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; + +namespace CommandLine.Tests.Unit +{ + //Reference: PR# 392 + public class Issue389Tests + { + + private const int ERROR_SUCCESS = 0; + + // Test method (xUnit) which fails + [Fact] + public void CallMain_GiveHelpArgument_ExpectSuccess() + { + var result = Program.__Main(new[] { "--help" }); + + Assert.Equal(ERROR_SUCCESS, result); + } + + // main program + internal class Program + { + + + internal static int __Main(string[] args) + { + bool hasError = false; + bool helpOrVersionRequested = false; + + ParserResult parsedOptions = Parser.Default.ParseArguments(args) + .WithNotParsed(errors => { + helpOrVersionRequested = errors.Any( + x => x.Tag == ErrorType.HelpRequestedError + || x.Tag == ErrorType.VersionRequestedError); + hasError = true; + }); + + if(helpOrVersionRequested) + { + return ERROR_SUCCESS; + } + + // Execute as a normal call + // ... + return ERROR_SUCCESS; + } + + } + + // Options + internal class Options + { + + [Option('c', "connectionString", Required = true, HelpText = "Texts.ExplainConnection")] + public string ConnectionString { get; set; } + + [Option('j', "jobId", Required = true, HelpText = "Texts.ExplainJob")] + public int JobId { get; set; } + + [Usage(ApplicationAlias = "Importer.exe")] + public static IEnumerable Examples + { + get => new[] { + new Example("Texts.ExplainExampleExecution", new Options() { + ConnectionString="Server=MyServer;Database=MyDatabase", + JobId = 5 + }), + }; + } + + } + } +} From 93c90070e50f2c12eab60cebaf8fb6f490bd797c Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Thu, 11 Apr 2019 18:37:42 +0200 Subject: [PATCH 025/198] add testcase of issue# 424 --- Directory.Build.props | 2 +- src/CommandLine/CommandLine.csproj | 2 +- .../CommandLine.Tests.csproj | 10 ++-- tests/CommandLine.Tests/Unit/Issue424Tests.cs | 55 +++++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/Issue424Tests.cs diff --git a/Directory.Build.props b/Directory.Build.props index c074efed..a6a8d8db 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - CS1591;8002;NU5125 + CS1591;CS0219;8002;NU5125 $(DefineConstants);NETFRAMEWORK diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 5b0ea0e0..cd666847 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -14,7 +14,7 @@ gsscoder;nemec;ericnewton76 Command Line Parser Library $(VersionSuffix) - 0.0.0 + 2.5.0-Dev Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors diff --git a/tests/CommandLine.Tests/CommandLine.Tests.csproj b/tests/CommandLine.Tests/CommandLine.Tests.csproj index 6d1851a9..99832d50 100644 --- a/tests/CommandLine.Tests/CommandLine.Tests.csproj +++ b/tests/CommandLine.Tests/CommandLine.Tests.csproj @@ -3,7 +3,6 @@ Library net461;netcoreapp2.0 - $(DefineConstants);PLATFORM_DOTNET $(DefineConstants);SKIP_FSHARP ..\..\CommandLine.snk true @@ -12,12 +11,15 @@ $(VersionSuffix) 2.5.0 Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors + true - + + $(DefineConstants);PLATFORM_DOTNET + @@ -26,8 +28,8 @@ - - + + diff --git a/tests/CommandLine.Tests/Unit/Issue424Tests.cs b/tests/CommandLine.Tests/Unit/Issue424Tests.cs new file mode 100644 index 00000000..c828c3ed --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue424Tests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Xunit; + +namespace CommandLine.Tests.Unit +{ + + //MailAndSmsWarningSenderTests + public class Issue424Tests + { + private MailAndSmsWarningSender _sut; + + public Issue424Tests() + { + _sut = new MailAndSmsWarningSender(); + } + + [Fact] + public void SendSmsOnWarning() + { + //Arrange + void Action() => _sut.ParseArgumentsAndRun( + new[] { "--task", "MailAndSmsWarningSender", "--test", "hejtest" }); + // Act & Assert + Assert.Throws((Action)Action); + } + } + + public class MailAndSmsWarningSender + { + internal class Options + { + [Option("task")] + public string Task { get; set; } + } + + public void ParseArgumentsAndRun(string[] args) + { + Parser.Default.ParseArguments(args) + .WithParsed(ExecuteTaskWithOptions) + .WithNotParsed(HandleParseError); + } + + private void HandleParseError(IEnumerable errs) + { + throw new NotImplementedException(); + } + + private void ExecuteTaskWithOptions(Options opts) + { + Console.WriteLine("Executing"); + } + + } +} \ No newline at end of file From 196a83096aa46c7e18392105523e15d5cfa8e3d6 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Thu, 4 Apr 2019 17:12:52 -0400 Subject: [PATCH 026/198] pass custom attribute test in ReflectionExtensions.GetUsageData, filter attributes by type before casting to that type --- src/CommandLine/Core/ReflectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index c82ebefc..ef940d95 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -40,8 +40,8 @@ public static Maybe> GetUsageData(this Type { return (from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties()) - let attrs = pi.GetCustomAttributes(true) - where attrs.OfType().Any() + let attrs = pi.GetCustomAttributes(typeof(UsageAttribute), true) + where attrs.Any() select Tuple.Create(pi, (UsageAttribute)attrs.First())) .SingleOrDefault() .ToMaybe(); From b27cb7e4820d8a0b3fb72aa0f081ee55f183d675 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Fri, 26 Apr 2019 20:24:04 +0200 Subject: [PATCH 027/198] Revert "fixed bad merge of Error.cs in build" (6d2dc12f0) --- src/CommandLine/Error.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index 90ee26d8..b1bc3e94 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -64,8 +64,7 @@ public enum ErrorType /// /// Value of type. /// - SetValueExceptionError, - + SetValueExceptionError VersionRequestedError, /// /// Value of type. From 6a6015eb25795d925e852cbe1fb88d9ccafdba55 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Fri, 26 Apr 2019 20:25:50 +0200 Subject: [PATCH 028/198] Revert "Merge branch 'develop' of github.com:commandlineparser/commandline into develop" (5343c5d, reversing changes 9f91088) --- src/CommandLine/Core/ReflectionExtensions.cs | 13 ------------- src/CommandLine/Error.cs | 5 ----- .../Fakes/Options_With_InvalidDefaults.cs | 11 ----------- .../Unit/Core/InstanceBuilderTests.cs | 17 +---------------- 4 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index 2f4faf0f..cd75e07e 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -14,8 +14,6 @@ namespace CommandLine.Core { static class ReflectionExtensions { - public const string CannotSetValueToTargetInstance = "Cannot set value to target instance."; - public static IEnumerable GetSpecifications(this Type type, Func selector) { return from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties()) @@ -93,10 +91,6 @@ public static IEnumerable SetProperties( private static IEnumerable SetValue(this SpecificationProperty specProp, T instance, object value) { - Action fail = inner => { - throw new InvalidOperationException(CannotSetValueToTargetInstance, inner); - }; - try { specProp.Property.SetValue(instance, value, null); @@ -110,13 +104,6 @@ private static IEnumerable SetValue(this SpecificationProperty specPro { return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) }; } - catch(ArgumentException e) - { - var argEx = new ArgumentException(InvalidAttributeConfigurationError.ErrorMessage, e); - fail(argEx); - } - - return instance; } public static object CreateEmptyArray(this Type type) diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index b1bc3e94..2b586912 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -65,11 +65,6 @@ public enum ErrorType /// Value of type. /// SetValueExceptionError - VersionRequestedError, - /// - /// Value of type. - /// - InvalidAttributeConfigurationError } /// diff --git a/tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs b/tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs deleted file mode 100644 index cedb1136..00000000 --- a/tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs +++ /dev/null @@ -1,11 +0,0 @@ - -namespace CommandLine.Tests.Fakes -{ - class Options_With_InvalidDefaults - { - // Default of string and integer type property will also throw. - - [Option(Default = false)] - public string FileName { get; set; } - } -} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index eef9a674..c150d23a 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1133,21 +1133,6 @@ public void Parse_TimeSpan() // Teardown } - [Fact] - public void Build_DefaultBoolTypeString_ThrowsInvalidOperationException() - { - // Exercize system - Action test = () => InvokeBuild( - new string[] { }); - - // Verify outcome - test.ShouldThrow() - .WithMessage(ReflectionExtensions.CannotSetValueToTargetInstance) - .WithInnerException() - .WithInnerMessage(InvalidAttributeConfigurationError.ErrorMessage); - } - - [Fact] public void OptionClass_IsImmutable_HasNoCtor() { @@ -1163,7 +1148,7 @@ private class ValueWithNoSetterOptions } - public static IEnumerable RequiredValueStringData + public static IEnumerable RequiredValueStringData { get { From 28d1376ad1a1f5cce25445cc7251c9744acdd70c Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Fri, 26 Apr 2019 22:07:15 +0200 Subject: [PATCH 029/198] resolving appveyor build error --- CommandLine.snk | Bin 595 -> 596 bytes appveyor.yml | 2 +- src/CommandLine/CommandLine.csproj | 1 + src/CommandLine/Properties/AssemblyInfo.cs | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CommandLine.snk b/CommandLine.snk index 96087a731df6b418f70cfe6df405afeb53af9025..6b0b650111ad1ef405aa994d57732ff390e61643 100644 GIT binary patch delta 587 zcmV-R0<`_p1k?nO6n_b;B=3lv(rff@ey-Xm;QCwy#SD|i;UP176-&wrAJvM+%Z3?S zaGW6^^n!B??F69VXQg$HR?|jH`nvuCD=|?FIi?wC{pXcP4b~58IbapV_L`I+bfffT zOKsL|3}ax6>Uh2RYc;X7i?>%n!pf~5a95uwZymfWRs|8pNPp7%=L0Lq+9^7rEYzH1 zb@{H&4fIh%1A#Be9PBmhieJHVSOpkXK{`l6Zkiqvf zUcRWtL48wl7#Xzb_yk#A6%05p-l_%I6f)mJ1n0J6kLcmEP&8%^B zfHQZ&FhufHlor{V_H_)BX!P!}h6{*et5iy&Xr@NSzz+GKvhPVu0vD2Ya^gx`v<6kYi+V$6dn=DR; z3xyNavf()^Du2$m5!)`rG9pe90Qr(rr`&n6Hg7mO{we0^4%`~!U@yK2eSDpk_-g}d zug^~rk5qh2!QC@_&G;YFDY*E$El3}nIWf4gGLr+t%X1n9fovdLPQ^+})N#3*cyRmr zyuWpl43ire`CXHd2*=^2iP(&JCOBMEBmKCT-q@SNpMR$_HvZf*?{z*zU_|RnE=c)7 z^V$RgVKR}ENDOzv39QEk^RC$p!FH1XR(Z&`kQ4TD$zc`WS@J%T$3?$HvCG>S27yE; zSVgj8M^qyZKp0P%+@LP&0%1ZGa$(DefineConstants);SKIP_FSHARP true ..\..\CommandLine.snk + true CommandLineParser CommandLineParser.FSharp diff --git a/src/CommandLine/Properties/AssemblyInfo.cs b/src/CommandLine/Properties/AssemblyInfo.cs index 4b4532b3..1dc94d20 100644 --- a/src/CommandLine/Properties/AssemblyInfo.cs +++ b/src/CommandLine/Properties/AssemblyInfo.cs @@ -2,4 +2,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("CommandLine.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015eb7571d696c075627830f9468969103bc35764467bdbccfc0850f2fbe6913ee233d5d7cf3bbcb870fd42e6a8cc846d706b5cef35389e5b90051991ee8b6ed73ee1e19f108e409be69af6219b2e31862405f4b8ba101662fbbb54ba92a35d97664fe65c90c2bebd07aef530b01b709be5ed01b7e4d67a6b01c8643e42a20fb4")] +[assembly: InternalsVisibleTo("CommandLine.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010009ab24ef889cd26bf46f7eaeda28e0fa5c04c50c93c6e121337b154bca0a1fd58ac6cb86195b709c2120f482730ced04a0e167a5758e56d3464bfabafe022b31510c39a61968fde795480dd60f6a396015c5f69a942074a3f4654b6dd66d0c63608bea78bdf96b35b1b48bb75741c2caad1f70579f286f1dbc2c560511c648d2")] From 4295f74dd387f0dd173e4e2cad62b2df3d0aa157 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Sat, 27 Apr 2019 01:32:16 +0200 Subject: [PATCH 030/198] resolve appveyor build error --- CommandLine.snk | Bin 595 -> 596 bytes src/CommandLine/CommandLine.csproj | 2 +- src/CommandLine/Properties/AssemblyInfo.cs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CommandLine.snk b/CommandLine.snk index 96087a731df6b418f70cfe6df405afeb53af9025..6b0b650111ad1ef405aa994d57732ff390e61643 100644 GIT binary patch delta 587 zcmV-R0<`_p1k?nO6n_b;B=3lv(rff@ey-Xm;QCwy#SD|i;UP176-&wrAJvM+%Z3?S zaGW6^^n!B??F69VXQg$HR?|jH`nvuCD=|?FIi?wC{pXcP4b~58IbapV_L`I+bfffT zOKsL|3}ax6>Uh2RYc;X7i?>%n!pf~5a95uwZymfWRs|8pNPp7%=L0Lq+9^7rEYzH1 zb@{H&4fIh%1A#Be9PBmhieJHVSOpkXK{`l6Zkiqvf zUcRWtL48wl7#Xzb_yk#A6%05p-l_%I6f)mJ1n0J6kLcmEP&8%^B zfHQZ&FhufHlor{V_H_)BX!P!}h6{*et5iy&Xr@NSzz+GKvhPVu0vD2Ya^gx`v<6kYi+V$6dn=DR; z3xyNavf()^Du2$m5!)`rG9pe90Qr(rr`&n6Hg7mO{we0^4%`~!U@yK2eSDpk_-g}d zug^~rk5qh2!QC@_&G;YFDY*E$El3}nIWf4gGLr+t%X1n9fovdLPQ^+})N#3*cyRmr zyuWpl43ire`CXHd2*=^2iP(&JCOBMEBmKCT-q@SNpMR$_HvZf*?{z*zU_|RnE=c)7 z^V$RgVKR}ENDOzv39QEk^RC$p!FH1XR(Z&`kQ4TD$zc`WS@J%T$3?$HvCG>S27yE; zSVgj8M^qyZKp0P%+@LP&0%1ZGa CommandLine Library - netstandard2.0;net40;net45 + netstandard2.0;net40;net45;net461 $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC $(DefineConstants);SKIP_FSHARP true diff --git a/src/CommandLine/Properties/AssemblyInfo.cs b/src/CommandLine/Properties/AssemblyInfo.cs index 4b4532b3..1dc94d20 100644 --- a/src/CommandLine/Properties/AssemblyInfo.cs +++ b/src/CommandLine/Properties/AssemblyInfo.cs @@ -2,4 +2,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("CommandLine.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015eb7571d696c075627830f9468969103bc35764467bdbccfc0850f2fbe6913ee233d5d7cf3bbcb870fd42e6a8cc846d706b5cef35389e5b90051991ee8b6ed73ee1e19f108e409be69af6219b2e31862405f4b8ba101662fbbb54ba92a35d97664fe65c90c2bebd07aef530b01b709be5ed01b7e4d67a6b01c8643e42a20fb4")] +[assembly: InternalsVisibleTo("CommandLine.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010009ab24ef889cd26bf46f7eaeda28e0fa5c04c50c93c6e121337b154bca0a1fd58ac6cb86195b709c2120f482730ced04a0e167a5758e56d3464bfabafe022b31510c39a61968fde795480dd60f6a396015c5f69a942074a3f4654b6dd66d0c63608bea78bdf96b35b1b48bb75741c2caad1f70579f286f1dbc2c560511c648d2")] From 0eafab4ad69bc36c58acbb1553a58585dea14148 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Sat, 27 Apr 2019 02:17:21 +0200 Subject: [PATCH 031/198] fix test build --- tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 6ef21ac0..7c4d7590 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; +using System.Globalization; +using CommandLine.Core; using System.Linq; using System.Reflection; using CommandLine.Infrastructure; From 8dcaa6fa37b531ac19b2561c8bbc63b9421db64a Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Sat, 27 Apr 2019 03:21:40 +0200 Subject: [PATCH 032/198] modify commandLine project to support net40 --- src/CommandLine/CommandLine.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 082af743..80933f08 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -41,5 +41,7 @@ - + + + \ No newline at end of file From 4d69a3898b84c821161b2db948075cd9e162832c Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Sat, 27 Apr 2019 19:36:21 +0200 Subject: [PATCH 033/198] Merge PR 314 (Wind010) --- src/CommandLine/Core/ReflectionExtensions.cs | 12 ++++++++-- src/CommandLine/Error.cs | 22 +++++++++++++++++-- .../Fakes/Options_With_InvalidDefaults.cs | 11 ++++++++++ .../Unit/Core/InstanceBuilderTests.cs | 17 ++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index cd75e07e..9b6f858c 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -100,10 +100,18 @@ private static IEnumerable SetValue(this SpecificationProperty specPro { return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e.InnerException, value) }; } + catch (ArgumentException e) + { + var argEx = new ArgumentException(InvalidAttributeConfigurationError.ErrorMessage, e); + + return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), argEx, value) }; + } + catch (Exception e) { - return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) }; + return new[] { new SetValueExceptionError(specProp.Specification.FromSpecification(), e, value) }; } + } public static object CreateEmptyArray(this Type type) @@ -202,4 +210,4 @@ public static bool IsPrimitiveEx(this Type type) || Convert.GetTypeCode(type) != TypeCode.Object; } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index 2b586912..2f208dec 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -64,7 +64,13 @@ public enum ErrorType /// /// Value of type. /// - SetValueExceptionError + + SetValueExceptionError, + /// + /// Value of type. + /// + InvalidAttributeConfigurationError + } /// @@ -506,6 +512,18 @@ public object Value { get { return value; } } + } + + /// + /// Models an error generated when an invalid token is detected. + /// + public sealed class InvalidAttributeConfigurationError : Error + { + public const string ErrorMessage = "Check if Option or Value attribute values are set properly for the given type."; + internal InvalidAttributeConfigurationError() + : base(ErrorType.InvalidAttributeConfigurationError, true) + { + } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs b/tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs new file mode 100644 index 00000000..cedb1136 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_InvalidDefaults.cs @@ -0,0 +1,11 @@ + +namespace CommandLine.Tests.Fakes +{ + class Options_With_InvalidDefaults + { + // Default of string and integer type property will also throw. + + [Option(Default = false)] + public string FileName { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index c150d23a..c6234089 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1007,6 +1007,22 @@ public void Parse_option_with_exception_thrown_from_setter_generates_SetValueExc // Teardown } + [Fact] + public void Parse_default_bool_type_string_SetValueExceptionError() + { + // Fixture setup + string name = nameof(Options_With_InvalidDefaults.FileName).ToLower(); + var expectedResult = new[] { new SetValueExceptionError(new NameInfo("", name), + new ArgumentException(InvalidAttributeConfigurationError.ErrorMessage), "bad") }; + + // Exercize system + var result = InvokeBuild( + new[] { name, "bad" }); + + // Verify outcome + ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); + } + [Theory] [InlineData(new[] { "--stringvalue", "x-" }, "x-")] @@ -1133,6 +1149,7 @@ public void Parse_TimeSpan() // Teardown } + [Fact] public void OptionClass_IsImmutable_HasNoCtor() { From c53acce3145bef93e3652be535245d6d6d17041c Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Sun, 2 Jun 2019 14:37:25 +0200 Subject: [PATCH 034/198] fix issue #104 of nullable enum --- src/CommandLine/Core/Specification.cs | 6 +- .../Infrastructure/ReflectionHelper.cs | 20 ++++-- ...ions_With_Nullable_Enum_Having_HelpText.cs | 13 ++++ tests/CommandLine.Tests/Unit/Issue104Tests.cs | 68 +++++++++++++++++++ 4 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Nullable_Enum_Having_HelpText.cs create mode 100644 tests/CommandLine.Tests/Unit/Issue104Tests.cs diff --git a/src/CommandLine/Core/Specification.cs b/src/CommandLine/Core/Specification.cs index 9b267741..b95b998c 100644 --- a/src/CommandLine/Core/Specification.cs +++ b/src/CommandLine/Core/Specification.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using CommandLine.Infrastructure; using CSharpx; namespace CommandLine.Core @@ -115,9 +116,8 @@ public static Specification FromProperty(PropertyInfo property) if (oa.Count() == 1) { var spec = OptionSpecification.FromAttribute(oa.Single(), property.PropertyType, - property.PropertyType.GetTypeInfo().IsEnum - ? Enum.GetNames(property.PropertyType) - : Enumerable.Empty()); + ReflectionHelper.GetNamesOfEnum(property.PropertyType)); + if (spec.ShortName.Length == 0 && spec.LongName.Length == 0) { return spec.WithLongName(property.Name.ToLowerInvariant()); diff --git a/src/CommandLine/Infrastructure/ReflectionHelper.cs b/src/CommandLine/Infrastructure/ReflectionHelper.cs index e2177947..a7926cb7 100644 --- a/src/CommandLine/Infrastructure/ReflectionHelper.cs +++ b/src/CommandLine/Infrastructure/ReflectionHelper.cs @@ -45,10 +45,10 @@ public static Maybe GetAttribute() // Test support if (_overrides != null) { - return + return _overrides.ContainsKey(typeof(TAttribute)) ? Maybe.Just((TAttribute)_overrides[typeof(TAttribute)]) : - Maybe.Nothing< TAttribute>(); + Maybe.Nothing(); } var assembly = GetExecutingOrEntryAssembly(); @@ -84,7 +84,7 @@ public static bool IsFSharpOptionType(Type type) public static T CreateDefaultImmutableInstance(Type[] constructorTypes) { - var t = typeof(T); + var t = typeof(T); return (T)CreateDefaultImmutableInstance(t, constructorTypes); } @@ -100,7 +100,17 @@ private static Assembly GetExecutingOrEntryAssembly() { //resolve issues of null EntryAssembly in Xunit Test #392,424,389 //return Assembly.GetEntryAssembly(); - return Assembly.GetEntryAssembly()??Assembly.GetCallingAssembly(); + return Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly(); + } + + public static IEnumerable GetNamesOfEnum(Type t) + { + if (t.IsEnum) + return Enum.GetNames(t); + Type u = Nullable.GetUnderlyingType(t); + if (u != null && u.IsEnum) + return Enum.GetNames(u); + return Enumerable.Empty(); } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Nullable_Enum_Having_HelpText.cs b/tests/CommandLine.Tests/Fakes/Options_With_Nullable_Enum_Having_HelpText.cs new file mode 100644 index 00000000..a316124e --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Nullable_Enum_Having_HelpText.cs @@ -0,0 +1,13 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +namespace CommandLine.Tests.Fakes +{ + class Options_With_Nullable_Enum_Having_HelpText + { + [Option(HelpText = "Define a string value here.")] + public string StringValue { get; set; } + + [Option(HelpText="Define a enum value here.")] + public Shapes? Shape { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Issue104Tests.cs b/tests/CommandLine.Tests/Unit/Issue104Tests.cs new file mode 100644 index 00000000..fcbefedd --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue104Tests.cs @@ -0,0 +1,68 @@ +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +//Issue #104 +//When outputting HelpText, the code will correctly list valid values for enum type options. However, if the option is a nullable enum type, then it will not list the valid values. + +namespace CommandLine.Tests.Unit +{ + public class Issue104Tests + { + + [Fact] + public void Create_instance_with_enum_options_enabled_and_nullable_enum() + { + // Fixture setup + // Exercize system + var sut = new HelpText { AddDashesToOption = true, AddEnumValuesToHelpText = true, MaximumDisplayWidth = 80 } + .AddPreOptionsLine("pre-options") + .AddOptions(new NotParsed(TypeInfo.Create(typeof(Options_With_Enum_Having_HelpText)), Enumerable.Empty())) + .AddPostOptionsLine("post-options"); + + // Verify outcome + + var lines = sut.ToString().ToNotEmptyLines().TrimStringArray(); + lines[0].Should().BeEquivalentTo("pre-options"); + lines[1].Should().BeEquivalentTo("--stringvalue Define a string value here."); + lines[2].Should().BeEquivalentTo("--shape Define a enum value here. Valid values: Circle, Square,"); + lines[3].Should().BeEquivalentTo("Triangle"); + lines[4].Should().BeEquivalentTo("--help Display this help screen."); + lines[5].Should().BeEquivalentTo("--version Display version information."); + lines[6].Should().BeEquivalentTo("post-options"); + // Teardown + } + + [Fact] + public void Help_with_enum_options_enabled_and_nullable_enum() + { + // Fixture setup + // Exercize system + var args = "--help".Split(); + var sut = new Parser(config => config.HelpWriter = null); + var parserResult = sut.ParseArguments(args); + HelpText helpText = null; + parserResult.WithNotParsed(errors => + { + // Use custom help text to ensure valid enum values are displayed + helpText = HelpText.AutoBuild(parserResult); + helpText.AddEnumValuesToHelpText = true; + helpText.AddOptions(parserResult); + }); + + // Verify outcome + + var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); + lines[2].Should().BeEquivalentTo("--stringvalue Define a string value here."); + lines[3].Should().BeEquivalentTo("--shape Define a enum value here. Valid values: Circle, Square,"); + lines[4].Should().BeEquivalentTo("Triangle"); + lines[5].Should().BeEquivalentTo("--help Display this help screen."); + lines[6].Should().BeEquivalentTo("--version Display version information."); + // Teardown + } + } + +} From 18e5e7850d9dfd8b33b7116f11fa252434340fa5 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Thu, 6 Jun 2019 18:19:59 +0100 Subject: [PATCH 035/198] Improve support for multiline help text --- src/CommandLine/Text/HelpText.cs | 205 +++++++++++------- ...WithLineBreaksAndSubIndentation_Options.cs | 13 ++ .../Fakes/HelpTextWithLineBreaks_Options.cs | 23 ++ .../Unit/Text/HelpTextTests.cs | 85 +++++++- 4 files changed, 240 insertions(+), 86 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs create mode 100644 tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index cd11a475..5a1b6953 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -21,6 +21,18 @@ public class HelpText { private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width + /// + /// The number of spaces between an option and its associated help text + /// + private const int OptionToHelpTextSeparatorWidth = 4; + /// + /// The width of the option prefix (either "--" or " " + /// + private const int OptionPrefixWidth = 2; + /// + /// The total amount of extra space that needs to accounted for when indenting Option help text + /// + private const int TotalOptionPadding = OptionToHelpTextSeparatorWidth + OptionPrefixWidth; private readonly StringBuilder preOptionsHelp; private readonly StringBuilder postOptionsHelp; private readonly SentenceBuilder sentenceBuilder; @@ -608,7 +620,7 @@ public static IEnumerable RenderUsageTextAsLines(ParserResult pars var styles = example.GetFormatStylesOrDefault(); foreach (var s in styles) { - var commandLine = new StringBuilder(2.Spaces()) + var commandLine = new StringBuilder(OptionPrefixWidth.Spaces()) .Append(appAlias) .Append(' ') .Append(Parser.Default.FormatCommandLine(example.Sample, @@ -645,7 +657,7 @@ public override string ToString() .ToString(); } - internal static void AddLine(StringBuilder builder, string value, int maximumLength) + internal static void AddLine(StringBuilder builder, string value, int maximumLength) { if (builder == null) { @@ -665,37 +677,7 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen value = value.TrimEnd(); builder.AppendWhen(builder.Length > 0, Environment.NewLine); - do - { - var wordBuffer = 0; - var words = value.Split(' '); - for (var i = 0; i < words.Length; i++) - { - if (words[i].Length < (maximumLength - wordBuffer)) - { - builder.Append(words[i]); - wordBuffer += words[i].Length; - if ((maximumLength - wordBuffer) > 1 && i != words.Length - 1) - { - builder.Append(" "); - wordBuffer++; - } - } - else if (words[i].Length >= maximumLength && wordBuffer == 0) - { - builder.Append(words[i].Substring(0, maximumLength)); - wordBuffer = maximumLength; - break; - } - else - break; - } - value = value.Substring(Math.Min(wordBuffer, value.Length)); - builder.AppendWhen(value.Length > 0, Environment.NewLine); - } - while (value.Length > maximumLength); - - builder.Append(value); + builder.Append(WrapAndIndentText(value, 0, maximumLength)); } private IEnumerable GetSpecificationsFromType(Type type) @@ -748,7 +730,7 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable return optionSpecs; } - private HelpText AddOptionsImpl( + private HelpText AddOptionsImpl( IEnumerable specifications, string requiredWord, int maximumLength) @@ -757,7 +739,7 @@ private HelpText AddOptionsImpl( optionsHelp = new StringBuilder(BuilderCapacity); - var remainingSpace = maximumLength - (maxLength + 6); + var remainingSpace = maximumLength - (maxLength + TotalOptionPadding); specifications.ForEach( option => @@ -809,7 +791,7 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe optionsHelp .Append(name.Length < maxLength ? name.ToString().PadRight(maxLength) : name.ToString()) - .Append(" "); + .Append(OptionToHelpTextSeparatorWidth.Spaces()); var optionHelpText = specification.HelpText; @@ -821,44 +803,13 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe if (specification.Required) optionHelpText = "{0} ".FormatInvariant(requiredWord) + optionHelpText; - - if (!string.IsNullOrEmpty(optionHelpText)) - { - do - { - var wordBuffer = 0; - var words = optionHelpText.Split(' '); - for (var i = 0; i < words.Length; i++) - { - if (words[i].Length < (widthOfHelpText - wordBuffer)) - { - optionsHelp.Append(words[i]); - wordBuffer += words[i].Length; - if ((widthOfHelpText - wordBuffer) > 1 && i != words.Length - 1) - { - optionsHelp.Append(" "); - wordBuffer++; - } - } - else if (words[i].Length >= widthOfHelpText && wordBuffer == 0) - { - optionsHelp.Append(words[i].Substring(0, widthOfHelpText)); - wordBuffer = widthOfHelpText; - break; - } - else - break; - } - - optionHelpText = optionHelpText.Substring(Math.Min(wordBuffer, optionHelpText.Length)).Trim(); - optionsHelp.AppendWhen(optionHelpText.Length > 0, Environment.NewLine, - new string(' ', maxLength + 6)); - } - while (optionHelpText.Length > widthOfHelpText); - } - + + //note that we need to indent trim the start of the string because it's going to be + //appended to an existing line that is as long as the indent-level + var indented = WrapAndIndentText(optionHelpText, maxLength+TotalOptionPadding, widthOfHelpText).TrimStart(); + optionsHelp - .Append(optionHelpText) + .Append(indented) .Append(Environment.NewLine) .AppendWhen(additionalNewLineAfterOption, Environment.NewLine); @@ -944,13 +895,13 @@ private int GetMaxOptionLength(OptionSpecification spec) { specLength += spec.LongName.Length; if (AddDashesToOption) - specLength += 2; + specLength += OptionPrefixWidth; specLength += metaLength; } if (hasShort && hasLong) - specLength += 2; // ", " + specLength += OptionPrefixWidth; return specLength; } @@ -997,5 +948,107 @@ private static string FormatDefaultValue(T value) ? builder.ToString(0, builder.Length - 1) : string.Empty; } + + /// + /// Splits a string into a words and performs wrapping while also preserving line-breaks and sub-indentation + /// + /// The string to wrap + /// The amount of padding at the start of each string + /// The number of characters we can use for text + /// + /// The use of "width" is slightly confusing in other methods. In this method, the columnWidth + /// parameter is the number of characters we can use for text regardless of the indent level. + /// For example, if columnWidth is 10 and indentLevel is 2, the input + /// "a string for wrapping 01234567890123" + /// would return + /// " a string" + newline + + /// " for" + newline + + /// " wrapping" + newline + + /// " 0123456789" + newline + + /// " 0123" + /// + /// A string that has been word-wrapped with padding on each line to indent it + private static string WrapAndIndentText(string input,int indentLevel,int columnWidth) + { + //start by splitting at newlines and then reinserting the newline as a separate word + var lines = input.Split(new[] {Environment.NewLine}, StringSplitOptions.None); + var lineCount = lines.Length; + + var tokens = lines + .Zip(new string[lineCount], (a, _) => new string[] {a, Environment.NewLine}) + .SelectMany(linePair=>linePair) + .Take(lineCount * 2 - 1); + + //split into words + var words= tokens + .SelectMany(l=>l.Split(' ')); + + //create a list of individual indented lines + var wrappedLines = words + .Aggregate>( + new List(), + (lineList,word)=>AddWordToLastLineOrCreateNewLineIfNecessary(lineList,word,columnWidth) + ) + .Select(builder => indentLevel.Spaces() + builder.ToString().TrimEnd()); + + //return the whole thing as a single string + return string.Join(Environment.NewLine,wrappedLines); + } + + /// + /// When presented with a word, either append to the last line in the list or start a new line + /// + /// A list of stringbuilders containing results so far + /// The individual word to append + /// The usable text space + /// + /// The 'word' can actually be an empty string or a linefeed. It's important to keep these - + /// empty strings allow us to preserve indentation and extra spaces within a line and linefeeds + /// allow us to honour the users formatting wishes when the pass in multi-line helptext. + /// + /// The same list as is passed in + private static List AddWordToLastLineOrCreateNewLineIfNecessary(List lines, string word,int columnWidth) + { + if (word == Environment.NewLine) + { + //A newline token just means advance to the next line. + lines.Add(new StringBuilder()); + return lines; + } + //The current indentLevel is based on the previous line. + var previousLine = lines.LastOrDefault()?.ToString() ??string.Empty; + var currentIndentLevel = previousLine.Length - previousLine.TrimStart().Length; + + var wouldWrap = !lines.Any() || previousLine.Length + word.Length > columnWidth; + + if (!wouldWrap) + { + //The usual case is we just append the 'word' and a space to the current line + //Note that trailing spaces will get removed later when we turn the line list + //into a single string + lines.Last().Append(word + ' '); + } + else + { + //The 'while' here is to take account of the possibility of someone providing a word + //which just can't fit in the current column. In that case we just split it at the + //column end. + //That's a rare case though - most of the time we'll succeed in a single pass without + //having to split + while (word.Length >0) + { + var availableCharacters = Math.Min(columnWidth - currentIndentLevel,word.Length); + + var segmentToAdd = currentIndentLevel.Spaces() + + word.Substring(0, availableCharacters) + ' '; + + lines.Add(new StringBuilder(segmentToAdd)); + word = word.Substring(availableCharacters); + } + } + return lines; + } + + } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs new file mode 100644 index 00000000..afa77f3a --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaksAndSubIndentation_Options.cs @@ -0,0 +1,13 @@ +namespace CommandLine.Tests.Fakes +{ + public class HelpTextWithLineBreaksAndSubIndentation_Options + { + + [Option(HelpText = @"This is a help text description where we want: + * The left pad after a linebreak to be honoured and the indentation to be preserved across to the next line + * The ability to return to no indent. +Like this.")] + public string StringValue { get; set; } + + } +} \ No newline at end of file diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs new file mode 100644 index 00000000..c93e73aa --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs @@ -0,0 +1,23 @@ +namespace CommandLine.Tests.Fakes +{ + public class HelpTextWithLineBreaks_Options + { + [Option(HelpText = + @"This is a help text description. +It has multiple lines. +We also want to ensure that indentation is correct.")] + public string StringValue { get; set; } + + + [Option(HelpText = @"This is a help text description where we want + The left pad after a linebreak to be honoured so that + we can sub-indent within a description.")] + public string StringValu2 { get; set; } + + + [Option(HelpText = @"This is a help text description where we want + The left pad after a linebreak to be honoured and the indentation to be preserved across to the next line in a way that looks pleasing")] + public string StringValu3 { get; set; } + + } +} diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 7c4d7590..8412e66e 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -77,6 +77,8 @@ public void Create_instance_with_options() // Teardown } + + //[Fact] public void Create_instance_with_enum_options_enabled() { @@ -154,10 +156,10 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c var lines = sut.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); lines[2].Should().BeEquivalentTo(" v, verbose This is the description"); //"The first line should have the arguments and the start of the Help Text."); //string formattingMessage = "Beyond the second line should be formatted as though it's in a column."; - lines[3].Should().BeEquivalentTo(" of the verbosity to "); - lines[4].Should().BeEquivalentTo(" test out the wrapping "); - lines[5].Should().BeEquivalentTo(" capabilities of the "); - lines[6].Should().BeEquivalentTo(" Help Text."); + lines[3].Should().BeEquivalentTo(" of the verbosity to test"); + lines[4].Should().BeEquivalentTo(" out the wrapping"); + lines[5].Should().BeEquivalentTo(" capabilities of the Help"); + lines[6].Should().BeEquivalentTo(" Text."); // Teardown } @@ -176,7 +178,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c // Verify outcome var lines = sut.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); - lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the wrapping capabilities of "); //"The first line should have the arguments and the start of the Help Text."); + lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the wrapping capabilities of"); //"The first line should have the arguments and the start of the Help Text."); //string formattingMessage = "Beyond the second line should be formatted as though it's in a column."; lines[3].Should().BeEquivalentTo(" the Help Text."); // Teardown @@ -216,10 +218,10 @@ public void Long_help_text_without_spaces() // Verify outcome var lines = sut.ToString().ToNotEmptyLines(); - lines[1].Should().BeEquivalentTo(" v, verbose Before "); + lines[1].Should().BeEquivalentTo(" v, verbose Before"); lines[2].Should().BeEquivalentTo(" 012345678901234567890123"); lines[3].Should().BeEquivalentTo(" After"); - lines[4].Should().BeEquivalentTo(" input-file Before "); + lines[4].Should().BeEquivalentTo(" input-file Before"); lines[5].Should().BeEquivalentTo(" 012345678901234567890123"); lines[6].Should().BeEquivalentTo(" 456789 After"); // Teardown @@ -238,12 +240,12 @@ public void Long_pre_and_post_lines_without_spaces() // Verify outcome var lines = sut.ToString().ToNotEmptyLines(); - lines[1].Should().BeEquivalentTo("Before "); + lines[1].Should().BeEquivalentTo("Before"); lines[2].Should().BeEquivalentTo("0123456789012345678901234567890123456789"); lines[3].Should().BeEquivalentTo("012 After"); - lines[lines.Length - 3].Should().BeEquivalentTo("Before "); + lines[lines.Length - 3].Should().BeEquivalentTo("Before"); lines[lines.Length - 2].Should().BeEquivalentTo("0123456789012345678901234567890123456789"); - lines[lines.Length - 1].Should().BeEquivalentTo(" After"); + lines[lines.Length - 1].Should().BeEquivalentTo("After"); // Teardown } @@ -653,5 +655,68 @@ public void Add_line_with_two_empty_spaces_at_the_end() Assert.Equal("T" + Environment.NewLine + "e" + Environment.NewLine + "s" + Environment.NewLine + "t", b.ToString()); } + + [Fact] + public void HelpTextHonoursLineBreaks() + { + // Fixture setup + // Exercize system + var sut = new HelpText {AddDashesToOption = true} + .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaks_Options)), + Enumerable.Empty())); + + // Verify outcome + + var lines = sut.ToString().ToNotEmptyLines(); + lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description."); + lines[1].Should().BeEquivalentTo(" It has multiple lines."); + lines[2].Should().BeEquivalentTo(" We also want to ensure that indentation is correct."); + + // Teardown + } + + [Fact] + public void HelpTextHonoursIndentationAfterLineBreaks() + { + // Fixture setup + // Exercize system + var sut = new HelpText {AddDashesToOption = true} + .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaks_Options)), + Enumerable.Empty())); + + // Verify outcome + + var lines = sut.ToString().ToNotEmptyLines(); + lines[3].Should().BeEquivalentTo(" --stringvalu2 This is a help text description where we want"); + lines[4].Should().BeEquivalentTo(" the left pad after a linebreak to be honoured so that"); + lines[5].Should().BeEquivalentTo(" we can sub-indent within a description."); + + // Teardown + } + + [Fact] + public void HelpTextPreservesIndentationAcrossWordWrap() + { + // Fixture setup + // Exercise system + var sut = new HelpText {AddDashesToOption = true,MaximumDisplayWidth = 60} + .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaksAndSubIndentation_Options)), + Enumerable.Empty())); + + // Verify outcome + + var lines = sut.ToString().ToNotEmptyLines(); + lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description where we"); + lines[1].Should().BeEquivalentTo(" want:"); + lines[2].Should().BeEquivalentTo(" * The left pad after a linebreak to"); + lines[3].Should().BeEquivalentTo(" be honoured and the indentation to be"); + lines[4].Should().BeEquivalentTo(" preserved across to the next line"); + lines[5].Should().BeEquivalentTo(" * The ability to return to no indent."); + lines[6].Should().BeEquivalentTo(" Like this."); + + // Teardown + } + + } } From f5aebb0b9fdc812cd189694325f1bed635eba7a6 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Thu, 6 Jun 2019 19:34:48 +0100 Subject: [PATCH 036/198] Better portability for line-break handling --- src/CommandLine/Text/HelpText.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 5a1b6953..dc5a1c0d 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -971,7 +971,12 @@ private static string FormatDefaultValue(T value) private static string WrapAndIndentText(string input,int indentLevel,int columnWidth) { //start by splitting at newlines and then reinserting the newline as a separate word - var lines = input.Split(new[] {Environment.NewLine}, StringSplitOptions.None); + //Note that on the input side, we can't assume the line-break style at run time so we have to + //be able to handle both. We cant use Environment.NewLine because that changes at + //_runtime_ and may not match the line-break style that was compiled in + var lines = input + .Replace("\r","") + .Split(new[] {'\n'}, StringSplitOptions.None); var lineCount = lines.Length; var tokens = lines From e30e5a6afbfc8cb36d59c3467132531d50151353 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Thu, 6 Jun 2019 19:44:36 +0100 Subject: [PATCH 037/198] Remove a couple of spurious indentation changes --- src/CommandLine/Text/HelpText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index dc5a1c0d..13129b37 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -657,7 +657,7 @@ public override string ToString() .ToString(); } - internal static void AddLine(StringBuilder builder, string value, int maximumLength) + internal static void AddLine(StringBuilder builder, string value, int maximumLength) { if (builder == null) { @@ -730,7 +730,7 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable return optionSpecs; } - private HelpText AddOptionsImpl( + private HelpText AddOptionsImpl( IEnumerable specifications, string requiredWord, int maximumLength) From 0a0fa208a008cf5707582ba6a2ab665497fa1532 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Thu, 6 Jun 2019 19:59:31 +0100 Subject: [PATCH 038/198] Add extra test to ensure mixed line-end styles are handled correctly --- .../HelpTextWithMixedLineBreaks_Options.cs | 9 +++++++++ .../Unit/Text/HelpTextTests.cs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs new file mode 100644 index 00000000..9950dbc7 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/HelpTextWithMixedLineBreaks_Options.cs @@ -0,0 +1,9 @@ +namespace CommandLine.Tests.Fakes +{ + public class HelpTextWithMixedLineBreaks_Options + { + [Option(HelpText = + "This is a help text description\n It has multiple lines.\r\n Third line")] + public string StringValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 8412e66e..f384e4dd 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -718,5 +718,24 @@ public void HelpTextPreservesIndentationAcrossWordWrap() } + [Fact] + public void HelpTextIsConsitentRegardlessOfCompileTimeLineStyle() + { + // Fixture setup + // Exercize system + var sut = new HelpText {AddDashesToOption = true} + .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithMixedLineBreaks_Options)), + Enumerable.Empty())); + + // Verify outcome + + var lines = sut.ToString().ToNotEmptyLines(); + lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description"); + lines[1].Should().BeEquivalentTo(" It has multiple lines."); + lines[2].Should().BeEquivalentTo(" Third line"); + + // Teardown + } + } } From ac9e31c5aa9840d3647a8f4f0d8c17b370cda14e Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sun, 9 Jun 2019 14:30:58 +0100 Subject: [PATCH 039/198] Move TextWrapper code in separate class to aid testability --- src/CommandLine/Text/HelpText.cs | 112 +----------- src/CommandLine/Text/TextWrapper.cs | 173 ++++++++++++++++++ .../Unit/Core/TextWrapperTests.cs | 170 +++++++++++++++++ 3 files changed, 348 insertions(+), 107 deletions(-) create mode 100644 src/CommandLine/Text/TextWrapper.cs create mode 100644 tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 13129b37..575c6fbc 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -677,7 +677,7 @@ internal static void AddLine(StringBuilder builder, string value, int maximumLen value = value.TrimEnd(); builder.AppendWhen(builder.Length > 0, Environment.NewLine); - builder.Append(WrapAndIndentText(value, 0, maximumLength)); + builder.Append(TextWrapper.WrapAndIndentText(value, 0, maximumLength)); } private IEnumerable GetSpecificationsFromType(Type type) @@ -806,7 +806,7 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe //note that we need to indent trim the start of the string because it's going to be //appended to an existing line that is as long as the indent-level - var indented = WrapAndIndentText(optionHelpText, maxLength+TotalOptionPadding, widthOfHelpText).TrimStart(); + var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength+TotalOptionPadding, widthOfHelpText).TrimStart(); optionsHelp .Append(indented) @@ -949,111 +949,9 @@ private static string FormatDefaultValue(T value) : string.Empty; } - /// - /// Splits a string into a words and performs wrapping while also preserving line-breaks and sub-indentation - /// - /// The string to wrap - /// The amount of padding at the start of each string - /// The number of characters we can use for text - /// - /// The use of "width" is slightly confusing in other methods. In this method, the columnWidth - /// parameter is the number of characters we can use for text regardless of the indent level. - /// For example, if columnWidth is 10 and indentLevel is 2, the input - /// "a string for wrapping 01234567890123" - /// would return - /// " a string" + newline + - /// " for" + newline + - /// " wrapping" + newline + - /// " 0123456789" + newline + - /// " 0123" - /// - /// A string that has been word-wrapped with padding on each line to indent it - private static string WrapAndIndentText(string input,int indentLevel,int columnWidth) - { - //start by splitting at newlines and then reinserting the newline as a separate word - //Note that on the input side, we can't assume the line-break style at run time so we have to - //be able to handle both. We cant use Environment.NewLine because that changes at - //_runtime_ and may not match the line-break style that was compiled in - var lines = input - .Replace("\r","") - .Split(new[] {'\n'}, StringSplitOptions.None); - var lineCount = lines.Length; - - var tokens = lines - .Zip(new string[lineCount], (a, _) => new string[] {a, Environment.NewLine}) - .SelectMany(linePair=>linePair) - .Take(lineCount * 2 - 1); - - //split into words - var words= tokens - .SelectMany(l=>l.Split(' ')); - - //create a list of individual indented lines - var wrappedLines = words - .Aggregate>( - new List(), - (lineList,word)=>AddWordToLastLineOrCreateNewLineIfNecessary(lineList,word,columnWidth) - ) - .Select(builder => indentLevel.Spaces() + builder.ToString().TrimEnd()); - - //return the whole thing as a single string - return string.Join(Environment.NewLine,wrappedLines); - } - - /// - /// When presented with a word, either append to the last line in the list or start a new line - /// - /// A list of stringbuilders containing results so far - /// The individual word to append - /// The usable text space - /// - /// The 'word' can actually be an empty string or a linefeed. It's important to keep these - - /// empty strings allow us to preserve indentation and extra spaces within a line and linefeeds - /// allow us to honour the users formatting wishes when the pass in multi-line helptext. - /// - /// The same list as is passed in - private static List AddWordToLastLineOrCreateNewLineIfNecessary(List lines, string word,int columnWidth) - { - if (word == Environment.NewLine) - { - //A newline token just means advance to the next line. - lines.Add(new StringBuilder()); - return lines; - } - //The current indentLevel is based on the previous line. - var previousLine = lines.LastOrDefault()?.ToString() ??string.Empty; - var currentIndentLevel = previousLine.Length - previousLine.TrimStart().Length; - - var wouldWrap = !lines.Any() || previousLine.Length + word.Length > columnWidth; - - if (!wouldWrap) - { - //The usual case is we just append the 'word' and a space to the current line - //Note that trailing spaces will get removed later when we turn the line list - //into a single string - lines.Last().Append(word + ' '); - } - else - { - //The 'while' here is to take account of the possibility of someone providing a word - //which just can't fit in the current column. In that case we just split it at the - //column end. - //That's a rare case though - most of the time we'll succeed in a single pass without - //having to split - while (word.Length >0) - { - var availableCharacters = Math.Min(columnWidth - currentIndentLevel,word.Length); - - var segmentToAdd = currentIndentLevel.Spaces() + - word.Substring(0, availableCharacters) + ' '; - - lines.Add(new StringBuilder(segmentToAdd)); - word = word.Substring(availableCharacters); - } - } - return lines; - } - + } } + + diff --git a/src/CommandLine/Text/TextWrapper.cs b/src/CommandLine/Text/TextWrapper.cs new file mode 100644 index 00000000..15c22d76 --- /dev/null +++ b/src/CommandLine/Text/TextWrapper.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using CommandLine.Infrastructure; + +namespace CommandLine.Text +{ + /// + /// A utility class to word-wrap and indent blocks of text + /// + public class TextWrapper + { + private string[] lines; + public TextWrapper(string input) + { + //start by splitting at newlines and then reinserting the newline as a separate word + //Note that on the input side, we can't assume the line-break style at run time so we have to + //be able to handle both. We can't use Environment.NewLine because that changes at + //_runtime_ and may not match the line-break style that was compiled in + lines = input + .Replace("\r","") + .Split(new[] {'\n'}, StringSplitOptions.None); + } + + /// + /// Splits a string into a words and performs wrapping while also preserving line-breaks and sub-indentation + /// + /// The number of characters we can use for text + /// + /// This method attempts to wrap text without breaking words + /// For example, if columnWidth is 10 , the input + /// "a string for wrapping 01234567890123" + /// would return + /// "a string + /// "for + /// "wrapping + /// "0123456789 + /// "0123" + /// + /// this + public TextWrapper WordWrap(int columnWidth) + { + + lines= lines + .SelectMany(line => WordWrapLine(line, columnWidth)) + .ToArray(); + return this; + } + + /// + /// Indent all lines in the TextWrapper by the desired number of spaces + /// + /// The number of spaces to indent by + /// this + public TextWrapper Indent(int numberOfSpaces) + { + lines = lines + .Select(line => numberOfSpaces.Spaces() + line) + .ToArray(); + return this; + } + + /// + /// Returns the current state of the TextWrapper as a string + /// + /// + public string ToText() + { + //return the whole thing as a single string + return string.Join(Environment.NewLine,lines); + } + + /// + /// Convenience method to wraps and indent a string in a single operation + /// + /// The string to operate on + /// The number of spaces to indent by + /// The width of the column used for wrapping + /// + /// The string is wrapped _then_ indented so the columnWidth is the width of the + /// usable text block, and does NOT include the indentLevel. + /// + /// the processed string + public static string WrapAndIndentText(string input, int indentLevel,int columnWidth) + { + return new TextWrapper(input) + .WordWrap(columnWidth) + .Indent(indentLevel) + .ToText(); + } + + + private string [] WordWrapLine(string line,int columnWidth) + { + //create a list of individual lines generated from the supplied line + + //When handling sub-indentation we must always reserve at least one column for text! + var unindentedLine = line.TrimStart(); + var currentIndentLevel = Math.Min(line.Length - unindentedLine.Length,columnWidth-1) ; + columnWidth -= currentIndentLevel; + + return unindentedLine.Split(' ') + .Aggregate( + new List(), + (lineList, word) => AddWordToLastLineOrCreateNewLineIfNecessary(lineList, word, columnWidth) + ) + .Select(builder => currentIndentLevel.Spaces()+builder.ToString().TrimEnd()) + .ToArray(); + } + + /// + /// When presented with a word, either append to the last line in the list or start a new line + /// + /// A list of StringBuilders containing results so far + /// The individual word to append + /// The usable text space + /// + /// The 'word' can actually be an empty string. It's important to keep these - + /// empty strings allow us to preserve indentation and extra spaces within a line. + /// + /// The same list as is passed in + private static List AddWordToLastLineOrCreateNewLineIfNecessary(List lines, string word,int columnWidth) + { + //The current indentation level is based on the previous line but we need to be careful + var previousLine = lines.LastOrDefault()?.ToString() ??string.Empty; + + var wouldWrap = !lines.Any() || (word.Length>0 && previousLine.Length + word.Length > columnWidth); + + if (!wouldWrap) + { + //The usual case is we just append the 'word' and a space to the current line + //Note that trailing spaces will get removed later when we turn the line list + //into a single string + lines.Last().Append(word + ' '); + } + else + { + //The 'while' here is to take account of the possibility of someone providing a word + //which just can't fit in the current column. In that case we just split it at the + //column end. + //That's a rare case though - most of the time we'll succeed in a single pass without + //having to split + //Note that we always do at least one pass even if the 'word' is empty in order to + //honour sub-indentation and extra spaces within strings + do + { + var availableCharacters = Math.Min(columnWidth, word.Length); + var segmentToAdd = LeftString(word,availableCharacters) + ' '; + lines.Add(new StringBuilder(segmentToAdd)); + word = RightString(word,availableCharacters); + } while (word.Length > 0); + } + return lines; + } + + + private static string RightString(string str,int n) + { + return (n >= str.Length || str.Length==0) + ? string.Empty + : str.Substring(n); + } + + private static string LeftString(string str,int n) + { + + return (n >= str.Length || str.Length==0) + ? str + : str.Substring(0,n); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs new file mode 100644 index 00000000..eaa52d1d --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs @@ -0,0 +1,170 @@ +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; + +namespace CommandLine.Tests.Unit.Core +{ + public class TextWrapperTests + { + [Fact] + public void IndentWorksCorrectly() + { + + var input = + @"line1 +line2"; + var expected = @" line1 + line2"; + var wrapper = new TextWrapper(input); + wrapper.Indent(2).ToText().Should().Be(expected); + + } + + [Fact] + public void SimpleWrappingIsAsExpected() + { + + var input = + @"here is some text that needs wrapping"; + var expected = @"here is +some text +that needs +wrapping"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(10).ToText().Should().Be(expected); + + } + + [Fact] + public void WrappingAvoidsBreakingWords() + { + + var input = + @"here hippopotamus is some text that needs wrapping"; + var expected = @"here +hippopotamus is +some text that +needs wrapping"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(15).ToText().Should().Be(expected); + + } + + [Fact] + public void WrappingObeysLineBreaksOfAllStyles() + { + + var input = + "here is some text\nthat needs\r\nwrapping"; + var expected = @"here is some text +that needs +wrapping"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + + [Fact] + public void WrappingPreservesSubIndentation() + { + + var input = + "here is some text\n that needs wrapping where we want the wrapped part to preserve indentation\nand this part to not be indented"; + var expected = @"here is some text + that needs + wrapping where we + want the wrapped + part to preserve + indentation +and this part to not +be indented"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + [Fact] + public void LongWordsAreBroken() + { + + var input = + "here is some text that contains a veryLongWordThatWontFitOnASingleLine"; + var expected = @"here is some text +that contains a +veryLongWordThatWont +FitOnASingleLine"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + [Fact] + public void SubIndentationIsPreservedWhenBreakingWords() + { + + var input = + "here is some text that contains \n a veryLongWordThatWontFitOnASingleLine"; + var expected = @"here is some text +that contains + a + veryLongWordThatWo + ntFitOnASingleLine"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + [Fact] + public void SpacesWithinStringAreRespected() + { + + var input = + "here is some text with some extra spacing"; + var expected = @"here is some +text with some extra +spacing"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + + [Fact] + public void ExtraSpacesAreTreatedAsNonBreaking() + { + + var input = + "here is some text with some extra spacing"; + var expected = @"here is some text +with some extra +spacing"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + + [Fact] + public void WrappingExtraSpacesObeySubIndent() + { + + var input = + "here is some\n text with some extra spacing"; + var expected = @"here is some + text + with some extra + spacing"; + var wrapper = new TextWrapper(input); + wrapper.WordWrap(20).ToText().Should().Be(expected); + + } + + + + } + + + +} From c78b312dff496fe6324449229f595450627299e5 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sun, 9 Jun 2019 14:37:53 +0100 Subject: [PATCH 040/198] Add some comments to kick AppVeyor again --- src/CommandLine/Text/TextWrapper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Text/TextWrapper.cs b/src/CommandLine/Text/TextWrapper.cs index 15c22d76..5af21576 100644 --- a/src/CommandLine/Text/TextWrapper.cs +++ b/src/CommandLine/Text/TextWrapper.cs @@ -155,13 +155,18 @@ private static List AddWordToLastLineOrCreateNewLineIfNecessary(L } + /// + /// Return the right part of a string in a way that compensates for Substring's deficiencies + /// private static string RightString(string str,int n) { return (n >= str.Length || str.Length==0) ? string.Empty : str.Substring(n); } - + /// + /// Return the left part of a string in a way that compensates for Substring's deficiencies + /// private static string LeftString(string str,int n) { From 6224f26e07e76613102016cc7ac8e367e9026d20 Mon Sep 17 00:00:00 2001 From: Neil MacMullen Date: Sun, 9 Jun 2019 14:45:43 +0100 Subject: [PATCH 041/198] Workaround line-end issue on build server --- .../Unit/Core/TextWrapperTests.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs index eaa52d1d..bcba9095 100644 --- a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs @@ -7,6 +7,17 @@ namespace CommandLine.Tests.Unit.Core { public class TextWrapperTests { + private string NormalizeLineBreaks(string str) + { + return str.Replace("\r", ""); + } + private void EnsureEquivalent(string a,string b) + { + //workaround build system line-end inconsistencies + NormalizeLineBreaks(a).Should().Be(NormalizeLineBreaks(b)); + } + + [Fact] public void IndentWorksCorrectly() { @@ -17,7 +28,7 @@ public void IndentWorksCorrectly() var expected = @" line1 line2"; var wrapper = new TextWrapper(input); - wrapper.Indent(2).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.Indent(2).ToText(),expected); } @@ -32,7 +43,7 @@ some text that needs wrapping"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(10).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(10).ToText(),expected); } @@ -47,7 +58,7 @@ hippopotamus is some text that needs wrapping"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(15).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(15).ToText(),expected); } @@ -61,7 +72,7 @@ public void WrappingObeysLineBreaksOfAllStyles() that needs wrapping"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } @@ -81,7 +92,7 @@ part to preserve and this part to not be indented"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } @@ -96,7 +107,7 @@ that contains a veryLongWordThatWont FitOnASingleLine"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } @@ -112,7 +123,7 @@ that contains veryLongWordThatWo ntFitOnASingleLine"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } @@ -126,7 +137,7 @@ public void SpacesWithinStringAreRespected() text with some extra spacing"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } @@ -141,7 +152,7 @@ public void ExtraSpacesAreTreatedAsNonBreaking() with some extra spacing"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } @@ -157,7 +168,7 @@ public void WrappingExtraSpacesObeySubIndent() with some extra spacing"; var wrapper = new TextWrapper(input); - wrapper.WordWrap(20).ToText().Should().Be(expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); } From 5573afbe55af77f7876cb96a40b8d4234732f841 Mon Sep 17 00:00:00 2001 From: neil macmullen Date: Fri, 28 Jun 2019 09:10:32 +0100 Subject: [PATCH 042/198] Fix bug where TextWrapper didn't cope gracefully with negative columnWidth. Also fix up old broken tests --- src/CommandLine/Text/TextWrapper.cs | 3 +- .../Unit/Core/TextWrapperTests.cs | 188 ++++++++++-------- .../Unit/Text/HelpTextTests.cs | 83 ++++---- 3 files changed, 149 insertions(+), 125 deletions(-) diff --git a/src/CommandLine/Text/TextWrapper.cs b/src/CommandLine/Text/TextWrapper.cs index 5af21576..19a93f15 100644 --- a/src/CommandLine/Text/TextWrapper.cs +++ b/src/CommandLine/Text/TextWrapper.cs @@ -41,7 +41,8 @@ public TextWrapper(string input) /// this public TextWrapper WordWrap(int columnWidth) { - + //ensure we always use at least 1 column even if the client has told us there's no space available + columnWidth = Math.Max(1, columnWidth); lines= lines .SelectMany(line => WordWrapLine(line, columnWidth)) .ToArray(); diff --git a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs index bcba9095..1db7497e 100644 --- a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs @@ -1,4 +1,5 @@ -using CommandLine.Tests.Fakes; +using System; +using System.Linq; using CommandLine.Text; using FluentAssertions; using Xunit; @@ -11,110 +12,120 @@ private string NormalizeLineBreaks(string str) { return str.Replace("\r", ""); } - private void EnsureEquivalent(string a,string b) + + private void EnsureEquivalent(string a, string b) { //workaround build system line-end inconsistencies NormalizeLineBreaks(a).Should().Be(NormalizeLineBreaks(b)); } - + [Fact] - public void IndentWorksCorrectly() + public void ExtraSpacesAreTreatedAsNonBreaking() { + var input = + "here is some text with some extra spacing"; + var expected = @"here is some text +with some extra +spacing"; + var wrapper = new TextWrapper(input); + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); + } + + [Fact] + public void IndentWorksCorrectly() + { var input = @"line1 line2"; var expected = @" line1 line2"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.Indent(2).ToText(),expected); - + EnsureEquivalent(wrapper.Indent(2).ToText(), expected); } [Fact] - public void SimpleWrappingIsAsExpected() + public void LongWordsAreBroken() { - var input = - @"here is some text that needs wrapping"; - var expected = @"here is -some text -that needs -wrapping"; + "here is some text that contains a veryLongWordThatWontFitOnASingleLine"; + var expected = @"here is some text +that contains a +veryLongWordThatWont +FitOnASingleLine"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(10).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); } [Fact] - public void WrappingAvoidsBreakingWords() + public void NegativeColumnWidthStillProducesOutput() { - - var input = - @"here hippopotamus is some text that needs wrapping"; - var expected = @"here -hippopotamus is -some text that -needs wrapping"; + var input = @"test"; + var expected = string.Join(Environment.NewLine, input.Select(c => c.ToString())); var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(15).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(-1).ToText(), expected); } [Fact] - public void WrappingObeysLineBreaksOfAllStyles() + public void SimpleWrappingIsAsExpected() { - var input = - "here is some text\nthat needs\r\nwrapping"; - var expected = @"here is some text + @"here is some text that needs wrapping"; + var expected = @"here is +some text that needs wrapping"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(10).ToText(), expected); } - [Fact] - public void WrappingPreservesSubIndentation() + public void SingleColumnStillProducesOutputForSubIndentation() { - - var input = - "here is some text\n that needs wrapping where we want the wrapped part to preserve indentation\nand this part to not be indented"; - var expected = @"here is some text - that needs - wrapping where we - want the wrapped - part to preserve - indentation -and this part to not -be indented"; + var input = @"test + ind"; + + var expected = @"t +e +s +t +i +n +d"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(-1).ToText(), expected); } [Fact] - public void LongWordsAreBroken() + public void SpacesWithinStringAreRespected() { - var input = - "here is some text that contains a veryLongWordThatWontFitOnASingleLine"; - var expected = @"here is some text -that contains a -veryLongWordThatWont -FitOnASingleLine"; + "here is some text with some extra spacing"; + var expected = @"here is some +text with some extra +spacing"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); + } + [Fact] + public void SubIndentationCorrectlyWrapsWhenColumnWidthRequiresIt() + { + var input = @"test + indented"; + var expected = @"test + in + de + nt + ed"; + var wrapper = new TextWrapper(input); + EnsureEquivalent(wrapper.WordWrap(6).ToText(), expected); } [Fact] public void SubIndentationIsPreservedWhenBreakingWords() { - var input = "here is some text that contains \n a veryLongWordThatWontFitOnASingleLine"; var expected = @"here is some text @@ -123,44 +134,26 @@ that contains veryLongWordThatWo ntFitOnASingleLine"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); } [Fact] - public void SpacesWithinStringAreRespected() + public void WrappingAvoidsBreakingWords() { - var input = - "here is some text with some extra spacing"; - var expected = @"here is some -text with some extra -spacing"; + @"here hippopotamus is some text that needs wrapping"; + var expected = @"here +hippopotamus is +some text that +needs wrapping"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(15).ToText(), expected); } - - [Fact] - public void ExtraSpacesAreTreatedAsNonBreaking() - { - var input = - "here is some text with some extra spacing"; - var expected = @"here is some text -with some extra -spacing"; - var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); - - } - - [Fact] public void WrappingExtraSpacesObeySubIndent() { - var input = "here is some\n text with some extra spacing"; var expected = @"here is some @@ -168,14 +161,37 @@ public void WrappingExtraSpacesObeySubIndent() with some extra spacing"; var wrapper = new TextWrapper(input); - EnsureEquivalent(wrapper.WordWrap(20).ToText(),expected); - + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); } + [Fact] + public void WrappingObeysLineBreaksOfAllStyles() + { + var input = + "here is some text\nthat needs\r\nwrapping"; + var expected = @"here is some text +that needs +wrapping"; + var wrapper = new TextWrapper(input); + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); + } + [Fact] + public void WrappingPreservesSubIndentation() + { + var input = + "here is some text\n that needs wrapping where we want the wrapped part to preserve indentation\nand this part to not be indented"; + var expected = @"here is some text + that needs + wrapping where we + want the wrapped + part to preserve + indentation +and this part to not +be indented"; + var wrapper = new TextWrapper(input); + EnsureEquivalent(wrapper.WordWrap(20).ToText(), expected); + } } - - - } diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index f384e4dd..d3d51254 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -79,7 +79,7 @@ public void Create_instance_with_options() - //[Fact] + [Fact] public void Create_instance_with_enum_options_enabled() { // Fixture setup @@ -184,7 +184,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c // Teardown } - //[Fact] + [Fact] public void When_help_text_has_hidden_option_it_should_not_be_added_to_help_text_output() { // Fixture setup @@ -198,7 +198,7 @@ public void When_help_text_has_hidden_option_it_should_not_be_added_to_help_text // Verify outcome var lines = sut.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); - lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the "); //"The first line should have the arguments and the start of the Help Text."); + lines[2].Should().BeEquivalentTo(" v, verbose This is the description of the verbosity to test out the"); //"The first line should have the arguments and the start of the Help Text."); //string formattingMessage = "Beyond the second line should be formatted as though it's in a column."; lines[3].Should().BeEquivalentTo(" wrapping capabilities of the Help Text."); // Teardown @@ -309,7 +309,7 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() { // Fixture setup @@ -330,9 +330,7 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() lines[0].Should().StartWithEquivalent("CommandLine"); lines[1].Should().StartWithEquivalent("Copyright (c)"); #else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); + // The first two lines depend on the test-runner so ignore them #endif lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); @@ -345,7 +343,7 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_formatted_text() { // Fixture setup @@ -366,9 +364,7 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo lines[0].Should().StartWithEquivalent("CommandLine"); lines[1].Should().StartWithEquivalent("Copyright (c)"); #else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); + // The first two lines depend on the test-runner so ignore them #endif lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which"); lines[3].Should().BeEquivalentTo("changes to commit."); @@ -378,7 +374,7 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_formatted_text_given_display_width_100() { // Fixture setup @@ -399,9 +395,7 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo lines[0].Should().StartWithEquivalent("CommandLine"); lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); #else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); + // The first two lines depend on the test-runner so ignore them #endif lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which changes to commit."); lines[3].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); @@ -410,7 +404,7 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Verbs_with_unknown_verb_returns_appropriate_formatted_text() { // Fixture setup @@ -431,9 +425,7 @@ public void Invoke_AutoBuild_for_Verbs_with_unknown_verb_returns_appropriate_for lines[0].Should().StartWithEquivalent("CommandLine"); lines[1].Should().StartWithEquivalent("Copyright (c)"); #else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); + // The first two lines depend on the test-runner so ignore them #endif lines[2].Should().BeEquivalentTo("add Add file contents to the index."); lines[3].Should().BeEquivalentTo("commit Record changes to the repository."); @@ -496,7 +488,7 @@ public static void RenderUsageText_returns_properly_formatted_text() lines[10].Should().BeEquivalentTo(" mono testapp.exe value"); } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatted_text() { // Fixture setup @@ -518,32 +510,32 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte lines[1].Should().StartWithEquivalent("Copyright (c)"); #else // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); + //the first lines may depend on the test-runner (Ncrunch in my case) so ignore them + #endif lines[2].Should().BeEquivalentTo("ERROR(S):"); - lines[3].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); + lines[3].Should().BeEquivalentTo(" Token 'badtoken' is not recognized."); lines[4].Should().BeEquivalentTo("USAGE:"); lines[5].Should().BeEquivalentTo("Normal scenario:"); - lines[6].Should().BeEquivalentTo("mono testapp.exe --input file.bin --output out.bin"); + lines[6].Should().BeEquivalentTo(" mono testapp.exe --input file.bin --output out.bin"); lines[7].Should().BeEquivalentTo("Logging warnings:"); - lines[8].Should().BeEquivalentTo("mono testapp.exe -w --input file.bin"); + lines[8].Should().BeEquivalentTo(" mono testapp.exe -w --input file.bin"); lines[9].Should().BeEquivalentTo("Logging errors:"); - lines[10].Should().BeEquivalentTo("mono testapp.exe -e --input file.bin"); - lines[11].Should().BeEquivalentTo("mono testapp.exe --errs --input=file.bin"); + lines[10].Should().BeEquivalentTo(" mono testapp.exe -e --input file.bin"); + lines[11].Should().BeEquivalentTo(" mono testapp.exe --errs --input=file.bin"); lines[12].Should().BeEquivalentTo("List:"); - lines[13].Should().BeEquivalentTo("mono testapp.exe -l 1,2"); + lines[13].Should().BeEquivalentTo(" mono testapp.exe -l 1,2"); lines[14].Should().BeEquivalentTo("Value:"); - lines[15].Should().BeEquivalentTo("mono testapp.exe value"); - lines[16].Should().BeEquivalentTo("-i, --input Set input file."); - lines[17].Should().BeEquivalentTo("-i, --output Set output file."); - lines[18].Should().BeEquivalentTo("--verbose Set verbosity level."); - lines[19].Should().BeEquivalentTo("-w, --warns Log warnings."); - lines[20].Should().BeEquivalentTo("-e, --errs Log errors."); - lines[21].Should().BeEquivalentTo("-l List."); - lines[22].Should().BeEquivalentTo("--help Display this help screen."); - lines[23].Should().BeEquivalentTo("--version Display version information."); - lines[24].Should().BeEquivalentTo("value pos. 0 Value."); + lines[15].Should().BeEquivalentTo(" mono testapp.exe value"); + lines[16].Should().BeEquivalentTo(" -i, --input Set input file."); + lines[17].Should().BeEquivalentTo(" -i, --output Set output file."); + lines[18].Should().BeEquivalentTo(" --verbose Set verbosity level."); + lines[19].Should().BeEquivalentTo(" -w, --warns Log warnings."); + lines[20].Should().BeEquivalentTo(" -e, --errs Log errors."); + lines[21].Should().BeEquivalentTo(" -l List."); + lines[22].Should().BeEquivalentTo(" --help Display this help screen."); + lines[23].Should().BeEquivalentTo(" --version Display version information."); + lines[24].Should().BeEquivalentTo(" value pos. 0 Value."); // Teardown } @@ -736,6 +728,21 @@ public void HelpTextIsConsitentRegardlessOfCompileTimeLineStyle() // Teardown } + [Fact] + public void HelpTextPreservesIndentationAcrossWordWrapWithSmallMaximumDisplayWidth() + { + // Fixture setup + // Exercise system + var sut = new HelpText {AddDashesToOption = true,MaximumDisplayWidth = 10} + .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaksAndSubIndentation_Options)), + Enumerable.Empty())); + // Verify outcome + + Assert.True(sut.ToString().Length>0); + + // Teardown + } + } } From 5a3828edd37808893db3a45bafabebd46a2c507c Mon Sep 17 00:00:00 2001 From: neil macmullen Date: Fri, 28 Jun 2019 18:41:46 +0100 Subject: [PATCH 043/198] Add another extra couple of tests --- tests/CommandLine.Tests/StringExtensions.cs | 5 +++ tests/CommandLine.Tests/Unit/ParserTests.cs | 20 ++++++++++++ .../Unit/Text/HelpTextTests.cs | 32 +++++++++++++++++-- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/tests/CommandLine.Tests/StringExtensions.cs b/tests/CommandLine.Tests/StringExtensions.cs index e3830b0c..1ea18538 100644 --- a/tests/CommandLine.Tests/StringExtensions.cs +++ b/tests/CommandLine.Tests/StringExtensions.cs @@ -13,6 +13,11 @@ public static string[] ToNotEmptyLines(this string value) return value.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); } + public static string[] ToLines(this string value) + { + return value.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + } + public static string[] TrimStringArray(this IEnumerable array) { return array.Select(item => item.Trim()).ToArray(); diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index c183c47d..15ca2e79 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -860,5 +860,25 @@ public void Parse_options_with_shuffled_index_values() Assert.Equal("two", args.Arg2); }); } + + + [Fact] + public void Blank_lines_are_inserted_between_verbs() + { + // Fixture setup + var help = new StringWriter(); + var sut = new Parser(config => config.HelpWriter = help); + + // Exercize system + sut.ParseArguments(new string[] { }); + var result = help.ToString(); + + // Verify outcome + var lines = result.ToLines().TrimStringArray(); + lines[6].Should().BeEquivalentTo("add Add file contents to the index."); + lines[8].Should().BeEquivalentTo("help Display more information on a specific command."); + lines[10].Should().BeEquivalentTo("version Display version information."); + // Teardown + } } } diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index d3d51254..b9d1531d 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -540,7 +540,7 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte // Teardown } -#if !PLATFORM_DOTNET + [Fact] public void Default_set_to_sequence_should_be_properly_printed() { @@ -566,7 +566,6 @@ public void Default_set_to_sequence_should_be_properly_printed() // Teardown } -#endif [Fact] public void AutoBuild_when_no_assembly_attributes() @@ -744,5 +743,34 @@ public void HelpTextPreservesIndentationAcrossWordWrapWithSmallMaximumDisplayWid // Teardown } + + + [Fact] + public void Options_should_be_separated_by_spaces() + { + // Fixture setup + var handlers = new CultureInfo("en-US").MakeCultureHandlers(); + var fakeResult = + new NotParsed( + typeof(Options_With_Default_Set_To_Sequence).ToTypeInfo(), + Enumerable.Empty() + ); + + // Exercize system + handlers.ChangeCulture(); + var helpText = HelpText.AutoBuild(fakeResult); + handlers.ResetCulture(); + + // Verify outcome + var text = helpText.ToString(); + var lines = text.ToLines().TrimStringArray(); + Console.WriteLine(text); + lines[3].Should().Be("-z, --strseq (Default: a b c)"); + lines[5].Should().Be("-y, --intseq (Default: 1 2 3)"); + lines[7].Should().Be("-q, --dblseq (Default: 1.1 2.2 3.3)"); + + // Teardown + } + } } From 8579025eb08f7d2eba4f75ee2b9e8a0f115e10f2 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Sun, 30 Jun 2019 17:15:28 +0200 Subject: [PATCH 044/198] Test maintainance: add missed tests and removing xUnit1013 warning (#462) * Add test cases ignored in v2.4.3 (18 test case) * Fix Xunit warning: 'Skipping test case with duplicate ID' * Fix warning xUnit1014: 'MemberData should use nameof operator to reference member' * Modify hard-wired copyrigt and heading constants to be platform independent and testsuit independent --- .../Unit/Core/TypeConverterTests.cs | 4 +- tests/CommandLine.Tests/Unit/ParserTests.cs | 117 +++++------------- .../Unit/Text/HelpTextTests.cs | 68 +++------- .../Unit/UnParserExtensionsTests.cs | 2 +- 4 files changed, 53 insertions(+), 138 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index 90782b4b..90fbc3f5 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -50,8 +50,8 @@ public static IEnumerable ChangeType_scalars_source new object[] {((long) int.MinValue - 1).ToString(), typeof (int), true, null}, new object[] {"1", typeof (uint), false, (uint) 1}, - new object[] {"0", typeof (uint), false, (uint) 0}, - new object[] {"-1", typeof (uint), true, null}, + // new object[] {"0", typeof (uint), false, (uint) 0}, //cause warning: Skipping test case with duplicate ID + // new object[] {"-1", typeof (uint), true, null}, //cause warning: Skipping test case with duplicate ID new object[] {uint.MaxValue.ToString(), typeof (uint), false, uint.MaxValue}, new object[] {uint.MinValue.ToString(), typeof (uint), false, uint.MinValue}, new object[] {((long) uint.MaxValue + 1).ToString(), typeof (uint), true, null}, diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index c183c47d..9d6e56fb 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -8,6 +8,7 @@ using CommandLine.Tests.Fakes; using FluentAssertions; using Xunit; +using CommandLine.Text; namespace CommandLine.Tests.Unit { @@ -326,7 +327,7 @@ public void Explicit_version_request_generates_version_requested_error() // Teardown } - //[Fact] + [Fact] public void Explicit_version_request_generates_version_info_screen() { // Fixture setup @@ -340,17 +341,12 @@ public void Explicit_version_request_generates_version_info_screen() // Verify outcome result.Length.Should().BeGreaterThan(0); var lines = result.ToNotEmptyLines().TrimStringArray(); - lines.Should().HaveCount(x => x == 1); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); -#endif + lines.Should().HaveCount(x => x == 1); + lines[0].Should().Be(HeadingInfo.Default.ToString()); // Teardown } - //[Fact] + [Fact] public void Implicit_help_screen_in_verb_scenario() { // Fixture setup @@ -364,14 +360,8 @@ public void Implicit_help_screen_in_verb_scenario() // Verify outcome result.Length.Should().BeGreaterThan(0); var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("No verb selected."); lines[4].Should().BeEquivalentTo("add Add file contents to the index."); @@ -382,7 +372,7 @@ public void Implicit_help_screen_in_verb_scenario() // Teardown } - //[Fact] + [Fact] public void Double_dash_help_dispalys_verbs_index_in_verbs_scenario() { // Fixture setup @@ -395,14 +385,8 @@ public void Double_dash_help_dispalys_verbs_index_in_verbs_scenario() // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("add Add file contents to the index."); lines[3].Should().BeEquivalentTo("commit Record changes to the repository."); lines[4].Should().BeEquivalentTo("clone Clone a repository into a new directory."); @@ -411,7 +395,7 @@ public void Double_dash_help_dispalys_verbs_index_in_verbs_scenario() // Teardown } - //[Theory] + [Theory] [InlineData("--version")] [InlineData("version")] public void Explicit_version_request_generates_version_info_screen_in_verbs_scenario(string command) @@ -428,16 +412,11 @@ public void Explicit_version_request_generates_version_info_screen_in_verbs_scen result.Length.Should().BeGreaterThan(0); var lines = result.ToNotEmptyLines().TrimStringArray(); lines.Should().HaveCount(x => x == 1); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); // Teardown } - //[Fact] + [Fact] public void Errors_of_type_MutuallyExclusiveSetError_are_properly_formatted() { // Fixture setup @@ -450,14 +429,8 @@ public void Errors_of_type_MutuallyExclusiveSetError_are_properly_formatted() // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("Option: 'weburl' is not compatible with: 'ftpurl'."); lines[4].Should().BeEquivalentTo("Option: 'ftpurl' is not compatible with: 'weburl'."); @@ -485,7 +458,7 @@ public void Explicit_help_request_with_specific_verb_generates_help_screen() // Teardown } - //[Fact] + [Fact] public void Properly_formatted_help_screen_is_displayed_when_usage_is_defined_in_verb_scenario() { // Fixture setup @@ -503,14 +476,8 @@ public void Properly_formatted_help_screen_is_displayed_when_usage_is_defined_in // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("Option 'badoption' is unknown."); lines[4].Should().BeEquivalentTo("USAGE:"); @@ -530,7 +497,7 @@ public void Properly_formatted_help_screen_is_displayed_when_usage_is_defined_in // Teardown } - //[Fact] + [Fact] public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_verb() { // Fixture setup @@ -543,14 +510,8 @@ public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_v // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("No verb selected."); lines[4].Should().BeEquivalentTo("add Add file contents to the index."); @@ -560,7 +521,7 @@ public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_v // Teardown } - //[Fact] + [Fact] public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_verb_selected_usage_displays_with_hidden_option() { // Fixture setup @@ -573,14 +534,8 @@ public void Properly_formatted_help_screen_is_displayed_when_there_is_a_hidden_v // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("-f, --force Allow adding otherwise ignored files."); lines[3].Should().BeEquivalentTo("--help Display this help screen."); lines[4].Should().BeEquivalentTo("--version Display version information."); @@ -627,7 +582,7 @@ public void Parse_options_when_given_hidden_verb_with_hidden_option() // Teardown } - //[Fact] + [Fact] public void Specific_verb_help_screen_should_be_displayed_regardless_other_argument() { // Fixture setup @@ -645,14 +600,8 @@ public void Specific_verb_help_screen_should_be_displayed_regardless_other_argum // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("--no-hardlinks Optimize the cloning process from a repository on a local"); lines[3].Should().BeEquivalentTo("filesystem by copying files."); lines[4].Should().BeEquivalentTo("-q, --quiet Suppress summary message."); @@ -701,7 +650,7 @@ public void When_IgnoreUnknownArguments_is_set_valid_unknown_arguments_avoid_a_f // Teardown } - //[Fact] + [Fact] public void Properly_formatted_help_screen_excludes_help_as_unknown_option() { // Fixture setup @@ -719,14 +668,8 @@ public void Properly_formatted_help_screen_excludes_help_as_unknown_option() // Verify outcome var lines = result.ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("Option 'bad-arg' is unknown."); lines[4].Should().BeEquivalentTo("--no-hardlinks Optimize the cloning process from a repository on a local"); diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 7c4d7590..bd2148a2 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -77,7 +77,7 @@ public void Create_instance_with_options() // Teardown } - //[Fact] + [Fact] public void Create_instance_with_enum_options_enabled() { // Fixture setup @@ -182,7 +182,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c // Teardown } - //[Fact] + [Fact] public void When_help_text_has_hidden_option_it_should_not_be_added_to_help_text_output() { // Fixture setup @@ -307,7 +307,7 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() { // Fixture setup @@ -324,14 +324,9 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().StartWithEquivalent("Copyright (c)"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); lines[4].Should().BeEquivalentTo("A sequence option 'i' is defined with fewer or more items than required."); @@ -343,7 +338,7 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_formatted_text() { // Fixture setup @@ -360,14 +355,8 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().StartWithEquivalent("Copyright (c)"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which"); lines[3].Should().BeEquivalentTo("changes to commit."); lines[4].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); @@ -376,7 +365,7 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_formatted_text_given_display_width_100() { // Fixture setup @@ -393,14 +382,8 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().BeEquivalentTo("Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which changes to commit."); lines[3].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); lines[4].Should().BeEquivalentTo("-m, --message Use the given message as the commit message."); @@ -408,7 +391,7 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo // Teardown } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Verbs_with_unknown_verb_returns_appropriate_formatted_text() { // Fixture setup @@ -425,14 +408,8 @@ public void Invoke_AutoBuild_for_Verbs_with_unknown_verb_returns_appropriate_for // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().StartWithEquivalent("Copyright (c)"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("add Add file contents to the index."); lines[3].Should().BeEquivalentTo("commit Record changes to the repository."); lines[4].Should().BeEquivalentTo("clone Clone a repository into a new directory."); @@ -494,7 +471,7 @@ public static void RenderUsageText_returns_properly_formatted_text() lines[10].Should().BeEquivalentTo(" mono testapp.exe value"); } - //[Fact] + [Fact] public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatted_text() { // Fixture setup @@ -510,15 +487,9 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte // Verify outcome var text = helpText.ToString(); - var lines = text.ToNotEmptyLines(); -#if !PLATFORM_DOTNET - lines[0].Should().StartWithEquivalent("CommandLine"); - lines[1].Should().StartWithEquivalent("Copyright (c)"); -#else - // Takes the name of the xUnit test program - lines[0].Should().StartWithEquivalent("xUnit"); - lines[1].Should().StartWithEquivalent("Copyright (C) Outercurve Foundation"); -#endif + var lines = text.ToNotEmptyLines().TrimStringArray(); + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("ERROR(S):"); lines[3].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); lines[4].Should().BeEquivalentTo("USAGE:"); @@ -653,5 +624,6 @@ public void Add_line_with_two_empty_spaces_at_the_end() Assert.Equal("T" + Environment.NewLine + "e" + Environment.NewLine + "s" + Environment.NewLine + "t", b.ToString()); } + } } diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 461fc969..8ff702a5 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -39,7 +39,7 @@ public static void UnParsing_immutable_instance_returns_command_line(Immutable_S } [Theory] - [MemberData("UnParseDataHidden")] + [MemberData(nameof(UnParseDataHidden))] public static void Unparsing_hidden_option_returns_command_line(Hidden_Option options, bool showHidden, string result) { new Parser() From ee30c24d4d4764befdb80077fa3e7ebaa64ec28d Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Wed, 3 Jul 2019 21:53:26 +0200 Subject: [PATCH 045/198] Fix issue #259 for HelpText.AutoBuild configuration --- src/CommandLine/ErrorExtensions.cs | 27 +++ src/CommandLine/Text/HelpText.cs | 6 +- .../Unit/Text/HelpTextAutoBuildFix.cs | 164 ++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs diff --git a/src/CommandLine/ErrorExtensions.cs b/src/CommandLine/ErrorExtensions.cs index 2ffe629d..43b7cfd0 100644 --- a/src/CommandLine/ErrorExtensions.cs +++ b/src/CommandLine/ErrorExtensions.cs @@ -1,5 +1,6 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using CommandLine.Core; @@ -23,5 +24,31 @@ public static IEnumerable OnlyMeaningfulOnes(this IEnumerable erro .Where(e => !(e.Tag == ErrorType.UnknownOptionError && ((UnknownOptionError)e).Token.EqualsOrdinalIgnoreCase("help"))); } + /// + /// return true when errors contain HelpXXXError + /// + public static bool IsHelp (this IEnumerable errs) + { + if (errs.Any(x=>x.Tag == ErrorType.HelpRequestedError || + x.Tag == ErrorType.HelpVerbRequestedError)) + return true; + //when AutoHelp=false in parser, help is disabled and Parser raise UnknownOptionError + if( errs.Any(x=> (x is UnknownOptionError ee ? ee.Token:"") == "help")) + return true; + return false; + } + + /// + /// return true when errors contain VersionXXXError + /// + public static bool IsVersion (this IEnumerable errs) + { + if (errs.Any(x=>x.Tag == ErrorType.VersionRequestedError )) + return true; + //when AutoVersion=false in parser, Version is disabled and Parser raise UnknownOptionError + if( errs.Any(x=> (x is UnknownOptionError ee ? ee.Token:"") == "version")) + return true; + return false; + } } } diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 575c6fbc..2d310191 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -269,11 +269,13 @@ public static HelpText AutoBuild( var errors = Enumerable.Empty(); + if (onError != null && parserResult.Tag == ParserResultType.NotParsed) { errors = ((NotParsed)parserResult).Errors; - - if (errors.OnlyMeaningfulOnes().Any()) + if (errors.IsHelp()) + auto = onError(auto); + else if (errors.OnlyMeaningfulOnes().Any()) auto = onError(auto); } diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs new file mode 100644 index 00000000..91c5b52e --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs @@ -0,0 +1,164 @@ +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; + +namespace CommandLine.Tests.Unit.Text +{ + public class HelpTextTests2 + { + [Fact] + public static void error_ishelp() + { + // Fixture setup + // Exercize system + var parser = new Parser(x => x.HelpWriter = null); + var result = parser.ParseArguments(new[]{"--help"}); + + result .WithNotParsed(errs => + { + errs.IsHelp().Should().BeTrue(); + errs.IsVersion().Should().BeFalse(); + }); + } + [Fact] + public static void error_isVersion() + { + // Fixture setup + // Exercize system + var parser = new Parser(x => x.HelpWriter = null); + var result = parser.ParseArguments(new[]{"--version"}); + + result .WithNotParsed(errs => + { + errs.IsHelp().Should().BeFalse(); + errs.IsVersion().Should().BeTrue(); + }); + } + + [Fact] + public static void custom_helptext_with_AdditionalNewLineAfterOption_false() + { + // Fixture setup + // Exercize system + var parser = new Parser(x => x.HelpWriter = null); + var result = parser.ParseArguments(new[]{"--help"}); + + result .WithNotParsed(errs => + { + + var sut = HelpText.AutoBuild(result, + h => + { + h.AdditionalNewLineAfterOption = false; + return h; + } + , e => e); + //Assert + var expected = new[] + { + " --help Display this help screen.", + " --version Display version information." + }; + var lines = sut.ToString().ToLines(); + lines.Should().ContainInOrder(expected); + }); + } + + [Fact] + public static void custom_helptext_with_AdditionalNewLineAfterOption_true() + { + // Fixture setup + // Exercize system + var parser = new Parser(x => x.HelpWriter = null); + var result = parser.ParseArguments(new[]{"--help"}); + + result .WithNotParsed(errs => + { + + var sut = HelpText.AutoBuild(result, + h =>h //AdditionalNewLineAfterOption =true by default + , e => e); + + //Assert + var expected = new[] + { + string.Empty, + " --help Display this help screen.", + string.Empty, + " --version Display version information." + }; + var lines = sut.ToString().ToLines(); + lines.Should().ContainInOrder(expected); + }); + } + + + [Fact] + public static void custom_helptext_with_parser_autohelp_false_and_AdditionalNewLineAfterOption_false() + { + // Fixture setup + // Exercize system + var parser = new Parser(x => + { + x.HelpWriter = null; + x.AutoHelp=false; + //x.AutoVersion=false; + }); + var result = parser.ParseArguments(new[]{"--help"}); + //you could generate help even parser.AutoHelp is disabled + result .WithNotParsed(errs => + { + errs.IsHelp().Should().BeTrue(); + var sut = HelpText.AutoBuild(result, + h => + { + h.AdditionalNewLineAfterOption = false; + return h; + } + , e => e); + + //Assert + var expected = new[] + { + " --help Display this help screen.", + " --version Display version information." + }; + var lines = sut.ToString().ToLines(); + lines.Should().ContainInOrder(expected); + }); + } + + [Fact] + public static void custom_helptext_with_autohelp_false() + { + // Fixture setup + // Exercize system + var parser = new Parser(x => + { + x.HelpWriter = null; + x.AutoHelp=false; + //x.AutoVersion=false; + }); + var result = parser.ParseArguments(new[]{"--help"}); + + result .WithNotParsed(errs => + { + errs.IsHelp().Should().BeTrue(); + var sut = HelpText.AutoBuild(result, + h =>h,e => e); + + //Assert + var expected = new[] + { + string.Empty, + " --help Display this help screen.", + string.Empty, + " --version Display version information." + }; + var lines = sut.ToString().ToLines(); + lines.Should().ContainInOrder(expected); + }); + } + } +} From 1de518f86ebe4f34ba8782ae2d85e630ca03aa8b Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Thu, 4 Jul 2019 03:04:39 +0200 Subject: [PATCH 046/198] move IsHelp/Isversion to separate file with scope public --- src/CommandLine/ErrorExtensions.cs | 28 +-- src/CommandLine/HelpTextExtensions.cs | 35 ++++ .../Unit/Text/HelpTextAutoBuildFix.cs | 193 ++++++------------ 3 files changed, 97 insertions(+), 159 deletions(-) create mode 100644 src/CommandLine/HelpTextExtensions.cs diff --git a/src/CommandLine/ErrorExtensions.cs b/src/CommandLine/ErrorExtensions.cs index 43b7cfd0..edd03478 100644 --- a/src/CommandLine/ErrorExtensions.cs +++ b/src/CommandLine/ErrorExtensions.cs @@ -1,6 +1,5 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; using System.Collections.Generic; using System.Linq; using CommandLine.Core; @@ -24,31 +23,6 @@ public static IEnumerable OnlyMeaningfulOnes(this IEnumerable erro .Where(e => !(e.Tag == ErrorType.UnknownOptionError && ((UnknownOptionError)e).Token.EqualsOrdinalIgnoreCase("help"))); } - /// - /// return true when errors contain HelpXXXError - /// - public static bool IsHelp (this IEnumerable errs) - { - if (errs.Any(x=>x.Tag == ErrorType.HelpRequestedError || - x.Tag == ErrorType.HelpVerbRequestedError)) - return true; - //when AutoHelp=false in parser, help is disabled and Parser raise UnknownOptionError - if( errs.Any(x=> (x is UnknownOptionError ee ? ee.Token:"") == "help")) - return true; - return false; - } - - /// - /// return true when errors contain VersionXXXError - /// - public static bool IsVersion (this IEnumerable errs) - { - if (errs.Any(x=>x.Tag == ErrorType.VersionRequestedError )) - return true; - //when AutoVersion=false in parser, Version is disabled and Parser raise UnknownOptionError - if( errs.Any(x=> (x is UnknownOptionError ee ? ee.Token:"") == "version")) - return true; - return false; - } + } } diff --git a/src/CommandLine/HelpTextExtensions.cs b/src/CommandLine/HelpTextExtensions.cs new file mode 100644 index 00000000..88bcdb18 --- /dev/null +++ b/src/CommandLine/HelpTextExtensions.cs @@ -0,0 +1,35 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace CommandLine +{ + public static class HelpTextExtensions + { + /// + /// return true when errors contain HelpXXXError + /// + public static bool IsHelp(this IEnumerable errs) + { + if (errs.Any(x => x.Tag == ErrorType.HelpRequestedError || + x.Tag == ErrorType.HelpVerbRequestedError)) + return true; + //when AutoHelp=false in parser, help is disabled and Parser raise UnknownOptionError + return errs.Any(x => (x is UnknownOptionError ee ? ee.Token : "") == "help"); + } + + /// + /// return true when errors contain VersionXXXError + /// + public static bool IsVersion(this IEnumerable errs) + { + if (errs.Any(x => x.Tag == ErrorType.VersionRequestedError)) + return true; + //when AutoVersion=false in parser, Version is disabled and Parser raise UnknownOptionError + return errs.Any(x => (x is UnknownOptionError ee ? ee.Token : "") == "version"); + } + } +} + + diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs index 91c5b52e..4b78aa13 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs @@ -1,164 +1,93 @@ -using CommandLine.Tests.Fakes; +using System; +using System.Linq; +using CommandLine.Tests.Fakes; using CommandLine.Text; using FluentAssertions; using Xunit; namespace CommandLine.Tests.Unit.Text { - public class HelpTextTests2 + public class HelpTextAutoBuildFix { - [Fact] - public static void error_ishelp() - { - // Fixture setup - // Exercize system - var parser = new Parser(x => x.HelpWriter = null); - var result = parser.ParseArguments(new[]{"--help"}); - result .WithNotParsed(errs => - { - errs.IsHelp().Should().BeTrue(); - errs.IsVersion().Should().BeFalse(); - }); - } [Fact] - public static void error_isVersion() + public void HelpText_wit_AdditionalNewLineAfterOption_true_should_have_newline() { // Fixture setup - // Exercize system - var parser = new Parser(x => x.HelpWriter = null); - var result = parser.ParseArguments(new[]{"--version"}); + // Exercize system + var sut = new HelpText { AdditionalNewLineAfterOption = true } + .AddOptions(new NotParsed(TypeInfo.Create(typeof(Simple_Options)), + Enumerable.Empty())); - result .WithNotParsed(errs => - { - errs.IsHelp().Should().BeFalse(); - errs.IsVersion().Should().BeTrue(); - }); - } - - [Fact] - public static void custom_helptext_with_AdditionalNewLineAfterOption_false() - { - // Fixture setup - // Exercize system - var parser = new Parser(x => x.HelpWriter = null); - var result = parser.ParseArguments(new[]{"--help"}); + // Verify outcome - result .WithNotParsed(errs => - { - - var sut = HelpText.AutoBuild(result, - h => - { - h.AdditionalNewLineAfterOption = false; - return h; - } - , e => e); - //Assert - var expected = new[] - { - " --help Display this help screen.", - " --version Display version information." - }; - var lines = sut.ToString().ToLines(); - lines.Should().ContainInOrder(expected); - }); + var lines = sut.ToString().ToLines(); + + lines[2].Should().BeEquivalentTo(" stringvalue Define a string value here."); + lines[3].Should().BeEquivalentTo(String.Empty); + lines[4].Should().BeEquivalentTo(" s, shortandlong Example with both short and long name."); + lines[5].Should().BeEquivalentTo(String.Empty); + lines[7].Should().BeEquivalentTo(String.Empty); + lines[9].Should().BeEquivalentTo(String.Empty); + lines[11].Should().BeEquivalentTo(String.Empty); + lines[13].Should().BeEquivalentTo(String.Empty); + lines[14].Should().BeEquivalentTo(" value pos. 0 Define a long value here."); + // Teardown } [Fact] - public static void custom_helptext_with_AdditionalNewLineAfterOption_true() + public void HelpText_wit_AdditionalNewLineAfterOption_false_should_not_have_newline() { // Fixture setup - // Exercize system - var parser = new Parser(x => x.HelpWriter = null); - var result = parser.ParseArguments(new[]{"--help"}); + // Exercize system + var sut = new HelpText { AdditionalNewLineAfterOption = false } + .AddOptions(new NotParsed(TypeInfo.Create(typeof(Simple_Options)), + Enumerable.Empty())); - result .WithNotParsed(errs => - { - - var sut = HelpText.AutoBuild(result, - h =>h //AdditionalNewLineAfterOption =true by default - , e => e); - - //Assert - var expected = new[] - { - string.Empty, - " --help Display this help screen.", - string.Empty, - " --version Display version information." - }; - var lines = sut.ToString().ToLines(); - lines.Should().ContainInOrder(expected); - }); - } + // Verify outcome + + var lines = sut.ToString().ToLines(); + + lines[2].Should().BeEquivalentTo(" stringvalue Define a string value here."); - + lines[3].Should().BeEquivalentTo(" s, shortandlong Example with both short and long name."); + lines[8].Should().BeEquivalentTo(" value pos. 0 Define a long value here."); + // Teardown + } [Fact] - public static void custom_helptext_with_parser_autohelp_false_and_AdditionalNewLineAfterOption_false() + public void HelpText_wit_by_default_should_include_help_version_option() { // Fixture setup - // Exercize system - var parser = new Parser(x => - { - x.HelpWriter = null; - x.AutoHelp=false; - //x.AutoVersion=false; - }); - var result = parser.ParseArguments(new[]{"--help"}); - //you could generate help even parser.AutoHelp is disabled - result .WithNotParsed(errs => - { - errs.IsHelp().Should().BeTrue(); - var sut = HelpText.AutoBuild(result, - h => - { - h.AdditionalNewLineAfterOption = false; - return h; - } - , e => e); - - //Assert - var expected = new[] - { - " --help Display this help screen.", - " --version Display version information." - }; - var lines = sut.ToString().ToLines(); - lines.Should().ContainInOrder(expected); - }); + // Exercize system + var sut = new HelpText () + .AddOptions(new NotParsed(TypeInfo.Create(typeof(Simple_Options)), + Enumerable.Empty())); + + // Verify outcome + + var lines = sut.ToString().ToNotEmptyLines(); + lines.Should().HaveCount(c => c ==7); + lines.Should().Contain(" help Display more information on a specific command."); + lines.Should().Contain(" version Display version information."); + // Teardown } [Fact] - public static void custom_helptext_with_autohelp_false() + public void HelpText_wit_AutoHelp_false_should_hide_help_option() { // Fixture setup - // Exercize system - var parser = new Parser(x => - { - x.HelpWriter = null; - x.AutoHelp=false; - //x.AutoVersion=false; - }); - var result = parser.ParseArguments(new[]{"--help"}); + // Exercize system + var sut = new HelpText { AutoHelp = false,AutoVersion = false} + .AddOptions(new NotParsed(TypeInfo.Create(typeof(Simple_Options)), + Enumerable.Empty())); + + // Verify outcome - result .WithNotParsed(errs => - { - errs.IsHelp().Should().BeTrue(); - var sut = HelpText.AutoBuild(result, - h =>h,e => e); - - //Assert - var expected = new[] - { - string.Empty, - " --help Display this help screen.", - string.Empty, - " --version Display version information." - }; - var lines = sut.ToString().ToLines(); - lines.Should().ContainInOrder(expected); - }); + var lines = sut.ToString().ToNotEmptyLines(); + lines.Should().HaveCount(c => c ==5); + lines.Should().NotContain(" help Display more information on a specific command."); + lines.Should().NotContain(" version Display version information."); + // Teardown } } } From d69be74fbd9854c44404ad1bbf13317bcb177795 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Wed, 10 Jul 2019 13:39:41 +0200 Subject: [PATCH 047/198] Simplify if-else clause --- src/CommandLine/Text/HelpText.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 2d310191..1306e969 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -273,9 +273,7 @@ public static HelpText AutoBuild( if (onError != null && parserResult.Tag == ParserResultType.NotParsed) { errors = ((NotParsed)parserResult).Errors; - if (errors.IsHelp()) - auto = onError(auto); - else if (errors.OnlyMeaningfulOnes().Any()) + if (errors.IsHelp() || errors.OnlyMeaningfulOnes().Any()) auto = onError(auto); } From 09c8f70d03a3c6ee8ded2476b955d3956fd782c4 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Thu, 11 Jul 2019 21:14:50 +0200 Subject: [PATCH 048/198] Localize VerbAttribute --- src/CommandLine/VerbAttribute.cs | 26 ++++++---- .../Unit/VerbAttributeTests.cs | 50 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/VerbAttributeTests.cs diff --git a/src/CommandLine/VerbAttribute.cs b/src/CommandLine/VerbAttribute.cs index 0078a7a8..5515bd20 100644 --- a/src/CommandLine/VerbAttribute.cs +++ b/src/CommandLine/VerbAttribute.cs @@ -8,10 +8,12 @@ namespace CommandLine /// Models a verb command specification. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] - public sealed class VerbAttribute : Attribute + //public sealed class VerbAttribute : Attribute + public class VerbAttribute : Attribute { private readonly string name; - private string helpText; + private Infrastructure.LocalizableAttributeProperty helpText; + private Type resourceType; /// /// Initializes a new instance of the class. @@ -23,7 +25,8 @@ public VerbAttribute(string name) if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name"); this.name = name; - this.helpText = string.Empty; + helpText = new Infrastructure.LocalizableAttributeProperty(nameof(HelpText)); + resourceType = null; } /// @@ -48,11 +51,16 @@ public bool Hidden /// public string HelpText { - get { return helpText; } - set - { - helpText = value ?? throw new ArgumentNullException("value"); - } + get => helpText.Value??string.Empty; + set => helpText.Value = value ?? throw new ArgumentNullException("value"); + } + /// + /// Gets or sets the that contains the resources for . + /// + public Type ResourceType + { + get => resourceType; + set => resourceType =helpText.ResourceType = value; } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs b/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs new file mode 100644 index 00000000..4dc1dd30 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs @@ -0,0 +1,50 @@ +using System; +using Xunit; + +namespace CommandLine.Tests +{ + //Test localization of VerbAttribute + public class VerbAttributeTests + { + [Theory] + [InlineData("", null, "")] + [InlineData("", typeof(Fakes.StaticResource), "")] + [InlineData("Help text", null, "Help text")] + [InlineData("HelpText", typeof(Fakes.StaticResource), "Localized HelpText")] + [InlineData("HelpText", typeof(Fakes.NonStaticResource), "Localized HelpText")] + public static void VerbHelpText(string helpText, Type resourceType, string expected) + { + TestVerbAttribute verbAttribute = new TestVerbAttribute + { + HelpText = helpText, + ResourceType = resourceType + }; + + Assert.Equal(expected, verbAttribute.HelpText); + } + [Theory] + [InlineData("HelpText", typeof(Fakes.NonStaticResource_WithNonStaticProperty))] + [InlineData("WriteOnlyText", typeof(Fakes.NonStaticResource))] + [InlineData("PrivateOnlyText", typeof(Fakes.NonStaticResource))] + [InlineData("HelpText", typeof(Fakes.InternalResource))] + public void ThrowsHelpText(string helpText, Type resourceType) + { + TestVerbAttribute verbAttribute = new TestVerbAttribute + { + HelpText = helpText, + ResourceType = resourceType + }; + + // Verify exception + Assert.Throws(() => verbAttribute.HelpText); + } + + private class TestVerbAttribute : VerbAttribute + { + public TestVerbAttribute() : base("verb") + { + // Do nothing + } + } + } +} From 4703c74e0ee25b8b364918f0bc5300cd8fb98e03 Mon Sep 17 00:00:00 2001 From: "Moh.Hassan" Date: Thu, 11 Jul 2019 22:10:42 +0200 Subject: [PATCH 049/198] Add errs.Output extension method to auto direct help/version to Console.Out and errors to Console.Error --- src/CommandLine/HelpTextExtensions.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/HelpTextExtensions.cs b/src/CommandLine/HelpTextExtensions.cs index 88bcdb18..d0106de8 100644 --- a/src/CommandLine/HelpTextExtensions.cs +++ b/src/CommandLine/HelpTextExtensions.cs @@ -1,5 +1,6 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - +using System; +using System.IO; using System.Collections.Generic; using System.Linq; @@ -28,6 +29,15 @@ public static bool IsVersion(this IEnumerable errs) return true; //when AutoVersion=false in parser, Version is disabled and Parser raise UnknownOptionError return errs.Any(x => (x is UnknownOptionError ee ? ee.Token : "") == "version"); + } + /// + /// redirect errs to Console.Error, and to Console.Out for help/version error + /// + public static TextWriter Output(this IEnumerable errs) + { + if (errs.IsHelp() || errs.IsVersion()) + return Console.Out; + return Console.Error; } } } From 7ede4da79e68386a1f85a13842bc68b0683e8645 Mon Sep 17 00:00:00 2001 From: Tomas Arci Kouba Date: Wed, 24 Jul 2019 21:49:53 +0200 Subject: [PATCH 050/198] Localized demo --- .../Navigation/RecentFilesHistory.xml | 16 + .../LocalizableSentenceBuilder.cs | 122 ++++++ demo/ReadText.LocalizedDemo/Options.cs | 69 ++++ demo/ReadText.LocalizedDemo/Program.cs | 83 ++++ .../Properties/AssemblyInfo.cs | 11 + .../Properties/Resources.Designer.cs | 378 ++++++++++++++++++ .../Properties/Resources.cs.resx | 225 +++++++++++ .../Properties/Resources.resx | 225 +++++++++++ .../Properties/launchSettings.json | 8 + .../ReadText.LocalizedDemo.csproj | 23 ++ .../ReadText.LocalizedDemo.sln | 31 ++ demo/ReadText.LocalizedDemo/packages.config | 4 + 12 files changed, 1195 insertions(+) create mode 100644 demo/ReadText.LocalizedDemo/.cr/personal/Navigation/RecentFilesHistory.xml create mode 100644 demo/ReadText.LocalizedDemo/LocalizableSentenceBuilder.cs create mode 100644 demo/ReadText.LocalizedDemo/Options.cs create mode 100644 demo/ReadText.LocalizedDemo/Program.cs create mode 100644 demo/ReadText.LocalizedDemo/Properties/AssemblyInfo.cs create mode 100644 demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs create mode 100644 demo/ReadText.LocalizedDemo/Properties/Resources.cs.resx create mode 100644 demo/ReadText.LocalizedDemo/Properties/Resources.resx create mode 100644 demo/ReadText.LocalizedDemo/Properties/launchSettings.json create mode 100644 demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.csproj create mode 100644 demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.sln create mode 100644 demo/ReadText.LocalizedDemo/packages.config diff --git a/demo/ReadText.LocalizedDemo/.cr/personal/Navigation/RecentFilesHistory.xml b/demo/ReadText.LocalizedDemo/.cr/personal/Navigation/RecentFilesHistory.xml new file mode 100644 index 00000000..7b2b2ccb --- /dev/null +++ b/demo/ReadText.LocalizedDemo/.cr/personal/Navigation/RecentFilesHistory.xml @@ -0,0 +1,16 @@ + + + + + + LocalizableAttributeProperty.cs + d:\work\arci\commandline\src\commandline\infrastructure\localizableattributeproperty.cs + + Infrastructure + + d:\WORK\ARCI\commandline\src\CommandLine\CommandLine.csproj + CommandLine + + + + \ No newline at end of file diff --git a/demo/ReadText.LocalizedDemo/LocalizableSentenceBuilder.cs b/demo/ReadText.LocalizedDemo/LocalizableSentenceBuilder.cs new file mode 100644 index 00000000..bf2b7c56 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/LocalizableSentenceBuilder.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using CommandLine; +using CommandLine.Text; + +namespace ReadText.LocalizedDemo +{ + public class LocalizableSentenceBuilder : SentenceBuilder + { + public override Func RequiredWord + { + get { return () => Properties.Resources.SentenceRequiredWord; } + } + + public override Func ErrorsHeadingText + { + // Cannot be pluralized + get { return () => Properties.Resources.SentenceErrorsHeadingText; } + } + + public override Func UsageHeadingText + { + get { return () => Properties.Resources.SentenceUsageHeadingText; } + } + + public override Func HelpCommandText + { + get + { + return isOption => isOption + ? Properties.Resources.SentenceHelpCommandTextOption + : Properties.Resources.SentenceHelpCommandTextVerb; + } + } + + public override Func VersionCommandText + { + get { return _ => Properties.Resources.SentenceVersionCommandText; } + } + + public override Func FormatError + { + get + { + return error => + { + switch (error.Tag) + { + case ErrorType.BadFormatTokenError: + return String.Format(Properties.Resources.SentenceBadFormatTokenError, ((BadFormatTokenError)error).Token); + case ErrorType.MissingValueOptionError: + return String.Format(Properties.Resources.SentenceMissingValueOptionError, ((MissingValueOptionError)error).NameInfo.NameText); + case ErrorType.UnknownOptionError: + return String.Format(Properties.Resources.SentenceUnknownOptionError, ((UnknownOptionError)error).Token); + case ErrorType.MissingRequiredOptionError: + var errMisssing = ((MissingRequiredOptionError)error); + return errMisssing.NameInfo.Equals(NameInfo.EmptyName) + ? Properties.Resources.SentenceMissingRequiredOptionError + : String.Format(Properties.Resources.SentenceMissingRequiredOptionError, errMisssing.NameInfo.NameText); + case ErrorType.BadFormatConversionError: + var badFormat = ((BadFormatConversionError)error); + return badFormat.NameInfo.Equals(NameInfo.EmptyName) + ? Properties.Resources.SentenceBadFormatConversionErrorValue + : String.Format(Properties.Resources.SentenceBadFormatConversionErrorOption, badFormat.NameInfo.NameText); + case ErrorType.SequenceOutOfRangeError: + var seqOutRange = ((SequenceOutOfRangeError)error); + return seqOutRange.NameInfo.Equals(NameInfo.EmptyName) + ? Properties.Resources.SentenceSequenceOutOfRangeErrorValue + : String.Format(Properties.Resources.SentenceSequenceOutOfRangeErrorOption, + seqOutRange.NameInfo.NameText); + case ErrorType.BadVerbSelectedError: + return String.Format(Properties.Resources.SentenceBadVerbSelectedError, ((BadVerbSelectedError)error).Token); + case ErrorType.NoVerbSelectedError: + return Properties.Resources.SentenceNoVerbSelectedError; + case ErrorType.RepeatedOptionError: + return String.Format(Properties.Resources.SentenceRepeatedOptionError, ((RepeatedOptionError)error).NameInfo.NameText); + case ErrorType.SetValueExceptionError: + var setValueError = (SetValueExceptionError)error; + return String.Format(Properties.Resources.SentenceSetValueExceptionError, setValueError.NameInfo.NameText, setValueError.Exception.Message); + } + throw new InvalidOperationException(); + }; + } + } + + public override Func, string> FormatMutuallyExclusiveSetErrors + { + get + { + return errors => + { + var bySet = from e in errors + group e by e.SetName into g + select new { SetName = g.Key, Errors = g.ToList() }; + + var msgs = bySet.Select( + set => + { + var names = String.Join( + String.Empty, + (from e in set.Errors select String.Format("'{0}', ", e.NameInfo.NameText)).ToArray()); + var namesCount = set.Errors.Count(); + + var incompat = String.Join( + String.Empty, + (from x in + (from s in bySet where !s.SetName.Equals(set.SetName) from e in s.Errors select e) + .Distinct() + select String.Format("'{0}', ", x.NameInfo.NameText)).ToArray()); + //TODO: Pluralize by namesCount + return + String.Format(Properties.Resources.SentenceMutuallyExclusiveSetErrors, + names.Substring(0, names.Length - 2), incompat.Substring(0, incompat.Length - 2)); + }).ToArray(); + return string.Join(Environment.NewLine, msgs); + }; + } + } + } +} diff --git a/demo/ReadText.LocalizedDemo/Options.cs b/demo/ReadText.LocalizedDemo/Options.cs new file mode 100644 index 00000000..6ab1e3ee --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Options.cs @@ -0,0 +1,69 @@ +using CommandLine; +using CommandLine.Text; +using System.Collections.Generic; + +namespace ReadText.LocalizedDemo +{ + interface IOptions + { + [Option('n', "lines", + Default = 5U, + SetName = "bylines", + HelpText = "HelpTextLines", + ResourceType = typeof(Properties.Resources))] + uint? Lines { get; set; } + + [Option('c', "bytes", + SetName = "bybytes", + HelpText = "HelpTextBytes", + ResourceType = typeof(Properties.Resources))] + uint? Bytes { get; set; } + + [Option('q', "quiet", + HelpText = "HelpTextQuiet", + ResourceType = typeof(Properties.Resources))] + bool Quiet { get; set; } + + [Value(0, MetaName = "input file", + HelpText = "HelpTextFileName", + Required = true, + ResourceType = typeof(Properties.Resources))] + string FileName { get; set; } + } + + [Verb("head", HelpText = "HelpTextVerbHead", ResourceType = typeof(Properties.Resources))] + class HeadOptions : IOptions + { + public uint? Lines { get; set; } + + public uint? Bytes { get; set; } + + public bool Quiet { get; set; } + + public string FileName { get; set; } + + [Usage(ApplicationAlias = "ReadText.LocalizedDemo.exe")] + public static IEnumerable Examples + { + get + { + yield return new Example(Properties.Resources.ExamplesNormalScenario, new HeadOptions { FileName = "file.bin"}); + yield return new Example(Properties.Resources.ExamplesSpecifyBytes, new HeadOptions { FileName = "file.bin", Bytes=100 }); + yield return new Example(Properties.Resources.ExamplesSuppressSummary, UnParserSettings.WithGroupSwitchesOnly(), new HeadOptions { FileName = "file.bin", Quiet = true }); + yield return new Example(Properties.Resources.ExamplesReadMoreLines, new[] { UnParserSettings.WithGroupSwitchesOnly(), UnParserSettings.WithUseEqualTokenOnly() }, new HeadOptions { FileName = "file.bin", Lines = 10 }); + } + } + } + + [Verb("tail", HelpText = "HelpTextVerbTail", ResourceType = typeof(Properties.Resources))] + class TailOptions : IOptions + { + public uint? Lines { get; set; } + + public uint? Bytes { get; set; } + + public bool Quiet { get; set; } + + public string FileName { get; set; } + } +} diff --git a/demo/ReadText.LocalizedDemo/Program.cs b/demo/ReadText.LocalizedDemo/Program.cs new file mode 100644 index 00000000..defa6412 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Program.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using CommandLine; +using CommandLine.Text; + +namespace ReadText.LocalizedDemo +{ + class Program + { + public static int Main(string[] args) + { + // Set sentence builder to localizable + SentenceBuilder.Factory = () => new LocalizableSentenceBuilder(); + + Func reader = opts => + { + var fromTop = opts.GetType() == typeof(HeadOptions); + return opts.Lines.HasValue + ? ReadLines(opts.FileName, fromTop, (int)opts.Lines) + : ReadBytes(opts.FileName, fromTop, (int)opts.Bytes); + }; + Func header = opts => + { + if (opts.Quiet) + { + return string.Empty; + } + var fromTop = opts.GetType() == typeof(HeadOptions); + var builder = new StringBuilder(Properties.Resources.Reading); + builder = opts.Lines.HasValue + ? builder.Append(opts.Lines).Append(Properties.Resources.Lines) + : builder.Append(opts.Bytes).Append(Properties.Resources.Bytes); + builder = fromTop ? builder.Append(Properties.Resources.FromTop) : builder.Append(Properties.Resources.FromBottom); + return builder.ToString(); + }; + Action printIfNotEmpty = text => + { + if (text.Length == 0) { return; } + Console.WriteLine(text); + }; + + var result = Parser.Default.ParseArguments(args); + var texts = result + .MapResult( + (HeadOptions opts) => Tuple.Create(header(opts), reader(opts)), + (TailOptions opts) => Tuple.Create(header(opts), reader(opts)), + _ => MakeError()); + + printIfNotEmpty(texts.Item1); + printIfNotEmpty(texts.Item2); + + return texts.Equals(MakeError()) ? 1 : 0; + } + + private static string ReadLines(string fileName, bool fromTop, int count) + { + var lines = File.ReadAllLines(fileName); + if (fromTop) + { + return string.Join(Environment.NewLine, lines.Take(count)); + } + return string.Join(Environment.NewLine, lines.Reverse().Take(count)); + } + + private static string ReadBytes(string fileName, bool fromTop, int count) + { + var bytes = File.ReadAllBytes(fileName); + if (fromTop) + { + return Encoding.UTF8.GetString(bytes, 0, count); + } + return Encoding.UTF8.GetString(bytes, bytes.Length - count, count); + } + + private static Tuple MakeError() + { + return Tuple.Create("\0", "\0"); + } + } +} diff --git a/demo/ReadText.LocalizedDemo/Properties/AssemblyInfo.cs b/demo/ReadText.LocalizedDemo/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..eaf1b66f --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("ReadText.Demo")] +[assembly: AssemblyDescription("ReadText.Demo for Command Line Parser Library")] +[assembly: AssemblyTrademark("")] +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif diff --git a/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs b/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs new file mode 100644 index 00000000..cc414359 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs @@ -0,0 +1,378 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ReadText.LocalizedDemo.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ReadText.LocalizedDemo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to bytes. + /// + public static string Bytes { + get { + return ResourceManager.GetString("Bytes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to normal scenario. + /// + public static string ExamplesNormalScenario { + get { + return ResourceManager.GetString("ExamplesNormalScenario", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to read more lines. + /// + public static string ExamplesReadMoreLines { + get { + return ResourceManager.GetString("ExamplesReadMoreLines", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to specify bytes. + /// + public static string ExamplesSpecifyBytes { + get { + return ResourceManager.GetString("ExamplesSpecifyBytes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to suppress summary. + /// + public static string ExamplesSuppressSummary { + get { + return ResourceManager.GetString("ExamplesSuppressSummary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to from bottom:. + /// + public static string FromBottom { + get { + return ResourceManager.GetString("FromBottom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to from top:. + /// + public static string FromTop { + get { + return ResourceManager.GetString("FromTop", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bytes to be printed from the beginning or end of the file.. + /// + public static string HelpTextBytes { + get { + return ResourceManager.GetString("HelpTextBytes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input file to be processed.. + /// + public static string HelpTextFileName { + get { + return ResourceManager.GetString("HelpTextFileName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lines to be printed from the beginning or end of the file.. + /// + public static string HelpTextLines { + get { + return ResourceManager.GetString("HelpTextLines", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Suppresses summary messages.. + /// + public static string HelpTextQuiet { + get { + return ResourceManager.GetString("HelpTextQuiet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Displays first lines of a file.. + /// + public static string HelpTextVerbHead { + get { + return ResourceManager.GetString("HelpTextVerbHead", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Displays last lines of a file.. + /// + public static string HelpTextVerbTail { + get { + return ResourceManager.GetString("HelpTextVerbTail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lines. + /// + public static string Lines { + get { + return ResourceManager.GetString("Lines", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reading . + /// + public static string Reading { + get { + return ResourceManager.GetString("Reading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Možnost '{0}' je definována ve špatném formátu.. + /// + public static string SentenceBadFormatConversionErrorOption { + get { + return ResourceManager.GetString("SentenceBadFormatConversionErrorOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A value not bound to option name is defined with a bad format.. + /// + public static string SentenceBadFormatConversionErrorValue { + get { + return ResourceManager.GetString("SentenceBadFormatConversionErrorValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Token '{0}' is not recognized.. + /// + public static string SentenceBadFormatTokenError { + get { + return ResourceManager.GetString("SentenceBadFormatTokenError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verb '{0}' is not recognized.. + /// + public static string SentenceBadVerbSelectedError { + get { + return ResourceManager.GetString("SentenceBadVerbSelectedError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ERROR(S):. + /// + public static string SentenceErrorsHeadingText { + get { + return ResourceManager.GetString("SentenceErrorsHeadingText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Display this help screen.. + /// + public static string SentenceHelpCommandTextOption { + get { + return ResourceManager.GetString("SentenceHelpCommandTextOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Display more information on a specific command.. + /// + public static string SentenceHelpCommandTextVerb { + get { + return ResourceManager.GetString("SentenceHelpCommandTextVerb", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Required option '{0}' is missing.. + /// + public static string SentenceMissingRequiredOptionError { + get { + return ResourceManager.GetString("SentenceMissingRequiredOptionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A required value not bound to option name is missing.. + /// + public static string SentenceMissingRequiredValueError { + get { + return ResourceManager.GetString("SentenceMissingRequiredValueError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Option '{0}' has no value.. + /// + public static string SentenceMissingValueOptionError { + get { + return ResourceManager.GetString("SentenceMissingValueOptionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Options: {0} are not compatible with {1}.. + /// + public static string SentenceMutuallyExclusiveSetErrors { + get { + return ResourceManager.GetString("SentenceMutuallyExclusiveSetErrors", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No verb selected.. + /// + public static string SentenceNoVerbSelectedError { + get { + return ResourceManager.GetString("SentenceNoVerbSelectedError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Option '{0}' is defined multiple times.. + /// + public static string SentenceRepeatedOptionError { + get { + return ResourceManager.GetString("SentenceRepeatedOptionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Required.. + /// + public static string SentenceRequiredWord { + get { + return ResourceManager.GetString("SentenceRequiredWord", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A sequence option '{0}' is defined with fewer or more items than required.. + /// + public static string SentenceSequenceOutOfRangeErrorOption { + get { + return ResourceManager.GetString("SentenceSequenceOutOfRangeErrorOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A sequence value not bound to option name is defined with few items than required.. + /// + public static string SentenceSequenceOutOfRangeErrorValue { + get { + return ResourceManager.GetString("SentenceSequenceOutOfRangeErrorValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error setting value to option '{0}': {1}. + /// + public static string SentenceSetValueExceptionError { + get { + return ResourceManager.GetString("SentenceSetValueExceptionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Option '{0}' is unknown.. + /// + public static string SentenceUnknownOptionError { + get { + return ResourceManager.GetString("SentenceUnknownOptionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to USAGE:. + /// + public static string SentenceUsageHeadingText { + get { + return ResourceManager.GetString("SentenceUsageHeadingText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Display version information.. + /// + public static string SentenceVersionCommandText { + get { + return ResourceManager.GetString("SentenceVersionCommandText", resourceCulture); + } + } + } +} diff --git a/demo/ReadText.LocalizedDemo/Properties/Resources.cs.resx b/demo/ReadText.LocalizedDemo/Properties/Resources.cs.resx new file mode 100644 index 00000000..cae11e19 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Properties/Resources.cs.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Čtení + + + řádků + + + bytů + + + od začátku: + + + ok konce: + + + Počet řádek zobrazených od začátku nebo konce souboru. + + + Počet bytů zobrazených od začátku nebo konce souboru. + + + Potlačit sumář. + + + Jméno vstupního souboru. + + + Zobrazit první řádky souboru. + + + normální scénář + + + specifikace počtu byte + + + potlačit sumář + + + přečíst více řádek + + + Zobrazit poslední řádky souboru. + + + CHYBY: + + + Povinné. + + + POUŽITÍ: + + + Zobrazit tuto nápovědu. + + + Zobrazit podrobnou nápovědu pro příkaz. + + + Zobrazit informaci o verzi. + + + Token '{0}' nebyl rozpoznán. + + + Přepínač '{0}' nemá hodnotu. + + + Neznámý přepínač '{0}' + + + Přepínač '{0}' je definován ve špatném formátu. + + + Hodnota nevázaná na přepínač je definována ve špatném formátu. + + + Příkaz '{0}' nebyl rozpoznán. + + + Chybí povinný přepínač '{0}'. + + + Chybí požadovaný přepínač, který není vázán na název možnosti. + + + Přepínače: {0} nejsou kompatibilní s {1}. + + + Nebyl vybrán příkaz. + + + Přepínač '{0}' je definován vícenásobně. + + + Přepínač sekvence '{0}' je definován méně nebo vícekrát než je povoleno. + + + Hodnota přepínače je definována méněkrát než je povoleno. + + + Chyba při nastavení hodnoty přepínače '{0}': {1} + + \ No newline at end of file diff --git a/demo/ReadText.LocalizedDemo/Properties/Resources.resx b/demo/ReadText.LocalizedDemo/Properties/Resources.resx new file mode 100644 index 00000000..afdea3d0 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Properties/Resources.resx @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Reading + + + lines + + + bytes + + + from top: + + + from bottom: + + + Lines to be printed from the beginning or end of the file. + + + Bytes to be printed from the beginning or end of the file. + + + Suppresses summary messages. + + + Input file to be processed. + + + Displays first lines of a file. + + + normal scenario + + + specify bytes + + + suppress summary + + + read more lines + + + Displays last lines of a file. + + + Možnost '{0}' je definována ve špatném formátu. + + + A value not bound to option name is defined with a bad format. + + + Token '{0}' is not recognized. + + + Verb '{0}' is not recognized. + + + ERROR(S): + + + Display this help screen. + + + Display more information on a specific command. + + + Required option '{0}' is missing. + + + A required value not bound to option name is missing. + + + Option '{0}' has no value. + + + Options: {0} are not compatible with {1}. + + + No verb selected. + + + Option '{0}' is defined multiple times. + + + Required. + + + A sequence option '{0}' is defined with fewer or more items than required. + + + A sequence value not bound to option name is defined with few items than required. + + + Error setting value to option '{0}': {1} + + + Option '{0}' is unknown. + + + USAGE: + + + Display version information. + + \ No newline at end of file diff --git a/demo/ReadText.LocalizedDemo/Properties/launchSettings.json b/demo/ReadText.LocalizedDemo/Properties/launchSettings.json new file mode 100644 index 00000000..a5627a1b --- /dev/null +++ b/demo/ReadText.LocalizedDemo/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "ReadText.LocalizedDemo": { + "commandName": "Project", + "commandLineArgs": "head" + } + } +} \ No newline at end of file diff --git a/demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.csproj b/demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.csproj new file mode 100644 index 00000000..57251e03 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.csproj @@ -0,0 +1,23 @@ + + + Exe + net40;net45;net461;netcoreapp2.1;netcoreapp2.0 +false + + + + + + + True + True + Resources.resx + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + \ No newline at end of file diff --git a/demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.sln b/demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.sln new file mode 100644 index 00000000..e769b7da --- /dev/null +++ b/demo/ReadText.LocalizedDemo/ReadText.LocalizedDemo.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.106 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadText.LocalizedDemo", "ReadText.LocalizedDemo.csproj", "{F9D3B288-1A73-4C91-8ED7-11ED1704B817}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandLine", "..\..\src\CommandLine\CommandLine.csproj", "{A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9D3B288-1A73-4C91-8ED7-11ED1704B817}.Release|Any CPU.Build.0 = Release|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A03AADAC-F7E5-44A6-8BCC-492B1697CCC9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FF14CDF0-EF51-448B-918C-47CD369568DF} + EndGlobalSection +EndGlobal diff --git a/demo/ReadText.LocalizedDemo/packages.config b/demo/ReadText.LocalizedDemo/packages.config new file mode 100644 index 00000000..d34c1336 --- /dev/null +++ b/demo/ReadText.LocalizedDemo/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From f956dd777d4530f0e997898805176f7fecbd408b Mon Sep 17 00:00:00 2001 From: Olivier Duhart <1224790+b3b00@users.noreply.github.com> Date: Mon, 29 Jul 2019 17:08:42 +0200 Subject: [PATCH 051/198] issue #482: reorder options in auto help text (#484) * issue #482 : fix * Revert "issue #482 : fix" This reverts commit 1811ce4d8c14a68e32b1f28700ec6b178dd1e727. * #482 rework * HelpText fluent API, initial * fluent API for help message options ordering * cleaning * correct UT * Revert "HelpText fluent API, initial" This reverts commit 113a21c66a1ebbedfd633a32f4d25bccdc99a1ee. * correction after Pull-Request remarks * set comparison on error action * fix * cleaning --- src/CommandLine/Text/HelpText.cs | 96 ++++++++- .../Fakes/OPtions_HelpText_Ordering.cs | 48 +++++ tests/CommandLine.Tests/Unit/Issue482Tests.cs | 192 ++++++++++++++++++ 3 files changed, 331 insertions(+), 5 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs create mode 100644 tests/CommandLine.Tests/Unit/Issue482Tests.cs diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 1306e969..b287a6e8 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -17,8 +17,72 @@ namespace CommandLine.Text /// Provides means to format an help screen. /// You can assign it in place of a instance. /// + + + + public struct ComparableOption + { + public bool Required; + public bool IsOption; + public bool IsValue; + public string LongName; + public string ShortName; + public int Index; + } + public class HelpText { + + #region ordering + + ComparableOption ToComparableOption(Specification spec, int index) + { + OptionSpecification option = spec as OptionSpecification; + ValueSpecification value = spec as ValueSpecification; + bool required = option?.Required ?? false; + + return new ComparableOption() + { + Required = required, + IsOption = option != null, + IsValue = value != null, + LongName = option?.LongName ?? value?.MetaName, + ShortName = option?.ShortName, + Index = index + }; + } + + + public Comparison OptionComparison { get; set; } = null; + + public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) => + { + if (attr1.IsOption && attr2.IsOption) + { + if (attr1.Required && !attr2.Required) + { + return -1; + } + else if (!attr1.Required && attr2.Required) + { + return 1; + } + + return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); + + } + else if (attr1.IsOption && attr2.IsValue) + { + return -1; + } + else + { + return 1; + } + }; + + #endregion + private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width /// @@ -240,6 +304,7 @@ public SentenceBuilder SentenceBuilder /// A delegate used to customize model used to render text block of usage examples. /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. /// The maximum width of the display. + /// a comparison lambda to order options in help text /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. public static HelpText AutoBuild( ParserResult parserResult, @@ -736,14 +801,37 @@ private HelpText AddOptionsImpl( int maximumLength) { var maxLength = GetMaxLength(specifications); + + optionsHelp = new StringBuilder(BuilderCapacity); var remainingSpace = maximumLength - (maxLength + TotalOptionPadding); - specifications.ForEach( - option => - AddOption(requiredWord, maxLength, option, remainingSpace)); + if (OptionComparison != null) + { + int i = -1; + var comparables = specifications.ToList().Select(s => + { + i++; + return ToComparableOption(s, i); + }).ToList(); + comparables.Sort(OptionComparison); + + + foreach (var comparable in comparables) + { + Specification spec = specifications.ElementAt(comparable.Index); + AddOption(requiredWord, maxLength, spec, remainingSpace); + } + } + else + { + specifications.ForEach( + option => + AddOption(requiredWord, maxLength, option, remainingSpace)); + + } return this; } @@ -953,5 +1041,3 @@ private static string FormatDefaultValue(T value) } } - - diff --git a/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs new file mode 100644 index 00000000..3896ab67 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs @@ -0,0 +1,48 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace CommandLine.Tests.Fakes +{ + + [Verb("verb1")] + class Options_HelpText_Ordering_Verb1 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('f', "foxtrot", Required = false)] + public string foxOption { get; set; } + + [Option('e', "echo", Required = false)] + public string echoOption { get; set; } + + [Value(0)] public string someExtraOption { get; set; } + } + + [Verb("verb2")] + class Options_HelpText_Ordering_Verb2 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Issue482Tests.cs b/tests/CommandLine.Tests/Unit/Issue482Tests.cs new file mode 100644 index 00000000..61bc1f25 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue482Tests.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using CommandLine.Core; +using System.Linq; +using System.Reflection; +using CommandLine.Infrastructure; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using System.Text; +using Xunit.Sdk; + +namespace CommandLine.Tests.Unit +{ + public class Issue482Tests + { + [Fact] + public void AutoBuild_without_ordering() + { + string expectedCompany = "Company"; + + + var parser = Parser.Default; + var parseResult = parser.ParseArguments( + new[] { "verb1", "--help" }) + .WithNotParsed(errors => { ; }) + .WithParsed(args => {; }); + + var message = HelpText.AutoBuild(parseResult, + error =>error, + ex => ex + ); + + string helpMessage = message.ToString(); + var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + List expected = new List() + { + " -a, --alpha Required.", + " -b, --alpha2 Required.", + " -d, --charlie", + " -c, --bravo", + "-f, --foxtrot", + "-e, --echo", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0" + }; + Assert.Equal(expected.Count, helps.Count); + int i = 0; + foreach (var expect in expected) + { + Assert.Equal(expect.Trim(), helps[i].Trim()); + i++; + } + + ; + } + + [Fact] + public void AutoBuild_with_ordering() + { + string expectedCompany = "Company"; + + + var parser = Parser.Default; + var parseResult = parser.ParseArguments( + new[] { "verb1", "--help" }) + .WithNotParsed(errors => { ; }) + .WithParsed(args => {; }); + + Comparison comparison = HelpText.RequiredThenAlphaComparison; + + string message = HelpText.AutoBuild(parseResult, + error => + { + error.OptionComparison = HelpText.RequiredThenAlphaComparison; + return error; + }, + ex => ex); + + + string helpMessage = message.ToString(); + var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + List expected = new List() + { + " -a, --alpha Required.", + " -b, --alpha2 Required.", + " -c, --bravo", + " -d, --charlie", + "-e, --echo", + "-f, --foxtrot", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0" + }; + Assert.Equal(expected.Count, helps.Count); + int i = 0; + foreach (var expect in expected) + { + Assert.Equal(expect.Trim(), helps[i].Trim()); + i++; + } + + ; + } + + [Fact] + public void AutoBuild_with_ordering_on_shortName() + { + string expectedCompany = "Company"; + + + var parser = Parser.Default; + var parseResult = parser.ParseArguments( + new[] { "verb1", "--help" }) + .WithNotParsed(errors => { ; }) + .WithParsed(args => {; }); + + Comparison orderOnShortName = (ComparableOption attr1, ComparableOption attr2) => + { + if (attr1.IsOption && attr2.IsOption) + { + if (attr1.Required && !attr2.Required) + { + return -1; + } + else if (!attr1.Required && attr2.Required) + { + return 1; + } + else + { + if (string.IsNullOrEmpty(attr1.ShortName) && !string.IsNullOrEmpty(attr2.ShortName)) + { + return 1; + } + else if (!string.IsNullOrEmpty(attr1.ShortName) && string.IsNullOrEmpty(attr2.ShortName)) + { + return -1; + } + return String.Compare(attr1.ShortName, attr2.ShortName, StringComparison.Ordinal); + } + } + else if (attr1.IsOption && attr2.IsValue) + { + return -1; + } + else + { + return 1; + } + }; + + string message = HelpText.AutoBuild(parseResult, + error => + { + error.OptionComparison = orderOnShortName; + return error; + }, + ex => ex, + false, + 80 + ); + + + var helps = message.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + List expected = new List() + { + " -a, --alpha Required.", + " -b, --alpha2 Required.", + " -c, --bravo", + " -d, --charlie", + "-e, --echo", + "-f, --foxtrot", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0" + }; + Assert.Equal(expected.Count, helps.Count); + int i = 0; + foreach (var expect in expected) + { + Assert.Equal(expect.Trim(), helps[i].Trim()); + i++; + } + } + + + } +} From 07eaee23406f2036062402786fdf526bc2d60345 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Mon, 29 Jul 2019 18:20:45 +0200 Subject: [PATCH 052/198] Fix issue #418, modify version screen to print a new line at the end (#443) * Fix issue #418, modify version screen to print a new line at the end * Move unit test to a separate file --- src/CommandLine/Text/HelpText.cs | 2 +- tests/CommandLine.Tests/Unit/Issue418Tests.cs | 36 +++++++++++++++++++ tests/CommandLine.Tests/Unit/ParserTests.cs | 2 ++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/CommandLine.Tests/Unit/Issue418Tests.cs diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index b287a6e8..b23bb804 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -392,7 +392,7 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay var errors = ((NotParsed)parserResult).Errors; if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError)) - return new HelpText(HeadingInfo.Default){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); + return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}"){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth); diff --git a/tests/CommandLine.Tests/Unit/Issue418Tests.cs b/tests/CommandLine.Tests/Unit/Issue418Tests.cs new file mode 100644 index 00000000..9c8678bd --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue418Tests.cs @@ -0,0 +1,36 @@ +using CommandLine.Text; +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using CommandLine.Tests.Fakes; +using Xunit; +using FluentAssertions; + +namespace CommandLine.Tests.Unit +{ + //issue#418, --version does not print a new line at the end cause trouble in Linux + public class Issue418Tests + { + + [Fact] + public void Explicit_version_request_generates_version_info_screen_with_eol() + { + // Fixture setup + var help = new StringWriter(); + var sut = new Parser(config => config.HelpWriter = help); + + // Exercize system + sut.ParseArguments(new[] { "--version" }); + var result = help.ToString(); + // Verify outcome + var lines = result.ToNotEmptyLines(); + result.Length.Should().BeGreaterThan(0); + result.Should().EndWith(Environment.NewLine); + result.ToNotEmptyLines().Length.Should().Be(1); + + // Teardown + } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 23cf0f32..07e9bed3 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -802,6 +802,7 @@ public void Parse_options_with_shuffled_index_values() Assert.Equal("one", args.Arg1); Assert.Equal("two", args.Arg2); }); + } @@ -823,5 +824,6 @@ public void Blank_lines_are_inserted_between_verbs() lines[10].Should().BeEquivalentTo("version Display version information."); // Teardown } + } } From cf64a6837283ec7a65f0b54695b4caade1a7d0c5 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Wed, 31 Jul 2019 21:44:20 +0200 Subject: [PATCH 053/198] Changelog (#488) * Add changelog.md --- CHANGELOG.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..15ecae61 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,96 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.6.0] - 2019-07-31 +### Added +- Support HelpText localization with ResourceType property by [@tkouba](https://github.com/commandlineparser/commandline/pull/356). +- Add demo for complete localization of command line help using resources by[@tkouba](https://github.com/commandlineparser/commandline/pull/485). +- Localize VerbAttribute by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/473). +- Improve support for multiline help text by [@NeilMacMullen](https://github.com/commandlineparser/commandline/pull/456/). +- Reorder options in auto help text (issue #482) [@b3b00](https://github.com/commandlineparser/commandline/pull/484). +- Add IsHelp() and IsVersion() Extension methods to mange HelpText errors by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/467). + +### Fixed +- Fix issues for HelpText.AutoBuild configuration (issues #224 , # 259) by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/467). +- Test maintainance: add missed tests and removing xUnit1013 warning by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/462). +- Fix issue #104 of nullable enum by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/453). +- Fix issue #418, modify version screen to print a new line at the end by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/443). + + +## [2.5.0] - 2019-04-27 +### Added +- Add support to NET40 and NET45 for both CSharp and FSharp by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/430). + + +### Changed +- Proposed changes for enhancement by [@Wind010](https://github.com/commandlineparser/commandline/pull/314), cover:appveyor.yml, ReflectionExtensions.cs and error.cs. +- Enhance the CSharp demo to run in multi-target net40;net45;netcoreapp2.0;netcoreapp2.1 by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/430). +- Added explicit support for .NET 4.6.1 and .NET Core 2.0 by [@ravenpride](https://github.com/commandlineparser/commandline/pull/400). +- Convert commandline project to multi-target project netstandard2.0;net40;net45;net461. +- Convert commandline Test to multi-target project net461;netcoreapp2.0. + + + +### Fixed +- Fix the null EntryAssembly Exception in unit test of net4x projects: issues #389,#424 by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/430). +- Fix the test case 'Add unit tests for Issue #389 and #392 +- Fix CSC error CS7027: Error signing output with public key from file 'CommandLine.snk' -- Invalid public key in appveyor CI. +- Fix the error CS0234: The type or namespace name 'FSharp' for net40 Framework. +- Fix Mis-typed CommandLine.BaseAttribute.Default results in ArgumentException: Object of type 'X' cannot be converted to type 'Y' (issue #189) by[@Wind010](https://github.com/commandlineparser/commandline/pull/314). + + + + +## [2.4.3] - 2019-01-09 +### Added +- Add support to NetStandard2.0 by [@ViktorHofer](https://github.com/commandlineparser/commandline/pull/307) +- Add strong name signing [@ViktorHofer](https://github.com/commandlineparser/commandline/pull/307) +- Added AutoHelp and AutoVersion properties to control adding of implicit 'help' and 'version' options/verbs by [@Athari](https://github.com/commandlineparser/commandline/pull/256). +- Added simpler C# Quick Start example at readme.md by [@lythix](https://github.com/commandlineparser/commandline/pull/274). +- Add validate feature in Set parameter, and throw exception, and show usage,Issue #283 by[@e673](https://github.com/commandlineparser/commandline/pull/286). + + +### Deprecated +- Drop support for NET40 and NET45 + + +### Removed +- Disable faulty tests in netsatbdard2.0 and enable testing in CI. + + +### Fixed +- Fix grammar error in specification error message by [@DillonAd](https://github.com/commandlineparser/commandline/pull/276). +- Fix HelpText.AutoBuild Usage spacing by[@ElijahReva](https://github.com/commandlineparser/commandline/pull/280). +- Fix type at readme.md file by [@matthewjberger](https://github.com/commandlineparser/commandline/pull/304) +- Fix not showing correct header info, issue #34 by[@tynar](https://github.com/commandlineparser/commandline/pull/312). +- Fix title of assembly renders oddly issue-#197 by [@Yiabiten](https://github.com/commandlineparser/commandline/pull/344). +- Fix nuget apikey by [@ericnewton76](https://github.com/commandlineparser/commandline/pull/386). +- Fix missing fsharp from github release deployment by @ericnewton76. +- Fix to Display Width Tests by [@Oddley](https://github.com/commandlineparser/commandline/pull/278). +- Fixing DisplayWidth for newer Mono by [@Oddley](https://github.com/commandlineparser/commandline/pull/279). + + +## [2.3.0] - 2018-08-13 +### Added +- Properly handle CaseInsensitiveEnumValues flag fixing issue #198 by [@niklaskarl](https://github.com/commandlineparser/commandline/pull/231). + +### Changed +- Updated README examples quick start example for c# and Vb.net to work with the new API by [@loligans](https://github.com/commandlineparser/commandline/pull/218). +- Updated README by [@ericnewton76](https://github.com/commandlineparser/commandline/pull/208). +- Update copyright in unit tests +- Patching appveyor dotnet csproj +- Updates to appveyor to create a build matrix + +### Fixed +- hotfix/issue #213 fsharp dependency by [@ericnewton76](https://github.com/commandlineparser/commandline/pull/215). + + +## [2.2.1] - 2018-01-10 + +## [2.2.0] - 2018-01-07 + +## [1.9.71.2] - 2013-02-27 +The starting bascode version \ No newline at end of file From dfb3098ad9cf9ec04233750f4bae8adce637082e Mon Sep 17 00:00:00 2001 From: Wallace Kelly Date: Tue, 6 Aug 2019 08:49:31 -0400 Subject: [PATCH 054/198] In F# demo include Help and Version matching --- demo/fsharp-demo.fsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/fsharp-demo.fsx b/demo/fsharp-demo.fsx index 81b1640d..8aff84e3 100644 --- a/demo/fsharp-demo.fsx +++ b/demo/fsharp-demo.fsx @@ -22,9 +22,11 @@ let formatLong o = let formatInput (o : options) = sprintf "--stringvalue: %s\n-i: %A\n-x: %b\nvalue: %s\n" o.stringValue o.intSequence o.boolValue (formatLong o.longValue) -let inline (|Success|Fail|) (result : ParserResult<'a>) = +let inline (|Success|Help|Version|Fail|) (result : ParserResult<'a>) = match result with | :? Parsed<'a> as parsed -> Success(parsed.Value) + | :? NotParsed<'a> as notParsed when notParsed.Errors.IsHelp() -> Help + | :? NotParsed<'a> as notParsed when notParsed.Errors.IsVersion() -> Version | :? NotParsed<'a> as notParsed -> Fail(notParsed.Errors) | _ -> failwith "invalid parser result" @@ -34,3 +36,4 @@ let result = Parser.Default.ParseArguments(args) match result with | Success(opts) -> printf "%s" (formatInput opts) | Fail(errs) -> printf "Invalid: %A, Errors: %u\n" args (Seq.length errs) + | Help | Version -> () From ce3011ced791a983b6a6eeed057919b060abaaf0 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Fri, 6 Dec 2019 20:16:12 +0200 Subject: [PATCH 055/198] fix nuget license info -issue #545 (#549) --- Directory.Build.props | 10 +++++++++- appveyor.yml | 9 ++++++++- src/CommandLine/CommandLine.csproj | 5 ++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a6a8d8db..91a53350 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,16 @@ CS1591;CS0219;8002;NU5125 + $(MSBuildThisFileDirectory) $(DefineConstants);NETFRAMEWORK - \ No newline at end of file + + + + runtime; build; native; contentfiles; analyzers + all + + + diff --git a/appveyor.yml b/appveyor.yml index e472f66c..56b877ae 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.5.{build} +version: 2.7.0-alpha-{build} image: Visual Studio 2017 @@ -56,3 +56,10 @@ deploy: artifact: 'NuGetPackages' on: APPVEYOR_REPO_TAG: true + +- provider: NuGet + server: https://www.myget.org/F/commandline/api/v2/package + api_key: + secure: hSatieECG8d1qvzcQejfzQH8vQa4W0GbXU10/xVXvL4dloo1vZMlCdHZbpZkLMNW + artifact: 'NuGetPackages' + symbol_server: https://www.myget.org/F/commandline/symbols/api/v2/package diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 80933f08..084921f8 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -19,7 +19,7 @@ Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors - https://raw.githubusercontent.com/gsscoder/commandline/master/doc/LICENSE + License.md https://github.com/commandlineparser/commandline https://raw.githubusercontent.com/commandlineparser/commandline/master/art/CommandLine20.png command line;commandline;argument;option;parser;parsing;library;syntax;shell @@ -44,4 +44,7 @@ + + + \ No newline at end of file From 70a60b97c8c7f4c420abe30a3bafd1c63ecfd479 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sat, 7 Dec 2019 22:15:53 +0200 Subject: [PATCH 056/198] Add ability to skip options with default values for UnParserSettings (#541) and Quote DatTime Option (#502) --- src/CommandLine/UnParserExtensions.cs | 56 ++++++++++++------- .../CommandLine.Tests/Fakes/Hidden_Option.cs | 2 +- .../Fakes/Options_With_Defaults.cs | 15 +++++ .../Unit/UnParserExtensionsTests.cs | 36 ++++++++++++ 4 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 7db948e7..701c04d2 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -19,6 +19,7 @@ public class UnParserSettings private bool groupSwitches; private bool useEqualToken; private bool showHidden; + private bool skipDefault; /// /// Gets or sets a value indicating whether unparsing process shall prefer short or long names. @@ -56,6 +57,14 @@ public bool ShowHidden set { PopsicleSetter.Set(Consumed, ref showHidden, value); } } /// + /// Gets or sets a value indicating whether unparsing process shall skip options with DefaultValue. + /// + public bool SkipDefault + { + get { return skipDefault; } + set { PopsicleSetter.Set(Consumed, ref skipDefault, value); } + } + /// /// Factory method that creates an instance of with GroupSwitches set to true. /// /// A properly initalized instance. @@ -90,7 +99,7 @@ public static class UnParserExtensions /// A string with command line arguments. public static string FormatCommandLine(this Parser parser, T options) { - return parser.FormatCommandLine(options, config => {}); + return parser.FormatCommandLine(options, config => { }); } /// @@ -119,34 +128,38 @@ public static string FormatCommandLine(this Parser parser, T options, Action< var specs = (from info in type.GetSpecifications( - pi => new { Specification = Specification.FromProperty(pi), - Value = pi.GetValue(options, null).NormalizeValue(), PropertyValue = pi.GetValue(options, null) }) - where !info.PropertyValue.IsEmpty() - select info) + pi => new + { + Specification = Specification.FromProperty(pi), + Value = pi.GetValue(options, null).NormalizeValue(), + PropertyValue = pi.GetValue(options, null) + }) + where !info.PropertyValue.IsEmpty(info.Specification,settings.SkipDefault) + select info) .Memorize(); var allOptSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Option) - let o = (OptionSpecification)info.Specification - where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value)) - where !o.Hidden || settings.ShowHidden - orderby o.UniqueName() - select info; + let o = (OptionSpecification)info.Specification + where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value)) + where !o.Hidden || settings.ShowHidden + orderby o.UniqueName() + select info; var shortSwitches = from info in allOptSpecs - let o = (OptionSpecification)info.Specification - where o.TargetType == TargetType.Switch - where o.ShortName.Length > 0 - orderby o.UniqueName() - select info; + let o = (OptionSpecification)info.Specification + where o.TargetType == TargetType.Switch + where o.ShortName.Length > 0 + orderby o.UniqueName() + select info; var optSpecs = settings.GroupSwitches ? allOptSpecs.Where(info => !shortSwitches.Contains(info)) : allOptSpecs; var valSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Value) - let v = (ValueSpecification)info.Specification - orderby v.Index - select info; + let v = (ValueSpecification)info.Specification + orderby v.Index + select info; builder = settings.GroupSwitches && shortSwitches.Any() ? builder.Append('-').Append(string.Join(string.Empty, shortSwitches.Select( @@ -191,6 +204,7 @@ private static string FormatValue(Specification spec, object value) private static object FormatWithQuotesIfString(object value) { + if (value is DateTime) value = $"\"{value}\""; Func doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; @@ -218,7 +232,7 @@ private static string FormatName(this OptionSpecification optionSpec, UnParserSe { // Have a long name and short name not preferred? Go with long! // No short name? Has to be long! - var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName) + var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName) || optionSpec.ShortName.Length == 0; return @@ -242,9 +256,11 @@ private static object NormalizeValue(this object value) return value; } - private static bool IsEmpty(this object value) + private static bool IsEmpty(this object value, Specification specification,bool skipDefault) { if (value == null) return true; + + if (skipDefault && value.Equals(specification.DefaultValue.FromJust())) return true; #if !SKIP_FSHARP if (ReflectionHelper.IsFSharpOptionType(value.GetType()) && !FSharpOptionHelper.IsSome(value)) return true; #endif diff --git a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs index 1f18f216..1917ecab 100644 --- a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs +++ b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs @@ -8,7 +8,7 @@ namespace CommandLine.Tests.Fakes { public class Hidden_Option { - [Option('h', "hiddenOption", Default="hidden", Hidden = true)] + [Option('h', "hiddenOption", Hidden = true)] public string HiddenOption { get; set; } } } diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs new file mode 100644 index 00000000..792f7933 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs @@ -0,0 +1,15 @@ +namespace CommandLine.Tests.Fakes +{ + class Options_With_Defaults + { + [Option(Default = 99)] + public int P1 { get; set; } + [Option()] + public string P2 { get; set; } + [Option(Default = 88)] + public int P3 { get; set; } + [Option(Default = Shapes.Square)] + public Shapes P4 { get; set; } + } +} + diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 8ff702a5..03247105 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -1,7 +1,9 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using CommandLine.Tests.Fakes; using Xunit; using FluentAssertions; @@ -103,6 +105,40 @@ public static void UnParsing_instance_with_dash_in_value_and_dashdash_disabled_r .Should().BeEquivalentTo("-something with dash"); } + [Fact] + public static void UnParsing_instance_with_default_values_when_skip_default_is_false() + { + var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square }; + new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo("--p1 99 --p2 xyz --p3 88 --p4 Square"); + } + + [Fact] + public static void UnParsing_instance_with_default_values_when_skip_default_is_true() + { + var options = new Options_With_Defaults {P2 = "xyz", P1 = 99, P3 = 88,P4= Shapes.Square } ; + new Parser() + .FormatCommandLine(options,x=>x.SkipDefault=true) + .Should().BeEquivalentTo("--p2 xyz"); + } + + [Fact] + public static void UnParsing_instance_with_datetime() + { + var date = new DateTime(2019, 5, 1); + var options = new Options_Date { Start=date }; + var result = new Parser() + .FormatCommandLine(options); //--start "1/5/2019 12:00:00 AM", date is based on Culture + var expected = Regex.Match(result, @"--start\s"".+""").Success; //result contain quote + Assert.True(expected); + } + + internal class Options_Date + { + [Option] + public DateTime? Start { get; set; } + } public static IEnumerable UnParseData { get From 0148bdac7868483694ea99a8dca08affc51b8d9f Mon Sep 17 00:00:00 2001 From: Aaron Sherber Date: Sat, 7 Dec 2019 15:45:09 -0500 Subject: [PATCH 057/198] Improve spacing in HelpText (#494) * Improve spacing in HelpText * Make new spacing an option * Refactor * Extend logic to PostOptions * Add XML docs --- .../Infrastructure/StringBuilderExtensions.cs | 36 ++- src/CommandLine/Text/HelpText.cs | 55 +++- .../Unit/StringBuilderExtensionsTests.cs | 104 +++++++ .../Unit/Text/HelpTextTests.cs | 279 ++++++++++++------ 4 files changed, 378 insertions(+), 96 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs diff --git a/src/CommandLine/Infrastructure/StringBuilderExtensions.cs b/src/CommandLine/Infrastructure/StringBuilderExtensions.cs index 6519b66f..ae4ccbc4 100644 --- a/src/CommandLine/Infrastructure/StringBuilderExtensions.cs +++ b/src/CommandLine/Infrastructure/StringBuilderExtensions.cs @@ -113,5 +113,39 @@ public static int TrailingSpaces(this StringBuilder builder) } return c; } + + /// + /// Indicates whether the string value of a + /// starts with the input parameter. Returns false if either + /// the StringBuilder or input string is null or empty. + /// + /// The to test. + /// The to look for. + /// + public static bool SafeStartsWith(this StringBuilder builder, string s) + { + if (string.IsNullOrEmpty(s)) + return false; + + return builder?.Length >= s.Length + && builder.ToString(0, s.Length) == s; + } + + /// + /// Indicates whether the string value of a + /// ends with the input parameter. Returns false if either + /// the StringBuilder or input string is null or empty. + /// + /// The to test. + /// The to look for. + /// + public static bool SafeEndsWith(this StringBuilder builder, string s) + { + if (string.IsNullOrEmpty(s)) + return false; + + return builder?.Length >= s.Length + && builder.ToString(builder.Length - s.Length, s.Length) == s; + } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index b23bb804..2112a904 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -109,6 +109,7 @@ ComparableOption ToComparableOption(Specification spec, int index) private bool addEnumValuesToHelpText; private bool autoHelp; private bool autoVersion; + private bool addNewLineBetweenHelpSections; /// /// Initializes a new instance of the class. @@ -258,6 +259,15 @@ public bool AdditionalNewLineAfterOption set { additionalNewLineAfterOption = value; } } + /// + /// Gets or sets a value indicating whether to add newlines between help sections. + /// + public bool AddNewLineBetweenHelpSections + { + get { return addNewLineBetweenHelpSections; } + set { addNewLineBetweenHelpSections = value; } + } + /// /// Gets or sets a value indicating whether to add the values of an enum after the description of the specification. /// @@ -352,7 +362,11 @@ public static HelpText AutoBuild( { var heading = auto.SentenceBuilder.UsageHeadingText(); if (heading.Length > 0) + { + if (auto.AddNewLineBetweenHelpSections) + heading = Environment.NewLine + heading; auto.AddPreOptionsLine(heading); + } } usageAttr.Do( @@ -707,19 +721,40 @@ public static IEnumerable RenderUsageTextAsLines(ParserResult pars public override string ToString() { const int ExtraLength = 10; - return - new StringBuilder( - heading.SafeLength() + copyright.SafeLength() + preOptionsHelp.SafeLength() + - optionsHelp.SafeLength() + ExtraLength).Append(heading) - .AppendWhen(!string.IsNullOrEmpty(copyright), Environment.NewLine, copyright) - .AppendWhen(preOptionsHelp.Length > 0, Environment.NewLine, preOptionsHelp.ToString()) - .AppendWhen( - optionsHelp != null && optionsHelp.Length > 0, + + var sbLength = heading.SafeLength() + copyright.SafeLength() + preOptionsHelp.SafeLength() + + optionsHelp.SafeLength() + postOptionsHelp.SafeLength() + ExtraLength; + var result = new StringBuilder(sbLength); + + result.Append(heading) + .AppendWhen(!string.IsNullOrEmpty(copyright), + Environment.NewLine, + copyright) + .AppendWhen(preOptionsHelp.SafeLength() > 0, + NewLineIfNeededBefore(preOptionsHelp), + Environment.NewLine, + preOptionsHelp.ToString()) + .AppendWhen(optionsHelp.SafeLength() > 0, Environment.NewLine, Environment.NewLine, optionsHelp.SafeToString()) - .AppendWhen(postOptionsHelp.Length > 0, Environment.NewLine, postOptionsHelp.ToString()) - .ToString(); + .AppendWhen(postOptionsHelp.SafeLength() > 0, + NewLineIfNeededBefore(postOptionsHelp), + Environment.NewLine, + postOptionsHelp.ToString()); + + string NewLineIfNeededBefore(StringBuilder sb) + { + if (AddNewLineBetweenHelpSections + && result.Length > 0 + && !result.SafeEndsWith(Environment.NewLine) + && !sb.SafeStartsWith(Environment.NewLine)) + return Environment.NewLine; + else + return null; + } + + return result.ToString(); } internal static void AddLine(StringBuilder builder, string value, int maximumLength) diff --git a/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs b/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs new file mode 100644 index 00000000..8fe73a60 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using FluentAssertions; +using CommandLine.Infrastructure; + +namespace CommandLine.Tests.Unit +{ + public class StringBuilderExtensionsTests + { + private static StringBuilder _sb = new StringBuilder("test string"); + private static StringBuilder _emptySb = new StringBuilder(); + private static StringBuilder _nullSb = null; + + public static IEnumerable GoodStartsWithData => new [] + { + new object[] { "t" }, + new object[] { "te" }, + new object[] { "test " }, + new object[] { "test string" } + }; + + public static IEnumerable BadTestData => new [] + { + new object[] { null }, + new object[] { "" }, + new object[] { "xyz" }, + new object[] { "some long test string" } + }; + + public static IEnumerable GoodEndsWithData => new[] + { + new object[] { "g" }, + new object[] { "ng" }, + new object[] { " string" }, + new object[] { "test string" } + }; + + + + [Theory] + [MemberData(nameof(GoodStartsWithData))] + [MemberData(nameof(BadTestData))] + public void StartsWith_null_builder_returns_false(string input) + { + _nullSb.SafeStartsWith(input).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(GoodStartsWithData))] + [MemberData(nameof(BadTestData))] + public void StartsWith_empty_builder_returns_false(string input) + { + _emptySb.SafeStartsWith(input).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(GoodStartsWithData))] + public void StartsWith_good_data_returns_true(string input) + { + _sb.SafeStartsWith(input).Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(BadTestData))] + public void StartsWith_bad_data_returns_false(string input) + { + _sb.SafeStartsWith(input).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(GoodEndsWithData))] + [MemberData(nameof(BadTestData))] + public void EndsWith_null_builder_returns_false(string input) + { + _nullSb.SafeEndsWith(input).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(GoodEndsWithData))] + [MemberData(nameof(BadTestData))] + public void EndsWith_empty_builder_returns_false(string input) + { + _emptySb.SafeEndsWith(input).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(GoodEndsWithData))] + public void EndsWith_good_data_returns_true(string input) + { + _sb.SafeEndsWith(input).Should().BeTrue(); + } + + [Theory] + [MemberData(nameof(BadTestData))] + public void EndsWith_bad_data_returns_false(string input) + { + _sb.SafeEndsWith(input).Should().BeFalse(); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 1dd8d45f..32e7ee90 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -28,53 +28,73 @@ public void Create_empty_instance() string.Empty.Should().BeEquivalentTo(new HelpText().ToString()); } - [Fact] - public void Create_instance_without_options() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Create_instance_without_options(bool newlineBetweenSections) { // Fixture setup // Exercize system var sut = - new HelpText(new HeadingInfo("Unit-tests", "2.0"), new CopyrightInfo(true, "Author", 2005, 2013)) - .AddPreOptionsLine("pre-options line 1") + new HelpText(new HeadingInfo("Unit-tests", "2.0"), new CopyrightInfo(true, "Author", 2005, 2013)); + sut.AddNewLineBetweenHelpSections = newlineBetweenSections; + sut.AddPreOptionsLine("pre-options line 1") .AddPreOptionsLine("pre-options line 2") .AddPostOptionsLine("post-options line 1") .AddPostOptionsLine("post-options line 2"); // Verify outcome - var lines = sut.ToString().ToNotEmptyLines(); + var expected = new List() + { + "Unit-tests 2.0", + "Copyright (C) 2005 - 2013 Author", + "pre-options line 1", + "pre-options line 2", + "post-options line 1", + "post-options line 2" + }; + + if (newlineBetweenSections) + { + expected.Insert(2, ""); + expected.Insert(5, ""); + } - lines[0].Should().BeEquivalentTo("Unit-tests 2.0"); - lines[1].Should().BeEquivalentTo("Copyright (C) 2005 - 2013 Author"); - lines[2].Should().BeEquivalentTo("pre-options line 1"); - lines[3].Should().BeEquivalentTo("pre-options line 2"); - lines[4].Should().BeEquivalentTo("post-options line 1"); - lines[5].Should().BeEquivalentTo("post-options line 2"); - // Teardown + var lines = sut.ToString().ToLines(); + lines.Should().StartWith(expected); } - [Fact] - public void Create_instance_with_options() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Create_instance_with_options(bool newlineBetweenSections) { // Fixture setup // Exercize system - var sut = new HelpText { AddDashesToOption = true } + var sut = new HelpText { AddDashesToOption = true, AddNewLineBetweenHelpSections = newlineBetweenSections } .AddPreOptionsLine("pre-options") .AddOptions(new NotParsed(TypeInfo.Create(typeof(Simple_Options)), Enumerable.Empty())) .AddPostOptionsLine("post-options"); // Verify outcome - - var lines = sut.ToString().ToNotEmptyLines().TrimStringArray(); - lines[0].Should().BeEquivalentTo("pre-options"); - lines[1].Should().BeEquivalentTo("--stringvalue Define a string value here."); - lines[2].Should().BeEquivalentTo("-s, --shortandlong Example with both short and long name."); - lines[3].Should().BeEquivalentTo("-i Define a int sequence here."); - lines[4].Should().BeEquivalentTo("-x Define a boolean or switch value here."); - lines[5].Should().BeEquivalentTo("--help Display this help screen."); - lines[6].Should().BeEquivalentTo("--version Display version information."); - lines[7].Should().BeEquivalentTo("value pos. 0 Define a long value here."); - lines[8].Should().BeEquivalentTo("post-options"); - // Teardown + var expected = new [] + { + "", + "pre-options", + "", + "--stringvalue Define a string value here.", + "-s, --shortandlong Example with both short and long name.", + "-i Define a int sequence here.", + "-x Define a boolean or switch value here.", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0 Define a long value here.", + "", + "post-options" + }; + + var lines = sut.ToString().ToLines().TrimStringArray(); + lines.Should().StartWith(expected); } [Fact] @@ -320,17 +340,23 @@ public void Invoke_AutoBuild_for_Options_returns_appropriate_formatted_text() var helpText = HelpText.AutoBuild(fakeResult); // Verify outcome - var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); + var lines = helpText.ToString().ToLines().TrimStringArray(); lines[0].Should().Be(HeadingInfo.Default.ToString()); - lines[1].Should().Be(CopyrightInfo.Default.ToString()); - lines[2].Should().BeEquivalentTo("ERROR(S):"); - lines[3].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); - lines[4].Should().BeEquivalentTo("A sequence option 'i' is defined with fewer or more items than required."); - lines[5].Should().BeEquivalentTo("--stringvalue Define a string value here."); - lines[6].Should().BeEquivalentTo("-s, --shortandlong Example with both short and long name."); - lines[7].Should().BeEquivalentTo("-i Define a int sequence here."); - lines[8].Should().BeEquivalentTo("-x Define a boolean or switch value here."); - lines[9].Should().BeEquivalentTo("--help Display this help screen."); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[2].Should().BeEmpty(); + lines[3].Should().BeEquivalentTo("ERROR(S):"); + lines[4].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); + lines[5].Should().BeEquivalentTo("A sequence option 'i' is defined with fewer or more items than required."); + lines[6].Should().BeEmpty(); + lines[7].Should().BeEquivalentTo("--stringvalue Define a string value here."); + lines[8].Should().BeEmpty(); + lines[9].Should().BeEquivalentTo("-s, --shortandlong Example with both short and long name."); + lines[10].Should().BeEmpty(); + lines[11].Should().BeEquivalentTo("-i Define a int sequence here."); + lines[12].Should().BeEmpty(); + lines[13].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[14].Should().BeEmpty(); + lines[15].Should().BeEquivalentTo("--help Display this help screen."); // Teardown } @@ -349,15 +375,19 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo var helpText = HelpText.AutoBuild(fakeResult); // Verify outcome - var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); + var lines = helpText.ToString().ToLines().TrimStringArray(); lines[0].Should().Be(HeadingInfo.Default.ToString()); - lines[1].Should().Be(CopyrightInfo.Default.ToString()); - lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which"); - lines[3].Should().BeEquivalentTo("changes to commit."); - lines[4].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); - lines[5].Should().BeEquivalentTo("-m, --message Use the given message as the commit message."); - lines[6].Should().BeEquivalentTo("--help Display this help screen."); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[2].Should().BeEmpty(); + lines[3].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which"); + lines[4].Should().BeEquivalentTo("changes to commit."); + lines[5].Should().BeEmpty(); + lines[6].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); + lines[7].Should().BeEmpty(); + lines[8].Should().BeEquivalentTo("-m, --message Use the given message as the commit message."); + lines[9].Should().BeEmpty(); + lines[10].Should().BeEquivalentTo("--help Display this help screen."); // Teardown } @@ -418,23 +448,26 @@ public void Create_instance_with_options_and_values() { // Fixture setup // Exercize system - var sut = new HelpText { AddDashesToOption = true } + var sut = new HelpText { AddDashesToOption = true, AdditionalNewLineAfterOption = false } .AddPreOptionsLine("pre-options") .AddOptions(new NotParsed(TypeInfo.Create(typeof(Options_With_HelpText_And_MetaValue)), Enumerable.Empty())) .AddPostOptionsLine("post-options"); // Verify outcome - var lines = sut.ToString().ToNotEmptyLines().TrimStringArray(); - lines[0].Should().BeEquivalentTo("pre-options"); - lines[1].Should().BeEquivalentTo("--stringvalue=STR Define a string value here."); - lines[2].Should().BeEquivalentTo("-i INTSEQ Define a int sequence here."); - lines[3].Should().BeEquivalentTo("-x Define a boolean or switch value here."); - lines[4].Should().BeEquivalentTo("--help Display this help screen."); - lines[5].Should().BeEquivalentTo("--version Display version information."); - lines[6].Should().BeEquivalentTo("number (pos. 0) NUM Define a long value here."); - lines[7].Should().BeEquivalentTo("paintcolor (pos. 1) COLOR Define a color value here."); - lines[8].Should().BeEquivalentTo("post-options", lines[8]); + var lines = sut.ToString().ToLines().TrimStringArray(); + lines[0].Should().BeEmpty(); + lines[1].Should().BeEquivalentTo("pre-options"); + lines[2].Should().BeEmpty(); + lines[3].Should().BeEquivalentTo("--stringvalue=STR Define a string value here."); + lines[4].Should().BeEquivalentTo("-i INTSEQ Define a int sequence here."); + lines[5].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[6].Should().BeEquivalentTo("--help Display this help screen."); + lines[7].Should().BeEquivalentTo("--version Display version information."); + lines[8].Should().BeEquivalentTo("number (pos. 0) NUM Define a long value here."); + lines[9].Should().BeEquivalentTo("paintcolor (pos. 1) COLOR Define a color value here."); + lines[10].Should().BeEmpty(); + lines[11].Should().BeEquivalentTo("post-options", lines[11]); // Teardown } @@ -466,8 +499,10 @@ public static void RenderUsageText_returns_properly_formatted_text() lines[10].Should().BeEquivalentTo(" mono testapp.exe value"); } - [Fact] - public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatted_text() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatted_text(bool newlineBetweenSections) { // Fixture setup var fakeResult = new NotParsed( @@ -478,38 +513,112 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte }); // Exercize system - var helpText = HelpText.AutoBuild(fakeResult); + var helpText = HelpText.AutoBuild(fakeResult, + h => + { + h.AddNewLineBetweenHelpSections = newlineBetweenSections; + return HelpText.DefaultParsingErrorsHandler(fakeResult, h); + }, + e => e + ); + + // Verify outcome + var expected = new List() + { + HeadingInfo.Default.ToString(), + CopyrightInfo.Default.ToString(), + "", + "ERROR(S):", + "Token 'badtoken' is not recognized.", + "USAGE:", + "Normal scenario:", + "mono testapp.exe --input file.bin --output out.bin", + "Logging warnings:", + "mono testapp.exe -w --input file.bin", + "Logging errors:", + "mono testapp.exe -e --input file.bin", + "mono testapp.exe --errs --input=file.bin", + "List:", + "mono testapp.exe -l 1,2", + "Value:", + "mono testapp.exe value", + "", + "-i, --input Set input file.", + "", + "-i, --output Set output file.", + "", + "--verbose Set verbosity level.", + "", + "-w, --warns Log warnings.", + "", + "-e, --errs Log errors.", + "", + "-l List.", + "", + "--help Display this help screen.", + "", + "--version Display version information.", + "", + "value pos. 0 Value." + }; + + if (newlineBetweenSections) + expected.Insert(5, ""); + + var text = helpText.ToString(); + var lines = text.ToLines().TrimStringArray(); + + lines.Should().StartWith(expected); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void AutoBuild_with_errors_and_preoptions_renders_correctly(bool startWithNewline, bool newlineBetweenSections) + { + // Fixture setup + var fakeResult = new NotParsed( + TypeInfo.Create(typeof(Simple_Options_Without_HelpText)), + new Error[] + { + new BadFormatTokenError("badtoken") + }); + + // Exercize system + var helpText = HelpText.AutoBuild(fakeResult, + h => + { + h.AddNewLineBetweenHelpSections = newlineBetweenSections; + h.AddPreOptionsLine((startWithNewline ? Environment.NewLine : null) + "pre-options"); + return HelpText.DefaultParsingErrorsHandler(fakeResult, h); + }, + e => e + ); // Verify outcome + var expected = new List() + { + HeadingInfo.Default.ToString(), + CopyrightInfo.Default.ToString(), + "pre-options", + "", + "ERROR(S):", + "Token 'badtoken' is not recognized.", + "", + "-v, --verbose", + "", + "--input-file" + }; + + if (newlineBetweenSections || startWithNewline) + expected.Insert(2, ""); + var text = helpText.ToString(); - var lines = text.ToNotEmptyLines().TrimStringArray(); - lines[0].Should().Be(HeadingInfo.Default.ToString()); - lines[1].Should().Be(CopyrightInfo.Default.ToString()); - lines[2].Should().BeEquivalentTo("ERROR(S):"); - lines[3].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); - lines[4].Should().BeEquivalentTo("USAGE:"); - lines[5].Should().BeEquivalentTo("Normal scenario:"); - lines[6].Should().BeEquivalentTo("mono testapp.exe --input file.bin --output out.bin"); - lines[7].Should().BeEquivalentTo("Logging warnings:"); - lines[8].Should().BeEquivalentTo("mono testapp.exe -w --input file.bin"); - lines[9].Should().BeEquivalentTo("Logging errors:"); - lines[10].Should().BeEquivalentTo("mono testapp.exe -e --input file.bin"); - lines[11].Should().BeEquivalentTo("mono testapp.exe --errs --input=file.bin"); - lines[12].Should().BeEquivalentTo("List:"); - lines[13].Should().BeEquivalentTo("mono testapp.exe -l 1,2"); - lines[14].Should().BeEquivalentTo("Value:"); - lines[15].Should().BeEquivalentTo("mono testapp.exe value"); - lines[16].Should().BeEquivalentTo("-i, --input Set input file."); - lines[17].Should().BeEquivalentTo("-i, --output Set output file."); - lines[18].Should().BeEquivalentTo("--verbose Set verbosity level."); - lines[19].Should().BeEquivalentTo("-w, --warns Log warnings."); - lines[20].Should().BeEquivalentTo("-e, --errs Log errors."); - lines[21].Should().BeEquivalentTo("-l List."); - lines[22].Should().BeEquivalentTo("--help Display this help screen."); - lines[23].Should().BeEquivalentTo("--version Display version information."); - lines[24].Should().BeEquivalentTo("value pos. 0 Value."); + var lines = text.ToLines().TrimStringArray(); - // Teardown + lines.Should().StartWith(expected); } [Fact] From 5c6540d66cd196bba46e6ef27deb9fe3f7026a99 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Sat, 7 Dec 2019 22:52:02 +0200 Subject: [PATCH 058/198] Fix PackageIconUrl warning (#551) --- src/CommandLine/CommandLine.csproj | 3 ++- src/CommandLine/Text/HelpText.cs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 084921f8..78b3ddfe 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -20,8 +20,8 @@ Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors License.md + CommandLine20.png https://github.com/commandlineparser/commandline - https://raw.githubusercontent.com/commandlineparser/commandline/master/art/CommandLine20.png command line;commandline;argument;option;parser;parsing;library;syntax;shell true 7.3 @@ -46,5 +46,6 @@ + \ No newline at end of file diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 2112a904..cc13ba07 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -314,7 +314,6 @@ public SentenceBuilder SentenceBuilder /// A delegate used to customize model used to render text block of usage examples. /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. /// The maximum width of the display. - /// a comparison lambda to order options in help text /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. public static HelpText AutoBuild( ParserResult parserResult, From 375d5dbe217a180e2de3767d26d755752f6ba979 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sat, 7 Dec 2019 23:26:22 +0200 Subject: [PATCH 059/198] fix Appveyor --- appveyor.yml | 7 ------- src/CommandLine/CommandLine.csproj | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 56b877ae..459a8bed 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,10 +56,3 @@ deploy: artifact: 'NuGetPackages' on: APPVEYOR_REPO_TAG: true - -- provider: NuGet - server: https://www.myget.org/F/commandline/api/v2/package - api_key: - secure: hSatieECG8d1qvzcQejfzQH8vQa4W0GbXU10/xVXvL4dloo1vZMlCdHZbpZkLMNW - artifact: 'NuGetPackages' - symbol_server: https://www.myget.org/F/commandline/symbols/api/v2/package diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 78b3ddfe..7624b2e7 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -48,4 +48,4 @@ - \ No newline at end of file + From 465cbd0a84847368e122c93beb0817b3336f1899 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 13:29:09 +0200 Subject: [PATCH 060/198] Introduce option groups When one or more options has group set, at least one of these properties should have set value (they behave as required) --- src/CommandLine/Core/OptionSpecification.cs | 16 +++-- .../Core/SpecificationExtensions.cs | 1 + .../Core/SpecificationPropertyRules.cs | 71 +++++++++++++------ src/CommandLine/Error.cs | 22 +++++- src/CommandLine/GroupAttribute.cs | 15 ++++ src/CommandLine/OptionAttribute.cs | 14 +++- .../Unit/Core/NameLookupTests.cs | 14 ++-- .../Unit/Core/OptionMapperTests.cs | 6 +- .../Unit/Core/TokenPartitionerTests.cs | 23 +++--- .../Unit/Core/TokenizerTests.cs | 22 +++--- 10 files changed, 149 insertions(+), 55 deletions(-) create mode 100644 src/CommandLine/GroupAttribute.cs diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 0bbbbb06..45dc2f58 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -1,4 +1,4 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System; using System.Collections.Generic; @@ -13,16 +13,18 @@ sealed class OptionSpecification : Specification private readonly string longName; private readonly char separator; private readonly string setName; + private readonly Maybe group; public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, bool hidden = false) + Type conversionType, TargetType targetType, Maybe group, bool hidden = false) : base(SpecificationType.Option, required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) { this.shortName = shortName; this.longName = longName; this.separator = separator; this.setName = setName; + this.group = group; } public static OptionSpecification FromAttribute(OptionAttribute attribute, Type conversionType, IEnumerable enumValues) @@ -41,13 +43,14 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type enumValues, conversionType, conversionType.ToTargetType(), + string.IsNullOrWhiteSpace(attribute.Group) ? Maybe.Nothing() : Maybe.Just(attribute.Group), attribute.Hidden); } public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) { return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, hidden); + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, Maybe.Nothing(), hidden); } public string ShortName @@ -69,5 +72,10 @@ public string SetName { get { return setName; } } + + public Maybe Group + { + get { return group; } + } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/SpecificationExtensions.cs b/src/CommandLine/Core/SpecificationExtensions.cs index 5f77a5dd..e223e987 100644 --- a/src/CommandLine/Core/SpecificationExtensions.cs +++ b/src/CommandLine/Core/SpecificationExtensions.cs @@ -34,6 +34,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific specification.EnumValues, specification.ConversionType, specification.TargetType, + specification.Group, specification.Hidden); } diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 71145e8a..c6a5ca50 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -1,4 +1,4 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System; using System.Collections.Generic; @@ -16,12 +16,43 @@ public static IEnumerable, IEnumerable, IEnumerable>> { EnforceMutuallyExclusiveSet(), + EnforceGroup(), EnforceRequired(), EnforceRange(), EnforceSingle(tokens) }; } + private static Func, IEnumerable> EnforceGroup() + { + return specProps => + { + var optionsValues = + from sp in specProps + where sp.Specification.IsOption() + let o = (OptionSpecification)sp.Specification + where o.Group.IsJust() + select new + { + Option = o, + Value = sp.Value + }; + + var groups = from o in optionsValues + group o by o.Option.Group.GetValueOrDefault(null) into g + select g; + + var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing())); + + if (errorGroups.Any()) + { + return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key)); + } + + return Enumerable.Empty(); + }; + } + private static Func, IEnumerable> EnforceMutuallyExclusiveSet() { return specProps => @@ -51,12 +82,12 @@ private static Func, IEnumerable> Enfo return specProps => { var requiredWithValue = from sp in specProps - where sp.Specification.IsOption() - where sp.Specification.Required - where sp.Value.IsJust() - let o = (OptionSpecification)sp.Specification - where o.SetName.Length > 0 - select sp.Specification; + where sp.Specification.IsOption() + where sp.Specification.Required + where sp.Value.IsJust() + let o = (OptionSpecification)sp.Specification + where o.SetName.Length > 0 + select sp.Specification; var setWithRequiredValue = ( from s in requiredWithValue let o = (OptionSpecification)s @@ -64,13 +95,13 @@ where o.SetName.Length > 0 select o.SetName) .Distinct(); var requiredWithoutValue = from sp in specProps - where sp.Specification.IsOption() - where sp.Specification.Required - where sp.Value.IsNothing() - let o = (OptionSpecification)sp.Specification - where o.SetName.Length > 0 - where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) - select sp.Specification; + where sp.Specification.IsOption() + where sp.Specification.Required + where sp.Value.IsNothing() + let o = (OptionSpecification)sp.Specification + where o.SetName.Length > 0 + where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) + select sp.Specification; var missing = requiredWithoutValue .Except(requiredWithValue) @@ -130,11 +161,11 @@ from o in to.DefaultIfEmpty() where o != null select new { o.ShortName, o.LongName }; var longOptions = from t in tokens - where t.IsName() - join o in specs on t.Text equals o.LongName into to - from o in to.DefaultIfEmpty() - where o != null - select new { o.ShortName, o.LongName }; + where t.IsName() + join o in specs on t.Text equals o.LongName into to + from o in to.DefaultIfEmpty() + where o != null + select new { o.ShortName, o.LongName }; var groups = from x in shortOptions.Concat(longOptions) group x by x into g let count = g.Count() @@ -155,4 +186,4 @@ private static bool ContainsIfNotEmpty(this IEnumerable sequence, T value) return true; } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index 2f208dec..d32ee293 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -210,7 +210,7 @@ public override bool Equals(object obj) /// A hash code for the current . public override int GetHashCode() { - return new {Tag, StopsProcessing, Token}.GetHashCode(); + return new { Tag, StopsProcessing, Token }.GetHashCode(); } /// @@ -289,7 +289,7 @@ public override bool Equals(object obj) /// A hash code for the current . public override int GetHashCode() { - return new {Tag, StopsProcessing, NameInfo}.GetHashCode(); + return new { Tag, StopsProcessing, NameInfo }.GetHashCode(); } /// @@ -526,4 +526,22 @@ internal InvalidAttributeConfigurationError() { } } + + public sealed class MissingGroupOptionError : Error + { + public const string ErrorMessage = "At least one option in a group must have value."; + + private readonly string group; + + internal MissingGroupOptionError(string group) + : base(ErrorType.HelpRequestedError, true) + { + this.group = group; + } + + public string Group + { + get { return group; } + } + } } diff --git a/src/CommandLine/GroupAttribute.cs b/src/CommandLine/GroupAttribute.cs new file mode 100644 index 00000000..82832d14 --- /dev/null +++ b/src/CommandLine/GroupAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace CommandLine +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class GroupAttribute : Attribute + { + public GroupAttribute(string name) + { + Name = name; + } + + public string Name { get; } + } +} diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 8ef6d63d..966f5baa 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -1,8 +1,9 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; using CommandLine.Infrastructure; +using System; + namespace CommandLine { /// @@ -15,6 +16,7 @@ public sealed class OptionAttribute : BaseAttribute private readonly string shortName; private string setName; private char separator; + private string group; private OptionAttribute(string shortName, string longName) : base() { @@ -100,8 +102,14 @@ public string SetName /// public char Separator { - get { return separator ; } + get { return separator; } set { separator = value; } } + + public string Group + { + get { return group; } + set { group = value; } + } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index f27e033c..caa0a386 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -1,11 +1,15 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; -using System.Collections.Generic; using CommandLine.Core; + +using CSharpx; + using FluentAssertions; + +using System; +using System.Collections.Generic; + using Xunit; -using CSharpx; namespace CommandLine.Tests.Unit.Core { @@ -17,7 +21,7 @@ public void Lookup_name_of_sequence_option_with_separator() // Fixture setup var expected = Maybe.Just("."); var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; // Exercize system var result = NameLookup.HavingSeparator("string-seq", specs, StringComparer.Ordinal); @@ -35,7 +39,7 @@ public void Get_name_from_option_specification() // Fixture setup var expected = new NameInfo(ShortName, LongName); - var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence); + var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()); // Exercize system var result = spec.FromOptionSpecification(); diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 460dac3b..41b93bf5 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -4,13 +4,17 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + #if PLATFORM_DOTNET using System.Reflection; #endif using CommandLine.Core; using CommandLine.Tests.Fakes; + using Xunit; + using CSharpx; + using RailwaySharp.ErrorHandling; namespace CommandLine.Tests.Unit.Core @@ -28,7 +32,7 @@ public void Map_boolean_switch_creates_boolean_value() var specProps = new[] { SpecificationProperty.Create( - new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch), + new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, Maybe.Nothing()), typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals("BoolValue", StringComparison.Ordinal)), Maybe.Nothing()) }; diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index d787645d..4266b2d4 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -1,10 +1,13 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CommandLine.Core; + +using CSharpx; + using System; using System.Collections.Generic; using System.Linq; -using CommandLine.Core; -using CSharpx; + using Xunit; namespace CommandLine.Tests.Unit.Core @@ -17,12 +20,12 @@ public void Partition_sequence_returns_sequence() // Fixture setup var expectedSequence = new[] { - new KeyValuePair>("i", new[] {"10", "20", "30", "40"}) + new KeyValuePair>("i", new[] {"10", "20", "30", "40"}) }; - var specs =new[] + var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) }; // Exercize system @@ -44,12 +47,12 @@ public void Partition_sequence_returns_sequence_with_duplicates() // Fixture setup var expectedSequence = new[] { - new KeyValuePair>("i", new[] {"10", "10", "30", "40"}) + new KeyValuePair>("i", new[] {"10", "10", "30", "40"}) }; - var specs =new[] + var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) }; // Exercize system diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index ecb21266..ac037cde 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -1,18 +1,20 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using CommandLine.Core; using CommandLine.Infrastructure; -using Xunit; using CSharpx; using FluentAssertions; using RailwaySharp.ErrorHandling; +using System; +using System.Collections.Generic; +using System.Linq; + +using Xunit; + namespace CommandLine.Tests.Unit.Core { public class TokenizerTests @@ -24,7 +26,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; // Exercize system var result = @@ -47,7 +49,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; // Exercize system var result = @@ -77,14 +79,14 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi // Exercize system var result = Tokenizer.Normalize( - //Result.Succeed( + //Result.Succeed( Enumerable.Empty() .Concat( new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Name("unknown"), Token.Value("value0", true), Token.Name("switch") }) - //,Enumerable.Empty()), - ,nameLookup); + //,Enumerable.Empty()), + , nameLookup); // Verify outcome result.Should().BeEquivalentTo(expectedTokens); @@ -127,5 +129,5 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() Assert.Equal(ErrorType.BadFormatTokenError, tokens.Last().Tag); } } - + } From f442e9aaefcd06fc36598f280605448c66f61442 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 14:24:54 +0200 Subject: [PATCH 061/198] Add help text on error --- .../Core/SpecificationPropertyRules.cs | 5 +++-- src/CommandLine/Error.cs | 18 ++++++++++++--- src/CommandLine/Text/SentenceBuilder.cs | 22 +++++++++++++------ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index c6a5ca50..d44b29c6 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -1,9 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CSharpx; + using System; using System.Collections.Generic; using System.Linq; -using CSharpx; namespace CommandLine.Core { @@ -46,7 +47,7 @@ group o by o.Option.Group.GetValueOrDefault(null) into g if (errorGroups.Any()) { - return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key)); + return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key, gr.Select(g => new NameInfo(g.Option.ShortName, g.Option.LongName)))); } return Enumerable.Empty(); diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index d32ee293..e54dbf6a 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -1,6 +1,7 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System; +using System.Collections.Generic; namespace CommandLine { @@ -69,7 +70,11 @@ public enum ErrorType /// /// Value of type. /// - InvalidAttributeConfigurationError + InvalidAttributeConfigurationError, + /// + /// Value of type. + /// + MissingGroupOptionError } @@ -532,16 +537,23 @@ public sealed class MissingGroupOptionError : Error public const string ErrorMessage = "At least one option in a group must have value."; private readonly string group; + private readonly IEnumerable names; - internal MissingGroupOptionError(string group) - : base(ErrorType.HelpRequestedError, true) + internal MissingGroupOptionError(string group, IEnumerable names) + : base(ErrorType.MissingGroupOptionError) { this.group = group; + this.names = names; } public string Group { get { return group; } } + + public IEnumerable Names + { + get { return names; } + } } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index 1c150b67..cdcf409c 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -1,10 +1,11 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CommandLine.Infrastructure; + using System; using System.Collections.Generic; using System.Linq; using System.Text; -using CommandLine.Infrastructure; namespace CommandLine.Text { @@ -41,7 +42,7 @@ public static SentenceBuilder Create() /// /// Gets a delegate that returns usage text block heading text. /// - public abstract Func UsageHeadingText { get; } + public abstract Func UsageHeadingText { get; } /// /// Get a delegate that returns the help text of help command. @@ -53,7 +54,7 @@ public static SentenceBuilder Create() /// Get a delegate that returns the help text of vesion command. /// The delegates must accept a boolean that is equal true for options; otherwise false for verbs. /// - public abstract Func VersionCommandText { get; } + public abstract Func VersionCommandText { get; } /// /// Gets a delegate that handles singular error formatting. @@ -67,7 +68,7 @@ public static SentenceBuilder Create() /// public abstract Func, string> FormatMutuallyExclusiveSetErrors { get; } - private class DefaultSentenceBuilder : SentenceBuilder + private class DefaultSentenceBuilder : SentenceBuilder { public override Func RequiredWord { @@ -140,6 +141,13 @@ public override Func FormatError case ErrorType.SetValueExceptionError: var setValueError = (SetValueExceptionError)error; return "Error setting value to option '".JoinTo(setValueError.NameInfo.NameText, "': ", setValueError.Exception.Message); + case ErrorType.MissingGroupOptionError: + var missingGroupOptionError = (MissingGroupOptionError)error; + return "At least one option from group '".JoinTo( + missingGroupOptionError.Group, + "' (", + string.Join(", ", missingGroupOptionError.Names.Select(n => n.NameText)), + ") is required."); } throw new InvalidOperationException(); }; @@ -153,8 +161,8 @@ public override Func, string> FormatMutua return errors => { var bySet = from e in errors - group e by e.SetName into g - select new { SetName = g.Key, Errors = g.ToList() }; + group e by e.SetName into g + select new { SetName = g.Key, Errors = g.ToList() }; var msgs = bySet.Select( set => @@ -169,7 +177,7 @@ group e by e.SetName into g (from x in (from s in bySet where !s.SetName.Equals(set.SetName) from e in s.Errors select e) .Distinct() - select "'".JoinTo(x.NameInfo.NameText, "', ")).ToArray()); + select "'".JoinTo(x.NameInfo.NameText, "', ")).ToArray()); return new StringBuilder("Option") From 1fe68f0d531ead6c3b5ed2aa0958721e5daed2eb Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 14:46:13 +0200 Subject: [PATCH 062/198] Add general help text. Display group name in option details --- src/CommandLine/OptionAttribute.cs | 3 + src/CommandLine/Text/HelpText.cs | 134 +++++++++++++----------- src/CommandLine/Text/SentenceBuilder.cs | 10 ++ 3 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 966f5baa..ce2e1fae 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -106,6 +106,9 @@ public char Separator set { separator = value; } } + /// + /// Gets or sets the option group name. When one or more options are grouped, at least one of them should have value. + /// public string Group { get { return group; } diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index b23bb804..a67e43dd 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -1,15 +1,17 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CommandLine.Core; +using CommandLine.Infrastructure; + +using CSharpx; + using System; using System.Collections; using System.Collections.Generic; using System.IO; -using System.Text; using System.Linq; using System.Reflection; -using CommandLine.Infrastructure; -using CommandLine.Core; -using CSharpx; +using System.Text; namespace CommandLine.Text { @@ -17,9 +19,9 @@ namespace CommandLine.Text /// Provides means to format an help screen. /// You can assign it in place of a instance. /// - - - + + + public struct ComparableOption { public bool Required; @@ -29,7 +31,7 @@ public struct ComparableOption public string ShortName; public int Index; } - + public class HelpText { @@ -55,40 +57,40 @@ ComparableOption ToComparableOption(Specification spec, int index) public Comparison OptionComparison { get; set; } = null; - public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) => - { - if (attr1.IsOption && attr2.IsOption) - { - if (attr1.Required && !attr2.Required) - { - return -1; - } - else if (!attr1.Required && attr2.Required) - { - return 1; - } - - return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); - - } - else if (attr1.IsOption && attr2.IsValue) - { - return -1; - } - else - { - return 1; - } - }; - + public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) => + { + if (attr1.IsOption && attr2.IsOption) + { + if (attr1.Required && !attr2.Required) + { + return -1; + } + else if (!attr1.Required && attr2.Required) + { + return 1; + } + + return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); + + } + else if (attr1.IsOption && attr2.IsValue) + { + return -1; + } + else + { + return 1; + } + }; + #endregion - + private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width /// /// The number of spaces between an option and its associated help text /// - private const int OptionToHelpTextSeparatorWidth = 4; + private const int OptionToHelpTextSeparatorWidth = 4; /// /// The width of the option prefix (either "--" or " " /// @@ -334,7 +336,7 @@ public static HelpText AutoBuild( var errors = Enumerable.Empty(); - + if (onError != null && parserResult.Tag == ParserResultType.NotParsed) { errors = ((NotParsed)parserResult).Errors; @@ -392,7 +394,7 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay var errors = ((NotParsed)parserResult).Errors; if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError)) - return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}"){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); + return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}") { MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth); @@ -520,6 +522,7 @@ public HelpText AddOptions(ParserResult result) return AddOptionsImpl( GetSpecificationsFromType(result.TypeInfo.Current), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), MaximumDisplayWidth); } @@ -537,6 +540,7 @@ public HelpText AddVerbs(params Type[] types) return AddOptionsImpl( AdaptVerbsToSpecifications(types), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), MaximumDisplayWidth); } @@ -553,6 +557,7 @@ public HelpText AddOptions(int maximumLength, ParserResult result) return AddOptionsImpl( GetSpecificationsFromType(result.TypeInfo.Current), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), maximumLength); } @@ -571,6 +576,7 @@ public HelpText AddVerbs(int maximumLength, params Type[] types) return AddOptionsImpl( AdaptVerbsToSpecifications(types), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), maximumLength); } @@ -614,7 +620,7 @@ public static IEnumerable RenderParsingErrorsTextAsLines( if (meaningfulErrors.Empty()) yield break; - foreach(var error in meaningfulErrors + foreach (var error in meaningfulErrors .Where(e => e.Tag != ErrorType.MutuallyExclusiveSetError)) { var line = new StringBuilder(indent.Spaces()) @@ -751,9 +757,9 @@ private IEnumerable GetSpecificationsFromType(Type type) var optionSpecs = specs .OfType(); if (autoHelp) - optionSpecs = optionSpecs.Concat(new [] { MakeHelpEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeHelpEntry() }); if (autoVersion) - optionSpecs = optionSpecs.Concat(new [] { MakeVersionEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeVersionEntry() }); var valueSpecs = specs .OfType() .OrderBy(v => v.Index); @@ -780,29 +786,30 @@ private static Maybe>> GetUsageFromTy private IEnumerable AdaptVerbsToSpecifications(IEnumerable types) { var optionSpecs = from verbTuple in Verb.SelectFromTypes(types) - select - OptionSpecification.NewSwitch( - string.Empty, - verbTuple.Item1.Name, - false, - verbTuple.Item1.HelpText, - string.Empty, - verbTuple.Item1.Hidden); + select + OptionSpecification.NewSwitch( + string.Empty, + verbTuple.Item1.Name, + false, + verbTuple.Item1.HelpText, + string.Empty, + verbTuple.Item1.Hidden); if (autoHelp) - optionSpecs = optionSpecs.Concat(new [] { MakeHelpEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeHelpEntry() }); if (autoVersion) - optionSpecs = optionSpecs.Concat(new [] { MakeVersionEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeVersionEntry() }); return optionSpecs; } private HelpText AddOptionsImpl( IEnumerable specifications, string requiredWord, + string optionGroupWord, int maximumLength) { var maxLength = GetMaxLength(specifications); - - + + optionsHelp = new StringBuilder(BuilderCapacity); @@ -822,14 +829,14 @@ private HelpText AddOptionsImpl( foreach (var comparable in comparables) { Specification spec = specifications.ElementAt(comparable.Index); - AddOption(requiredWord, maxLength, spec, remainingSpace); + AddOption(requiredWord, optionGroupWord, maxLength, spec, remainingSpace); } } else { specifications.ForEach( option => - AddOption(requiredWord, maxLength, option, remainingSpace)); + AddOption(requiredWord, optionGroupWord, maxLength, option, remainingSpace)); } @@ -865,7 +872,7 @@ private HelpText AddPreOptionsLine(string value, int maximumLength) return this; } - private HelpText AddOption(string requiredWord, int maxLength, Specification specification, int widthOfHelpText) + private HelpText AddOption(string requiredWord, string optionGroupWord, int maxLength, Specification specification, int widthOfHelpText) { if (specification.Hidden) return this; @@ -891,11 +898,16 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe if (specification.Required) optionHelpText = "{0} ".FormatInvariant(requiredWord) + optionHelpText; - + + if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + { + optionHelpText = "({0}: {1})".FormatInvariant(optionGroupWord, optionSpecification.Group.GetValueOrDefault(null)) + optionHelpText; + } + //note that we need to indent trim the start of the string because it's going to be //appended to an existing line that is as long as the indent-level - var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength+TotalOptionPadding, widthOfHelpText).TrimStart(); - + var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength + TotalOptionPadding, widthOfHelpText).TrimStart(); + optionsHelp .Append(indented) .Append(Environment.NewLine) @@ -989,7 +1001,7 @@ private int GetMaxOptionLength(OptionSpecification spec) } if (hasShort && hasLong) - specLength += OptionPrefixWidth; + specLength += OptionPrefixWidth; return specLength; } @@ -1037,7 +1049,7 @@ private static string FormatDefaultValue(T value) : string.Empty; } - + } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index cdcf409c..d58bf9c7 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -34,6 +34,11 @@ public static SentenceBuilder Create() /// public abstract Func RequiredWord { get; } + /// + /// Gets a delegate that returns the word 'group'. + /// + public abstract Func OptionGroupWord { get; } + /// /// Gets a delegate that returns that errors block heading text. /// @@ -85,6 +90,11 @@ public override Func UsageHeadingText get { return () => "USAGE:"; } } + public override Func OptionGroupWord + { + get { return () => "Group"; } + } + public override Func HelpCommandText { get From 06fcda7c70134b74284e46e59c9d06f5d2f522c5 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 14:52:04 +0200 Subject: [PATCH 063/198] Ignore required rules when group specification has group option set. --- src/CommandLine/Core/SpecificationPropertyRules.cs | 2 ++ src/CommandLine/OptionAttribute.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index d44b29c6..31a20027 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -101,6 +101,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length > 0 + where o.Group.IsNothing() where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) select sp.Specification; var missing = @@ -113,6 +114,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length == 0 + where o.Group.IsNothing() select sp.Specification) .Concat( from sp in specProps diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index ce2e1fae..4a38a4da 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -107,7 +107,7 @@ public char Separator } /// - /// Gets or sets the option group name. When one or more options are grouped, at least one of them should have value. + /// Gets or sets the option group name. When one or more options are grouped, at least one of them should have value. Required rules are ignored. /// public string Group { From 56a8138dd39b18b9419930ceef23202f6e34217b Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 22:01:23 +0200 Subject: [PATCH 064/198] Remove required word if option group is available in help text --- src/CommandLine/Text/HelpText.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index a67e43dd..5c0ecea0 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -874,6 +874,16 @@ private HelpText AddPreOptionsLine(string value, int maximumLength) private HelpText AddOption(string requiredWord, string optionGroupWord, int maxLength, Specification specification, int widthOfHelpText) { + OptionSpecification GetOptionGroupSpecification() + { + if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + { + return optionSpecification; + } + + return null; + } + if (specification.Hidden) return this; @@ -896,12 +906,14 @@ private HelpText AddOption(string requiredWord, string optionGroupWord, int maxL specification.DefaultValue.Do( defaultValue => optionHelpText = "(Default: {0}) ".FormatInvariant(FormatDefaultValue(defaultValue)) + optionHelpText); - if (specification.Required) + var optionGroupSpecification = GetOptionGroupSpecification(); + + if (specification.Required && optionGroupSpecification == null) optionHelpText = "{0} ".FormatInvariant(requiredWord) + optionHelpText; - if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + if (optionGroupSpecification != null) { - optionHelpText = "({0}: {1})".FormatInvariant(optionGroupWord, optionSpecification.Group.GetValueOrDefault(null)) + optionHelpText; + optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group.GetValueOrDefault(null)) + optionHelpText; } //note that we need to indent trim the start of the string because it's going to be From 33dfcce8465af089b3a58991475ae5f95263fcaa Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 22:01:43 +0200 Subject: [PATCH 065/198] Remove redundant xml doc parameter --- src/CommandLine/Text/HelpText.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 5c0ecea0..949e6876 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -306,7 +306,6 @@ public SentenceBuilder SentenceBuilder /// A delegate used to customize model used to render text block of usage examples. /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. /// The maximum width of the display. - /// a comparison lambda to order options in help text /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. public static HelpText AutoBuild( ParserResult parserResult, From a56720f3fcba88c50d3fbacc5f9a16a34fd6c173 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 11 Dec 2019 05:56:19 +0200 Subject: [PATCH 066/198] Fix Nullable in UnParserExtensions --- src/CommandLine/UnParserExtensions.cs | 8 +- .../Fakes/Options_With_Defaults.cs | 11 ++ .../Options_With_Enum_Having_HelpText.cs | 2 +- .../Unit/UnParserExtensionsTests.cs | 172 +++++++++++++++--- 4 files changed, 168 insertions(+), 25 deletions(-) diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 701c04d2..59c4d3b7 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -134,7 +134,7 @@ public static string FormatCommandLine(this Parser parser, T options, Action< Value = pi.GetValue(options, null).NormalizeValue(), PropertyValue = pi.GetValue(options, null) }) - where !info.PropertyValue.IsEmpty(info.Specification,settings.SkipDefault) + where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault) select info) .Memorize(); @@ -204,7 +204,7 @@ private static string FormatValue(Specification spec, object value) private static object FormatWithQuotesIfString(object value) { - if (value is DateTime) value = $"\"{value}\""; + if (value is DateTime || value is TimeSpan || value is DateTimeOffset) return $"\"{value}\""; Func doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; @@ -256,11 +256,13 @@ private static object NormalizeValue(this object value) return value; } - private static bool IsEmpty(this object value, Specification specification,bool skipDefault) + private static bool IsEmpty(this object value, Specification specification, bool skipDefault) { if (value == null) return true; if (skipDefault && value.Equals(specification.DefaultValue.FromJust())) return true; + if (Nullable.GetUnderlyingType(specification.ConversionType) != null) return false; //nullable + #if !SKIP_FSHARP if (ReflectionHelper.IsFSharpOptionType(value.GetType()) && !FSharpOptionHelper.IsSome(value)) return true; #endif diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs index 792f7933..eca68790 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs @@ -11,5 +11,16 @@ class Options_With_Defaults [Option(Default = Shapes.Square)] public Shapes P4 { get; set; } } + class Nuulable_Options_With_Defaults + { + [Option(Default = 99)] + public int? P1 { get; set; } + [Option()] + public string P2 { get; set; } + [Option(Default = 88)] + public int? P3 { get; set; } + [Option(Default = Shapes.Square)] + public Shapes? P4 { get; set; } + } } diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs b/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs index 4e1560b1..e3ede175 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs @@ -2,7 +2,7 @@ namespace CommandLine.Tests.Fakes { - enum Shapes + public enum Shapes { Circle, Square, diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 03247105..90cb8929 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -105,6 +105,8 @@ public static void UnParsing_instance_with_dash_in_value_and_dashdash_disabled_r .Should().BeEquivalentTo("-something with dash"); } + #region PR 550 + [Fact] public static void UnParsing_instance_with_default_values_when_skip_default_is_false() { @@ -114,31 +116,159 @@ public static void UnParsing_instance_with_default_values_when_skip_default_is_f .Should().BeEquivalentTo("--p1 99 --p2 xyz --p3 88 --p4 Square"); } - [Fact] - public static void UnParsing_instance_with_default_values_when_skip_default_is_true() + [Theory] + [InlineData(true, "--p2 xyz")] + [InlineData(false, "--p1 99 --p2 xyz --p3 88 --p4 Square")] + public static void UnParsing_instance_with_default_values_when_skip_default_is_true(bool skipDefault, string expected) { - var options = new Options_With_Defaults {P2 = "xyz", P1 = 99, P3 = 88,P4= Shapes.Square } ; + var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square }; new Parser() - .FormatCommandLine(options,x=>x.SkipDefault=true) - .Should().BeEquivalentTo("--p2 xyz"); + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); } + [Theory] + [InlineData(true, "--p2 xyz")] + [InlineData(false, "--p1 99 --p2 xyz --p3 88 --p4 Square")] + public static void UnParsing_instance_with_nullable_default_values_when_skip_default_is_true(bool skipDefault, string expected) + { + var options = new Nuulable_Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square }; + new Parser() + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); + } [Fact] public static void UnParsing_instance_with_datetime() { - var date = new DateTime(2019, 5, 1); - var options = new Options_Date { Start=date }; + var date = new DateTime(2019, 5, 1); + var options = new Options_Date { Start = date }; + var result = new Parser() + .FormatCommandLine(options) + .Should().MatchRegex("--start\\s\".+\""); + } + + [Fact] + public static void UnParsing_instance_with_datetime_nullable() + { + var date = new DateTime(2019, 5, 1); + var options = new Options_Date_Nullable { Start = date }; + var result = new Parser() + .FormatCommandLine(options) + .Should().MatchRegex("--start\\s\".+\""); + } + + [Fact] + public static void UnParsing_instance_with_datetime_offset() + { + DateTimeOffset date = new DateTime(2019, 5, 1); + var options = new Options_DateTimeOffset { Start = date }; var result = new Parser() - .FormatCommandLine(options); //--start "1/5/2019 12:00:00 AM", date is based on Culture - var expected = Regex.Match(result, @"--start\s"".+""").Success; //result contain quote - Assert.True(expected); + .FormatCommandLine(options) + .Should().MatchRegex("--start\\s\".+\""); } - internal class Options_Date + [Fact] + public static void UnParsing_instance_with_timespan() + { + var ts = new TimeSpan(1,2,3); + var options = new Options_TimeSpan { Start = ts }; + var result = new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo("--start \"01:02:03\""); + } + + [Theory] + [InlineData(false, 0, "")] //default behaviour based on type + [InlineData(false, 1, "-v 1")] //default skip=false + [InlineData(false, 2, "-v 2")] + [InlineData(true, 1, "")] //default skip=true + public static void UnParsing_instance_with_int(bool skipDefault, int value, string expected) + { + var options = new Option_Int { VerboseLevel = value }; + var result = new Parser() + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); + + } + + [Theory] + [InlineData(false, 0, "-v 0")] + [InlineData(false, 1, "-v 1")] //default + [InlineData(false, 2, "-v 2")] + [InlineData(false, null, "")] + [InlineData(true, 1, "")] //default + public static void UnParsing_instance_with_int_nullable(bool skipDefault, int? value, string expected) + { + var options = new Option_Int_Nullable { VerboseLevel = value }; + var result = new Parser() + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); + + } + [Theory] + [InlineData(Shapes.Circle, "--shape circle")] + [InlineData(Shapes.Square, "--shape square")] + [InlineData(null, "")] + public static void UnParsing_instance_with_nullable_enum(Shapes? shape, string expected) + { + var options = new Option_Nullable_Enum { Shape = shape }; + var result = new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo(expected); + } + + [Theory] + [InlineData(true, "-v True")] + [InlineData(false, "-v False")] + [InlineData(null, "")] + public static void UnParsing_instance_with_nullable_bool(bool? flag, string expected) + { + var options = new Option_Nullable_Bool { Verbose = flag }; + var result = new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo(expected); + } + class Option_Int_Nullable + { + [Option('v', Default = 1)] + public int? VerboseLevel { get; set; } + } + class Option_Int + { + [Option('v', Default = 1)] + public int VerboseLevel { get; set; } + } + class Option_Nullable_Bool + { + [Option('v')] + public bool? Verbose { get; set; } + } + class Option_Nullable_Enum + { + [Option] + public Shapes? Shape { get; set; } + } + class Options_Date + { + [Option] + public DateTime Start { get; set; } + } + class Options_Date_Nullable { [Option] public DateTime? Start { get; set; } } + class Options_TimeSpan + { + [Option] + public TimeSpan Start { get; set; } + } + class Options_DateTimeOffset + { + [Option] + public DateTimeOffset Start { get; set; } + } + #endregion public static IEnumerable UnParseData { get @@ -172,15 +302,15 @@ public static IEnumerable UnParseDataImmutable get { yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), default(bool), default(long)), "" }; - yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty(), true, default(long) ), "-x" }; - yield return new object[] { new Immutable_Simple_Options ("", new[] { 1, 2, 3 }, default(bool), default(long) ), "-i 1 2 3" }; - yield return new object[] { new Immutable_Simple_Options ("nospaces", Enumerable.Empty(), default(bool), default(long)), "--stringvalue nospaces" }; - yield return new object[] { new Immutable_Simple_Options (" with spaces ", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \" with spaces \"" }; - yield return new object[] { new Immutable_Simple_Options ("with\"quote", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" }; - yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" }; - yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty(), default(bool), 123456789), "123456789" }; - yield return new object[] { new Immutable_Simple_Options ("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" }; - yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue \"with \\\"quotes\\\" spaced\" -x 123456789" }; + yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), true, default(long)), "-x" }; + yield return new object[] { new Immutable_Simple_Options("", new[] { 1, 2, 3 }, default(bool), default(long)), "-i 1 2 3" }; + yield return new object[] { new Immutable_Simple_Options("nospaces", Enumerable.Empty(), default(bool), default(long)), "--stringvalue nospaces" }; + yield return new object[] { new Immutable_Simple_Options(" with spaces ", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \" with spaces \"" }; + yield return new object[] { new Immutable_Simple_Options("with\"quote", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" }; + yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" }; + yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), default(bool), 123456789), "123456789" }; + yield return new object[] { new Immutable_Simple_Options("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" }; + yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue \"with \\\"quotes\\\" spaced\" -x 123456789" }; } } @@ -189,7 +319,7 @@ public static IEnumerable UnParseDataHidden get { yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, true, "--hiddenOption hidden" }; - yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, ""}; + yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, "" }; } } #if !SKIP_FSHARP From dc9cc826ce074e0115ee0992620ba38fb212ee20 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 14:39:01 +0200 Subject: [PATCH 067/198] Option group help text tests without errors section --- ...mple_Options_With_Multiple_OptionGroups.cs | 19 +++++ .../Fakes/Simple_Options_With_OptionGroup.cs | 14 ++++ ...imple_Options_With_Required_OptionGroup.cs | 14 ++++ .../Unit/Text/HelpTextTests.cs | 78 +++++++++++++++++-- 4 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs new file mode 100644 index 00000000..d4496666 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_Multiple_OptionGroups + { + [Option(HelpText = "Define a string value here.", Group = "string-group")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.", Group = "second-group")] + public bool BoolValue { get; set; } + + [Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.", Group = "second-group")] + public IEnumerable IntSequence { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs new file mode 100644 index 00000000..f1b2bfdd --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup + { + [Option(HelpText = "Define a string value here.", Group = "string-group")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs new file mode 100644 index 00000000..3cf6f478 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_Required_OptionGroup + { + [Option(HelpText = "Define a string value here.", Required = true, Group = "string-group")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 1dd8d45f..c6eb9312 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -3,20 +3,25 @@ using System; using System.Collections.Generic; using System.Globalization; -using CommandLine.Core; using System.Linq; using System.Reflection; +using System.Text; + +using CommandLine.Core; using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; using CommandLine.Text; + using FluentAssertions; + using Xunit; -using System.Text; namespace CommandLine.Tests.Unit.Text { public class HelpTextTests : IDisposable { + private readonly HeadingInfo headingInfo = new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131"); + public void Dispose() { ReflectionHelper.SetAttributeOverride(null); @@ -143,7 +148,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")); + var sut = new HelpText(headingInfo); sut.MaximumDisplayWidth = 40; sut.AddOptions( new NotParsed( @@ -166,7 +171,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")) { MaximumDisplayWidth = 100} ; + var sut = new HelpText(headingInfo) { MaximumDisplayWidth = 100} ; sut.AddOptions( new NotParsed( TypeInfo.Create(typeof(Simple_Options_With_HelpText_Set_To_Long_Description)), @@ -185,7 +190,7 @@ public void When_help_text_has_hidden_option_it_should_not_be_added_to_help_text { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")); + var sut = new HelpText(headingInfo); sut.MaximumDisplayWidth = 80; sut.AddOptions( new NotParsed( @@ -205,7 +210,7 @@ public void Long_help_text_without_spaces() { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")); + var sut = new HelpText(headingInfo); sut.MaximumDisplayWidth = 40; sut.AddOptions( new NotParsed( @@ -739,5 +744,66 @@ public void Options_should_be_separated_by_spaces() // Teardown } + + [Fact] + public void Options_Should_Render_OptionGroup_In_Parenthesis_When_Available() + { + var sut = new HelpText(headingInfo) { AddDashesToOption = true, MaximumDisplayWidth = 100 } + .AddOptions( + new NotParsed(TypeInfo.Create(typeof(Simple_Options_With_OptionGroup)), Enumerable.Empty())); + + var text = sut.ToString(); + var lines = text.ToLines().TrimStringArray(); + + + lines[0].Should().BeEquivalentTo(headingInfo.ToString()); + lines[1].Should().BeEmpty(); + lines[2].Should().BeEquivalentTo("--stringvalue (Group: string-group) Define a string value here."); + lines[3].Should().BeEquivalentTo("-s, --shortandlong (Group: string-group) Example with both short and long name."); + lines[4].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[5].Should().BeEquivalentTo("--help Display this help screen."); + lines[6].Should().BeEquivalentTo("--version Display version information."); + } + + [Fact] + public void Options_Should_Render_OptionGroup_When_Available_And_Should_Not_Render_Required() + { + var sut = new HelpText(headingInfo) { AddDashesToOption = true, MaximumDisplayWidth = 100 } + .AddOptions( + new NotParsed(TypeInfo.Create(typeof(Simple_Options_With_Required_OptionGroup)), Enumerable.Empty())); + + var text = sut.ToString(); + var lines = text.ToLines().TrimStringArray(); + + + lines[0].Should().BeEquivalentTo(headingInfo.ToString()); + lines[1].Should().BeEmpty(); + lines[2].Should().BeEquivalentTo("--stringvalue (Group: string-group) Define a string value here."); + lines[3].Should().BeEquivalentTo("-s, --shortandlong (Group: string-group) Example with both short and long name."); + lines[4].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[5].Should().BeEquivalentTo("--help Display this help screen."); + lines[6].Should().BeEquivalentTo("--version Display version information."); + } + + [Fact] + public void Options_Should_Render_Multiple_OptionGroups_When_Available() + { + var sut = new HelpText(headingInfo) { AddDashesToOption = true, MaximumDisplayWidth = 100 } + .AddOptions( + new NotParsed(TypeInfo.Create(typeof(Simple_Options_With_Multiple_OptionGroups)), Enumerable.Empty())); + + var text = sut.ToString(); + var lines = text.ToLines().TrimStringArray(); + + + lines[0].Should().BeEquivalentTo(headingInfo.ToString()); + lines[1].Should().BeEmpty(); + lines[2].Should().BeEquivalentTo("--stringvalue (Group: string-group) Define a string value here."); + lines[3].Should().BeEquivalentTo("-s, --shortandlong (Group: string-group) Example with both short and long name."); + lines[4].Should().BeEquivalentTo("-x (Group: second-group) Define a boolean or switch value here."); + lines[5].Should().BeEquivalentTo("-i (Group: second-group) Define a int sequence here."); + lines[6].Should().BeEquivalentTo("--help Display this help screen."); + lines[7].Should().BeEquivalentTo("--version Display version information."); + } } } From a1952beb22f5f880a0470af276962a15e284e3a9 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 15:20:36 +0200 Subject: [PATCH 068/198] Error render test update --- tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index c6eb9312..df447ac9 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -255,6 +255,11 @@ public void Long_pre_and_post_lines_without_spaces() public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text() { // Fixture setup + var optionsInGroup = new List + { + new NameInfo("t", "testOption1"), + new NameInfo("c", "testOption2") + }; var fakeResult = new NotParsed( TypeInfo.Create(typeof(NullInstance)), new Error[] @@ -267,7 +272,8 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( new NoVerbSelectedError(), new BadVerbSelectedError("badverb"), new HelpRequestedError(), // should be ignored - new HelpVerbRequestedError(null, null, false) // should be ignored + new HelpVerbRequestedError(null, null, false), // should be ignored + new MissingGroupOptionError("bad-option-group", optionsInGroup), }); Func fakeRenderer = err => { @@ -287,6 +293,11 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( return "ERR no-verb-selected"; case ErrorType.BadVerbSelectedError: return "ERR " + ((BadVerbSelectedError)err).Token; + case ErrorType.MissingGroupOptionError: + { + var groupErr = (MissingGroupOptionError)err; + return "ERR " + groupErr.Group + ": " + string.Join("---", groupErr.Names.Select(n => n.NameText)); + } default: throw new InvalidOperationException(); } @@ -306,6 +317,7 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( lines[4].Should().BeEquivalentTo(" ERR s, sequence"); lines[5].Should().BeEquivalentTo(" ERR no-verb-selected"); lines[6].Should().BeEquivalentTo(" ERR badverb"); + lines[7].Should().BeEquivalentTo(" ERR bad-option-group: t, testOption1---c, testOption2"); // Teardown } From 8d91fb04734a3830ec6253b314d99d796eff9d6a Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 15:33:28 +0200 Subject: [PATCH 069/198] instance builder tests with option group error --- .../Fakes/Options_With_Group.cs | 14 ++++++ .../Unit/Core/InstanceBuilderTests.cs | 43 +++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Group.cs diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Group.cs b/tests/CommandLine.Tests/Fakes/Options_With_Group.cs new file mode 100644 index 00000000..849171bd --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Group.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Group + { + [Option('v', "version")] + public string Version { get; set; } + + [Option("option1", Group = "err-group")] + public string Option1 { get; set; } + + [Option("option2", Group = "err-group")] + public string Option2 { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index c6234089..59cdf882 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -4,15 +4,16 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Microsoft.FSharp.Core; + using CommandLine.Core; using CommandLine.Infrastructure; +using CommandLine.Tests.Fakes; using CSharpx; -using CommandLine.Tests.Fakes; + using FluentAssertions; + using Xunit; -using System.Reflection; namespace CommandLine.Tests.Unit.Core { @@ -1158,6 +1159,42 @@ public void OptionClass_IsImmutable_HasNoCtor() act.Should().Throw(); } + [Fact] + public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError() + { + // Fixture setup + var optionNames = new List + { + new NameInfo("", "option1"), + new NameInfo("", "option2") + }; + var expectedResult = new[] { new MissingGroupOptionError("err-group", optionNames) }; + + // Exercize system + var result = InvokeBuild( + new[] { "-v 10.42" }); + + // Verify outcome + ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); + + // Teardown + } + + [Theory] + [InlineData("-v", "10.5", "--option1", "test1", "--option2", "test2")] + [InlineData("-v", "10.5", "--option1", "test1")] + [InlineData("-v", "10.5", "--option2", "test2")] + public void Options_In_Group_With_Values_Does_Not_Generate_MissingGroupOptionError(params string[] args) + { + // Exercize system + var result = InvokeBuild(args); + + // Verify outcome + result.Should().BeOfType>(); + + // Teardown + } + private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From d002cf487ba3b518553e43fefcc3fc202090a4c8 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 16:08:23 +0200 Subject: [PATCH 070/198] Fix instance builder tests - missing fsharp using --- tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 59cdf882..a776a7ce 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -4,16 +4,15 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; - +using Microsoft.FSharp.Core; using CommandLine.Core; using CommandLine.Infrastructure; -using CommandLine.Tests.Fakes; using CSharpx; - +using CommandLine.Tests.Fakes; using FluentAssertions; - using Xunit; +using System.Reflection; namespace CommandLine.Tests.Unit.Core { From dd51d955f01fce0be974e2d96bdaa52cb34520ac Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Fri, 13 Dec 2019 14:25:28 +0200 Subject: [PATCH 071/198] Delete unused GroupAttribute --- src/CommandLine/GroupAttribute.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 src/CommandLine/GroupAttribute.cs diff --git a/src/CommandLine/GroupAttribute.cs b/src/CommandLine/GroupAttribute.cs deleted file mode 100644 index 82832d14..00000000 --- a/src/CommandLine/GroupAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace CommandLine -{ - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class GroupAttribute : Attribute - { - public GroupAttribute(string name) - { - Name = name; - } - - public string Name { get; } - } -} From 108ecfb222ebfa63012335c5122f3f7c28841568 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 15 Dec 2019 22:13:17 +0200 Subject: [PATCH 072/198] Option group - empty name tests + ignore required tests --- ...tions_With_OptionGroup_WithDefaultValue.cs | 14 +++++ ...imple_Options_With_Required_OptionGroup.cs | 2 +- .../Unit/Core/InstanceBuilderTests.cs | 56 +++++++++++++++++-- 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs new file mode 100644 index 00000000..ccfc643d --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup_WithDefaultValue + { + [Option(HelpText = "Define a string value here.", Required = true, Group = "")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs index 3cf6f478..bbeaaf53 100644 --- a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs @@ -5,7 +5,7 @@ public class Simple_Options_With_Required_OptionGroup [Option(HelpText = "Define a string value here.", Required = true, Group = "string-group")] public string StringValue { get; set; } - [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "string-group")] public string ShortAndLong { get; set; } [Option('x', HelpText = "Define a boolean or switch value here.")] diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index a776a7ce..0941d7b5 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1,16 +1,19 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using Microsoft.FSharp.Core; using CommandLine.Core; using CommandLine.Infrastructure; +using CommandLine.Tests.Fakes; using CSharpx; -using CommandLine.Tests.Fakes; + using FluentAssertions; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + using Xunit; using System.Reflection; @@ -1194,6 +1197,47 @@ public void Options_In_Group_With_Values_Does_Not_Generate_MissingGroupOptionErr // Teardown } + [Fact] + public void Options_In_Group_WithRequired_Does_Not_Generate_RequiredError() + { + // Fixture setup + var optionNames = new List + { + new NameInfo("", "stingvalue"), + new NameInfo("s", "shortandlong") + }; + var expectedResult = new[] { new MissingGroupOptionError("string-group", optionNames) }; + + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + var errors = ((NotParsed)result).Errors; + + errors.Should().HaveCount(1); + errors.Should().BeEquivalentTo(expectedResult); + } + + [Fact] + public void Options_In_Group_Ignore_Option_Group_If_Option_Group_Name_Empty() + { + var expectedResult = new[] + { + new MissingRequiredOptionError(new NameInfo("", "stringvalue")), + new MissingRequiredOptionError(new NameInfo("s", "shortandlong")) + }; + + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + var errors = ((NotParsed)result).Errors; + + errors.Should().BeEquivalentTo(expectedResult); + } + private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From 4274c25b981e84d66fd1ed54c627e0b69d4d3a4f Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 15 Dec 2019 22:14:10 +0200 Subject: [PATCH 073/198] Delete useless 'teardown' comment + fix formatting --- .../Unit/Core/InstanceBuilderTests.cs | 163 ++++-------------- 1 file changed, 32 insertions(+), 131 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 0941d7b5..d4a4fd12 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -78,12 +78,10 @@ public void Explicit_help_request_generates_help_requested_error() // Verify outcome result.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] - [InlineData(new[] {"-123"}, -123L)] + [InlineData(new[] { "-123" }, -123L)] [InlineData(new[] { "-1" }, -1L)] [InlineData(new[] { "-9223372036854775807" }, -9223372036854775807)] // long.MaxValue * -1 public void Parse_negative_long_value(string[] arguments, long expected) @@ -96,8 +94,6 @@ public void Parse_negative_long_value(string[] arguments, long expected) // Verify outcome ((Parsed)result).Value.LongValue.Should().Be(expected); - - // Teardown } [Theory] @@ -116,8 +112,6 @@ public void Parse_double_value(string[] arguments, double expected) // Verify outcome ((Parsed)result).Value.DoubleValue.Should().Be(expected); - - // Teardown } [Theory] @@ -137,8 +131,6 @@ public void Parse_int_sequence(string[] arguments, int[] expected) // Verify outcome ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Theory] @@ -156,14 +148,12 @@ public void Parse_int_sequence_with_range(string[] arguments, int[] expected) // Verify outcome ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Theory] - [InlineData(new[] {"-s", "just-one"}, new[] {"just-one"})] - [InlineData(new[] {"-sjust-one-samearg"}, new[] {"just-one-samearg"})] - [InlineData(new[] {"-s", "also-two", "are-ok" }, new[] { "also-two", "are-ok" })] + [InlineData(new[] { "-s", "just-one" }, new[] { "just-one" })] + [InlineData(new[] { "-sjust-one-samearg" }, new[] { "just-one-samearg" })] + [InlineData(new[] { "-s", "also-two", "are-ok" }, new[] { "also-two", "are-ok" })] [InlineData(new[] { "--string-seq", "one", "two", "three" }, new[] { "one", "two", "three" })] [InlineData(new[] { "--string-seq=one", "two", "three", "4" }, new[] { "one", "two", "three", "4" })] public void Parse_string_sequence_with_only_min_constraint(string[] arguments, string[] expected) @@ -176,8 +166,6 @@ public void Parse_string_sequence_with_only_min_constraint(string[] arguments, s // Verify outcome ((Parsed)result).Value.StringSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Theory] @@ -195,8 +183,6 @@ public void Parse_string_sequence_with_only_max_constraint(string[] arguments, s // Verify outcome ((Parsed)result).Value.StringSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Fact] @@ -211,8 +197,6 @@ public void Breaking_min_constraint_in_string_sequence_gererates_MissingValueOpt // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -227,8 +211,6 @@ public void Breaking_min_constraint_in_string_sequence_as_value_gererates_Sequen // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -243,8 +225,6 @@ public void Breaking_max_constraint_in_string_sequence_gererates_SequenceOutOfRa // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -259,8 +239,6 @@ public void Breaking_max_constraint_in_string_sequence_as_value_gererates_Sequen // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -280,8 +258,6 @@ public void Parse_enum_value(string[] arguments, Colors expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.Colors); - - // Teardown } [Theory] @@ -301,8 +277,6 @@ public void Parse_enum_value_ignore_case(string[] arguments, Colors expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.Colors); - - // Teardown } [Fact] @@ -317,8 +291,6 @@ public void Parse_enum_value_with_wrong_index_generates_BadFormatConversionError // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -333,8 +305,6 @@ public void Parse_enum_value_with_wrong_item_name_generates_BadFormatConversionE // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -349,8 +319,6 @@ public void Parse_enum_value_with_wrong_item_name_case_generates_BadFormatConver // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -358,12 +326,12 @@ public void Parse_values_partitioned_between_sequence_and_scalar() { // Fixture setup var expectedResult = new Simple_Options_With_Values - { - StringValue = string.Empty, - LongValue = 10L, - StringSequence = new[] { "a", "b", "c" }, - IntValue = 20 - }; + { + StringValue = string.Empty, + LongValue = 10L, + StringSequence = new[] { "a", "b", "c" }, + IntValue = 20 + }; // Exercize system var result = InvokeBuild( @@ -371,8 +339,6 @@ public void Parse_values_partitioned_between_sequence_and_scalar() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Theory] @@ -391,8 +357,6 @@ public void Parse_sequence_value_without_range_constraints(string[] arguments, l // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.LongSequence); - - // Teardown } [Theory] @@ -410,8 +374,6 @@ public void Parse_long_sequence_with_separator(string[] arguments, long[] expect // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.LongSequence); - - // Teardown } [Theory] @@ -429,8 +391,6 @@ public void Parse_string_sequence_with_separator(string[] arguments, string[] ex // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.StringSequence); - - // Teardown } /// @@ -441,12 +401,12 @@ public void Double_dash_force_subsequent_arguments_as_values() { // Fixture setup var expectedResult = new Simple_Options_With_Values - { - StringValue = "str1", - LongValue = 10L, - StringSequence = new[] { "-a", "--bee", "-c" }, - IntValue = 20 - }; + { + StringValue = "str1", + LongValue = 10L, + StringSequence = new[] { "-a", "--bee", "-c" }, + IntValue = 20 + }; var arguments = new[] { "--stringvalue", "str1", "--", "10", "-a", "--bee", "-c", "20" }; // Exercize system @@ -465,8 +425,6 @@ public void Double_dash_force_subsequent_arguments_as_values() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -485,14 +443,14 @@ public void Parse_option_from_different_sets_gererates_MutuallyExclusiveSetError // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] - public void Two_required_options_at_the_same_set_and_both_are_true() { + public void Two_required_options_at_the_same_set_and_both_are_true() + { // Fixture setup - var expectedResult = new Options_With_Required_Set_To_True_Within_Same_Set { + var expectedResult = new Options_With_Required_Set_To_True_Within_Same_Set + { FtpUrl = "str1", WebUrl = "str2" }; @@ -506,7 +464,8 @@ public void Two_required_options_at_the_same_set_and_both_are_true() { } [Fact] - public void Two_required_options_at_the_same_set_and_none_are_true() { + public void Two_required_options_at_the_same_set_and_none_are_true() + { // Fixture setup var expectedResult = new[] { @@ -519,8 +478,6 @@ public void Two_required_options_at_the_same_set_and_none_are_true() { // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -535,8 +492,6 @@ public void Omitting_required_option_gererates_MissingRequiredOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -551,8 +506,6 @@ public void Wrong_range_in_sequence_gererates_SequenceOutOfRangeError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -567,8 +520,6 @@ public void Parse_unknown_long_option_gererates_UnknownOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -583,8 +534,6 @@ public void Parse_unknown_short_option_gererates_UnknownOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -599,13 +548,11 @@ public void Parse_unknown_short_option_in_option_group_gererates_UnknownOptionEr // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] - [InlineData(new[] {"--stringvalue", "this-value"}, "this-value")] - [InlineData(new[] {"--stringvalue=this-other"}, "this-other")] + [InlineData(new[] { "--stringvalue", "this-value" }, "this-value")] + [InlineData(new[] { "--stringvalue=this-other" }, "this-other")] public void Omitting_names_assumes_identifier_as_long_name(string[] arguments, string expected) { // Fixture setup in attributes @@ -616,8 +563,6 @@ public void Omitting_names_assumes_identifier_as_long_name(string[] arguments, s // Verify outcome ((Parsed)result).Value.StringValue.Should().BeEquivalentTo(expected); - - // Teardown } [Fact] @@ -632,8 +577,6 @@ public void Breaking_required_constraint_in_string_scalar_as_value_generates_Mis // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -651,8 +594,6 @@ public void Parse_utf8_string_correctly(string[] arguments, string expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.StringValue); - - // Teardown } [Fact] @@ -667,8 +608,6 @@ public void Breaking_equal_min_max_constraint_in_string_sequence_as_value_gerera // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -686,8 +625,6 @@ public void Parse_nullable_int(string[] arguments, int? expected) // Verify outcome expected.Should().Be(((Parsed)result).Value.NullableInt); - - // Teardown } [Theory] @@ -705,8 +642,6 @@ public void Parse_nullable_long(string[] arguments, long? expected) // Verify outcome expected.Should().Be(((Parsed)result).Value.NullableLong); - - // Teardown } #if !SKIP_FSHARP @@ -727,8 +662,6 @@ public void Parse_fsharp_option_string(string[] arguments, string expectedValue, expectedValue.Should().BeEquivalentTo(((Parsed)result).Value.FileName.Value); } expectedSome.Should().Be(FSharpOption.get_IsSome(((Parsed)result).Value.FileName)); - - // Teardown } [Theory] @@ -748,8 +681,6 @@ public void Parse_fsharp_option_int(string[] arguments, int expectedValue, bool expectedValue.Should().Be(((Parsed)result).Value.Offset.Value); } expectedSome.Should().Be(FSharpOption.get_IsSome(((Parsed)result).Value.Offset)); - - // Teardown } #endif @@ -788,7 +719,7 @@ public void Min_and_max_constraint_set_to_zero_throws_exception() } [Theory] - [InlineData(new[] {"--weburl", "value.com", "--verbose"}, ParserResultType.Parsed, 0)] + [InlineData(new[] { "--weburl", "value.com", "--verbose" }, ParserResultType.Parsed, 0)] [InlineData(new[] { "--ftpurl", "value.org", "--interactive" }, ParserResultType.Parsed, 0)] [InlineData(new[] { "--weburl", "value.com", "--verbose", "--interactive" }, ParserResultType.Parsed, 0)] [InlineData(new[] { "--ftpurl=fvalue", "--weburl=wvalue" }, ParserResultType.NotParsed, 2)] @@ -908,8 +839,6 @@ public void Parse_string_scalar_with_required_constraint_as_value(string[] argum // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Theory] @@ -924,15 +853,13 @@ public void Parse_string_scalar_and_sequence_adjacent(string[] arguments, Option // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] public void Parse_to_mutable() { // Fixture setup - var expectedResult = new Simple_Options { StringValue="strval0", IntSequence=new[] { 9, 7, 8 }, BoolValue = true, LongValue = 9876543210L }; + var expectedResult = new Simple_Options { StringValue = "strval0", IntSequence = new[] { 9, 7, 8 }, BoolValue = true, LongValue = 9876543210L }; // Exercize system var result = InvokeBuild( @@ -940,17 +867,15 @@ public void Parse_to_mutable() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Theory] [InlineData(new string[] { }, 2)] - [InlineData(new [] { "--str=val0" }, 1)] - [InlineData(new [] { "--long=9" }, 1)] - [InlineData(new [] { "--int=7" }, 2)] - [InlineData(new [] { "--str", "val1", "--int=3" }, 1)] - [InlineData(new [] { "--long", "9", "--int=11" }, 1)] + [InlineData(new[] { "--str=val0" }, 1)] + [InlineData(new[] { "--long=9" }, 1)] + [InlineData(new[] { "--int=7" }, 2)] + [InlineData(new[] { "--str", "val1", "--int=3" }, 1)] + [InlineData(new[] { "--long", "9", "--int=11" }, 1)] public void Breaking_required_constraint_generate_MissingRequiredOptionError(string[] arguments, int expected) { // Exercize system @@ -974,8 +899,6 @@ public void Parse_to_immutable_instance(string[] arguments, Immutable_Simple_Opt // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -990,8 +913,6 @@ public void Parse_to_type_with_single_string_ctor_builds_up_correct_instance() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -1006,8 +927,6 @@ public void Parse_option_with_exception_thrown_from_setter_generates_SetValueExc // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -1046,8 +965,6 @@ public void Parse_string_with_dashes_except_in_beginning(string[] arguments, str // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.StringValue); - - // Teardown } [Theory] @@ -1063,8 +980,6 @@ public void Parse_without_auto_help_should_not_recognize_help_option(string[] ar result.Should().BeOfType>() .Which.Errors.Should().ContainSingle() .Which.Tag.Should().Be(errorType); - - // Teardown } [Theory] @@ -1081,8 +996,6 @@ public void Parse_with_custom_help_option(string[] arguments, bool isHelp) // Verify outcome result.Should().BeOfType>() .Which.Value.Help.Should().Be(isHelp); - - // Teardown } [Theory] @@ -1098,8 +1011,6 @@ public void Parse_without_auto_version_should_not_recognize_version_option(strin result.Should().BeOfType>() .Which.Errors.Should().ContainSingle() .Which.Tag.Should().Be(errorType); - - // Teardown } [Theory] @@ -1116,8 +1027,6 @@ public void Parse_with_custom_version_option(string[] arguments, bool isVersion) // Verify outcome result.Should().BeOfType>() .Which.Value.MyVersion.Should().Be(isVersion); - - // Teardown } [Theory] @@ -1132,8 +1041,6 @@ public void Parse_Guid(string[] arguments, Options_With_Guid expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -1148,8 +1055,6 @@ public void Parse_TimeSpan() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } @@ -1178,8 +1083,6 @@ public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -1193,8 +1096,6 @@ public void Options_In_Group_With_Values_Does_Not_Generate_MissingGroupOptionErr // Verify outcome result.Should().BeOfType>(); - - // Teardown } [Fact] @@ -1261,7 +1162,7 @@ public static IEnumerable ScalarSequenceStringAdjacentData { get { - yield return new object[] { new[] { "to-value" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new string[] {} } }; + yield return new object[] { new[] { "to-value" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new string[] { } } }; yield return new object[] { new[] { "to-value", "-s", "to-seq-0" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new[] { "to-seq-0" } } }; yield return new object[] { new[] { "to-value", "-s", "to-seq-0", "to-seq-1" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new[] { "to-seq-0", "to-seq-1" } } }; yield return new object[] { new[] { "-s", "cant-capture", "value-anymore" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringOptionSequence = new[] { "cant-capture", "value-anymore" } } }; From ff61b69a19a870b3ec1e905eee4c2e319ae00d0a Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 17 Dec 2019 21:29:43 +0200 Subject: [PATCH 074/198] Modify type of Group to string instead of `Mayb' --- src/CommandLine/Core/OptionSpecification.cs | 13 +++++++------ src/CommandLine/Core/SpecificationPropertyRules.cs | 8 ++++---- src/CommandLine/OptionAttribute.cs | 2 +- src/CommandLine/Text/HelpText.cs | 9 +++++++-- .../CommandLine.Tests/Unit/Core/NameLookupTests.cs | 4 ++-- .../Unit/Core/OptionMapperTests.cs | 2 +- .../Unit/Core/TokenPartitionerTests.cs | 8 ++++---- tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs | 4 ++-- 8 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 45dc2f58..77e7977f 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -13,12 +13,13 @@ sealed class OptionSpecification : Specification private readonly string longName; private readonly char separator; private readonly string setName; - private readonly Maybe group; + private readonly string group; public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, Maybe group, bool hidden = false) - : base(SpecificationType.Option, required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) + Type conversionType, TargetType targetType, string group, bool hidden = false) + : base(SpecificationType.Option, + required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) { this.shortName = shortName; this.longName = longName; @@ -43,14 +44,14 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type enumValues, conversionType, conversionType.ToTargetType(), - string.IsNullOrWhiteSpace(attribute.Group) ? Maybe.Nothing() : Maybe.Just(attribute.Group), + attribute.Group, attribute.Hidden); } public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) { return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, Maybe.Nothing(), hidden); + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, hidden); } public string ShortName @@ -73,7 +74,7 @@ public string SetName get { return setName; } } - public Maybe Group + public string Group { get { return group; } } diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 31a20027..9122ee3a 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -32,7 +32,7 @@ private static Func, IEnumerable> Enfo from sp in specProps where sp.Specification.IsOption() let o = (OptionSpecification)sp.Specification - where o.Group.IsJust() + where o.Group.Length > 0 select new { Option = o, @@ -40,7 +40,7 @@ where o.Group.IsJust() }; var groups = from o in optionsValues - group o by o.Option.Group.GetValueOrDefault(null) into g + group o by o.Option.Group into g select g; var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing())); @@ -101,7 +101,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length > 0 - where o.Group.IsNothing() + where o.Group.Length == 0 where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) select sp.Specification; var missing = @@ -114,7 +114,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length == 0 - where o.Group.IsNothing() + where o.Group.Length == 0 select sp.Specification) .Concat( from sp in specProps diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 4a38a4da..7448b697 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -16,7 +16,7 @@ public sealed class OptionAttribute : BaseAttribute private readonly string shortName; private string setName; private char separator; - private string group; + private string group=string.Empty; private OptionAttribute(string shortName, string longName) : base() { diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 949e6876..319a8740 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -875,7 +875,12 @@ private HelpText AddOption(string requiredWord, string optionGroupWord, int maxL { OptionSpecification GetOptionGroupSpecification() { - if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + if (specification.Tag == SpecificationType.Option && + specification is OptionSpecification optionSpecification && + optionSpecification.Group.Length > 0 + ) + + { return optionSpecification; } @@ -912,7 +917,7 @@ OptionSpecification GetOptionGroupSpecification() if (optionGroupSpecification != null) { - optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group.GetValueOrDefault(null)) + optionHelpText; + optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group) + optionHelpText; } //note that we need to indent trim the start of the string because it's going to be diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index caa0a386..785b1fe5 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -21,7 +21,7 @@ public void Lookup_name_of_sequence_option_with_separator() // Fixture setup var expected = Maybe.Just("."); var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = NameLookup.HavingSeparator("string-seq", specs, StringComparer.Ordinal); @@ -39,7 +39,7 @@ public void Get_name_from_option_specification() // Fixture setup var expected = new NameInfo(ShortName, LongName); - var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()); + var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty); // Exercize system var result = spec.FromOptionSpecification(); diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 41b93bf5..75ddade7 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -32,7 +32,7 @@ public void Map_boolean_switch_creates_boolean_value() var specProps = new[] { SpecificationProperty.Create( - new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, Maybe.Nothing()), + new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, string.Empty), typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals("BoolValue", StringComparison.Ordinal)), Maybe.Nothing()) }; diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index 4266b2d4..11a9ffdd 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -24,8 +24,8 @@ public void Partition_sequence_returns_sequence() }; var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) }; // Exercize system @@ -51,8 +51,8 @@ public void Partition_sequence_returns_sequence_with_duplicates() }; var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) }; // Exercize system diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index ac037cde..3bd95891 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -26,7 +26,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = @@ -49,7 +49,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = From 146fe94500f50efb8de3d4d6d804a4bb3ce68b4c Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 24 Dec 2019 21:16:00 +0200 Subject: [PATCH 075/198] Support SourceLink #548 --- Directory.Build.props | 15 +++-- src/CommandLine/CommandLine.csproj | 92 +++++++++++++++--------------- 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 91a53350..ac39cb16 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,9 @@ - - CS1591;CS0219;8002;NU5125 - $(MSBuildThisFileDirectory) - - + + CS1591;CS0219;8002;NU5125 + $(MSBuildThisFileDirectory) + + $(DefineConstants);NETFRAMEWORK @@ -12,5 +12,10 @@ runtime; build; native; contentfiles; analyzers all + + + + + diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 7624b2e7..182013f7 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -1,51 +1,53 @@  - - CommandLine - Library - netstandard2.0;net40;net45;net461 - $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC - $(DefineConstants);SKIP_FSHARP - true - ..\..\CommandLine.snk - - true - CommandLineParser - CommandLineParser.FSharp - gsscoder;nemec;ericnewton76 - Command Line Parser Library - $(VersionSuffix) - 0.0.0 - Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. - Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. - Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors - License.md - CommandLine20.png - https://github.com/commandlineparser/commandline - command line;commandline;argument;option;parser;parsing;library;syntax;shell - true - 7.3 - + + CommandLine + Library + netstandard2.0;net40;net45;net461 + $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC + $(DefineConstants);SKIP_FSHARP + true + ..\..\CommandLine.snk + true + CommandLineParser + CommandLineParser.FSharp + gsscoder;nemec;ericnewton76 + Command Line Parser Library + $(VersionSuffix) + 0.0.0 + Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. + Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. + Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors + License.md + CommandLine20.png + https://github.com/commandlineparser/commandline + command line;commandline;argument;option;parser;parsing;library;syntax;shell + true + 8.0 + true + portable + + - - - + + + - - - true - README.md - - + + + true + README.md + + - - - - - - - - - - + + + + + + + + + + From 2063ffc6e6238d92d63bf376acb74eba3bf8ee57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Thu, 26 Dec 2019 05:41:44 +0100 Subject: [PATCH 076/198] Fix NullReferenceException when creating a default immutable instance (#495) * Fix NullReferenceException when creating a default immutable instance * Improve exception message when immutable type can't be created --- src/CommandLine/Core/InstanceBuilder.cs | 2 +- src/CommandLine/Infrastructure/ReflectionHelper.cs | 5 +++++ .../Unit/Core/InstanceBuilderTests.cs | 12 +++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index a1fdd9f2..5ea056d8 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -167,7 +167,7 @@ private static T BuildImmutable(Type typeInfo, Maybe> factory, IEnume if(ctor == null) { - throw new InvalidOperationException($"Type appears to be immutable, but no constructor found for type {typeInfo.FullName} to accept values."); + throw new InvalidOperationException($"Type {typeInfo.FullName} appears to be immutable, but no constructor found to accept values."); } var values = diff --git a/src/CommandLine/Infrastructure/ReflectionHelper.cs b/src/CommandLine/Infrastructure/ReflectionHelper.cs index a7926cb7..47fe70ea 100644 --- a/src/CommandLine/Infrastructure/ReflectionHelper.cs +++ b/src/CommandLine/Infrastructure/ReflectionHelper.cs @@ -91,6 +91,11 @@ public static T CreateDefaultImmutableInstance(Type[] constructorTypes) public static object CreateDefaultImmutableInstance(Type type, Type[] constructorTypes) { var ctor = type.GetTypeInfo().GetConstructor(constructorTypes); + if (ctor == null) + { + throw new InvalidOperationException($"Type {type.FullName} appears to be immutable, but no constructor found to accept values."); + } + var values = (from prms in ctor.GetParameters() select prms.ParameterType.CreateDefaultForImmutable()).ToArray(); return ctor.Invoke(values); diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index d4a4fd12..836a7b93 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1063,7 +1063,17 @@ public void OptionClass_IsImmutable_HasNoCtor() { Action act = () => InvokeBuild(new string[] { "Test" }, false, false); - act.Should().Throw(); + act.Should().Throw() + .Which.Message.Should().Be("Type CommandLine.Tests.Unit.Core.InstanceBuilderTests+ValueWithNoSetterOptions appears to be immutable, but no constructor found to accept values."); + } + + [Fact] + public void OptionClass_IsImmutable_HasNoCtor_HelpRequested() + { + Action act = () => InvokeBuild(new string[] { "--help" }); + + act.Should().Throw() + .Which.Message.Should().Be("Type CommandLine.Tests.Unit.Core.InstanceBuilderTests+ValueWithNoSetterOptions appears to be immutable, but no constructor found to accept values."); } [Fact] From 58b4f0b84b1a5d7260bb0e51413e4d60b23395ac Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Mon, 30 Dec 2019 21:51:46 +0200 Subject: [PATCH 077/198] Fix #496 - Cryptic error message with immutable option class (#555) --- src/CommandLine/Core/InstanceBuilder.cs | 20 +++++++++++-- .../Fakes/Immutable_Simple_Options.cs | 28 +++++++++++++++++++ .../Unit/Core/InstanceBuilderTests.cs | 16 +++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 5ea056d8..40b15917 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -169,8 +169,9 @@ private static T BuildImmutable(Type typeInfo, Maybe> factory, IEnume { throw new InvalidOperationException($"Type {typeInfo.FullName} appears to be immutable, but no constructor found to accept values."); } - - var values = + try + { + var values = (from prms in ctor.GetParameters() join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToLower() into spv from sp in spv.DefaultIfEmpty() @@ -185,6 +186,21 @@ from sp in spv.DefaultIfEmpty() var immutable = (T)ctor.Invoke(values); return immutable; + } + catch (Exception) + { + var ctorArgs = specPropsWithValue + .Select(x => x.Property.Name.ToLowerInvariant()).ToArray(); + throw GetException(ctorArgs); + } + Exception GetException(string[] s) + { + var ctorSyntax = s != null ? " Constructor Parameters can be ordered as: " + $"'({string.Join(", ", s)})'" : string.Empty; + var msg = + $"Type {typeInfo.FullName} appears to be Immutable with invalid constructor. Check that constructor arguments have the same name and order of their underlying Type. {ctorSyntax}"; + InvalidOperationException invalidOperationException = new InvalidOperationException(msg); + return invalidOperationException; + } } } diff --git a/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs b/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs index d305dcde..5f272876 100644 --- a/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs +++ b/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs @@ -31,4 +31,32 @@ public Immutable_Simple_Options(string stringValue, IEnumerable intSequence [Value(0)] public long LongValue { get { return longValue; } } } + + public class Immutable_Simple_Options_Invalid_Ctor_Args + { + private readonly string stringValue; + private readonly IEnumerable intSequence; + private readonly bool boolValue; + private readonly long longValue; + + public Immutable_Simple_Options_Invalid_Ctor_Args(string stringValue1, IEnumerable intSequence2, bool boolValue, long longValue) + { + this.stringValue = stringValue; + this.intSequence = intSequence; + this.boolValue = boolValue; + this.longValue = longValue; + } + + [Option(HelpText = "Define a string value here.")] + public string StringValue { get { return stringValue; } } + + [Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.")] + public IEnumerable IntSequence { get { return intSequence; } } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get { return boolValue; } } + + [Value(0)] + public long LongValue { get { return longValue; } } + } } diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 836a7b93..abeadeb9 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -901,6 +901,22 @@ public void Parse_to_immutable_instance(string[] arguments, Immutable_Simple_Opt expected.Should().BeEquivalentTo(((Parsed)result).Value); } + [Theory] + [MemberData(nameof(ImmutableInstanceData))] + public void Parse_to_immutable_instance_with_Invalid_Ctor_Args(string[] arguments, Immutable_Simple_Options _) + { + // Fixture setup in attributes + + // Exercize system + Action act = () => InvokeBuildImmutable( + arguments); + + // Verify outcome + var expectedMsg = + "Type CommandLine.Tests.Fakes.Immutable_Simple_Options_Invalid_Ctor_Args appears to be Immutable with invalid constructor. Check that constructor arguments have the same name and order of their underlying Type. Constructor Parameters can be ordered as: '(stringvalue, intsequence, boolvalue, longvalue)'"; + act.Should().Throw().WithMessage(expectedMsg); + } + [Fact] public void Parse_to_type_with_single_string_ctor_builds_up_correct_instance() { From fe9e602d9f573769ba1336104a557b3df7452232 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 30 Dec 2019 02:01:02 +0200 Subject: [PATCH 078/198] Remove the Exception when the AssemblyCompanyAttribute and AssemblyCopyrightAttribute are null and set Copyright to a default value. --- src/CommandLine/Text/CopyrightInfo.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/CommandLine/Text/CopyrightInfo.cs b/src/CommandLine/Text/CopyrightInfo.cs index 3fd2b6a8..c8bc3593 100644 --- a/src/CommandLine/Text/CopyrightInfo.cs +++ b/src/CommandLine/Text/CopyrightInfo.cs @@ -27,11 +27,11 @@ public class CopyrightInfo /// /// An empty object used for initialization. /// - public static CopyrightInfo Empty + public static CopyrightInfo Empty { get { - return new CopyrightInfo("author", 1); + return new CopyrightInfo("author", DateTime.Now.Year); } } @@ -115,12 +115,13 @@ public static CopyrightInfo Default case MaybeType.Just: return new CopyrightInfo(copyrightAttr.FromJustOrFail()); default: - // if no copyright attribute exist but a company attribute does, use it as copyright holder - return new CopyrightInfo( - ReflectionHelper.GetAttribute().FromJustOrFail( - new InvalidOperationException("CopyrightInfo::Default requires that you define AssemblyCopyrightAttribute or AssemblyCompanyAttribute.") - ).Company, - DateTime.Now.Year); + var companyAttr = ReflectionHelper.GetAttribute(); + return companyAttr.IsNothing() + //if both copyrightAttr and companyAttr aren't available in Assembly,don't fire Exception + ? Empty + // if no copyright attribute exist but a company attribute does, use it as copyright holder + : new CopyrightInfo(companyAttr.FromJust().Company, DateTime.Now.Year); + } } } @@ -192,4 +193,4 @@ protected virtual string FormatYears(int[] years) return yearsPart.ToString(); } } -} \ No newline at end of file +} From a282612f236f5746d2592372e6706c704cfd8433 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sat, 28 Dec 2019 18:39:37 +0200 Subject: [PATCH 079/198] Add a new AutoBuild overload method in the HelpText class. --- src/CommandLine/Text/HelpText.cs | 37 ++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index f5fd8065..f89e9723 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -389,7 +389,7 @@ public static HelpText AutoBuild( } /// - /// Creates a new instance of the class, + /// Creates a default instance of the class, /// automatically handling verbs or options scenario. /// /// The containing the instance that collected command line arguments parsed with class. @@ -400,6 +400,23 @@ public static HelpText AutoBuild( /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property /// of . public static HelpText AutoBuild(ParserResult parserResult, int maxDisplayWidth = DefaultMaximumLength) + { + return AutoBuild(parserResult, h => h, maxDisplayWidth); + } + + /// + /// Creates a custom instance of the class, + /// automatically handling verbs or options scenario. + /// + /// The containing the instance that collected command line arguments parsed with class. + /// A delegate used to customize the text block of reporting parsing errors text block. + /// The maximum width of the display. + /// + /// An instance of class. + /// + /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property + /// of . + public static HelpText AutoBuild(ParserResult parserResult, Func onError,int maxDisplayWidth = DefaultMaximumLength) { if (parserResult.Tag != ParserResultType.NotParsed) throw new ArgumentException("Excepting NotParsed type.", "parserResult"); @@ -410,13 +427,25 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}") { MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) - return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth); + return AutoBuild(parserResult, current => + { + onError?.Invoke(current); + return DefaultParsingErrorsHandler(parserResult, current); + }, e => e, maxDisplayWidth: maxDisplayWidth); var err = errors.OfType().Single(); var pr = new NotParsed(TypeInfo.Create(err.Type), Enumerable.Empty()); return err.Matched - ? AutoBuild(pr, current => DefaultParsingErrorsHandler(pr, current), e => e, maxDisplayWidth: maxDisplayWidth) - : AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, true, maxDisplayWidth); + ? AutoBuild(pr, current => + { + onError?.Invoke(current); + return DefaultParsingErrorsHandler(pr, current); + }, e => e, maxDisplayWidth: maxDisplayWidth) + : AutoBuild(parserResult, current => + { + onError?.Invoke(current); + return DefaultParsingErrorsHandler(parserResult, current); + }, e => e, true, maxDisplayWidth); } /// From b8169cd00c53d9cfb9d01343267073515c9f2e60 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 30 Dec 2019 21:47:48 +0200 Subject: [PATCH 080/198] Add tests for AutoBuild overload method in the HelpText class. --- src/CommandLine/Text/HelpText.cs | 21 +- .../Unit/Text/HelpTextTests.cs | 217 +++++++++++++----- 2 files changed, 168 insertions(+), 70 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index f89e9723..94b7e4f3 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -266,7 +266,7 @@ public bool AdditionalNewLineAfterOption /// public bool AddNewLineBetweenHelpSections { - get { return addNewLineBetweenHelpSections; } + get { return addNewLineBetweenHelpSections; } set { addNewLineBetweenHelpSections = value; } } @@ -416,7 +416,7 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay /// /// This feature is meant to be invoked automatically by the parser, setting the HelpWriter property /// of . - public static HelpText AutoBuild(ParserResult parserResult, Func onError,int maxDisplayWidth = DefaultMaximumLength) + public static HelpText AutoBuild(ParserResult parserResult, Func onError, int maxDisplayWidth = DefaultMaximumLength) { if (parserResult.Tag != ParserResultType.NotParsed) throw new ArgumentException("Excepting NotParsed type.", "parserResult"); @@ -434,9 +434,9 @@ public static HelpText AutoBuild(ParserResult parserResult, Func e, maxDisplayWidth: maxDisplayWidth); var err = errors.OfType().Single(); - var pr = new NotParsed(TypeInfo.Create(err.Type), Enumerable.Empty()); + var pr = new NotParsed(TypeInfo.Create(err.Type), new Error[] { err }); return err.Matched - ? AutoBuild(pr, current => + ? AutoBuild(pr, current => { onError?.Invoke(current); return DefaultParsingErrorsHandler(pr, current); @@ -460,7 +460,6 @@ public static HelpText DefaultParsingErrorsHandler(ParserResult parserResu if (((NotParsed)parserResult).Errors.OnlyMeaningfulOnes().Empty()) return current; - var errors = RenderParsingErrorsTextAsLines(parserResult, current.SentenceBuilder.FormatError, current.SentenceBuilder.FormatMutuallyExclusiveSetErrors, @@ -761,8 +760,8 @@ public override string ToString() var result = new StringBuilder(sbLength); result.Append(heading) - .AppendWhen(!string.IsNullOrEmpty(copyright), - Environment.NewLine, + .AppendWhen(!string.IsNullOrEmpty(copyright), + Environment.NewLine, copyright) .AppendWhen(preOptionsHelp.SafeLength() > 0, NewLineIfNeededBefore(preOptionsHelp), @@ -772,15 +771,15 @@ public override string ToString() Environment.NewLine, Environment.NewLine, optionsHelp.SafeToString()) - .AppendWhen(postOptionsHelp.SafeLength() > 0, + .AppendWhen(postOptionsHelp.SafeLength() > 0, NewLineIfNeededBefore(postOptionsHelp), Environment.NewLine, postOptionsHelp.ToString()); string NewLineIfNeededBefore(StringBuilder sb) { - if (AddNewLineBetweenHelpSections - && result.Length > 0 + if (AddNewLineBetweenHelpSections + && result.Length > 0 && !result.SafeEndsWith(Environment.NewLine) && !sb.SafeStartsWith(Environment.NewLine)) return Environment.NewLine; @@ -981,7 +980,7 @@ specification is OptionSpecification optionSpecification && if (optionGroupSpecification != null) { - optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group) + optionHelpText; + optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group) + optionHelpText; } //note that we need to indent trim the start of the string because it's going to be diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 1364c982..29e64431 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -191,7 +191,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c { // Fixture setup // Exercize system - var sut = new HelpText(headingInfo) { MaximumDisplayWidth = 100} ; + var sut = new HelpText(headingInfo) { MaximumDisplayWidth = 100 }; sut.AddOptions( new NotParsed( TypeInfo.Create(typeof(Simple_Options_With_HelpText_Set_To_Long_Description)), @@ -270,7 +270,7 @@ public void Long_pre_and_post_lines_without_spaces() // Teardown } - + [Fact] public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text() { @@ -296,32 +296,32 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( new MissingGroupOptionError("bad-option-group", optionsInGroup), }); Func fakeRenderer = err => + { + switch (err.Tag) { - switch (err.Tag) - { - case ErrorType.BadFormatTokenError: - return "ERR " + ((BadFormatTokenError)err).Token; - case ErrorType.MissingValueOptionError: - return "ERR " + ((MissingValueOptionError)err).NameInfo.NameText; - case ErrorType.UnknownOptionError: - return "ERR " + ((UnknownOptionError)err).Token; - case ErrorType.MissingRequiredOptionError: - return "ERR " + ((MissingRequiredOptionError)err).NameInfo.NameText; - case ErrorType.SequenceOutOfRangeError: - return "ERR " + ((SequenceOutOfRangeError)err).NameInfo.NameText; - case ErrorType.NoVerbSelectedError: - return "ERR no-verb-selected"; - case ErrorType.BadVerbSelectedError: - return "ERR " + ((BadVerbSelectedError)err).Token; - case ErrorType.MissingGroupOptionError: + case ErrorType.BadFormatTokenError: + return "ERR " + ((BadFormatTokenError)err).Token; + case ErrorType.MissingValueOptionError: + return "ERR " + ((MissingValueOptionError)err).NameInfo.NameText; + case ErrorType.UnknownOptionError: + return "ERR " + ((UnknownOptionError)err).Token; + case ErrorType.MissingRequiredOptionError: + return "ERR " + ((MissingRequiredOptionError)err).NameInfo.NameText; + case ErrorType.SequenceOutOfRangeError: + return "ERR " + ((SequenceOutOfRangeError)err).NameInfo.NameText; + case ErrorType.NoVerbSelectedError: + return "ERR no-verb-selected"; + case ErrorType.BadVerbSelectedError: + return "ERR " + ((BadVerbSelectedError)err).Token; + case ErrorType.MissingGroupOptionError: { var groupErr = (MissingGroupOptionError)err; return "ERR " + groupErr.Group + ": " + string.Join("---", groupErr.Names.Select(n => n.NameText)); } - default: - throw new InvalidOperationException(); - } - }; + default: + throw new InvalidOperationException(); + } + }; Func, string> fakeMutExclRenderer = _ => string.Empty; @@ -420,12 +420,12 @@ public void Invoke_AutoBuild_for_Verbs_with_specific_verb_returns_appropriate_fo }); // Exercize system - var helpText = HelpText.AutoBuild(fakeResult, maxDisplayWidth: 100); + var helpText = HelpText.AutoBuild(fakeResult, maxDisplayWidth: 100); // Verify outcome var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); lines[0].Should().Be(HeadingInfo.Default.ToString()); - lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which changes to commit."); lines[3].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); lines[4].Should().BeEquivalentTo("-m, --message Use the given message as the commit message."); @@ -451,7 +451,7 @@ public void Invoke_AutoBuild_for_Verbs_with_unknown_verb_returns_appropriate_for var lines = helpText.ToString().ToNotEmptyLines().TrimStringArray(); lines[0].Should().Be(HeadingInfo.Default.ToString()); - lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); lines[2].Should().BeEquivalentTo("add Add file contents to the index."); lines[3].Should().BeEquivalentTo("commit Record changes to the repository."); lines[4].Should().BeEquivalentTo("clone Clone a repository into a new directory."); @@ -495,7 +495,7 @@ public static void RenderUsageText_returns_properly_formatted_text() ParserResult result = new NotParsed( TypeInfo.Create(typeof(Options_With_Usage_Attribute)), Enumerable.Empty()); - + // Exercize system var text = HelpText.RenderUsageText(result); @@ -584,7 +584,7 @@ public void Invoke_AutoBuild_for_Options_with_Usage_returns_appropriate_formatte var text = helpText.ToString(); var lines = text.ToLines().TrimStringArray(); - + lines.Should().StartWith(expected); } @@ -667,23 +667,17 @@ public void Default_set_to_sequence_should_be_properly_printed() [Fact] public void AutoBuild_when_no_assembly_attributes() { - string expectedCopyright = "Copyright (C) 1 author"; + string expectedCopyright = $"Copyright (C) {DateTime.Now.Year} author"; ReflectionHelper.SetAttributeOverride(new Attribute[0]); ParserResult fakeResult = new NotParsed( - TypeInfo.Create(typeof (Simple_Options)), new Error[0]); - bool onErrorCalled = false; - HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => - { - onErrorCalled = true; - return ht; - }, ex => ex); - - onErrorCalled.Should().BeTrue(); + TypeInfo.Create(typeof(Simple_Options)), new Error[0]); + HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => ht, ex => ex); actualResult.Copyright.Should().Be(expectedCopyright); } + [Fact] public void AutoBuild_with_assembly_title_and_version_attributes_only() { @@ -697,15 +691,8 @@ public void AutoBuild_with_assembly_title_and_version_attributes_only() }); ParserResult fakeResult = new NotParsed( - TypeInfo.Create(typeof (Simple_Options)), new Error[0]); - bool onErrorCalled = false; - HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => - { - onErrorCalled = true; - return ht; - }, ex => ex); - - onErrorCalled.Should().BeTrue(); + TypeInfo.Create(typeof(Simple_Options)), new Error[0]); + HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => ht, ex => ex); actualResult.Heading.Should().Be(string.Format("{0} {1}", expectedTitle, expectedVersion)); } @@ -720,7 +707,7 @@ public void AutoBuild_with_assembly_company_attribute_only() }); ParserResult fakeResult = new NotParsed( - TypeInfo.Create(typeof (Simple_Options)), new Error[0]); + TypeInfo.Create(typeof(Simple_Options)), new Error[0]); bool onErrorCalled = false; HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => { @@ -748,7 +735,7 @@ public void HelpTextHonoursLineBreaks() { // Fixture setup // Exercize system - var sut = new HelpText {AddDashesToOption = true} + var sut = new HelpText { AddDashesToOption = true } .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaks_Options)), Enumerable.Empty())); @@ -758,7 +745,7 @@ public void HelpTextHonoursLineBreaks() lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description."); lines[1].Should().BeEquivalentTo(" It has multiple lines."); lines[2].Should().BeEquivalentTo(" We also want to ensure that indentation is correct."); - + // Teardown } @@ -767,7 +754,7 @@ public void HelpTextHonoursIndentationAfterLineBreaks() { // Fixture setup // Exercize system - var sut = new HelpText {AddDashesToOption = true} + var sut = new HelpText { AddDashesToOption = true } .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaks_Options)), Enumerable.Empty())); @@ -777,7 +764,7 @@ public void HelpTextHonoursIndentationAfterLineBreaks() lines[3].Should().BeEquivalentTo(" --stringvalu2 This is a help text description where we want"); lines[4].Should().BeEquivalentTo(" the left pad after a linebreak to be honoured so that"); lines[5].Should().BeEquivalentTo(" we can sub-indent within a description."); - + // Teardown } @@ -786,7 +773,7 @@ public void HelpTextPreservesIndentationAcrossWordWrap() { // Fixture setup // Exercise system - var sut = new HelpText {AddDashesToOption = true,MaximumDisplayWidth = 60} + var sut = new HelpText { AddDashesToOption = true, MaximumDisplayWidth = 60 } .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaksAndSubIndentation_Options)), Enumerable.Empty())); @@ -809,7 +796,7 @@ public void HelpTextIsConsitentRegardlessOfCompileTimeLineStyle() { // Fixture setup // Exercize system - var sut = new HelpText {AddDashesToOption = true} + var sut = new HelpText { AddDashesToOption = true } .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithMixedLineBreaks_Options)), Enumerable.Empty())); @@ -819,7 +806,7 @@ public void HelpTextIsConsitentRegardlessOfCompileTimeLineStyle() lines[0].Should().BeEquivalentTo(" --stringvalue This is a help text description"); lines[1].Should().BeEquivalentTo(" It has multiple lines."); lines[2].Should().BeEquivalentTo(" Third line"); - + // Teardown } @@ -828,14 +815,14 @@ public void HelpTextPreservesIndentationAcrossWordWrapWithSmallMaximumDisplayWid { // Fixture setup // Exercise system - var sut = new HelpText {AddDashesToOption = true,MaximumDisplayWidth = 10} + var sut = new HelpText { AddDashesToOption = true, MaximumDisplayWidth = 10 } .AddOptions(new NotParsed(TypeInfo.Create(typeof(HelpTextWithLineBreaksAndSubIndentation_Options)), Enumerable.Empty())); // Verify outcome - - Assert.True(sut.ToString().Length>0); - + + Assert.True(sut.ToString().Length > 0); + // Teardown } @@ -926,5 +913,117 @@ public void Options_Should_Render_Multiple_OptionGroups_When_Available() lines[6].Should().BeEquivalentTo("--help Display this help screen."); lines[7].Should().BeEquivalentTo("--version Display version information."); } + + #region Custom Help + + [Fact] + [Trait("Category", "CustomHelp")] + public void AutoBuild_with_custom_copyright_using_onError_action() + { + string expectedCopyright = "Copyright (c) 2019 Global.com"; + var expectedHeading = "MyApp 2.0.0-beta"; + ParserResult fakeResult = new NotParsed( + TypeInfo.Create(typeof(Simple_Options)), + new Error[] { new HelpRequestedError() }); + bool onErrorCalled = false; + HelpText actualResult = HelpText.AutoBuild(fakeResult, ht => + { + ht.AdditionalNewLineAfterOption = false; + ht.Heading = "MyApp 2.0.0-beta"; + ht.Copyright = "Copyright (c) 2019 Global.com"; + return ht; + }); + actualResult.Copyright.Should().Be(expectedCopyright); + actualResult.Heading.Should().Be(expectedHeading); + } + + [Fact] + [Trait("Category", "CustomHelp")] + public void AutoBuild_with_custom_help_and_version_request() + { + string expectedTitle = "Title"; + string expectedVersion = "1.2.3.4"; + + ReflectionHelper.SetAttributeOverride(new Attribute[] + { + new AssemblyTitleAttribute(expectedTitle), + new AssemblyInformationalVersionAttribute(expectedVersion) + }); + + ParserResult fakeResult = new NotParsed( + TypeInfo.Create(typeof(Simple_Options)), + new Error[] { new VersionRequestedError() }); + + HelpText helpText = HelpText.AutoBuild(fakeResult, ht => ht); + helpText.ToString().Trim().Should().Be($"{expectedTitle} {expectedVersion}"); + } + [Fact] + [Trait("Category", "CustomHelp")] + public void Invoke_Custom_AutoBuild_for_Verbs_with_specific_verb_and_no_AdditionalNewLineAfterOption_returns_appropriate_formatted_text() + { + // Fixture setup + var fakeResult = new NotParsed( + TypeInfo.Create(typeof(NullInstance)), + new Error[] + { + new HelpVerbRequestedError("commit", typeof(Commit_Verb), true) + }); + + // Exercize system + var helpText = HelpText.AutoBuild(fakeResult, h => { + h.AdditionalNewLineAfterOption = false; + return h; + }); + + // Verify outcome + var lines = helpText.ToString().ToLines().TrimStringArray(); + var i = 0; + lines[i++].Should().Be(HeadingInfo.Default.ToString()); + lines[i++].Should().Be(CopyrightInfo.Default.ToString()); + lines[i++].Should().BeEmpty(); + lines[i++].Should().BeEquivalentTo("-p, --patch Use the interactive patch selection interface to chose which"); + lines[i++].Should().BeEquivalentTo("changes to commit."); + lines[i++].Should().BeEquivalentTo("--amend Used to amend the tip of the current branch."); + lines[i++].Should().BeEquivalentTo("-m, --message Use the given message as the commit message."); + lines[i++].Should().BeEquivalentTo("--help Display this help screen."); + + } + [Fact] + [Trait("Category", "CustomHelp")] + public void Invoke_AutoBuild_for_Options_with_custom_help_returns_appropriate_formatted_text() + { + // Fixture setup + var fakeResult = new NotParsed( + TypeInfo.Create(typeof(Simple_Options)), + new Error[] + { + new BadFormatTokenError("badtoken"), + new SequenceOutOfRangeError(new NameInfo("i", "")) + }); + + // Exercize system + var helpText = HelpText.AutoBuild(fakeResult, h => h); + + // Verify outcome + var lines = helpText.ToString().ToLines().TrimStringArray(); + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[2].Should().BeEmpty(); + lines[3].Should().BeEquivalentTo("ERROR(S):"); + lines[4].Should().BeEquivalentTo("Token 'badtoken' is not recognized."); + lines[5].Should().BeEquivalentTo("A sequence option 'i' is defined with fewer or more items than required."); + lines[6].Should().BeEmpty(); + lines[7].Should().BeEquivalentTo("--stringvalue Define a string value here."); + lines[8].Should().BeEmpty(); + lines[9].Should().BeEquivalentTo("-s, --shortandlong Example with both short and long name."); + lines[10].Should().BeEmpty(); + lines[11].Should().BeEquivalentTo("-i Define a int sequence here."); + lines[12].Should().BeEmpty(); + lines[13].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[14].Should().BeEmpty(); + lines[15].Should().BeEquivalentTo("--help Display this help screen."); + + } + #endregion } } From 86e2dba9c55e2dec625fcbbe1985c70bbaf24880 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 31 Dec 2019 01:26:01 +0200 Subject: [PATCH 081/198] Modify changelog and readme --- CHANGELOG.md | 37 +++++++++++-- README.md | 86 +++++++++++++++++++++++------- src/CommandLine/CommandLine.csproj | 5 +- 3 files changed, 103 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ecae61..f83f501c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,36 @@ # Changelog All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.7.0] - 2019-12-31 +### Added +- Add option groups feature by [@hadzhiyski](https://github.com/commandlineparser/commandline/pull/552) - When one or more options has group set, at least one of these properties should have set value (they behave as required). +- Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](). +- Improve spacing in HelpText by [@asherber](https://github.com/commandlineparser/commandline/pull/494) by adding a new option in the HelpText. +- Add a new option "SkipDefault" in UnParserSettings by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/550) to add the ability of skipping the options with a default value and fix [#541](https://github.com/commandlineparser/commandline/issues/541). +- Generate a new symbolic nuget Package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/554) to Improve the debugging of Applications with the NuGet package using [symbols experience](https://github.com/NuGet/Home/wiki/NuGet-Package-Debugging-&-Symbols-Improvements). +- Add Support to [SourceLink](https://github.com/dotnet/sourcelink/blob/master/docs/README.md) in the nuget package [@moh-hassan](https://github.com/commandlineparser/commandline/pull/554). + +### Changed +- Remove the Exception when both CompanyAttribute and CopyRightAttribute are null in the Excuting assembly and set the copyright text to a default value. +- Change default copyright to include current year instead of 1 by @moh-hassan. +- Enabling c# 8 and Vs2019. + +### Fixed +- Fix NullReferenceException when creating a default immutable instance by [@0xced](https://github.com/commandlineparser/commandline/pull/495). +- Fix issue [#496](https://github.com/commandlineparser/commandline/issues/496) - Cryptic error message with immutable option class by[@moh-hassan](https://github.com/commandlineparser/commandline/pull/555). +- Fix UnParserExtensions.FormatCommandLine by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/550) to resolve: + - Fix Quote for Options of type DatTime [#502](https://github.com/commandlineparser/commandline/issues/502) and [#528](https://github.com/commandlineparser/commandline/issues/258). + - Fix Quote for options of type TimeSpan and DateTimeOffset. + - Fix Nullable type [#305](https://github.com/commandlineparser/commandline/issues/305) + +- Fix nuget Licence in nuget package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/549) and fix issue [#545](https://github.com/commandlineparser/commandline/issues/545). +- Fix PackageIconUrl warning in nuget package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/551). +- Fix immutable nullException, Improve exception message when immutable type can't be created +- Fix Custom help for verbs issue[#529](https://github.com/commandlineparser/commandline/issues/529) +- Fix --help switch throwing exception in F# [#366](https://github.com/commandlineparser/commandline/issues/366) +by [@WallaceKelly](https://github.com/commandlineparser/commandline/pull/493) ## [2.6.0] - 2019-07-31 ### Added @@ -48,7 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add support to NetStandard2.0 by [@ViktorHofer](https://github.com/commandlineparser/commandline/pull/307) - Add strong name signing [@ViktorHofer](https://github.com/commandlineparser/commandline/pull/307) -- Added AutoHelp and AutoVersion properties to control adding of implicit 'help' and 'version' options/verbs by [@Athari](https://github.com/commandlineparser/commandline/pull/256). +- Added AutoBuild and AutoVersion properties to control adding of implicit 'help' and 'version' options/verbs by [@Athari](https://github.com/commandlineparser/commandline/pull/256). - Added simpler C# Quick Start example at readme.md by [@lythix](https://github.com/commandlineparser/commandline/pull/274). - Add validate feature in Set parameter, and throw exception, and show usage,Issue #283 by[@e673](https://github.com/commandlineparser/commandline/pull/286). @@ -92,5 +120,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.2.0] - 2018-01-07 -## [1.9.71.2] - 2013-02-27 -The starting bascode version \ No newline at end of file +## [1.9.71.2] - 2013-02-27: The starting bascode version \ No newline at end of file diff --git a/README.md b/README.md index 91727097..debd9767 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build status](https://ci.appveyor.com/api/projects/status/p61dj8udxs2aocmo/branch/master?svg=true)](https://ci.appveyor.com/project/commandlineparser/commandline/branch/master) [![NuGet](https://img.shields.io/nuget/dt/commandlineparser.svg)](http://nuget.org/packages/commandlineparser) -[![NuGet](https://img.shields.io/nuget/v/commandlineparser.svg)](http://nuget.org/packages/commandlineparser) -[![NuGet](https://img.shields.io/nuget/vpre/commandlineparser.svg)](http://nuget.org/packages/commandlineparser) +[![NuGet](https://img.shields.io/nuget/v/commandlineparser.svg)](https://www.nuget.org/packages/CommandLineParser/) +[![NuGet](https://img.shields.io/nuget/vpre/commandlineparser.svg)](https://www.nuget.org/packages/CommandLineParser.FSharp/) [![Join the Gitter chat!](https://badges.gitter.im/gsscoder/commandline.svg)](https://gitter.im/gsscoder/commandline?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Command Line Parser Library for CLR and NetStandard @@ -20,14 +20,19 @@ __This library provides _hassle free_ command line parsing with a constantly upd # At a glance: -- Compatible with __.NET Framework 4.0+__, __Mono 2.1+ Profile__, and __.NET Core__ +- Compatible with __.NET Framework 4.0+__, __Mono 2.1+ Profile__, __.NET Standard__ and __.NET Core__ - Doesn't depend on other packages (No dependencies beyond standard base libraries) - One line parsing using default singleton: `CommandLine.Parser.Default.ParseArguments(...)`. - Automatic or one line help screen generator: `HelpText.AutoBuild(...)`. -- Supports `--help`, `--version`, `version` and `help [verb]` by default. +- Supports `--help`, `--version`, `version` and `help [verb]` by default with customization. - Map to sequences (via `IEnumerable` and similar) and scalar types, including Enums and `Nullable`. - You can also map to every type with a constructor that accepts a string (like `System.Uri`). - Define [verb commands](https://github.com/commandlineparser/commandline/wiki/Verbs) similar to `git commit -a`. +- Support HelpText localization. +- Support ordering of options in HelpText. +- Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and Options groups. +- Support named and value options. +- Support Asynchronous programming with async and await. - Unparsing support: `CommandLine.Parser.Default.FormatCommandLine(T options)`. - CommandLineParser.FSharp package is F#-friendly with support for `option<'a>`, see [demo](https://github.com/commandlineparser/commandline/blob/master/demo/fsharp-demo.fsx). _NOTE: This is a separate NuGet package._ - Most of features applies with a [CoC](http://en.wikipedia.org/wiki/Convention_over_configuration) philosophy. @@ -48,7 +53,7 @@ You can utilize the parser library in several ways: C# Quick Start: -```csharp +```cs using System; using CommandLine; @@ -83,9 +88,13 @@ namespace QuickStart } ``` -C# Examples: +## C# Examples: + +
+ Click to expand! + +```cs -```csharp class Options { [Option('r', "read", Required = true, HelpText = "Input files to be processed.")] @@ -109,14 +118,31 @@ class Options static void Main(string[] args) { CommandLine.Parser.Default.ParseArguments(args) - .WithParsed(opts => RunOptionsAndReturnExitCode(opts)) - .WithNotParsed((errs) => HandleParseError(errs)); + .WithParsed(RunOptions) + .WithNotParsed(HandleParseError); +} +static void RunOptions(Options opts) +{ + //handle options } +static void HandleParseError(IEnumerable errs) +{ + //handle errors +} + ``` -F# Examples: +
+ +Demo to show IEnumerable options and other usage: [Online Demo](https://dotnetfiddle.net/wrcAxr) + +## F# Examples: + +
+ Click to expand! ```fsharp + type options = { [] files : seq; [] verbose : bool; @@ -130,10 +156,15 @@ let main argv = | :? Parsed as parsed -> run parsed.Value | :? NotParsed as notParsed -> fail notParsed.Errors ``` +
+ +## VB.NET Example: + +
+ Click to expand! -VB.NET: +```vb -```VB.NET Class Options @@ -159,14 +190,19 @@ Sub Main(ByVal args As String()) .WithNotParsed(Function(errs As IEnumerable(Of [Error])) 1) End Sub ``` +
-### For verbs: +## For verbs: 1. Create separate option classes for each verb. An options base class is supported. 2. Call ParseArguments with all the verb attribute decorated options classes. 3. Use MapResult to direct program flow to the verb that was parsed. -C# example: +### C# example: + + +
+ Click to expand! ```csharp [Verb("add", HelpText = "Add file contents to the index.")] @@ -191,10 +227,15 @@ int Main(string[] args) { errs => 1); } ``` +
+ +### VB.NET example: -VB.NET example: -```VB.NET +
+ Click to expand! + +```vb Public Class AddOptions 'Normal options here @@ -218,10 +259,14 @@ Function Main(ByVal args As String()) As Integer ) End Function ``` +
-F# Example: +### F# Example: -```fsharp +
+ Click to expand! + +```fs open CommandLine [] @@ -248,6 +293,11 @@ let main args = | :? CloneOptions as opts -> RunCloneAndReturnExitCode opts | :? CommandLine.NotParsed -> 1 ``` +
+ +# Release History + +See the [changelog](CHANGELOG.md) # Contributors First off, _Thank you!_ All contributions are welcome. diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 182013f7..6c08e435 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -22,6 +22,7 @@ CommandLine20.png https://github.com/commandlineparser/commandline command line;commandline;argument;option;parser;parsing;library;syntax;shell + https://github.com/commandlineparser/commandline/blob/master/CHANGELOG.md true 8.0 true @@ -47,7 +48,7 @@ - - + + From 612dbbccc3554219c0a4e025ce3c823302168442 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 31 Dec 2019 23:25:35 +0200 Subject: [PATCH 082/198] Remove Xunit warning --- Directory.Build.props | 35 +++++++++---------- appveyor.yml | 4 +-- src/CommandLine/CommandLine.csproj | 5 ++- .../Fakes/Immutable_Simple_Options.cs | 4 +-- .../Unit/Core/InstanceBuilderTests.cs | 17 +++++++-- tests/CommandLine.Tests/Unit/ParserTests.cs | 16 ++++----- .../Unit/Text/HelpTextTests.cs | 2 +- 7 files changed, 46 insertions(+), 37 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index ac39cb16..3db45ca4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,21 +1,18 @@ - - CS1591;CS0219;8002;NU5125 - $(MSBuildThisFileDirectory) - - - $(DefineConstants);NETFRAMEWORK - - - - - runtime; build; native; contentfiles; analyzers - all - - - - - - - + + CS1591;CS0219;8002;NU5125 + $(MSBuildThisFileDirectory) + false + + + $(DefineConstants);NETFRAMEWORK + + + + + runtime; build; native; contentfiles; analyzers + all + + + diff --git a/appveyor.yml b/appveyor.yml index 459a8bed..97a6a13e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.0-alpha-{build} +version: 2.7.0-{build} -image: Visual Studio 2017 +image: Visual Studio 2019 clone_depth: 1 pull_requests: diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 6c08e435..52d8c16c 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -27,7 +27,6 @@ 8.0 true portable - @@ -48,7 +47,7 @@ - - + + diff --git a/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs b/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs index 5f272876..ae829e7d 100644 --- a/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs +++ b/tests/CommandLine.Tests/Fakes/Immutable_Simple_Options.cs @@ -41,8 +41,8 @@ public class Immutable_Simple_Options_Invalid_Ctor_Args public Immutable_Simple_Options_Invalid_Ctor_Args(string stringValue1, IEnumerable intSequence2, bool boolValue, long longValue) { - this.stringValue = stringValue; - this.intSequence = intSequence; + this.stringValue = stringValue1; + this.intSequence = intSequence2; this.boolValue = boolValue; this.longValue = longValue; } diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index abeadeb9..bf885f76 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -902,8 +902,9 @@ public void Parse_to_immutable_instance(string[] arguments, Immutable_Simple_Opt } [Theory] - [MemberData(nameof(ImmutableInstanceData))] - public void Parse_to_immutable_instance_with_Invalid_Ctor_Args(string[] arguments, Immutable_Simple_Options _) + [MemberData(nameof(ImmutableInstanceDataArgs))] + [Trait("Category", "Immutable")] + public void Parse_to_immutable_instance_with_Invalid_Ctor_Args(string[] arguments) { // Fixture setup in attributes @@ -1209,6 +1210,18 @@ public static IEnumerable ImmutableInstanceData yield return new object[] { new[] { "--stringvalue=strval0", "-i", "9", "7", "8", "-x", "9876543210" }, new Immutable_Simple_Options("strval0", new[] { 9, 7, 8 }, true, 9876543210L) }; } } + public static IEnumerable ImmutableInstanceDataArgs + { + get + { + yield return new object[] { new string[] { } } ; + yield return new object[] {new [] {"--stringvalue=strval0"}}; + yield return new object[] { new[] { "-i", "9", "7", "8" } }; + yield return new object[] { new[] { "-x" }}; + yield return new object[] { new[] { "9876543210" }}; + yield return new object[] { new[] { "--stringvalue=strval0", "-i", "9", "7", "8", "-x", "9876543210" }}; + } + } public static IEnumerable GuidData { diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 07e9bed3..af5a6eec 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -682,15 +682,16 @@ public void Properly_formatted_help_screen_excludes_help_as_unknown_option() // Teardown } - //[Fact] - public static void Breaking_mutually_exclusive_set_constraint_with_set_name_with_partial_string_right_side_equality_gererates_MissingValueOptionError() + [Fact] + public static void Breaking_mutually_exclusive_set_constraint_with_both_set_name_with_gererates_Error() { // Fixture setup var expectedResult = new[] - { - new MutuallyExclusiveSetError(new NameInfo("", "weburl"), string.Empty), - new MutuallyExclusiveSetError(new NameInfo("", "somethingelese"), string.Empty) - }; + { + new MutuallyExclusiveSetError(new NameInfo("", "weburl"), "theweb"), + new MutuallyExclusiveSetError(new NameInfo("", "somethingelse"), "theweb"), + + }; var sut = new Parser(); // Exercize system @@ -699,8 +700,7 @@ public static void Breaking_mutually_exclusive_set_constraint_with_set_name_with // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown + } [Fact] diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 29e64431..dde64dc8 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -845,7 +845,7 @@ public void Options_should_be_separated_by_spaces() // Verify outcome var text = helpText.ToString(); var lines = text.ToLines().TrimStringArray(); - Console.WriteLine(text); + lines[3].Should().Be("-z, --strseq (Default: a b c)"); lines[5].Should().Be("-y, --intseq (Default: 1 2 3)"); lines[7].Should().Be("-q, --dblseq (Default: 1.1 2.2 3.3)"); From b2898102f59c21e0744594d91c748d1630b2e61f Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 31 Dec 2019 23:43:24 +0200 Subject: [PATCH 083/198] Modify Changelog.md --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f83f501c..46677cf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,16 @@ CommandLineParser project adheres to [Semantic Versioning](https://semver.org/sp ## [2.7.0] - 2019-12-31 ### Added - Add option groups feature by [@hadzhiyski](https://github.com/commandlineparser/commandline/pull/552) - When one or more options has group set, at least one of these properties should have set value (they behave as required). -- Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](). +- Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). - Improve spacing in HelpText by [@asherber](https://github.com/commandlineparser/commandline/pull/494) by adding a new option in the HelpText. - Add a new option "SkipDefault" in UnParserSettings by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/550) to add the ability of skipping the options with a default value and fix [#541](https://github.com/commandlineparser/commandline/issues/541). - Generate a new symbolic nuget Package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/554) to Improve the debugging of Applications with the NuGet package using [symbols experience](https://github.com/NuGet/Home/wiki/NuGet-Package-Debugging-&-Symbols-Improvements). - Add Support to [SourceLink](https://github.com/dotnet/sourcelink/blob/master/docs/README.md) in the nuget package [@moh-hassan](https://github.com/commandlineparser/commandline/pull/554). ### Changed -- Remove the Exception when both CompanyAttribute and CopyRightAttribute are null in the Excuting assembly and set the copyright text to a default value. -- Change default copyright to include current year instead of 1 by @moh-hassan. -- Enabling c# 8 and Vs2019. +- Remove the Exception when both CompanyAttribute and CopyRightAttribute are null in the Excuting assembly and set the copyright text to a default value by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). +- Change the default copyright to include current year instead of 1 by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). +- Enabling c# 8 and Vs2019 image in Appveyor. ### Fixed - Fix NullReferenceException when creating a default immutable instance by [@0xced](https://github.com/commandlineparser/commandline/pull/495). @@ -28,7 +28,7 @@ CommandLineParser project adheres to [Semantic Versioning](https://semver.org/sp - Fix nuget Licence in nuget package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/549) and fix issue [#545](https://github.com/commandlineparser/commandline/issues/545). - Fix PackageIconUrl warning in nuget package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/551). - Fix immutable nullException, Improve exception message when immutable type can't be created -- Fix Custom help for verbs issue[#529](https://github.com/commandlineparser/commandline/issues/529) +- Fix Custom help for verbs issue[#529](https://github.com/commandlineparser/commandline/issues/529) by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). - Fix --help switch throwing exception in F# [#366](https://github.com/commandlineparser/commandline/issues/366) by [@WallaceKelly](https://github.com/commandlineparser/commandline/pull/493) From 75fb2308ecfff94f7749423a0eb3bb99ed088d89 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 31 Dec 2019 23:43:24 +0200 Subject: [PATCH 084/198] Modify appveyor.yml --- CHANGELOG.md | 10 +++++----- appveyor.yml | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f83f501c..46677cf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,16 @@ CommandLineParser project adheres to [Semantic Versioning](https://semver.org/sp ## [2.7.0] - 2019-12-31 ### Added - Add option groups feature by [@hadzhiyski](https://github.com/commandlineparser/commandline/pull/552) - When one or more options has group set, at least one of these properties should have set value (they behave as required). -- Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](). +- Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). - Improve spacing in HelpText by [@asherber](https://github.com/commandlineparser/commandline/pull/494) by adding a new option in the HelpText. - Add a new option "SkipDefault" in UnParserSettings by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/550) to add the ability of skipping the options with a default value and fix [#541](https://github.com/commandlineparser/commandline/issues/541). - Generate a new symbolic nuget Package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/554) to Improve the debugging of Applications with the NuGet package using [symbols experience](https://github.com/NuGet/Home/wiki/NuGet-Package-Debugging-&-Symbols-Improvements). - Add Support to [SourceLink](https://github.com/dotnet/sourcelink/blob/master/docs/README.md) in the nuget package [@moh-hassan](https://github.com/commandlineparser/commandline/pull/554). ### Changed -- Remove the Exception when both CompanyAttribute and CopyRightAttribute are null in the Excuting assembly and set the copyright text to a default value. -- Change default copyright to include current year instead of 1 by @moh-hassan. -- Enabling c# 8 and Vs2019. +- Remove the Exception when both CompanyAttribute and CopyRightAttribute are null in the Excuting assembly and set the copyright text to a default value by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). +- Change the default copyright to include current year instead of 1 by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). +- Enabling c# 8 and Vs2019 image in Appveyor. ### Fixed - Fix NullReferenceException when creating a default immutable instance by [@0xced](https://github.com/commandlineparser/commandline/pull/495). @@ -28,7 +28,7 @@ CommandLineParser project adheres to [Semantic Versioning](https://semver.org/sp - Fix nuget Licence in nuget package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/549) and fix issue [#545](https://github.com/commandlineparser/commandline/issues/545). - Fix PackageIconUrl warning in nuget package by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/551). - Fix immutable nullException, Improve exception message when immutable type can't be created -- Fix Custom help for verbs issue[#529](https://github.com/commandlineparser/commandline/issues/529) +- Fix Custom help for verbs issue[#529](https://github.com/commandlineparser/commandline/issues/529) by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). - Fix --help switch throwing exception in F# [#366](https://github.com/commandlineparser/commandline/issues/366) by [@WallaceKelly](https://github.com/commandlineparser/commandline/pull/493) diff --git a/appveyor.yml b/appveyor.yml index 97a6a13e..39c21520 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,6 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.0-{build} + +version: 2.7.1-beta4 image: Visual Studio 2019 From ebb3bbbaed07169d67dc36baac239ab33b630a5a Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 1 Jan 2020 02:33:10 +0200 Subject: [PATCH 085/198] Modify Changelog.md --- CHANGELOG.md | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46677cf0..24336458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.7.0] - 2019-12-31 +## [2.7.1] - 2020-1-1 ### Added - Add option groups feature by [@hadzhiyski](https://github.com/commandlineparser/commandline/pull/552) - When one or more options has group set, at least one of these properties should have set value (they behave as required). - Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). diff --git a/appveyor.yml b/appveyor.yml index 39c21520..73b580d2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.1-beta4 +version: 2.7.{build} image: Visual Studio 2019 From fd50cac9a2d989918edbafd036555f8002f6518f Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 1 Jan 2020 02:55:53 +0200 Subject: [PATCH 086/198] Fix license file --- src/CommandLine/CommandLine.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 52d8c16c..dbf41e11 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -47,7 +47,7 @@ - - + + From 66706df91236ab5683e21cc1b9974591768b6bd4 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 1 Jan 2020 03:16:57 +0200 Subject: [PATCH 087/198] Fix license file --- src/CommandLine/CommandLine.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index dbf41e11..20050b11 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -47,7 +47,7 @@ - - + + From 31fa871f6f2ba033250f2e9680cb3ed6afa150bd Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 1 Jan 2020 20:48:21 +0200 Subject: [PATCH 088/198] Modify Readme.md --- CHANGELOG.md | 5 +++-- README.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24336458..e5c5e55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.7.1] - 2020-1-1 +## [2.7.0] - 2020-1-1 +## [2.7.0-preview1] - 2020-1-1 ### Added - Add option groups feature by [@hadzhiyski](https://github.com/commandlineparser/commandline/pull/552) - When one or more options has group set, at least one of these properties should have set value (they behave as required). - Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). @@ -120,4 +121,4 @@ by [@WallaceKelly](https://github.com/commandlineparser/commandline/pull/493) ## [2.2.0] - 2018-01-07 -## [1.9.71.2] - 2013-02-27: The starting bascode version \ No newline at end of file +## [1.9.71.2] - 2013-02-27: The starting bascode version diff --git a/README.md b/README.md index debd9767..d1b0c174 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build status](https://ci.appveyor.com/api/projects/status/p61dj8udxs2aocmo/branch/master?svg=true)](https://ci.appveyor.com/project/commandlineparser/commandline/branch/master) [![NuGet](https://img.shields.io/nuget/dt/commandlineparser.svg)](http://nuget.org/packages/commandlineparser) [![NuGet](https://img.shields.io/nuget/v/commandlineparser.svg)](https://www.nuget.org/packages/CommandLineParser/) -[![NuGet](https://img.shields.io/nuget/vpre/commandlineparser.svg)](https://www.nuget.org/packages/CommandLineParser.FSharp/) +[![NuGet](https://img.shields.io/nuget/vpre/commandlineparser.svg)](https://www.nuget.org/packages/CommandLineParser/) [![Join the Gitter chat!](https://badges.gitter.im/gsscoder/commandline.svg)](https://gitter.im/gsscoder/commandline?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Command Line Parser Library for CLR and NetStandard From 4311f3fe99e060ab15a18638a184de37725ff463 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 15:38:56 +0100 Subject: [PATCH 089/198] Upgraded parts of CSharpx from Version 1.6.2-alpha --- src/CommandLine/CommandLine.csproj | 9 +- src/CommandLine/Core/InstanceBuilder.cs | 20 +- src/CommandLine/Core/OptionMapper.cs | 2 +- src/CommandLine/Core/TokenPartitioner.cs | 8 +- src/CommandLine/Core/Tokenizer.cs | 12 +- .../Infrastructure/{ => CSharpx}/Either.cs | 35 ++- .../{ => CSharpx}/EnumerableExtensions.cs | 230 +++++++++++------- .../Infrastructure/{ => CSharpx}/Maybe.cs | 39 ++- src/CommandLine/UnParserExtensions.cs | 2 +- 9 files changed, 196 insertions(+), 161 deletions(-) rename src/CommandLine/Infrastructure/{ => CSharpx}/Either.cs (91%) rename src/CommandLine/Infrastructure/{ => CSharpx}/EnumerableExtensions.cs (75%) rename src/CommandLine/Infrastructure/{ => CSharpx}/Maybe.cs (92%) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 20050b11..b05cbb04 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -4,7 +4,7 @@ CommandLine Library netstandard2.0;net40;net45;net461 - $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC + $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND $(DefineConstants);SKIP_FSHARP true ..\..\CommandLine.snk @@ -47,7 +47,10 @@ - - + + + + + diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 40b15917..4aff4080 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -28,14 +28,14 @@ public static ParserResult Build( var specProps = typeInfo.GetSpecifications(pi => SpecificationProperty.Create( Specification.FromProperty(pi), pi, Maybe.Nothing())) - .Memorize(); + .Memoize(); var specs = from pt in specProps select pt.Specification; var optionSpecs = specs .ThrowingValidate(SpecificationGuards.Lookup) .OfType() - .Memorize(); + .Memoize(); Func makeDefault = () => typeof(T).IsMutable() @@ -46,19 +46,19 @@ public static ParserResult Build( Func, ParserResult> notParsed = errs => new NotParsed(makeDefault().GetType().ToTypeInfo(), errs); - var argumentsList = arguments.Memorize(); + var argumentsList = arguments.Memoize(); Func> buildUp = () => { var tokenizerResult = tokenizer(argumentsList, optionSpecs); - var tokens = tokenizerResult.SucceededWith().Memorize(); + var tokens = tokenizerResult.SucceededWith().Memoize(); var partitions = TokenPartitioner.Partition( tokens, name => TypeLookup.FindTypeDescriptorAndSibling(name, optionSpecs, nameComparer)); - var optionsPartition = partitions.Item1.Memorize(); - var valuesPartition = partitions.Item2.Memorize(); - var errorsPartition = partitions.Item3.Memorize(); + var optionsPartition = partitions.Item1.Memoize(); + var valuesPartition = partitions.Item2.Memoize(); + var errorsPartition = partitions.Item3.Memoize(); var optionSpecPropsResult = OptionMapper.MapValues( @@ -80,7 +80,7 @@ public static ParserResult Build( .FromOptionSpecification()); var specPropsWithValue = - optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memorize(); + optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memoize(); var setPropertyErrors = new List(); @@ -104,7 +104,7 @@ public static ParserResult Build( .Concat(valueSpecPropsResult.SuccessfulMessages()) .Concat(validationErrors) .Concat(setPropertyErrors) - .Memorize(); + .Memoize(); var warnings = from e in allErrors where nonFatalErrors.Contains(e.Tag) select e; @@ -115,7 +115,7 @@ public static ParserResult Build( argumentsList.Any() ? arguments.Preprocess(PreprocessorGuards.Lookup(nameComparer, autoHelp, autoVersion)) : Enumerable.Empty() - ).Memorize(); + ).Memoize(); var result = argumentsList.Any() ? preprocessorErrors.Any() diff --git a/src/CommandLine/Core/OptionMapper.cs b/src/CommandLine/Core/OptionMapper.cs index 102646a9..18349b40 100644 --- a/src/CommandLine/Core/OptionMapper.cs +++ b/src/CommandLine/Core/OptionMapper.cs @@ -43,7 +43,7 @@ select Tuple.Create( ((OptionSpecification)pt.Specification).FromOptionSpecification())))) : Tuple.Create(pt, Maybe.Nothing()); } - ).Memorize(); + ).Memoize(); return Result.Succeed( sequencesAndErrors.Select(se => se.Item1), sequencesAndErrors.Select(se => se.Item2).OfType>().Select(se => se.Value)); diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index cc1d8c26..be38a6d0 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -17,16 +17,16 @@ Tuple>>, IEnumerable tokenComparer = ReferenceEqualityComparer.Default; - var tokenList = tokens.Memorize(); + var tokenList = tokens.Memoize(); var switches = new HashSet(Switch.Partition(tokenList, typeLookup), tokenComparer); var scalars = new HashSet(Scalar.Partition(tokenList, typeLookup), tokenComparer); var sequences = new HashSet(Sequence.Partition(tokenList, typeLookup), tokenComparer); var nonOptions = tokenList .Where(t => !switches.Contains(t)) .Where(t => !scalars.Contains(t)) - .Where(t => !sequences.Contains(t)).Memorize(); - var values = nonOptions.Where(v => v.IsValue()).Memorize(); - var errors = nonOptions.Except(values, (IEqualityComparer)ReferenceEqualityComparer.Default).Memorize(); + .Where(t => !sequences.Contains(t)).Memoize(); + var values = nonOptions.Where(v => v.IsValue()).Memoize(); + var errors = nonOptions.Except(values, (IEqualityComparer)ReferenceEqualityComparer.Default).Memoize(); return Tuple.Create( KeyValuePairHelper.ForSwitch(switches) diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index fb241579..9fd8863c 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -34,11 +34,11 @@ public static Result, Error> Tokenize( ? TokenizeLongName(arg, onError) : TokenizeShortName(arg, nameLookup) select token) - .Memorize(); + .Memoize(); - var normalized = normalize(tokens).Memorize(); + var normalized = normalize(tokens).Memoize(); - var unkTokens = (from t in normalized where t.IsName() && nameLookup(t.Text) == NameLookupResult.NoOptionFound select t).Memorize(); + var unkTokens = (from t in normalized where t.IsName() && nameLookup(t.Text) == NameLookupResult.NoOptionFound select t).Memoize(); return Result.Succeed(normalized.Where(x => !unkTokens.Contains(x)), errors.Concat(from t in unkTokens select new UnknownOptionError(t.Text))); } @@ -60,12 +60,12 @@ public static Result, Error> ExplodeOptionList( Result, Error> tokenizerResult, Func> optionSequenceWithSeparatorLookup) { - var tokens = tokenizerResult.SucceededWith().Memorize(); + var tokens = tokenizerResult.SucceededWith().Memoize(); var replaces = tokens.Select((t, i) => optionSequenceWithSeparatorLookup(t.Text) .MapValueOrDefault(sep => Tuple.Create(i + 1, sep), - Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0).Memorize(); + Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0).Memoize(); var exploded = tokens.Select((t, i) => replaces.FirstOrDefault(x => x.Item1 == i).ToMaybe() @@ -205,4 +205,4 @@ private static IEnumerable TokenizeLongName( } } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Infrastructure/Either.cs b/src/CommandLine/Infrastructure/CSharpx/Either.cs similarity index 91% rename from src/CommandLine/Infrastructure/Either.cs rename to src/CommandLine/Infrastructure/CSharpx/Either.cs index 71666dc2..f3c80585 100644 --- a/src/CommandLine/Infrastructure/Either.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Either.cs @@ -1,6 +1,5 @@ -//Use project level define(s) when referencing with Paket. -//#define CSX_EITHER_INTERNAL // Uncomment this to set visibility to internal. -//#define CSX_REM_MAYBE_FUNC // Uncomment this to remove dependency to Maybe.cs. +//#define CSX_EITHER_INTERNAL // Uncomment or define at build time to set accessibility to internal. +//#define CSX_REM_MAYBE_FUNC // Uncomment or define at build time to remove dependency to Maybe.cs. using System; @@ -133,8 +132,7 @@ public static Either Fail(string message) public static Either Bind(Either either, Func> func) { TRight right; - if (either.MatchRight(out right)) - { + if (either.MatchRight(out right)) { return func(right); } return Either.Left(either.GetLeft()); @@ -148,8 +146,7 @@ public static Either Bind(Either Map(Either either, Func func) { TRight right; - if (either.MatchRight(out right)) - { + if (either.MatchRight(out right)) { return Either.Right(func(right)); } return Either.Left(either.GetLeft()); @@ -164,8 +161,7 @@ public static Either Map(Either Bimap(Either either, Func mapLeft, Func mapRight) { TRight right; - if (either.MatchRight(out right)) - { + if (either.MatchRight(out right)) { return Either.Right(mapRight(right)); } return Either.Left(mapLeft(either.GetLeft())); @@ -196,9 +192,10 @@ public static Either SelectMany(this Eit public static TRight GetOrFail(Either either) { TRight value; - if (either.MatchRight(out value)) + if (either.MatchRight(out value)) { return value; - throw new ArgumentException("either", string.Format("The either value was Left {0}", either)); + } + throw new ArgumentException(nameof(either), string.Format("The either value was Left {0}", either)); } /// @@ -224,12 +221,10 @@ public static TRight GetRightOrDefault(Either eith /// public static Either Try(Func func) { - try - { + try { return new Right(func()); } - catch (Exception ex) - { + catch (Exception ex) { return new Left(ex); } } @@ -244,10 +239,9 @@ public static Either Cast(object obj) } #if !CSX_REM_MAYBE_FUNC - public static Either OfMaybe(Maybe maybe, TLeft left) + public static Either FromMaybe(Maybe maybe, TLeft left) { - if (maybe.Tag == MaybeType.Just) - { + if (maybe.Tag == MaybeType.Just) { return Either.Right(((Just)maybe).Value); } return Either.Left(left); @@ -269,8 +263,7 @@ static class EitherExtensions public static void Match(this Either either, Action ifLeft, Action ifRight) { TLeft left; - if (either.MatchLeft(out left)) - { + if (either.MatchLeft(out left)) { ifLeft(left); return; } @@ -279,7 +272,7 @@ public static void Match(this Either either, Actio #endregion /// - /// Equivalent to monadic operation. + /// Equivalent to monadic operation. /// Builds a value in case by default. /// public static Either ToEither(this TRight value) diff --git a/src/CommandLine/Infrastructure/EnumerableExtensions.cs b/src/CommandLine/Infrastructure/CSharpx/EnumerableExtensions.cs similarity index 75% rename from src/CommandLine/Infrastructure/EnumerableExtensions.cs rename to src/CommandLine/Infrastructure/CSharpx/EnumerableExtensions.cs index fa9a7951..b668eb46 100644 --- a/src/CommandLine/Infrastructure/EnumerableExtensions.cs +++ b/src/CommandLine/Infrastructure/CSharpx/EnumerableExtensions.cs @@ -1,8 +1,6 @@ -//Use project level define(s) when referencing with Paket. -//#define CSX_ENUM_INTERNAL // Uncomment this to set visibility to internal. -//#define CSX_ENUM_REM_STD_FUNC // Uncomment this to remove standard functions. -//#define CSX_REM_MAYBE_FUNC // Uncomment this to remove dependency to Maybe.cs. -//#define CSX_REM_EXTRA_FUNC // Uncomment this to extra functions. +//#define CSX_ENUM_INTERNAL // Uncomment or define at build time to set accessibility to internal. +//#define CSX_REM_MAYBE_FUNC // Uncomment or define at build time to remove dependency to Maybe.cs. +//#define CSX_REM_CRYPTORAND // Uncomment or define at build time to remove dependency to CryptoRandom.cs. using System; using System.Collections; @@ -20,15 +18,41 @@ namespace CSharpx #endif static class EnumerableExtensions { -#if !CSX_ENUM_REM_STD_FUNC +#if !CSX_REM_MAYBE_FUNC + /// + /// Safe function that returns Just(first element) or None. + /// + public static Maybe TryHead(this IEnumerable source) + { + using (var e = source.GetEnumerator()) { + return e.MoveNext() + ? Maybe.Just(e.Current) + : Maybe.Nothing(); + } + } + + /// + /// Turns an empty sequence to Nothing, otherwise Just(sequence). + /// + public static Maybe> ToMaybe(this IEnumerable source) + { + using (var e = source.GetEnumerator()) { + return e.MoveNext() + ? Maybe.Just(source) + : Maybe.Nothing>(); + } + } +#endif + private static IEnumerable AssertCountImpl(IEnumerable source, int count, Func errorSelector) { var collection = source as ICollection; // Optimization for collections if (collection != null) { - if (collection.Count != count) + if (collection.Count != count) { throw errorSelector(collection.Count.CompareTo(count), count); + } return source; } @@ -42,14 +66,12 @@ private static IEnumerable ExpectingCountYieldingImpl(IEnumera foreach (var element in source) { iterations++; - if (iterations > count) - { + if (iterations > count) { throw errorSelector(1, count); } yield return element; } - if (iterations != count) - { + if (iterations != count) { throw errorSelector(-1, count); } } @@ -60,13 +82,13 @@ private static IEnumerable ExpectingCountYieldingImpl(IEnumera /// public static IEnumerable Cartesian(this IEnumerable first, IEnumerable second, Func resultSelector) { - if (first == null) throw new ArgumentNullException("first"); - if (second == null) throw new ArgumentNullException("second"); - if (resultSelector == null) throw new ArgumentNullException("resultSelector"); + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return from item1 in first - from item2 in second // TODO buffer to avoid multiple enumerations - select resultSelector(item1, item2); + return from element1 in first + from element2 in second // TODO buffer to avoid multiple enumerations + select resultSelector(element1, element2); } /// @@ -74,7 +96,7 @@ from item2 in second // TODO buffer to avoid multiple enumerations /// public static IEnumerable Prepend(this IEnumerable source, TSource value) { - if (source == null) throw new ArgumentNullException("source"); + if (source == null) throw new ArgumentNullException(nameof(source)); return LinqEnumerable.Concat(LinqEnumerable.Repeat(value, 1), source); } @@ -84,7 +106,7 @@ public static IEnumerable Prepend(this IEnumerable so /// public static IEnumerable Concat(this T head, IEnumerable tail) { - if (tail == null) throw new ArgumentNullException("tail"); + if (tail == null) throw new ArgumentNullException(nameof(tail)); return tail.Prepend(head); } @@ -94,7 +116,7 @@ public static IEnumerable Concat(this T head, IEnumerable tail) /// public static IEnumerable Concat(this IEnumerable head, T tail) { - if (head == null) throw new ArgumentNullException("head"); + if (head == null) throw new ArgumentNullException(nameof(head)); return LinqEnumerable.Concat(head, LinqEnumerable.Repeat(tail, 1)); } @@ -105,9 +127,9 @@ public static IEnumerable Concat(this IEnumerable head, T tail) /// The type of the elements of the sequence public static IEnumerable Exclude(this IEnumerable sequence, int startIndex, int count) { - if (sequence == null) throw new ArgumentNullException("sequence"); - if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); - if (count < 0) throw new ArgumentOutOfRangeException("count"); + if (sequence == null) throw new ArgumentNullException(nameof(sequence)); + if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); return ExcludeImpl(sequence, startIndex, count); } @@ -119,14 +141,17 @@ private static IEnumerable ExcludeImpl(IEnumerable sequence, int startI using (var iter = sequence.GetEnumerator()) { // yield the first part of the sequence - while (iter.MoveNext() && ++index < startIndex) + while (iter.MoveNext() && ++index < startIndex) { yield return iter.Current; - // skip the next part (up to count items) - while (++index < endIndex && iter.MoveNext()) + } + // skip the next part (up to count elements) + while (++index < endIndex && iter.MoveNext()) { continue; + } // yield the remainder of the sequence - while (iter.MoveNext()) + while (iter.MoveNext()) { yield return iter.Current; + } } } @@ -147,7 +172,7 @@ public static IEnumerable> Index(this IEnume /// public static IEnumerable> Index(this IEnumerable source, int startIndex) { - return source.Select((item, index) => new KeyValuePair(startIndex + index, item)); + return source.Select((element, index) => new KeyValuePair(startIndex + index, element)); } /// @@ -192,7 +217,7 @@ static TResult FoldImpl(IEnumerable source, int count, Func folder3, Func folder4) { - if (source == null) throw new ArgumentNullException("source"); + if (source == null) throw new ArgumentNullException(nameof(source)); if (count == 1 && folder1 == null || count == 2 && folder2 == null || count == 3 && folder3 == null @@ -202,11 +227,12 @@ static TResult FoldImpl(IEnumerable source, int count, } var elements = new T[count]; - foreach (var e in AssertCountImpl(source.Index(), count, OnFolderSourceSizeErrorSelector)) + foreach (var e in AssertCountImpl( + source.Index(), count, OnFolderSourceSizeErrorSelector)) { elements[e.Key] = e.Value; + } - switch (count) - { + switch (count) { case 1: return folder1(elements[0]); case 2: return folder2(elements[0], elements[1]); case 3: return folder3(elements[0], elements[1], elements[2]); @@ -220,22 +246,20 @@ static TResult FoldImpl(IEnumerable source, int count, static Exception OnFolderSourceSizeError(int cmp, int count) { var message = cmp < 0 - ? "Sequence contains too few elements when exactly {0} {1} expected." - : "Sequence contains too many elements when exactly {0} {1} expected."; + ? "Sequence contains too few elements when exactly {0} {1} expected" + : "Sequence contains too many elements when exactly {0} {1} expected"; return new Exception(string.Format(message, count.ToString("N0"), count == 1 ? "was" : "were")); } /// /// Immediately executes the given action on each element in the source sequence. /// - /// The type of the elements in the sequence public static void ForEach(this IEnumerable source, Action action) { - if (source == null) throw new ArgumentNullException("source"); - if (action == null) throw new ArgumentNullException("action"); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (action == null) throw new ArgumentNullException(nameof(action)); - foreach (var element in source) - { + foreach (var element in source) { action(element); } } @@ -248,8 +272,8 @@ public static void ForEach(this IEnumerable source, Action action) /// public static IEnumerable Pairwise(this IEnumerable source, Func resultSelector) { - if (source == null) throw new ArgumentNullException("source"); - if (resultSelector == null) throw new ArgumentNullException("resultSelector"); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); return PairwiseImpl(source, resultSelector); } @@ -259,14 +283,13 @@ private static IEnumerable PairwiseImpl(this IEnumera Debug.Assert(source != null); Debug.Assert(resultSelector != null); - using (var e = source.GetEnumerator()) - { - if (!e.MoveNext()) + using (var e = source.GetEnumerator()) { + if (!e.MoveNext()) { yield break; + } var previous = e.Current; - while (e.MoveNext()) - { + while (e.MoveNext()) { yield return resultSelector(previous, e.Current); previous = e.Current; } @@ -288,7 +311,7 @@ public static string ToDelimitedString(this IEnumerable source /// public static string ToDelimitedString(this IEnumerable source, string delimiter) { - if (source == null) throw new ArgumentNullException("source"); + if (source == null) throw new ArgumentNullException(nameof(source)); return ToDelimitedStringImpl(source, delimiter, (sb, e) => sb.Append(e)); } @@ -302,57 +325,28 @@ static string ToDelimitedStringImpl(IEnumerable source, string delimiter, var sb = new StringBuilder(); var i = 0; - foreach (var value in source) - { + foreach (var value in source) { if (i++ > 0) sb.Append(delimiter); append(sb, value); } return sb.ToString(); } -#endif -#if !CSX_REM_MAYBE_FUNC - /// - /// Safe function that returns Just(first element) or None. - /// - public static Maybe TryHead(this IEnumerable source) - { - using (var e = source.GetEnumerator()) - { - return e.MoveNext() - ? Maybe.Just(e.Current) - : Maybe.Nothing(); - } - } - - /// - /// Turns an empty sequence to Nothing, otherwise Just(sequence). - /// - public static Maybe> ToMaybe(this IEnumerable source) - { - using (var e = source.GetEnumerator()) - { - return e.MoveNext() - ? Maybe.Just(source) - : Maybe.Nothing>(); - } - } -#endif - -#if !CSX_REM_EXTRA_FUNC /// /// Return everything except first element and throws exception if empty. /// public static IEnumerable Tail(this IEnumerable source) { - using (var e = source.GetEnumerator()) - { - if (e.MoveNext()) - while (e.MoveNext()) + using (var e = source.GetEnumerator()) { + if (e.MoveNext()) { + while (e.MoveNext()) { yield return e.Current; - else - throw new ArgumentException("Source sequence cannot be empty.", "source"); + } + } + else { + throw new ArgumentException("Source sequence cannot be empty", nameof(source)); + } } } @@ -363,16 +357,18 @@ public static IEnumerable TailNoFail(this IEnumerable source) { using (var e = source.GetEnumerator()) { - if (e.MoveNext()) - while (e.MoveNext()) + if (e.MoveNext()) { + while (e.MoveNext()) { yield return e.Current; + } + } } } /// /// Captures current state of a sequence. /// - public static IEnumerable Memorize(this IEnumerable source) + public static IEnumerable Memoize(this IEnumerable source) { return source.GetType().IsArray ? source : source.ToArray(); } @@ -382,8 +378,7 @@ public static IEnumerable Memorize(this IEnumerable source) /// public static IEnumerable Materialize(this IEnumerable source) { - if (source is MaterializedEnumerable || source.GetType().IsArray) - { + if (source is MaterializedEnumerable || source.GetType().IsArray) { return source; } return new MaterializedEnumerable(source); @@ -408,6 +403,61 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } + + /// + /// Selects a random element. + /// + public static T Choice(this IEnumerable source) + { +#if CSX_REM_CRYPTORAND + var index = new Random().Next(source.Count() - 1); +#else + var index = new CryptoRandom().Next(source.Count() - 1); #endif + return source.ElementAt(index); + } + + /// + /// Takes an element and a sequence and `intersperses' that element between its elements. + /// + public static IEnumerable Intersperse(this IEnumerable source, T element) + { + if (element == null) throw new ArgumentNullException(nameof(element)); + + var count = source.Count(); + var last = count - 1; + for (var i = 0; i < count; i++) { + yield return source.ElementAt(i); + if (i != last) { + yield return element; + } + } + } + + /// + /// Flattens a sequence by one level. + /// + public static IEnumerable FlattenOnce(this IEnumerable> source) + { + foreach (var element in source) { + foreach (var subelement in element) { + yield return subelement; + } + } + } + + /// + /// Reduces a sequence of strings to a sequence of parts, splitted by space, + /// of each original string. + /// + public static IEnumerable FlattenOnce(this IEnumerable source) + { + foreach (var element in source) { + var parts = element.Split(); + foreach (var part in parts) { + yield return part; + } + } + } } } \ No newline at end of file diff --git a/src/CommandLine/Infrastructure/Maybe.cs b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs similarity index 92% rename from src/CommandLine/Infrastructure/Maybe.cs rename to src/CommandLine/Infrastructure/CSharpx/Maybe.cs index 63451865..1dacf4e8 100644 --- a/src/CommandLine/Infrastructure/Maybe.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs @@ -1,6 +1,5 @@ -//Use project level define(s) when referencing with Paket. -//#define CSX_MAYBE_INTERNAL // Uncomment this to set visibility to internal. -//#define CSX_REM_EITHER_FUNC // Uncomment this to remove dependency to Either.cs. +//#define CSX_MAYBE_INTERNAL // Uncomment or define at build time set accessibility to internal. +//#define CSX_REM_EITHER_FUNC // Uncomment or define at build time to remove dependency to Either.cs. using System; using System.Collections.Generic; @@ -165,8 +164,7 @@ public static Maybe> Merge(Maybe first, Maybe seco { T1 value1; T2 value2; - if (first.MatchJust(out value1) && second.MatchJust(out value2)) - { + if (first.MatchJust(out value1) && second.MatchJust(out value2)) { return Maybe.Just(Tuple.Create(value1, value2)); } return Maybe.Nothing>(); @@ -176,10 +174,9 @@ public static Maybe> Merge(Maybe first, Maybe seco /// /// Maps Either Right value to Maybe Just, otherwise Maybe Nothing. /// - public static Maybe OfEither(Either either) + public static Maybe FromEither(Either either) { - if (either.Tag == EitherType.Right) - { + if (either.Tag == EitherType.Right) { return Maybe.Just(((Right)either).Value); } return Maybe.Nothing(); @@ -202,8 +199,7 @@ static class MaybeExtensions public static void Match(this Maybe maybe, Action ifJust, Action ifNothing) { T value; - if (maybe.MatchJust(out value)) - { + if (maybe.MatchJust(out value)) { ifJust(value); return; } @@ -217,8 +213,7 @@ public static void Match(this Maybe> maybe, Action { T1 value1; T2 value2; - if (maybe.MatchJust(out value1, out value2)) - { + if (maybe.MatchJust(out value1, out value2)) { ifJust(value1, value2); return; } @@ -231,8 +226,7 @@ public static void Match(this Maybe> maybe, Action public static bool MatchJust(this Maybe> maybe, out T1 value1, out T2 value2) { Tuple value; - if (maybe.MatchJust(out value)) - { + if (maybe.MatchJust(out value)) { value1 = value.Item1; value2 = value.Item2; return true; @@ -296,13 +290,12 @@ public static Maybe SelectMany( #region Do Semantic /// - /// If contans a value executes an delegate over it. + /// If contains a value executes an delegate over it. /// public static void Do(this Maybe maybe, Action action) { T value; - if (maybe.MatchJust(out value)) - { + if (maybe.MatchJust(out value)) { action(value); } } @@ -314,8 +307,7 @@ public static void Do(this Maybe> maybe, Action ac { T1 value1; T2 value2; - if (maybe.MatchJust(out value1, out value2)) - { + if (maybe.MatchJust(out value1, out value2)) { action(value1, value2); } } @@ -343,8 +335,7 @@ public static bool IsNothing(this Maybe maybe) public static T FromJust(this Maybe maybe) { T value; - if (maybe.MatchJust(out value)) - { + if (maybe.MatchJust(out value)) { return value; } return default(T); @@ -356,8 +347,7 @@ public static T FromJust(this Maybe maybe) public static T FromJustOrFail(this Maybe maybe, Exception exceptionToThrow = null) { T value; - if (maybe.MatchJust(out value)) - { + if (maybe.MatchJust(out value)) { return value; } throw exceptionToThrow ?? new ArgumentException("Value empty."); @@ -387,8 +377,7 @@ public static T2 MapValueOrDefault(this Maybe maybe, Func fu public static IEnumerable ToEnumerable(this Maybe maybe) { T value; - if (maybe.MatchJust(out value)) - { + if (maybe.MatchJust(out value)) { return Enumerable.Empty().Concat(new[] { value }); } return Enumerable.Empty(); diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 59c4d3b7..e06ebf56 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -136,7 +136,7 @@ public static string FormatCommandLine(this Parser parser, T options, Action< }) where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault) select info) - .Memorize(); + .Memoize(); var allOptSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Option) let o = (OptionSpecification)info.Specification From 5cc4a01c04df342a6933bf2dd51ab315619ed311 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 15:49:10 +0100 Subject: [PATCH 090/198] Changed no more needed name mangling --- ...xtensions`1.cs => EnumerableExtensions.cs} | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) rename src/CommandLine/Infrastructure/{EnumerableExtensions`1.cs => EnumerableExtensions.cs} (96%) diff --git a/src/CommandLine/Infrastructure/EnumerableExtensions`1.cs b/src/CommandLine/Infrastructure/EnumerableExtensions.cs similarity index 96% rename from src/CommandLine/Infrastructure/EnumerableExtensions`1.cs rename to src/CommandLine/Infrastructure/EnumerableExtensions.cs index 056fa152..bdada5de 100644 --- a/src/CommandLine/Infrastructure/EnumerableExtensions`1.cs +++ b/src/CommandLine/Infrastructure/EnumerableExtensions.cs @@ -1,68 +1,68 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace CommandLine.Infrastructure -{ - static class EnumerableExtensions - { - public static int IndexOf(this IEnumerable source, Func predicate) - { - var index = -1; - foreach (var item in source) - { - index++; - if (predicate(item)) - { - break; - } - } - return index; - } - - public static object ToUntypedArray(this IEnumerable value, Type type) - { - var array = Array.CreateInstance(type, value.Count()); - value.ToArray().CopyTo(array, 0); - return array; - } - - public static bool Empty(this IEnumerable source) - { - return !source.Any(); - } - - /// - /// Breaks a collection into groups of a specified size. - /// - /// A collection of . - /// The number of items each group shall contain. - /// An enumeration of T[]. - /// An incomplete group at the end of the source collection will be silently dropped. - public static IEnumerable Group(this IEnumerable source, int groupSize) - { - if (groupSize < 1) - { - throw new ArgumentOutOfRangeException(nameof(groupSize)); - } - - T[] group = new T[groupSize]; - int groupIndex = 0; - - foreach (var item in source) - { - group[groupIndex++] = item; - - if (groupIndex == groupSize) - { - yield return group; - - group = new T[groupSize]; - groupIndex = 0; - } - } - } - } +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CommandLine.Infrastructure +{ + static class EnumerableExtensions + { + public static int IndexOf(this IEnumerable source, Func predicate) + { + var index = -1; + foreach (var item in source) + { + index++; + if (predicate(item)) + { + break; + } + } + return index; + } + + public static object ToUntypedArray(this IEnumerable value, Type type) + { + var array = Array.CreateInstance(type, value.Count()); + value.ToArray().CopyTo(array, 0); + return array; + } + + public static bool Empty(this IEnumerable source) + { + return !source.Any(); + } + + /// + /// Breaks a collection into groups of a specified size. + /// + /// A collection of . + /// The number of items each group shall contain. + /// An enumeration of T[]. + /// An incomplete group at the end of the source collection will be silently dropped. + public static IEnumerable Group(this IEnumerable source, int groupSize) + { + if (groupSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(groupSize)); + } + + T[] group = new T[groupSize]; + int groupIndex = 0; + + foreach (var item in source) + { + group[groupIndex++] = item; + + if (groupIndex == groupSize) + { + yield return group; + + group = new T[groupSize]; + groupIndex = 0; + } + } + } + } } \ No newline at end of file From 3760d03f943871e6845bf1fc571c6987554b8e74 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Mon, 3 Feb 2020 10:31:24 +0100 Subject: [PATCH 091/198] Removed useless testing code (#568) --- .../Fakes/Scalar_String_Mutable.cs | 11 -------- tests/CommandLine.Tests/ParserProperties.cs | 26 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 tests/CommandLine.Tests/Fakes/Scalar_String_Mutable.cs delete mode 100644 tests/CommandLine.Tests/ParserProperties.cs diff --git a/tests/CommandLine.Tests/Fakes/Scalar_String_Mutable.cs b/tests/CommandLine.Tests/Fakes/Scalar_String_Mutable.cs deleted file mode 100644 index abfc7dfb..00000000 --- a/tests/CommandLine.Tests/Fakes/Scalar_String_Mutable.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - - -namespace CommandLine.Tests.Properties.Fakes -{ - class Scalar_String_Mutable - { - [Option] - public string StringValue { get; set; } - } -} diff --git a/tests/CommandLine.Tests/ParserProperties.cs b/tests/CommandLine.Tests/ParserProperties.cs deleted file mode 100644 index ae55d18c..00000000 --- a/tests/CommandLine.Tests/ParserProperties.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using CommandLine.Tests.Properties.Fakes; -using FluentAssertions; -using FsCheck; -using Xunit; - -namespace CommandLine.Tests.Properties -{ - public class ParserProperties - { - private static readonly Parser Sut = new Parser(); - - //[Fact] - public void Parsing_a_string_returns_original_string() - { - Prop.ForAll>( - x => - { - var value = x.Get; - var result = Sut.ParseArguments(new[] { "--stringvalue", value }); - ((Parsed)result).Value.StringValue.Should().BeEquivalentTo(value); - }).QuickCheckThrowOnFailure(); - } - } -} From 2e1e6e42b53a6a224bee8a72987587777ab23f8a Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 07:51:11 +0100 Subject: [PATCH 092/198] Fixed typo in test methods --- .../CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs index 4b78aa13..29f666b5 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs @@ -9,9 +9,8 @@ namespace CommandLine.Tests.Unit.Text { public class HelpTextAutoBuildFix { - [Fact] - public void HelpText_wit_AdditionalNewLineAfterOption_true_should_have_newline() + public void HelpText_with_AdditionalNewLineAfterOption_true_should_have_newline() { // Fixture setup // Exercize system @@ -36,7 +35,7 @@ public void HelpText_wit_AdditionalNewLineAfterOption_true_should_have_newline() } [Fact] - public void HelpText_wit_AdditionalNewLineAfterOption_false_should_not_have_newline() + public void HelpText_with_AdditionalNewLineAfterOption_false_should_not_have_newline() { // Fixture setup // Exercize system @@ -55,7 +54,7 @@ public void HelpText_wit_AdditionalNewLineAfterOption_false_should_not_have_newl // Teardown } [Fact] - public void HelpText_wit_by_default_should_include_help_version_option() + public void HelpText_with_by_default_should_include_help_version_option() { // Fixture setup // Exercize system @@ -73,7 +72,7 @@ public void HelpText_wit_by_default_should_include_help_version_option() } [Fact] - public void HelpText_wit_AutoHelp_false_should_hide_help_option() + public void HelpText_with_AutoHelp_false_should_hide_help_option() { // Fixture setup // Exercize system From 37dafb6513d9f1e1d9507a4ee62926cb5f8d915b Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:03:17 +0100 Subject: [PATCH 093/198] Temporarily removed a file to cheat wrong git tracking --- .../Fakes/OPtions_HelpText_Ordering.cs | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs diff --git a/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs deleted file mode 100644 index 3896ab67..00000000 --- a/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace CommandLine.Tests.Fakes -{ - - [Verb("verb1")] - class Options_HelpText_Ordering_Verb1 - { - [Option('a', "alpha", Required = true)] - public string alphaOption { get; set; } - - [Option('b', "alpha2", Required = true)] - public string alphaTwoOption { get; set; } - - [Option('d', "charlie", Required = false)] - public string deltaOption { get; set; } - - [Option('c', "bravo", Required = false)] - public string charlieOption { get; set; } - - [Option('f', "foxtrot", Required = false)] - public string foxOption { get; set; } - - [Option('e', "echo", Required = false)] - public string echoOption { get; set; } - - [Value(0)] public string someExtraOption { get; set; } - } - - [Verb("verb2")] - class Options_HelpText_Ordering_Verb2 - { - [Option('a', "alpha", Required = true)] - public string alphaOption { get; set; } - - [Option('b', "alpha2", Required = true)] - public string alphaTwoOption { get; set; } - - [Option('c', "bravo", Required = false)] - public string charlieOption { get; set; } - - [Option('d', "charlie", Required = false)] - public string deltaOption { get; set; } - } -} From 8d1f04270a3a5fd5fc5af8082a18ad8fa9630261 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:05:15 +0100 Subject: [PATCH 094/198] File added again without wrong case in filename --- .../Fakes/Options_HelpText_Ordering.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs diff --git a/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs new file mode 100644 index 00000000..3896ab67 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs @@ -0,0 +1,48 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace CommandLine.Tests.Fakes +{ + + [Verb("verb1")] + class Options_HelpText_Ordering_Verb1 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('f', "foxtrot", Required = false)] + public string foxOption { get; set; } + + [Option('e', "echo", Required = false)] + public string echoOption { get; set; } + + [Value(0)] public string someExtraOption { get; set; } + } + + [Verb("verb2")] + class Options_HelpText_Ordering_Verb2 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + } +} From 8ced1a3c99850a369069139419af6f50c9ded1a3 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:27:45 +0100 Subject: [PATCH 095/198] Using statements cleanup --- tests/CommandLine.Tests/Fakes/Hidden_Option.cs | 8 +------- .../Fakes/Options_HelpText_Ordering.cs | 3 --- ...With_Sequence_Having_Both_Min_And_Max_Equal.cs | 3 --- tests/CommandLine.Tests/Fakes/ResourceFakes.cs | 8 +------- tests/CommandLine.Tests/ParserProperties.cs | 3 +-- .../CommandLine.Tests/Unit/BaseAttributeTests.cs | 1 - .../Unit/Core/InstanceBuilderTests.cs | 2 -- .../Unit/Core/InstanceChooserTests.cs | 4 ++-- .../Unit/Core/KeyValuePairHelperTests.cs | 2 +- .../Unit/Core/NameLookupTests.cs | 10 +++------- .../Unit/Core/OptionMapperTests.cs | 8 ++------ .../Unit/Core/ReflectionExtensions.cs | 4 ++-- tests/CommandLine.Tests/Unit/Core/ScalarTests.cs | 4 ++-- .../CommandLine.Tests/Unit/Core/SequenceTests.cs | 4 ++-- tests/CommandLine.Tests/Unit/Core/SwitchTests.cs | 4 ++-- .../Unit/Core/TextWrapperTests.cs | 5 +++-- .../Unit/Core/TokenPartitionerTests.cs | 7 ++----- tests/CommandLine.Tests/Unit/Core/TokenTests.cs | 2 +- .../CommandLine.Tests/Unit/Core/TokenizerTests.cs | 15 +++++---------- .../Unit/Core/TypeConverterTests.cs | 6 +++--- tests/CommandLine.Tests/Unit/Issue104Tests.cs | 5 ++--- tests/CommandLine.Tests/Unit/Issue389Tests.cs | 6 ++---- tests/CommandLine.Tests/Unit/Issue418Tests.cs | 8 ++------ tests/CommandLine.Tests/Unit/Issue482Tests.cs | 7 ------- .../Unit/ParserResultExtensionsTests.cs | 4 +--- .../CommandLine.Tests/Unit/ParserSettingsTests.cs | 9 ++------- tests/CommandLine.Tests/Unit/ParserTests.cs | 5 ++--- .../Unit/StringBuilderExtensionsTests.cs | 5 +---- .../Unit/Text/HelpTextAutoBuildFix.cs | 4 ++-- .../CommandLine.Tests/Unit/Text/HelpTextTests.cs | 7 ++----- .../Unit/UnParserExtensionsTests.cs | 4 +--- 31 files changed, 50 insertions(+), 117 deletions(-) diff --git a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs index 1917ecab..b9a87cd3 100644 --- a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs +++ b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CommandLine.Tests.Fakes +namespace CommandLine.Tests.Fakes { public class Hidden_Option { diff --git a/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs index 3896ab67..27c7fa6b 100644 --- a/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs +++ b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs @@ -1,8 +1,5 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System.Collections.Generic; -using System.Runtime.CompilerServices; - namespace CommandLine.Tests.Fakes { diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs b/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs index d24a8036..21671d76 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs @@ -1,9 +1,6 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace CommandLine.Tests.Fakes { diff --git a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs index 917d51bf..f7b46bac 100644 --- a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs +++ b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CommandLine.Tests.Fakes +namespace CommandLine.Tests.Fakes { public static class StaticResource { diff --git a/tests/CommandLine.Tests/ParserProperties.cs b/tests/CommandLine.Tests/ParserProperties.cs index ae55d18c..ceb8b524 100644 --- a/tests/CommandLine.Tests/ParserProperties.cs +++ b/tests/CommandLine.Tests/ParserProperties.cs @@ -1,9 +1,8 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Tests.Properties.Fakes; using FluentAssertions; using FsCheck; -using Xunit; +using CommandLine.Tests.Properties.Fakes; namespace CommandLine.Tests.Properties { diff --git a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs index 72cf81b4..3c2bfbbd 100644 --- a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Xunit; namespace CommandLine.Tests.Unit diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index bf885f76..c2cbb77a 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -13,9 +13,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; - using Xunit; -using System.Reflection; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs index 52e12cae..c9dae5fb 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs @@ -4,10 +4,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Xunit; +using FluentAssertions; using CommandLine.Core; using CommandLine.Tests.Fakes; -using FluentAssertions; -using Xunit; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs b/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs index 77ba39cf..3f1fb293 100644 --- a/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; -using CommandLine.Core; using Xunit; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index 785b1fe5..f009c49e 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -1,15 +1,11 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; - -using CSharpx; - -using FluentAssertions; - using System; using System.Collections.Generic; - using Xunit; +using FluentAssertions; +using CommandLine.Core; +using CSharpx; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 75ddade7..b2219683 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -4,18 +4,14 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; - #if PLATFORM_DOTNET using System.Reflection; #endif -using CommandLine.Core; -using CommandLine.Tests.Fakes; - using Xunit; - using CSharpx; - using RailwaySharp.ErrorHandling; +using CommandLine.Core; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs b/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs index 2fdb951d..035b9e03 100644 --- a/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs +++ b/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs @@ -1,9 +1,9 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using Xunit; +using FluentAssertions; using CommandLine.Core; using CommandLine.Tests.Fakes; -using FluentAssertions; -using Xunit; namespace CommandLine.Tests.Unit.Infrastructure { diff --git a/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs b/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs index 2984b77e..9b1028f0 100644 --- a/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs @@ -1,10 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System.Linq; -using CommandLine.Core; -using CSharpx; using Xunit; using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs index 36e3e262..b26575b8 100644 --- a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs @@ -1,10 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System.Linq; -using CommandLine.Core; -using CSharpx; using Xunit; using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs b/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs index a4163990..82edb635 100644 --- a/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs @@ -1,10 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System.Linq; -using CommandLine.Core; -using CSharpx; using Xunit; using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs index 1db7497e..8de39610 100644 --- a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs @@ -1,8 +1,9 @@ using System; using System.Linq; -using CommandLine.Text; -using FluentAssertions; using Xunit; +using FluentAssertions; +using CSharpx; +using CommandLine.Text; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index 11a9ffdd..20006e59 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -1,14 +1,11 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; - -using CSharpx; - using System; using System.Collections.Generic; using System.Linq; - using Xunit; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TokenTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenTests.cs index 991171db..1290f9b3 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenTests.cs @@ -1,7 +1,7 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; using Xunit; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index 3bd95891..61c7f184 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -1,19 +1,14 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; -using CommandLine.Infrastructure; - -using CSharpx; - -using FluentAssertions; - -using RailwaySharp.ErrorHandling; - using System; using System.Collections.Generic; using System.Linq; - using Xunit; +using FluentAssertions; +using CSharpx; +using RailwaySharp.ErrorHandling; +using CommandLine.Core; +using CommandLine.Infrastructure; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index 90fbc3f5..d9f3988c 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Globalization; -using CommandLine.Core; -using CSharpx; -using FluentAssertions; using Xunit; +using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Issue104Tests.cs b/tests/CommandLine.Tests/Unit/Issue104Tests.cs index fcbefedd..ca35689e 100644 --- a/tests/CommandLine.Tests/Unit/Issue104Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue104Tests.cs @@ -1,9 +1,8 @@ using System.Linq; +using Xunit; +using FluentAssertions; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; -using Xunit; -using Xunit.Abstractions; //Issue #104 //When outputting HelpText, the code will correctly list valid values for enum type options. However, if the option is a nullable enum type, then it will not list the valid values. diff --git a/tests/CommandLine.Tests/Unit/Issue389Tests.cs b/tests/CommandLine.Tests/Unit/Issue389Tests.cs index ef4f6169..5c81e6e0 100644 --- a/tests/CommandLine.Tests/Unit/Issue389Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue389Tests.cs @@ -1,9 +1,7 @@ -using CommandLine.Text; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using Xunit; +using CommandLine.Text; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/Issue418Tests.cs b/tests/CommandLine.Tests/Unit/Issue418Tests.cs index 9c8678bd..cac1e9f1 100644 --- a/tests/CommandLine.Tests/Unit/Issue418Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue418Tests.cs @@ -1,12 +1,8 @@ -using CommandLine.Text; using System; using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using CommandLine.Tests.Fakes; -using Xunit; using FluentAssertions; +using Xunit; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/Issue482Tests.cs b/tests/CommandLine.Tests/Unit/Issue482Tests.cs index 61bc1f25..44359358 100644 --- a/tests/CommandLine.Tests/Unit/Issue482Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue482Tests.cs @@ -1,16 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; -using CommandLine.Core; using System.Linq; -using System.Reflection; -using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; using Xunit; -using System.Text; -using Xunit.Sdk; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs index 97755940..178483ed 100644 --- a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs @@ -1,11 +1,9 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; -using System.Collections.Generic; using System.Linq; -using CommandLine.Tests.Fakes; using Xunit; using FluentAssertions; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs b/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs index 86691c8f..c8da9063 100644 --- a/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FluentAssertions; +using System.IO; using Xunit; +using FluentAssertions; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index af5a6eec..fc2ae150 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -2,13 +2,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using CommandLine.Tests.Fakes; -using FluentAssertions; using Xunit; +using FluentAssertions; using CommandLine.Text; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs b/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs index 8fe73a60..fdbd6526 100644 --- a/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Text; -using System.Threading.Tasks; using Xunit; using FluentAssertions; using CommandLine.Infrastructure; diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs index 29f666b5..d777c8f9 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs @@ -1,9 +1,9 @@ using System; using System.Linq; +using Xunit; +using FluentAssertions; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; -using Xunit; namespace CommandLine.Tests.Unit.Text { diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index dde64dc8..9811f7be 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -6,16 +6,13 @@ using System.Linq; using System.Reflection; using System.Text; - +using Xunit; +using FluentAssertions; using CommandLine.Core; using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; - -using Xunit; - namespace CommandLine.Tests.Unit.Text { public class HelpTextTests : IDisposable diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 90cb8929..9397b17b 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; -using CommandLine.Tests.Fakes; using Xunit; using FluentAssertions; -using Microsoft.FSharp.Core; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { From 54bf73d23ded4d19c950a892ccbc3f7b261499a4 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:31:26 +0100 Subject: [PATCH 096/198] Fixed indentation --- tests/CommandLine.Tests/Unit/VerbAttributeTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs b/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs index 4dc1dd30..386e3015 100644 --- a/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs @@ -4,7 +4,7 @@ namespace CommandLine.Tests { //Test localization of VerbAttribute - public class VerbAttributeTests + public class VerbAttributeTests { [Theory] [InlineData("", null, "")] @@ -22,6 +22,7 @@ public static void VerbHelpText(string helpText, Type resourceType, string expec Assert.Equal(expected, verbAttribute.HelpText); } + [Theory] [InlineData("HelpText", typeof(Fakes.NonStaticResource_WithNonStaticProperty))] [InlineData("WriteOnlyText", typeof(Fakes.NonStaticResource))] From 120edbe851a6115c26df31127f7afed699212337 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:35:42 +0100 Subject: [PATCH 097/198] Refactored tests with FluentAssertions --- tests/CommandLine.Tests/Unit/Issue482Tests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Issue482Tests.cs b/tests/CommandLine.Tests/Unit/Issue482Tests.cs index 44359358..9d2ea971 100644 --- a/tests/CommandLine.Tests/Unit/Issue482Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue482Tests.cs @@ -4,6 +4,7 @@ using CommandLine.Tests.Fakes; using CommandLine.Text; using Xunit; +using FluentAssertions; namespace CommandLine.Tests.Unit { @@ -40,11 +41,11 @@ public void AutoBuild_without_ordering() "--version Display version information.", "value pos. 0" }; - Assert.Equal(expected.Count, helps.Count); + expected.Count.Should().Be(helps.Count); int i = 0; foreach (var expect in expected) { - Assert.Equal(expect.Trim(), helps[i].Trim()); + expect.Trim().Should().Be(helps[i].Trim()); i++; } @@ -88,11 +89,11 @@ public void AutoBuild_with_ordering() "--version Display version information.", "value pos. 0" }; - Assert.Equal(expected.Count, helps.Count); + expected.Count.Should().Be(helps.Count); int i = 0; foreach (var expect in expected) { - Assert.Equal(expect.Trim(), helps[i].Trim()); + expect.Trim().Should().Be(helps[i].Trim()); i++; } @@ -171,11 +172,11 @@ public void AutoBuild_with_ordering_on_shortName() "--version Display version information.", "value pos. 0" }; - Assert.Equal(expected.Count, helps.Count); + expected.Count.Should().Be(helps.Count); int i = 0; foreach (var expect in expected) { - Assert.Equal(expect.Trim(), helps[i].Trim()); + expect.Trim().Should().Be(helps[i].Trim()); i++; } } From f9b056f9bb1df86b04a6b87d931497892a7ccb2b Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 8 Jan 2020 23:03:52 +0200 Subject: [PATCH 098/198] Fix Appveyor error --- src/CommandLine/Infrastructure/CSharpx/Either.cs | 4 ++-- tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CommandLine/Infrastructure/CSharpx/Either.cs b/src/CommandLine/Infrastructure/CSharpx/Either.cs index f3c80585..3d985948 100644 --- a/src/CommandLine/Infrastructure/CSharpx/Either.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Either.cs @@ -272,7 +272,7 @@ public static void Match(this Either either, Actio #endregion /// - /// Equivalent to monadic operation. + /// Equivalent to monadic operation. /// Builds a value in case by default. /// public static Either ToEither(this TRight value) @@ -312,4 +312,4 @@ public static bool IsRight(this Either either) return either.Tag == EitherType.Right; } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 9397b17b..3edb8f20 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -6,7 +6,10 @@ using Xunit; using FluentAssertions; using CommandLine.Tests.Fakes; - +#if !SKIP_FSHARP +using Microsoft.FSharp.Core; +#endif + namespace CommandLine.Tests.Unit { public class UnParserExtensionsTests From f0f526140fdda67444c1c2a4fbf15bc97253ca49 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 8 Jan 2020 23:03:52 +0200 Subject: [PATCH 099/198] Fix Appveyor error --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 73b580d2..827ded7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.{build} +version: 2.7.83-beta-{build} image: Visual Studio 2019 From 6ca8435cab169c029bf61a429556cd7cc3ba0017 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Fri, 10 Jan 2020 04:34:34 +0100 Subject: [PATCH 100/198] moh-hassan added as author and year updated in copyright (#563) --- src/CommandLine/CommandLine.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index b05cbb04..a3798437 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -11,13 +11,13 @@ true CommandLineParser CommandLineParser.FSharp - gsscoder;nemec;ericnewton76 + gsscoder;nemec;ericnewton76;moh-hassan Command Line Parser Library $(VersionSuffix) 0.0.0 Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. - Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors + Copyright (c) 2005 - 2020 Giacomo Stelluti Scala & Contributors License.md CommandLine20.png https://github.com/commandlineparser/commandline From 79dd8adba31b185c3e8166488fedbc9c8dcf4757 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Fri, 10 Jan 2020 05:02:32 +0100 Subject: [PATCH 101/198] Upgraded RailwaySharp from Version 1.1.0 (#562) --- src/CommandLine/CommandLine.csproj | 2 +- src/CommandLine/Core/InstanceBuilder.cs | 6 +- src/CommandLine/Core/Tokenizer.cs | 2 +- .../Infrastructure/ErrorHandling.cs | 257 +++++++++--------- .../Infrastructure/ResultExtensions.cs | 33 --- .../Unit/Core/TokenizerTests.cs | 2 +- 6 files changed, 133 insertions(+), 169 deletions(-) delete mode 100644 src/CommandLine/Infrastructure/ResultExtensions.cs diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index a3798437..4dac1db1 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -4,7 +4,7 @@ CommandLine Library netstandard2.0;net40;net45;net461 - $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND + $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND;ERRH_ADD_MAYBE_METHODS $(DefineConstants);SKIP_FSHARP true ..\..\CommandLine.snk diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 4aff4080..0ae564b5 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -98,10 +98,10 @@ public static ParserResult Build( var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens)); var allErrors = - tokenizerResult.SuccessfulMessages() + tokenizerResult.SuccessMessages() .Concat(missingValueErrors) - .Concat(optionSpecPropsResult.SuccessfulMessages()) - .Concat(valueSpecPropsResult.SuccessfulMessages()) + .Concat(optionSpecPropsResult.SuccessMessages()) + .Concat(valueSpecPropsResult.SuccessMessages()) .Concat(validationErrors) .Concat(setPropertyErrors) .Memoize(); diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index 9fd8863c..ba6f1ef5 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -74,7 +74,7 @@ public static Result, Error> ExplodeOptionList( var flattened = exploded.SelectMany(x => x); - return Result.Succeed(flattened, tokenizerResult.SuccessfulMessages()); + return Result.Succeed(flattened, tokenizerResult.SuccessMessages()); } public static IEnumerable Normalize( diff --git a/src/CommandLine/Infrastructure/ErrorHandling.cs b/src/CommandLine/Infrastructure/ErrorHandling.cs index 142e1461..8aee4bac 100644 --- a/src/CommandLine/Infrastructure/ErrorHandling.cs +++ b/src/CommandLine/Infrastructure/ErrorHandling.cs @@ -1,62 +1,17 @@ //Use project level define(s) when referencing with Paket. -//#define ERRH_INTERNAL // Uncomment this to set visibility to internal. -//#define ERRH_DISABLE_INLINE_METHODS // Uncomment this to enable method inlining when compiling for >= NET 4.5. -//#define ERRH_BUILTIN_TYPES // Uncomment this to use built-in Unit type, instead of extenral identical CSharpx.Unit. +//#define ERRH_INTERNAL // Uncomment or define at build time to set accessibility to internal. +//#define ERRH_ENABLE_INLINE_METHODS // Uncomment or define at build time to enable method inlining when compiling for >= NET 4.5. +//#define ERRH_ADD_MAYBE_METHODS // Uncomment or define at build time to add methods that use Maybe type using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -#if !ERRH_BUILTIN_TYPES +#if ERRH_ADD_MAYBE_METHODS using CSharpx; #endif namespace RailwaySharp.ErrorHandling { - #region Unit Type -#if ERRH_BUILTIN_TYPES -#if !ERRH_INTERNAL - public -#endif - struct Unit : IEquatable - { - private static readonly Unit @default = new Unit(); - - public bool Equals(Unit other) - { - return true; - } - - public override bool Equals(object obj) - { - return obj is Unit; - } - - public override int GetHashCode() - { - return 0; - } - - public override string ToString() - { - return "()"; - } - - public static bool operator ==(Unit first, Unit second) - { - return true; - } - - public static bool operator !=(Unit first, Unit second) - { - return false; - } - - public static Unit Default { get { return @default; } } - } -#endif - #endregion - #if !ERRH_INTERNAL public #endif @@ -76,29 +31,28 @@ enum ResultType #endif abstract class Result { - private readonly ResultType tag; + private readonly ResultType _tag; protected Result(ResultType tag) { - this.tag = tag; + _tag = tag; } public ResultType Tag { - get { return tag; } + get { return _tag; } } public override string ToString() { - switch (Tag) - { - case ResultType.Ok: + switch (Tag) { + default: var ok = (Ok)this; return string.Format( "OK: {0} - {1}", ok.Success, string.Join(Environment.NewLine, ok.Messages.Select(v => v.ToString()))); - default: + case ResultType.Bad: var bad = (Bad)this; return string.Format( "Error: {0}", @@ -117,22 +71,24 @@ public override string ToString() #endif sealed class Ok : Result { - private readonly Tuple> value; + private readonly Tuple> _value; public Ok(TSuccess success, IEnumerable messages) : base(ResultType.Ok) { - this.value = Tuple.Create(success, messages); + if (messages == null) throw new ArgumentNullException(nameof(messages)); + + _value = Tuple.Create(success, messages); } public TSuccess Success { - get { return value.Item1; } + get { return _value.Item1; } } public IEnumerable Messages { - get { return value.Item2; } + get { return _value.Item2; } } } @@ -146,17 +102,19 @@ public IEnumerable Messages #endif sealed class Bad : Result { - private readonly IEnumerable messages; + private readonly IEnumerable _messages; public Bad(IEnumerable messages) : base(ResultType.Bad) { - this.messages = messages; + if (messages == null) throw new ArgumentException(nameof(messages)); + + _messages = messages; } public IEnumerable Messages { - get { return messages; } + get { return _messages; } } } @@ -170,6 +128,8 @@ static class Result /// public static Result FailWith(IEnumerable messages) { + if (messages == null) throw new ArgumentException(nameof(messages)); + return new Bad(messages); } @@ -178,6 +138,8 @@ public static Result FailWith(IEnumerabl /// public static Result FailWith(TMessage message) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Bad(new[] { message }); } @@ -194,6 +156,8 @@ public static Result Succeed(TSuccess va /// public static Result Succeed(TSuccess value, TMessage message) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Ok(value, new[] { message }); } @@ -202,6 +166,8 @@ public static Result Succeed(TSuccess va /// public static Result Succeed(TSuccess value, IEnumerable messages) { + if (messages == null) throw new ArgumentException(nameof(messages)); + return new Ok(value, messages); } @@ -210,13 +176,13 @@ public static Result Succeed(TSuccess va /// public static Result Try(Func func) { - try - { + if (func == null) throw new ArgumentException(nameof(func)); + + try { return new Ok( func(), Enumerable.Empty()); } - catch (Exception ex) - { + catch (Exception ex) { return new Bad( new[] { ex }); } @@ -231,7 +197,7 @@ static class Trial /// /// Wraps a value in a Success. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Ok(TSuccess value) @@ -242,7 +208,7 @@ public static Result Ok(TSuccess value) /// /// Wraps a value in a Success. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Pass(TSuccess value) @@ -253,29 +219,33 @@ public static Result Pass(TSuccess value /// /// Wraps a value in a Success and adds a message. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Warn(TMessage message, TSuccess value) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Ok(value, new[] { message }); } /// /// Wraps a message in a Failure. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Fail(TMessage message) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Bad(new[] { message }); } /// /// Returns true if the result was not successful. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool Failed(Result result) @@ -286,7 +256,7 @@ public static bool Failed(Result result) /// /// Takes a Result and maps it with successFunc if it is a Success otherwise it maps it with failureFunc. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TResult Either( @@ -294,9 +264,11 @@ public static TResult Either( Func, TResult> failureFunc, Result trialResult) { + if (successFunc == null) throw new ArgumentException(nameof(successFunc)); + if (failureFunc == null) throw new ArgumentException(nameof(failureFunc)); + var ok = trialResult as Ok; - if (ok != null) - { + if (ok != null) { return successFunc(ok.Success, ok.Messages); } var bad = (Bad)trialResult; @@ -307,7 +279,7 @@ public static TResult Either( /// If the given result is a Success the wrapped value will be returned. /// Otherwise the function throws an exception with Failure message of the result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TSuccess ReturnOrFail(Result result) @@ -325,13 +297,15 @@ public static TSuccess ReturnOrFail(Result /// Appends the given messages with the messages in the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result MergeMessages( IEnumerable messages, Result result) { + if (messages == null) throw new ArgumentException(nameof(messages)); + Func, Result> successFunc = (succ, msgs) => new Ok( @@ -347,13 +321,15 @@ public static Result MergeMessages( /// If the result is a Success it executes the given function on the value. /// Otherwise the exisiting failure is propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Bind( Func> func, Result result) { + if (func == null) throw new ArgumentException(nameof(func)); + Func, Result> successFunc = (succ, msgs) => MergeMessages(msgs, func(succ)); @@ -366,7 +342,7 @@ public static Result Bind( /// /// Flattens a nested result given the Failure types are equal. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Flatten( @@ -374,46 +350,44 @@ public static Result Flatten( { return Bind(x => x, result); } - + /// /// If the wrapped function is a success and the given result is a success the function is applied on the value. /// Otherwise the exisiting error messages are propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Apply( Result, TMessage> wrappedFunction, Result result) { - if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Ok) - { + if (wrappedFunction == null) throw new ArgumentException(nameof(wrappedFunction)); + + if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Ok) { var ok1 = (Ok, TMessage>)wrappedFunction; var ok2 = (Ok)result; return new Ok( ok1.Success(ok2.Success), ok1.Messages.Concat(ok2.Messages)); } - if (wrappedFunction.Tag == ResultType.Bad && result.Tag == ResultType.Ok) - { + if (wrappedFunction.Tag == ResultType.Bad && result.Tag == ResultType.Ok) { return new Bad(((Bad)result).Messages); } - if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Bad) - { + if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Bad) { return new Bad( ((Bad)result).Messages); } var bad1 = (Bad, TMessage>)wrappedFunction; var bad2 = (Bad)result; - return new Bad(bad1.Messages.Concat(bad2.Messages)); } /// /// Lifts a function into a Result container and applies it on the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Lift( @@ -426,22 +400,22 @@ public static Result Lift( /// /// Promote a function to a monad/applicative, scanning the monadic/applicative arguments from left to right. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Lift2( Func> func, - Result a, - Result b) + Result first, + Result second) { - return Apply(Lift(func, a), b); + return Apply(Lift(func, first), second); } /// /// Collects a sequence of Results and accumulates their values. /// If the sequence contains an error the error will be propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result, TMessage> Collect( @@ -449,21 +423,18 @@ public static Result, TMessage> Collect, Result, TMessage>, Result, TMessage>>( - null, - (result, next) => - { - if (result.Tag == ResultType.Ok && next.Tag == ResultType.Ok) - { + null, + (result, next) => { + if (result.Tag == ResultType.Ok && next.Tag == ResultType.Ok) { var ok1 = (Ok, TMessage>)result; var ok2 = (Ok)next; return new Ok, TMessage>( - Enumerable.Empty().Concat(new[] { ok2.Success }).Concat(ok1.Success), + Enumerable.Empty().Concat(new [] { ok2.Success }).Concat(ok1.Success), ok1.Messages.Concat(ok2.Messages)); } if ((result.Tag == ResultType.Ok && next.Tag == ResultType.Bad) - || (result.Tag == ResultType.Bad && next.Tag == ResultType.Ok)) - { + || (result.Tag == ResultType.Bad && next.Tag == ResultType.Ok)) { var m1 = result.Tag == ResultType.Ok ? ((Ok, TMessage>)result).Messages : ((Bad)next).Messages; @@ -472,8 +443,9 @@ public static Result, TMessage> Collect)next).Messages; return new Bad, TMessage>(m1.Concat(m2)); } + var bad1 = (Bad, TMessage>)result; - var bad2 = (Bad)next; + var bad2 = (Bad)next; return new Bad, TMessage>(bad1.Messages.Concat(bad2.Messages)); }, x => x)); } @@ -490,19 +462,22 @@ static class ResultExtensions /// /// Allows pattern matching on Results. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static void Match(this Result result, Action> ifSuccess, Action> ifFailure) { + if (ifSuccess == null) throw new ArgumentException(nameof(ifSuccess)); + if (ifFailure == null) throw new ArgumentException(nameof(ifFailure)); + var ok = result as Ok; - if (ok != null) - { + if (ok != null) { ifSuccess(ok.Success, ok.Messages); return; } + var bad = (Bad)result; ifFailure(bad.Messages); } @@ -510,26 +485,20 @@ public static void Match(this Result res /// /// Allows pattern matching on Results. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TResult Either(this Result result, Func, TResult> ifSuccess, Func, TResult> ifFailure) { - var ok = result as Ok; - if (ok != null) - { - return ifSuccess(ok.Success, ok.Messages); - } - var bad = (Bad)result; - return ifFailure(bad.Messages); + return Trial.Either(ifSuccess, ifFailure, result); } /// /// Lifts a Func into a Result and applies it on the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Map(this Result result, @@ -542,7 +511,7 @@ public static Result Map(this Re /// Collects a sequence of Results and accumulates their values. /// If the sequence contains an error the error will be propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result, TMessage> Collect( @@ -555,18 +524,16 @@ public static Result, TMessage> Collect -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result, TMessage> Flatten(this Result>, TMessage> result) { - if (result.Tag == ResultType.Ok) - { + if (result.Tag == ResultType.Ok) { var ok = (Ok>, TMessage>)result; var values = ok.Success; var result1 = Collect(values); - if (result1.Tag == ResultType.Ok) - { + if (result1.Tag == ResultType.Ok) { var ok1 = (Ok, TMessage>)result1; return new Ok, TMessage>(ok1.Success, ok1.Messages); } @@ -581,7 +548,7 @@ public static Result, TMessage> Flatten -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result SelectMany(this Result result, @@ -595,7 +562,7 @@ public static Result SelectMany( /// If the result of the Func is a Success it maps it using the given Func. /// Otherwise the exisiting failure is propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result SelectMany( @@ -603,6 +570,9 @@ public static Result SelectMany> func, Func mapperFunc) { + if (func == null) throw new ArgumentException(nameof(func)); + if (mapperFunc == null) throw new ArgumentException(nameof(mapperFunc)); + Func> curriedMapper = suc => val => mapperFunc(suc, val); Func< Result, @@ -616,7 +586,7 @@ public static Result SelectMany /// Lifts a Func into a Result and applies it on the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Select(this Result result, @@ -628,13 +598,12 @@ public static Result Select(this /// /// Returns the error messages or fails if the result was a success. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static IEnumerable FailedWith(this Result result) { - if (result.Tag == ResultType.Ok) - { + if (result.Tag == ResultType.Ok) { var ok = (Ok)result; throw new Exception( string.Format("Result was a success: {0} - {1}", @@ -648,13 +617,12 @@ public static IEnumerable FailedWith(this Result /// Returns the result or fails if the result was an error. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TSuccess SucceededWith(this Result result) { - if (result.Tag == ResultType.Ok) - { + if (result.Tag == ResultType.Ok) { var ok = (Ok)result; return ok.Success; } @@ -663,5 +631,34 @@ public static TSuccess SucceededWith(this Result m.ToString())))); } + + /// + /// Returns messages in case of success, otherwise an empty sequence. + /// + public static IEnumerable SuccessMessages(this Result result) + { + if (result.Tag == ResultType.Ok) { + var ok = (Ok)result; + return ok.Messages; + } + return Enumerable.Empty(); + } + +#if ERRH_ADD_MAYBE_METHODS +#if ERRH_ENABLE_INLINE_METHODS + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + /// + /// Builds a Maybe type instance from a Result one. + /// + public static Maybe ToMaybe(this Result result) + { + if (result.Tag == ResultType.Ok) { + var ok = (Ok)result; + return Maybe.Just(ok.Success); + } + return Maybe.Nothing(); + } +#endif } } \ No newline at end of file diff --git a/src/CommandLine/Infrastructure/ResultExtensions.cs b/src/CommandLine/Infrastructure/ResultExtensions.cs deleted file mode 100644 index bdc2a480..00000000 --- a/src/CommandLine/Infrastructure/ResultExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System.Collections.Generic; -using System.Linq; - -using CSharpx; -using RailwaySharp.ErrorHandling; - -namespace CommandLine.Infrastructure -{ - static class ResultExtensions - { - public static IEnumerable SuccessfulMessages(this Result result) - { - if (result.Tag == ResultType.Ok) - { - var ok = (Ok)result; - return ok.Messages; - } - return Enumerable.Empty(); - } - - public static Maybe ToMaybe(this Result result) - { - if (result.Tag == ResultType.Ok) - { - var ok = (Ok)result; - return Maybe.Just(ok.Success); - } - return Maybe.Nothing(); - } - } -} \ No newline at end of file diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index 61c7f184..32d79b4f 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -116,7 +116,7 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() var result = Tokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound, token => token); - var tokens = result.SuccessfulMessages(); + var tokens = result.SuccessMessages(); Assert.NotNull(tokens); Assert.Equal(2, tokens.Count()); From 8ee695e3648c28376f5fa875b3da160db3a8d509 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 2 Feb 2020 20:02:04 +0200 Subject: [PATCH 102/198] Fix option groups (#575) * Add MyGet package provider to consume the daily builds. (#566) * Options groups take in account default value * Do not allow options groups and exclusive set names to be used together * Multiple group errors are shown together * MissingGroupOptionError compare option names Co-authored-by: Mohamed Hassan --- appveyor.yml | 18 ++++-- .../Core/SpecificationPropertyRules.cs | 28 ++++++++- src/CommandLine/Error.cs | 38 ++++++++++++- .../Fakes/Options_With_Multiple_Groups.cs | 20 +++++++ ...s_With_OptionGroup_MutuallyExclusiveSet.cs | 14 +++++ ...With_OptionGroup_WithOptionDefaultValue.cs | 14 +++++ .../Unit/Core/InstanceBuilderTests.cs | 57 +++++++++++++++++++ 7 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs diff --git a/appveyor.yml b/appveyor.yml index 827ded7f..e9af5e38 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,12 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.83-beta-{build} +version: 2.7.84-beta-{build} image: Visual Studio 2019 clone_depth: 1 pull_requests: - do_not_increment_build_number: true + do_not_increment_build_number: false init: - ps: | @@ -15,8 +15,9 @@ init: if ($env:APPVEYOR_REPO_TAG -eq "true") { $ver = $env:APPVEYOR_REPO_TAG_NAME if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) } - Update-AppveyorBuild -Version $ver - } + Update-AppveyorBuild -Version $ver + } + - ps: Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow environment: matrix: @@ -57,3 +58,12 @@ deploy: artifact: 'NuGetPackages' on: APPVEYOR_REPO_TAG: true + +#myget +- provider: NuGet + server: https://www.myget.org/F/commandlineparser/api/v2/package + api_key: + secure: ltHh/DsAk+Y7qbJwzUO4+i1U+7uGTLVYXTdW0+Rk2z7jqj5DDNNlih9J8K7bU4bH + artifact: 'NuGetPackages' + symbol_server: https://www.myget.org/F/commandlineparser/symbols/api/v2/package + diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 9122ee3a..5dc1a406 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -18,12 +18,35 @@ public static IEnumerable, IEnumerable, IEnumerable> EnforceMutuallyExclusiveSetAndGroupAreNotUsedTogether() + { + return specProps => + { + var options = + from sp in specProps + where sp.Specification.IsOption() + let o = (OptionSpecification)sp.Specification + where o.SetName.Length > 0 + where o.Group.Length > 0 + select o; + + if (options.Any()) + { + return from o in options + select new GroupOptionAmbiguityError(new NameInfo(o.ShortName, o.LongName)); + } + + return Enumerable.Empty(); + }; + } + private static Func, IEnumerable> EnforceGroup() { return specProps => @@ -36,14 +59,15 @@ where o.Group.Length > 0 select new { Option = o, - Value = sp.Value + Value = sp.Value, + DefaultValue = sp.Specification.DefaultValue }; var groups = from o in optionsValues group o by o.Option.Group into g select g; - var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing())); + var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing() && g.DefaultValue.IsNothing())); if (errorGroups.Any()) { diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index e54dbf6a..b6e8a605 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace CommandLine { @@ -74,8 +75,11 @@ public enum ErrorType /// /// Value of type. /// - MissingGroupOptionError - + MissingGroupOptionError, + /// + /// Value of type. + /// + GroupOptionAmbiguityError } /// @@ -532,7 +536,7 @@ internal InvalidAttributeConfigurationError() } } - public sealed class MissingGroupOptionError : Error + public sealed class MissingGroupOptionError : Error, IEquatable, IEquatable { public const string ErrorMessage = "At least one option in a group must have value."; @@ -555,5 +559,33 @@ public IEnumerable Names { get { return names; } } + + public new bool Equals(Error obj) + { + var other = obj as MissingGroupOptionError; + if (other != null) + { + return Equals(other); + } + + return base.Equals(obj); + } + + public bool Equals(MissingGroupOptionError other) + { + if (other == null) + { + return false; + } + + return Group.Equals(other.Group) && Names.SequenceEqual(other.Names); + } + } + + public sealed class GroupOptionAmbiguityError : NamedError + { + internal GroupOptionAmbiguityError(NameInfo option) + : base(ErrorType.GroupOptionAmbiguityError, option) + { } } } diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs b/tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs new file mode 100644 index 00000000..8f2d21ab --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs @@ -0,0 +1,20 @@ +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Multiple_Groups + { + [Option('v', "version")] + public string Version { get; set; } + + [Option("option11", Group = "err-group")] + public string Option11 { get; set; } + + [Option("option12", Group = "err-group")] + public string Option12 { get; set; } + + [Option("option21", Group = "err-group2")] + public string Option21 { get; set; } + + [Option("option22", Group = "err-group2")] + public string Option22 { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs new file mode 100644 index 00000000..52ead41c --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup_MutuallyExclusiveSet + { + [Option(HelpText = "Define a string value here.", Group = "test", SetName = "setname", Default = "qwerty123")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "test", SetName = "setname")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs new file mode 100644 index 00000000..9ae3a59e --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup_WithOptionDefaultValue + { + [Option(HelpText = "Define a string value here.", Required = true, Group = "test", Default = "qwerty123")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "test")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index c2cbb77a..643878fd 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1110,6 +1110,34 @@ public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError() ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); } + [Fact] + public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionErrors() + { + // Fixture setup + var optionNames1 = new List + { + new NameInfo("", "option11"), + new NameInfo("", "option12") + }; + var optionNames2 = new List + { + new NameInfo("", "option21"), + new NameInfo("", "option22") + }; + var expectedResult = new[] + { + new MissingGroupOptionError("err-group", optionNames1), + new MissingGroupOptionError("err-group2", optionNames2) + }; + + // Exercize system + var result = InvokeBuild( + new[] { "-v 10.42" }); + + // Verify outcome + ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); + } + [Theory] [InlineData("-v", "10.5", "--option1", "test1", "--option2", "test2")] [InlineData("-v", "10.5", "--option1", "test1")] @@ -1164,6 +1192,35 @@ public void Options_In_Group_Ignore_Option_Group_If_Option_Group_Name_Empty() errors.Should().BeEquivalentTo(expectedResult); } + [Fact] + public void Options_In_Group_Use_Option_Default_Value_When_Available() + { + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + } + + [Fact] + public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() + { + var expectedResult = new[] + { + new GroupOptionAmbiguityError(new NameInfo("", "stringvalue")), + new GroupOptionAmbiguityError(new NameInfo("s", "shortandlong")) + }; + + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + var errors = ((NotParsed)result).Errors; + + errors.Should().BeEquivalentTo(expectedResult); + } + private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From 9f47f1376feb4ced0417e0651e00451357e3d937 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sun, 2 Feb 2020 20:09:08 +0200 Subject: [PATCH 103/198] Update Appveyor --- appveyor.yml | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e9af5e38..ff6d4476 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,12 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.84-beta-{build} +version: 2.8.0-beta-{build} image: Visual Studio 2019 clone_depth: 1 pull_requests: - do_not_increment_build_number: false + do_not_increment_build_number: true init: - ps: | @@ -15,9 +15,23 @@ init: if ($env:APPVEYOR_REPO_TAG -eq "true") { $ver = $env:APPVEYOR_REPO_TAG_NAME if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) } - Update-AppveyorBuild -Version $ver - } - - ps: Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow + $env:PACKAGE_VERSION = $ver + } else { + $env:PACKAGE_VERSION = $env:APPVEYOR_BUILD_VERSION + } + - ps: | + Write-Host "PACKAGE_VERSION:$env:PACKAGE_VERSION | APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow + Write-Host "APPVEYOR_REPO_TAG_NAME:$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Yellow + +skip_commits: + files: + - docs/* + - art/* + - '**/*.md' + #- .travis.yml + - .gitignore + - .editorconfig + message: /updated readme.*|update readme.*s|update docs.*|update version.*|update changelog.*/ environment: matrix: @@ -25,13 +39,13 @@ environment: - BUILD_TARGET: fsharp build_script: -- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% +- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% test_script: - cmd: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=%BUILD_TARGET% after_test: -- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% +- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% artifacts: - path: 'src/CommandLine/bin/Release/*.nupkg' @@ -59,11 +73,3 @@ deploy: on: APPVEYOR_REPO_TAG: true -#myget -- provider: NuGet - server: https://www.myget.org/F/commandlineparser/api/v2/package - api_key: - secure: ltHh/DsAk+Y7qbJwzUO4+i1U+7uGTLVYXTdW0+Rk2z7jqj5DDNNlih9J8K7bU4bH - artifact: 'NuGetPackages' - symbol_server: https://www.myget.org/F/commandlineparser/symbols/api/v2/package - From 551a0be20357e9d4f8501ac2c0bb4c837b9c01ef Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Feb 2020 09:06:46 +0200 Subject: [PATCH 104/198] Fix exception of groupOptionAmbiguityError --- src/CommandLine/Error.cs | 6 +++++- src/CommandLine/Text/SentenceBuilder.cs | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index b6e8a605..ed2b0dce 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -584,8 +584,12 @@ public bool Equals(MissingGroupOptionError other) public sealed class GroupOptionAmbiguityError : NamedError { + public NameInfo Option; + internal GroupOptionAmbiguityError(NameInfo option) : base(ErrorType.GroupOptionAmbiguityError, option) - { } + { + Option = option; + } } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index d58bf9c7..cdf8f7d2 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -158,6 +158,9 @@ public override Func FormatError "' (", string.Join(", ", missingGroupOptionError.Names.Select(n => n.NameText)), ") is required."); + case ErrorType.GroupOptionAmbiguityError: + var groupOptionAmbiguityError = (GroupOptionAmbiguityError)error; + return "Both SetName and Group are not allowed in option: (".JoinTo(groupOptionAmbiguityError.Option.NameText, ")"); } throw new InvalidOperationException(); }; From 31d2c91b7604bbdea44ba9415a21b024459d818b Mon Sep 17 00:00:00 2001 From: Mathis Rech Date: Sat, 28 Dec 2019 21:16:38 +0100 Subject: [PATCH 105/198] Add default verb support --- demo/ReadText.Demo/Options.cs | 4 +- src/CommandLine/Core/InstanceChooser.cs | 46 ++++++++++++++++++--- src/CommandLine/Core/Verb.cs | 19 +++++++-- src/CommandLine/Error.cs | 19 ++++++++- src/CommandLine/Text/SentenceBuilder.cs | 3 ++ src/CommandLine/VerbAttribute.cs | 17 ++++++-- tests/CommandLine.Tests/Fakes/Verb_Fakes.cs | 23 ++++++++++- tests/CommandLine.Tests/Unit/ParserTests.cs | 45 ++++++++++++++++++++ 8 files changed, 160 insertions(+), 16 deletions(-) diff --git a/demo/ReadText.Demo/Options.cs b/demo/ReadText.Demo/Options.cs index ed4db350..3b14014a 100644 --- a/demo/ReadText.Demo/Options.cs +++ b/demo/ReadText.Demo/Options.cs @@ -27,7 +27,7 @@ interface IOptions string FileName { get; set; } } - [Verb("head", HelpText = "Displays first lines of a file.")] + [Verb("head", true, HelpText = "Displays first lines of a file.")] class HeadOptions : IOptions { public uint? Lines { get; set; } @@ -62,4 +62,4 @@ class TailOptions : IOptions public string FileName { get; set; } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 86917233..f3ab9b99 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -23,6 +23,15 @@ public static ParserResult Choose( bool autoVersion, IEnumerable nonFatalErrors) { + var verbs = Verb.SelectFromTypes(types); + var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); + + int defaultVerbCount = defaultVerbs.Count(); + if (defaultVerbCount > 1) + return MakeNotParsed(types, new MultipleDefaultVerbsError()); + + var defaultVerb = defaultVerbCount == 1 ? defaultVerbs.First() : null; + Func> choose = () => { var firstArg = arguments.First(); @@ -31,25 +40,52 @@ public static ParserResult Choose( nameComparer.Equals(command, firstArg) || nameComparer.Equals(string.Concat("--", command), firstArg); - var verbs = Verb.SelectFromTypes(types); - return (autoHelp && preprocCompare("help")) ? MakeNotParsed(types, MakeHelpVerbRequestedError(verbs, arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); }; return arguments.Any() ? choose() - : MakeNotParsed(types, new NoVerbSelectedError()); + : (defaultVerbCount == 1 + ? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors) + : MakeNotParsed(types, new NoVerbSelectedError())); + } + + private static ParserResult MatchDefaultVerb( + Func, IEnumerable, Result, Error>> tokenizer, + IEnumerable> verbs, + Tuple defaultVerb, + IEnumerable arguments, + StringComparer nameComparer, + bool ignoreValueCase, + CultureInfo parsingCulture, + bool autoHelp, + bool autoVersion, + IEnumerable nonFatalErrors) + { + return !(defaultVerb is null) + ? InstanceBuilder.Build( + Maybe.Just>(() => defaultVerb.Item2.AutoDefault()), + tokenizer, + arguments, + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + nonFatalErrors) + : MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First())); } private static ParserResult MatchVerb( Func, IEnumerable, Result, Error>> tokenizer, IEnumerable> verbs, + Tuple defaultVerb, IEnumerable arguments, StringComparer nameComparer, bool ignoreValueCase, @@ -71,7 +107,7 @@ private static ParserResult MatchVerb( autoHelp, autoVersion, nonFatalErrors) - : MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First())); + : MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); } private static HelpVerbRequestedError MakeHelpVerbRequestedError( diff --git a/src/CommandLine/Core/Verb.cs b/src/CommandLine/Core/Verb.cs index 2fb6674d..3a7f12a3 100644 --- a/src/CommandLine/Core/Verb.cs +++ b/src/CommandLine/Core/Verb.cs @@ -12,12 +12,17 @@ sealed class Verb private readonly string name; private readonly string helpText; private readonly bool hidden; + private readonly bool isDefault; - public Verb(string name, string helpText, bool hidden = false) + public Verb(string name, string helpText, bool hidden = false, bool isDefault = false) { - this.name = name ?? throw new ArgumentNullException(nameof(name)); + if ( string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + this.name = name; + this.helpText = helpText ?? throw new ArgumentNullException(nameof(helpText)); this.hidden = hidden; + this.isDefault = isDefault; } public string Name @@ -35,12 +40,18 @@ public bool Hidden get { return hidden; } } + public bool IsDefault + { + get => isDefault; + } + public static Verb FromAttribute(VerbAttribute attribute) { return new Verb( attribute.Name, attribute.HelpText, - attribute.Hidden + attribute.Hidden, + attribute.IsDefault ); } @@ -54,4 +65,4 @@ select Tuple.Create( type); } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index ed2b0dce..21c2fdcd 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -79,7 +79,12 @@ public enum ErrorType /// /// Value of type. /// - GroupOptionAmbiguityError + GroupOptionAmbiguityError, + /// + /// Value of type. + /// + MultipleDefaultVerbsError + } /// @@ -592,4 +597,16 @@ internal GroupOptionAmbiguityError(NameInfo option) Option = option; } } + + /// + /// Models an error generated when multiple default verbs are defined. + /// + public sealed class MultipleDefaultVerbsError : Error + { + public const string ErrorMessage = "More than one default verb is not allowed."; + + internal MultipleDefaultVerbsError() + : base(ErrorType.MultipleDefaultVerbsError) + { } + } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index cdf8f7d2..c8537542 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -161,6 +161,9 @@ public override Func FormatError case ErrorType.GroupOptionAmbiguityError: var groupOptionAmbiguityError = (GroupOptionAmbiguityError)error; return "Both SetName and Group are not allowed in option: (".JoinTo(groupOptionAmbiguityError.Option.NameText, ")"); + case ErrorType.MultipleDefaultVerbsError: + return MultipleDefaultVerbsError.ErrorMessage; + } throw new InvalidOperationException(); }; diff --git a/src/CommandLine/VerbAttribute.cs b/src/CommandLine/VerbAttribute.cs index 5515bd20..57318b3a 100644 --- a/src/CommandLine/VerbAttribute.cs +++ b/src/CommandLine/VerbAttribute.cs @@ -12,6 +12,7 @@ namespace CommandLine public class VerbAttribute : Attribute { private readonly string name; + private readonly bool isDefault; private Infrastructure.LocalizableAttributeProperty helpText; private Type resourceType; @@ -19,12 +20,14 @@ public class VerbAttribute : Attribute /// Initializes a new instance of the class. /// /// The long name of the verb command. - /// Thrown if is null, empty or whitespace. - public VerbAttribute(string name) + /// Whether the verb is the default verb. + /// Thrown if is null, empty or whitespace and is false. + public VerbAttribute(string name, bool isDefault = false) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name"); - this.name = name; + this.name = name ; + this.isDefault = isDefault; helpText = new Infrastructure.LocalizableAttributeProperty(nameof(HelpText)); resourceType = null; } @@ -62,5 +65,13 @@ public Type ResourceType get => resourceType; set => resourceType =helpText.ResourceType = value; } + + /// + /// Gets whether this verb is the default verb. + /// + public bool IsDefault + { + get => isDefault; + } } } diff --git a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs index 133c65b5..83426bd7 100644 --- a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs +++ b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs @@ -85,4 +85,25 @@ class Verb_With_Option_And_Value_Of_String_Type [Value(0)] public string PosValue { get; set; } } -} \ No newline at end of file + + [Verb("default1", true)] + class Default_Verb_One + { + [Option('t', "test-one")] + public bool TestValueOne { get; set; } + } + + [Verb("default2", true)] + class Default_Verb_Two + { + [Option('t', "test-two")] + public bool TestValueTwo { get; set; } + } + + [Verb(null, true)] + class Default_Verb_With_Empty_Name + { + [Option('t', "test")] + public bool TestValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index fc2ae150..fe8c4d65 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -824,5 +824,50 @@ public void Blank_lines_are_inserted_between_verbs() // Teardown } + + [Fact] + public void Parse_default_verb_implicit() + { + var parser = Parser.Default; + parser.ParseArguments(new[] { "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => + { + Assert.True(args.TestValueOne); + }); + } + + [Fact] + public void Parse_default_verb_explicit() + { + var parser = Parser.Default; + parser.ParseArguments(new[] { "default1", "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => + { + Assert.True(args.TestValueOne); + }); + } + + [Fact] + public void Parse_multiple_default_verbs() + { + var parser = Parser.Default; + parser.ParseArguments(new string[] { }) + .WithNotParsed(errors => Assert.IsType(errors.First())) + .WithParsed(args => throw new InvalidOperationException("Should not be parsed.")); + } + + [Fact] + public void Parse_default_verb_with_empty_name() + { + var parser = Parser.Default; + parser.ParseArguments(new[] { "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => + { + Assert.True(args.TestValue); + }); + } } } From 1873aeb4b8873ef5bcaca2734fcbc893da2f249d Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Feb 2020 11:08:43 +0200 Subject: [PATCH 106/198] Update helptext to show Default Verb --- src/CommandLine/Text/HelpText.cs | 2 +- tests/CommandLine.Tests/Fakes/Verb_Fakes.cs | 13 ++++++++++++ tests/CommandLine.Tests/Unit/ParserTests.cs | 23 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 94b7e4f3..a04de0c7 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -853,7 +853,7 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable string.Empty, verbTuple.Item1.Name, false, - verbTuple.Item1.HelpText, + verbTuple.Item1.IsDefault? "(Default Verb) "+verbTuple.Item1.HelpText: verbTuple.Item1.HelpText, //Default verb string.Empty, verbTuple.Item1.Hidden); if (autoHelp) diff --git a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs index 83426bd7..9710d0de 100644 --- a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs +++ b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs @@ -18,7 +18,20 @@ public class Add_Verb [Value(0)] public string FileName { get; set; } } + [Verb("add", isDefault:true,HelpText = "Add file contents to the index.")] + public class Add_Verb_As_Default + { + [Option('p', "patch", SetName = "mode-p", + HelpText = "Interactively choose hunks of patch between the index and the work tree and add them to the index.")] + public bool Patch { get; set; } + [Option('f', "force", SetName = "mode-f", + HelpText = "Allow adding otherwise ignored files.")] + public bool Force { get; set; } + + [Value(0)] + public string FileName { get; set; } + } [Verb("commit", HelpText = "Record changes to the repository.")] public class Commit_Verb { diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index fe8c4d65..b6b08ca3 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -370,7 +370,30 @@ public void Implicit_help_screen_in_verb_scenario() lines[8].Should().BeEquivalentTo("version Display version information."); // Teardown } + + [Fact] + public void Help_screen_in_default_verb_scenario() + { + // Fixture setup + var help = new StringWriter(); + var sut = new Parser(config => config.HelpWriter = help); + // Exercise system + sut.ParseArguments(new string[] {"--help" }); + var result = help.ToString(); + + // Verify outcome + result.Length.Should().BeGreaterThan(0); + var lines = result.ToNotEmptyLines().TrimStringArray(); + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[2].Should().BeEquivalentTo("add (Default Verb) Add file contents to the index."); + lines[3].Should().BeEquivalentTo("commit Record changes to the repository."); + lines[4].Should().BeEquivalentTo("clone Clone a repository into a new directory."); + lines[5].Should().BeEquivalentTo("help Display more information on a specific command."); + lines[6].Should().BeEquivalentTo("version Display version information."); + + } [Fact] public void Double_dash_help_dispalys_verbs_index_in_verbs_scenario() { From d6e834f1d120e769a03dfc47444fe60fdea42341 Mon Sep 17 00:00:00 2001 From: kendfrey Date: Mon, 3 Feb 2020 04:34:06 -0500 Subject: [PATCH 107/198] SkipDefault was being ignored by [Usage] Examples (#565) --- src/CommandLine/Text/HelpText.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index a04de0c7..e9ce218d 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -741,6 +741,7 @@ public static IEnumerable RenderUsageTextAsLines(ParserResult pars config.PreferShortName = s.PreferShortName; config.GroupSwitches = s.GroupSwitches; config.UseEqualToken = s.UseEqualToken; + config.SkipDefault = s.SkipDefault; })); yield return commandLine.ToString(); } From 746885a8eae18183e2de57b64edcb1a0a5806140 Mon Sep 17 00:00:00 2001 From: edmondshtogu Date: Mon, 3 Feb 2020 10:35:53 +0100 Subject: [PATCH 108/198] more details for localized attribute properties... (#558) If the resources are not visible the parser library throws a useless exception. These extra details help with finding the issue quicker. --- .../Infrastructure/LocalizableAttributeProperty.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index b5a36100..9edd621b 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -45,10 +45,10 @@ private string GetLocalizedValue() { // Static class IsAbstract if (!_type.IsVisible) - throw new ArgumentException("Invalid resource type", _propertyName); + throw new ArgumentException($"Invalid resource type '{_type.FullName}'! {_type.Name} is not visible for the parser! Change resources 'Access Modifier' to 'Public'", _propertyName); PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); if (propertyInfo == null || !propertyInfo.CanRead || propertyInfo.PropertyType != typeof(string)) - throw new ArgumentException("Invalid resource property name", _propertyName); + throw new ArgumentException("Invalid resource property name! Localized value: {_value}", _propertyName); _localizationPropertyInfo = propertyInfo; } return (string)_localizationPropertyInfo.GetValue(null, null); From fc00b187878b500ae2d1dc58615e2e99ca50391a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:14:39 +0100 Subject: [PATCH 109/198] Revert "Added async versions of MapResult extension methods" This reverts commit 8f18fcb5da4c323c24d31e396afd516dd50a1f52. --- src/CommandLine/ParserResultExtensions.cs | 1500 ++------------------- 1 file changed, 128 insertions(+), 1372 deletions(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index d551bc29..c1bd35a7 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -143,26 +143,6 @@ public static TResult MapResult(this ParserResult res return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// Type of the target instance built with parsed value. - /// The type of the new value. - /// An instance. - /// Async lambda executed on successful parsing. - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - return parsedFunc(parsed.Value); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -188,30 +168,6 @@ public static TResult MapResult(this ParserResult result, return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -244,37 +200,6 @@ public static TResult MapResult(this ParserResult resul return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) - { - return parsedFunc2((T2)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -314,44 +239,6 @@ public static TResult MapResult(this ParserResult r return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -398,51 +285,6 @@ public static TResult MapResult(this ParserResult)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -496,58 +338,6 @@ public static TResult MapResult(this ParserResult)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -608,65 +398,6 @@ public static TResult MapResult(this ParserResu return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -734,72 +465,6 @@ public static TResult MapResult(this Parser return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -875,7 +540,7 @@ public static TResult MapResult(this Pa } /// - /// Provides a way to asynchronously transform result data into another value. + /// Provides a way to transform result data into another value. /// /// First verb type. /// Second verb type. @@ -885,62 +550,70 @@ public static TResult MapResult(this Pa /// Sixth verb type. /// Seventh verb type. /// Eighth verb type. + /// Ninth verb type. /// /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func, Task> notParsedFunc) + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func, TResult> notParsedFunc) { - if (result is Parsed parsed) + var parsed = result as Parsed; + if (parsed != null) { - if (parsed.Value is T1 t1) + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) { - return parsedFunc1(t1); + return parsedFunc2((T2)parsed.Value); } - if (parsed.Value is T2 t2) + if (parsed.Value is T3) { - return parsedFunc2(t2); + return parsedFunc3((T3)parsed.Value); } - if (parsed.Value is T3 t3) + if (parsed.Value is T4) { - return parsedFunc3(t3); + return parsedFunc4((T4)parsed.Value); } - if (parsed.Value is T4 t4) + if (parsed.Value is T5) { - return parsedFunc4(t4); + return parsedFunc5((T5)parsed.Value); } - if (parsed.Value is T5 t5) + if (parsed.Value is T6) { - return parsedFunc5(t5); + return parsedFunc6((T6)parsed.Value); } - if (parsed.Value is T6 t6) + if (parsed.Value is T7) { - return parsedFunc6(t6); + return parsedFunc7((T7)parsed.Value); } - if (parsed.Value is T7 t7) + if (parsed.Value is T8) { - return parsedFunc7(t7); + return parsedFunc8((T8)parsed.Value); } - if (parsed.Value is T8 t8) + if (parsed.Value is T9) { - return parsedFunc8(t8); + return parsedFunc9((T9)parsed.Value); } throw new InvalidOperationException(); } @@ -959,6 +632,7 @@ public static Task MapResultAsyncSeventh verb type. /// Eighth verb type. /// Ninth verb type. + /// Tenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -970,9 +644,10 @@ public static Task MapResultAsyncLambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -982,6 +657,7 @@ public static TResult MapResult(thi Func parsedFunc7, Func parsedFunc8, Func parsedFunc9, + Func parsedFunc10, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -1023,13 +699,17 @@ public static TResult MapResult(thi { return parsedFunc9((T9)parsed.Value); } + if (parsed.Value is T10) + { + return parsedFunc10((T10)parsed.Value); + } throw new InvalidOperationException(); } return notParsedFunc(((NotParsed)result).Errors); } /// - /// Provides a way to asynchronously transform result data into another value. + /// Provides a way to transform result data into another value. /// /// First verb type. /// Second verb type. @@ -1040,263 +720,8 @@ public static TResult MapResult(thi /// Seventh verb type. /// Eighth verb type. /// Ninth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// - /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. - /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func parsedFunc10, - Func, TResult> notParsedFunc) - { - var parsed = result as Parsed; - if (parsed != null) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) - { - return parsedFunc2((T2)parsed.Value); - } - if (parsed.Value is T3) - { - return parsedFunc3((T3)parsed.Value); - } - if (parsed.Value is T4) - { - return parsedFunc4((T4)parsed.Value); - } - if (parsed.Value is T5) - { - return parsedFunc5((T5)parsed.Value); - } - if (parsed.Value is T6) - { - return parsedFunc6((T6)parsed.Value); - } - if (parsed.Value is T7) - { - return parsedFunc7((T7)parsed.Value); - } - if (parsed.Value is T8) - { - return parsedFunc8((T8)parsed.Value); - } - if (parsed.Value is T9) - { - return parsedFunc9((T9)parsed.Value); - } - if (parsed.Value is T10) - { - return parsedFunc10((T10)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. + /// Tenth verb type. + /// Eleventh verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -1349,326 +774,29 @@ public static TResult MapResult)result).Errors); - } - - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// - /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. - /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func parsedFunc10, - Func parsedFunc11, - Func parsedFunc12, - Func, TResult> notParsedFunc) - { - var parsed = result as Parsed; - if (parsed != null) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) - { - return parsedFunc2((T2)parsed.Value); - } - if (parsed.Value is T3) - { - return parsedFunc3((T3)parsed.Value); - } - if (parsed.Value is T4) - { - return parsedFunc4((T4)parsed.Value); - } - if (parsed.Value is T5) - { - return parsedFunc5((T5)parsed.Value); - } - if (parsed.Value is T6) - { - return parsedFunc6((T6)parsed.Value); - } - if (parsed.Value is T7) - { - return parsedFunc7((T7)parsed.Value); - } - if (parsed.Value is T8) - { - return parsedFunc8((T8)parsed.Value); - } - if (parsed.Value is T9) - { - return parsedFunc9((T9)parsed.Value); - } - if (parsed.Value is T10) - { - return parsedFunc10((T10)parsed.Value); - } - if (parsed.Value is T11) - { - return parsedFunc11((T11)parsed.Value); - } - if (parsed.Value is T12) - { - return parsedFunc12((T12)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) + if (parsed.Value is T6) { - return parsedFunc7(t7); + return parsedFunc6((T6)parsed.Value); } - if (parsed.Value is T8 t8) + if (parsed.Value is T7) { - return parsedFunc8(t8); + return parsedFunc7((T7)parsed.Value); } - if (parsed.Value is T9 t9) + if (parsed.Value is T8) { - return parsedFunc9(t9); + return parsedFunc8((T8)parsed.Value); } - if (parsed.Value is T10 t10) + if (parsed.Value is T9) { - return parsedFunc10(t10); + return parsedFunc9((T9)parsed.Value); } - if (parsed.Value is T11 t11) + if (parsed.Value is T10) { - return parsedFunc11(t11); + return parsedFunc10((T10)parsed.Value); } - if (parsed.Value is T12 t12) + if (parsed.Value is T11) { - return parsedFunc12(t12); + return parsedFunc11((T11)parsed.Value); } throw new InvalidOperationException(); } @@ -1690,7 +818,6 @@ public static Task MapResultAsyncTenth verb type. /// Eleventh verb type. /// Twelfth verb type. - /// Thirteenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -1705,10 +832,9 @@ public static Task MapResultAsyncLambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -1721,7 +847,6 @@ public static TResult MapResult parsedFunc10, Func parsedFunc11, Func parsedFunc12, - Func parsedFunc13, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -1775,17 +900,13 @@ public static TResult MapResult)result).Errors); } /// - /// Provides a way to asynchronously result data into another value. + /// Provides a way to transform result data into another value. /// /// First verb type. /// Second verb type. @@ -1802,95 +923,97 @@ public static TResult MapResultThirteenth verb type. /// /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func, Task> notParsedFunc) + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func parsedFunc10, + Func parsedFunc11, + Func parsedFunc12, + Func parsedFunc13, + Func, TResult> notParsedFunc) { - if (result is Parsed parsed) + var parsed = result as Parsed; + if (parsed != null) { - if (parsed.Value is T1 t1) + if (parsed.Value is T1) { - return parsedFunc1(t1); + return parsedFunc1((T1)parsed.Value); } - if (parsed.Value is T2 t2) + if (parsed.Value is T2) { - return parsedFunc2(t2); + return parsedFunc2((T2)parsed.Value); } - if (parsed.Value is T3 t3) + if (parsed.Value is T3) { - return parsedFunc3(t3); + return parsedFunc3((T3)parsed.Value); } - if (parsed.Value is T4 t4) + if (parsed.Value is T4) { - return parsedFunc4(t4); + return parsedFunc4((T4)parsed.Value); } - if (parsed.Value is T5 t5) + if (parsed.Value is T5) { - return parsedFunc5(t5); + return parsedFunc5((T5)parsed.Value); } - if (parsed.Value is T6 t6) + if (parsed.Value is T6) { - return parsedFunc6(t6); + return parsedFunc6((T6)parsed.Value); } - if (parsed.Value is T7 t7) + if (parsed.Value is T7) { - return parsedFunc7(t7); + return parsedFunc7((T7)parsed.Value); } - if (parsed.Value is T8 t8) + if (parsed.Value is T8) { - return parsedFunc8(t8); + return parsedFunc8((T8)parsed.Value); } - if (parsed.Value is T9 t9) + if (parsed.Value is T9) { - return parsedFunc9(t9); + return parsedFunc9((T9)parsed.Value); } - if (parsed.Value is T10 t10) + if (parsed.Value is T10) { - return parsedFunc10(t10); + return parsedFunc10((T10)parsed.Value); } - if (parsed.Value is T11 t11) + if (parsed.Value is T11) { - return parsedFunc11(t11); + return parsedFunc11((T11)parsed.Value); } - if (parsed.Value is T12 t12) + if (parsed.Value is T12) { - return parsedFunc12(t12); + return parsedFunc12((T12)parsed.Value); } - if (parsed.Value is T13 t13) + if (parsed.Value is T13) { - return parsedFunc13(t13); + return parsedFunc13((T13)parsed.Value); } throw new InvalidOperationException(); } return notParsedFunc(((NotParsed)result).Errors); } + /// /// Provides a way to transform result data into another value. /// @@ -2007,121 +1130,6 @@ public static TResult MapResult)result).Errors); } - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// Thirteenth verb type. - /// Fourteenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func> parsedFunc14, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - if (parsed.Value is T12 t12) - { - return parsedFunc12(t12); - } - if (parsed.Value is T13 t13) - { - return parsedFunc13(t13); - } - if (parsed.Value is T14 t14) - { - return parsedFunc14(t14); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -2245,128 +1253,6 @@ public static TResult MapResult)result).Errors); } - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// Thirteenth verb type. - /// Fourteenth verb type. - /// Fifteenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func> parsedFunc14, - Func> parsedFunc15, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - if (parsed.Value is T12 t12) - { - return parsedFunc12(t12); - } - if (parsed.Value is T13 t13) - { - return parsedFunc13(t13); - } - if (parsed.Value is T14 t14) - { - return parsedFunc14(t14); - } - if (parsed.Value is T15 t15) - { - return parsedFunc15(t15); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -2496,135 +1382,5 @@ public static TResult MapResult)result).Errors); } - - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// Thirteenth verb type. - /// Fourteenth verb type. - /// Fifteenth verb type. - /// Sixteenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func> parsedFunc14, - Func> parsedFunc15, - Func> parsedFunc16, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - if (parsed.Value is T12 t12) - { - return parsedFunc12(t12); - } - if (parsed.Value is T13 t13) - { - return parsedFunc13(t13); - } - if (parsed.Value is T14 t14) - { - return parsedFunc14(t14); - } - if (parsed.Value is T15 t15) - { - return parsedFunc15(t15); - } - if (parsed.Value is T16 t16) - { - return parsedFunc16(t16); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - } } From 446c36f43c405ff7d1a7986f06b86a5f2679eba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:20:12 +0100 Subject: [PATCH 110/198] Revert "Added async versions of WithParsed extension methods" This reverts commit dae308144a0ed37613eea505a6273b3744322f57. --- src/CommandLine/ParserResultExtensions.cs | 58 +---------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index c1bd35a7..66201dbb 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; namespace CommandLine { @@ -29,24 +28,6 @@ public static ParserResult WithParsed(this ParserResult result, Action< return result; } - /// - /// Executes asynchronously if contains - /// parsed values. - /// - /// Type of the target instance built with parsed value. - /// An instance. - /// The to execute. - /// The same instance as a instance. - public static async Task> WithParsedAsync(this ParserResult result, Func action) - { - if (result is Parsed parsed) - { - await action(parsed.Value); - } - return result; - } - - /// /// Executes if parsed values are of . /// @@ -67,26 +48,6 @@ public static ParserResult WithParsed(this ParserResult resul return result; } - /// - /// Executes asynchronously if parsed values are of . - /// - /// Type of the target instance built with parsed value. - /// An verb result instance. - /// The to execute. - /// The same instance as a instance. - public static async Task> WithParsedAsync(this ParserResult result, Func action) - { - if (result is Parsed parsed) - { - if (parsed.Value is T) - { - await action((T)parsed.Value); - } - } - return result; - } - - /// /// Executes if lacks /// parsed values and contains errors. @@ -96,28 +57,11 @@ public static async Task> WithParsedAsync(this ParserRes /// The delegate to execute. /// The same instance. public static ParserResult WithNotParsed(this ParserResult result, Action> action) - { - if (result is NotParsed notParsed) - { - action(notParsed.Errors); - } - return result; - } - - /// - /// Executes asynchronously if lacks - /// parsed values and contains errors. - /// - /// Type of the target instance built with parsed value. - /// An instance. - /// The delegate to execute. - /// The same instance as a instance. - public static async Task> WithNotParsedAsync(this ParserResult result, Func, Task> action) { var notParsed = result as NotParsed; if (notParsed != null) { - await action(notParsed.Errors); + action(notParsed.Errors); } return result; } From b938939e295852c4377341096d9ce3c3e16b1fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:21:09 +0100 Subject: [PATCH 111/198] Added async versions of WithParsed extension methods --- .../ParserResultExtensionsAsync.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/CommandLine/ParserResultExtensionsAsync.cs diff --git a/src/CommandLine/ParserResultExtensionsAsync.cs b/src/CommandLine/ParserResultExtensionsAsync.cs new file mode 100644 index 00000000..207dfa23 --- /dev/null +++ b/src/CommandLine/ParserResultExtensionsAsync.cs @@ -0,0 +1,63 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CommandLine +{ + public static partial class ParserResultExtensions + { + /// + /// Executes asynchronously if contains + /// parsed values. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + await action(parsed.Value); + } + return result; + } + + /// + /// Executes asynchronously if parsed values are of . + /// + /// Type of the target instance built with parsed value. + /// An verb result instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + if (parsed.Value is T value) + { + await action(value); + } + } + return result; + } + + /// + /// Executes asynchronously if lacks + /// parsed values and contains errors. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The delegate to execute. + /// The same instance as a instance. + public static async Task> WithNotParsedAsync(this ParserResult result, Func, Task> action) + { + if (result is NotParsed notParsed) + { + await action(notParsed.Errors); + } + return result; + } + } +} From 4d25d14cba7b3fccffcde5450d34bf0cd1a3b3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:44:40 +0100 Subject: [PATCH 112/198] Added unit tests for async extension methods --- .../Unit/ParserResultExtensionsTests.cs | 74 ------------------- 1 file changed, 74 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs index f992996a..f639a72f 100644 --- a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs @@ -161,15 +161,6 @@ public static void Turn_sucessful_parsing_into_exit_code() 0.Should().Be(expected); } - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_codeAsync() - { - var expected = await Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }) - .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); - - 0.Should().Be(expected); - } - [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_verbs() { @@ -184,20 +175,6 @@ public static void Turn_sucessful_parsing_into_exit_code_for_verbs() 2.Should().Be(expected); } - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_code_for_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "clone", "https://value.org/user/file.git" }) - .MapResultAsync( - (Add_Verb opts) => Task.FromResult(0), - (Commit_Verb opts) => Task.FromResult(1), - (Clone_Verb opts) => Task.FromResult(2), - errs => Task.FromResult(3)); - - 2.Should().Be(expected); - } - [Fact] public static void Turn_failed_parsing_into_exit_code() { @@ -207,15 +184,6 @@ public static void Turn_failed_parsing_into_exit_code() (-1).Should().Be(expected); } - [Fact] - public static async Task Turn_failed_parsing_into_exit_codeAsync() - { - var expected = await Parser.Default.ParseArguments(new[] { "-i", "aaa" }) - .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); - - (-1).Should().Be(expected); - } - [Fact] public static void Turn_failed_parsing_into_exit_code_for_verbs() { @@ -230,20 +198,6 @@ public static void Turn_failed_parsing_into_exit_code_for_verbs() 3.Should().Be(expected); } - [Fact] - public static async Task Turn_failed_parsing_into_exit_code_for_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "undefined", "-xyz" }) - .MapResultAsync( - (Add_Verb opts) => Task.FromResult(0), - (Commit_Verb opts) => Task.FromResult(1), - (Clone_Verb opts) => Task.FromResult(2), - errs => Task.FromResult(3)); - - 3.Should().Be(expected); - } - [Fact] public static void Invoke_parsed_lambda_when_parsed_for_base_verbs() { @@ -285,18 +239,6 @@ public static void Turn_sucessful_parsing_into_exit_code_for_single_base_verbs() 1.Should().Be(expected); } - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_code_for_single_base_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "derivedadd", "dummy.bin" }) - .MapResultAsync( - (Base_Class_For_Verb opts) => Task.FromResult(1), - errs => Task.FromResult(2)); - - 1.Should().Be(expected); - } - [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs() { @@ -312,21 +254,5 @@ public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs 4.Should().Be(expected); } - - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "derivedadd", "dummy.bin" }) - .MapResultAsync( - (Add_Verb opts) => Task.FromResult(0), - (Commit_Verb opts) => Task.FromResult(1), - (Clone_Verb opts) => Task.FromResult(2), - (Base_Class_For_Verb opts) => Task.FromResult(4), - (Derived_Verb opts) => Task.FromResult(3), - errs => Task.FromResult(5)); - - 4.Should().Be(expected); - } } } From 65c1e4ba6fa60848f148976dc45914497ca79dc3 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Fri, 7 Feb 2020 19:40:12 +0200 Subject: [PATCH 113/198] Exclude Net40 from async --- src/CommandLine/ParserResultExtensionsAsync.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CommandLine/ParserResultExtensionsAsync.cs b/src/CommandLine/ParserResultExtensionsAsync.cs index 207dfa23..d58d769f 100644 --- a/src/CommandLine/ParserResultExtensionsAsync.cs +++ b/src/CommandLine/ParserResultExtensionsAsync.cs @@ -7,6 +7,7 @@ namespace CommandLine { public static partial class ParserResultExtensions { +#if !NET40 /// /// Executes asynchronously if contains /// parsed values. @@ -59,5 +60,6 @@ public static async Task> WithNotParsedAsync(this ParserResul } return result; } +#endif } } From de94a89570eceb3619a88833993708ad7f94eb30 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Mon, 9 Mar 2020 14:48:37 +0200 Subject: [PATCH 114/198] Fix issue# 339 of custom struct (#588) --- src/CommandLine/Core/ReflectionExtensions.cs | 8 +++ src/CommandLine/Core/TypeConverter.cs | 3 +- .../CommandLine.Tests/Fakes/Custom_Struct.cs | 56 +++++++++++++++++++ .../Unit/Core/InstanceBuilderTests.cs | 36 ++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tests/CommandLine.Tests/Fakes/Custom_Struct.cs diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index 9b6f858c..72e162b5 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -209,5 +209,13 @@ public static bool IsPrimitiveEx(this Type type) }.Contains(type) || Convert.GetTypeCode(type) != TypeCode.Object; } + + public static bool IsCustomStruct(this Type type) + { + var isStruct = type.GetTypeInfo().IsValueType && !type.GetTypeInfo().IsPrimitive && !type.GetTypeInfo().IsEnum && type != typeof(Guid); + if (!isStruct) return false; + var ctor = type.GetTypeInfo().GetConstructor(new[] { typeof(string) }); + return ctor != null; + } } } diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index fef7945a..354c4316 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -111,6 +111,7 @@ private static Result ChangeTypeScalarImpl(string value, Type } }; + if (conversionType.IsCustomStruct()) return Result.Try(makeType); return Result.Try( conversionType.IsPrimitiveEx() || ReflectionHelper.IsFSharpOptionType(conversionType) ? changeType @@ -135,4 +136,4 @@ private static object ToEnum(this string value, Type conversionType, bool ignore throw new FormatException(); } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Fakes/Custom_Struct.cs b/tests/CommandLine.Tests/Fakes/Custom_Struct.cs new file mode 100644 index 00000000..64807f6b --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Custom_Struct.cs @@ -0,0 +1,56 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System; + +namespace CommandLine.Tests.Fakes +{ + public class CustomStructOptions + { + [Option('c', "custom", HelpText = "Custom Type")] + public CustomStruct Custom { get; set; } + } + + public struct CustomStruct + { + public string Input { get; set; } + public string Server { get; set; } + public int Port { get; set; } + public CustomStruct(string url) + { + Input = url; + Server = ""; + Port = 80; + var data = url.Split(':'); + if (data.Length == 2) + { + Server = data[0]; + Port = Convert.ToInt32(data[1]); + } + } + } + + public class CustomClassOptions + { + [Option('c', "custom", HelpText = "Custom Type")] + public CustomClass Custom { get; set; } + } + + public class CustomClass + { + public string Input { get; set; } + public string Server { get; set; } + public int Port { get; set; } + public CustomClass(string url) + { + Input = url; + Server = ""; + Port = 80; + var data = url.Split(':'); + if (data.Length == 2) + { + Server = data[0]; + Port = Convert.ToInt32(data[1]); + } + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 643878fd..a74a1af0 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1221,6 +1221,42 @@ public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() errors.Should().BeEquivalentTo(expectedResult); } + #region custom types + + + [Theory] + [InlineData(new[] { "-c", "localhost:8080" }, "localhost", 8080)] + public void Parse_custom_struct_type(string[] arguments, string expectedServer, int expectedPort) + { + //Arrange + + // Act + var result = InvokeBuild(arguments); + + // Assert + var customValue = ((Parsed)result).Value.Custom; + customValue.Server.Should().Be(expectedServer); + customValue.Port.Should().Be(expectedPort); + customValue.Input.Should().Be(arguments[1]); + } + + [Theory] + [InlineData(new[] { "-c", "localhost:8080" }, "localhost", 8080)] + public void Parse_custom_class_type(string[] arguments, string expectedServer, int expectedPort) + { + //Arrange + + // Act + var result = InvokeBuild(arguments); + + // Assert + var customValue = ((Parsed)result).Value.Custom; + customValue.Server.Should().Be(expectedServer); + customValue.Port.Should().Be(expectedPort); + customValue.Input.Should().Be(arguments[1]); + } + + #endregion private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From 30539a51285735bf906a0ae350750982ddb28691 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Mon, 9 Mar 2020 14:52:16 +0200 Subject: [PATCH 115/198] Fix issue #409 to avoid IOException break in Debug mode in WPF app. (#589) --- src/CommandLine/ParserSettings.cs | 23 +++++++++++++++------ tests/CommandLine.Tests/Unit/ParserTests.cs | 12 +++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 7b182a22..07c10c4c 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -36,18 +36,29 @@ public ParserSettings() autoHelp = true; autoVersion = true; parsingCulture = CultureInfo.InvariantCulture; + maximumDisplayWidth = GetWindowWidth(); + } + + private int GetWindowWidth() + { + +#if !NET40 + if (Console.IsOutputRedirected) return DefaultMaximumLength; +#endif + var width = 1; try { - maximumDisplayWidth = Console.WindowWidth; - if (maximumDisplayWidth < 1) + width = Console.WindowWidth; + if (width < 1) { - maximumDisplayWidth = DefaultMaximumLength; + width = DefaultMaximumLength; } - } - catch (IOException) + } + catch (Exception e) when (e is IOException || e is PlatformNotSupportedException || e is ArgumentOutOfRangeException) { - maximumDisplayWidth = DefaultMaximumLength; + width = DefaultMaximumLength; } + return width; } /// diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index b6b08ca3..743bb0f6 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -892,5 +892,17 @@ public void Parse_default_verb_with_empty_name() Assert.True(args.TestValue); }); } + //Fix Issue #409 for WPF + [Fact] + public void When_HelpWriter_is_null_it_should_not_fire_exception() + { + // Arrange + + //Act + var sut = new Parser(config => config.HelpWriter = null); + sut.ParseArguments(new[] {"--dummy"}); + //Assert + sut.Settings.MaximumDisplayWidth.Should().Be(80); + } } } From 3d3d96724e1e8dae5ad9475adb47777867a064d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20G=C3=A5rdebrink?= Date: Mon, 9 Mar 2020 14:15:50 +0100 Subject: [PATCH 116/198] Remove constraint on T for ParseArguments with factory (#590) * Remove constraint on T for ParseArguments with factory * Make it possible to use factory for classes without public empty constructor * Allow types with explicitly defined interface implementations * Add test for #70 * Make sure explicit interface implementations can be parsed * Add test to make sure parser can detect explicit interface implementations --- src/CommandLine/Core/InstanceBuilder.cs | 4 +-- src/CommandLine/Core/ReflectionExtensions.cs | 17 +++++++++-- .../Infrastructure/CSharpx/Maybe.cs | 8 +++++ src/CommandLine/Parser.cs | 1 - .../Mutable_Without_Empty_Constructor.cs | 19 ++++++++++++ .../Options_With_Only_Explicit_Interface.cs | 9 ++++++ .../Unit/Core/InstanceBuilderTests.cs | 14 +++++++++ tests/CommandLine.Tests/Unit/Issue591ests.cs | 29 +++++++++++++++++++ tests/CommandLine.Tests/Unit/Issue70Tests.cs | 29 +++++++++++++++++++ 9 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs create mode 100644 tests/CommandLine.Tests/Unit/Issue591ests.cs create mode 100644 tests/CommandLine.Tests/Unit/Issue70Tests.cs diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 0ae564b5..dce377f1 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -39,7 +39,7 @@ public static ParserResult Build( Func makeDefault = () => typeof(T).IsMutable() - ? factory.MapValueOrDefault(f => f(), Activator.CreateInstance()) + ? factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()) : ReflectionHelper.CreateDefaultImmutableInstance( (from p in specProps select p.Specification.ConversionType).ToArray()); @@ -128,7 +128,7 @@ public static ParserResult Build( private static T BuildMutable(Maybe> factory, IEnumerable specPropsWithValue, List setPropertyErrors ) { - var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance()); + var mutable = factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()); setPropertyErrors.AddRange( mutable.SetProperties( diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index 72e162b5..e8b7e766 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -133,10 +133,21 @@ public static bool IsMutable(this Type type) if(type == typeof(object)) return true; - var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite); - var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any(); + // Find all inherited defined properties and fields on the type + var inheritedTypes = type.GetTypeInfo().FlattenHierarchy().Select(i => i.GetTypeInfo()); - return props || fields; + foreach (var inheritedType in inheritedTypes) + { + if ( + inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite) || + inheritedType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any() + ) + { + return true; + } + } + + return false; } public static object CreateDefaultForImmutable(this Type type) diff --git a/src/CommandLine/Infrastructure/CSharpx/Maybe.cs b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs index 1dacf4e8..044bb681 100644 --- a/src/CommandLine/Infrastructure/CSharpx/Maybe.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs @@ -371,6 +371,14 @@ public static T2 MapValueOrDefault(this Maybe maybe, Func fu return maybe.MatchJust(out value1) ? func(value1) : noneValue; } + /// + /// If contains a values executes a mapping function over it, otherwise returns the value from . + /// + public static T2 MapValueOrDefault(this Maybe maybe, Func func, Func noneValueFactory) { + T1 value1; + return maybe.MatchJust(out value1) ? func(value1) : noneValueFactory(); + } + /// /// Returns an empty list when given or a singleton list when given a . /// diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 8f4bd049..f801c0f7 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -116,7 +116,6 @@ public ParserResult ParseArguments(IEnumerable args) /// and a sequence of . /// Thrown if one or more arguments are null. public ParserResult ParseArguments(Func factory, IEnumerable args) - where T : new() { if (factory == null) throw new ArgumentNullException("factory"); if (!typeof(T).IsMutable()) throw new ArgumentException("factory"); diff --git a/tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs b/tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs new file mode 100644 index 00000000..6f311bb1 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs @@ -0,0 +1,19 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +namespace CommandLine.Tests.Fakes +{ + class Mutable_Without_Empty_Constructor + { + [Option("amend", HelpText = "Used to amend the tip of the current branch.")] + public bool Amend { get; set; } + + private Mutable_Without_Empty_Constructor() + { + } + + public static Mutable_Without_Empty_Constructor Create() + { + return new Mutable_Without_Empty_Constructor(); + } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs b/tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs new file mode 100644 index 00000000..d367bfc0 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs @@ -0,0 +1,9 @@ +namespace CommandLine.Tests.Fakes +{ + class Options_With_Only_Explicit_Interface : IInterface_With_Two_Scalar_Options + { + bool IInterface_With_Two_Scalar_Options.Verbose { get; set; } + + string IInterface_With_Two_Scalar_Options.InputFile { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index a74a1af0..8dd7371c 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -787,6 +787,20 @@ public void Specifying_options_two_or_more_times_with_mixed_short_long_options_g ((NotParsed)result).Errors.Should().HaveCount(x => x == expected); } + [Theory] + [InlineData(new[] { "--inputfile=file1.bin" }, "file1.bin")] + [InlineData(new[] { "--inputfile", "file2.txt" }, "file2.txt")] + public void Can_define_options_on_explicit_interface_properties(string[] arguments, string expected) + { + // Exercize system + var result = InvokeBuild( + arguments); + + // Verify outcome + expected.Should().BeEquivalentTo(((IInterface_With_Two_Scalar_Options)((Parsed)result).Value).InputFile); + } + + [Theory] [InlineData(new[] { "--inputfile=file1.bin" }, "file1.bin")] [InlineData(new[] { "--inputfile", "file2.txt" }, "file2.txt")] diff --git a/tests/CommandLine.Tests/Unit/Issue591ests.cs b/tests/CommandLine.Tests/Unit/Issue591ests.cs new file mode 100644 index 00000000..3888a705 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue591ests.cs @@ -0,0 +1,29 @@ +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +//Issue #591 +//When options class is only having explicit interface declarations, it should be detected as mutable. + +namespace CommandLine.Tests.Unit +{ + public class Issue591ests + { + [Fact] + public void Parse_option_with_only_explicit_interface_implementation() + { + string actual = string.Empty; + + var arguments = new[] { "--inputfile", "file2.txt" }; + var result = Parser.Default.ParseArguments(arguments); + result.WithParsed(options => { + actual = ((IInterface_With_Two_Scalar_Options)options).InputFile; + }); + + actual.Should().Be("file2.txt"); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Issue70Tests.cs b/tests/CommandLine.Tests/Unit/Issue70Tests.cs new file mode 100644 index 00000000..0acc1116 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue70Tests.cs @@ -0,0 +1,29 @@ +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +//Issue #70 +//When the factory overload is used for ParseArguments, there should be no constraint not having an empty constructor. + +namespace CommandLine.Tests.Unit +{ + public class Issue70Tests + { + [Fact] + public void Create_instance_with_factory_method_should_not_fail() + { + bool actual = false; + + var arguments = new[] { "--amend" }; + var result = Parser.Default.ParseArguments(() => Mutable_Without_Empty_Constructor.Create(), arguments); + result.WithParsed(options => { + actual = options.Amend; + }); + + actual.Should().BeTrue(); + } + } +} From 25bd81cfc8033c885544713960f3dd89c4ff994c Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 07:51:11 +0100 Subject: [PATCH 117/198] Fixed typo in test methods --- .../CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs index 4b78aa13..29f666b5 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs @@ -9,9 +9,8 @@ namespace CommandLine.Tests.Unit.Text { public class HelpTextAutoBuildFix { - [Fact] - public void HelpText_wit_AdditionalNewLineAfterOption_true_should_have_newline() + public void HelpText_with_AdditionalNewLineAfterOption_true_should_have_newline() { // Fixture setup // Exercize system @@ -36,7 +35,7 @@ public void HelpText_wit_AdditionalNewLineAfterOption_true_should_have_newline() } [Fact] - public void HelpText_wit_AdditionalNewLineAfterOption_false_should_not_have_newline() + public void HelpText_with_AdditionalNewLineAfterOption_false_should_not_have_newline() { // Fixture setup // Exercize system @@ -55,7 +54,7 @@ public void HelpText_wit_AdditionalNewLineAfterOption_false_should_not_have_newl // Teardown } [Fact] - public void HelpText_wit_by_default_should_include_help_version_option() + public void HelpText_with_by_default_should_include_help_version_option() { // Fixture setup // Exercize system @@ -73,7 +72,7 @@ public void HelpText_wit_by_default_should_include_help_version_option() } [Fact] - public void HelpText_wit_AutoHelp_false_should_hide_help_option() + public void HelpText_with_AutoHelp_false_should_hide_help_option() { // Fixture setup // Exercize system From c3884b13446b2caeb4a5c247501635c2b0d8310f Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:03:17 +0100 Subject: [PATCH 118/198] Temporarily removed a file to cheat wrong git tracking --- .../Fakes/OPtions_HelpText_Ordering.cs | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs diff --git a/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs deleted file mode 100644 index 3896ab67..00000000 --- a/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace CommandLine.Tests.Fakes -{ - - [Verb("verb1")] - class Options_HelpText_Ordering_Verb1 - { - [Option('a', "alpha", Required = true)] - public string alphaOption { get; set; } - - [Option('b', "alpha2", Required = true)] - public string alphaTwoOption { get; set; } - - [Option('d', "charlie", Required = false)] - public string deltaOption { get; set; } - - [Option('c', "bravo", Required = false)] - public string charlieOption { get; set; } - - [Option('f', "foxtrot", Required = false)] - public string foxOption { get; set; } - - [Option('e', "echo", Required = false)] - public string echoOption { get; set; } - - [Value(0)] public string someExtraOption { get; set; } - } - - [Verb("verb2")] - class Options_HelpText_Ordering_Verb2 - { - [Option('a', "alpha", Required = true)] - public string alphaOption { get; set; } - - [Option('b', "alpha2", Required = true)] - public string alphaTwoOption { get; set; } - - [Option('c', "bravo", Required = false)] - public string charlieOption { get; set; } - - [Option('d', "charlie", Required = false)] - public string deltaOption { get; set; } - } -} From a261a0d92fc34dbf8a51a90af2539e1e283490bd Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:05:15 +0100 Subject: [PATCH 119/198] File added again without wrong case in filename --- .../Fakes/Options_HelpText_Ordering.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs diff --git a/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs new file mode 100644 index 00000000..3896ab67 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs @@ -0,0 +1,48 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace CommandLine.Tests.Fakes +{ + + [Verb("verb1")] + class Options_HelpText_Ordering_Verb1 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('f', "foxtrot", Required = false)] + public string foxOption { get; set; } + + [Option('e', "echo", Required = false)] + public string echoOption { get; set; } + + [Value(0)] public string someExtraOption { get; set; } + } + + [Verb("verb2")] + class Options_HelpText_Ordering_Verb2 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + } +} From ea4714b8aead6d0d42037b8fb87888c9ae0da35d Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:27:45 +0100 Subject: [PATCH 120/198] Using statements cleanup --- tests/CommandLine.Tests/Fakes/Hidden_Option.cs | 8 +------- .../Fakes/Options_HelpText_Ordering.cs | 3 --- ...With_Sequence_Having_Both_Min_And_Max_Equal.cs | 3 --- tests/CommandLine.Tests/Fakes/ResourceFakes.cs | 8 +------- .../CommandLine.Tests/Unit/BaseAttributeTests.cs | 1 - .../Unit/Core/InstanceBuilderTests.cs | 2 -- .../Unit/Core/InstanceChooserTests.cs | 4 ++-- .../Unit/Core/KeyValuePairHelperTests.cs | 2 +- .../Unit/Core/NameLookupTests.cs | 10 +++------- .../Unit/Core/OptionMapperTests.cs | 8 ++------ .../Unit/Core/ReflectionExtensions.cs | 4 ++-- tests/CommandLine.Tests/Unit/Core/ScalarTests.cs | 4 ++-- .../CommandLine.Tests/Unit/Core/SequenceTests.cs | 4 ++-- tests/CommandLine.Tests/Unit/Core/SwitchTests.cs | 4 ++-- .../Unit/Core/TextWrapperTests.cs | 5 +++-- .../Unit/Core/TokenPartitionerTests.cs | 7 ++----- tests/CommandLine.Tests/Unit/Core/TokenTests.cs | 2 +- .../CommandLine.Tests/Unit/Core/TokenizerTests.cs | 15 +++++---------- .../Unit/Core/TypeConverterTests.cs | 6 +++--- tests/CommandLine.Tests/Unit/Issue104Tests.cs | 5 ++--- tests/CommandLine.Tests/Unit/Issue389Tests.cs | 6 ++---- tests/CommandLine.Tests/Unit/Issue418Tests.cs | 8 ++------ tests/CommandLine.Tests/Unit/Issue482Tests.cs | 7 ------- .../Unit/ParserResultExtensionsTests.cs | 4 +--- .../CommandLine.Tests/Unit/ParserSettingsTests.cs | 9 ++------- tests/CommandLine.Tests/Unit/ParserTests.cs | 5 ++--- .../Unit/StringBuilderExtensionsTests.cs | 5 +---- .../Unit/Text/HelpTextAutoBuildFix.cs | 4 ++-- .../CommandLine.Tests/Unit/Text/HelpTextTests.cs | 7 ++----- .../Unit/UnParserExtensionsTests.cs | 4 +--- 30 files changed, 49 insertions(+), 115 deletions(-) diff --git a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs index 1917ecab..b9a87cd3 100644 --- a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs +++ b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CommandLine.Tests.Fakes +namespace CommandLine.Tests.Fakes { public class Hidden_Option { diff --git a/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs index 3896ab67..27c7fa6b 100644 --- a/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs +++ b/tests/CommandLine.Tests/Fakes/Options_HelpText_Ordering.cs @@ -1,8 +1,5 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System.Collections.Generic; -using System.Runtime.CompilerServices; - namespace CommandLine.Tests.Fakes { diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs b/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs index d24a8036..21671d76 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Both_Min_And_Max_Equal.cs @@ -1,9 +1,6 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace CommandLine.Tests.Fakes { diff --git a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs index 917d51bf..f7b46bac 100644 --- a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs +++ b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CommandLine.Tests.Fakes +namespace CommandLine.Tests.Fakes { public static class StaticResource { diff --git a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs index 72cf81b4..3c2bfbbd 100644 --- a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Xunit; namespace CommandLine.Tests.Unit diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index bf885f76..c2cbb77a 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -13,9 +13,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; - using Xunit; -using System.Reflection; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs index 52e12cae..c9dae5fb 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs @@ -4,10 +4,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Xunit; +using FluentAssertions; using CommandLine.Core; using CommandLine.Tests.Fakes; -using FluentAssertions; -using Xunit; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs b/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs index 77ba39cf..3f1fb293 100644 --- a/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/KeyValuePairHelperTests.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; -using CommandLine.Core; using Xunit; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index 785b1fe5..f009c49e 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -1,15 +1,11 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; - -using CSharpx; - -using FluentAssertions; - using System; using System.Collections.Generic; - using Xunit; +using FluentAssertions; +using CommandLine.Core; +using CSharpx; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 75ddade7..b2219683 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -4,18 +4,14 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; - #if PLATFORM_DOTNET using System.Reflection; #endif -using CommandLine.Core; -using CommandLine.Tests.Fakes; - using Xunit; - using CSharpx; - using RailwaySharp.ErrorHandling; +using CommandLine.Core; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs b/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs index 2fdb951d..035b9e03 100644 --- a/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs +++ b/tests/CommandLine.Tests/Unit/Core/ReflectionExtensions.cs @@ -1,9 +1,9 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using Xunit; +using FluentAssertions; using CommandLine.Core; using CommandLine.Tests.Fakes; -using FluentAssertions; -using Xunit; namespace CommandLine.Tests.Unit.Infrastructure { diff --git a/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs b/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs index 2984b77e..9b1028f0 100644 --- a/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs @@ -1,10 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System.Linq; -using CommandLine.Core; -using CSharpx; using Xunit; using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs index 36e3e262..b26575b8 100644 --- a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs @@ -1,10 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System.Linq; -using CommandLine.Core; -using CSharpx; using Xunit; using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs b/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs index a4163990..82edb635 100644 --- a/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs @@ -1,10 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System.Linq; -using CommandLine.Core; -using CSharpx; using Xunit; using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs index 1db7497e..8de39610 100644 --- a/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TextWrapperTests.cs @@ -1,8 +1,9 @@ using System; using System.Linq; -using CommandLine.Text; -using FluentAssertions; using Xunit; +using FluentAssertions; +using CSharpx; +using CommandLine.Text; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index 11a9ffdd..20006e59 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -1,14 +1,11 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; - -using CSharpx; - using System; using System.Collections.Generic; using System.Linq; - using Xunit; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TokenTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenTests.cs index 991171db..1290f9b3 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenTests.cs @@ -1,7 +1,7 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; using Xunit; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index 3bd95891..61c7f184 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -1,19 +1,14 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using CommandLine.Core; -using CommandLine.Infrastructure; - -using CSharpx; - -using FluentAssertions; - -using RailwaySharp.ErrorHandling; - using System; using System.Collections.Generic; using System.Linq; - using Xunit; +using FluentAssertions; +using CSharpx; +using RailwaySharp.ErrorHandling; +using CommandLine.Core; +using CommandLine.Infrastructure; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index 90fbc3f5..d9f3988c 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Globalization; -using CommandLine.Core; -using CSharpx; -using FluentAssertions; using Xunit; +using FluentAssertions; +using CSharpx; +using CommandLine.Core; namespace CommandLine.Tests.Unit.Core { diff --git a/tests/CommandLine.Tests/Unit/Issue104Tests.cs b/tests/CommandLine.Tests/Unit/Issue104Tests.cs index fcbefedd..ca35689e 100644 --- a/tests/CommandLine.Tests/Unit/Issue104Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue104Tests.cs @@ -1,9 +1,8 @@ using System.Linq; +using Xunit; +using FluentAssertions; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; -using Xunit; -using Xunit.Abstractions; //Issue #104 //When outputting HelpText, the code will correctly list valid values for enum type options. However, if the option is a nullable enum type, then it will not list the valid values. diff --git a/tests/CommandLine.Tests/Unit/Issue389Tests.cs b/tests/CommandLine.Tests/Unit/Issue389Tests.cs index ef4f6169..5c81e6e0 100644 --- a/tests/CommandLine.Tests/Unit/Issue389Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue389Tests.cs @@ -1,9 +1,7 @@ -using CommandLine.Text; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using Xunit; +using CommandLine.Text; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/Issue418Tests.cs b/tests/CommandLine.Tests/Unit/Issue418Tests.cs index 9c8678bd..cac1e9f1 100644 --- a/tests/CommandLine.Tests/Unit/Issue418Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue418Tests.cs @@ -1,12 +1,8 @@ -using CommandLine.Text; using System; using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using CommandLine.Tests.Fakes; -using Xunit; using FluentAssertions; +using Xunit; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/Issue482Tests.cs b/tests/CommandLine.Tests/Unit/Issue482Tests.cs index 61bc1f25..44359358 100644 --- a/tests/CommandLine.Tests/Unit/Issue482Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue482Tests.cs @@ -1,16 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; -using CommandLine.Core; using System.Linq; -using System.Reflection; -using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; using Xunit; -using System.Text; -using Xunit.Sdk; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs index 97755940..178483ed 100644 --- a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs @@ -1,11 +1,9 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. -using System; -using System.Collections.Generic; using System.Linq; -using CommandLine.Tests.Fakes; using Xunit; using FluentAssertions; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs b/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs index 86691c8f..c8da9063 100644 --- a/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserSettingsTests.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FluentAssertions; +using System.IO; using Xunit; +using FluentAssertions; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index af5a6eec..fc2ae150 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -2,13 +2,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using CommandLine.Tests.Fakes; -using FluentAssertions; using Xunit; +using FluentAssertions; using CommandLine.Text; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { diff --git a/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs b/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs index 8fe73a60..fdbd6526 100644 --- a/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/StringBuilderExtensionsTests.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Text; -using System.Threading.Tasks; using Xunit; using FluentAssertions; using CommandLine.Infrastructure; diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs index 29f666b5..d777c8f9 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextAutoBuildFix.cs @@ -1,9 +1,9 @@ using System; using System.Linq; +using Xunit; +using FluentAssertions; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; -using Xunit; namespace CommandLine.Tests.Unit.Text { diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index dde64dc8..9811f7be 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -6,16 +6,13 @@ using System.Linq; using System.Reflection; using System.Text; - +using Xunit; +using FluentAssertions; using CommandLine.Core; using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; using CommandLine.Text; -using FluentAssertions; - -using Xunit; - namespace CommandLine.Tests.Unit.Text { public class HelpTextTests : IDisposable diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 90cb8929..9397b17b 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; -using CommandLine.Tests.Fakes; using Xunit; using FluentAssertions; -using Microsoft.FSharp.Core; +using CommandLine.Tests.Fakes; namespace CommandLine.Tests.Unit { From 8d14621d4aee6d5319b707994ab0adcb50bd81ab Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:31:26 +0100 Subject: [PATCH 121/198] Fixed indentation --- tests/CommandLine.Tests/Unit/VerbAttributeTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs b/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs index 4dc1dd30..386e3015 100644 --- a/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/VerbAttributeTests.cs @@ -4,7 +4,7 @@ namespace CommandLine.Tests { //Test localization of VerbAttribute - public class VerbAttributeTests + public class VerbAttributeTests { [Theory] [InlineData("", null, "")] @@ -22,6 +22,7 @@ public static void VerbHelpText(string helpText, Type resourceType, string expec Assert.Equal(expected, verbAttribute.HelpText); } + [Theory] [InlineData("HelpText", typeof(Fakes.NonStaticResource_WithNonStaticProperty))] [InlineData("WriteOnlyText", typeof(Fakes.NonStaticResource))] From f6edcaf21f94e1bb0c284edbb74b0f9e7de5c42c Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Wed, 8 Jan 2020 08:35:42 +0100 Subject: [PATCH 122/198] Refactored tests with FluentAssertions --- tests/CommandLine.Tests/Unit/Issue482Tests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Issue482Tests.cs b/tests/CommandLine.Tests/Unit/Issue482Tests.cs index 44359358..9d2ea971 100644 --- a/tests/CommandLine.Tests/Unit/Issue482Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue482Tests.cs @@ -4,6 +4,7 @@ using CommandLine.Tests.Fakes; using CommandLine.Text; using Xunit; +using FluentAssertions; namespace CommandLine.Tests.Unit { @@ -40,11 +41,11 @@ public void AutoBuild_without_ordering() "--version Display version information.", "value pos. 0" }; - Assert.Equal(expected.Count, helps.Count); + expected.Count.Should().Be(helps.Count); int i = 0; foreach (var expect in expected) { - Assert.Equal(expect.Trim(), helps[i].Trim()); + expect.Trim().Should().Be(helps[i].Trim()); i++; } @@ -88,11 +89,11 @@ public void AutoBuild_with_ordering() "--version Display version information.", "value pos. 0" }; - Assert.Equal(expected.Count, helps.Count); + expected.Count.Should().Be(helps.Count); int i = 0; foreach (var expect in expected) { - Assert.Equal(expect.Trim(), helps[i].Trim()); + expect.Trim().Should().Be(helps[i].Trim()); i++; } @@ -171,11 +172,11 @@ public void AutoBuild_with_ordering_on_shortName() "--version Display version information.", "value pos. 0" }; - Assert.Equal(expected.Count, helps.Count); + expected.Count.Should().Be(helps.Count); int i = 0; foreach (var expect in expected) { - Assert.Equal(expect.Trim(), helps[i].Trim()); + expect.Trim().Should().Be(helps[i].Trim()); i++; } } From 3c9ec3c284b504c26c7fb04a478eb64ab87404b0 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 8 Jan 2020 23:03:52 +0200 Subject: [PATCH 123/198] Fix Appveyor error --- src/CommandLine/Infrastructure/CSharpx/Either.cs | 4 ++-- tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CommandLine/Infrastructure/CSharpx/Either.cs b/src/CommandLine/Infrastructure/CSharpx/Either.cs index f3c80585..3d985948 100644 --- a/src/CommandLine/Infrastructure/CSharpx/Either.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Either.cs @@ -272,7 +272,7 @@ public static void Match(this Either either, Actio #endregion /// - /// Equivalent to monadic operation. + /// Equivalent to monadic operation. /// Builds a value in case by default. /// public static Either ToEither(this TRight value) @@ -312,4 +312,4 @@ public static bool IsRight(this Either either) return either.Tag == EitherType.Right; } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 9397b17b..3edb8f20 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -6,7 +6,10 @@ using Xunit; using FluentAssertions; using CommandLine.Tests.Fakes; - +#if !SKIP_FSHARP +using Microsoft.FSharp.Core; +#endif + namespace CommandLine.Tests.Unit { public class UnParserExtensionsTests From c3e490672dff77924bfd18b46fda6223556cc2f8 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 8 Jan 2020 23:03:52 +0200 Subject: [PATCH 124/198] Fix Appveyor error --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 73b580d2..827ded7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.{build} +version: 2.7.83-beta-{build} image: Visual Studio 2019 From 55cf7eda53fea5183e0a1fb6a662c17a70658bc2 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Fri, 10 Jan 2020 04:34:34 +0100 Subject: [PATCH 125/198] moh-hassan added as author and year updated in copyright (#563) --- src/CommandLine/CommandLine.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index b05cbb04..a3798437 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -11,13 +11,13 @@ true CommandLineParser CommandLineParser.FSharp - gsscoder;nemec;ericnewton76 + gsscoder;nemec;ericnewton76;moh-hassan Command Line Parser Library $(VersionSuffix) 0.0.0 Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. Terse syntax C# command line parser for .NET with F# support. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks. - Copyright (c) 2005 - 2018 Giacomo Stelluti Scala & Contributors + Copyright (c) 2005 - 2020 Giacomo Stelluti Scala & Contributors License.md CommandLine20.png https://github.com/commandlineparser/commandline From 9d95de60313697d55522cfa69eec45611207e301 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Fri, 10 Jan 2020 05:02:32 +0100 Subject: [PATCH 126/198] Upgraded RailwaySharp from Version 1.1.0 (#562) --- src/CommandLine/CommandLine.csproj | 2 +- src/CommandLine/Core/InstanceBuilder.cs | 6 +- src/CommandLine/Core/Tokenizer.cs | 2 +- .../Infrastructure/ErrorHandling.cs | 257 +++++++++--------- .../Infrastructure/ResultExtensions.cs | 33 --- .../Unit/Core/TokenizerTests.cs | 2 +- 6 files changed, 133 insertions(+), 169 deletions(-) delete mode 100644 src/CommandLine/Infrastructure/ResultExtensions.cs diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index a3798437..4dac1db1 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -4,7 +4,7 @@ CommandLine Library netstandard2.0;net40;net45;net461 - $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;ERRH_DISABLE_INLINE_METHODS;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND + $(DefineConstants);CSX_EITHER_INTERNAL;CSX_REM_EITHER_BEYOND_2;CSX_ENUM_INTERNAL;ERRH_INTERNAL;CSX_MAYBE_INTERNAL;CSX_REM_EITHER_FUNC;CSX_REM_CRYPTORAND;ERRH_ADD_MAYBE_METHODS $(DefineConstants);SKIP_FSHARP true ..\..\CommandLine.snk diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 4aff4080..0ae564b5 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -98,10 +98,10 @@ public static ParserResult Build( var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens)); var allErrors = - tokenizerResult.SuccessfulMessages() + tokenizerResult.SuccessMessages() .Concat(missingValueErrors) - .Concat(optionSpecPropsResult.SuccessfulMessages()) - .Concat(valueSpecPropsResult.SuccessfulMessages()) + .Concat(optionSpecPropsResult.SuccessMessages()) + .Concat(valueSpecPropsResult.SuccessMessages()) .Concat(validationErrors) .Concat(setPropertyErrors) .Memoize(); diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index 9fd8863c..ba6f1ef5 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -74,7 +74,7 @@ public static Result, Error> ExplodeOptionList( var flattened = exploded.SelectMany(x => x); - return Result.Succeed(flattened, tokenizerResult.SuccessfulMessages()); + return Result.Succeed(flattened, tokenizerResult.SuccessMessages()); } public static IEnumerable Normalize( diff --git a/src/CommandLine/Infrastructure/ErrorHandling.cs b/src/CommandLine/Infrastructure/ErrorHandling.cs index 142e1461..8aee4bac 100644 --- a/src/CommandLine/Infrastructure/ErrorHandling.cs +++ b/src/CommandLine/Infrastructure/ErrorHandling.cs @@ -1,62 +1,17 @@ //Use project level define(s) when referencing with Paket. -//#define ERRH_INTERNAL // Uncomment this to set visibility to internal. -//#define ERRH_DISABLE_INLINE_METHODS // Uncomment this to enable method inlining when compiling for >= NET 4.5. -//#define ERRH_BUILTIN_TYPES // Uncomment this to use built-in Unit type, instead of extenral identical CSharpx.Unit. +//#define ERRH_INTERNAL // Uncomment or define at build time to set accessibility to internal. +//#define ERRH_ENABLE_INLINE_METHODS // Uncomment or define at build time to enable method inlining when compiling for >= NET 4.5. +//#define ERRH_ADD_MAYBE_METHODS // Uncomment or define at build time to add methods that use Maybe type using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -#if !ERRH_BUILTIN_TYPES +#if ERRH_ADD_MAYBE_METHODS using CSharpx; #endif namespace RailwaySharp.ErrorHandling { - #region Unit Type -#if ERRH_BUILTIN_TYPES -#if !ERRH_INTERNAL - public -#endif - struct Unit : IEquatable - { - private static readonly Unit @default = new Unit(); - - public bool Equals(Unit other) - { - return true; - } - - public override bool Equals(object obj) - { - return obj is Unit; - } - - public override int GetHashCode() - { - return 0; - } - - public override string ToString() - { - return "()"; - } - - public static bool operator ==(Unit first, Unit second) - { - return true; - } - - public static bool operator !=(Unit first, Unit second) - { - return false; - } - - public static Unit Default { get { return @default; } } - } -#endif - #endregion - #if !ERRH_INTERNAL public #endif @@ -76,29 +31,28 @@ enum ResultType #endif abstract class Result { - private readonly ResultType tag; + private readonly ResultType _tag; protected Result(ResultType tag) { - this.tag = tag; + _tag = tag; } public ResultType Tag { - get { return tag; } + get { return _tag; } } public override string ToString() { - switch (Tag) - { - case ResultType.Ok: + switch (Tag) { + default: var ok = (Ok)this; return string.Format( "OK: {0} - {1}", ok.Success, string.Join(Environment.NewLine, ok.Messages.Select(v => v.ToString()))); - default: + case ResultType.Bad: var bad = (Bad)this; return string.Format( "Error: {0}", @@ -117,22 +71,24 @@ public override string ToString() #endif sealed class Ok : Result { - private readonly Tuple> value; + private readonly Tuple> _value; public Ok(TSuccess success, IEnumerable messages) : base(ResultType.Ok) { - this.value = Tuple.Create(success, messages); + if (messages == null) throw new ArgumentNullException(nameof(messages)); + + _value = Tuple.Create(success, messages); } public TSuccess Success { - get { return value.Item1; } + get { return _value.Item1; } } public IEnumerable Messages { - get { return value.Item2; } + get { return _value.Item2; } } } @@ -146,17 +102,19 @@ public IEnumerable Messages #endif sealed class Bad : Result { - private readonly IEnumerable messages; + private readonly IEnumerable _messages; public Bad(IEnumerable messages) : base(ResultType.Bad) { - this.messages = messages; + if (messages == null) throw new ArgumentException(nameof(messages)); + + _messages = messages; } public IEnumerable Messages { - get { return messages; } + get { return _messages; } } } @@ -170,6 +128,8 @@ static class Result /// public static Result FailWith(IEnumerable messages) { + if (messages == null) throw new ArgumentException(nameof(messages)); + return new Bad(messages); } @@ -178,6 +138,8 @@ public static Result FailWith(IEnumerabl /// public static Result FailWith(TMessage message) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Bad(new[] { message }); } @@ -194,6 +156,8 @@ public static Result Succeed(TSuccess va /// public static Result Succeed(TSuccess value, TMessage message) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Ok(value, new[] { message }); } @@ -202,6 +166,8 @@ public static Result Succeed(TSuccess va /// public static Result Succeed(TSuccess value, IEnumerable messages) { + if (messages == null) throw new ArgumentException(nameof(messages)); + return new Ok(value, messages); } @@ -210,13 +176,13 @@ public static Result Succeed(TSuccess va /// public static Result Try(Func func) { - try - { + if (func == null) throw new ArgumentException(nameof(func)); + + try { return new Ok( func(), Enumerable.Empty()); } - catch (Exception ex) - { + catch (Exception ex) { return new Bad( new[] { ex }); } @@ -231,7 +197,7 @@ static class Trial /// /// Wraps a value in a Success. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Ok(TSuccess value) @@ -242,7 +208,7 @@ public static Result Ok(TSuccess value) /// /// Wraps a value in a Success. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Pass(TSuccess value) @@ -253,29 +219,33 @@ public static Result Pass(TSuccess value /// /// Wraps a value in a Success and adds a message. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Warn(TMessage message, TSuccess value) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Ok(value, new[] { message }); } /// /// Wraps a message in a Failure. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Fail(TMessage message) { + if (message == null) throw new ArgumentException(nameof(message)); + return new Bad(new[] { message }); } /// /// Returns true if the result was not successful. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static bool Failed(Result result) @@ -286,7 +256,7 @@ public static bool Failed(Result result) /// /// Takes a Result and maps it with successFunc if it is a Success otherwise it maps it with failureFunc. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TResult Either( @@ -294,9 +264,11 @@ public static TResult Either( Func, TResult> failureFunc, Result trialResult) { + if (successFunc == null) throw new ArgumentException(nameof(successFunc)); + if (failureFunc == null) throw new ArgumentException(nameof(failureFunc)); + var ok = trialResult as Ok; - if (ok != null) - { + if (ok != null) { return successFunc(ok.Success, ok.Messages); } var bad = (Bad)trialResult; @@ -307,7 +279,7 @@ public static TResult Either( /// If the given result is a Success the wrapped value will be returned. /// Otherwise the function throws an exception with Failure message of the result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TSuccess ReturnOrFail(Result result) @@ -325,13 +297,15 @@ public static TSuccess ReturnOrFail(Result /// Appends the given messages with the messages in the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result MergeMessages( IEnumerable messages, Result result) { + if (messages == null) throw new ArgumentException(nameof(messages)); + Func, Result> successFunc = (succ, msgs) => new Ok( @@ -347,13 +321,15 @@ public static Result MergeMessages( /// If the result is a Success it executes the given function on the value. /// Otherwise the exisiting failure is propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Bind( Func> func, Result result) { + if (func == null) throw new ArgumentException(nameof(func)); + Func, Result> successFunc = (succ, msgs) => MergeMessages(msgs, func(succ)); @@ -366,7 +342,7 @@ public static Result Bind( /// /// Flattens a nested result given the Failure types are equal. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Flatten( @@ -374,46 +350,44 @@ public static Result Flatten( { return Bind(x => x, result); } - + /// /// If the wrapped function is a success and the given result is a success the function is applied on the value. /// Otherwise the exisiting error messages are propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Apply( Result, TMessage> wrappedFunction, Result result) { - if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Ok) - { + if (wrappedFunction == null) throw new ArgumentException(nameof(wrappedFunction)); + + if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Ok) { var ok1 = (Ok, TMessage>)wrappedFunction; var ok2 = (Ok)result; return new Ok( ok1.Success(ok2.Success), ok1.Messages.Concat(ok2.Messages)); } - if (wrappedFunction.Tag == ResultType.Bad && result.Tag == ResultType.Ok) - { + if (wrappedFunction.Tag == ResultType.Bad && result.Tag == ResultType.Ok) { return new Bad(((Bad)result).Messages); } - if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Bad) - { + if (wrappedFunction.Tag == ResultType.Ok && result.Tag == ResultType.Bad) { return new Bad( ((Bad)result).Messages); } var bad1 = (Bad, TMessage>)wrappedFunction; var bad2 = (Bad)result; - return new Bad(bad1.Messages.Concat(bad2.Messages)); } /// /// Lifts a function into a Result container and applies it on the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Lift( @@ -426,22 +400,22 @@ public static Result Lift( /// /// Promote a function to a monad/applicative, scanning the monadic/applicative arguments from left to right. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Lift2( Func> func, - Result a, - Result b) + Result first, + Result second) { - return Apply(Lift(func, a), b); + return Apply(Lift(func, first), second); } /// /// Collects a sequence of Results and accumulates their values. /// If the sequence contains an error the error will be propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result, TMessage> Collect( @@ -449,21 +423,18 @@ public static Result, TMessage> Collect, Result, TMessage>, Result, TMessage>>( - null, - (result, next) => - { - if (result.Tag == ResultType.Ok && next.Tag == ResultType.Ok) - { + null, + (result, next) => { + if (result.Tag == ResultType.Ok && next.Tag == ResultType.Ok) { var ok1 = (Ok, TMessage>)result; var ok2 = (Ok)next; return new Ok, TMessage>( - Enumerable.Empty().Concat(new[] { ok2.Success }).Concat(ok1.Success), + Enumerable.Empty().Concat(new [] { ok2.Success }).Concat(ok1.Success), ok1.Messages.Concat(ok2.Messages)); } if ((result.Tag == ResultType.Ok && next.Tag == ResultType.Bad) - || (result.Tag == ResultType.Bad && next.Tag == ResultType.Ok)) - { + || (result.Tag == ResultType.Bad && next.Tag == ResultType.Ok)) { var m1 = result.Tag == ResultType.Ok ? ((Ok, TMessage>)result).Messages : ((Bad)next).Messages; @@ -472,8 +443,9 @@ public static Result, TMessage> Collect)next).Messages; return new Bad, TMessage>(m1.Concat(m2)); } + var bad1 = (Bad, TMessage>)result; - var bad2 = (Bad)next; + var bad2 = (Bad)next; return new Bad, TMessage>(bad1.Messages.Concat(bad2.Messages)); }, x => x)); } @@ -490,19 +462,22 @@ static class ResultExtensions /// /// Allows pattern matching on Results. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static void Match(this Result result, Action> ifSuccess, Action> ifFailure) { + if (ifSuccess == null) throw new ArgumentException(nameof(ifSuccess)); + if (ifFailure == null) throw new ArgumentException(nameof(ifFailure)); + var ok = result as Ok; - if (ok != null) - { + if (ok != null) { ifSuccess(ok.Success, ok.Messages); return; } + var bad = (Bad)result; ifFailure(bad.Messages); } @@ -510,26 +485,20 @@ public static void Match(this Result res /// /// Allows pattern matching on Results. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TResult Either(this Result result, Func, TResult> ifSuccess, Func, TResult> ifFailure) { - var ok = result as Ok; - if (ok != null) - { - return ifSuccess(ok.Success, ok.Messages); - } - var bad = (Bad)result; - return ifFailure(bad.Messages); + return Trial.Either(ifSuccess, ifFailure, result); } /// /// Lifts a Func into a Result and applies it on the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Map(this Result result, @@ -542,7 +511,7 @@ public static Result Map(this Re /// Collects a sequence of Results and accumulates their values. /// If the sequence contains an error the error will be propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result, TMessage> Collect( @@ -555,18 +524,16 @@ public static Result, TMessage> Collect -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result, TMessage> Flatten(this Result>, TMessage> result) { - if (result.Tag == ResultType.Ok) - { + if (result.Tag == ResultType.Ok) { var ok = (Ok>, TMessage>)result; var values = ok.Success; var result1 = Collect(values); - if (result1.Tag == ResultType.Ok) - { + if (result1.Tag == ResultType.Ok) { var ok1 = (Ok, TMessage>)result1; return new Ok, TMessage>(ok1.Success, ok1.Messages); } @@ -581,7 +548,7 @@ public static Result, TMessage> Flatten -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result SelectMany(this Result result, @@ -595,7 +562,7 @@ public static Result SelectMany( /// If the result of the Func is a Success it maps it using the given Func. /// Otherwise the exisiting failure is propagated. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result SelectMany( @@ -603,6 +570,9 @@ public static Result SelectMany> func, Func mapperFunc) { + if (func == null) throw new ArgumentException(nameof(func)); + if (mapperFunc == null) throw new ArgumentException(nameof(mapperFunc)); + Func> curriedMapper = suc => val => mapperFunc(suc, val); Func< Result, @@ -616,7 +586,7 @@ public static Result SelectMany /// Lifts a Func into a Result and applies it on the given result. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static Result Select(this Result result, @@ -628,13 +598,12 @@ public static Result Select(this /// /// Returns the error messages or fails if the result was a success. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static IEnumerable FailedWith(this Result result) { - if (result.Tag == ResultType.Ok) - { + if (result.Tag == ResultType.Ok) { var ok = (Ok)result; throw new Exception( string.Format("Result was a success: {0} - {1}", @@ -648,13 +617,12 @@ public static IEnumerable FailedWith(this Result /// Returns the result or fails if the result was an error. /// -#if !ERRH_DISABLE_INLINE_METHODS +#if ERRH_ENABLE_INLINE_METHODS [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public static TSuccess SucceededWith(this Result result) { - if (result.Tag == ResultType.Ok) - { + if (result.Tag == ResultType.Ok) { var ok = (Ok)result; return ok.Success; } @@ -663,5 +631,34 @@ public static TSuccess SucceededWith(this Result m.ToString())))); } + + /// + /// Returns messages in case of success, otherwise an empty sequence. + /// + public static IEnumerable SuccessMessages(this Result result) + { + if (result.Tag == ResultType.Ok) { + var ok = (Ok)result; + return ok.Messages; + } + return Enumerable.Empty(); + } + +#if ERRH_ADD_MAYBE_METHODS +#if ERRH_ENABLE_INLINE_METHODS + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + /// + /// Builds a Maybe type instance from a Result one. + /// + public static Maybe ToMaybe(this Result result) + { + if (result.Tag == ResultType.Ok) { + var ok = (Ok)result; + return Maybe.Just(ok.Success); + } + return Maybe.Nothing(); + } +#endif } } \ No newline at end of file diff --git a/src/CommandLine/Infrastructure/ResultExtensions.cs b/src/CommandLine/Infrastructure/ResultExtensions.cs deleted file mode 100644 index bdc2a480..00000000 --- a/src/CommandLine/Infrastructure/ResultExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System.Collections.Generic; -using System.Linq; - -using CSharpx; -using RailwaySharp.ErrorHandling; - -namespace CommandLine.Infrastructure -{ - static class ResultExtensions - { - public static IEnumerable SuccessfulMessages(this Result result) - { - if (result.Tag == ResultType.Ok) - { - var ok = (Ok)result; - return ok.Messages; - } - return Enumerable.Empty(); - } - - public static Maybe ToMaybe(this Result result) - { - if (result.Tag == ResultType.Ok) - { - var ok = (Ok)result; - return Maybe.Just(ok.Success); - } - return Maybe.Nothing(); - } - } -} \ No newline at end of file diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index 61c7f184..32d79b4f 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -116,7 +116,7 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() var result = Tokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound, token => token); - var tokens = result.SuccessfulMessages(); + var tokens = result.SuccessMessages(); Assert.NotNull(tokens); Assert.Equal(2, tokens.Count()); From 81dc3f28b94c22a107c98f6a33c876716f8214c2 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 2 Feb 2020 20:02:04 +0200 Subject: [PATCH 127/198] Fix option groups (#575) * Add MyGet package provider to consume the daily builds. (#566) * Options groups take in account default value * Do not allow options groups and exclusive set names to be used together * Multiple group errors are shown together * MissingGroupOptionError compare option names Co-authored-by: Mohamed Hassan --- appveyor.yml | 18 ++++-- .../Core/SpecificationPropertyRules.cs | 28 ++++++++- src/CommandLine/Error.cs | 38 ++++++++++++- .../Fakes/Options_With_Multiple_Groups.cs | 20 +++++++ ...s_With_OptionGroup_MutuallyExclusiveSet.cs | 14 +++++ ...With_OptionGroup_WithOptionDefaultValue.cs | 14 +++++ .../Unit/Core/InstanceBuilderTests.cs | 57 +++++++++++++++++++ 7 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs diff --git a/appveyor.yml b/appveyor.yml index 827ded7f..e9af5e38 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,12 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.83-beta-{build} +version: 2.7.84-beta-{build} image: Visual Studio 2019 clone_depth: 1 pull_requests: - do_not_increment_build_number: true + do_not_increment_build_number: false init: - ps: | @@ -15,8 +15,9 @@ init: if ($env:APPVEYOR_REPO_TAG -eq "true") { $ver = $env:APPVEYOR_REPO_TAG_NAME if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) } - Update-AppveyorBuild -Version $ver - } + Update-AppveyorBuild -Version $ver + } + - ps: Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow environment: matrix: @@ -57,3 +58,12 @@ deploy: artifact: 'NuGetPackages' on: APPVEYOR_REPO_TAG: true + +#myget +- provider: NuGet + server: https://www.myget.org/F/commandlineparser/api/v2/package + api_key: + secure: ltHh/DsAk+Y7qbJwzUO4+i1U+7uGTLVYXTdW0+Rk2z7jqj5DDNNlih9J8K7bU4bH + artifact: 'NuGetPackages' + symbol_server: https://www.myget.org/F/commandlineparser/symbols/api/v2/package + diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 9122ee3a..5dc1a406 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -18,12 +18,35 @@ public static IEnumerable, IEnumerable, IEnumerable> EnforceMutuallyExclusiveSetAndGroupAreNotUsedTogether() + { + return specProps => + { + var options = + from sp in specProps + where sp.Specification.IsOption() + let o = (OptionSpecification)sp.Specification + where o.SetName.Length > 0 + where o.Group.Length > 0 + select o; + + if (options.Any()) + { + return from o in options + select new GroupOptionAmbiguityError(new NameInfo(o.ShortName, o.LongName)); + } + + return Enumerable.Empty(); + }; + } + private static Func, IEnumerable> EnforceGroup() { return specProps => @@ -36,14 +59,15 @@ where o.Group.Length > 0 select new { Option = o, - Value = sp.Value + Value = sp.Value, + DefaultValue = sp.Specification.DefaultValue }; var groups = from o in optionsValues group o by o.Option.Group into g select g; - var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing())); + var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing() && g.DefaultValue.IsNothing())); if (errorGroups.Any()) { diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index e54dbf6a..b6e8a605 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace CommandLine { @@ -74,8 +75,11 @@ public enum ErrorType /// /// Value of type. /// - MissingGroupOptionError - + MissingGroupOptionError, + /// + /// Value of type. + /// + GroupOptionAmbiguityError } /// @@ -532,7 +536,7 @@ internal InvalidAttributeConfigurationError() } } - public sealed class MissingGroupOptionError : Error + public sealed class MissingGroupOptionError : Error, IEquatable, IEquatable { public const string ErrorMessage = "At least one option in a group must have value."; @@ -555,5 +559,33 @@ public IEnumerable Names { get { return names; } } + + public new bool Equals(Error obj) + { + var other = obj as MissingGroupOptionError; + if (other != null) + { + return Equals(other); + } + + return base.Equals(obj); + } + + public bool Equals(MissingGroupOptionError other) + { + if (other == null) + { + return false; + } + + return Group.Equals(other.Group) && Names.SequenceEqual(other.Names); + } + } + + public sealed class GroupOptionAmbiguityError : NamedError + { + internal GroupOptionAmbiguityError(NameInfo option) + : base(ErrorType.GroupOptionAmbiguityError, option) + { } } } diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs b/tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs new file mode 100644 index 00000000..8f2d21ab --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Multiple_Groups.cs @@ -0,0 +1,20 @@ +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Multiple_Groups + { + [Option('v', "version")] + public string Version { get; set; } + + [Option("option11", Group = "err-group")] + public string Option11 { get; set; } + + [Option("option12", Group = "err-group")] + public string Option12 { get; set; } + + [Option("option21", Group = "err-group2")] + public string Option21 { get; set; } + + [Option("option22", Group = "err-group2")] + public string Option22 { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs new file mode 100644 index 00000000..52ead41c --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_MutuallyExclusiveSet.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup_MutuallyExclusiveSet + { + [Option(HelpText = "Define a string value here.", Group = "test", SetName = "setname", Default = "qwerty123")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "test", SetName = "setname")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs new file mode 100644 index 00000000..9ae3a59e --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithOptionDefaultValue.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup_WithOptionDefaultValue + { + [Option(HelpText = "Define a string value here.", Required = true, Group = "test", Default = "qwerty123")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "test")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index c2cbb77a..643878fd 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1110,6 +1110,34 @@ public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError() ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); } + [Fact] + public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionErrors() + { + // Fixture setup + var optionNames1 = new List + { + new NameInfo("", "option11"), + new NameInfo("", "option12") + }; + var optionNames2 = new List + { + new NameInfo("", "option21"), + new NameInfo("", "option22") + }; + var expectedResult = new[] + { + new MissingGroupOptionError("err-group", optionNames1), + new MissingGroupOptionError("err-group2", optionNames2) + }; + + // Exercize system + var result = InvokeBuild( + new[] { "-v 10.42" }); + + // Verify outcome + ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); + } + [Theory] [InlineData("-v", "10.5", "--option1", "test1", "--option2", "test2")] [InlineData("-v", "10.5", "--option1", "test1")] @@ -1164,6 +1192,35 @@ public void Options_In_Group_Ignore_Option_Group_If_Option_Group_Name_Empty() errors.Should().BeEquivalentTo(expectedResult); } + [Fact] + public void Options_In_Group_Use_Option_Default_Value_When_Available() + { + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + } + + [Fact] + public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() + { + var expectedResult = new[] + { + new GroupOptionAmbiguityError(new NameInfo("", "stringvalue")), + new GroupOptionAmbiguityError(new NameInfo("s", "shortandlong")) + }; + + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + var errors = ((NotParsed)result).Errors; + + errors.Should().BeEquivalentTo(expectedResult); + } + private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From 99a619ba19d5e7b68842edaafa926a6212bd243a Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sun, 2 Feb 2020 20:09:08 +0200 Subject: [PATCH 128/198] Update Appveyor --- appveyor.yml | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e9af5e38..ff6d4476 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,12 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.7.84-beta-{build} +version: 2.8.0-beta-{build} image: Visual Studio 2019 clone_depth: 1 pull_requests: - do_not_increment_build_number: false + do_not_increment_build_number: true init: - ps: | @@ -15,9 +15,23 @@ init: if ($env:APPVEYOR_REPO_TAG -eq "true") { $ver = $env:APPVEYOR_REPO_TAG_NAME if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) } - Update-AppveyorBuild -Version $ver - } - - ps: Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow + $env:PACKAGE_VERSION = $ver + } else { + $env:PACKAGE_VERSION = $env:APPVEYOR_BUILD_VERSION + } + - ps: | + Write-Host "PACKAGE_VERSION:$env:PACKAGE_VERSION | APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow + Write-Host "APPVEYOR_REPO_TAG_NAME:$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Yellow + +skip_commits: + files: + - docs/* + - art/* + - '**/*.md' + #- .travis.yml + - .gitignore + - .editorconfig + message: /updated readme.*|update readme.*s|update docs.*|update version.*|update changelog.*/ environment: matrix: @@ -25,13 +39,13 @@ environment: - BUILD_TARGET: fsharp build_script: -- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% +- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% test_script: - cmd: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=%BUILD_TARGET% after_test: -- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% +- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% artifacts: - path: 'src/CommandLine/bin/Release/*.nupkg' @@ -59,11 +73,3 @@ deploy: on: APPVEYOR_REPO_TAG: true -#myget -- provider: NuGet - server: https://www.myget.org/F/commandlineparser/api/v2/package - api_key: - secure: ltHh/DsAk+Y7qbJwzUO4+i1U+7uGTLVYXTdW0+Rk2z7jqj5DDNNlih9J8K7bU4bH - artifact: 'NuGetPackages' - symbol_server: https://www.myget.org/F/commandlineparser/symbols/api/v2/package - From fdf2d3f9a28c8e92f446ff84bc1d5edd730e2ce5 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Feb 2020 09:06:46 +0200 Subject: [PATCH 129/198] Fix exception of groupOptionAmbiguityError --- src/CommandLine/Error.cs | 6 +++++- src/CommandLine/Text/SentenceBuilder.cs | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index b6e8a605..ed2b0dce 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -584,8 +584,12 @@ public bool Equals(MissingGroupOptionError other) public sealed class GroupOptionAmbiguityError : NamedError { + public NameInfo Option; + internal GroupOptionAmbiguityError(NameInfo option) : base(ErrorType.GroupOptionAmbiguityError, option) - { } + { + Option = option; + } } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index d58bf9c7..cdf8f7d2 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -158,6 +158,9 @@ public override Func FormatError "' (", string.Join(", ", missingGroupOptionError.Names.Select(n => n.NameText)), ") is required."); + case ErrorType.GroupOptionAmbiguityError: + var groupOptionAmbiguityError = (GroupOptionAmbiguityError)error; + return "Both SetName and Group are not allowed in option: (".JoinTo(groupOptionAmbiguityError.Option.NameText, ")"); } throw new InvalidOperationException(); }; From e826508c52814f6f65e0453d75ca4b4072f76b84 Mon Sep 17 00:00:00 2001 From: Mathis Rech Date: Sat, 28 Dec 2019 21:16:38 +0100 Subject: [PATCH 130/198] Add default verb support --- demo/ReadText.Demo/Options.cs | 4 +- src/CommandLine/Core/InstanceChooser.cs | 46 ++++++++++++++++++--- src/CommandLine/Core/Verb.cs | 19 +++++++-- src/CommandLine/Error.cs | 19 ++++++++- src/CommandLine/Text/SentenceBuilder.cs | 3 ++ src/CommandLine/VerbAttribute.cs | 17 ++++++-- tests/CommandLine.Tests/Fakes/Verb_Fakes.cs | 23 ++++++++++- tests/CommandLine.Tests/Unit/ParserTests.cs | 45 ++++++++++++++++++++ 8 files changed, 160 insertions(+), 16 deletions(-) diff --git a/demo/ReadText.Demo/Options.cs b/demo/ReadText.Demo/Options.cs index ed4db350..3b14014a 100644 --- a/demo/ReadText.Demo/Options.cs +++ b/demo/ReadText.Demo/Options.cs @@ -27,7 +27,7 @@ interface IOptions string FileName { get; set; } } - [Verb("head", HelpText = "Displays first lines of a file.")] + [Verb("head", true, HelpText = "Displays first lines of a file.")] class HeadOptions : IOptions { public uint? Lines { get; set; } @@ -62,4 +62,4 @@ class TailOptions : IOptions public string FileName { get; set; } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 86917233..f3ab9b99 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -23,6 +23,15 @@ public static ParserResult Choose( bool autoVersion, IEnumerable nonFatalErrors) { + var verbs = Verb.SelectFromTypes(types); + var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); + + int defaultVerbCount = defaultVerbs.Count(); + if (defaultVerbCount > 1) + return MakeNotParsed(types, new MultipleDefaultVerbsError()); + + var defaultVerb = defaultVerbCount == 1 ? defaultVerbs.First() : null; + Func> choose = () => { var firstArg = arguments.First(); @@ -31,25 +40,52 @@ public static ParserResult Choose( nameComparer.Equals(command, firstArg) || nameComparer.Equals(string.Concat("--", command), firstArg); - var verbs = Verb.SelectFromTypes(types); - return (autoHelp && preprocCompare("help")) ? MakeNotParsed(types, MakeHelpVerbRequestedError(verbs, arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); }; return arguments.Any() ? choose() - : MakeNotParsed(types, new NoVerbSelectedError()); + : (defaultVerbCount == 1 + ? MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors) + : MakeNotParsed(types, new NoVerbSelectedError())); + } + + private static ParserResult MatchDefaultVerb( + Func, IEnumerable, Result, Error>> tokenizer, + IEnumerable> verbs, + Tuple defaultVerb, + IEnumerable arguments, + StringComparer nameComparer, + bool ignoreValueCase, + CultureInfo parsingCulture, + bool autoHelp, + bool autoVersion, + IEnumerable nonFatalErrors) + { + return !(defaultVerb is null) + ? InstanceBuilder.Build( + Maybe.Just>(() => defaultVerb.Item2.AutoDefault()), + tokenizer, + arguments, + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + nonFatalErrors) + : MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First())); } private static ParserResult MatchVerb( Func, IEnumerable, Result, Error>> tokenizer, IEnumerable> verbs, + Tuple defaultVerb, IEnumerable arguments, StringComparer nameComparer, bool ignoreValueCase, @@ -71,7 +107,7 @@ private static ParserResult MatchVerb( autoHelp, autoVersion, nonFatalErrors) - : MakeNotParsed(verbs.Select(v => v.Item2), new BadVerbSelectedError(arguments.First())); + : MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); } private static HelpVerbRequestedError MakeHelpVerbRequestedError( diff --git a/src/CommandLine/Core/Verb.cs b/src/CommandLine/Core/Verb.cs index 2fb6674d..3a7f12a3 100644 --- a/src/CommandLine/Core/Verb.cs +++ b/src/CommandLine/Core/Verb.cs @@ -12,12 +12,17 @@ sealed class Verb private readonly string name; private readonly string helpText; private readonly bool hidden; + private readonly bool isDefault; - public Verb(string name, string helpText, bool hidden = false) + public Verb(string name, string helpText, bool hidden = false, bool isDefault = false) { - this.name = name ?? throw new ArgumentNullException(nameof(name)); + if ( string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + this.name = name; + this.helpText = helpText ?? throw new ArgumentNullException(nameof(helpText)); this.hidden = hidden; + this.isDefault = isDefault; } public string Name @@ -35,12 +40,18 @@ public bool Hidden get { return hidden; } } + public bool IsDefault + { + get => isDefault; + } + public static Verb FromAttribute(VerbAttribute attribute) { return new Verb( attribute.Name, attribute.HelpText, - attribute.Hidden + attribute.Hidden, + attribute.IsDefault ); } @@ -54,4 +65,4 @@ select Tuple.Create( type); } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index ed2b0dce..21c2fdcd 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -79,7 +79,12 @@ public enum ErrorType /// /// Value of type. /// - GroupOptionAmbiguityError + GroupOptionAmbiguityError, + /// + /// Value of type. + /// + MultipleDefaultVerbsError + } /// @@ -592,4 +597,16 @@ internal GroupOptionAmbiguityError(NameInfo option) Option = option; } } + + /// + /// Models an error generated when multiple default verbs are defined. + /// + public sealed class MultipleDefaultVerbsError : Error + { + public const string ErrorMessage = "More than one default verb is not allowed."; + + internal MultipleDefaultVerbsError() + : base(ErrorType.MultipleDefaultVerbsError) + { } + } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index cdf8f7d2..c8537542 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -161,6 +161,9 @@ public override Func FormatError case ErrorType.GroupOptionAmbiguityError: var groupOptionAmbiguityError = (GroupOptionAmbiguityError)error; return "Both SetName and Group are not allowed in option: (".JoinTo(groupOptionAmbiguityError.Option.NameText, ")"); + case ErrorType.MultipleDefaultVerbsError: + return MultipleDefaultVerbsError.ErrorMessage; + } throw new InvalidOperationException(); }; diff --git a/src/CommandLine/VerbAttribute.cs b/src/CommandLine/VerbAttribute.cs index 5515bd20..57318b3a 100644 --- a/src/CommandLine/VerbAttribute.cs +++ b/src/CommandLine/VerbAttribute.cs @@ -12,6 +12,7 @@ namespace CommandLine public class VerbAttribute : Attribute { private readonly string name; + private readonly bool isDefault; private Infrastructure.LocalizableAttributeProperty helpText; private Type resourceType; @@ -19,12 +20,14 @@ public class VerbAttribute : Attribute /// Initializes a new instance of the class. /// /// The long name of the verb command. - /// Thrown if is null, empty or whitespace. - public VerbAttribute(string name) + /// Whether the verb is the default verb. + /// Thrown if is null, empty or whitespace and is false. + public VerbAttribute(string name, bool isDefault = false) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name"); - this.name = name; + this.name = name ; + this.isDefault = isDefault; helpText = new Infrastructure.LocalizableAttributeProperty(nameof(HelpText)); resourceType = null; } @@ -62,5 +65,13 @@ public Type ResourceType get => resourceType; set => resourceType =helpText.ResourceType = value; } + + /// + /// Gets whether this verb is the default verb. + /// + public bool IsDefault + { + get => isDefault; + } } } diff --git a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs index 133c65b5..83426bd7 100644 --- a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs +++ b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs @@ -85,4 +85,25 @@ class Verb_With_Option_And_Value_Of_String_Type [Value(0)] public string PosValue { get; set; } } -} \ No newline at end of file + + [Verb("default1", true)] + class Default_Verb_One + { + [Option('t', "test-one")] + public bool TestValueOne { get; set; } + } + + [Verb("default2", true)] + class Default_Verb_Two + { + [Option('t', "test-two")] + public bool TestValueTwo { get; set; } + } + + [Verb(null, true)] + class Default_Verb_With_Empty_Name + { + [Option('t', "test")] + public bool TestValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index fc2ae150..fe8c4d65 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -824,5 +824,50 @@ public void Blank_lines_are_inserted_between_verbs() // Teardown } + + [Fact] + public void Parse_default_verb_implicit() + { + var parser = Parser.Default; + parser.ParseArguments(new[] { "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => + { + Assert.True(args.TestValueOne); + }); + } + + [Fact] + public void Parse_default_verb_explicit() + { + var parser = Parser.Default; + parser.ParseArguments(new[] { "default1", "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => + { + Assert.True(args.TestValueOne); + }); + } + + [Fact] + public void Parse_multiple_default_verbs() + { + var parser = Parser.Default; + parser.ParseArguments(new string[] { }) + .WithNotParsed(errors => Assert.IsType(errors.First())) + .WithParsed(args => throw new InvalidOperationException("Should not be parsed.")); + } + + [Fact] + public void Parse_default_verb_with_empty_name() + { + var parser = Parser.Default; + parser.ParseArguments(new[] { "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => + { + Assert.True(args.TestValue); + }); + } } } From f1f4649855670e62450585e3855e4ed268907db2 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Feb 2020 11:08:43 +0200 Subject: [PATCH 131/198] Update helptext to show Default Verb --- src/CommandLine/Text/HelpText.cs | 2 +- tests/CommandLine.Tests/Fakes/Verb_Fakes.cs | 13 ++++++++++++ tests/CommandLine.Tests/Unit/ParserTests.cs | 23 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 94b7e4f3..a04de0c7 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -853,7 +853,7 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable string.Empty, verbTuple.Item1.Name, false, - verbTuple.Item1.HelpText, + verbTuple.Item1.IsDefault? "(Default Verb) "+verbTuple.Item1.HelpText: verbTuple.Item1.HelpText, //Default verb string.Empty, verbTuple.Item1.Hidden); if (autoHelp) diff --git a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs index 83426bd7..9710d0de 100644 --- a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs +++ b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs @@ -18,7 +18,20 @@ public class Add_Verb [Value(0)] public string FileName { get; set; } } + [Verb("add", isDefault:true,HelpText = "Add file contents to the index.")] + public class Add_Verb_As_Default + { + [Option('p', "patch", SetName = "mode-p", + HelpText = "Interactively choose hunks of patch between the index and the work tree and add them to the index.")] + public bool Patch { get; set; } + [Option('f', "force", SetName = "mode-f", + HelpText = "Allow adding otherwise ignored files.")] + public bool Force { get; set; } + + [Value(0)] + public string FileName { get; set; } + } [Verb("commit", HelpText = "Record changes to the repository.")] public class Commit_Verb { diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index fe8c4d65..b6b08ca3 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -370,7 +370,30 @@ public void Implicit_help_screen_in_verb_scenario() lines[8].Should().BeEquivalentTo("version Display version information."); // Teardown } + + [Fact] + public void Help_screen_in_default_verb_scenario() + { + // Fixture setup + var help = new StringWriter(); + var sut = new Parser(config => config.HelpWriter = help); + // Exercise system + sut.ParseArguments(new string[] {"--help" }); + var result = help.ToString(); + + // Verify outcome + result.Length.Should().BeGreaterThan(0); + var lines = result.ToNotEmptyLines().TrimStringArray(); + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[2].Should().BeEquivalentTo("add (Default Verb) Add file contents to the index."); + lines[3].Should().BeEquivalentTo("commit Record changes to the repository."); + lines[4].Should().BeEquivalentTo("clone Clone a repository into a new directory."); + lines[5].Should().BeEquivalentTo("help Display more information on a specific command."); + lines[6].Should().BeEquivalentTo("version Display version information."); + + } [Fact] public void Double_dash_help_dispalys_verbs_index_in_verbs_scenario() { From 9dc43b53c3e15a310b77dae3d8c9400a7a7863e0 Mon Sep 17 00:00:00 2001 From: kendfrey Date: Mon, 3 Feb 2020 04:34:06 -0500 Subject: [PATCH 132/198] SkipDefault was being ignored by [Usage] Examples (#565) --- src/CommandLine/Text/HelpText.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index a04de0c7..e9ce218d 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -741,6 +741,7 @@ public static IEnumerable RenderUsageTextAsLines(ParserResult pars config.PreferShortName = s.PreferShortName; config.GroupSwitches = s.GroupSwitches; config.UseEqualToken = s.UseEqualToken; + config.SkipDefault = s.SkipDefault; })); yield return commandLine.ToString(); } From 8cfda3ec7bebb3b1e9504e12d08c099280462cb5 Mon Sep 17 00:00:00 2001 From: edmondshtogu Date: Mon, 3 Feb 2020 10:35:53 +0100 Subject: [PATCH 133/198] more details for localized attribute properties... (#558) If the resources are not visible the parser library throws a useless exception. These extra details help with finding the issue quicker. --- .../Infrastructure/LocalizableAttributeProperty.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index b5a36100..9edd621b 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -45,10 +45,10 @@ private string GetLocalizedValue() { // Static class IsAbstract if (!_type.IsVisible) - throw new ArgumentException("Invalid resource type", _propertyName); + throw new ArgumentException($"Invalid resource type '{_type.FullName}'! {_type.Name} is not visible for the parser! Change resources 'Access Modifier' to 'Public'", _propertyName); PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); if (propertyInfo == null || !propertyInfo.CanRead || propertyInfo.PropertyType != typeof(string)) - throw new ArgumentException("Invalid resource property name", _propertyName); + throw new ArgumentException("Invalid resource property name! Localized value: {_value}", _propertyName); _localizationPropertyInfo = propertyInfo; } return (string)_localizationPropertyInfo.GetValue(null, null); From 78b15ad0b2df302651f65c5be1f3808afe9dbd13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Wed, 16 Jan 2019 11:06:13 +0100 Subject: [PATCH 134/198] Added async versions of WithParsed extension methods --- src/CommandLine/ParserResultExtensions.cs | 58 ++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index 66201dbb..c1bd35a7 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace CommandLine { @@ -28,6 +29,24 @@ public static ParserResult WithParsed(this ParserResult result, Action< return result; } + /// + /// Executes asynchronously if contains + /// parsed values. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + await action(parsed.Value); + } + return result; + } + + /// /// Executes if parsed values are of . /// @@ -48,6 +67,26 @@ public static ParserResult WithParsed(this ParserResult resul return result; } + /// + /// Executes asynchronously if parsed values are of . + /// + /// Type of the target instance built with parsed value. + /// An verb result instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + if (parsed.Value is T) + { + await action((T)parsed.Value); + } + } + return result; + } + + /// /// Executes if lacks /// parsed values and contains errors. @@ -57,11 +96,28 @@ public static ParserResult WithParsed(this ParserResult resul /// The delegate to execute. /// The same instance. public static ParserResult WithNotParsed(this ParserResult result, Action> action) + { + if (result is NotParsed notParsed) + { + action(notParsed.Errors); + } + return result; + } + + /// + /// Executes asynchronously if lacks + /// parsed values and contains errors. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The delegate to execute. + /// The same instance as a instance. + public static async Task> WithNotParsedAsync(this ParserResult result, Func, Task> action) { var notParsed = result as NotParsed; if (notParsed != null) { - action(notParsed.Errors); + await action(notParsed.Errors); } return result; } From 0bd9e6c0833bdb4e9d3437363394158b21a7d6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Wed, 16 Jan 2019 16:12:12 +0100 Subject: [PATCH 135/198] Added async versions of MapResult extension methods --- src/CommandLine/ParserResultExtensions.cs | 1508 +++++++++++++++++++-- 1 file changed, 1376 insertions(+), 132 deletions(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index c1bd35a7..d551bc29 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -143,6 +143,26 @@ public static TResult MapResult(this ParserResult res return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// Type of the target instance built with parsed value. + /// The type of the new value. + /// An instance. + /// Async lambda executed on successful parsing. + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + return parsedFunc(parsed.Value); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -168,6 +188,30 @@ public static TResult MapResult(this ParserResult result, return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -200,6 +244,37 @@ public static TResult MapResult(this ParserResult resul return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) + { + return parsedFunc2((T2)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -239,6 +314,44 @@ public static TResult MapResult(this ParserResult r return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -285,6 +398,51 @@ public static TResult MapResult(this ParserResult)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -338,6 +496,58 @@ public static TResult MapResult(this ParserResult)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -398,6 +608,65 @@ public static TResult MapResult(this ParserResu return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -465,6 +734,72 @@ public static TResult MapResult(this Parser return notParsedFunc(((NotParsed)result).Errors); } + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -540,7 +875,7 @@ public static TResult MapResult(this Pa } /// - /// Provides a way to transform result data into another value. + /// Provides a way to asynchronously transform result data into another value. /// /// First verb type. /// Second verb type. @@ -550,70 +885,62 @@ public static TResult MapResult(this Pa /// Sixth verb type. /// Seventh verb type. /// Eighth verb type. - /// Ninth verb type. /// /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func, TResult> notParsedFunc) + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func, Task> notParsedFunc) { - var parsed = result as Parsed; - if (parsed != null) + if (result is Parsed parsed) { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) + if (parsed.Value is T1 t1) { - return parsedFunc2((T2)parsed.Value); + return parsedFunc1(t1); } - if (parsed.Value is T3) + if (parsed.Value is T2 t2) { - return parsedFunc3((T3)parsed.Value); + return parsedFunc2(t2); } - if (parsed.Value is T4) + if (parsed.Value is T3 t3) { - return parsedFunc4((T4)parsed.Value); + return parsedFunc3(t3); } - if (parsed.Value is T5) + if (parsed.Value is T4 t4) { - return parsedFunc5((T5)parsed.Value); + return parsedFunc4(t4); } - if (parsed.Value is T6) + if (parsed.Value is T5 t5) { - return parsedFunc6((T6)parsed.Value); + return parsedFunc5(t5); } - if (parsed.Value is T7) + if (parsed.Value is T6 t6) { - return parsedFunc7((T7)parsed.Value); + return parsedFunc6(t6); } - if (parsed.Value is T8) + if (parsed.Value is T7 t7) { - return parsedFunc8((T8)parsed.Value); + return parsedFunc7(t7); } - if (parsed.Value is T9) + if (parsed.Value is T8 t8) { - return parsedFunc9((T9)parsed.Value); + return parsedFunc8(t8); } throw new InvalidOperationException(); } @@ -632,7 +959,6 @@ public static TResult MapResult(thi /// Seventh verb type. /// Eighth verb type. /// Ninth verb type. - /// Tenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -644,10 +970,9 @@ public static TResult MapResult(thi /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -657,7 +982,6 @@ public static TResult MapResult parsedFunc7, Func parsedFunc8, Func parsedFunc9, - Func parsedFunc10, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -699,17 +1023,13 @@ public static TResult MapResult)result).Errors); } /// - /// Provides a way to transform result data into another value. + /// Provides a way to asynchronously transform result data into another value. /// /// First verb type. /// Second verb type. @@ -720,14 +1040,269 @@ public static TResult MapResultSeventh verb type. /// Eighth verb type. /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. /// /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// + /// The result in verb scenario. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. + /// The new value. + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func parsedFunc10, + Func, TResult> notParsedFunc) + { + var parsed = result as Parsed; + if (parsed != null) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) + { + return parsedFunc2((T2)parsed.Value); + } + if (parsed.Value is T3) + { + return parsedFunc3((T3)parsed.Value); + } + if (parsed.Value is T4) + { + return parsedFunc4((T4)parsed.Value); + } + if (parsed.Value is T5) + { + return parsedFunc5((T5)parsed.Value); + } + if (parsed.Value is T6) + { + return parsedFunc6((T6)parsed.Value); + } + if (parsed.Value is T7) + { + return parsedFunc7((T7)parsed.Value); + } + if (parsed.Value is T8) + { + return parsedFunc8((T8)parsed.Value); + } + if (parsed.Value is T9) + { + return parsedFunc9((T9)parsed.Value); + } + if (parsed.Value is T10) + { + return parsedFunc10((T10)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// + /// The result in verb scenario. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . @@ -774,29 +1349,326 @@ public static TResult MapResult)result).Errors); + } + + /// + /// Provides a way to asynchronously transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to transform result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// + /// The result in verb scenario. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. + /// The new value. + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func parsedFunc10, + Func parsedFunc11, + Func parsedFunc12, + Func, TResult> notParsedFunc) + { + var parsed = result as Parsed; + if (parsed != null) + { + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) + { + return parsedFunc2((T2)parsed.Value); + } + if (parsed.Value is T3) + { + return parsedFunc3((T3)parsed.Value); + } + if (parsed.Value is T4) + { + return parsedFunc4((T4)parsed.Value); + } + if (parsed.Value is T5) + { + return parsedFunc5((T5)parsed.Value); + } + if (parsed.Value is T6) + { + return parsedFunc6((T6)parsed.Value); + } + if (parsed.Value is T7) + { + return parsedFunc7((T7)parsed.Value); + } + if (parsed.Value is T8) + { + return parsedFunc8((T8)parsed.Value); + } + if (parsed.Value is T9) + { + return parsedFunc9((T9)parsed.Value); + } + if (parsed.Value is T10) + { + return parsedFunc10((T10)parsed.Value); + } + if (parsed.Value is T11) + { + return parsedFunc11((T11)parsed.Value); + } + if (parsed.Value is T12) + { + return parsedFunc12((T12)parsed.Value); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) { - return parsedFunc6((T6)parsed.Value); + return parsedFunc7(t7); } - if (parsed.Value is T7) + if (parsed.Value is T8 t8) { - return parsedFunc7((T7)parsed.Value); + return parsedFunc8(t8); } - if (parsed.Value is T8) + if (parsed.Value is T9 t9) { - return parsedFunc8((T8)parsed.Value); + return parsedFunc9(t9); } - if (parsed.Value is T9) + if (parsed.Value is T10 t10) { - return parsedFunc9((T9)parsed.Value); + return parsedFunc10(t10); } - if (parsed.Value is T10) + if (parsed.Value is T11 t11) { - return parsedFunc10((T10)parsed.Value); + return parsedFunc11(t11); } - if (parsed.Value is T11) + if (parsed.Value is T12 t12) { - return parsedFunc11((T11)parsed.Value); + return parsedFunc12(t12); } throw new InvalidOperationException(); } @@ -818,6 +1690,7 @@ public static TResult MapResultTenth verb type. /// Eleventh verb type. /// Twelfth verb type. + /// Thirteenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -832,9 +1705,10 @@ public static TResult MapResultLambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -847,6 +1721,7 @@ public static TResult MapResult parsedFunc10, Func parsedFunc11, Func parsedFunc12, + Func parsedFunc13, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -900,13 +1775,17 @@ public static TResult MapResult)result).Errors); } /// - /// Provides a way to transform result data into another value. + /// Provides a way to asynchronously result data into another value. /// /// First verb type. /// Second verb type. @@ -923,97 +1802,95 @@ public static TResult MapResultThirteenth verb type. /// /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func parsedFunc10, - Func parsedFunc11, - Func parsedFunc12, - Func parsedFunc13, - Func, TResult> notParsedFunc) + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func, Task> notParsedFunc) { - var parsed = result as Parsed; - if (parsed != null) + if (result is Parsed parsed) { - if (parsed.Value is T1) + if (parsed.Value is T1 t1) { - return parsedFunc1((T1)parsed.Value); + return parsedFunc1(t1); } - if (parsed.Value is T2) + if (parsed.Value is T2 t2) { - return parsedFunc2((T2)parsed.Value); + return parsedFunc2(t2); } - if (parsed.Value is T3) + if (parsed.Value is T3 t3) { - return parsedFunc3((T3)parsed.Value); + return parsedFunc3(t3); } - if (parsed.Value is T4) + if (parsed.Value is T4 t4) { - return parsedFunc4((T4)parsed.Value); + return parsedFunc4(t4); } - if (parsed.Value is T5) + if (parsed.Value is T5 t5) { - return parsedFunc5((T5)parsed.Value); + return parsedFunc5(t5); } - if (parsed.Value is T6) + if (parsed.Value is T6 t6) { - return parsedFunc6((T6)parsed.Value); + return parsedFunc6(t6); } - if (parsed.Value is T7) + if (parsed.Value is T7 t7) { - return parsedFunc7((T7)parsed.Value); + return parsedFunc7(t7); } - if (parsed.Value is T8) + if (parsed.Value is T8 t8) { - return parsedFunc8((T8)parsed.Value); + return parsedFunc8(t8); } - if (parsed.Value is T9) + if (parsed.Value is T9 t9) { - return parsedFunc9((T9)parsed.Value); + return parsedFunc9(t9); } - if (parsed.Value is T10) + if (parsed.Value is T10 t10) { - return parsedFunc10((T10)parsed.Value); + return parsedFunc10(t10); } - if (parsed.Value is T11) + if (parsed.Value is T11 t11) { - return parsedFunc11((T11)parsed.Value); + return parsedFunc11(t11); } - if (parsed.Value is T12) + if (parsed.Value is T12 t12) { - return parsedFunc12((T12)parsed.Value); + return parsedFunc12(t12); } - if (parsed.Value is T13) + if (parsed.Value is T13 t13) { - return parsedFunc13((T13)parsed.Value); + return parsedFunc13(t13); } throw new InvalidOperationException(); } return notParsedFunc(((NotParsed)result).Errors); } - /// /// Provides a way to transform result data into another value. /// @@ -1130,6 +2007,121 @@ public static TResult MapResult)result).Errors); } + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// Thirteenth verb type. + /// Fourteenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func> parsedFunc14, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + if (parsed.Value is T12 t12) + { + return parsedFunc12(t12); + } + if (parsed.Value is T13 t13) + { + return parsedFunc13(t13); + } + if (parsed.Value is T14 t14) + { + return parsedFunc14(t14); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -1253,6 +2245,128 @@ public static TResult MapResult)result).Errors); } + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// Thirteenth verb type. + /// Fourteenth verb type. + /// Fifteenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func> parsedFunc14, + Func> parsedFunc15, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + if (parsed.Value is T12 t12) + { + return parsedFunc12(t12); + } + if (parsed.Value is T13 t13) + { + return parsedFunc13(t13); + } + if (parsed.Value is T14 t14) + { + return parsedFunc14(t14); + } + if (parsed.Value is T15 t15) + { + return parsedFunc15(t15); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + /// /// Provides a way to transform result data into another value. /// @@ -1382,5 +2496,135 @@ public static TResult MapResult)result).Errors); } + + /// + /// Provides a way to asynchronously result data into another value. + /// + /// First verb type. + /// Second verb type. + /// Third verb type. + /// Fourth verb type. + /// Fifth verb type. + /// Sixth verb type. + /// Seventh verb type. + /// Eighth verb type. + /// Ninth verb type. + /// Tenth verb type. + /// Eleventh verb type. + /// Twelfth verb type. + /// Thirteenth verb type. + /// Fourteenth verb type. + /// Fifteenth verb type. + /// Sixteenth verb type. + /// + /// The result in verb scenario. + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on successful parsing of . + /// Async lambda executed on failed parsing. + /// The new value. + public static Task MapResultAsync(this ParserResult result, + Func> parsedFunc1, + Func> parsedFunc2, + Func> parsedFunc3, + Func> parsedFunc4, + Func> parsedFunc5, + Func> parsedFunc6, + Func> parsedFunc7, + Func> parsedFunc8, + Func> parsedFunc9, + Func> parsedFunc10, + Func> parsedFunc11, + Func> parsedFunc12, + Func> parsedFunc13, + Func> parsedFunc14, + Func> parsedFunc15, + Func> parsedFunc16, + Func, Task> notParsedFunc) + { + if (result is Parsed parsed) + { + if (parsed.Value is T1 t1) + { + return parsedFunc1(t1); + } + if (parsed.Value is T2 t2) + { + return parsedFunc2(t2); + } + if (parsed.Value is T3 t3) + { + return parsedFunc3(t3); + } + if (parsed.Value is T4 t4) + { + return parsedFunc4(t4); + } + if (parsed.Value is T5 t5) + { + return parsedFunc5(t5); + } + if (parsed.Value is T6 t6) + { + return parsedFunc6(t6); + } + if (parsed.Value is T7 t7) + { + return parsedFunc7(t7); + } + if (parsed.Value is T8 t8) + { + return parsedFunc8(t8); + } + if (parsed.Value is T9 t9) + { + return parsedFunc9(t9); + } + if (parsed.Value is T10 t10) + { + return parsedFunc10(t10); + } + if (parsed.Value is T11 t11) + { + return parsedFunc11(t11); + } + if (parsed.Value is T12 t12) + { + return parsedFunc12(t12); + } + if (parsed.Value is T13 t13) + { + return parsedFunc13(t13); + } + if (parsed.Value is T14 t14) + { + return parsedFunc14(t14); + } + if (parsed.Value is T15 t15) + { + return parsedFunc15(t15); + } + if (parsed.Value is T16 t16) + { + return parsedFunc16(t16); + } + throw new InvalidOperationException(); + } + return notParsedFunc(((NotParsed)result).Errors); + } + } } From c147400ac34084bfc8a4796694dadcdaa17e0577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Wed, 16 Jan 2019 16:45:22 +0100 Subject: [PATCH 136/198] Added unit tests for async extension methods --- .../Unit/ParserResultExtensionsTests.cs | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs index 178483ed..2ebfadd8 100644 --- a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs @@ -4,6 +4,7 @@ using Xunit; using FluentAssertions; using CommandLine.Tests.Fakes; +using System.Threading.Tasks; namespace CommandLine.Tests.Unit { @@ -19,6 +20,16 @@ public static void Invoke_parsed_lambda_when_parsed() "value".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_parsed_lambda_when_parsedAsync() + { + var expected = string.Empty; + await Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }) + .WithParsedAsync(opts => Task.Run(() => expected = opts.StringValue)); + + "value".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_parsed_lambda_when_parsed_for_verbs() { @@ -32,6 +43,20 @@ public static void Invoke_parsed_lambda_when_parsed_for_verbs() "https://value.org/user/file.git".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_parsed_lambda_when_parsed_for_verbsAsync() + { + var expected = string.Empty; + var parsedArguments = Parser.Default.ParseArguments( + new[] { "clone", "https://value.org/user/file.git" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong1")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong2")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.Urls.First())); + + "https://value.org/user/file.git".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_not_parsed_lambda_when_not_parsed() { @@ -42,6 +67,16 @@ public static void Invoke_not_parsed_lambda_when_not_parsed() "changed".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_not_parsed_lambda_when_not_parsedAsync() + { + var expected = "a default"; + await Parser.Default.ParseArguments(new[] { "-i", "aaa" }) + .WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "changed".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_not_parsed_lambda_when_parsed_for_verbs() { @@ -55,6 +90,20 @@ public static void Invoke_not_parsed_lambda_when_parsed_for_verbs() "changed".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_not_parsed_lambda_when_parsed_for_verbsAsync() + { + var expected = "a default"; + var parsedArguments = Parser.Default.ParseArguments(new[] { "undefined", "-xyz" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong1")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong2")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong3")); + await parsedArguments.WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "changed".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_proper_lambda_when_parsed() { @@ -66,6 +115,18 @@ public static void Invoke_proper_lambda_when_parsed() "value".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_proper_lambda_when_parsedAsync() + { + var expected = string.Empty; + var parsedArguments = Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.StringValue)); + await parsedArguments.WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "value".Should().BeEquivalentTo(expected); + } + [Fact] public static void Invoke_proper_lambda_when_not_parsed() { @@ -77,6 +138,18 @@ public static void Invoke_proper_lambda_when_not_parsed() "changed".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_proper_lambda_when_not_parsedAsync() + { + var expected = "a default"; + var parsedArguments = Parser.Default.ParseArguments(new[] { "-i", "aaa" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.StringValue)); + await parsedArguments.WithNotParsedAsync(_ => Task.Run(() => expected = "changed")); + + "changed".Should().BeEquivalentTo(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code() { @@ -86,6 +159,15 @@ public static void Turn_sucessful_parsing_into_exit_code() 0.Should().Be(expected); } + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_codeAsync() + { + var expected = await Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }) + .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); + + 0.Should().Be(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_verbs() { @@ -100,6 +182,20 @@ public static void Turn_sucessful_parsing_into_exit_code_for_verbs() 2.Should().Be(expected); } + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_code_for_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "clone", "https://value.org/user/file.git" }) + .MapResultAsync( + (Add_Verb opts) => Task.FromResult(0), + (Commit_Verb opts) => Task.FromResult(1), + (Clone_Verb opts) => Task.FromResult(2), + errs => Task.FromResult(3)); + + 2.Should().Be(expected); + } + [Fact] public static void Turn_failed_parsing_into_exit_code() { @@ -109,6 +205,15 @@ public static void Turn_failed_parsing_into_exit_code() (-1).Should().Be(expected); } + [Fact] + public static async Task Turn_failed_parsing_into_exit_codeAsync() + { + var expected = await Parser.Default.ParseArguments(new[] { "-i", "aaa" }) + .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); + + (-1).Should().Be(expected); + } + [Fact] public static void Turn_failed_parsing_into_exit_code_for_verbs() { @@ -123,6 +228,20 @@ public static void Turn_failed_parsing_into_exit_code_for_verbs() 3.Should().Be(expected); } + [Fact] + public static async Task Turn_failed_parsing_into_exit_code_for_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "undefined", "-xyz" }) + .MapResultAsync( + (Add_Verb opts) => Task.FromResult(0), + (Commit_Verb opts) => Task.FromResult(1), + (Clone_Verb opts) => Task.FromResult(2), + errs => Task.FromResult(3)); + + 3.Should().Be(expected); + } + [Fact] public static void Invoke_parsed_lambda_when_parsed_for_base_verbs() { @@ -137,6 +256,21 @@ public static void Invoke_parsed_lambda_when_parsed_for_base_verbs() "dummy.bin".Should().BeEquivalentTo(expected); } + [Fact] + public static async Task Invoke_parsed_lambda_when_parsed_for_base_verbsAsync() + { + var expected = string.Empty; + var parsedArguments = Parser.Default.ParseArguments( + new[] { "derivedadd", "dummy.bin" }); + + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong1")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong2")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = "wrong3")); + await parsedArguments.WithParsedAsync(opts => Task.Run(() => expected = opts.FileName)); + + "dummy.bin".Should().BeEquivalentTo(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_single_base_verbs() { @@ -149,6 +283,18 @@ public static void Turn_sucessful_parsing_into_exit_code_for_single_base_verbs() 1.Should().Be(expected); } + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_code_for_single_base_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "derivedadd", "dummy.bin" }) + .MapResultAsync( + (Base_Class_For_Verb opts) => Task.FromResult(1), + errs => Task.FromResult(2)); + + 1.Should().Be(expected); + } + [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs() { @@ -164,5 +310,21 @@ public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs 4.Should().Be(expected); } + + [Fact] + public static async Task Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbsAsync() + { + var expected = await Parser.Default.ParseArguments( + new[] { "derivedadd", "dummy.bin" }) + .MapResultAsync( + (Add_Verb opts) => Task.FromResult(0), + (Commit_Verb opts) => Task.FromResult(1), + (Clone_Verb opts) => Task.FromResult(2), + (Base_Class_For_Verb opts) => Task.FromResult(4), + (Derived_Verb opts) => Task.FromResult(3), + errs => Task.FromResult(5)); + + 4.Should().Be(expected); + } } } From f5b50fa98f035a7e0164079647ccc78f6b14131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:14:39 +0100 Subject: [PATCH 137/198] Revert "Added async versions of MapResult extension methods" This reverts commit 8f18fcb5da4c323c24d31e396afd516dd50a1f52. --- src/CommandLine/ParserResultExtensions.cs | 1500 ++------------------- 1 file changed, 128 insertions(+), 1372 deletions(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index d551bc29..c1bd35a7 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -143,26 +143,6 @@ public static TResult MapResult(this ParserResult res return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// Type of the target instance built with parsed value. - /// The type of the new value. - /// An instance. - /// Async lambda executed on successful parsing. - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - return parsedFunc(parsed.Value); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -188,30 +168,6 @@ public static TResult MapResult(this ParserResult result, return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -244,37 +200,6 @@ public static TResult MapResult(this ParserResult resul return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) - { - return parsedFunc2((T2)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -314,44 +239,6 @@ public static TResult MapResult(this ParserResult r return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -398,51 +285,6 @@ public static TResult MapResult(this ParserResult)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -496,58 +338,6 @@ public static TResult MapResult(this ParserResult)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -608,65 +398,6 @@ public static TResult MapResult(this ParserResu return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -734,72 +465,6 @@ public static TResult MapResult(this Parser return notParsedFunc(((NotParsed)result).Errors); } - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -875,7 +540,7 @@ public static TResult MapResult(this Pa } /// - /// Provides a way to asynchronously transform result data into another value. + /// Provides a way to transform result data into another value. /// /// First verb type. /// Second verb type. @@ -885,62 +550,70 @@ public static TResult MapResult(this Pa /// Sixth verb type. /// Seventh verb type. /// Eighth verb type. + /// Ninth verb type. /// /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func, Task> notParsedFunc) + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func, TResult> notParsedFunc) { - if (result is Parsed parsed) + var parsed = result as Parsed; + if (parsed != null) { - if (parsed.Value is T1 t1) + if (parsed.Value is T1) + { + return parsedFunc1((T1)parsed.Value); + } + if (parsed.Value is T2) { - return parsedFunc1(t1); + return parsedFunc2((T2)parsed.Value); } - if (parsed.Value is T2 t2) + if (parsed.Value is T3) { - return parsedFunc2(t2); + return parsedFunc3((T3)parsed.Value); } - if (parsed.Value is T3 t3) + if (parsed.Value is T4) { - return parsedFunc3(t3); + return parsedFunc4((T4)parsed.Value); } - if (parsed.Value is T4 t4) + if (parsed.Value is T5) { - return parsedFunc4(t4); + return parsedFunc5((T5)parsed.Value); } - if (parsed.Value is T5 t5) + if (parsed.Value is T6) { - return parsedFunc5(t5); + return parsedFunc6((T6)parsed.Value); } - if (parsed.Value is T6 t6) + if (parsed.Value is T7) { - return parsedFunc6(t6); + return parsedFunc7((T7)parsed.Value); } - if (parsed.Value is T7 t7) + if (parsed.Value is T8) { - return parsedFunc7(t7); + return parsedFunc8((T8)parsed.Value); } - if (parsed.Value is T8 t8) + if (parsed.Value is T9) { - return parsedFunc8(t8); + return parsedFunc9((T9)parsed.Value); } throw new InvalidOperationException(); } @@ -959,6 +632,7 @@ public static Task MapResultAsyncSeventh verb type. /// Eighth verb type. /// Ninth verb type. + /// Tenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -970,9 +644,10 @@ public static Task MapResultAsyncLambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -982,6 +657,7 @@ public static TResult MapResult(thi Func parsedFunc7, Func parsedFunc8, Func parsedFunc9, + Func parsedFunc10, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -1023,13 +699,17 @@ public static TResult MapResult(thi { return parsedFunc9((T9)parsed.Value); } + if (parsed.Value is T10) + { + return parsedFunc10((T10)parsed.Value); + } throw new InvalidOperationException(); } return notParsedFunc(((NotParsed)result).Errors); } /// - /// Provides a way to asynchronously transform result data into another value. + /// Provides a way to transform result data into another value. /// /// First verb type. /// Second verb type. @@ -1040,263 +720,8 @@ public static TResult MapResult(thi /// Seventh verb type. /// Eighth verb type. /// Ninth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// - /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. - /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func parsedFunc10, - Func, TResult> notParsedFunc) - { - var parsed = result as Parsed; - if (parsed != null) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) - { - return parsedFunc2((T2)parsed.Value); - } - if (parsed.Value is T3) - { - return parsedFunc3((T3)parsed.Value); - } - if (parsed.Value is T4) - { - return parsedFunc4((T4)parsed.Value); - } - if (parsed.Value is T5) - { - return parsedFunc5((T5)parsed.Value); - } - if (parsed.Value is T6) - { - return parsedFunc6((T6)parsed.Value); - } - if (parsed.Value is T7) - { - return parsedFunc7((T7)parsed.Value); - } - if (parsed.Value is T8) - { - return parsedFunc8((T8)parsed.Value); - } - if (parsed.Value is T9) - { - return parsedFunc9((T9)parsed.Value); - } - if (parsed.Value is T10) - { - return parsedFunc10((T10)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. + /// Tenth verb type. + /// Eleventh verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -1349,326 +774,29 @@ public static TResult MapResult)result).Errors); - } - - /// - /// Provides a way to asynchronously transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to transform result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// - /// The result in verb scenario. - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . - /// Lambda executed on failed parsing. - /// The new value. - public static TResult MapResult(this ParserResult result, - Func parsedFunc1, - Func parsedFunc2, - Func parsedFunc3, - Func parsedFunc4, - Func parsedFunc5, - Func parsedFunc6, - Func parsedFunc7, - Func parsedFunc8, - Func parsedFunc9, - Func parsedFunc10, - Func parsedFunc11, - Func parsedFunc12, - Func, TResult> notParsedFunc) - { - var parsed = result as Parsed; - if (parsed != null) - { - if (parsed.Value is T1) - { - return parsedFunc1((T1)parsed.Value); - } - if (parsed.Value is T2) - { - return parsedFunc2((T2)parsed.Value); - } - if (parsed.Value is T3) - { - return parsedFunc3((T3)parsed.Value); - } - if (parsed.Value is T4) - { - return parsedFunc4((T4)parsed.Value); - } - if (parsed.Value is T5) - { - return parsedFunc5((T5)parsed.Value); - } - if (parsed.Value is T6) - { - return parsedFunc6((T6)parsed.Value); - } - if (parsed.Value is T7) - { - return parsedFunc7((T7)parsed.Value); - } - if (parsed.Value is T8) - { - return parsedFunc8((T8)parsed.Value); - } - if (parsed.Value is T9) - { - return parsedFunc9((T9)parsed.Value); - } - if (parsed.Value is T10) - { - return parsedFunc10((T10)parsed.Value); - } - if (parsed.Value is T11) - { - return parsedFunc11((T11)parsed.Value); - } - if (parsed.Value is T12) - { - return parsedFunc12((T12)parsed.Value); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) + if (parsed.Value is T6) { - return parsedFunc7(t7); + return parsedFunc6((T6)parsed.Value); } - if (parsed.Value is T8 t8) + if (parsed.Value is T7) { - return parsedFunc8(t8); + return parsedFunc7((T7)parsed.Value); } - if (parsed.Value is T9 t9) + if (parsed.Value is T8) { - return parsedFunc9(t9); + return parsedFunc8((T8)parsed.Value); } - if (parsed.Value is T10 t10) + if (parsed.Value is T9) { - return parsedFunc10(t10); + return parsedFunc9((T9)parsed.Value); } - if (parsed.Value is T11 t11) + if (parsed.Value is T10) { - return parsedFunc11(t11); + return parsedFunc10((T10)parsed.Value); } - if (parsed.Value is T12 t12) + if (parsed.Value is T11) { - return parsedFunc12(t12); + return parsedFunc11((T11)parsed.Value); } throw new InvalidOperationException(); } @@ -1690,7 +818,6 @@ public static Task MapResultAsyncTenth verb type. /// Eleventh verb type. /// Twelfth verb type. - /// Thirteenth verb type. /// /// The result in verb scenario. /// Lambda executed on successful parsing of . @@ -1705,10 +832,9 @@ public static Task MapResultAsyncLambda executed on successful parsing of . /// Lambda executed on successful parsing of . /// Lambda executed on successful parsing of . - /// Lambda executed on successful parsing of . /// Lambda executed on failed parsing. /// The new value. - public static TResult MapResult(this ParserResult result, + public static TResult MapResult(this ParserResult result, Func parsedFunc1, Func parsedFunc2, Func parsedFunc3, @@ -1721,7 +847,6 @@ public static TResult MapResult parsedFunc10, Func parsedFunc11, Func parsedFunc12, - Func parsedFunc13, Func, TResult> notParsedFunc) { var parsed = result as Parsed; @@ -1775,17 +900,13 @@ public static TResult MapResult)result).Errors); } /// - /// Provides a way to asynchronously result data into another value. + /// Provides a way to transform result data into another value. /// /// First verb type. /// Second verb type. @@ -1802,95 +923,97 @@ public static TResult MapResultThirteenth verb type. /// /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on successful parsing of . + /// Lambda executed on failed parsing. /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func, Task> notParsedFunc) + public static TResult MapResult(this ParserResult result, + Func parsedFunc1, + Func parsedFunc2, + Func parsedFunc3, + Func parsedFunc4, + Func parsedFunc5, + Func parsedFunc6, + Func parsedFunc7, + Func parsedFunc8, + Func parsedFunc9, + Func parsedFunc10, + Func parsedFunc11, + Func parsedFunc12, + Func parsedFunc13, + Func, TResult> notParsedFunc) { - if (result is Parsed parsed) + var parsed = result as Parsed; + if (parsed != null) { - if (parsed.Value is T1 t1) + if (parsed.Value is T1) { - return parsedFunc1(t1); + return parsedFunc1((T1)parsed.Value); } - if (parsed.Value is T2 t2) + if (parsed.Value is T2) { - return parsedFunc2(t2); + return parsedFunc2((T2)parsed.Value); } - if (parsed.Value is T3 t3) + if (parsed.Value is T3) { - return parsedFunc3(t3); + return parsedFunc3((T3)parsed.Value); } - if (parsed.Value is T4 t4) + if (parsed.Value is T4) { - return parsedFunc4(t4); + return parsedFunc4((T4)parsed.Value); } - if (parsed.Value is T5 t5) + if (parsed.Value is T5) { - return parsedFunc5(t5); + return parsedFunc5((T5)parsed.Value); } - if (parsed.Value is T6 t6) + if (parsed.Value is T6) { - return parsedFunc6(t6); + return parsedFunc6((T6)parsed.Value); } - if (parsed.Value is T7 t7) + if (parsed.Value is T7) { - return parsedFunc7(t7); + return parsedFunc7((T7)parsed.Value); } - if (parsed.Value is T8 t8) + if (parsed.Value is T8) { - return parsedFunc8(t8); + return parsedFunc8((T8)parsed.Value); } - if (parsed.Value is T9 t9) + if (parsed.Value is T9) { - return parsedFunc9(t9); + return parsedFunc9((T9)parsed.Value); } - if (parsed.Value is T10 t10) + if (parsed.Value is T10) { - return parsedFunc10(t10); + return parsedFunc10((T10)parsed.Value); } - if (parsed.Value is T11 t11) + if (parsed.Value is T11) { - return parsedFunc11(t11); + return parsedFunc11((T11)parsed.Value); } - if (parsed.Value is T12 t12) + if (parsed.Value is T12) { - return parsedFunc12(t12); + return parsedFunc12((T12)parsed.Value); } - if (parsed.Value is T13 t13) + if (parsed.Value is T13) { - return parsedFunc13(t13); + return parsedFunc13((T13)parsed.Value); } throw new InvalidOperationException(); } return notParsedFunc(((NotParsed)result).Errors); } + /// /// Provides a way to transform result data into another value. /// @@ -2007,121 +1130,6 @@ public static TResult MapResult)result).Errors); } - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// Thirteenth verb type. - /// Fourteenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func> parsedFunc14, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - if (parsed.Value is T12 t12) - { - return parsedFunc12(t12); - } - if (parsed.Value is T13 t13) - { - return parsedFunc13(t13); - } - if (parsed.Value is T14 t14) - { - return parsedFunc14(t14); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -2245,128 +1253,6 @@ public static TResult MapResult)result).Errors); } - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// Thirteenth verb type. - /// Fourteenth verb type. - /// Fifteenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func> parsedFunc14, - Func> parsedFunc15, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - if (parsed.Value is T12 t12) - { - return parsedFunc12(t12); - } - if (parsed.Value is T13 t13) - { - return parsedFunc13(t13); - } - if (parsed.Value is T14 t14) - { - return parsedFunc14(t14); - } - if (parsed.Value is T15 t15) - { - return parsedFunc15(t15); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - /// /// Provides a way to transform result data into another value. /// @@ -2496,135 +1382,5 @@ public static TResult MapResult)result).Errors); } - - /// - /// Provides a way to asynchronously result data into another value. - /// - /// First verb type. - /// Second verb type. - /// Third verb type. - /// Fourth verb type. - /// Fifth verb type. - /// Sixth verb type. - /// Seventh verb type. - /// Eighth verb type. - /// Ninth verb type. - /// Tenth verb type. - /// Eleventh verb type. - /// Twelfth verb type. - /// Thirteenth verb type. - /// Fourteenth verb type. - /// Fifteenth verb type. - /// Sixteenth verb type. - /// - /// The result in verb scenario. - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on successful parsing of . - /// Async lambda executed on failed parsing. - /// The new value. - public static Task MapResultAsync(this ParserResult result, - Func> parsedFunc1, - Func> parsedFunc2, - Func> parsedFunc3, - Func> parsedFunc4, - Func> parsedFunc5, - Func> parsedFunc6, - Func> parsedFunc7, - Func> parsedFunc8, - Func> parsedFunc9, - Func> parsedFunc10, - Func> parsedFunc11, - Func> parsedFunc12, - Func> parsedFunc13, - Func> parsedFunc14, - Func> parsedFunc15, - Func> parsedFunc16, - Func, Task> notParsedFunc) - { - if (result is Parsed parsed) - { - if (parsed.Value is T1 t1) - { - return parsedFunc1(t1); - } - if (parsed.Value is T2 t2) - { - return parsedFunc2(t2); - } - if (parsed.Value is T3 t3) - { - return parsedFunc3(t3); - } - if (parsed.Value is T4 t4) - { - return parsedFunc4(t4); - } - if (parsed.Value is T5 t5) - { - return parsedFunc5(t5); - } - if (parsed.Value is T6 t6) - { - return parsedFunc6(t6); - } - if (parsed.Value is T7 t7) - { - return parsedFunc7(t7); - } - if (parsed.Value is T8 t8) - { - return parsedFunc8(t8); - } - if (parsed.Value is T9 t9) - { - return parsedFunc9(t9); - } - if (parsed.Value is T10 t10) - { - return parsedFunc10(t10); - } - if (parsed.Value is T11 t11) - { - return parsedFunc11(t11); - } - if (parsed.Value is T12 t12) - { - return parsedFunc12(t12); - } - if (parsed.Value is T13 t13) - { - return parsedFunc13(t13); - } - if (parsed.Value is T14 t14) - { - return parsedFunc14(t14); - } - if (parsed.Value is T15 t15) - { - return parsedFunc15(t15); - } - if (parsed.Value is T16 t16) - { - return parsedFunc16(t16); - } - throw new InvalidOperationException(); - } - return notParsedFunc(((NotParsed)result).Errors); - } - } } From 63aaaf2209cf9d97669f4544c49e7cd143bd43ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:20:12 +0100 Subject: [PATCH 138/198] Revert "Added async versions of WithParsed extension methods" This reverts commit dae308144a0ed37613eea505a6273b3744322f57. --- src/CommandLine/ParserResultExtensions.cs | 58 +---------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/src/CommandLine/ParserResultExtensions.cs b/src/CommandLine/ParserResultExtensions.cs index c1bd35a7..66201dbb 100644 --- a/src/CommandLine/ParserResultExtensions.cs +++ b/src/CommandLine/ParserResultExtensions.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; namespace CommandLine { @@ -29,24 +28,6 @@ public static ParserResult WithParsed(this ParserResult result, Action< return result; } - /// - /// Executes asynchronously if contains - /// parsed values. - /// - /// Type of the target instance built with parsed value. - /// An instance. - /// The to execute. - /// The same instance as a instance. - public static async Task> WithParsedAsync(this ParserResult result, Func action) - { - if (result is Parsed parsed) - { - await action(parsed.Value); - } - return result; - } - - /// /// Executes if parsed values are of . /// @@ -67,26 +48,6 @@ public static ParserResult WithParsed(this ParserResult resul return result; } - /// - /// Executes asynchronously if parsed values are of . - /// - /// Type of the target instance built with parsed value. - /// An verb result instance. - /// The to execute. - /// The same instance as a instance. - public static async Task> WithParsedAsync(this ParserResult result, Func action) - { - if (result is Parsed parsed) - { - if (parsed.Value is T) - { - await action((T)parsed.Value); - } - } - return result; - } - - /// /// Executes if lacks /// parsed values and contains errors. @@ -96,28 +57,11 @@ public static async Task> WithParsedAsync(this ParserRes /// The delegate to execute. /// The same instance. public static ParserResult WithNotParsed(this ParserResult result, Action> action) - { - if (result is NotParsed notParsed) - { - action(notParsed.Errors); - } - return result; - } - - /// - /// Executes asynchronously if lacks - /// parsed values and contains errors. - /// - /// Type of the target instance built with parsed value. - /// An instance. - /// The delegate to execute. - /// The same instance as a instance. - public static async Task> WithNotParsedAsync(this ParserResult result, Func, Task> action) { var notParsed = result as NotParsed; if (notParsed != null) { - await action(notParsed.Errors); + action(notParsed.Errors); } return result; } From a362f908d822b92ba428d497f452b52baf3521d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:21:09 +0100 Subject: [PATCH 139/198] Added async versions of WithParsed extension methods --- .../ParserResultExtensionsAsync.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/CommandLine/ParserResultExtensionsAsync.cs diff --git a/src/CommandLine/ParserResultExtensionsAsync.cs b/src/CommandLine/ParserResultExtensionsAsync.cs new file mode 100644 index 00000000..207dfa23 --- /dev/null +++ b/src/CommandLine/ParserResultExtensionsAsync.cs @@ -0,0 +1,63 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace CommandLine +{ + public static partial class ParserResultExtensions + { + /// + /// Executes asynchronously if contains + /// parsed values. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + await action(parsed.Value); + } + return result; + } + + /// + /// Executes asynchronously if parsed values are of . + /// + /// Type of the target instance built with parsed value. + /// An verb result instance. + /// The to execute. + /// The same instance as a instance. + public static async Task> WithParsedAsync(this ParserResult result, Func action) + { + if (result is Parsed parsed) + { + if (parsed.Value is T value) + { + await action(value); + } + } + return result; + } + + /// + /// Executes asynchronously if lacks + /// parsed values and contains errors. + /// + /// Type of the target instance built with parsed value. + /// An instance. + /// The delegate to execute. + /// The same instance as a instance. + public static async Task> WithNotParsedAsync(this ParserResult result, Func, Task> action) + { + if (result is NotParsed notParsed) + { + await action(notParsed.Errors); + } + return result; + } + } +} From 75de874cae48c61bd7d7375c1ae9c89906e6c9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20=C3=81ngel?= Date: Thu, 6 Feb 2020 09:44:40 +0100 Subject: [PATCH 140/198] Added unit tests for async extension methods --- .../Unit/ParserResultExtensionsTests.cs | 74 ------------------- 1 file changed, 74 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs index 2ebfadd8..a6ed812d 100644 --- a/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserResultExtensionsTests.cs @@ -159,15 +159,6 @@ public static void Turn_sucessful_parsing_into_exit_code() 0.Should().Be(expected); } - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_codeAsync() - { - var expected = await Parser.Default.ParseArguments(new[] { "--stringvalue", "value" }) - .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); - - 0.Should().Be(expected); - } - [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_verbs() { @@ -182,20 +173,6 @@ public static void Turn_sucessful_parsing_into_exit_code_for_verbs() 2.Should().Be(expected); } - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_code_for_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "clone", "https://value.org/user/file.git" }) - .MapResultAsync( - (Add_Verb opts) => Task.FromResult(0), - (Commit_Verb opts) => Task.FromResult(1), - (Clone_Verb opts) => Task.FromResult(2), - errs => Task.FromResult(3)); - - 2.Should().Be(expected); - } - [Fact] public static void Turn_failed_parsing_into_exit_code() { @@ -205,15 +182,6 @@ public static void Turn_failed_parsing_into_exit_code() (-1).Should().Be(expected); } - [Fact] - public static async Task Turn_failed_parsing_into_exit_codeAsync() - { - var expected = await Parser.Default.ParseArguments(new[] { "-i", "aaa" }) - .MapResultAsync(_ => Task.FromResult(0), _ => Task.FromResult(-1)); - - (-1).Should().Be(expected); - } - [Fact] public static void Turn_failed_parsing_into_exit_code_for_verbs() { @@ -228,20 +196,6 @@ public static void Turn_failed_parsing_into_exit_code_for_verbs() 3.Should().Be(expected); } - [Fact] - public static async Task Turn_failed_parsing_into_exit_code_for_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "undefined", "-xyz" }) - .MapResultAsync( - (Add_Verb opts) => Task.FromResult(0), - (Commit_Verb opts) => Task.FromResult(1), - (Clone_Verb opts) => Task.FromResult(2), - errs => Task.FromResult(3)); - - 3.Should().Be(expected); - } - [Fact] public static void Invoke_parsed_lambda_when_parsed_for_base_verbs() { @@ -283,18 +237,6 @@ public static void Turn_sucessful_parsing_into_exit_code_for_single_base_verbs() 1.Should().Be(expected); } - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_code_for_single_base_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "derivedadd", "dummy.bin" }) - .MapResultAsync( - (Base_Class_For_Verb opts) => Task.FromResult(1), - errs => Task.FromResult(2)); - - 1.Should().Be(expected); - } - [Fact] public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs() { @@ -310,21 +252,5 @@ public static void Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbs 4.Should().Be(expected); } - - [Fact] - public static async Task Turn_sucessful_parsing_into_exit_code_for_multiple_base_verbsAsync() - { - var expected = await Parser.Default.ParseArguments( - new[] { "derivedadd", "dummy.bin" }) - .MapResultAsync( - (Add_Verb opts) => Task.FromResult(0), - (Commit_Verb opts) => Task.FromResult(1), - (Clone_Verb opts) => Task.FromResult(2), - (Base_Class_For_Verb opts) => Task.FromResult(4), - (Derived_Verb opts) => Task.FromResult(3), - errs => Task.FromResult(5)); - - 4.Should().Be(expected); - } } } From dcfbd8db7f46ad18fdea72cb2c6ff0102aa58343 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Fri, 7 Feb 2020 19:40:12 +0200 Subject: [PATCH 141/198] Exclude Net40 from async --- src/CommandLine/ParserResultExtensionsAsync.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CommandLine/ParserResultExtensionsAsync.cs b/src/CommandLine/ParserResultExtensionsAsync.cs index 207dfa23..d58d769f 100644 --- a/src/CommandLine/ParserResultExtensionsAsync.cs +++ b/src/CommandLine/ParserResultExtensionsAsync.cs @@ -7,6 +7,7 @@ namespace CommandLine { public static partial class ParserResultExtensions { +#if !NET40 /// /// Executes asynchronously if contains /// parsed values. @@ -59,5 +60,6 @@ public static async Task> WithNotParsedAsync(this ParserResul } return result; } +#endif } } From 3c4e726c5c7289c46b1199106c409e75bd64645c Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Mon, 9 Mar 2020 14:48:37 +0200 Subject: [PATCH 142/198] Fix issue# 339 of custom struct (#588) --- src/CommandLine/Core/ReflectionExtensions.cs | 8 +++ src/CommandLine/Core/TypeConverter.cs | 3 +- .../CommandLine.Tests/Fakes/Custom_Struct.cs | 56 +++++++++++++++++++ .../Unit/Core/InstanceBuilderTests.cs | 36 ++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tests/CommandLine.Tests/Fakes/Custom_Struct.cs diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index 9b6f858c..72e162b5 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -209,5 +209,13 @@ public static bool IsPrimitiveEx(this Type type) }.Contains(type) || Convert.GetTypeCode(type) != TypeCode.Object; } + + public static bool IsCustomStruct(this Type type) + { + var isStruct = type.GetTypeInfo().IsValueType && !type.GetTypeInfo().IsPrimitive && !type.GetTypeInfo().IsEnum && type != typeof(Guid); + if (!isStruct) return false; + var ctor = type.GetTypeInfo().GetConstructor(new[] { typeof(string) }); + return ctor != null; + } } } diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index fef7945a..354c4316 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -111,6 +111,7 @@ private static Result ChangeTypeScalarImpl(string value, Type } }; + if (conversionType.IsCustomStruct()) return Result.Try(makeType); return Result.Try( conversionType.IsPrimitiveEx() || ReflectionHelper.IsFSharpOptionType(conversionType) ? changeType @@ -135,4 +136,4 @@ private static object ToEnum(this string value, Type conversionType, bool ignore throw new FormatException(); } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Fakes/Custom_Struct.cs b/tests/CommandLine.Tests/Fakes/Custom_Struct.cs new file mode 100644 index 00000000..64807f6b --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Custom_Struct.cs @@ -0,0 +1,56 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System; + +namespace CommandLine.Tests.Fakes +{ + public class CustomStructOptions + { + [Option('c', "custom", HelpText = "Custom Type")] + public CustomStruct Custom { get; set; } + } + + public struct CustomStruct + { + public string Input { get; set; } + public string Server { get; set; } + public int Port { get; set; } + public CustomStruct(string url) + { + Input = url; + Server = ""; + Port = 80; + var data = url.Split(':'); + if (data.Length == 2) + { + Server = data[0]; + Port = Convert.ToInt32(data[1]); + } + } + } + + public class CustomClassOptions + { + [Option('c', "custom", HelpText = "Custom Type")] + public CustomClass Custom { get; set; } + } + + public class CustomClass + { + public string Input { get; set; } + public string Server { get; set; } + public int Port { get; set; } + public CustomClass(string url) + { + Input = url; + Server = ""; + Port = 80; + var data = url.Split(':'); + if (data.Length == 2) + { + Server = data[0]; + Port = Convert.ToInt32(data[1]); + } + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 643878fd..a74a1af0 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1221,6 +1221,42 @@ public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() errors.Should().BeEquivalentTo(expectedResult); } + #region custom types + + + [Theory] + [InlineData(new[] { "-c", "localhost:8080" }, "localhost", 8080)] + public void Parse_custom_struct_type(string[] arguments, string expectedServer, int expectedPort) + { + //Arrange + + // Act + var result = InvokeBuild(arguments); + + // Assert + var customValue = ((Parsed)result).Value.Custom; + customValue.Server.Should().Be(expectedServer); + customValue.Port.Should().Be(expectedPort); + customValue.Input.Should().Be(arguments[1]); + } + + [Theory] + [InlineData(new[] { "-c", "localhost:8080" }, "localhost", 8080)] + public void Parse_custom_class_type(string[] arguments, string expectedServer, int expectedPort) + { + //Arrange + + // Act + var result = InvokeBuild(arguments); + + // Assert + var customValue = ((Parsed)result).Value.Custom; + customValue.Server.Should().Be(expectedServer); + customValue.Port.Should().Be(expectedPort); + customValue.Input.Should().Be(arguments[1]); + } + + #endregion private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From 19e2c95c68636efaa0d7196c5da1872aa1783592 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Mon, 9 Mar 2020 14:52:16 +0200 Subject: [PATCH 143/198] Fix issue #409 to avoid IOException break in Debug mode in WPF app. (#589) --- src/CommandLine/ParserSettings.cs | 23 +++++++++++++++------ tests/CommandLine.Tests/Unit/ParserTests.cs | 12 +++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 7b182a22..07c10c4c 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -36,18 +36,29 @@ public ParserSettings() autoHelp = true; autoVersion = true; parsingCulture = CultureInfo.InvariantCulture; + maximumDisplayWidth = GetWindowWidth(); + } + + private int GetWindowWidth() + { + +#if !NET40 + if (Console.IsOutputRedirected) return DefaultMaximumLength; +#endif + var width = 1; try { - maximumDisplayWidth = Console.WindowWidth; - if (maximumDisplayWidth < 1) + width = Console.WindowWidth; + if (width < 1) { - maximumDisplayWidth = DefaultMaximumLength; + width = DefaultMaximumLength; } - } - catch (IOException) + } + catch (Exception e) when (e is IOException || e is PlatformNotSupportedException || e is ArgumentOutOfRangeException) { - maximumDisplayWidth = DefaultMaximumLength; + width = DefaultMaximumLength; } + return width; } /// diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index b6b08ca3..743bb0f6 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -892,5 +892,17 @@ public void Parse_default_verb_with_empty_name() Assert.True(args.TestValue); }); } + //Fix Issue #409 for WPF + [Fact] + public void When_HelpWriter_is_null_it_should_not_fire_exception() + { + // Arrange + + //Act + var sut = new Parser(config => config.HelpWriter = null); + sut.ParseArguments(new[] {"--dummy"}); + //Assert + sut.Settings.MaximumDisplayWidth.Should().Be(80); + } } } From 00ae0f2254197746bf5efcfaf0502bc827aefa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20G=C3=A5rdebrink?= Date: Mon, 9 Mar 2020 14:15:50 +0100 Subject: [PATCH 144/198] Remove constraint on T for ParseArguments with factory (#590) * Remove constraint on T for ParseArguments with factory * Make it possible to use factory for classes without public empty constructor * Allow types with explicitly defined interface implementations * Add test for #70 * Make sure explicit interface implementations can be parsed * Add test to make sure parser can detect explicit interface implementations --- src/CommandLine/Core/InstanceBuilder.cs | 4 +-- src/CommandLine/Core/ReflectionExtensions.cs | 17 +++++++++-- .../Infrastructure/CSharpx/Maybe.cs | 8 +++++ src/CommandLine/Parser.cs | 1 - .../Mutable_Without_Empty_Constructor.cs | 19 ++++++++++++ .../Options_With_Only_Explicit_Interface.cs | 9 ++++++ .../Unit/Core/InstanceBuilderTests.cs | 14 +++++++++ tests/CommandLine.Tests/Unit/Issue591ests.cs | 29 +++++++++++++++++++ tests/CommandLine.Tests/Unit/Issue70Tests.cs | 29 +++++++++++++++++++ 9 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs create mode 100644 tests/CommandLine.Tests/Unit/Issue591ests.cs create mode 100644 tests/CommandLine.Tests/Unit/Issue70Tests.cs diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 0ae564b5..dce377f1 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -39,7 +39,7 @@ public static ParserResult Build( Func makeDefault = () => typeof(T).IsMutable() - ? factory.MapValueOrDefault(f => f(), Activator.CreateInstance()) + ? factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()) : ReflectionHelper.CreateDefaultImmutableInstance( (from p in specProps select p.Specification.ConversionType).ToArray()); @@ -128,7 +128,7 @@ public static ParserResult Build( private static T BuildMutable(Maybe> factory, IEnumerable specPropsWithValue, List setPropertyErrors ) { - var mutable = factory.MapValueOrDefault(f => f(), Activator.CreateInstance()); + var mutable = factory.MapValueOrDefault(f => f(), () => Activator.CreateInstance()); setPropertyErrors.AddRange( mutable.SetProperties( diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index 72e162b5..e8b7e766 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -133,10 +133,21 @@ public static bool IsMutable(this Type type) if(type == typeof(object)) return true; - var props = type.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite); - var fields = type.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any(); + // Find all inherited defined properties and fields on the type + var inheritedTypes = type.GetTypeInfo().FlattenHierarchy().Select(i => i.GetTypeInfo()); - return props || fields; + foreach (var inheritedType in inheritedTypes) + { + if ( + inheritedType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(p => p.CanWrite) || + inheritedType.GetTypeInfo().GetFields(BindingFlags.Public | BindingFlags.Instance).Any() + ) + { + return true; + } + } + + return false; } public static object CreateDefaultForImmutable(this Type type) diff --git a/src/CommandLine/Infrastructure/CSharpx/Maybe.cs b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs index 1dacf4e8..044bb681 100644 --- a/src/CommandLine/Infrastructure/CSharpx/Maybe.cs +++ b/src/CommandLine/Infrastructure/CSharpx/Maybe.cs @@ -371,6 +371,14 @@ public static T2 MapValueOrDefault(this Maybe maybe, Func fu return maybe.MatchJust(out value1) ? func(value1) : noneValue; } + /// + /// If contains a values executes a mapping function over it, otherwise returns the value from . + /// + public static T2 MapValueOrDefault(this Maybe maybe, Func func, Func noneValueFactory) { + T1 value1; + return maybe.MatchJust(out value1) ? func(value1) : noneValueFactory(); + } + /// /// Returns an empty list when given or a singleton list when given a . /// diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 8f4bd049..f801c0f7 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -116,7 +116,6 @@ public ParserResult ParseArguments(IEnumerable args) /// and a sequence of . /// Thrown if one or more arguments are null. public ParserResult ParseArguments(Func factory, IEnumerable args) - where T : new() { if (factory == null) throw new ArgumentNullException("factory"); if (!typeof(T).IsMutable()) throw new ArgumentException("factory"); diff --git a/tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs b/tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs new file mode 100644 index 00000000..6f311bb1 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Mutable_Without_Empty_Constructor.cs @@ -0,0 +1,19 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +namespace CommandLine.Tests.Fakes +{ + class Mutable_Without_Empty_Constructor + { + [Option("amend", HelpText = "Used to amend the tip of the current branch.")] + public bool Amend { get; set; } + + private Mutable_Without_Empty_Constructor() + { + } + + public static Mutable_Without_Empty_Constructor Create() + { + return new Mutable_Without_Empty_Constructor(); + } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs b/tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs new file mode 100644 index 00000000..d367bfc0 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Only_Explicit_Interface.cs @@ -0,0 +1,9 @@ +namespace CommandLine.Tests.Fakes +{ + class Options_With_Only_Explicit_Interface : IInterface_With_Two_Scalar_Options + { + bool IInterface_With_Two_Scalar_Options.Verbose { get; set; } + + string IInterface_With_Two_Scalar_Options.InputFile { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index a74a1af0..8dd7371c 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -787,6 +787,20 @@ public void Specifying_options_two_or_more_times_with_mixed_short_long_options_g ((NotParsed)result).Errors.Should().HaveCount(x => x == expected); } + [Theory] + [InlineData(new[] { "--inputfile=file1.bin" }, "file1.bin")] + [InlineData(new[] { "--inputfile", "file2.txt" }, "file2.txt")] + public void Can_define_options_on_explicit_interface_properties(string[] arguments, string expected) + { + // Exercize system + var result = InvokeBuild( + arguments); + + // Verify outcome + expected.Should().BeEquivalentTo(((IInterface_With_Two_Scalar_Options)((Parsed)result).Value).InputFile); + } + + [Theory] [InlineData(new[] { "--inputfile=file1.bin" }, "file1.bin")] [InlineData(new[] { "--inputfile", "file2.txt" }, "file2.txt")] diff --git a/tests/CommandLine.Tests/Unit/Issue591ests.cs b/tests/CommandLine.Tests/Unit/Issue591ests.cs new file mode 100644 index 00000000..3888a705 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue591ests.cs @@ -0,0 +1,29 @@ +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +//Issue #591 +//When options class is only having explicit interface declarations, it should be detected as mutable. + +namespace CommandLine.Tests.Unit +{ + public class Issue591ests + { + [Fact] + public void Parse_option_with_only_explicit_interface_implementation() + { + string actual = string.Empty; + + var arguments = new[] { "--inputfile", "file2.txt" }; + var result = Parser.Default.ParseArguments(arguments); + result.WithParsed(options => { + actual = ((IInterface_With_Two_Scalar_Options)options).InputFile; + }); + + actual.Should().Be("file2.txt"); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Issue70Tests.cs b/tests/CommandLine.Tests/Unit/Issue70Tests.cs new file mode 100644 index 00000000..0acc1116 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue70Tests.cs @@ -0,0 +1,29 @@ +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +//Issue #70 +//When the factory overload is used for ParseArguments, there should be no constraint not having an empty constructor. + +namespace CommandLine.Tests.Unit +{ + public class Issue70Tests + { + [Fact] + public void Create_instance_with_factory_method_should_not_fail() + { + bool actual = false; + + var arguments = new[] { "--amend" }; + var result = Parser.Default.ParseArguments(() => Mutable_Without_Empty_Constructor.Create(), arguments); + result.WithParsed(options => { + actual = options.Amend; + }); + + actual.Should().BeTrue(); + } + } +} From 0f891d3c0e8a8954abdded021311af787a90c08b Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 11 Mar 2020 23:26:54 +0200 Subject: [PATCH 145/198] Support snupkg nuget package --- appveyor.yml | 4 ++-- src/CommandLine/CommandLine.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ff6d4476..34efa505 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,12 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.8.0-beta-{build} +version: 2.8.0-ci-{build} image: Visual Studio 2019 clone_depth: 1 pull_requests: - do_not_increment_build_number: true + do_not_increment_build_number: false init: - ps: | diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 4dac1db1..04496eb8 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -26,7 +26,7 @@ true 8.0 true - portable + snupkg From c9ba3a76461480ddd865d03d2cc01e84fe46e7a1 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Thu, 12 Mar 2020 01:47:38 +0200 Subject: [PATCH 146/198] Update changelog.md & readme.md --- CHANGELOG.md | 25 ++++++++++++++++++++++++- README.md | 11 +++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c5e55b..ede9176a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,31 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.8.0-preview1] - 2020-3-12 + +### Added +- Added support for async programming for `WithParsed and WithNotParsed` by [@joseangelmt, PR# 390 ](https://github.com/commandlineparser/commandline/pull/390). +- Add default verb support by [@Artentus, PR# 556](https://github.com/commandlineparser/commandline/pull/556). +- Add more details for localized attribute properties by [@EdmondShtogu, PR# 558](https://github.com/commandlineparser/commandline/pull/558) +- Support Default in Group Options and raise error if both SetName and Group are applied on option by [@hadzhiyski, PR# 575](https://github.com/commandlineparser/commandline/pull/575). +- Support mutable types without empty constructor that only does explicit implementation of interfaces by [@pergardebrink](https://github.com/commandlineparser/commandline/pull/590). + +### Changed +- Tests cleanup by [@gsscoder, PR# 560](https://github.com/commandlineparser/commandline/pull/560). +- Upgraded parts of CSharpx from Version 1.6.2-alpha by [@gsscoder, PR# 561](https://github.com/commandlineparser/commandline/pull/561). +- Upgraded RailwaySharp from Version 1.1.0 by [@gsscoder, PR# 562](https://github.com/commandlineparser/commandline/pull/562). +- SkipDefault is being respected by [Usage] Examples by [@kendfrey, PR# 565](https://github.com/commandlineparser/commandline/pull/565). +- Remove useless testing code by [@gsscoder, PR# 568](https://github.com/commandlineparser/commandline/pull/568). +- Remove constraint on T for ParseArguments with factory (required by issue #70) by [@pergardebrink](https://github.com/commandlineparser/commandline/pull/590). + +### Fixed +- Fix #579 Unable to parse TimeSpan given from the FormatCommandLine by [@gsscoder, PR# 580](https://github.com/commandlineparser/commandline/pull/580). +- Fix issue #339 for using custom struct having a constructor with string parameter by [moh-hassan, PR# 588](https://github.com/commandlineparser/commandline/pull/588). +- Fix issue #409 to avoid IOException break in Debug mode in WPF app by [moh-hassan, PR# 589 ](https://github.com/commandlineparser/commandline/pull/589). + + +## [2.7.82] - 2020-1-1 ## [2.7.0] - 2020-1-1 -## [2.7.0-preview1] - 2020-1-1 ### Added - Add option groups feature by [@hadzhiyski](https://github.com/commandlineparser/commandline/pull/552) - When one or more options has group set, at least one of these properties should have set value (they behave as required). - Add a new overload method for AutoBuild to enable HelpText customization by [@moh-hassan](https://github.com/commandlineparser/commandline/pull/557). diff --git a/README.md b/README.md index d1b0c174..75901a63 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,15 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Compatible with __.NET Framework 4.0+__, __Mono 2.1+ Profile__, __.NET Standard__ and __.NET Core__ - Doesn't depend on other packages (No dependencies beyond standard base libraries) -- One line parsing using default singleton: `CommandLine.Parser.Default.ParseArguments(...)`. +- One line parsing using default singleton: `CommandLine.Parser.Default.ParseArguments(...)` and three overload methods. - Automatic or one line help screen generator: `HelpText.AutoBuild(...)`. - Supports `--help`, `--version`, `version` and `help [verb]` by default with customization. - Map to sequences (via `IEnumerable` and similar) and scalar types, including Enums and `Nullable`. -- You can also map to every type with a constructor that accepts a string (like `System.Uri`). +- You can also map to every type with a constructor that accepts a string (like `System.Uri`) for reference and value types. +- Verbs can be array of types collected from Plugins or IoC container. - Define [verb commands](https://github.com/commandlineparser/commandline/wiki/Verbs) similar to `git commit -a`. +- Support default verb. +- Support Mutable and Imutable types. - Support HelpText localization. - Support ordering of options in HelpText. - Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and Options groups. @@ -35,6 +38,8 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Support Asynchronous programming with async and await. - Unparsing support: `CommandLine.Parser.Default.FormatCommandLine(T options)`. - CommandLineParser.FSharp package is F#-friendly with support for `option<'a>`, see [demo](https://github.com/commandlineparser/commandline/blob/master/demo/fsharp-demo.fsx). _NOTE: This is a separate NuGet package._ +- Include good wiki documentation with lot of examples ready to run online. +- Support Sourcelink and symbolic package. - Most of features applies with a [CoC](http://en.wikipedia.org/wiki/Convention_over_configuration) philosophy. - C# demo: source [here](https://github.com/commandlineparser/commandline/tree/master/demo/ReadText.Demo). @@ -313,6 +318,7 @@ __And most importantly, please target the ```develop``` branch in your pull requ - Dan Nemec (@nemec) - Eric Newton (@ericnewton76) - Kevin Moore (@gimmemoore) +- Moh-Hassan (@moh-hassan) - Steven Evans - Thomas Démoulins (@Thilas) @@ -334,3 +340,4 @@ __And most importantly, please target the ```develop``` branch in your pull requ - GitHub: [ericnewton76](https://github.com/ericnewton76) - Blog: - Twitter: [enorl76](http://twitter.com/enorl76) +- Moh-Hassan From b420f040b447df8e92958de9fb929e8125c8175f Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Thu, 12 Mar 2020 19:00:29 +0200 Subject: [PATCH 147/198] fix issue #593 (#597) --- tests/CommandLine.Tests/Unit/ParserTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 743bb0f6..90147ba6 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -902,7 +902,7 @@ public void When_HelpWriter_is_null_it_should_not_fire_exception() var sut = new Parser(config => config.HelpWriter = null); sut.ParseArguments(new[] {"--dummy"}); //Assert - sut.Settings.MaximumDisplayWidth.Should().Be(80); + sut.Settings.MaximumDisplayWidth.Should().BeGreaterThan(1); } } } From e80b4dc278bb0d559829c6f45e62fd8de9450ff4 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Thu, 12 Mar 2020 20:20:58 +0200 Subject: [PATCH 148/198] Add Linux as CI in appveyor (#598) --- README.md | 7 +-- appveyor.yml | 45 +++++++++++-------- .../Fakes/HelpTextWithLineBreaks_Options.cs | 2 +- .../Unit/UnParserExtensionsTests.cs | 4 +- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 75901a63..aa1145e5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Verbs can be array of types collected from Plugins or IoC container. - Define [verb commands](https://github.com/commandlineparser/commandline/wiki/Verbs) similar to `git commit -a`. - Support default verb. -- Support Mutable and Imutable types. +- Support Mutable and Immutable types. - Support HelpText localization. - Support ordering of options in HelpText. - Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and Options groups. @@ -38,8 +38,9 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Support Asynchronous programming with async and await. - Unparsing support: `CommandLine.Parser.Default.FormatCommandLine(T options)`. - CommandLineParser.FSharp package is F#-friendly with support for `option<'a>`, see [demo](https://github.com/commandlineparser/commandline/blob/master/demo/fsharp-demo.fsx). _NOTE: This is a separate NuGet package._ -- Include good wiki documentation with lot of examples ready to run online. -- Support Sourcelink and symbolic package. +- Include wiki documentation with lot of examples ready to run online. +- Support Source Link and symbolic nuget package snupkg. +- Tested in Windows, Linux Ubuntu 18.04 and Mac OS. - Most of features applies with a [CoC](http://en.wikipedia.org/wiki/Convention_over_configuration) philosophy. - C# demo: source [here](https://github.com/commandlineparser/commandline/tree/master/demo/ReadText.Demo). diff --git a/appveyor.yml b/appveyor.yml index 283a785c..04882c0c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,9 @@ #version should be only changed with RELEASE eminent, see RELEASE.md version: 2.8.0-ci-{build} -image: Visual Studio 2019 +image: + - Visual Studio 2019 + - ubuntu1804 clone_depth: 1 pull_requests: @@ -26,8 +28,7 @@ skip_commits: files: - docs/* - art/* - - '**/*.md' - #- .travis.yml + - '**/*.md' - .gitignore - .editorconfig message: /updated readme.*|update readme.*s|update docs.*|update version.*|update changelog.*/ @@ -39,12 +40,15 @@ environment: build_script: - cmd: dotnet build src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% +- sh: dotnet build src/CommandLine/ -c Release --version-suffix $PACKAGE_VERSION /p:BuildTarget=$BUILD_TARGET test_script: - cmd: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=%BUILD_TARGET% +- sh: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=$BUILD_TARGET -f netcoreapp2.0 after_test: - cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% +- sh: dotnet pack src/CommandLine/ -c Release --version-suffix $PACKAGE_VERSION /p:BuildTarget=$BUILD_TARGET artifacts: - path: 'src/CommandLine/bin/Release/*.nupkg' @@ -56,20 +60,25 @@ on_failure: tree /f /a >files.lst appveyor PushArtifact .\files.lst -DeploymentName "Failed Build File Listing" -deploy: -- provider: GitHub - auth_token: - secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ - artifact: 'NuGetPackages' - prerelease: false - force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added - on: - APPVEYOR_REPO_TAG: true +for: +- + matrix: + only: + - image: Visual Studio 2019 + deploy: + - provider: GitHub + auth_token: + secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ + artifact: 'NuGetPackages' + prerelease: false + force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added + on: + APPVEYOR_REPO_TAG: true -- provider: NuGet - api_key: - secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT - artifact: 'NuGetPackages' - on: - APPVEYOR_REPO_TAG: true + - provider: NuGet + api_key: + secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT + artifact: 'NuGetPackages' + on: + APPVEYOR_REPO_TAG: true diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs index c93e73aa..b8974ca7 100644 --- a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs +++ b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs @@ -10,7 +10,7 @@ It has multiple lines. [Option(HelpText = @"This is a help text description where we want - The left pad after a linebreak to be honoured so that + the left pad after a linebreak to be honoured so that we can sub-indent within a description.")] public string StringValu2 { get; set; } diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 3edb8f20..2c95e893 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -207,8 +207,8 @@ public static void UnParsing_instance_with_int_nullable(bool skipDefault, int? v } [Theory] - [InlineData(Shapes.Circle, "--shape circle")] - [InlineData(Shapes.Square, "--shape square")] + [InlineData(Shapes.Circle, "--shape Circle")] + [InlineData(Shapes.Square, "--shape Square")] [InlineData(null, "")] public static void UnParsing_instance_with_nullable_enum(Shapes? shape, string expected) { From 78171b080e04e1054a19ed2c72de05635c07ebc9 Mon Sep 17 00:00:00 2001 From: Tyler Dunkel <40210514+tydunkel@users.noreply.github.com> Date: Sun, 26 May 2019 21:32:44 -0700 Subject: [PATCH 149/198] Add multi-instance option support --- src/CommandLine/Core/InstanceBuilder.cs | 31 +++- src/CommandLine/Core/InstanceChooser.cs | 29 +++- src/CommandLine/Core/OptionMapper.cs | 34 ++-- src/CommandLine/Core/Sequence.cs | 153 +++++++++++++++--- .../Core/SpecificationPropertyRules.cs | 17 +- src/CommandLine/Core/TokenPartitioner.cs | 5 +- src/CommandLine/Core/TypeConverter.cs | 2 +- src/CommandLine/Parser.cs | 5 +- src/CommandLine/ParserSettings.cs | 10 ++ .../Unit/Core/InstanceBuilderTests.cs | 14 +- .../Unit/Core/InstanceChooserTests.cs | 17 +- .../Unit/Core/OptionMapperTests.cs | 62 +++++++ .../Unit/Core/SequenceTests.cs | 64 +++++++- .../Core/SpecificationPropertyRulesTests.cs | 58 +++++++ .../Unit/Core/TypeConverterTests.cs | 10 ++ tests/CommandLine.Tests/Unit/ParserTests.cs | 66 +++++--- 16 files changed, 505 insertions(+), 72 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index dce377f1..788ce187 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -23,6 +23,31 @@ public static ParserResult Build( bool autoHelp, bool autoVersion, IEnumerable nonFatalErrors) + { + return Build( + factory, + tokenizer, + arguments, + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + false, + nonFatalErrors); + } + + public static ParserResult Build( + Maybe> factory, + Func, IEnumerable, Result, Error>> tokenizer, + IEnumerable arguments, + StringComparer nameComparer, + bool ignoreValueCase, + CultureInfo parsingCulture, + bool autoHelp, + bool autoVersion, + bool allowMultiInstance, + IEnumerable nonFatalErrors) { var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T)); @@ -70,7 +95,7 @@ public static ParserResult Build( var valueSpecPropsResult = ValueMapper.MapValues( (from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt), - valuesPartition, + valuesPartition, (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase)); var missingValueErrors = from token in errorsPartition @@ -86,7 +111,7 @@ public static ParserResult Build( //build the instance, determining if the type is mutable or not. T instance; - if(typeInfo.IsMutable() == true) + if (typeInfo.IsMutable() == true) { instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors); } @@ -95,7 +120,7 @@ public static ParserResult Build( instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors); } - var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens)); + var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens, allowMultiInstance)); var allErrors = tokenizerResult.SuccessMessages() diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index f3ab9b99..2b868f7c 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -22,6 +22,31 @@ public static ParserResult Choose( bool autoHelp, bool autoVersion, IEnumerable nonFatalErrors) + { + return Choose( + tokenizer, + types, + arguments, + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + false, + nonFatalErrors); + } + + public static ParserResult Choose( + Func, IEnumerable, Result, Error>> tokenizer, + IEnumerable types, + IEnumerable arguments, + StringComparer nameComparer, + bool ignoreValueCase, + CultureInfo parsingCulture, + bool autoHelp, + bool autoVersion, + bool allowMultiInstance, + IEnumerable nonFatalErrors) { var verbs = Verb.SelectFromTypes(types); var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); @@ -46,7 +71,7 @@ public static ParserResult Choose( arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors); }; return arguments.Any() @@ -92,6 +117,7 @@ private static ParserResult MatchVerb( CultureInfo parsingCulture, bool autoHelp, bool autoVersion, + bool allowMultiInstance, IEnumerable nonFatalErrors) { return verbs.Any(a => nameComparer.Equals(a.Item1.Name, arguments.First())) @@ -106,6 +132,7 @@ private static ParserResult MatchVerb( parsingCulture, autoHelp, autoVersion, + allowMultiInstance, nonFatalErrors) : MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); } diff --git a/src/CommandLine/Core/OptionMapper.cs b/src/CommandLine/Core/OptionMapper.cs index 18349b40..f01f14ee 100644 --- a/src/CommandLine/Core/OptionMapper.cs +++ b/src/CommandLine/Core/OptionMapper.cs @@ -22,26 +22,32 @@ public static Result< .Select( pt => { - var matched = options.FirstOrDefault(s => + var matched = options.Where(s => s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongName, comparer)).ToMaybe(); - return matched.IsJust() - ? ( - from sequence in matched - from converted in - converter( - sequence.Value, - pt.Property.PropertyType, - pt.Specification.TargetType != TargetType.Sequence) - select Tuple.Create( - pt.WithValue(Maybe.Just(converted)), Maybe.Nothing()) - ) + + if (matched.IsJust()) + { + var matches = matched.GetValueOrDefault(Enumerable.Empty>>()); + var values = new HashSet(); + foreach (var kvp in matches) + { + foreach (var value in kvp.Value) + { + values.Add(value); + } + } + + return converter(values, pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence) + .Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing())) .GetValueOrDefault( Tuple.Create>( pt, Maybe.Just( new BadFormatConversionError( - ((OptionSpecification)pt.Specification).FromOptionSpecification())))) - : Tuple.Create(pt, Maybe.Nothing()); + ((OptionSpecification)pt.Specification).FromOptionSpecification())))); + } + + return Tuple.Create(pt, Maybe.Nothing()); } ).Memoize(); return Result.Succeed( diff --git a/src/CommandLine/Core/Sequence.cs b/src/CommandLine/Core/Sequence.cs index 04d1b4ae..10b9c600 100644 --- a/src/CommandLine/Core/Sequence.cs +++ b/src/CommandLine/Core/Sequence.cs @@ -14,30 +14,141 @@ public static IEnumerable Partition( IEnumerable tokens, Func> typeLookup) { - return from tseq in tokens.Pairwise( - (f, s) => - f.IsName() && s.IsValue() - ? typeLookup(f.Text).MapValueOrDefault(info => - info.TargetType == TargetType.Sequence - ? new[] { f }.Concat(tokens.OfSequence(f, info)) - : new Token[] { }, new Token[] { }) - : new Token[] { }) - from t in tseq - select t; - } + var sequences = new Dictionary>(); + var state = SequenceState.TokenSearch; + Token nameToken = default; + foreach (var token in tokens) + { + switch (state) + { + case SequenceState.TokenSearch: + if (token.IsName()) + { + if (typeLookup(token.Text).MatchJust(out var info) && info.TargetType == TargetType.Sequence) + { + nameToken = token; + state = SequenceState.TokenFound; + } + } + break; - private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) - { - var nameIndex = tokens.IndexOf(t => t.Equals(nameToken)); - if (nameIndex >= 0) + case SequenceState.TokenFound: + if (token.IsValue()) + { + if (sequences.TryGetValue(nameToken, out var sequence)) + { + sequence.Add(token); + } + else + { + sequences[nameToken] = new List(new[] { token }); + } + } + else if (token.IsName()) + { + if (typeLookup(token.Text).MatchJust(out var info) && info.TargetType == TargetType.Sequence) + { + nameToken = token; + state = SequenceState.TokenFound; + } + else + { + state = SequenceState.TokenSearch; + } + } + else + { + state = SequenceState.TokenSearch; + } + break; + } + } + + foreach (var kvp in sequences) { - return info.NextValue.MapValueOrDefault( - _ => info.MaxItems.MapValueOrDefault( - n => tokens.Skip(nameIndex + 1).Take(n), - tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue())), - tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue())); + yield return kvp.Key; + foreach (var value in kvp.Value) + { + yield return value; + } } - return new Token[] { }; + + //return from tseq in tokens.Pairwise( + //(f, s) => + // f.IsName() && s.IsValue() + // ? typeLookup(f.Text).MapValueOrDefault(info => + // info.TargetType == TargetType.Sequence + // ? new[] { f }.Concat(tokens.OfSequence(f, info)) + // : new Token[] { }, new Token[] { }) + // : new Token[] { }) + // from t in tseq + // select t; + } + + //private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) + //{ + // var state = SequenceState.TokenSearch; + // var count = 0; + // var max = info.MaxItems.GetValueOrDefault(int.MaxValue); + // var values = max != int.MaxValue + // ? new List(max) + // : new List(); + + // foreach (var token in tokens) + // { + // if (count == max) + // { + // break; + // } + + // switch (state) + // { + // case SequenceState.TokenSearch: + // if (token.IsName() && token.Text.Equals(nameToken.Text)) + // { + // state = SequenceState.TokenFound; + // } + // break; + + // case SequenceState.TokenFound: + // if (token.IsValue()) + // { + // state = SequenceState.ValueFound; + // count++; + // values.Add(token); + // } + // else + // { + // // Invalid to provide option without value + // return Enumerable.Empty(); + // } + // break; + + // case SequenceState.ValueFound: + // if (token.IsValue()) + // { + // count++; + // values.Add(token); + // } + // else if (token.IsName() && token.Text.Equals(nameToken.Text)) + // { + // state = SequenceState.TokenFound; + // } + // else + // { + // state = SequenceState.TokenSearch; + // } + // break; + // } + // } + + // return values; + //} + + private enum SequenceState + { + TokenSearch, + TokenFound, } } } diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 5dc1a406..4f8b78a9 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -13,6 +13,14 @@ static class SpecificationPropertyRules public static IEnumerable, IEnumerable>> Lookup( IEnumerable tokens) + { + return Lookup(tokens, false); + } + + public static IEnumerable, IEnumerable>> + Lookup( + IEnumerable tokens, + bool allowMultiInstance) { return new List, IEnumerable>> { @@ -21,7 +29,7 @@ public static IEnumerable, IEnumerable, IEnumerable> EnforceSingle(IEnumerable tokens) + private static Func, IEnumerable> EnforceSingle(IEnumerable tokens, bool allowMultiInstance) { return specProps => { + if (allowMultiInstance) + { + return Enumerable.Empty(); + } + var specs = from sp in specProps where sp.Specification.IsOption() where sp.Value.IsJust() diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index be38a6d0..608ae0e8 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -21,10 +21,11 @@ Tuple>>, IEnumerable(Switch.Partition(tokenList, typeLookup), tokenComparer); var scalars = new HashSet(Scalar.Partition(tokenList, typeLookup), tokenComparer); var sequences = new HashSet(Sequence.Partition(tokenList, typeLookup), tokenComparer); + var dedupedSequences = new HashSet(sequences); var nonOptions = tokenList .Where(t => !switches.Contains(t)) .Where(t => !scalars.Contains(t)) - .Where(t => !sequences.Contains(t)).Memoize(); + .Where(t => !dedupedSequences.Contains(t)).Memoize(); var values = nonOptions.Where(v => v.IsValue()).Memoize(); var errors = nonOptions.Except(values, (IEqualityComparer)ReferenceEqualityComparer.Default).Memoize(); @@ -36,4 +37,4 @@ Tuple>>, IEnumerable ChangeType(IEnumerable values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase) { return scalar - ? ChangeTypeScalar(values.Single(), conversionType, conversionCulture, ignoreValueCase) + ? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase) : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); } diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index f801c0f7..10c9b4e1 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -101,6 +101,7 @@ public ParserResult ParseArguments(IEnumerable args) settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, + settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -131,6 +132,7 @@ public ParserResult ParseArguments(Func factory, IEnumerable ar settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, + settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -163,6 +165,7 @@ public ParserResult ParseArguments(IEnumerable args, params Type settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, + settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -228,4 +231,4 @@ private void Dispose(bool disposing) } } } -} \ No newline at end of file +} diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 07c10c4c..95a4cd81 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -25,6 +25,7 @@ public class ParserSettings : IDisposable private CultureInfo parsingCulture; private bool enableDashDash; private int maximumDisplayWidth; + private bool allowMultiInstance; /// /// Initializes a new instance of the class. @@ -174,6 +175,15 @@ public int MaximumDisplayWidth set { maximumDisplayWidth = value; } } + /// + /// Gets or sets a value indicating whether options are allowed to be specified multiple times. + /// + public bool AllowMultiInstance + { + get => allowMultiInstance; + set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, value); + } + internal StringComparer NameComparer { get diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 8dd7371c..8b95af1c 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -19,7 +19,7 @@ namespace CommandLine.Tests.Unit.Core { public class InstanceBuilderTests { - private static ParserResult InvokeBuild(string[] arguments, bool autoHelp = true, bool autoVersion = true) + private static ParserResult InvokeBuild(string[] arguments, bool autoHelp = true, bool autoVersion = true, bool multiInstance = false) where T : new() { return InstanceBuilder.Build( @@ -31,6 +31,7 @@ private static ParserResult InvokeBuild(string[] arguments, bool autoHelp CultureInfo.InvariantCulture, autoHelp, autoVersion, + multiInstance, Enumerable.Empty()); } @@ -1235,6 +1236,17 @@ public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() errors.Should().BeEquivalentTo(expectedResult); } + [Fact] + public void Parse_int_sequence_with_multi_instance() + { + var expected = new[] { 1, 2, 3 }; + var result = InvokeBuild( + new[] { "--int-seq", "1", "2", "--int-seq", "3" }, + multiInstance: true); + + ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); + } + #region custom types diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs index c9dae5fb..d5cb9a21 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs @@ -15,7 +15,8 @@ public class InstanceChooserTests { private static ParserResult InvokeChoose( IEnumerable types, - IEnumerable arguments) + IEnumerable arguments, + bool multiInstance = false) { return InstanceChooser.Choose( (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), @@ -26,6 +27,7 @@ private static ParserResult InvokeChoose( CultureInfo.InvariantCulture, true, true, + multiInstance, Enumerable.Empty()); } @@ -168,5 +170,18 @@ public void Parse_sequence_verb_with_separator_returns_verb_instance(string[] ar expected.Should().BeEquivalentTo(((Parsed)result).Value); // Teardown } + + [Fact] + public void Parse_sequence_verb_with_multi_instance_returns_verb_instance() + { + var expected = new SequenceOptions { LongSequence = new long[] { }, StringSequence = new[] { "s1", "s2" } }; + var result = InvokeChoose( + new[] { typeof(Add_Verb), typeof(Commit_Verb), typeof(Clone_Verb), typeof(SequenceOptions) }, + new[] { "sequence", "-s", "s1", "-s", "s2" }, + true); + + Assert.IsType(((Parsed)result).Value); + expected.Should().BeEquivalentTo(((Parsed)result).Value); + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index b2219683..63bf22f3 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -49,5 +49,67 @@ public void Map_boolean_switch_creates_boolean_value() // Teardown } + + [Fact] + public void Map_with_multi_instance_scalar() + { + var tokenPartitions = new[] + { + new KeyValuePair>("s", new[] { "string1" }), + new KeyValuePair>("shortandlong", new[] { "string2" }), + new KeyValuePair>("shortandlong", new[] { "string3" }), + new KeyValuePair>("s", new[] { "string4" }), + }; + + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification("s", "shortandlong", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals(nameof(Simple_Options.ShortAndLong), StringComparison.Ordinal)), + Maybe.Nothing()), + }; + + var result = OptionMapper.MapValues( + specProps.Where(pt => pt.Specification.IsOption()), + tokenPartitions, + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + StringComparer.Ordinal); + + var property = result.SucceededWith().Single(); + Assert.True(property.Specification.IsOption()); + Assert.True(property.Value.MatchJust(out var stringVal)); + Assert.Equal(tokenPartitions.Last().Value.Last(), stringVal); + } + + [Fact] + public void Map_with_multi_instance_sequence() + { + var tokenPartitions = new[] + { + new KeyValuePair>("i", new [] { "1", "2" }), + new KeyValuePair>("i", new [] { "3" }), + new KeyValuePair>("i", new [] { "4", "5" }), + }; + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), + typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals(nameof(Simple_Options.IntSequence), StringComparison.Ordinal)), + Maybe.Nothing()) + }; + + var result = OptionMapper.MapValues( + specProps.Where(pt => pt.Specification.IsOption()), + tokenPartitions, + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + StringComparer.Ordinal); + + var property = result.SucceededWith().Single(); + Assert.True(property.Specification.IsOption()); + Assert.True(property.Value.MatchJust(out var sequence)); + + var expected = tokenPartitions.Aggregate(Enumerable.Empty(), (prev, part) => prev.Concat(part.Value.Select(i => int.Parse(i)))); + Assert.Equal(expected, sequence); + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs index b26575b8..65d3dd3e 100644 --- a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs @@ -49,7 +49,7 @@ public void Partition_sequence_values() } [Fact] - public void Partition_sequence_values_from_two_sequneces() + public void Partition_sequence_values_from_two_sequences() { var expected = new[] { @@ -93,5 +93,67 @@ public void Partition_sequence_values_only() expected.Should().BeEquivalentTo(result); } + + [Fact] + public void Partition_sequence_multi_instance() + { + var expected = new[] + { + Token.Name("seq"), + Token.Value("seqval0"), + Token.Value("seqval1"), + Token.Value("seqval2"), + Token.Value("seqval3"), + Token.Value("seqval4"), + }; + + var result = Sequence.Partition( + new[] + { + Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), + Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1"), + Token.Name("x"), Token.Value("freevalue2"), + Token.Name("seq"), Token.Value("seqval2"), Token.Value("seqval3"), + Token.Name("seq"), Token.Value("seqval4") + }, + name => + new[] { "seq" }.Contains(name) + ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) + : Maybe.Nothing()); + + var actual = result.ToArray(); + Assert.Equal(expected, actual); + } + + [Fact] + public void Partition_sequence_multi_instance_with_max() + { + var expected = new[] + { + Token.Name("seq"), + Token.Value("seqval0"), + Token.Value("seqval1"), + Token.Value("seqval2"), + Token.Value("seqval3"), + Token.Value("seqval4"), + Token.Value("seqval5"), + }; + + var result = Sequence.Partition( + new[] + { + Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), + Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1"), + Token.Name("x"), Token.Value("freevalue2"), + Token.Name("seq"), Token.Value("seqval2"), Token.Value("seqval3"), + Token.Name("seq"), Token.Value("seqval4"), Token.Value("seqval5"), + }, + name => + new[] { "seq" }.Contains(name) + ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Just(3))) + : Maybe.Nothing()); + + Assert.Equal(expected, result); + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs b/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs new file mode 100644 index 00000000..6e055c55 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs @@ -0,0 +1,58 @@ +using CommandLine.Core; +using CommandLine.Tests.Fakes; +using CSharpx; +using System.Collections.Generic; +using Xunit; + +namespace CommandLine.Tests.Unit.Core +{ + + public class SpecificationPropertyRulesTests + { + [Fact] + public void Lookup_allows_multi_instance() + { + var tokens = new[] + { + Token.Name("name"), + Token.Value("value"), + Token.Name("name"), + Token.Value("value2"), + }; + + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification(string.Empty, "name", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), + typeof(SequenceOptions).GetProperty(nameof(SequenceOptions.StringSequence)), + Maybe.Just(new object())), + }; + + var results = specProps.Validate(SpecificationPropertyRules.Lookup(tokens, true)); + Assert.Empty(results); + } + + [Fact] + public void Lookup_fails_with_repeated_options_false_multi_instance() + { + var tokens = new[] + { + Token.Name("name"), + Token.Value("value"), + Token.Name("name"), + Token.Value("value2"), + }; + + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification(string.Empty, "name", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), + typeof(SequenceOptions).GetProperty(nameof(SequenceOptions.StringSequence)), + Maybe.Just(new object())), + }; + + var results = specProps.Validate(SpecificationPropertyRules.Lookup(tokens, false)); + Assert.Contains(results, r => r.GetType() == typeof(RepeatedOptionError)); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index d9f3988c..ed9d6015 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -33,6 +33,16 @@ public void ChangeType_scalars(string testValue, Type destinationType, bool expe } } + [Fact] + public void ChangeType_Scalar_LastOneWins() + { + var values = new[] { "100", "200", "300", "400", "500" }; + var result = TypeConverter.ChangeType(values, typeof(int), true, CultureInfo.InvariantCulture, true); + result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully"); + Assert.Equal(500, matchedValue); + + } + public static IEnumerable ChangeType_scalars_source { get diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 90147ba6..46aad457 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -847,40 +847,58 @@ public void Blank_lines_are_inserted_between_verbs() // Teardown } - [Fact] - public void Parse_default_verb_implicit() + public void Parse_repeated_options_in_verbs_scenario_with_multi_instance() { - var parser = Parser.Default; - parser.ParseArguments(new[] { "-t" }) - .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) - .WithParsed(args => + using (var sut = new Parser(settings => settings.AllowMultiInstance = true)) + { + var longVal1 = 100; + var longVal2 = 200; + var longVal3 = 300; + var stringVal = "shortSeq1"; + + var result = sut.ParseArguments( + new[] { "sequence", "--long-seq", $"{longVal1}", "-s", stringVal, "--long-seq", $"{longVal2};{longVal3}" }, + typeof(Add_Verb), typeof(Commit_Verb), typeof(SequenceOptions)); + + Assert.IsType>(result); + Assert.IsType(((Parsed)result).Value); + result.WithParsed(verb => { - Assert.True(args.TestValueOne); + Assert.Equal(new long[] { longVal1, longVal2, longVal3 }, verb.LongSequence); + Assert.Equal(new[] { stringVal }, verb.StringSequence); }); + } } [Fact] - public void Parse_default_verb_explicit() + public void Parse_repeated_options_in_verbs_scenario_without_multi_instance() { - var parser = Parser.Default; - parser.ParseArguments(new[] { "default1", "-t" }) - .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) - .WithParsed(args => - { - Assert.True(args.TestValueOne); - }); - } + using (var sut = new Parser(settings => settings.AllowMultiInstance = false)) + { + var longVal1 = 100; + var longVal2 = 200; + var longVal3 = 300; + var stringVal = "shortSeq1"; - [Fact] - public void Parse_multiple_default_verbs() - { - var parser = Parser.Default; - parser.ParseArguments(new string[] { }) - .WithNotParsed(errors => Assert.IsType(errors.First())) - .WithParsed(args => throw new InvalidOperationException("Should not be parsed.")); - } + var result = sut.ParseArguments( + new[] { "sequence", "--long-seq", $"{longVal1}", "-s", stringVal, "--long-seq", $"{longVal2};{longVal3}" }, + typeof(Add_Verb), typeof(Commit_Verb), typeof(SequenceOptions)); + Assert.IsType>(result); + result.WithNotParsed(errors => Assert.All(errors, e => + { + if (e is RepeatedOptionError) + { + // expected + } + else + { + throw new Exception($"{nameof(RepeatedOptionError)} expected"); + } + })); + } + } [Fact] public void Parse_default_verb_with_empty_name() { From 182e72fa363485f10b2cdbf22b8c1ca23d03eae5 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Thu, 12 Mar 2020 20:20:58 +0200 Subject: [PATCH 150/198] Add Linux as CI in appveyor (#598) --- CHANGELOG.md | 3 +- README.md | 7 +- appveyor.yml | 78 ++++++++++++------- .../Fakes/HelpTextWithLineBreaks_Options.cs | 2 +- .../Unit/UnParserExtensionsTests.cs | 4 +- 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ede9176a..526e3095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.8.0-preview1] - 2020-3-12 +## [2.8.0-preview1] - 2020-3-14 ### Added - Added support for async programming for `WithParsed and WithNotParsed` by [@joseangelmt, PR# 390 ](https://github.com/commandlineparser/commandline/pull/390). @@ -11,6 +11,7 @@ CommandLineParser project adheres to [Semantic Versioning](https://semver.org/sp - Add more details for localized attribute properties by [@EdmondShtogu, PR# 558](https://github.com/commandlineparser/commandline/pull/558) - Support Default in Group Options and raise error if both SetName and Group are applied on option by [@hadzhiyski, PR# 575](https://github.com/commandlineparser/commandline/pull/575). - Support mutable types without empty constructor that only does explicit implementation of interfaces by [@pergardebrink](https://github.com/commandlineparser/commandline/pull/590). +- ### Changed - Tests cleanup by [@gsscoder, PR# 560](https://github.com/commandlineparser/commandline/pull/560). diff --git a/README.md b/README.md index 75901a63..aa1145e5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Verbs can be array of types collected from Plugins or IoC container. - Define [verb commands](https://github.com/commandlineparser/commandline/wiki/Verbs) similar to `git commit -a`. - Support default verb. -- Support Mutable and Imutable types. +- Support Mutable and Immutable types. - Support HelpText localization. - Support ordering of options in HelpText. - Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and Options groups. @@ -38,8 +38,9 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Support Asynchronous programming with async and await. - Unparsing support: `CommandLine.Parser.Default.FormatCommandLine(T options)`. - CommandLineParser.FSharp package is F#-friendly with support for `option<'a>`, see [demo](https://github.com/commandlineparser/commandline/blob/master/demo/fsharp-demo.fsx). _NOTE: This is a separate NuGet package._ -- Include good wiki documentation with lot of examples ready to run online. -- Support Sourcelink and symbolic package. +- Include wiki documentation with lot of examples ready to run online. +- Support Source Link and symbolic nuget package snupkg. +- Tested in Windows, Linux Ubuntu 18.04 and Mac OS. - Most of features applies with a [CoC](http://en.wikipedia.org/wiki/Convention_over_configuration) philosophy. - C# demo: source [here](https://github.com/commandlineparser/commandline/tree/master/demo/ReadText.Demo). diff --git a/appveyor.yml b/appveyor.yml index 283a785c..02ef623d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,9 @@ #version should be only changed with RELEASE eminent, see RELEASE.md version: 2.8.0-ci-{build} -image: Visual Studio 2019 +image: + - Visual Studio 2019 + - ubuntu1804 clone_depth: 1 pull_requests: @@ -10,24 +12,37 @@ pull_requests: init: - ps: | git config --global core.autocrlf input - + $env:CAN_PUBLISH = $true if ($env:APPVEYOR_REPO_TAG -eq "true") { $ver = $env:APPVEYOR_REPO_TAG_NAME if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) } - $env:PACKAGE_VERSION = $ver - } else { - $env:PACKAGE_VERSION = $env:APPVEYOR_BUILD_VERSION + try + { + Update-AppveyorBuild -Version $ver + } + catch + { + Write-Output "Update-AppveyorBuild Fail to change version to TAG: '$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Red + Write-Output "Exception Error: $PSItem.Exception.Message" -ForegroundColor Red + $env:CAN_PUBLISH = $false + } } + + - ps: | + if( $ver -match '^\d+\.\d+\.\d+$') { + $env:IS_RELEASE = $true + } else { + $env:IS_RELEASE = $false } + - ps: | - Write-Host "PACKAGE_VERSION:$env:PACKAGE_VERSION | APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow - Write-Host "APPVEYOR_REPO_TAG_NAME:$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Yellow + Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow + Write-Host "APPVEYOR_REPO_TAG_NAME= '$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Yellow skip_commits: files: - docs/* - art/* - - '**/*.md' - #- .travis.yml + - '**/*.md' - .gitignore - .editorconfig message: /updated readme.*|update readme.*s|update docs.*|update version.*|update changelog.*/ @@ -38,13 +53,16 @@ environment: - BUILD_TARGET: fsharp build_script: -- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% +- cmd: dotnet build src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% +- sh: dotnet build src/CommandLine/ -c Release --version-suffix $APPVEYOR_BUILD_VERSION /p:BuildTarget=$BUILD_TARGET test_script: - cmd: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=%BUILD_TARGET% +- sh: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=$BUILD_TARGET -f netcoreapp2.0 after_test: -- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %PACKAGE_VERSION% /p:BuildTarget=%BUILD_TARGET% +- cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% +- sh: dotnet pack src/CommandLine/ -c Release --version-suffix $APPVEYOR_BUILD_VERSION /p:BuildTarget=$BUILD_TARGET artifacts: - path: 'src/CommandLine/bin/Release/*.nupkg' @@ -56,20 +74,28 @@ on_failure: tree /f /a >files.lst appveyor PushArtifact .\files.lst -DeploymentName "Failed Build File Listing" -deploy: -- provider: GitHub - auth_token: - secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ - artifact: 'NuGetPackages' - prerelease: false - force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added - on: - APPVEYOR_REPO_TAG: true +for: +- + matrix: + only: + - image: Visual Studio 2019 + deploy: + - provider: GitHub + auth_token: + secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ + artifact: /.*(\.|\.s)nupkg/ + prerelease: false + force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added + on: + APPVEYOR_REPO_TAG: true + CAN_PUBLISH: true -- provider: NuGet - api_key: - secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT - artifact: 'NuGetPackages' - on: - APPVEYOR_REPO_TAG: true + - provider: NuGet + api_key: + secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT + artifact: 'NuGetPackages' + on: + APPVEYOR_REPO_TAG: true + CAN_PUBLISH: true + IS_RELEASE : true diff --git a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs index c93e73aa..b8974ca7 100644 --- a/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs +++ b/tests/CommandLine.Tests/Fakes/HelpTextWithLineBreaks_Options.cs @@ -10,7 +10,7 @@ It has multiple lines. [Option(HelpText = @"This is a help text description where we want - The left pad after a linebreak to be honoured so that + the left pad after a linebreak to be honoured so that we can sub-indent within a description.")] public string StringValu2 { get; set; } diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 3edb8f20..2c95e893 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -207,8 +207,8 @@ public static void UnParsing_instance_with_int_nullable(bool skipDefault, int? v } [Theory] - [InlineData(Shapes.Circle, "--shape circle")] - [InlineData(Shapes.Square, "--shape square")] + [InlineData(Shapes.Circle, "--shape Circle")] + [InlineData(Shapes.Square, "--shape Square")] [InlineData(null, "")] public static void UnParsing_instance_with_nullable_enum(Shapes? shape, string expected) { From 2dff646ffc3955888ba00a56990429cd896f41c1 Mon Sep 17 00:00:00 2001 From: Giacomo Stelluti Scala Date: Tue, 17 Mar 2020 23:01:52 +0100 Subject: [PATCH 151/198] Fix/issue #579 (#580) * Added tests to verify issue #579 * Unparsing TimeSpan without quotes --- src/CommandLine/UnParserExtensions.cs | 2 +- .../Unit/Core/InstanceBuilderTests.cs | 16 ++++++++++++++++ .../Unit/UnParserExtensionsTests.cs | 13 ++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index e06ebf56..63921e22 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -204,7 +204,7 @@ private static string FormatValue(Specification spec, object value) private static object FormatWithQuotesIfString(object value) { - if (value is DateTime || value is TimeSpan || value is DateTimeOffset) return $"\"{value}\""; + if (value is DateTime || value is DateTimeOffset) return $"\"{value}\""; Func doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 8dd7371c..3ef33261 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1086,6 +1086,22 @@ public void Parse_TimeSpan() expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); } + #region Issue 579 + [Fact] + public void Should_not_parse_quoted_TimeSpan() + { + // Exercize system + var result = InvokeBuild(new[] { "--duration=\"00:42:00\"" }); + + var outcome = result as NotParsed; + + // Verify outcome + outcome.Should().NotBeNull(); + outcome.Errors.Should().NotBeNullOrEmpty() + .And.HaveCount(1) + .And.OnlyContain(e => e.GetType().Equals(typeof(BadFormatConversionError))); + } + #endregion [Fact] public void OptionClass_IsImmutable_HasNoCtor() diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 2c95e893..1c295642 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -106,6 +106,17 @@ public static void UnParsing_instance_with_dash_in_value_and_dashdash_disabled_r .Should().BeEquivalentTo("-something with dash"); } + #region Issue 579 + [Fact] + public static void UnParsing_instance_with_TimeSpan_returns_the_value_unquoted_in_command_line() + { + var options = new Options_With_TimeSpan { Duration = TimeSpan.FromMinutes(1) }; + new Parser() + .FormatCommandLine(options) + .Should().Be("--duration 00:01:00"); + } + #endregion + #region PR 550 [Fact] @@ -175,7 +186,7 @@ public static void UnParsing_instance_with_timespan() var options = new Options_TimeSpan { Start = ts }; var result = new Parser() .FormatCommandLine(options) - .Should().BeEquivalentTo("--start \"01:02:03\""); + .Should().BeEquivalentTo("--start 01:02:03"); //changed for issue 579 } [Theory] From 3e43750c5e0c3a433c61d8b7df0bffb8cb9d76ec Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 18 Mar 2020 16:53:24 +0200 Subject: [PATCH 152/198] Update readme.md --- README.md | 7 +++++++ appveyor.yml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aa1145e5..68b10659 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,13 @@ The Command Line Parser Library offers CLR applications a clean and concise API C:\Project> NuGet Install CommandLineParser ``` +# Nightly Build + +Nightly version of the CommandLineParser can be downloaded from github [Releases](https://github.com/commandlineparser/commandline/releases). + +The Last new features and fixes, read [changelog](https://github.com/commandlineparser/commandline/blob/master/CHANGELOG.md) + + _NOTE: Mentioned F# Support is provided via ```CommandLineParser.FSharp``` package with FSharp dependencies._ __This library provides _hassle free_ command line parsing with a constantly updated API since 2005.__ diff --git a/appveyor.yml b/appveyor.yml index 02ef623d..a3516230 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -97,5 +97,5 @@ for: on: APPVEYOR_REPO_TAG: true CAN_PUBLISH: true - IS_RELEASE : true + From 137328622ded19d228e0d380e3965659455918c6 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Fri, 20 Mar 2020 12:14:58 +0200 Subject: [PATCH 153/198] Update appveyor.yml --- appveyor.yml | 86 ++++++++++++++-------------------------------------- 1 file changed, 23 insertions(+), 63 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a3516230..ed3ac8a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,52 +1,23 @@ #version should be only changed with RELEASE eminent, see RELEASE.md version: 2.8.0-ci-{build} -image: - - Visual Studio 2019 - - ubuntu1804 + +image: Visual Studio 2019 clone_depth: 1 pull_requests: - do_not_increment_build_number: false + do_not_increment_build_number: true init: - ps: | git config --global core.autocrlf input - $env:CAN_PUBLISH = $true + if ($env:APPVEYOR_REPO_TAG -eq "true") { $ver = $env:APPVEYOR_REPO_TAG_NAME if($ver.StartsWith("v") -eq $true) { $ver = $ver.Substring(1) } - try - { - Update-AppveyorBuild -Version $ver - } - catch - { - Write-Output "Update-AppveyorBuild Fail to change version to TAG: '$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Red - Write-Output "Exception Error: $PSItem.Exception.Message" -ForegroundColor Red - $env:CAN_PUBLISH = $false - } + Update-AppveyorBuild -Version $ver } - - ps: | - if( $ver -match '^\d+\.\d+\.\d+$') { - $env:IS_RELEASE = $true - } else { - $env:IS_RELEASE = $false } - - - ps: | - Write-Host "APPVEYOR_BUILD_VERSION='$env:APPVEYOR_BUILD_VERSION'" -ForegroundColor Yellow - Write-Host "APPVEYOR_REPO_TAG_NAME= '$env:APPVEYOR_REPO_TAG_NAME'" -ForegroundColor Yellow - -skip_commits: - files: - - docs/* - - art/* - - '**/*.md' - - .gitignore - - .editorconfig - message: /updated readme.*|update readme.*s|update docs.*|update version.*|update changelog.*/ - environment: matrix: - BUILD_TARGET: base @@ -54,48 +25,37 @@ environment: build_script: - cmd: dotnet build src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% -- sh: dotnet build src/CommandLine/ -c Release --version-suffix $APPVEYOR_BUILD_VERSION /p:BuildTarget=$BUILD_TARGET test_script: - cmd: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=%BUILD_TARGET% -- sh: dotnet test tests/CommandLine.Tests/ /p:BuildTarget=$BUILD_TARGET -f netcoreapp2.0 after_test: - cmd: dotnet pack src/CommandLine/ -c Release --version-suffix %APPVEYOR_BUILD_VERSION% /p:BuildTarget=%BUILD_TARGET% -- sh: dotnet pack src/CommandLine/ -c Release --version-suffix $APPVEYOR_BUILD_VERSION /p:BuildTarget=$BUILD_TARGET artifacts: - path: 'src/CommandLine/bin/Release/*.nupkg' name: NuGetPackages - path: 'src/CommandLine/bin/Release/*.snupkg' - name: symbol + name: symbol + on_failure: - cmd: | tree /f /a >files.lst appveyor PushArtifact .\files.lst -DeploymentName "Failed Build File Listing" -for: -- - matrix: - only: - - image: Visual Studio 2019 - deploy: - - provider: GitHub - auth_token: - secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ - artifact: /.*(\.|\.s)nupkg/ - prerelease: false - force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added - on: - APPVEYOR_REPO_TAG: true - CAN_PUBLISH: true - - - provider: NuGet - api_key: - secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT - artifact: 'NuGetPackages' - on: - APPVEYOR_REPO_TAG: true - CAN_PUBLISH: true - - +deploy: +- provider: GitHub + auth_token: + secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ + artifact: /.*(\.|\.s)nupkg/ + prerelease: false + force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added + on: + APPVEYOR_REPO_TAG: true + +- provider: NuGet + api_key: + secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT + artifact: /.*(\.|\.s)nupkg/ + on: + APPVEYOR_REPO_TAG: true From 2218294550e94bcbc2b76783970541385eaf9c07 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Thu, 30 Apr 2020 04:03:18 -0400 Subject: [PATCH 154/198] updated nuget apikey --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index ed3ac8a7..d73225c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -55,7 +55,7 @@ deploy: - provider: NuGet api_key: - secure: Ab4T/48EyIJhVrqkfKdUxmHUtseEVuXuyrGACxZ0KN35rb/BzABlBM2YjZojicvT + secure: oy2copryr5y25hmyvmrav2dn3jzzjizu5gpwoepiu6yhsu artifact: /.*(\.|\.s)nupkg/ on: APPVEYOR_REPO_TAG: true From 9409df24391c83a8cbcd99b8ce78bb6b4c08da1d Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Thu, 30 Apr 2020 17:38:23 -0400 Subject: [PATCH 155/198] regenerate nuget key and reencrypt --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index d73225c2..1ce67042 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -55,7 +55,7 @@ deploy: - provider: NuGet api_key: - secure: oy2copryr5y25hmyvmrav2dn3jzzjizu5gpwoepiu6yhsu + secure: e2gJJ3r6Uls5trJwryaudAZd49QniNfIjax/A+tfywlchSnIQVOzOQCO9tTSNccI artifact: /.*(\.|\.s)nupkg/ on: APPVEYOR_REPO_TAG: true From 24e2be23ce695d36316d921d2a52f9eb94cb0558 Mon Sep 17 00:00:00 2001 From: Moh-hassan Date: Fri, 1 May 2020 18:10:51 +0200 Subject: [PATCH 156/198] Publish Release 2.8.0 --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 526e3095..f6b794e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,15 +3,18 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.8.0] - 2020-5-1 +## [2.8.0-preview4] - 2020-4-30 ## [2.8.0-preview1] - 2020-3-14 ### Added - Added support for async programming for `WithParsed and WithNotParsed` by [@joseangelmt, PR# 390 ](https://github.com/commandlineparser/commandline/pull/390). +- Publish a new symbol packages with source link support for c# and F# (.snupkg) to improved package debugging experience by [@moh-hassan, PR#554](https://github.com/commandlineparser/commandline/pull/554) - Add default verb support by [@Artentus, PR# 556](https://github.com/commandlineparser/commandline/pull/556). - Add more details for localized attribute properties by [@EdmondShtogu, PR# 558](https://github.com/commandlineparser/commandline/pull/558) - Support Default in Group Options and raise error if both SetName and Group are applied on option by [@hadzhiyski, PR# 575](https://github.com/commandlineparser/commandline/pull/575). -- Support mutable types without empty constructor that only does explicit implementation of interfaces by [@pergardebrink](https://github.com/commandlineparser/commandline/pull/590). -- +- Support mutable types without empty constructor that only does explicit implementation of interfaces by [@pergardebrink, PR#590](https://github.com/commandlineparser/commandline/pull/590). + ### Changed - Tests cleanup by [@gsscoder, PR# 560](https://github.com/commandlineparser/commandline/pull/560). @@ -20,6 +23,7 @@ CommandLineParser project adheres to [Semantic Versioning](https://semver.org/sp - SkipDefault is being respected by [Usage] Examples by [@kendfrey, PR# 565](https://github.com/commandlineparser/commandline/pull/565). - Remove useless testing code by [@gsscoder, PR# 568](https://github.com/commandlineparser/commandline/pull/568). - Remove constraint on T for ParseArguments with factory (required by issue #70) by [@pergardebrink](https://github.com/commandlineparser/commandline/pull/590). +- Update nuget api key by [@ericnewton76](https://github.com/commandlineparser/commandline/commit/2218294550e94bcbc2b76783970541385eaf9c07) ### Fixed - Fix #579 Unable to parse TimeSpan given from the FormatCommandLine by [@gsscoder, PR# 580](https://github.com/commandlineparser/commandline/pull/580). From b7e07eaa2cd37329e3369095ff665805d68f54e1 Mon Sep 17 00:00:00 2001 From: Alexandr Shevchenko Date: Mon, 4 May 2020 14:54:19 +0300 Subject: [PATCH 157/198] add support for flags enums --- src/CommandLine/Core/TypeConverter.cs | 11 +++++++++- .../Unit/Core/TypeConverterTests.cs | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index 354c4316..8f193c46 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -129,11 +129,20 @@ private static object ToEnum(this string value, Type conversionType, bool ignore { throw new FormatException(); } - if (Enum.IsDefined(conversionType, parsedValue)) + if (IsDefinedEx(parsedValue)) { return parsedValue; } throw new FormatException(); } + + private static bool IsDefinedEx(object enumValue) + { + char firstChar = enumValue.ToString()[0]; + if (Char.IsDigit(firstChar) || firstChar == '-') + return false; + + return true; + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index d9f3988c..c62a7836 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -16,6 +16,13 @@ enum TestEnum ValueB = 2 } + [Flags] + enum TestFlagEnum + { + ValueA = 0x1, + ValueB = 0x2 + } + [Theory] [MemberData(nameof(ChangeType_scalars_source))] public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult) @@ -94,6 +101,19 @@ public static IEnumerable ChangeType_scalars_source new object[] {((int) TestEnum.ValueB + 1).ToString(), typeof (TestEnum), true, null}, new object[] {((int) TestEnum.ValueA - 1).ToString(), typeof (TestEnum), true, null}, + new object[] {"ValueA", typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, + new object[] {"VALUEA", typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, + new object[] {"ValueB", typeof(TestFlagEnum), false, TestFlagEnum.ValueB}, + new object[] {"ValueA,ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {"ValueA, ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {"VALUEA,ValueB", typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {((int) TestFlagEnum.ValueA).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueA}, + new object[] {((int) TestFlagEnum.ValueB).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueB}, + new object[] {((int) (TestFlagEnum.ValueA | TestFlagEnum.ValueB)).ToString(), typeof (TestFlagEnum), false, TestFlagEnum.ValueA | TestFlagEnum.ValueB}, + new object[] {((int) TestFlagEnum.ValueB + 2).ToString(), typeof (TestFlagEnum), true, null}, + new object[] {((int) TestFlagEnum.ValueA - 1).ToString(), typeof (TestFlagEnum), true, null}, + + // Failed before #339 new object[] {"false", typeof (int), true, 0}, new object[] {"true", typeof (int), true, 0} From 8fbcc33ff3f8940429a89fc0882723e221408546 Mon Sep 17 00:00:00 2001 From: kapsiR Date: Wed, 13 May 2020 15:28:26 +0200 Subject: [PATCH 158/198] Fix unparsing FileInfo and DirectoryInfo This treats FileInfo and DirectoryInfo as special as e.g. DateTime. Unparsing a FileInfo or DirectoryInfo works now with a path that contains spaces. Fixes #626 --- src/CommandLine/UnParserExtensions.cs | 3 ++- .../Fakes/Options_With_FileDirectoryInfo.cs | 18 ++++++++++++++++++ .../Unit/UnParserExtensionsTests.cs | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_FileDirectoryInfo.cs diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 63921e22..76757138 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections; +using System.IO; using System.Linq; using System.Text; using CommandLine.Core; @@ -204,7 +205,7 @@ private static string FormatValue(Specification spec, object value) private static object FormatWithQuotesIfString(object value) { - if (value is DateTime || value is DateTimeOffset) return $"\"{value}\""; + if (value is DateTime || value is DateTimeOffset || value is FileInfo || value is DirectoryInfo) return $"\"{value}\""; Func doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; diff --git a/tests/CommandLine.Tests/Fakes/Options_With_FileDirectoryInfo.cs b/tests/CommandLine.Tests/Fakes/Options_With_FileDirectoryInfo.cs new file mode 100644 index 00000000..0d05afbf --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_FileDirectoryInfo.cs @@ -0,0 +1,18 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.IO; + +namespace CommandLine.Tests.Fakes +{ + public class Options_With_FileDirectoryInfo + { + [Option('s', "stringPath")] + public string StringPath { get; set; } + + [Option('f', "filePath")] + public FileInfo FilePath { get; set; } + + [Option('d', "directoryPath")] + public DirectoryInfo DirectoryPath { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 1c295642..d25c99e1 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Xunit; using FluentAssertions; @@ -23,6 +24,15 @@ public static void UnParsing_instance_returns_command_line(Simple_Options option .Should().BeEquivalentTo(result); } + [Theory] + [MemberData(nameof(UnParseFileDirectoryData))] + public static void UnParsing_instance_returns_command_line_for_file_directory_paths(Options_With_FileDirectoryInfo options, string result) + { + new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo(result); + } + [Theory] [MemberData(nameof(UnParseDataVerbs))] public static void UnParsing_instance_returns_command_line_for_verbs(Add_Verb verb, string result) @@ -298,6 +308,15 @@ public static IEnumerable UnParseData } } + public static IEnumerable UnParseFileDirectoryData + { + get + { + yield return new object[] { new Options_With_FileDirectoryInfo(), "" }; + yield return new object[] { new Options_With_FileDirectoryInfo { FilePath = new FileInfo(@"C:\my path\with spaces\file with spaces.txt"), DirectoryPath = new DirectoryInfo(@"C:\my path\with spaces\"), StringPath = @"C:\my path\with spaces\file with spaces.txt" }, @"--directoryPath ""C:\my path\with spaces\"" --filePath ""C:\my path\with spaces\file with spaces.txt"" --stringPath ""C:\my path\with spaces\file with spaces.txt""" }; + } + } + public static IEnumerable UnParseDataVerbs { From ad50269e7dd4db96e53f615974f858827b70d6e7 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Fri, 22 May 2020 10:50:30 -0400 Subject: [PATCH 159/198] move Errors and Value up to abstract class definition --- src/CommandLine/ParserResult.cs | 55 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/CommandLine/ParserResult.cs b/src/CommandLine/ParserResult.cs index 20761ada..d5c2dfc3 100644 --- a/src/CommandLine/ParserResult.cs +++ b/src/CommandLine/ParserResult.cs @@ -9,7 +9,7 @@ namespace CommandLine public sealed class TypeInfo { private readonly Type current; - private readonly IEnumerable choices; + private readonly IEnumerable choices; private TypeInfo(Type current, IEnumerable choices) { @@ -64,10 +64,20 @@ public abstract class ParserResult private readonly ParserResultType tag; private readonly TypeInfo typeInfo; - internal ParserResult(ParserResultType tag, TypeInfo typeInfo) + internal ParserResult(IEnumerable errors, TypeInfo typeInfo) { - this.tag = tag; + this.tag = ParserResultType.NotParsed; this.typeInfo = typeInfo; + Errors = errors; + Value = default; + } + + internal ParserResult(T value, TypeInfo typeInfo) + { + this.tag = ParserResultType.Parsed; + this.typeInfo = typeInfo; + Errors = new Error[0]; + Value = value; } /// @@ -82,6 +92,16 @@ public TypeInfo TypeInfo { get { return typeInfo; } } + + /// + /// Gets the instance with parsed values. If one or more errors occures, is returned. + /// + public T Value { get; } + + /// + /// Gets the sequence of parsing errors. If there are no errors, then an empty IEnumerable is returned. + /// + public IEnumerable Errors { get; } } /// @@ -90,12 +110,9 @@ public TypeInfo TypeInfo /// The type with attributes that define the syntax of parsing rules. public sealed class Parsed : ParserResult, IEquatable> { - private readonly T value; - internal Parsed(T value, TypeInfo typeInfo) - : base(ParserResultType.Parsed, typeInfo) + : base(value, typeInfo) { - this.value = value; } internal Parsed(T value) @@ -103,13 +120,6 @@ internal Parsed(T value) { } - /// - /// Gets the instance with parsed values. - /// - public T Value - { - get { return value; } - } /// /// Determines whether the specified is equal to the current . @@ -118,8 +128,7 @@ public T Value /// true if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { - var other = obj as Parsed; - if (other != null) + if (obj is Parsed other) { return Equals(other); } @@ -159,21 +168,12 @@ public bool Equals(Parsed other) /// The type with attributes that define the syntax of parsing rules. public sealed class NotParsed : ParserResult, IEquatable> { - private readonly IEnumerable errors; internal NotParsed(TypeInfo typeInfo, IEnumerable errors) - : base(ParserResultType.NotParsed, typeInfo) + : base(errors, typeInfo) { - this.errors = errors; } - /// - /// Gets the sequence of parsing errors. - /// - public IEnumerable Errors - { - get { return errors; } - } /// /// Determines whether the specified is equal to the current . @@ -182,8 +182,7 @@ public IEnumerable Errors /// true if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { - var other = obj as NotParsed; - if (other != null) + if (obj is NotParsed other) { return Equals(other); } From 3094bfb1f044b9409a858956ddec72bc514fbf21 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Fri, 22 May 2020 11:05:51 -0400 Subject: [PATCH 160/198] add null checks to constructors to ensure valid data --- src/CommandLine/ParserResult.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CommandLine/ParserResult.cs b/src/CommandLine/ParserResult.cs index d5c2dfc3..c7c9c833 100644 --- a/src/CommandLine/ParserResult.cs +++ b/src/CommandLine/ParserResult.cs @@ -67,17 +67,17 @@ public abstract class ParserResult internal ParserResult(IEnumerable errors, TypeInfo typeInfo) { this.tag = ParserResultType.NotParsed; - this.typeInfo = typeInfo; - Errors = errors; + this.typeInfo = typeInfo ?? TypeInfo.Create(typeof(T)); + Errors = errors ?? new Error[0]; Value = default; } internal ParserResult(T value, TypeInfo typeInfo) { + Value = value ?? throw new ArgumentNullException(nameof(value)); this.tag = ParserResultType.Parsed; - this.typeInfo = typeInfo; + this.typeInfo = typeInfo ?? TypeInfo.Create(value.GetType()); Errors = new Error[0]; - Value = value; } /// From d3b6315e7ec25c6a568bbbf6ec93278eb8ae59d7 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Fri, 22 May 2020 18:01:39 -0400 Subject: [PATCH 161/198] Adds test cases --- tests/CommandLine.Tests/Unit/Issue543Tests.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/CommandLine.Tests/Unit/Issue543Tests.cs diff --git a/tests/CommandLine.Tests/Unit/Issue543Tests.cs b/tests/CommandLine.Tests/Unit/Issue543Tests.cs new file mode 100644 index 00000000..61a83512 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue543Tests.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; +using CommandLine.Text; + +namespace CommandLine.Tests.Unit +{ + //Reference: PR# 634 + public class Issue543Tests + { + + private const int ERROR_SUCCESS = 0; + + [Fact] + public void Parser_GiveHelpArgument_ExpectSuccess() + { + var result = Parser.Default.ParseArguments(new[] { "--help" }); + + Assert.Equal(ParserResultType.NotParsed, result.Tag); + Assert.Null(result.Value); + Assert.NotEmpty(result.Errors); + } + + [Fact] + public void Parser_GiveConnectionStringAndJobId_ExpectSuccess() + { + var result = Parser.Default.ParseArguments(new[] { + "-c", "someConnectionString", + "-j", "1234", + }); + + Assert.Equal(ParserResultType.Parsed, result.Tag); + Assert.NotNull(result.Value); + Assert.Empty(result.Errors); + Assert.Equal("someConnectionString", result.Value.ConnectionString); + Assert.Equal(1234, result.Value.JobId); + } + + [Fact] + public void Parser_GiveVerb1_ExpectSuccess() + { + var result = Parser.Default.ParseArguments(new[] { + "verb1", + "-j", "1234", + }); + + Assert.Equal(ParserResultType.Parsed, result.Tag); + Assert.Empty(result.Errors); + Assert.NotNull(result.Value); + Assert.NotNull(result.Value as Verb1Options); + Assert.Equal(1234, (result.Value as Verb1Options).JobId); + } + + [Fact] + public void Parser_GiveVerb2_ExpectSuccess() + { + var result = Parser.Default.ParseArguments(new[] { + "verb2", + "-c", "someConnectionString", + }); + + Assert.Equal(ParserResultType.Parsed, result.Tag); + Assert.Empty(result.Errors); + Assert.NotNull(result.Value); + Assert.NotNull(result.Value as Verb2Options); + Assert.Equal("someConnectionString", (result.Value as Verb2Options).ConnectionString); + } + + // Options + internal class Options + { + [Option('c', "connectionString", Required = true, HelpText = "Texts.ExplainConnection")] + public string ConnectionString { get; set; } + + [Option('j', "jobId", Required = true, HelpText = "Texts.ExplainJob")] + public int JobId { get; set; } + + [Usage(ApplicationAlias = "Importer.exe")] + public static IEnumerable Examples + { + get => new[] { + new Example("Texts.ExplainExampleExecution", new Options() { + ConnectionString="Server=MyServer;Database=MyDatabase", + JobId = 5 + }), + }; + } + } + + // Options + [Verb("verb1")] + internal class Verb1Options + { + [Option('j', "jobId", Required = false, HelpText = "Texts.ExplainJob")] + public int JobId { get; set; } + } + + // Options + [Verb("verb2")] + internal class Verb2Options + { + [Option('c', "connectionString", Required = false, HelpText = "Texts.ExplainConnection")] + public string ConnectionString { get; set; } + } + + } +} + From 4c66e5ca423d487b47f10c332279fae7c61715c7 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Tue, 26 May 2020 17:35:08 -0400 Subject: [PATCH 162/198] fix test name --- .../Unit/{Issue591ests.cs => Issue591Tests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/CommandLine.Tests/Unit/{Issue591ests.cs => Issue591Tests.cs} (96%) diff --git a/tests/CommandLine.Tests/Unit/Issue591ests.cs b/tests/CommandLine.Tests/Unit/Issue591Tests.cs similarity index 96% rename from tests/CommandLine.Tests/Unit/Issue591ests.cs rename to tests/CommandLine.Tests/Unit/Issue591Tests.cs index 3888a705..41b66b74 100644 --- a/tests/CommandLine.Tests/Unit/Issue591ests.cs +++ b/tests/CommandLine.Tests/Unit/Issue591Tests.cs @@ -10,7 +10,7 @@ namespace CommandLine.Tests.Unit { - public class Issue591ests + public class Issue591Tests { [Fact] public void Parse_option_with_only_explicit_interface_implementation() From fe15c5de0814f293f6b4f8dd3ed90a91b8717786 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Tue, 26 May 2020 17:35:22 -0400 Subject: [PATCH 163/198] implement verb aliases --- src/CommandLine/Core/InstanceChooser.cs | 46 +++++++++++--------- src/CommandLine/Core/Verb.cs | 41 ++++++------------ src/CommandLine/VerbAttribute.cs | 32 +++++++------- tests/CommandLine.Tests/Unit/Issue6Tests.cs | 47 +++++++++++++++++++++ 4 files changed, 104 insertions(+), 62 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/Issue6Tests.cs diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 2b868f7c..7e292bfb 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -50,18 +50,18 @@ public static ParserResult Choose( { var verbs = Verb.SelectFromTypes(types); var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); - + int defaultVerbCount = defaultVerbs.Count(); if (defaultVerbCount > 1) return MakeNotParsed(types, new MultipleDefaultVerbsError()); var defaultVerb = defaultVerbCount == 1 ? defaultVerbs.First() : null; - Func> choose = () => + ParserResult choose() { var firstArg = arguments.First(); - Func preprocCompare = command => + bool preprocCompare(string command) => nameComparer.Equals(command, firstArg) || nameComparer.Equals(string.Concat("--", command), firstArg); @@ -72,7 +72,7 @@ public static ParserResult Choose( : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors); - }; + } return arguments.Any() ? choose() @@ -120,21 +120,29 @@ private static ParserResult MatchVerb( bool allowMultiInstance, IEnumerable nonFatalErrors) { - return verbs.Any(a => nameComparer.Equals(a.Item1.Name, arguments.First())) - ? InstanceBuilder.Build( - Maybe.Just>( - () => - verbs.Single(v => nameComparer.Equals(v.Item1.Name, arguments.First())).Item2.AutoDefault()), - tokenizer, - arguments.Skip(1), - nameComparer, - ignoreValueCase, - parsingCulture, - autoHelp, - autoVersion, - allowMultiInstance, - nonFatalErrors) - : MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + string firstArg = arguments.First(); + + var verbUsed = verbs.FirstOrDefault(vt => + nameComparer.Equals(vt.Item1.Name, firstArg) + || vt.Item1.Aliases.Any(alias => nameComparer.Equals(alias, firstArg)) + ); + + if (verbUsed == default) + { + return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + } + return InstanceBuilder.Build( + Maybe.Just>( + () => verbUsed.Item2.AutoDefault()), + tokenizer, + arguments.Skip(1), + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + allowMultiInstance, + nonFatalErrors); } private static HelpVerbRequestedError MakeHelpVerbRequestedError( diff --git a/src/CommandLine/Core/Verb.cs b/src/CommandLine/Core/Verb.cs index 3a7f12a3..b1a79ab5 100644 --- a/src/CommandLine/Core/Verb.cs +++ b/src/CommandLine/Core/Verb.cs @@ -9,41 +9,27 @@ namespace CommandLine.Core { sealed class Verb { - private readonly string name; - private readonly string helpText; - private readonly bool hidden; - private readonly bool isDefault; - - public Verb(string name, string helpText, bool hidden = false, bool isDefault = false) + public Verb(string name, string helpText, bool hidden, bool isDefault, IEnumerable aliases) { if ( string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); - this.name = name; + Name = name; - this.helpText = helpText ?? throw new ArgumentNullException(nameof(helpText)); - this.hidden = hidden; - this.isDefault = isDefault; + HelpText = helpText ?? throw new ArgumentNullException(nameof(helpText)); + Hidden = hidden; + IsDefault = isDefault; + Aliases = aliases ?? new string[0]; } - public string Name - { - get { return name; } - } + public string Name { get; private set; } - public string HelpText - { - get { return helpText; } - } + public string HelpText { get; private set; } - public bool Hidden - { - get { return hidden; } - } + public bool Hidden { get; private set; } - public bool IsDefault - { - get => isDefault; - } + public bool IsDefault { get; private set; } + + public IEnumerable Aliases { get; private set; } public static Verb FromAttribute(VerbAttribute attribute) { @@ -51,7 +37,8 @@ public static Verb FromAttribute(VerbAttribute attribute) attribute.Name, attribute.HelpText, attribute.Hidden, - attribute.IsDefault + attribute.IsDefault, + attribute.Aliases ); } diff --git a/src/CommandLine/VerbAttribute.cs b/src/CommandLine/VerbAttribute.cs index 57318b3a..eb3c8e18 100644 --- a/src/CommandLine/VerbAttribute.cs +++ b/src/CommandLine/VerbAttribute.cs @@ -1,6 +1,7 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System; +using System.Collections.Generic; namespace CommandLine { @@ -9,10 +10,8 @@ namespace CommandLine /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] //public sealed class VerbAttribute : Attribute - public class VerbAttribute : Attribute + public class VerbAttribute : Attribute { - private readonly string name; - private readonly bool isDefault; private Infrastructure.LocalizableAttributeProperty helpText; private Type resourceType; @@ -21,24 +20,23 @@ public class VerbAttribute : Attribute /// /// The long name of the verb command. /// Whether the verb is the default verb. + /// aliases for this verb. i.e. "move" and "mv" /// Thrown if is null, empty or whitespace and is false. - public VerbAttribute(string name, bool isDefault = false) + public VerbAttribute(string name, bool isDefault = false, string[] aliases = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name"); - this.name = name ; - this.isDefault = isDefault; + Name = name; + IsDefault = isDefault; helpText = new Infrastructure.LocalizableAttributeProperty(nameof(HelpText)); resourceType = null; + Aliases = aliases ?? new string[0]; } /// /// Gets the verb name. /// - public string Name - { - get { return name; } - } + public string Name { get; private set; } /// /// Gets or sets a value indicating whether a command line verb is visible in the help text. @@ -54,7 +52,7 @@ public bool Hidden /// public string HelpText { - get => helpText.Value??string.Empty; + get => helpText.Value ?? string.Empty; set => helpText.Value = value ?? throw new ArgumentNullException("value"); } /// @@ -63,15 +61,17 @@ public string HelpText public Type ResourceType { get => resourceType; - set => resourceType =helpText.ResourceType = value; + set => resourceType = helpText.ResourceType = value; } /// /// Gets whether this verb is the default verb. /// - public bool IsDefault - { - get => isDefault; - } + public bool IsDefault { get; private set; } + + /// + /// Gets or sets the aliases + /// + public IEnumerable Aliases { get; private set; } } } diff --git a/tests/CommandLine.Tests/Unit/Issue6Tests.cs b/tests/CommandLine.Tests/Unit/Issue6Tests.cs new file mode 100644 index 00000000..33394dcb --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue6Tests.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +//Issue #6 +//Support Aliases on verbs (i.e. "move" and "mv" are the same verb). + +namespace CommandLine.Tests.Unit +{ + public class Issue6Tests + { + [Theory] + [InlineData("move -a bob", typeof(AliasedVerbOption1))] + [InlineData("mv -a bob", typeof(AliasedVerbOption1))] + [InlineData("copy -a bob", typeof(AliasedVerbOption2))] + [InlineData("cp -a bob", typeof(AliasedVerbOption2))] + public void Parse_option_with_aliased_verbs(string args, Type expectedArgType) + { + var arguments = args.Split(' '); + object options = null; + var result = Parser.Default.ParseArguments(arguments) + .WithParsed((o) => options = o) + ; + + Assert.NotNull(options); + Assert.Equal(expectedArgType, options.GetType()); + } + + [Verb("move", aliases:new string[] { "mv" })] + public class AliasedVerbOption1 + { + [Option('a', "alpha", Required = true)] + public string Option { get; set; } + } + + [Verb("copy", aliases: new string[] { "cp" })] + public class AliasedVerbOption2 + { + [Option('a', "alpha", Required = true)] + public string Option { get; set; } + } + } +} From 5afbdda0945c0d26b01f1decc0dc7228c04a813e Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Wed, 27 May 2020 11:25:39 -0400 Subject: [PATCH 164/198] updates help text to incorporate verb aliases --- src/CommandLine/Core/Verb.cs | 6 +- src/CommandLine/Text/HelpText.cs | 4 +- src/CommandLine/VerbAttribute.cs | 4 +- tests/CommandLine.Tests/Unit/Issue6Tests.cs | 98 ++++++++++++++++++++- 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/src/CommandLine/Core/Verb.cs b/src/CommandLine/Core/Verb.cs index b1a79ab5..48e9427c 100644 --- a/src/CommandLine/Core/Verb.cs +++ b/src/CommandLine/Core/Verb.cs @@ -9,9 +9,9 @@ namespace CommandLine.Core { sealed class Verb { - public Verb(string name, string helpText, bool hidden, bool isDefault, IEnumerable aliases) + public Verb(string name, string helpText, bool hidden, bool isDefault, string[] aliases) { - if ( string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); Name = name; @@ -29,7 +29,7 @@ public Verb(string name, string helpText, bool hidden, bool isDefault, IEnumerab public bool IsDefault { get; private set; } - public IEnumerable Aliases { get; private set; } + public string[] Aliases { get; private set; } public static Verb FromAttribute(VerbAttribute attribute) { diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index e9ce218d..3894bc9d 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -851,10 +851,10 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable var optionSpecs = from verbTuple in Verb.SelectFromTypes(types) select OptionSpecification.NewSwitch( - string.Empty, verbTuple.Item1.Name, + verbTuple.Item1.Aliases.ToDelimitedString(", "), false, - verbTuple.Item1.IsDefault? "(Default Verb) "+verbTuple.Item1.HelpText: verbTuple.Item1.HelpText, //Default verb + verbTuple.Item1.IsDefault ? "(Default Verb) " + verbTuple.Item1.HelpText : verbTuple.Item1.HelpText, //Default verb string.Empty, verbTuple.Item1.Hidden); if (autoHelp) diff --git a/src/CommandLine/VerbAttribute.cs b/src/CommandLine/VerbAttribute.cs index eb3c8e18..6ee6024d 100644 --- a/src/CommandLine/VerbAttribute.cs +++ b/src/CommandLine/VerbAttribute.cs @@ -12,7 +12,7 @@ namespace CommandLine //public sealed class VerbAttribute : Attribute public class VerbAttribute : Attribute { - private Infrastructure.LocalizableAttributeProperty helpText; + private readonly Infrastructure.LocalizableAttributeProperty helpText; private Type resourceType; /// @@ -72,6 +72,6 @@ public Type ResourceType /// /// Gets or sets the aliases /// - public IEnumerable Aliases { get; private set; } + public string[] Aliases { get; private set; } } } diff --git a/tests/CommandLine.Tests/Unit/Issue6Tests.cs b/tests/CommandLine.Tests/Unit/Issue6Tests.cs index 33394dcb..02420b39 100644 --- a/tests/CommandLine.Tests/Unit/Issue6Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue6Tests.cs @@ -1,8 +1,11 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; using CommandLine.Tests.Fakes; using CommandLine.Text; using FluentAssertions; +using Microsoft.FSharp.Core; using Xunit; using Xunit.Abstractions; @@ -22,26 +25,115 @@ public void Parse_option_with_aliased_verbs(string args, Type expectedArgType) { var arguments = args.Split(' '); object options = null; + IEnumerable errors = null; var result = Parser.Default.ParseArguments(arguments) - .WithParsed((o) => options = o) + .WithParsed(o => options = o) + .WithNotParsed(o => errors = o) ; + if (errors != null && errors.Any()) + { + foreach (Error e in errors) + { + System.Console.WriteLine(e.ToString()); + } + } Assert.NotNull(options); Assert.Equal(expectedArgType, options.GetType()); } - [Verb("move", aliases:new string[] { "mv" })] + [Theory] + [InlineData("--help", true, new string[] + { + "copy, cp, cpy (Default Verb) Copy some stuff", + "move, mv", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", + })] + [InlineData("help", true, new string[] + { + "copy, cp, cpy (Default Verb) Copy some stuff", + "move, mv", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", + })] + [InlineData("move --help", false, new string[] + { + "-a, --alpha Required.", + "--help Display this help screen.", + "--version Display version information.", + })] + [InlineData("mv --help", false, new string[] + { + "-a, --alpha Required.", + "--help Display this help screen.", + "--version Display version information.", + })] + [InlineData("delete --help", false, new string[] + { + "-b, --beta Required.", + "--help Display this help screen.", + "--version Display version information.", + })] + public void Parse_help_option_for_aliased_verbs(string args, bool verbsIndex, string[] expected) + { + var arguments = args.Split(' '); + object options = null; + IEnumerable errors = null; + // the order of the arguments here drives the order of the commands shown + // in the help message + var result = Parser.Default.ParseArguments< + AliasedVerbOption2, + AliasedVerbOption1, + VerbNoAlias + >(arguments) + .WithParsed(o => options = o) + .WithNotParsed(o => errors = o) + ; + + var message = HelpText.AutoBuild(result, + error => error, + ex => ex, + verbsIndex: verbsIndex + ); + + string helpMessage = message.ToString(); + var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + + expected.Length.Should().Be(helps.Count); + int i = 0; + foreach (var expect in expected) + { + helps[i].Trim().Should().Be(expect); + i++; + } + } + + [Verb("move", aliases: new string[] { "mv" })] public class AliasedVerbOption1 { [Option('a', "alpha", Required = true)] public string Option { get; set; } } - [Verb("copy", aliases: new string[] { "cp" })] + [Verb("copy", + isDefault: true, + aliases: new string[] { "cp", "cpy" }, + HelpText = "Copy some stuff" + )] public class AliasedVerbOption2 { [Option('a', "alpha", Required = true)] public string Option { get; set; } } + + [Verb("delete",HelpText = "Delete stuff")] + public class VerbNoAlias + { + [Option('b', "beta", Required = true)] + public string Option { get; set; } + } } } From dd1f9409d99ff4454e5fbd3308b26537e5aed55c Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Mon, 15 Jun 2020 13:07:12 -0400 Subject: [PATCH 165/198] add test case to show default verb still works with alias --- tests/CommandLine.Tests/Unit/Issue6Tests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/CommandLine.Tests/Unit/Issue6Tests.cs b/tests/CommandLine.Tests/Unit/Issue6Tests.cs index 02420b39..79b8d6d9 100644 --- a/tests/CommandLine.Tests/Unit/Issue6Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue6Tests.cs @@ -21,6 +21,7 @@ public class Issue6Tests [InlineData("mv -a bob", typeof(AliasedVerbOption1))] [InlineData("copy -a bob", typeof(AliasedVerbOption2))] [InlineData("cp -a bob", typeof(AliasedVerbOption2))] + [InlineData("-a bob", typeof(AliasedVerbOption2))] public void Parse_option_with_aliased_verbs(string args, Type expectedArgType) { var arguments = args.Split(' '); From 46938c12040bbf8e858d7853544942d89abbcd1d Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Mon, 15 Jun 2020 13:21:56 -0400 Subject: [PATCH 166/198] Add more test cases that don't use a default verb. --- tests/CommandLine.Tests/Unit/Issue6Tests.cs | 119 +++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Issue6Tests.cs b/tests/CommandLine.Tests/Unit/Issue6Tests.cs index 79b8d6d9..224ba41a 100644 --- a/tests/CommandLine.Tests/Unit/Issue6Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue6Tests.cs @@ -16,6 +16,11 @@ namespace CommandLine.Tests.Unit { public class Issue6Tests { + /// + /// Test Verb aliases when one verb is set as a default + /// + /// + /// [Theory] [InlineData("move -a bob", typeof(AliasedVerbOption1))] [InlineData("mv -a bob", typeof(AliasedVerbOption1))] @@ -43,6 +48,42 @@ public void Parse_option_with_aliased_verbs(string args, Type expectedArgType) Assert.Equal(expectedArgType, options.GetType()); } + /// + /// Test verb aliases with no default verb and 1 verb with no aliases + /// + /// + /// + [Theory] + [InlineData("move -a bob", typeof(AliasedVerbOption1))] + [InlineData("mv -a bob", typeof(AliasedVerbOption1))] + [InlineData("delete -b fred", typeof(VerbNoAlias))] + public void Parse_option_with_aliased_verb(string args, Type expectedArgType) + { + var arguments = args.Split(' '); + object options = null; + IEnumerable errors = null; + var result = Parser.Default.ParseArguments(arguments) + .WithParsed(o => options = o) + .WithNotParsed(o => errors = o) + ; + if (errors != null && errors.Any()) + { + foreach (Error e in errors) + { + System.Console.WriteLine(e.ToString()); + } + } + + Assert.NotNull(options); + Assert.Equal(expectedArgType, options.GetType()); + } + + /// + /// Verify auto-help generation. + /// + /// + /// + /// [Theory] [InlineData("--help", true, new string[] { @@ -112,7 +153,81 @@ public void Parse_help_option_for_aliased_verbs(string args, bool verbsIndex, st } } - [Verb("move", aliases: new string[] { "mv" })] + /// + /// Verify auto-help generation with no default verb. + /// + /// + /// + /// + [Theory] + [InlineData("--help", true, new string[] + { + "move, mv", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", + })] + [InlineData("help", true, new string[] + { + "move, mv", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", + })] + [InlineData("move --help", false, new string[] + { + "-a, --alpha Required.", + "--help Display this help screen.", + "--version Display version information.", + })] + [InlineData("mv --help", false, new string[] + { + "-a, --alpha Required.", + "--help Display this help screen.", + "--version Display version information.", + })] + [InlineData("delete --help", false, new string[] + { + "-b, --beta Required.", + "--help Display this help screen.", + "--version Display version information.", + })] + public void Parse_help_option_for_aliased_verbs_no_default(string args, bool verbsIndex, string[] expected) + { + var arguments = args.Split(' '); + object options = null; + IEnumerable errors = null; + // the order of the arguments here drives the order of the commands shown + // in the help message + var result = Parser.Default.ParseArguments< + AliasedVerbOption1, + VerbNoAlias + >(arguments) + .WithParsed(o => options = o) + .WithNotParsed(o => errors = o) + ; + + var message = HelpText.AutoBuild(result, + error => error, + ex => ex, + verbsIndex: verbsIndex + ); + + string helpMessage = message.ToString(); + var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + + expected.Length.Should().Be(helps.Count); + int i = 0; + foreach (var expect in expected) + { + helps[i].Trim().Should().Be(expect); + i++; + } + } + + [Verb("move", + aliases: new string[] { "mv" } + )] public class AliasedVerbOption1 { [Option('a', "alpha", Required = true)] @@ -130,7 +245,7 @@ public class AliasedVerbOption2 public string Option { get; set; } } - [Verb("delete",HelpText = "Delete stuff")] + [Verb("delete", HelpText = "Delete stuff")] public class VerbNoAlias { [Option('b', "beta", Required = true)] From eeb3ee40336b904e1daf269b91c1a6bc0eb8d500 Mon Sep 17 00:00:00 2001 From: kapsiR Date: Mon, 15 Jun 2020 23:45:32 +0200 Subject: [PATCH 167/198] Change FormatWithQuotesIfString to accept any string with spaces (PR feedback) Feedback via issue comment: https://github.com/commandlineparser/commandline/issues/626#issuecomment-644399305 --- src/CommandLine/UnParserExtensions.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 76757138..8e726ca5 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -2,7 +2,6 @@ using System; using System.Collections; -using System.IO; using System.Linq; using System.Text; using CommandLine.Core; @@ -205,14 +204,16 @@ private static string FormatValue(Specification spec, object value) private static object FormatWithQuotesIfString(object value) { - if (value is DateTime || value is DateTimeOffset || value is FileInfo || value is DirectoryInfo) return $"\"{value}\""; + string s = value.ToString(); + if (!string.IsNullOrEmpty(s) && !s.Contains("\"") && s.Contains(" ")) + return $"\"{s}\""; + Func doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; - return (value as string) - .ToMaybe() - .MapValueOrDefault(v => v.Contains(' ') || v.Contains("\"") - ? "\"".JoinTo(doubQt(v), "\"") : v, value); + return s.ToMaybe() + .MapValueOrDefault(v => v.Contains(' ') || v.Contains("\"") + ? "\"".JoinTo(doubQt(v), "\"") : v, value); } private static char SeperatorOrSpace(this Specification spec) From d3ecf50203f29823f48ef7b7cafba950393fb1e3 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Wed, 17 Jun 2020 23:59:32 -0400 Subject: [PATCH 168/198] fix spacing on help messages when verbs have aliases --- src/CommandLine/Text/HelpText.cs | 4 ++-- tests/CommandLine.Tests/Unit/Issue6Tests.cs | 24 ++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 3894bc9d..f5e9a7b9 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -851,8 +851,8 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable var optionSpecs = from verbTuple in Verb.SelectFromTypes(types) select OptionSpecification.NewSwitch( - verbTuple.Item1.Name, - verbTuple.Item1.Aliases.ToDelimitedString(", "), + string.Empty, + verbTuple.Item1.Name.Concat(verbTuple.Item1.Aliases).ToDelimitedString(", "), false, verbTuple.Item1.IsDefault ? "(Default Verb) " + verbTuple.Item1.HelpText : verbTuple.Item1.HelpText, //Default verb string.Empty, diff --git a/tests/CommandLine.Tests/Unit/Issue6Tests.cs b/tests/CommandLine.Tests/Unit/Issue6Tests.cs index 224ba41a..2f6ea3f4 100644 --- a/tests/CommandLine.Tests/Unit/Issue6Tests.cs +++ b/tests/CommandLine.Tests/Unit/Issue6Tests.cs @@ -89,17 +89,17 @@ public void Parse_option_with_aliased_verb(string args, Type expectedArgType) { "copy, cp, cpy (Default Verb) Copy some stuff", "move, mv", - "delete Delete stuff", - "help Display more information on a specific command.", - "version Display version information.", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", })] [InlineData("help", true, new string[] { "copy, cp, cpy (Default Verb) Copy some stuff", "move, mv", - "delete Delete stuff", - "help Display more information on a specific command.", - "version Display version information.", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", })] [InlineData("move --help", false, new string[] { @@ -163,16 +163,16 @@ public void Parse_help_option_for_aliased_verbs(string args, bool verbsIndex, st [InlineData("--help", true, new string[] { "move, mv", - "delete Delete stuff", - "help Display more information on a specific command.", - "version Display version information.", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", })] [InlineData("help", true, new string[] { "move, mv", - "delete Delete stuff", - "help Display more information on a specific command.", - "version Display version information.", + "delete Delete stuff", + "help Display more information on a specific command.", + "version Display version information.", })] [InlineData("move --help", false, new string[] { From cccae2db749c2ebf25125bfd18e05427be0adbcf Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sat, 4 Jul 2020 03:34:06 +0300 Subject: [PATCH 169/198] Add ParseArg method to split commandline --- src/CommandLine/UnParserExtensions.cs | 57 ++++++++++++++++++- .../Unit/UnParserExtensionsTests.cs | 24 ++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 8e726ca5..51811ef9 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Text; using CommandLine.Core; @@ -102,6 +103,18 @@ public static string FormatCommandLine(this Parser parser, T options) return parser.FormatCommandLine(options, config => { }); } + /// + /// Format a command line argument string from a parsed instance in the form of string[]. + /// + /// Type of . + /// Parser instance. + /// A parsed (or manually correctly constructed instance). + /// A string[] with command line arguments. + public static string[] FormatCommandLineArgs(this Parser parser, T options) + { + return parser.FormatCommandLine(options, config => { }).SplitArgs(); + } + /// /// Format a command line argument string from a parsed instance. /// @@ -180,7 +193,19 @@ orderby v.Index return builder .ToString().TrimEnd(' '); } - + /// + /// Format a command line argument string[] from a parsed instance. + /// + /// Type of . + /// Parser instance. + /// A parsed (or manually correctly constructed instance). + /// The lambda used to configure + /// aspects and behaviors of the unparsersing process. + /// A string[] with command line arguments. + public static string[] FormatCommandLineArgs(this Parser parser, T options, Action configuration) + { + return FormatCommandLine(parser, options, configuration).SplitArgs(); + } private static string FormatValue(Specification spec, object value) { var builder = new StringBuilder(); @@ -273,5 +298,35 @@ private static bool IsEmpty(this object value, Specification specification, bool if (value is IEnumerable && !((IEnumerable)value).GetEnumerator().MoveNext()) return true; return false; } + + + #region splitter + /// + /// Returns a string array that contains the substrings in this instance that are delimited by space considering string between double quote. + /// + /// the commandline string + /// don't remove the quote + /// a string array that contains the substrings in this instance + public static string[] SplitArgs(this string command, bool keepQuote = false) + { + if (string.IsNullOrEmpty(command)) + return new string[0]; + + var inQuote = false; + var chars = command.ToCharArray().Select(v => + { + if (v == '"') + inQuote = !inQuote; + return !inQuote && v == ' ' ? '\n' : v; + }).ToArray(); + + return new string(chars).Split('\n') + .Select(x => keepQuote ? x : x.Trim('"')) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToArray(); + } + + #endregion + } } diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index d25c99e1..45850cb1 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -250,6 +250,30 @@ public static void UnParsing_instance_with_nullable_bool(bool? flag, string expe .FormatCommandLine(options) .Should().BeEquivalentTo(expected); } + #region SplitArgs + [Theory] + [InlineData("--shape Circle", new[] { "--shape","Circle" })] + [InlineData(" --shape Circle ", new[] { "--shape", "Circle" })] + [InlineData("-a --shape Circle", new[] {"-a", "--shape", "Circle" })] + [InlineData("-a --shape Circle -- -x1 -x2", new[] { "-a", "--shape", "Circle","--","-x1","-x2" })] + [InlineData("--name \"name with space and quote\" -x1", new[] { "--name", "name with space and quote","-x1" })] + public static void Split_arguments(string command, string[] expectedArgs) + { + var args = command.SplitArgs(); + args.Should().BeEquivalentTo(expectedArgs); + } + [Theory] + [InlineData("--shape Circle", new[] { "--shape", "Circle" })] + [InlineData(" --shape Circle ", new[] { "--shape", "Circle" })] + [InlineData("-a --shape Circle", new[] { "-a", "--shape", "Circle" })] + [InlineData("-a --shape Circle -- -x1 -x2", new[] { "-a", "--shape", "Circle", "--", "-x1", "-x2" })] + [InlineData("--name \"name with space and quote\" -x1", new[] { "--name", "\"name with space and quote\"", "-x1" })] + public static void Split_arguments_with_keep_quote(string command, string[] expectedArgs) + { + var args = command.SplitArgs(true); + args.Should().BeEquivalentTo(expectedArgs); + } + #endregion class Option_Int_Nullable { [Option('v', Default = 1)] From ca7d933450a56ac404060827b7fd6973fff2427f Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Wed, 8 Jul 2020 08:08:16 -0700 Subject: [PATCH 170/198] Add more tests for FormatCommandLineArgs (#662) --- .../Unit/UnParserExtensionsTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 45850cb1..50cb3290 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -24,6 +24,16 @@ public static void UnParsing_instance_returns_command_line(Simple_Options option .Should().BeEquivalentTo(result); } + [Theory] + [MemberData(nameof(UnParseData))] + public static void UnParsing_instance_with_splitArgs_returns_same_option_class(Simple_Options options, string result) + { + new Parser() + .FormatCommandLineArgs(options) + .Should().BeEquivalentTo(result.SplitArgs()); + + } + [Theory] [MemberData(nameof(UnParseFileDirectoryData))] public static void UnParsing_instance_returns_command_line_for_file_directory_paths(Options_With_FileDirectoryInfo options, string result) @@ -33,6 +43,14 @@ public static void UnParsing_instance_returns_command_line_for_file_directory_pa .Should().BeEquivalentTo(result); } + [Theory] + [MemberData(nameof(UnParseFileDirectoryData))] + public static void UnParsing_instance_by_splitArgs_returns_command_line_for_file_directory_paths(Options_With_FileDirectoryInfo options, string result) + { + new Parser() + .FormatCommandLineArgs(options) + .Should().BeEquivalentTo(result.SplitArgs()); + } [Theory] [MemberData(nameof(UnParseDataVerbs))] public static void UnParsing_instance_returns_command_line_for_verbs(Add_Verb verb, string result) @@ -42,6 +60,15 @@ public static void UnParsing_instance_returns_command_line_for_verbs(Add_Verb ve .Should().BeEquivalentTo(result); } + [Theory] + [MemberData(nameof(UnParseDataVerbs))] + public static void UnParsing_instance_to_splitArgs_returns_command_line_for_verbs(Add_Verb verb, string result) + { + new Parser() + .FormatCommandLineArgs(verb) + .Should().BeEquivalentTo(result.SplitArgs()); + } + [Theory] [MemberData(nameof(UnParseDataImmutable))] public static void UnParsing_immutable_instance_returns_command_line(Immutable_Simple_Options options, string result) From 280789690d91c2bc0e36c1eea1194620e129b451 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Thu, 23 Jul 2020 17:24:53 -0700 Subject: [PATCH 171/198] Allow single dash as a value (#669) --- src/CommandLine/Core/Tokenizer.cs | 6 ++++++ .../Unit/Core/TokenizerTests.cs | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index ba6f1ef5..c588a98f 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -135,6 +135,12 @@ private static IEnumerable TokenizeShortName( string value, Func nameLookup) { + //Allow single dash as a value + if (value.Length == 1 && value[0] == '-') + { + yield return Token.Value(value); + yield break; + } if (value.Length > 1 && value[0] == '-' && value[1] != '-') { var text = value.Substring(1); diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index 32d79b4f..f14eea51 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -123,6 +123,26 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() Assert.Equal(ErrorType.BadFormatTokenError, tokens.First().Tag); Assert.Equal(ErrorType.BadFormatTokenError, tokens.Last().Tag); } + + + [Theory] + [InlineData(new[] { "-a", "-" }, 2,"a","-")] + [InlineData(new[] { "--file", "-" }, 2,"file","-")] + [InlineData(new[] { "-f-" }, 2,"f", "-")] + [InlineData(new[] { "--file=-" }, 2, "file", "-")] + [InlineData(new[] { "-a", "--" }, 1, "a", "a")] + public void single_dash_as_a_value(string[] args, int countExcepted,string first,string last) + { + //Arrange + //Act + var result = Tokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound, token => token); + var tokens = result.SucceededWith().ToList(); + //Assert + tokens.Should().NotBeNull(); + tokens.Count.Should().Be(countExcepted); + tokens.First().Text.Should().Be(first); + tokens.Last().Text.Should().Be(last); + } } } From 959d3b2e4d3117c5c9c3ebb6e128da3d60911dbc Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Fri, 24 Jul 2020 07:59:50 +0300 Subject: [PATCH 172/198] prepare for release 2.9 --- CHANGELOG.md | 13 +++++++++++++ appveyor.yml | 2 +- tests/CommandLine.Tests/CommandLine.Tests.csproj | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b794e6..eb001542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.9.0-preview1] - 2020-7-24 + +### Added +- Add multi-instance option support by [@rmunn and @tydunkel, PR# 594](https://github.com/commandlineparser/commandline/pull/594). +- Fix unparsing FileInfo and DirectoryInfo by[@kapsiR, PR# 627](https://github.com/commandlineparser/commandline/pull/627). +- Move Errors and Value up to the abstract class definition, fixes #543 , #165 by [@johnjaylward, PR# 634](https://github.com/commandlineparser/commandline/pull/634). +- Add support for flags enums, fixes #247, #599 and #582 by [@shaosss, PR# 623](https://github.com/commandlineparser/commandline/pull/623). +- Implement verb aliases, fixes #6, #517 by[@johnjaylward, PR# 636](https://github.com/commandlineparser/commandline/pull/636). +- Add a new method FormatCommandLineArgs to unparse commandline to array of string, Fix #375 and #628 . + - Also, add SplitArgs method to split commandline, fix #665 by[@moh-hassan, PR# 662](https://github.com/commandlineparser/commandline/pull/662) and [commit cccae2db](https://github.com/commandlineparser/commandline/commit/cccae2db749c2ebf25125bfd18e05427be0adbcf). +- Allow single dash as a value, fix #300 and #574 by [@moh-hassan, PR# 669](https://github.com/commandlineparser/commandline/pull/669). + + ## [2.8.0] - 2020-5-1 ## [2.8.0-preview4] - 2020-4-30 ## [2.8.0-preview1] - 2020-3-14 diff --git a/appveyor.yml b/appveyor.yml index 1ce67042..91a36832 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.8.0-ci-{build} +version: 2.9.0-ci-{build} image: Visual Studio 2019 diff --git a/tests/CommandLine.Tests/CommandLine.Tests.csproj b/tests/CommandLine.Tests/CommandLine.Tests.csproj index 99832d50..d4dbcab0 100644 --- a/tests/CommandLine.Tests/CommandLine.Tests.csproj +++ b/tests/CommandLine.Tests/CommandLine.Tests.csproj @@ -2,7 +2,7 @@ Library - net461;netcoreapp2.0 + net461;netcoreapp3.1 $(DefineConstants);SKIP_FSHARP ..\..\CommandLine.snk true From e0f68c68893c15908308647328900ea68accbf89 Mon Sep 17 00:00:00 2001 From: Rob Nasby Date: Tue, 31 Mar 2020 09:33:18 -0500 Subject: [PATCH 173/198] Properly assign arguments after a double dash to values, rather than options. --- src/CommandLine/Core/Sequence.cs | 78 ++----------------- src/CommandLine/Core/Token.cs | 28 ++++++- src/CommandLine/Core/Tokenizer.cs | 2 +- ...With_Option_Sequence_And_Value_Sequence.cs | 13 ++++ tests/CommandLine.Tests/Unit/ParserTests.cs | 20 +++++ 5 files changed, 64 insertions(+), 77 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs diff --git a/src/CommandLine/Core/Sequence.cs b/src/CommandLine/Core/Sequence.cs index 10b9c600..d1980728 100644 --- a/src/CommandLine/Core/Sequence.cs +++ b/src/CommandLine/Core/Sequence.cs @@ -33,7 +33,8 @@ public static IEnumerable Partition( break; case SequenceState.TokenFound: - if (token.IsValue()) + //IsValueForced are tokens after -- + if (token.IsValue() && !token.IsValueForced()) { if (sequences.TryGetValue(nameToken, out var sequence)) { @@ -66,85 +67,16 @@ public static IEnumerable Partition( foreach (var kvp in sequences) { + yield return kvp.Key; foreach (var value in kvp.Value) { yield return value; } - } - - //return from tseq in tokens.Pairwise( - //(f, s) => - // f.IsName() && s.IsValue() - // ? typeLookup(f.Text).MapValueOrDefault(info => - // info.TargetType == TargetType.Sequence - // ? new[] { f }.Concat(tokens.OfSequence(f, info)) - // : new Token[] { }, new Token[] { }) - // : new Token[] { }) - // from t in tseq - // select t; + } } - //private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) - //{ - // var state = SequenceState.TokenSearch; - // var count = 0; - // var max = info.MaxItems.GetValueOrDefault(int.MaxValue); - // var values = max != int.MaxValue - // ? new List(max) - // : new List(); - - // foreach (var token in tokens) - // { - // if (count == max) - // { - // break; - // } - - // switch (state) - // { - // case SequenceState.TokenSearch: - // if (token.IsName() && token.Text.Equals(nameToken.Text)) - // { - // state = SequenceState.TokenFound; - // } - // break; - - // case SequenceState.TokenFound: - // if (token.IsValue()) - // { - // state = SequenceState.ValueFound; - // count++; - // values.Add(token); - // } - // else - // { - // // Invalid to provide option without value - // return Enumerable.Empty(); - // } - // break; - - // case SequenceState.ValueFound: - // if (token.IsValue()) - // { - // count++; - // values.Add(token); - // } - // else if (token.IsName() && token.Text.Equals(nameToken.Text)) - // { - // state = SequenceState.TokenFound; - // } - // else - // { - // state = SequenceState.TokenSearch; - // } - // break; - // } - // } - - // return values; - //} - + private enum SequenceState { TokenSearch, diff --git a/src/CommandLine/Core/Token.cs b/src/CommandLine/Core/Token.cs index 2afee98f..c8641bdd 100644 --- a/src/CommandLine/Core/Token.cs +++ b/src/CommandLine/Core/Token.cs @@ -32,6 +32,11 @@ public static Token Value(string text, bool explicitlyAssigned) return new Value(text, explicitlyAssigned); } + public static Token ValueForced(string text) + { + return new Value(text, false, true); + } + public TokenType Tag { get { return tag; } @@ -80,16 +85,23 @@ public bool Equals(Name other) class Value : Token, IEquatable { private readonly bool explicitlyAssigned; + private readonly bool forced; public Value(string text) - : this(text, false) + : this(text, false, false) { } public Value(string text, bool explicitlyAssigned) + : this(text, explicitlyAssigned, false) + { + } + + public Value(string text, bool explicitlyAssigned, bool forced) : base(TokenType.Value, text) { this.explicitlyAssigned = explicitlyAssigned; + this.forced = forced; } public bool ExplicitlyAssigned @@ -97,6 +109,11 @@ public bool ExplicitlyAssigned get { return explicitlyAssigned; } } + public bool Forced + { + get { return forced; } + } + public override bool Equals(object obj) { var other = obj as Value; @@ -120,7 +137,7 @@ public bool Equals(Value other) return false; } - return Tag.Equals(other.Tag) && Text.Equals(other.Text); + return Tag.Equals(other.Tag) && Text.Equals(other.Text) && this.Forced == other.Forced; } } @@ -135,5 +152,10 @@ public static bool IsValue(this Token token) { return token.Tag == TokenType.Value; } + + public static bool IsValueForced(this Token token) + { + return token.IsValue() && ((Value)token).Forced; + } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index c588a98f..e35edc9d 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -50,7 +50,7 @@ public static Result, Error> PreprocessDashDash( if (arguments.Any(arg => arg.EqualsOrdinal("--"))) { var tokenizerResult = tokenizer(arguments.TakeWhile(arg => !arg.EqualsOrdinal("--"))); - var values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--")).Skip(1).Select(Token.Value); + var values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--")).Skip(1).Select(Token.ValueForced); return tokenizerResult.Map(tokens => tokens.Concat(values)); } return tokenizer(arguments); diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs b/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs new file mode 100644 index 00000000..c0ce7cdf --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Option_Sequence_And_Value_Sequence + { + [Option('o', "option-seq")] + public IEnumerable OptionSequence { get; set; } + + [Value(0)] + public IEnumerable ValueSequence { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 46aad457..081121bc 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -132,6 +132,26 @@ public void Parse_options_with_double_dash() // Teardown } + [Fact] + public void Parse_options_with_double_dash_and_option_sequence() + { + var expectedOptions = new Options_With_Option_Sequence_And_Value_Sequence + { + OptionSequence = new[] { "option1", "option2", "option3" }, + ValueSequence = new[] { "value1", "value2", "value3" } + }; + + var sut = new Parser(with => with.EnableDashDash = true); + + // Exercize system + var result = + sut.ParseArguments( + new[] { "--option-seq", "option1", "option2", "option3", "--", "value1", "value2", "value3" }); + + // Verify outcome + ((Parsed)result).Value.Should().BeEquivalentTo(expectedOptions); + } + [Fact] public void Parse_options_with_double_dash_in_verbs_scenario() { From c52e8fbbe8a5119080883be9a63a049dbc57504f Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Aug 2020 13:16:51 +0300 Subject: [PATCH 174/198] Revert "Merge branch 'robnasby-robnasby/double-dash-fix' of Properly assign arguments after a double dash to values (PR #610)" This reverts commit c4eed1052ba80f138a2fb573c5aefca06e5a983b, reversing changes made to 959d3b2e4d3117c5c9c3ebb6e128da3d60911dbc. --- src/CommandLine/Core/Sequence.cs | 78 +++++++++++++++++-- src/CommandLine/Core/Token.cs | 28 +------ src/CommandLine/Core/Tokenizer.cs | 2 +- ...With_Option_Sequence_And_Value_Sequence.cs | 13 ---- tests/CommandLine.Tests/Unit/ParserTests.cs | 20 ----- 5 files changed, 77 insertions(+), 64 deletions(-) delete mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs diff --git a/src/CommandLine/Core/Sequence.cs b/src/CommandLine/Core/Sequence.cs index d1980728..10b9c600 100644 --- a/src/CommandLine/Core/Sequence.cs +++ b/src/CommandLine/Core/Sequence.cs @@ -33,8 +33,7 @@ public static IEnumerable Partition( break; case SequenceState.TokenFound: - //IsValueForced are tokens after -- - if (token.IsValue() && !token.IsValueForced()) + if (token.IsValue()) { if (sequences.TryGetValue(nameToken, out var sequence)) { @@ -67,16 +66,85 @@ public static IEnumerable Partition( foreach (var kvp in sequences) { - yield return kvp.Key; foreach (var value in kvp.Value) { yield return value; } - } + } + + //return from tseq in tokens.Pairwise( + //(f, s) => + // f.IsName() && s.IsValue() + // ? typeLookup(f.Text).MapValueOrDefault(info => + // info.TargetType == TargetType.Sequence + // ? new[] { f }.Concat(tokens.OfSequence(f, info)) + // : new Token[] { }, new Token[] { }) + // : new Token[] { }) + // from t in tseq + // select t; } - + //private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) + //{ + // var state = SequenceState.TokenSearch; + // var count = 0; + // var max = info.MaxItems.GetValueOrDefault(int.MaxValue); + // var values = max != int.MaxValue + // ? new List(max) + // : new List(); + + // foreach (var token in tokens) + // { + // if (count == max) + // { + // break; + // } + + // switch (state) + // { + // case SequenceState.TokenSearch: + // if (token.IsName() && token.Text.Equals(nameToken.Text)) + // { + // state = SequenceState.TokenFound; + // } + // break; + + // case SequenceState.TokenFound: + // if (token.IsValue()) + // { + // state = SequenceState.ValueFound; + // count++; + // values.Add(token); + // } + // else + // { + // // Invalid to provide option without value + // return Enumerable.Empty(); + // } + // break; + + // case SequenceState.ValueFound: + // if (token.IsValue()) + // { + // count++; + // values.Add(token); + // } + // else if (token.IsName() && token.Text.Equals(nameToken.Text)) + // { + // state = SequenceState.TokenFound; + // } + // else + // { + // state = SequenceState.TokenSearch; + // } + // break; + // } + // } + + // return values; + //} + private enum SequenceState { TokenSearch, diff --git a/src/CommandLine/Core/Token.cs b/src/CommandLine/Core/Token.cs index c8641bdd..2afee98f 100644 --- a/src/CommandLine/Core/Token.cs +++ b/src/CommandLine/Core/Token.cs @@ -32,11 +32,6 @@ public static Token Value(string text, bool explicitlyAssigned) return new Value(text, explicitlyAssigned); } - public static Token ValueForced(string text) - { - return new Value(text, false, true); - } - public TokenType Tag { get { return tag; } @@ -85,23 +80,16 @@ public bool Equals(Name other) class Value : Token, IEquatable { private readonly bool explicitlyAssigned; - private readonly bool forced; public Value(string text) - : this(text, false, false) + : this(text, false) { } public Value(string text, bool explicitlyAssigned) - : this(text, explicitlyAssigned, false) - { - } - - public Value(string text, bool explicitlyAssigned, bool forced) : base(TokenType.Value, text) { this.explicitlyAssigned = explicitlyAssigned; - this.forced = forced; } public bool ExplicitlyAssigned @@ -109,11 +97,6 @@ public bool ExplicitlyAssigned get { return explicitlyAssigned; } } - public bool Forced - { - get { return forced; } - } - public override bool Equals(object obj) { var other = obj as Value; @@ -137,7 +120,7 @@ public bool Equals(Value other) return false; } - return Tag.Equals(other.Tag) && Text.Equals(other.Text) && this.Forced == other.Forced; + return Tag.Equals(other.Tag) && Text.Equals(other.Text); } } @@ -152,10 +135,5 @@ public static bool IsValue(this Token token) { return token.Tag == TokenType.Value; } - - public static bool IsValueForced(this Token token) - { - return token.IsValue() && ((Value)token).Forced; - } } -} +} \ No newline at end of file diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index e35edc9d..c588a98f 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -50,7 +50,7 @@ public static Result, Error> PreprocessDashDash( if (arguments.Any(arg => arg.EqualsOrdinal("--"))) { var tokenizerResult = tokenizer(arguments.TakeWhile(arg => !arg.EqualsOrdinal("--"))); - var values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--")).Skip(1).Select(Token.ValueForced); + var values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--")).Skip(1).Select(Token.Value); return tokenizerResult.Map(tokens => tokens.Concat(values)); } return tokenizer(arguments); diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs b/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs deleted file mode 100644 index c0ce7cdf..00000000 --- a/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace CommandLine.Tests.Fakes -{ - public class Options_With_Option_Sequence_And_Value_Sequence - { - [Option('o', "option-seq")] - public IEnumerable OptionSequence { get; set; } - - [Value(0)] - public IEnumerable ValueSequence { get; set; } - } -} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 081121bc..46aad457 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -132,26 +132,6 @@ public void Parse_options_with_double_dash() // Teardown } - [Fact] - public void Parse_options_with_double_dash_and_option_sequence() - { - var expectedOptions = new Options_With_Option_Sequence_And_Value_Sequence - { - OptionSequence = new[] { "option1", "option2", "option3" }, - ValueSequence = new[] { "value1", "value2", "value3" } - }; - - var sut = new Parser(with => with.EnableDashDash = true); - - // Exercize system - var result = - sut.ParseArguments( - new[] { "--option-seq", "option1", "option2", "option3", "--", "value1", "value2", "value3" }); - - // Verify outcome - ((Parsed)result).Value.Should().BeEquivalentTo(expectedOptions); - } - [Fact] public void Parse_options_with_double_dash_in_verbs_scenario() { From 3a2c435b57b1c3f23c175f22c63b1bf4e4806d28 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Aug 2020 13:32:56 +0300 Subject: [PATCH 175/198] Revert "Merge branch 'rmunn-feature/multi-instance-args' by @tydunkel, @rmunn into develop" This reverts commit 6caf72821a110e86531e1e1ee5eeee4cca40bf2b, reversing changes made to 4c2a11570c7864fbb6f42783bcb4c63e834ace8e. --- src/CommandLine/Core/InstanceBuilder.cs | 31 +--- src/CommandLine/Core/InstanceChooser.cs | 67 +++----- src/CommandLine/Core/OptionMapper.cs | 34 ++-- src/CommandLine/Core/Sequence.cs | 153 +++--------------- .../Core/SpecificationPropertyRules.cs | 17 +- src/CommandLine/Core/TokenPartitioner.cs | 5 +- src/CommandLine/Core/TypeConverter.cs | 2 +- src/CommandLine/Parser.cs | 5 +- src/CommandLine/ParserSettings.cs | 10 -- .../Unit/Core/InstanceBuilderTests.cs | 14 +- .../Unit/Core/InstanceChooserTests.cs | 17 +- .../Unit/Core/OptionMapperTests.cs | 62 ------- .../Unit/Core/SequenceTests.cs | 64 +------- .../Core/SpecificationPropertyRulesTests.cs | 58 ------- .../Unit/Core/TypeConverterTests.cs | 10 -- tests/CommandLine.Tests/Unit/ParserTests.cs | 66 +++----- 16 files changed, 89 insertions(+), 526 deletions(-) delete mode 100644 tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index 788ce187..dce377f1 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -23,31 +23,6 @@ public static ParserResult Build( bool autoHelp, bool autoVersion, IEnumerable nonFatalErrors) - { - return Build( - factory, - tokenizer, - arguments, - nameComparer, - ignoreValueCase, - parsingCulture, - autoHelp, - autoVersion, - false, - nonFatalErrors); - } - - public static ParserResult Build( - Maybe> factory, - Func, IEnumerable, Result, Error>> tokenizer, - IEnumerable arguments, - StringComparer nameComparer, - bool ignoreValueCase, - CultureInfo parsingCulture, - bool autoHelp, - bool autoVersion, - bool allowMultiInstance, - IEnumerable nonFatalErrors) { var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T)); @@ -95,7 +70,7 @@ public static ParserResult Build( var valueSpecPropsResult = ValueMapper.MapValues( (from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt), - valuesPartition, + valuesPartition, (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase)); var missingValueErrors = from token in errorsPartition @@ -111,7 +86,7 @@ public static ParserResult Build( //build the instance, determining if the type is mutable or not. T instance; - if (typeInfo.IsMutable() == true) + if(typeInfo.IsMutable() == true) { instance = BuildMutable(factory, specPropsWithValue, setPropertyErrors); } @@ -120,7 +95,7 @@ public static ParserResult Build( instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors); } - var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens, allowMultiInstance)); + var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens)); var allErrors = tokenizerResult.SuccessMessages() diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 7e292bfb..2f76d5ce 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -22,31 +22,6 @@ public static ParserResult Choose( bool autoHelp, bool autoVersion, IEnumerable nonFatalErrors) - { - return Choose( - tokenizer, - types, - arguments, - nameComparer, - ignoreValueCase, - parsingCulture, - autoHelp, - autoVersion, - false, - nonFatalErrors); - } - - public static ParserResult Choose( - Func, IEnumerable, Result, Error>> tokenizer, - IEnumerable types, - IEnumerable arguments, - StringComparer nameComparer, - bool ignoreValueCase, - CultureInfo parsingCulture, - bool autoHelp, - bool autoVersion, - bool allowMultiInstance, - IEnumerable nonFatalErrors) { var verbs = Verb.SelectFromTypes(types); var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); @@ -71,8 +46,10 @@ bool preprocCompare(string command) => arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors); - } + + : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + }; + return arguments.Any() ? choose() @@ -117,32 +94,24 @@ private static ParserResult MatchVerb( CultureInfo parsingCulture, bool autoHelp, bool autoVersion, - bool allowMultiInstance, IEnumerable nonFatalErrors) { - string firstArg = arguments.First(); - var verbUsed = verbs.FirstOrDefault(vt => - nameComparer.Equals(vt.Item1.Name, firstArg) - || vt.Item1.Aliases.Any(alias => nameComparer.Equals(alias, firstArg)) - ); + return verbs.Any(a => nameComparer.Equals(a.Item1.Name, arguments.First())) + ? InstanceBuilder.Build( + Maybe.Just>( + () => + verbs.Single(v => nameComparer.Equals(v.Item1.Name, arguments.First())).Item2.AutoDefault()), + tokenizer, + arguments.Skip(1), + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + nonFatalErrors) + : MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); - if (verbUsed == default) - { - return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); - } - return InstanceBuilder.Build( - Maybe.Just>( - () => verbUsed.Item2.AutoDefault()), - tokenizer, - arguments.Skip(1), - nameComparer, - ignoreValueCase, - parsingCulture, - autoHelp, - autoVersion, - allowMultiInstance, - nonFatalErrors); } private static HelpVerbRequestedError MakeHelpVerbRequestedError( diff --git a/src/CommandLine/Core/OptionMapper.cs b/src/CommandLine/Core/OptionMapper.cs index f01f14ee..18349b40 100644 --- a/src/CommandLine/Core/OptionMapper.cs +++ b/src/CommandLine/Core/OptionMapper.cs @@ -22,32 +22,26 @@ public static Result< .Select( pt => { - var matched = options.Where(s => + var matched = options.FirstOrDefault(s => s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongName, comparer)).ToMaybe(); - - if (matched.IsJust()) - { - var matches = matched.GetValueOrDefault(Enumerable.Empty>>()); - var values = new HashSet(); - foreach (var kvp in matches) - { - foreach (var value in kvp.Value) - { - values.Add(value); - } - } - - return converter(values, pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence) - .Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing())) + return matched.IsJust() + ? ( + from sequence in matched + from converted in + converter( + sequence.Value, + pt.Property.PropertyType, + pt.Specification.TargetType != TargetType.Sequence) + select Tuple.Create( + pt.WithValue(Maybe.Just(converted)), Maybe.Nothing()) + ) .GetValueOrDefault( Tuple.Create>( pt, Maybe.Just( new BadFormatConversionError( - ((OptionSpecification)pt.Specification).FromOptionSpecification())))); - } - - return Tuple.Create(pt, Maybe.Nothing()); + ((OptionSpecification)pt.Specification).FromOptionSpecification())))) + : Tuple.Create(pt, Maybe.Nothing()); } ).Memoize(); return Result.Succeed( diff --git a/src/CommandLine/Core/Sequence.cs b/src/CommandLine/Core/Sequence.cs index 10b9c600..04d1b4ae 100644 --- a/src/CommandLine/Core/Sequence.cs +++ b/src/CommandLine/Core/Sequence.cs @@ -14,141 +14,30 @@ public static IEnumerable Partition( IEnumerable tokens, Func> typeLookup) { - var sequences = new Dictionary>(); - var state = SequenceState.TokenSearch; - Token nameToken = default; - foreach (var token in tokens) - { - switch (state) - { - case SequenceState.TokenSearch: - if (token.IsName()) - { - if (typeLookup(token.Text).MatchJust(out var info) && info.TargetType == TargetType.Sequence) - { - nameToken = token; - state = SequenceState.TokenFound; - } - } - break; - - case SequenceState.TokenFound: - if (token.IsValue()) - { - if (sequences.TryGetValue(nameToken, out var sequence)) - { - sequence.Add(token); - } - else - { - sequences[nameToken] = new List(new[] { token }); - } - } - else if (token.IsName()) - { - if (typeLookup(token.Text).MatchJust(out var info) && info.TargetType == TargetType.Sequence) - { - nameToken = token; - state = SequenceState.TokenFound; - } - else - { - state = SequenceState.TokenSearch; - } - } - else - { - state = SequenceState.TokenSearch; - } - break; - } - } - - foreach (var kvp in sequences) - { - yield return kvp.Key; - foreach (var value in kvp.Value) - { - yield return value; - } - } - - //return from tseq in tokens.Pairwise( - //(f, s) => - // f.IsName() && s.IsValue() - // ? typeLookup(f.Text).MapValueOrDefault(info => - // info.TargetType == TargetType.Sequence - // ? new[] { f }.Concat(tokens.OfSequence(f, info)) - // : new Token[] { }, new Token[] { }) - // : new Token[] { }) - // from t in tseq - // select t; + return from tseq in tokens.Pairwise( + (f, s) => + f.IsName() && s.IsValue() + ? typeLookup(f.Text).MapValueOrDefault(info => + info.TargetType == TargetType.Sequence + ? new[] { f }.Concat(tokens.OfSequence(f, info)) + : new Token[] { }, new Token[] { }) + : new Token[] { }) + from t in tseq + select t; } - //private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) - //{ - // var state = SequenceState.TokenSearch; - // var count = 0; - // var max = info.MaxItems.GetValueOrDefault(int.MaxValue); - // var values = max != int.MaxValue - // ? new List(max) - // : new List(); - - // foreach (var token in tokens) - // { - // if (count == max) - // { - // break; - // } - - // switch (state) - // { - // case SequenceState.TokenSearch: - // if (token.IsName() && token.Text.Equals(nameToken.Text)) - // { - // state = SequenceState.TokenFound; - // } - // break; - - // case SequenceState.TokenFound: - // if (token.IsValue()) - // { - // state = SequenceState.ValueFound; - // count++; - // values.Add(token); - // } - // else - // { - // // Invalid to provide option without value - // return Enumerable.Empty(); - // } - // break; - - // case SequenceState.ValueFound: - // if (token.IsValue()) - // { - // count++; - // values.Add(token); - // } - // else if (token.IsName() && token.Text.Equals(nameToken.Text)) - // { - // state = SequenceState.TokenFound; - // } - // else - // { - // state = SequenceState.TokenSearch; - // } - // break; - // } - // } - - // return values; - //} - - private enum SequenceState + private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) { - TokenSearch, - TokenFound, + var nameIndex = tokens.IndexOf(t => t.Equals(nameToken)); + if (nameIndex >= 0) + { + return info.NextValue.MapValueOrDefault( + _ => info.MaxItems.MapValueOrDefault( + n => tokens.Skip(nameIndex + 1).Take(n), + tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue())), + tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue())); + } + return new Token[] { }; } } } diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 4f8b78a9..5dc1a406 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -13,14 +13,6 @@ static class SpecificationPropertyRules public static IEnumerable, IEnumerable>> Lookup( IEnumerable tokens) - { - return Lookup(tokens, false); - } - - public static IEnumerable, IEnumerable>> - Lookup( - IEnumerable tokens, - bool allowMultiInstance) { return new List, IEnumerable>> { @@ -29,7 +21,7 @@ public static IEnumerable, IEnumerable, IEnumerable> EnforceSingle(IEnumerable tokens, bool allowMultiInstance) + private static Func, IEnumerable> EnforceSingle(IEnumerable tokens) { return specProps => { - if (allowMultiInstance) - { - return Enumerable.Empty(); - } - var specs = from sp in specProps where sp.Specification.IsOption() where sp.Value.IsJust() diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index 608ae0e8..be38a6d0 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -21,11 +21,10 @@ Tuple>>, IEnumerable(Switch.Partition(tokenList, typeLookup), tokenComparer); var scalars = new HashSet(Scalar.Partition(tokenList, typeLookup), tokenComparer); var sequences = new HashSet(Sequence.Partition(tokenList, typeLookup), tokenComparer); - var dedupedSequences = new HashSet(sequences); var nonOptions = tokenList .Where(t => !switches.Contains(t)) .Where(t => !scalars.Contains(t)) - .Where(t => !dedupedSequences.Contains(t)).Memoize(); + .Where(t => !sequences.Contains(t)).Memoize(); var values = nonOptions.Where(v => v.IsValue()).Memoize(); var errors = nonOptions.Except(values, (IEqualityComparer)ReferenceEqualityComparer.Default).Memoize(); @@ -37,4 +36,4 @@ Tuple>>, IEnumerable ChangeType(IEnumerable values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase) { return scalar - ? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase) + ? ChangeTypeScalar(values.Single(), conversionType, conversionCulture, ignoreValueCase) : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); } diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 10c9b4e1..f801c0f7 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -101,7 +101,6 @@ public ParserResult ParseArguments(IEnumerable args) settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, - settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -132,7 +131,6 @@ public ParserResult ParseArguments(Func factory, IEnumerable ar settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, - settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -165,7 +163,6 @@ public ParserResult ParseArguments(IEnumerable args, params Type settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, - settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -231,4 +228,4 @@ private void Dispose(bool disposing) } } } -} +} \ No newline at end of file diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 95a4cd81..07c10c4c 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -25,7 +25,6 @@ public class ParserSettings : IDisposable private CultureInfo parsingCulture; private bool enableDashDash; private int maximumDisplayWidth; - private bool allowMultiInstance; /// /// Initializes a new instance of the class. @@ -175,15 +174,6 @@ public int MaximumDisplayWidth set { maximumDisplayWidth = value; } } - /// - /// Gets or sets a value indicating whether options are allowed to be specified multiple times. - /// - public bool AllowMultiInstance - { - get => allowMultiInstance; - set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, value); - } - internal StringComparer NameComparer { get diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index be09f375..3ef33261 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -19,7 +19,7 @@ namespace CommandLine.Tests.Unit.Core { public class InstanceBuilderTests { - private static ParserResult InvokeBuild(string[] arguments, bool autoHelp = true, bool autoVersion = true, bool multiInstance = false) + private static ParserResult InvokeBuild(string[] arguments, bool autoHelp = true, bool autoVersion = true) where T : new() { return InstanceBuilder.Build( @@ -31,7 +31,6 @@ private static ParserResult InvokeBuild(string[] arguments, bool autoHelp CultureInfo.InvariantCulture, autoHelp, autoVersion, - multiInstance, Enumerable.Empty()); } @@ -1252,17 +1251,6 @@ public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() errors.Should().BeEquivalentTo(expectedResult); } - [Fact] - public void Parse_int_sequence_with_multi_instance() - { - var expected = new[] { 1, 2, 3 }; - var result = InvokeBuild( - new[] { "--int-seq", "1", "2", "--int-seq", "3" }, - multiInstance: true); - - ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); - } - #region custom types diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs index d5cb9a21..c9dae5fb 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs @@ -15,8 +15,7 @@ public class InstanceChooserTests { private static ParserResult InvokeChoose( IEnumerable types, - IEnumerable arguments, - bool multiInstance = false) + IEnumerable arguments) { return InstanceChooser.Choose( (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), @@ -27,7 +26,6 @@ private static ParserResult InvokeChoose( CultureInfo.InvariantCulture, true, true, - multiInstance, Enumerable.Empty()); } @@ -170,18 +168,5 @@ public void Parse_sequence_verb_with_separator_returns_verb_instance(string[] ar expected.Should().BeEquivalentTo(((Parsed)result).Value); // Teardown } - - [Fact] - public void Parse_sequence_verb_with_multi_instance_returns_verb_instance() - { - var expected = new SequenceOptions { LongSequence = new long[] { }, StringSequence = new[] { "s1", "s2" } }; - var result = InvokeChoose( - new[] { typeof(Add_Verb), typeof(Commit_Verb), typeof(Clone_Verb), typeof(SequenceOptions) }, - new[] { "sequence", "-s", "s1", "-s", "s2" }, - true); - - Assert.IsType(((Parsed)result).Value); - expected.Should().BeEquivalentTo(((Parsed)result).Value); - } } } diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 63bf22f3..b2219683 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -49,67 +49,5 @@ public void Map_boolean_switch_creates_boolean_value() // Teardown } - - [Fact] - public void Map_with_multi_instance_scalar() - { - var tokenPartitions = new[] - { - new KeyValuePair>("s", new[] { "string1" }), - new KeyValuePair>("shortandlong", new[] { "string2" }), - new KeyValuePair>("shortandlong", new[] { "string3" }), - new KeyValuePair>("s", new[] { "string4" }), - }; - - var specProps = new[] - { - SpecificationProperty.Create( - new OptionSpecification("s", "shortandlong", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), - typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals(nameof(Simple_Options.ShortAndLong), StringComparison.Ordinal)), - Maybe.Nothing()), - }; - - var result = OptionMapper.MapValues( - specProps.Where(pt => pt.Specification.IsOption()), - tokenPartitions, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), - StringComparer.Ordinal); - - var property = result.SucceededWith().Single(); - Assert.True(property.Specification.IsOption()); - Assert.True(property.Value.MatchJust(out var stringVal)); - Assert.Equal(tokenPartitions.Last().Value.Last(), stringVal); - } - - [Fact] - public void Map_with_multi_instance_sequence() - { - var tokenPartitions = new[] - { - new KeyValuePair>("i", new [] { "1", "2" }), - new KeyValuePair>("i", new [] { "3" }), - new KeyValuePair>("i", new [] { "4", "5" }), - }; - var specProps = new[] - { - SpecificationProperty.Create( - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), - typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals(nameof(Simple_Options.IntSequence), StringComparison.Ordinal)), - Maybe.Nothing()) - }; - - var result = OptionMapper.MapValues( - specProps.Where(pt => pt.Specification.IsOption()), - tokenPartitions, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), - StringComparer.Ordinal); - - var property = result.SucceededWith().Single(); - Assert.True(property.Specification.IsOption()); - Assert.True(property.Value.MatchJust(out var sequence)); - - var expected = tokenPartitions.Aggregate(Enumerable.Empty(), (prev, part) => prev.Concat(part.Value.Select(i => int.Parse(i)))); - Assert.Equal(expected, sequence); - } } } diff --git a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs index 65d3dd3e..b26575b8 100644 --- a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs @@ -49,7 +49,7 @@ public void Partition_sequence_values() } [Fact] - public void Partition_sequence_values_from_two_sequences() + public void Partition_sequence_values_from_two_sequneces() { var expected = new[] { @@ -93,67 +93,5 @@ public void Partition_sequence_values_only() expected.Should().BeEquivalentTo(result); } - - [Fact] - public void Partition_sequence_multi_instance() - { - var expected = new[] - { - Token.Name("seq"), - Token.Value("seqval0"), - Token.Value("seqval1"), - Token.Value("seqval2"), - Token.Value("seqval3"), - Token.Value("seqval4"), - }; - - var result = Sequence.Partition( - new[] - { - Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), - Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1"), - Token.Name("x"), Token.Value("freevalue2"), - Token.Name("seq"), Token.Value("seqval2"), Token.Value("seqval3"), - Token.Name("seq"), Token.Value("seqval4") - }, - name => - new[] { "seq" }.Contains(name) - ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) - : Maybe.Nothing()); - - var actual = result.ToArray(); - Assert.Equal(expected, actual); - } - - [Fact] - public void Partition_sequence_multi_instance_with_max() - { - var expected = new[] - { - Token.Name("seq"), - Token.Value("seqval0"), - Token.Value("seqval1"), - Token.Value("seqval2"), - Token.Value("seqval3"), - Token.Value("seqval4"), - Token.Value("seqval5"), - }; - - var result = Sequence.Partition( - new[] - { - Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), - Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1"), - Token.Name("x"), Token.Value("freevalue2"), - Token.Name("seq"), Token.Value("seqval2"), Token.Value("seqval3"), - Token.Name("seq"), Token.Value("seqval4"), Token.Value("seqval5"), - }, - name => - new[] { "seq" }.Contains(name) - ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Just(3))) - : Maybe.Nothing()); - - Assert.Equal(expected, result); - } } } diff --git a/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs b/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs deleted file mode 100644 index 6e055c55..00000000 --- a/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using CommandLine.Core; -using CommandLine.Tests.Fakes; -using CSharpx; -using System.Collections.Generic; -using Xunit; - -namespace CommandLine.Tests.Unit.Core -{ - - public class SpecificationPropertyRulesTests - { - [Fact] - public void Lookup_allows_multi_instance() - { - var tokens = new[] - { - Token.Name("name"), - Token.Value("value"), - Token.Name("name"), - Token.Value("value2"), - }; - - var specProps = new[] - { - SpecificationProperty.Create( - new OptionSpecification(string.Empty, "name", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), - typeof(SequenceOptions).GetProperty(nameof(SequenceOptions.StringSequence)), - Maybe.Just(new object())), - }; - - var results = specProps.Validate(SpecificationPropertyRules.Lookup(tokens, true)); - Assert.Empty(results); - } - - [Fact] - public void Lookup_fails_with_repeated_options_false_multi_instance() - { - var tokens = new[] - { - Token.Name("name"), - Token.Value("value"), - Token.Name("name"), - Token.Value("value2"), - }; - - var specProps = new[] - { - SpecificationProperty.Create( - new OptionSpecification(string.Empty, "name", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), - typeof(SequenceOptions).GetProperty(nameof(SequenceOptions.StringSequence)), - Maybe.Just(new object())), - }; - - var results = specProps.Validate(SpecificationPropertyRules.Lookup(tokens, false)); - Assert.Contains(results, r => r.GetType() == typeof(RepeatedOptionError)); - } - } -} diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index 31157ede..c62a7836 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -40,16 +40,6 @@ public void ChangeType_scalars(string testValue, Type destinationType, bool expe } } - [Fact] - public void ChangeType_Scalar_LastOneWins() - { - var values = new[] { "100", "200", "300", "400", "500" }; - var result = TypeConverter.ChangeType(values, typeof(int), true, CultureInfo.InvariantCulture, true); - result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully"); - Assert.Equal(500, matchedValue); - - } - public static IEnumerable ChangeType_scalars_source { get diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 46aad457..90147ba6 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -847,58 +847,40 @@ public void Blank_lines_are_inserted_between_verbs() // Teardown } + [Fact] - public void Parse_repeated_options_in_verbs_scenario_with_multi_instance() + public void Parse_default_verb_implicit() { - using (var sut = new Parser(settings => settings.AllowMultiInstance = true)) - { - var longVal1 = 100; - var longVal2 = 200; - var longVal3 = 300; - var stringVal = "shortSeq1"; - - var result = sut.ParseArguments( - new[] { "sequence", "--long-seq", $"{longVal1}", "-s", stringVal, "--long-seq", $"{longVal2};{longVal3}" }, - typeof(Add_Verb), typeof(Commit_Verb), typeof(SequenceOptions)); - - Assert.IsType>(result); - Assert.IsType(((Parsed)result).Value); - result.WithParsed(verb => + var parser = Parser.Default; + parser.ParseArguments(new[] { "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => { - Assert.Equal(new long[] { longVal1, longVal2, longVal3 }, verb.LongSequence); - Assert.Equal(new[] { stringVal }, verb.StringSequence); + Assert.True(args.TestValueOne); }); - } } [Fact] - public void Parse_repeated_options_in_verbs_scenario_without_multi_instance() + public void Parse_default_verb_explicit() { - using (var sut = new Parser(settings => settings.AllowMultiInstance = false)) - { - var longVal1 = 100; - var longVal2 = 200; - var longVal3 = 300; - var stringVal = "shortSeq1"; - - var result = sut.ParseArguments( - new[] { "sequence", "--long-seq", $"{longVal1}", "-s", stringVal, "--long-seq", $"{longVal2};{longVal3}" }, - typeof(Add_Verb), typeof(Commit_Verb), typeof(SequenceOptions)); - - Assert.IsType>(result); - result.WithNotParsed(errors => Assert.All(errors, e => + var parser = Parser.Default; + parser.ParseArguments(new[] { "default1", "-t" }) + .WithNotParsed(errors => throw new InvalidOperationException("Must be parsed.")) + .WithParsed(args => { - if (e is RepeatedOptionError) - { - // expected - } - else - { - throw new Exception($"{nameof(RepeatedOptionError)} expected"); - } - })); - } + Assert.True(args.TestValueOne); + }); + } + + [Fact] + public void Parse_multiple_default_verbs() + { + var parser = Parser.Default; + parser.ParseArguments(new string[] { }) + .WithNotParsed(errors => Assert.IsType(errors.First())) + .WithParsed(args => throw new InvalidOperationException("Should not be parsed.")); } + [Fact] public void Parse_default_verb_with_empty_name() { From ddccb70a827401db093c22ebf83e4cc9acd0065f Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Mon, 3 Aug 2020 13:46:39 +0300 Subject: [PATCH 176/198] Resolve confliction in instanceChooser.cs after revert --- src/CommandLine/Core/InstanceChooser.cs | 38 ++++++++++++++----------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 2f76d5ce..0593a2b2 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -46,10 +46,8 @@ bool preprocCompare(string command) => arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); - }; - + } return arguments.Any() ? choose() @@ -96,22 +94,28 @@ private static ParserResult MatchVerb( bool autoVersion, IEnumerable nonFatalErrors) { + string firstArg = arguments.First(); - return verbs.Any(a => nameComparer.Equals(a.Item1.Name, arguments.First())) - ? InstanceBuilder.Build( - Maybe.Just>( - () => - verbs.Single(v => nameComparer.Equals(v.Item1.Name, arguments.First())).Item2.AutoDefault()), - tokenizer, - arguments.Skip(1), - nameComparer, - ignoreValueCase, - parsingCulture, - autoHelp, - autoVersion, - nonFatalErrors) - : MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + var verbUsed = verbs.FirstOrDefault(vt => + nameComparer.Equals(vt.Item1.Name, firstArg) + || vt.Item1.Aliases.Any(alias => nameComparer.Equals(alias, firstArg)) + ); + if (verbUsed == default) + { + return MatchDefaultVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + } + return InstanceBuilder.Build( + Maybe.Just>( + () => verbUsed.Item2.AutoDefault()), + tokenizer, + arguments.Skip(1), + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + nonFatalErrors); } private static HelpVerbRequestedError MakeHelpVerbRequestedError( From b1a265c729e7cea288ce58bb7d86e3be568ae590 Mon Sep 17 00:00:00 2001 From: Rob Nasby Date: Tue, 31 Mar 2020 09:33:18 -0500 Subject: [PATCH 177/198] Properly assign arguments after a double dash to values, rather than options. --- src/CommandLine/Core/Sequence.cs | 4 +-- src/CommandLine/Core/Token.cs | 28 +++++++++++++++++-- src/CommandLine/Core/Tokenizer.cs | 2 +- ...With_Option_Sequence_And_Value_Sequence.cs | 13 +++++++++ tests/CommandLine.Tests/Unit/ParserTests.cs | 20 +++++++++++++ 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs diff --git a/src/CommandLine/Core/Sequence.cs b/src/CommandLine/Core/Sequence.cs index 04d1b4ae..3a6147b2 100644 --- a/src/CommandLine/Core/Sequence.cs +++ b/src/CommandLine/Core/Sequence.cs @@ -34,8 +34,8 @@ private static IEnumerable OfSequence(this IEnumerable tokens, Tok return info.NextValue.MapValueOrDefault( _ => info.MaxItems.MapValueOrDefault( n => tokens.Skip(nameIndex + 1).Take(n), - tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue())), - tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue())); + tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue() && !v.IsValueForced())), + tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue() && !v.IsValueForced())); } return new Token[] { }; } diff --git a/src/CommandLine/Core/Token.cs b/src/CommandLine/Core/Token.cs index 2afee98f..c8641bdd 100644 --- a/src/CommandLine/Core/Token.cs +++ b/src/CommandLine/Core/Token.cs @@ -32,6 +32,11 @@ public static Token Value(string text, bool explicitlyAssigned) return new Value(text, explicitlyAssigned); } + public static Token ValueForced(string text) + { + return new Value(text, false, true); + } + public TokenType Tag { get { return tag; } @@ -80,16 +85,23 @@ public bool Equals(Name other) class Value : Token, IEquatable { private readonly bool explicitlyAssigned; + private readonly bool forced; public Value(string text) - : this(text, false) + : this(text, false, false) { } public Value(string text, bool explicitlyAssigned) + : this(text, explicitlyAssigned, false) + { + } + + public Value(string text, bool explicitlyAssigned, bool forced) : base(TokenType.Value, text) { this.explicitlyAssigned = explicitlyAssigned; + this.forced = forced; } public bool ExplicitlyAssigned @@ -97,6 +109,11 @@ public bool ExplicitlyAssigned get { return explicitlyAssigned; } } + public bool Forced + { + get { return forced; } + } + public override bool Equals(object obj) { var other = obj as Value; @@ -120,7 +137,7 @@ public bool Equals(Value other) return false; } - return Tag.Equals(other.Tag) && Text.Equals(other.Text); + return Tag.Equals(other.Tag) && Text.Equals(other.Text) && this.Forced == other.Forced; } } @@ -135,5 +152,10 @@ public static bool IsValue(this Token token) { return token.Tag == TokenType.Value; } + + public static bool IsValueForced(this Token token) + { + return token.IsValue() && ((Value)token).Forced; + } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index c588a98f..e35edc9d 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -50,7 +50,7 @@ public static Result, Error> PreprocessDashDash( if (arguments.Any(arg => arg.EqualsOrdinal("--"))) { var tokenizerResult = tokenizer(arguments.TakeWhile(arg => !arg.EqualsOrdinal("--"))); - var values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--")).Skip(1).Select(Token.Value); + var values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--")).Skip(1).Select(Token.ValueForced); return tokenizerResult.Map(tokens => tokens.Concat(values)); } return tokenizer(arguments); diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs b/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs new file mode 100644 index 00000000..c0ce7cdf --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Option_Sequence_And_Value_Sequence.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Option_Sequence_And_Value_Sequence + { + [Option('o', "option-seq")] + public IEnumerable OptionSequence { get; set; } + + [Value(0)] + public IEnumerable ValueSequence { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 90147ba6..bc6d77a8 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -132,6 +132,26 @@ public void Parse_options_with_double_dash() // Teardown } + [Fact] + public void Parse_options_with_double_dash_and_option_sequence() + { + var expectedOptions = new Options_With_Option_Sequence_And_Value_Sequence + { + OptionSequence = new[] { "option1", "option2", "option3" }, + ValueSequence = new[] { "value1", "value2", "value3" } + }; + + var sut = new Parser(with => with.EnableDashDash = true); + + // Exercize system + var result = + sut.ParseArguments( + new[] { "--option-seq", "option1", "option2", "option3", "--", "value1", "value2", "value3" }); + + // Verify outcome + ((Parsed)result).Value.Should().BeEquivalentTo(expectedOptions); + } + [Fact] public void Parse_options_with_double_dash_in_verbs_scenario() { From d443a51aeb3a418425e970542b3b96e9da5f62e2 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Thu, 6 Aug 2020 12:59:37 +0300 Subject: [PATCH 178/198] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb001542..eea8ad12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. CommandLineParser project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.9.0-preview2] + +### Added +- Properly assign arguments after a double dash to values, fix #605 by [@robnasby, PR# 610](https://github.com/commandlineparser/commandline/pull/610). + +### Changed +- Drop "Add multi-instance option support". + + ## [2.9.0-preview1] - 2020-7-24 ### Added From 690136eaed700cb873d1f750076da0360e11dd1e Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 18 Aug 2020 04:23:20 +0700 Subject: [PATCH 179/198] Add multi-instance option support (#678) * Add multi-instance option support Fixes #357 * Fix Appveyor build It seems Appveyor's C# compiler is old enough that it doesn't know how to do tuple deconstruction, so we'll avoid doing that. It makes TokenPartitioner uglier, but no getting around that if we're dealing with an old C# compiler. --- src/CommandLine/Core/InstanceBuilder.cs | 26 +++- src/CommandLine/Core/InstanceChooser.cs | 31 +++- src/CommandLine/Core/OptionMapper.cs | 33 ++-- src/CommandLine/Core/PartitionExtensions.cs | 25 +++ src/CommandLine/Core/Scalar.cs | 27 ---- src/CommandLine/Core/Sequence.cs | 43 ------ .../Core/SpecificationPropertyRules.cs | 17 +- src/CommandLine/Core/Switch.cs | 21 --- src/CommandLine/Core/TokenPartitioner.cs | 145 ++++++++++++++++-- src/CommandLine/Core/TypeConverter.cs | 2 +- src/CommandLine/Parser.cs | 5 +- src/CommandLine/ParserSettings.cs | 10 ++ ...s_With_Value_Sequence_And_Normal_Option.cs | 28 ++++ .../Unit/Core/InstanceBuilderTests.cs | 14 +- .../Unit/Core/InstanceChooserTests.cs | 17 +- .../Unit/Core/OptionMapperTests.cs | 62 ++++++++ .../Unit/Core/ScalarTests.cs | 6 +- .../Unit/Core/SequenceTests.cs | 76 ++++++++- .../Core/SpecificationPropertyRulesTests.cs | 58 +++++++ .../Unit/Core/SwitchTests.cs | 6 +- .../Unit/Core/TypeConverterTests.cs | 10 ++ tests/CommandLine.Tests/Unit/ParserTests.cs | 68 ++++++++ 22 files changed, 598 insertions(+), 132 deletions(-) create mode 100644 src/CommandLine/Core/PartitionExtensions.cs delete mode 100644 src/CommandLine/Core/Scalar.cs delete mode 100644 src/CommandLine/Core/Sequence.cs delete mode 100644 src/CommandLine/Core/Switch.cs create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Value_Sequence_And_Normal_Option.cs create mode 100644 tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index dce377f1..ffd6250b 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -24,6 +24,30 @@ public static ParserResult Build( bool autoVersion, IEnumerable nonFatalErrors) { + return Build( + factory, + tokenizer, + arguments, + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + false, + nonFatalErrors); + } + + public static ParserResult Build( + Maybe> factory, + Func, IEnumerable, Result, Error>> tokenizer, + IEnumerable arguments, + StringComparer nameComparer, + bool ignoreValueCase, + CultureInfo parsingCulture, + bool autoHelp, + bool autoVersion, + bool allowMultiInstance, + IEnumerable nonFatalErrors) { var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T)); var specProps = typeInfo.GetSpecifications(pi => SpecificationProperty.Create( @@ -95,7 +119,7 @@ public static ParserResult Build( instance = BuildImmutable(typeInfo, factory, specProps, specPropsWithValue, setPropertyErrors); } - var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens)); + var validationErrors = specPropsWithValue.Validate(SpecificationPropertyRules.Lookup(tokens, allowMultiInstance)); var allErrors = tokenizerResult.SuccessMessages() diff --git a/src/CommandLine/Core/InstanceChooser.cs b/src/CommandLine/Core/InstanceChooser.cs index 0593a2b2..72307bf2 100644 --- a/src/CommandLine/Core/InstanceChooser.cs +++ b/src/CommandLine/Core/InstanceChooser.cs @@ -22,6 +22,31 @@ public static ParserResult Choose( bool autoHelp, bool autoVersion, IEnumerable nonFatalErrors) + { + return Choose( + tokenizer, + types, + arguments, + nameComparer, + ignoreValueCase, + parsingCulture, + autoHelp, + autoVersion, + false, + nonFatalErrors); + } + + public static ParserResult Choose( + Func, IEnumerable, Result, Error>> tokenizer, + IEnumerable types, + IEnumerable arguments, + StringComparer nameComparer, + bool ignoreValueCase, + CultureInfo parsingCulture, + bool autoHelp, + bool autoVersion, + bool allowMultiInstance, + IEnumerable nonFatalErrors) { var verbs = Verb.SelectFromTypes(types); var defaultVerbs = verbs.Where(t => t.Item1.IsDefault); @@ -46,7 +71,7 @@ bool preprocCompare(string command) => arguments.Skip(1).FirstOrDefault() ?? string.Empty, nameComparer)) : (autoVersion && preprocCompare("version")) ? MakeNotParsed(types, new VersionRequestedError()) - : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, nonFatalErrors); + : MatchVerb(tokenizer, verbs, defaultVerb, arguments, nameComparer, ignoreValueCase, parsingCulture, autoHelp, autoVersion, allowMultiInstance, nonFatalErrors); } return arguments.Any() @@ -92,6 +117,7 @@ private static ParserResult MatchVerb( CultureInfo parsingCulture, bool autoHelp, bool autoVersion, + bool allowMultiInstance, IEnumerable nonFatalErrors) { string firstArg = arguments.First(); @@ -114,7 +140,8 @@ private static ParserResult MatchVerb( ignoreValueCase, parsingCulture, autoHelp, - autoVersion, + autoVersion, + allowMultiInstance, nonFatalErrors); } diff --git a/src/CommandLine/Core/OptionMapper.cs b/src/CommandLine/Core/OptionMapper.cs index 18349b40..d222c100 100644 --- a/src/CommandLine/Core/OptionMapper.cs +++ b/src/CommandLine/Core/OptionMapper.cs @@ -22,26 +22,31 @@ public static Result< .Select( pt => { - var matched = options.FirstOrDefault(s => + var matched = options.Where(s => s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongName, comparer)).ToMaybe(); - return matched.IsJust() - ? ( - from sequence in matched - from converted in - converter( - sequence.Value, - pt.Property.PropertyType, - pt.Specification.TargetType != TargetType.Sequence) - select Tuple.Create( - pt.WithValue(Maybe.Just(converted)), Maybe.Nothing()) - ) + if (matched.IsJust()) + { + var matches = matched.GetValueOrDefault(Enumerable.Empty>>()); + var values = new HashSet(); + foreach (var kvp in matches) + { + foreach (var value in kvp.Value) + { + values.Add(value); + } + } + + return converter(values, pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence) + .Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing())) .GetValueOrDefault( Tuple.Create>( pt, Maybe.Just( new BadFormatConversionError( - ((OptionSpecification)pt.Specification).FromOptionSpecification())))) - : Tuple.Create(pt, Maybe.Nothing()); + ((OptionSpecification)pt.Specification).FromOptionSpecification())))); + } + + return Tuple.Create(pt, Maybe.Nothing()); } ).Memoize(); return Result.Succeed( diff --git a/src/CommandLine/Core/PartitionExtensions.cs b/src/CommandLine/Core/PartitionExtensions.cs new file mode 100644 index 00000000..47cc397e --- /dev/null +++ b/src/CommandLine/Core/PartitionExtensions.cs @@ -0,0 +1,25 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using CSharpx; + +namespace CommandLine.Core +{ + static class PartitionExtensions + { + public static Tuple,IEnumerable> PartitionByPredicate( + this IEnumerable items, + Func pred) + { + List yes = new List(); + List no = new List(); + foreach (T item in items) { + List list = pred(item) ? yes : no; + list.Add(item); + } + return Tuple.Create,IEnumerable>(yes, no); + } + } +} diff --git a/src/CommandLine/Core/Scalar.cs b/src/CommandLine/Core/Scalar.cs deleted file mode 100644 index 215ca2d2..00000000 --- a/src/CommandLine/Core/Scalar.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using CommandLine.Infrastructure; -using CSharpx; - -namespace CommandLine.Core -{ - static class Scalar - { - public static IEnumerable Partition( - IEnumerable tokens, - Func> typeLookup) - { - return from tseq in tokens.Pairwise( - (f, s) => - f.IsName() && s.IsValue() - ? typeLookup(f.Text).MapValueOrDefault(info => - info.TargetType == TargetType.Scalar ? new[] { f, s } : new Token[] { }, new Token[] { }) - : new Token[] { }) - from t in tseq - select t; - } - } -} diff --git a/src/CommandLine/Core/Sequence.cs b/src/CommandLine/Core/Sequence.cs deleted file mode 100644 index 3a6147b2..00000000 --- a/src/CommandLine/Core/Sequence.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using CommandLine.Infrastructure; -using CSharpx; - -namespace CommandLine.Core -{ - static class Sequence - { - public static IEnumerable Partition( - IEnumerable tokens, - Func> typeLookup) - { - return from tseq in tokens.Pairwise( - (f, s) => - f.IsName() && s.IsValue() - ? typeLookup(f.Text).MapValueOrDefault(info => - info.TargetType == TargetType.Sequence - ? new[] { f }.Concat(tokens.OfSequence(f, info)) - : new Token[] { }, new Token[] { }) - : new Token[] { }) - from t in tseq - select t; - } - - private static IEnumerable OfSequence(this IEnumerable tokens, Token nameToken, TypeDescriptor info) - { - var nameIndex = tokens.IndexOf(t => t.Equals(nameToken)); - if (nameIndex >= 0) - { - return info.NextValue.MapValueOrDefault( - _ => info.MaxItems.MapValueOrDefault( - n => tokens.Skip(nameIndex + 1).Take(n), - tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue() && !v.IsValueForced())), - tokens.Skip(nameIndex + 1).TakeWhile(v => v.IsValue() && !v.IsValueForced())); - } - return new Token[] { }; - } - } -} diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 5dc1a406..4f8b78a9 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -13,6 +13,14 @@ static class SpecificationPropertyRules public static IEnumerable, IEnumerable>> Lookup( IEnumerable tokens) + { + return Lookup(tokens, false); + } + + public static IEnumerable, IEnumerable>> + Lookup( + IEnumerable tokens, + bool allowMultiInstance) { return new List, IEnumerable>> { @@ -21,7 +29,7 @@ public static IEnumerable, IEnumerable, IEnumerable> EnforceSingle(IEnumerable tokens) + private static Func, IEnumerable> EnforceSingle(IEnumerable tokens, bool allowMultiInstance) { return specProps => { + if (allowMultiInstance) + { + return Enumerable.Empty(); + } + var specs = from sp in specProps where sp.Specification.IsOption() where sp.Value.IsJust() diff --git a/src/CommandLine/Core/Switch.cs b/src/CommandLine/Core/Switch.cs deleted file mode 100644 index 96e62443..00000000 --- a/src/CommandLine/Core/Switch.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using CSharpx; - -namespace CommandLine.Core -{ - static class Switch - { - public static IEnumerable Partition( - IEnumerable tokens, - Func> typeLookup) - { - return from t in tokens - where typeLookup(t.Text).MapValueOrDefault(info => t.IsName() && info.TargetType == TargetType.Switch, false) - select t; - } - } -} diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index be38a6d0..a735a762 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -18,15 +18,14 @@ Tuple>>, IEnumerable tokenComparer = ReferenceEqualityComparer.Default; var tokenList = tokens.Memoize(); - var switches = new HashSet(Switch.Partition(tokenList, typeLookup), tokenComparer); - var scalars = new HashSet(Scalar.Partition(tokenList, typeLookup), tokenComparer); - var sequences = new HashSet(Sequence.Partition(tokenList, typeLookup), tokenComparer); - var nonOptions = tokenList - .Where(t => !switches.Contains(t)) - .Where(t => !scalars.Contains(t)) - .Where(t => !sequences.Contains(t)).Memoize(); - var values = nonOptions.Where(v => v.IsValue()).Memoize(); - var errors = nonOptions.Except(values, (IEqualityComparer)ReferenceEqualityComparer.Default).Memoize(); + var partitioned = PartitionTokensByType(tokenList, typeLookup); + var switches = partitioned.Item1; + var scalars = partitioned.Item2; + var sequences = partitioned.Item3; + var nonOptions = partitioned.Item4; + var valuesAndErrors = nonOptions.PartitionByPredicate(v => v.IsValue()); + var values = valuesAndErrors.Item1; + var errors = valuesAndErrors.Item2; return Tuple.Create( KeyValuePairHelper.ForSwitch(switches) @@ -35,5 +34,131 @@ Tuple>>, IEnumerable t.Text), errors); } + + public static Tuple, IEnumerable, IEnumerable, IEnumerable> PartitionTokensByType( + IEnumerable tokens, + Func> typeLookup) + { + var switchTokens = new List(); + var scalarTokens = new List(); + var sequenceTokens = new List(); + var nonOptionTokens = new List(); + var sequences = new Dictionary>(); + var count = new Dictionary(); + var max = new Dictionary>(); + var state = SequenceState.TokenSearch; + Token nameToken = null; + foreach (var token in tokens) + { + if (token.IsValueForced()) + { + nonOptionTokens.Add(token); + } + else if (token.IsName()) + { + if (typeLookup(token.Text).MatchJust(out var info)) + { + switch (info.TargetType) + { + case TargetType.Switch: + nameToken = null; + switchTokens.Add(token); + state = SequenceState.TokenSearch; + break; + case TargetType.Scalar: + nameToken = token; + scalarTokens.Add(nameToken); + state = SequenceState.ScalarTokenFound; + break; + case TargetType.Sequence: + nameToken = token; + if (! sequences.ContainsKey(nameToken)) + { + sequences[nameToken] = new List(); + count[nameToken] = 0; + max[nameToken] = info.MaxItems; + } + state = SequenceState.SequenceTokenFound; + break; + } + } + else + { + nameToken = null; + nonOptionTokens.Add(token); + state = SequenceState.TokenSearch; + } + } + else + { + switch (state) + { + case SequenceState.TokenSearch: + case SequenceState.ScalarTokenFound when nameToken == null: + case SequenceState.SequenceTokenFound when nameToken == null: + // if (nameToken == null) Console.WriteLine($" (because there was no nameToken)"); + nameToken = null; + nonOptionTokens.Add(token); + state = SequenceState.TokenSearch; + break; + + case SequenceState.ScalarTokenFound: + nameToken = null; + scalarTokens.Add(token); + state = SequenceState.TokenSearch; + break; + + case SequenceState.SequenceTokenFound: + if (sequences.TryGetValue(nameToken, out var sequence)) { + // if (max[nameToken].MatchJust(out int m) && count[nameToken] >= m) + // { + // // This sequence is completed, so this and any further values are non-option values + // nameToken = null; + // nonOptionTokens.Add(token); + // state = SequenceState.TokenSearch; + // } + // else + { + sequence.Add(token); + count[nameToken]++; + } + } + else + { + Console.WriteLine("***BUG!!!***"); + throw new NullReferenceException($"Sequence for name {nameToken} doesn't exist, and it should"); + // sequences[nameToken] = new List(new[] { token }); + } + break; + } + } + } + + foreach (var kvp in sequences) + { + if (kvp.Value.Empty()) { + nonOptionTokens.Add(kvp.Key); + } + else + { + sequenceTokens.Add(kvp.Key); + sequenceTokens.AddRange(kvp.Value); + } + } + return Tuple.Create( + (IEnumerable)switchTokens, + (IEnumerable)scalarTokens, + (IEnumerable)sequenceTokens, + (IEnumerable)nonOptionTokens + ); + } + + private enum SequenceState + { + TokenSearch, + SequenceTokenFound, + ScalarTokenFound, + } + } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index 8f193c46..ee69373e 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -16,7 +16,7 @@ static class TypeConverter public static Maybe ChangeType(IEnumerable values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase) { return scalar - ? ChangeTypeScalar(values.Single(), conversionType, conversionCulture, ignoreValueCase) + ? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase) : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); } diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index f801c0f7..10c9b4e1 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -101,6 +101,7 @@ public ParserResult ParseArguments(IEnumerable args) settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, + settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -131,6 +132,7 @@ public ParserResult ParseArguments(Func factory, IEnumerable ar settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, + settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -163,6 +165,7 @@ public ParserResult ParseArguments(IEnumerable args, params Type settings.ParsingCulture, settings.AutoHelp, settings.AutoVersion, + settings.AllowMultiInstance, HandleUnknownArguments(settings.IgnoreUnknownArguments)), settings); } @@ -228,4 +231,4 @@ private void Dispose(bool disposing) } } } -} \ No newline at end of file +} diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 07c10c4c..95a4cd81 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -25,6 +25,7 @@ public class ParserSettings : IDisposable private CultureInfo parsingCulture; private bool enableDashDash; private int maximumDisplayWidth; + private bool allowMultiInstance; /// /// Initializes a new instance of the class. @@ -174,6 +175,15 @@ public int MaximumDisplayWidth set { maximumDisplayWidth = value; } } + /// + /// Gets or sets a value indicating whether options are allowed to be specified multiple times. + /// + public bool AllowMultiInstance + { + get => allowMultiInstance; + set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, value); + } + internal StringComparer NameComparer { get diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Value_Sequence_And_Normal_Option.cs b/tests/CommandLine.Tests/Fakes/Options_With_Value_Sequence_And_Normal_Option.cs new file mode 100644 index 00000000..e8e7bf47 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Value_Sequence_And_Normal_Option.cs @@ -0,0 +1,28 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Value_Sequence_And_Normal_Option + { + [Option('c', "compress", + HelpText = "Compress Match Pattern, Pipe Separated (|) ", + Separator = '|', + Default = new[] + { + "*.txt", "*.log", "*.ini" + })] + public IEnumerable Compress { get; set; } + + [Value(0, + HelpText = "Input Directories.", + Required = true)] + public IEnumerable InputDirs { get; set; } + + + [Option('n', "name", + HelpText = "Metadata Name.", + Default = "WILDCARD")] + public string Name { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 3ef33261..be09f375 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -19,7 +19,7 @@ namespace CommandLine.Tests.Unit.Core { public class InstanceBuilderTests { - private static ParserResult InvokeBuild(string[] arguments, bool autoHelp = true, bool autoVersion = true) + private static ParserResult InvokeBuild(string[] arguments, bool autoHelp = true, bool autoVersion = true, bool multiInstance = false) where T : new() { return InstanceBuilder.Build( @@ -31,6 +31,7 @@ private static ParserResult InvokeBuild(string[] arguments, bool autoHelp CultureInfo.InvariantCulture, autoHelp, autoVersion, + multiInstance, Enumerable.Empty()); } @@ -1251,6 +1252,17 @@ public void Options_In_Group_Do_Not_Allow_Mutually_Exclusive_Set() errors.Should().BeEquivalentTo(expectedResult); } + [Fact] + public void Parse_int_sequence_with_multi_instance() + { + var expected = new[] { 1, 2, 3 }; + var result = InvokeBuild( + new[] { "--int-seq", "1", "2", "--int-seq", "3" }, + multiInstance: true); + + ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); + } + #region custom types diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs index c9dae5fb..d5cb9a21 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceChooserTests.cs @@ -15,7 +15,8 @@ public class InstanceChooserTests { private static ParserResult InvokeChoose( IEnumerable types, - IEnumerable arguments) + IEnumerable arguments, + bool multiInstance = false) { return InstanceChooser.Choose( (args, optionSpecs) => Tokenizer.ConfigureTokenizer(StringComparer.Ordinal, false, false)(args, optionSpecs), @@ -26,6 +27,7 @@ private static ParserResult InvokeChoose( CultureInfo.InvariantCulture, true, true, + multiInstance, Enumerable.Empty()); } @@ -168,5 +170,18 @@ public void Parse_sequence_verb_with_separator_returns_verb_instance(string[] ar expected.Should().BeEquivalentTo(((Parsed)result).Value); // Teardown } + + [Fact] + public void Parse_sequence_verb_with_multi_instance_returns_verb_instance() + { + var expected = new SequenceOptions { LongSequence = new long[] { }, StringSequence = new[] { "s1", "s2" } }; + var result = InvokeChoose( + new[] { typeof(Add_Verb), typeof(Commit_Verb), typeof(Clone_Verb), typeof(SequenceOptions) }, + new[] { "sequence", "-s", "s1", "-s", "s2" }, + true); + + Assert.IsType(((Parsed)result).Value); + expected.Should().BeEquivalentTo(((Parsed)result).Value); + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index b2219683..63bf22f3 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -49,5 +49,67 @@ public void Map_boolean_switch_creates_boolean_value() // Teardown } + + [Fact] + public void Map_with_multi_instance_scalar() + { + var tokenPartitions = new[] + { + new KeyValuePair>("s", new[] { "string1" }), + new KeyValuePair>("shortandlong", new[] { "string2" }), + new KeyValuePair>("shortandlong", new[] { "string3" }), + new KeyValuePair>("s", new[] { "string4" }), + }; + + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification("s", "shortandlong", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals(nameof(Simple_Options.ShortAndLong), StringComparison.Ordinal)), + Maybe.Nothing()), + }; + + var result = OptionMapper.MapValues( + specProps.Where(pt => pt.Specification.IsOption()), + tokenPartitions, + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + StringComparer.Ordinal); + + var property = result.SucceededWith().Single(); + Assert.True(property.Specification.IsOption()); + Assert.True(property.Value.MatchJust(out var stringVal)); + Assert.Equal(tokenPartitions.Last().Value.Last(), stringVal); + } + + [Fact] + public void Map_with_multi_instance_sequence() + { + var tokenPartitions = new[] + { + new KeyValuePair>("i", new [] { "1", "2" }), + new KeyValuePair>("i", new [] { "3" }), + new KeyValuePair>("i", new [] { "4", "5" }), + }; + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), + typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals(nameof(Simple_Options.IntSequence), StringComparison.Ordinal)), + Maybe.Nothing()) + }; + + var result = OptionMapper.MapValues( + specProps.Where(pt => pt.Specification.IsOption()), + tokenPartitions, + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + StringComparer.Ordinal); + + var property = result.SucceededWith().Single(); + Assert.True(property.Specification.IsOption()); + Assert.True(property.Value.MatchJust(out var sequence)); + + var expected = tokenPartitions.Aggregate(Enumerable.Empty(), (prev, part) => prev.Concat(part.Value.Select(i => int.Parse(i)))); + Assert.Equal(expected, sequence); + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs b/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs index 9b1028f0..2d08bbcb 100644 --- a/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/ScalarTests.cs @@ -15,12 +15,13 @@ public void Partition_scalar_values_from_empty_token_sequence() { var expected = new Token[] { }; - var result = Scalar.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new Token[] { }, name => new[] { "str", "int" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Scalar, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item2; // Switch, *Scalar*, Sequence, NonOption expected.Should().BeEquivalentTo(result); } @@ -30,7 +31,7 @@ public void Partition_scalar_values() { var expected = new [] { Token.Name("str"), Token.Value("strvalue") }; - var result = Scalar.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new [] { Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), @@ -40,6 +41,7 @@ public void Partition_scalar_values() new[] { "str", "int" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Scalar, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item2; // Switch, *Scalar*, Sequence, NonOption expected.Should().BeEquivalentTo(result); } diff --git a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs index b26575b8..cd17f592 100644 --- a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs @@ -15,12 +15,13 @@ public void Partition_sequence_values_from_empty_token_sequence() { var expected = new Token[] { }; - var result = Sequence.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new Token[] { }, name => new[] { "seq" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption expected.Should().AllBeEquivalentTo(result); } @@ -33,7 +34,7 @@ public void Partition_sequence_values() Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1") }; - var result = Sequence.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new[] { Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), @@ -44,6 +45,7 @@ public void Partition_sequence_values() new[] { "seq" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption expected.Should().BeEquivalentTo(result); } @@ -57,7 +59,7 @@ public void Partition_sequence_values_from_two_sequneces() Token.Name("seqb"), Token.Value("seqbval0") }; - var result = Sequence.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new[] { Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), @@ -69,6 +71,7 @@ public void Partition_sequence_values_from_two_sequneces() new[] { "seq", "seqb" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption expected.Should().BeEquivalentTo(result); } @@ -81,7 +84,7 @@ public void Partition_sequence_values_only() Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1") }; - var result = Sequence.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new[] { Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1") @@ -90,8 +93,73 @@ public void Partition_sequence_values_only() new[] { "seq" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption expected.Should().BeEquivalentTo(result); } + + [Fact] + public void Partition_sequence_multi_instance() + { + var expected = new[] + { + Token.Name("seq"), + Token.Value("seqval0"), + Token.Value("seqval1"), + Token.Value("seqval2"), + Token.Value("seqval3"), + Token.Value("seqval4"), + }; + + var tokens = TokenPartitioner.PartitionTokensByType( + new[] + { + Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), + Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1"), + Token.Name("x"), Token.Value("freevalue2"), + Token.Name("seq"), Token.Value("seqval2"), Token.Value("seqval3"), + Token.Name("seq"), Token.Value("seqval4") + }, + name => + new[] { "seq" }.Contains(name) + ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Nothing())) + : Maybe.Nothing()); + var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption + + var actual = result.ToArray(); + Assert.Equal(expected, actual); + } + + [Fact] + public void Partition_sequence_multi_instance_with_max() + { + var expected = new[] + { + Token.Name("seq"), + Token.Value("seqval0"), + Token.Value("seqval1"), + Token.Value("seqval2"), + Token.Value("seqval3"), + Token.Value("seqval4"), + Token.Value("seqval5"), + }; + + var tokens = TokenPartitioner.PartitionTokensByType( + new[] + { + Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), + Token.Name("seq"), Token.Value("seqval0"), Token.Value("seqval1"), + Token.Name("x"), Token.Value("freevalue2"), + Token.Name("seq"), Token.Value("seqval2"), Token.Value("seqval3"), + Token.Name("seq"), Token.Value("seqval4"), Token.Value("seqval5"), + }, + name => + new[] { "seq" }.Contains(name) + ? Maybe.Just(TypeDescriptor.Create(TargetType.Sequence, Maybe.Just(3))) + : Maybe.Nothing()); + var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption + + Assert.Equal(expected, result); + } } } diff --git a/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs b/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs new file mode 100644 index 00000000..6e055c55 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Core/SpecificationPropertyRulesTests.cs @@ -0,0 +1,58 @@ +using CommandLine.Core; +using CommandLine.Tests.Fakes; +using CSharpx; +using System.Collections.Generic; +using Xunit; + +namespace CommandLine.Tests.Unit.Core +{ + + public class SpecificationPropertyRulesTests + { + [Fact] + public void Lookup_allows_multi_instance() + { + var tokens = new[] + { + Token.Name("name"), + Token.Value("value"), + Token.Name("name"), + Token.Value("value2"), + }; + + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification(string.Empty, "name", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), + typeof(SequenceOptions).GetProperty(nameof(SequenceOptions.StringSequence)), + Maybe.Just(new object())), + }; + + var results = specProps.Validate(SpecificationPropertyRules.Lookup(tokens, true)); + Assert.Empty(results); + } + + [Fact] + public void Lookup_fails_with_repeated_options_false_multi_instance() + { + var tokens = new[] + { + Token.Name("name"), + Token.Value("value"), + Token.Name("name"), + Token.Value("value2"), + }; + + var specProps = new[] + { + SpecificationProperty.Create( + new OptionSpecification(string.Empty, "name", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty), + typeof(SequenceOptions).GetProperty(nameof(SequenceOptions.StringSequence)), + Maybe.Just(new object())), + }; + + var results = specProps.Validate(SpecificationPropertyRules.Lookup(tokens, false)); + Assert.Contains(results, r => r.GetType() == typeof(RepeatedOptionError)); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs b/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs index 82edb635..0fc6db70 100644 --- a/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SwitchTests.cs @@ -15,12 +15,13 @@ public void Partition_switch_values_from_empty_token_sequence() { var expected = new Token[] { }; - var result = Switch.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new Token[] { }, name => new[] { "x", "switch" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Switch, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item1; // *Switch*, Scalar, Sequence, NonOption expected.Should().BeEquivalentTo(result); } @@ -30,7 +31,7 @@ public void Partition_switch_values() { var expected = new [] { Token.Name("x") }; - var result = Switch.Partition( + var tokens = TokenPartitioner.PartitionTokensByType( new [] { Token.Name("str"), Token.Value("strvalue"), Token.Value("freevalue"), @@ -40,6 +41,7 @@ public void Partition_switch_values() new[] { "x", "switch" }.Contains(name) ? Maybe.Just(TypeDescriptor.Create(TargetType.Switch, Maybe.Nothing())) : Maybe.Nothing()); + var result = tokens.Item1; // *Switch*, Scalar, Sequence, NonOption expected.Should().BeEquivalentTo(result); } diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index c62a7836..dc16dfd0 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -120,5 +120,15 @@ public static IEnumerable ChangeType_scalars_source }; } } + + [Fact] + public void ChangeType_Scalar_LastOneWins() + { + var values = new[] { "100", "200", "300", "400", "500" }; + var result = TypeConverter.ChangeType(values, typeof(int), true, CultureInfo.InvariantCulture, true); + result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully"); + Assert.Equal(500, matchedValue); + + } } } diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index bc6d77a8..58ce7d3f 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -132,6 +132,21 @@ public void Parse_options_with_double_dash() // Teardown } + [Fact] + public void Parse_options_with_repeated_value_in_values_sequence_and_option() + { + var text = "x1 x2 x3 -c x1"; // x1 is the same in -c option and first value + var args = text.Split(); + var parser = new Parser(with => + { + with.HelpWriter = Console.Out; + }); + var result = parser.ParseArguments(args); + var options= (result as Parsed).Value; + options.Compress.Should().BeEquivalentTo(new[] { "x1" }); + options.InputDirs.Should().BeEquivalentTo(new[] { "x1","x2","x3" }); + } + [Fact] public void Parse_options_with_double_dash_and_option_sequence() { @@ -901,6 +916,59 @@ public void Parse_multiple_default_verbs() .WithParsed(args => throw new InvalidOperationException("Should not be parsed.")); } + [Fact] + public void Parse_repeated_options_in_verbs_scenario_with_multi_instance() + { + using (var sut = new Parser(settings => settings.AllowMultiInstance = true)) + { + var longVal1 = 100; + var longVal2 = 200; + var longVal3 = 300; + var stringVal = "shortSeq1"; + + var result = sut.ParseArguments( + new[] { "sequence", "--long-seq", $"{longVal1}", "-s", stringVal, "--long-seq", $"{longVal2};{longVal3}" }, + typeof(Add_Verb), typeof(Commit_Verb), typeof(SequenceOptions)); + + Assert.IsType>(result); + Assert.IsType(((Parsed)result).Value); + result.WithParsed(verb => + { + Assert.Equal(new long[] { longVal1, longVal2, longVal3 }, verb.LongSequence); + Assert.Equal(new[] { stringVal }, verb.StringSequence); + }); + } + } + + [Fact] + public void Parse_repeated_options_in_verbs_scenario_without_multi_instance() + { + using (var sut = new Parser(settings => settings.AllowMultiInstance = false)) + { + var longVal1 = 100; + var longVal2 = 200; + var longVal3 = 300; + var stringVal = "shortSeq1"; + + var result = sut.ParseArguments( + new[] { "sequence", "--long-seq", $"{longVal1}", "-s", stringVal, "--long-seq", $"{longVal2};{longVal3}" }, + typeof(Add_Verb), typeof(Commit_Verb), typeof(SequenceOptions)); + + Assert.IsType>(result); + result.WithNotParsed(errors => Assert.All(errors, e => + { + if (e is RepeatedOptionError) + { + // expected + } + else + { + throw new Exception($"{nameof(RepeatedOptionError)} expected"); + } + })); + } + } + [Fact] public void Parse_default_verb_with_empty_name() { From 889ac3bf1fce7bfd014810e1aaa160f908209752 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Wed, 19 Aug 2020 21:37:37 +0700 Subject: [PATCH 180/198] Sequences that hit max stop grabbing values (#682) If a sequence option (one with IEnumerable or similar type) has a Max=N assigned, then once it has hit N values it will stop grabbing values from the command line, and any remaining values will be able to be assigned to other properties with the Value attribute. --- src/CommandLine/Core/TokenPartitioner.cs | 24 ++++++++--------- .../Unit/Core/InstanceBuilderTests.cs | 27 ++++++++++--------- .../Unit/Core/SequenceTests.cs | 12 ++++++++- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index a735a762..d6fe97e1 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -96,7 +96,6 @@ public static Tuple, IEnumerable, IEnumerable, case SequenceState.TokenSearch: case SequenceState.ScalarTokenFound when nameToken == null: case SequenceState.SequenceTokenFound when nameToken == null: - // if (nameToken == null) Console.WriteLine($" (because there was no nameToken)"); nameToken = null; nonOptionTokens.Add(token); state = SequenceState.TokenSearch; @@ -110,14 +109,14 @@ public static Tuple, IEnumerable, IEnumerable, case SequenceState.SequenceTokenFound: if (sequences.TryGetValue(nameToken, out var sequence)) { - // if (max[nameToken].MatchJust(out int m) && count[nameToken] >= m) - // { - // // This sequence is completed, so this and any further values are non-option values - // nameToken = null; - // nonOptionTokens.Add(token); - // state = SequenceState.TokenSearch; - // } - // else + if (max[nameToken].MatchJust(out int m) && count[nameToken] >= m) + { + // This sequence is completed, so this and any further values are non-option values + nameToken = null; + nonOptionTokens.Add(token); + state = SequenceState.TokenSearch; + } + else { sequence.Add(token); count[nameToken]++; @@ -125,9 +124,10 @@ public static Tuple, IEnumerable, IEnumerable, } else { - Console.WriteLine("***BUG!!!***"); - throw new NullReferenceException($"Sequence for name {nameToken} doesn't exist, and it should"); - // sequences[nameToken] = new List(new[] { token }); + // Should never get here, but just in case: + sequences[nameToken] = new List(new[] { token }); + count[nameToken] = 0; + max[nameToken] = Maybe.Nothing(); } break; } diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index be09f375..2f8d02b7 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -185,7 +185,7 @@ public void Parse_string_sequence_with_only_max_constraint(string[] arguments, s } [Fact] - public void Breaking_min_constraint_in_string_sequence_gererates_MissingValueOptionError() + public void Breaking_min_constraint_in_string_sequence_generates_MissingValueOptionError() { // Fixture setup var expectedResult = new[] { new MissingValueOptionError(new NameInfo("s", "string-seq")) }; @@ -199,7 +199,7 @@ public void Breaking_min_constraint_in_string_sequence_gererates_MissingValueOpt } [Fact] - public void Breaking_min_constraint_in_string_sequence_as_value_gererates_SequenceOutOfRangeError() + public void Breaking_min_constraint_in_string_sequence_as_value_generates_SequenceOutOfRangeError() { // Fixture setup var expectedResult = new[] { new SequenceOutOfRangeError(NameInfo.EmptyName) }; @@ -213,21 +213,22 @@ public void Breaking_min_constraint_in_string_sequence_as_value_gererates_Sequen } [Fact] - public void Breaking_max_constraint_in_string_sequence_gererates_SequenceOutOfRangeError() + public void Breaking_max_constraint_in_string_sequence_does_not_generate_SequenceOutOfRangeError() { // Fixture setup - var expectedResult = new[] { new SequenceOutOfRangeError(new NameInfo("s", "string-seq")) }; + var expectedResult = new[] { "one", "two", "three" }; // Exercize system var result = InvokeBuild( new[] { "--string-seq=one", "two", "three", "this-is-too-much" }); // Verify outcome - ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); + ((Parsed)result).Value.StringSequence.Should().BeEquivalentTo(expectedResult); + // The "this-is-too-much" arg would end up assigned to a Value; since there is no Value, it is silently dropped } [Fact] - public void Breaking_max_constraint_in_string_sequence_as_value_gererates_SequenceOutOfRangeError() + public void Breaking_max_constraint_in_string_sequence_as_value_generates_SequenceOutOfRangeError() { // Fixture setup var expectedResult = new[] { new SequenceOutOfRangeError(NameInfo.EmptyName) }; @@ -427,7 +428,7 @@ public void Double_dash_force_subsequent_arguments_as_values() } [Fact] - public void Parse_option_from_different_sets_gererates_MutuallyExclusiveSetError() + public void Parse_option_from_different_sets_generates_MutuallyExclusiveSetError() { // Fixture setup var expectedResult = new[] @@ -480,7 +481,7 @@ public void Two_required_options_at_the_same_set_and_none_are_true() } [Fact] - public void Omitting_required_option_gererates_MissingRequiredOptionError() + public void Omitting_required_option_generates_MissingRequiredOptionError() { // Fixture setup var expectedResult = new[] { new MissingRequiredOptionError(new NameInfo("", "str")) }; @@ -494,7 +495,7 @@ public void Omitting_required_option_gererates_MissingRequiredOptionError() } [Fact] - public void Wrong_range_in_sequence_gererates_SequenceOutOfRangeError() + public void Wrong_range_in_sequence_generates_SequenceOutOfRangeError() { // Fixture setup var expectedResult = new[] { new SequenceOutOfRangeError(new NameInfo("i", "")) }; @@ -508,7 +509,7 @@ public void Wrong_range_in_sequence_gererates_SequenceOutOfRangeError() } [Fact] - public void Parse_unknown_long_option_gererates_UnknownOptionError() + public void Parse_unknown_long_option_generates_UnknownOptionError() { // Fixture setup var expectedResult = new[] { new UnknownOptionError("xyz") }; @@ -522,7 +523,7 @@ public void Parse_unknown_long_option_gererates_UnknownOptionError() } [Fact] - public void Parse_unknown_short_option_gererates_UnknownOptionError() + public void Parse_unknown_short_option_generates_UnknownOptionError() { // Fixture setup var expectedResult = new[] { new UnknownOptionError("z") }; @@ -536,7 +537,7 @@ public void Parse_unknown_short_option_gererates_UnknownOptionError() } [Fact] - public void Parse_unknown_short_option_in_option_group_gererates_UnknownOptionError() + public void Parse_unknown_short_option_in_option_group_generates_UnknownOptionError() { // Fixture setup var expectedResult = new[] { new UnknownOptionError("z") }; @@ -596,7 +597,7 @@ public void Parse_utf8_string_correctly(string[] arguments, string expected) } [Fact] - public void Breaking_equal_min_max_constraint_in_string_sequence_as_value_gererates_SequenceOutOfRangeError() + public void Breaking_equal_min_max_constraint_in_string_sequence_as_value_generates_SequenceOutOfRangeError() { // Fixture setup var expectedResult = new[] { new SequenceOutOfRangeError(NameInfo.EmptyName) }; diff --git a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs index cd17f592..74a3d877 100644 --- a/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/SequenceTests.cs @@ -133,7 +133,7 @@ public void Partition_sequence_multi_instance() [Fact] public void Partition_sequence_multi_instance_with_max() { - var expected = new[] + var incorrect = new[] { Token.Name("seq"), Token.Value("seqval0"), @@ -144,6 +144,14 @@ public void Partition_sequence_multi_instance_with_max() Token.Value("seqval5"), }; + var expected = new[] + { + Token.Name("seq"), + Token.Value("seqval0"), + Token.Value("seqval1"), + Token.Value("seqval2"), + }; + var tokens = TokenPartitioner.PartitionTokensByType( new[] { @@ -159,6 +167,8 @@ public void Partition_sequence_multi_instance_with_max() : Maybe.Nothing()); var result = tokens.Item3; // Switch, Scalar, *Sequence*, NonOption + // Max of 3 will apply to the total values, so there should only be 3 values, not 6 + Assert.NotEqual(incorrect, result); Assert.Equal(expected, result); } } From 3354ffbdf5970745a7262c60db867c7409286e13 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 18 Aug 2020 16:41:04 +0700 Subject: [PATCH 181/198] Add FlagCounter property to OptionSpecification FlagCounter lets an int property count the number of times a flag appears, e.g. "-v -v -v" would produce the value 3 in an int property decorated with [Option('v', FlagCounter=true)]. --- src/CommandLine/Core/InstanceBuilder.cs | 4 +-- src/CommandLine/Core/NameLookup.cs | 2 +- src/CommandLine/Core/OptionMapper.cs | 8 +++-- src/CommandLine/Core/OptionSpecification.cs | 17 +++++++-- .../Core/SpecificationExtensions.cs | 1 + src/CommandLine/Core/TypeConverter.cs | 18 +++++++--- src/CommandLine/OptionAttribute.cs | 11 ++++++ src/CommandLine/UnParserExtensions.cs | 18 +++++++--- .../Options_With_FlagCounter_Switches.cs | 13 +++++++ .../Unit/Core/OptionMapperTests.cs | 6 ++-- .../Unit/Core/TypeConverterTests.cs | 36 +++++++++++++++++-- tests/CommandLine.Tests/Unit/ParserTests.cs | 30 ++++++++++++++++ .../Unit/UnParserExtensionsTests.cs | 22 ++++++++++++ 13 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_FlagCounter_Switches.cs diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index ffd6250b..f48127b1 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -88,14 +88,14 @@ public static ParserResult Build( OptionMapper.MapValues( (from pt in specProps where pt.Specification.IsOption() select pt), optionsPartition, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase), + (vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, parsingCulture, ignoreValueCase), nameComparer); var valueSpecPropsResult = ValueMapper.MapValues( (from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt), valuesPartition, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase)); + (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, false, parsingCulture, ignoreValueCase)); var missingValueErrors = from token in errorsPartition select diff --git a/src/CommandLine/Core/NameLookup.cs b/src/CommandLine/Core/NameLookup.cs index 3605d1a3..ccb24ea5 100644 --- a/src/CommandLine/Core/NameLookup.cs +++ b/src/CommandLine/Core/NameLookup.cs @@ -20,7 +20,7 @@ public static NameLookupResult Contains(string name, IEnumerable name.MatchName(a.ShortName, a.LongName, comparer)); if (option == null) return NameLookupResult.NoOptionFound; - return option.ConversionType == typeof(bool) + return option.ConversionType == typeof(bool) || (option.ConversionType == typeof(int) && option.FlagCounter) ? NameLookupResult.BooleanOptionFound : NameLookupResult.OtherOptionFound; } diff --git a/src/CommandLine/Core/OptionMapper.cs b/src/CommandLine/Core/OptionMapper.cs index d222c100..ded42c4f 100644 --- a/src/CommandLine/Core/OptionMapper.cs +++ b/src/CommandLine/Core/OptionMapper.cs @@ -15,7 +15,7 @@ public static Result< MapValues( IEnumerable propertyTuples, IEnumerable>> options, - Func, Type, bool, Maybe> converter, + Func, Type, bool, bool, Maybe> converter, StringComparer comparer) { var sequencesAndErrors = propertyTuples @@ -27,7 +27,7 @@ public static Result< if (matched.IsJust()) { var matches = matched.GetValueOrDefault(Enumerable.Empty>>()); - var values = new HashSet(); + var values = new List(); foreach (var kvp in matches) { foreach (var value in kvp.Value) @@ -36,7 +36,9 @@ public static Result< } } - return converter(values, pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence) + bool isFlag = pt.Specification.Tag == SpecificationType.Option && ((OptionSpecification)pt.Specification).FlagCounter; + + return converter(values, isFlag ? typeof(bool) : pt.Property.PropertyType, pt.Specification.TargetType != TargetType.Sequence, isFlag) .Select(value => Tuple.Create(pt.WithValue(Maybe.Just(value)), Maybe.Nothing())) .GetValueOrDefault( Tuple.Create>( diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 77e7977f..1c2e4f88 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -14,18 +14,20 @@ sealed class OptionSpecification : Specification private readonly char separator; private readonly string setName; private readonly string group; + private readonly bool flagCounter; public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, string group, bool hidden = false) + Type conversionType, TargetType targetType, string group, bool flagCounter = false, bool hidden = false) : base(SpecificationType.Option, - required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) + required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden) { this.shortName = shortName; this.longName = longName; this.separator = separator; this.setName = setName; this.group = group; + this.flagCounter = flagCounter; } public static OptionSpecification FromAttribute(OptionAttribute attribute, Type conversionType, IEnumerable enumValues) @@ -45,13 +47,14 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type conversionType, conversionType.ToTargetType(), attribute.Group, + attribute.FlagCounter, attribute.Hidden); } public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) { return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, hidden); + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, false, hidden); } public string ShortName @@ -78,5 +81,13 @@ public string Group { get { return group; } } + + /// + /// Whether this is an int option that counts how many times a flag was set rather than taking a value on the command line + /// + public bool FlagCounter + { + get { return flagCounter; } + } } } diff --git a/src/CommandLine/Core/SpecificationExtensions.cs b/src/CommandLine/Core/SpecificationExtensions.cs index e223e987..c080e983 100644 --- a/src/CommandLine/Core/SpecificationExtensions.cs +++ b/src/CommandLine/Core/SpecificationExtensions.cs @@ -35,6 +35,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific specification.ConversionType, specification.TargetType, specification.Group, + specification.FlagCounter, specification.Hidden); } diff --git a/src/CommandLine/Core/TypeConverter.cs b/src/CommandLine/Core/TypeConverter.cs index ee69373e..2e27af40 100644 --- a/src/CommandLine/Core/TypeConverter.cs +++ b/src/CommandLine/Core/TypeConverter.cs @@ -13,11 +13,13 @@ namespace CommandLine.Core { static class TypeConverter { - public static Maybe ChangeType(IEnumerable values, Type conversionType, bool scalar, CultureInfo conversionCulture, bool ignoreValueCase) + public static Maybe ChangeType(IEnumerable values, Type conversionType, bool scalar, bool isFlag, CultureInfo conversionCulture, bool ignoreValueCase) { - return scalar - ? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase) - : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); + return isFlag + ? ChangeTypeFlagCounter(values, conversionType, conversionCulture, ignoreValueCase) + : scalar + ? ChangeTypeScalar(values.Last(), conversionType, conversionCulture, ignoreValueCase) + : ChangeTypeSequence(values, conversionType, conversionCulture, ignoreValueCase); } private static Maybe ChangeTypeSequence(IEnumerable values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) @@ -46,6 +48,14 @@ private static Maybe ChangeTypeScalar(string value, Type conversionType, return result.ToMaybe(); } + private static Maybe ChangeTypeFlagCounter(IEnumerable values, Type conversionType, CultureInfo conversionCulture, bool ignoreValueCase) + { + var converted = values.Select(value => ChangeTypeScalar(value, typeof(bool), conversionCulture, ignoreValueCase)); + return converted.Any(maybe => maybe.MatchNothing()) + ? Maybe.Nothing() + : Maybe.Just((object)converted.Count(value => value.IsJust())); + } + private static object ConvertString(string value, Type type, CultureInfo conversionCulture) { try diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 7448b697..6ae51dac 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -15,6 +15,7 @@ public sealed class OptionAttribute : BaseAttribute private readonly string longName; private readonly string shortName; private string setName; + private bool flagCounter; private char separator; private string group=string.Empty; @@ -96,6 +97,16 @@ public string SetName } } + /// + /// If true, this is an int option that counts how many times a flag was set (e.g. "-v -v -v" or "-vvv" would return 3). + /// The property must be of type int (signed 32-bit integer). + /// + public bool FlagCounter + { + get { return flagCounter; } + set { flagCounter = value; } + } + /// /// When applying attribute to target properties, /// it allows you to split an argument and consume its content as a sequence. diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 51811ef9..e823a7fa 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -153,7 +153,9 @@ public static string FormatCommandLine(this Parser parser, T options, Action< var allOptSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Option) let o = (OptionSpecification)info.Specification - where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value)) + where o.TargetType != TargetType.Switch || + (o.TargetType == TargetType.Switch && o.FlagCounter && ((int)info.Value > 0)) || + (o.TargetType == TargetType.Switch && ((bool)info.Value)) where !o.Hidden || settings.ShowHidden orderby o.UniqueName() select info; @@ -176,7 +178,12 @@ orderby v.Index builder = settings.GroupSwitches && shortSwitches.Any() ? builder.Append('-').Append(string.Join(string.Empty, shortSwitches.Select( - info => ((OptionSpecification)info.Specification).ShortName).ToArray())).Append(' ') + info => { + var o = (OptionSpecification)info.Specification; + return o.FlagCounter + ? string.Concat(Enumerable.Repeat(o.ShortName, (int)info.Value)) + : o.ShortName; + }).ToArray())).Append(' ') : builder; optSpecs.ForEach( opt => @@ -250,24 +257,25 @@ private static char SeperatorOrSpace(this Specification spec) private static string FormatOption(OptionSpecification spec, object value, UnParserSettings settings) { return new StringBuilder() - .Append(spec.FormatName(settings)) + .Append(spec.FormatName(value, settings)) .AppendWhen(spec.TargetType != TargetType.Switch, FormatValue(spec, value)) .ToString(); } - private static string FormatName(this OptionSpecification optionSpec, UnParserSettings settings) + private static string FormatName(this OptionSpecification optionSpec, object value, UnParserSettings settings) { // Have a long name and short name not preferred? Go with long! // No short name? Has to be long! var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName) || optionSpec.ShortName.Length == 0; - return + var formattedName = new StringBuilder(longName ? "--".JoinTo(optionSpec.LongName) : "-".JoinTo(optionSpec.ShortName)) .AppendWhen(optionSpec.TargetType != TargetType.Switch, longName && settings.UseEqualToken ? "=" : " ") .ToString(); + return optionSpec.FlagCounter ? String.Join(" ", Enumerable.Repeat(formattedName, (int)value)) : formattedName; } private static object NormalizeValue(this object value) diff --git a/tests/CommandLine.Tests/Fakes/Options_With_FlagCounter_Switches.cs b/tests/CommandLine.Tests/Fakes/Options_With_FlagCounter_Switches.cs new file mode 100644 index 00000000..06787b31 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_FlagCounter_Switches.cs @@ -0,0 +1,13 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +namespace CommandLine.Tests.Fakes +{ + public class Options_With_FlagCounter_Switches + { + [Option('v', FlagCounter=true)] + public int Verbose { get; set; } + + [Option('s', FlagCounter=true)] + public int Silent { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 63bf22f3..0a948cea 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -37,7 +37,7 @@ public void Map_boolean_switch_creates_boolean_value() var result = OptionMapper.MapValues( specProps.Where(pt => pt.Specification.IsOption()), tokenPartitions, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + (vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false), StringComparer.Ordinal ); @@ -72,7 +72,7 @@ public void Map_with_multi_instance_scalar() var result = OptionMapper.MapValues( specProps.Where(pt => pt.Specification.IsOption()), tokenPartitions, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + (vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false), StringComparer.Ordinal); var property = result.SucceededWith().Single(); @@ -101,7 +101,7 @@ public void Map_with_multi_instance_sequence() var result = OptionMapper.MapValues( specProps.Where(pt => pt.Specification.IsOption()), tokenPartitions, - (vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, CultureInfo.InvariantCulture, false), + (vals, type, isScalar, isFlag) => TypeConverter.ChangeType(vals, type, isScalar, isFlag, CultureInfo.InvariantCulture, false), StringComparer.Ordinal); var property = result.SucceededWith().Single(); diff --git a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs index dc16dfd0..c3e93781 100644 --- a/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TypeConverterTests.cs @@ -27,7 +27,7 @@ enum TestFlagEnum [MemberData(nameof(ChangeType_scalars_source))] public void ChangeType_scalars(string testValue, Type destinationType, bool expectFail, object expectedResult) { - Maybe result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, CultureInfo.InvariantCulture, true); + Maybe result = TypeConverter.ChangeType(new[] {testValue}, destinationType, true, false, CultureInfo.InvariantCulture, true); if (expectFail) { @@ -121,11 +121,43 @@ public static IEnumerable ChangeType_scalars_source } } + [Theory] + [MemberData(nameof(ChangeType_flagCounters_source))] + public void ChangeType_flagCounters(string[] testValue, Type destinationType, bool expectFail, object expectedResult) + { + Maybe result = TypeConverter.ChangeType(testValue, destinationType, true, true, CultureInfo.InvariantCulture, true); + + if (expectFail) + { + result.MatchNothing().Should().BeTrue("should fail parsing"); + } + else + { + result.MatchJust(out object matchedValue).Should().BeTrue("should parse successfully"); + Assert.Equal(matchedValue, expectedResult); + } + } + + public static IEnumerable ChangeType_flagCounters_source + { + get + { + return new[] + { + new object[] {new string[0], typeof (int), false, 0}, + new object[] {new[] {"true"}, typeof (int), false, 1}, + new object[] {new[] {"true", "true"}, typeof (int), false, 2}, + new object[] {new[] {"true", "true", "true"}, typeof (int), false, 3}, + new object[] {new[] {"true", "x"}, typeof (int), true, 0}, + }; + } + } + [Fact] public void ChangeType_Scalar_LastOneWins() { var values = new[] { "100", "200", "300", "400", "500" }; - var result = TypeConverter.ChangeType(values, typeof(int), true, CultureInfo.InvariantCulture, true); + var result = TypeConverter.ChangeType(values, typeof(int), true, false, CultureInfo.InvariantCulture, true); result.MatchJust(out var matchedValue).Should().BeTrue("should parse successfully"); Assert.Equal(500, matchedValue); diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index 58ce7d3f..f8593cf3 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -95,6 +95,36 @@ public void Parse_options_with_short_name(string outputFile, string[] args) // Teardown } + [Theory] + [InlineData(new string[0], 0, 0)] + [InlineData(new[] { "-v" }, 1, 0)] + [InlineData(new[] { "-vv" }, 2, 0)] + [InlineData(new[] { "-v", "-v" }, 2, 0)] + [InlineData(new[] { "-v", "-v", "-v" }, 3, 0)] + [InlineData(new[] { "-v", "-vv" }, 3, 0)] + [InlineData(new[] { "-vv", "-v" }, 3, 0)] + [InlineData(new[] { "-vvv" }, 3, 0)] + [InlineData(new[] { "-v", "-s", "-v", "-v" }, 3, 1)] + [InlineData(new[] { "-v", "-ss", "-v", "-v" }, 3, 2)] + [InlineData(new[] { "-v", "-s", "-sv", "-v" }, 3, 2)] + [InlineData(new[] { "-vsvv" }, 3, 1)] + [InlineData(new[] { "-vssvv" }, 3, 2)] + [InlineData(new[] { "-vsvsv" }, 3, 2)] + public void Parse_FlagCounter_options_with_short_name(string[] args, int verboseCount, int silentCount) + { + // Fixture setup + var expectedOptions = new Options_With_FlagCounter_Switches { Verbose = verboseCount, Silent = silentCount }; + var sut = new Parser(with => with.AllowMultiInstance = true); + + // Exercize system + var result = sut.ParseArguments(args); + + // Verify outcome + // ((NotParsed)result).Errors.Should().BeEmpty(); + ((Parsed)result).Value.Should().BeEquivalentTo(expectedOptions); + // Teardown + } + [Fact] public void Parse_repeated_options_with_default_parser() { diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 50cb3290..7e878f32 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -254,6 +254,23 @@ public static void UnParsing_instance_with_int_nullable(bool skipDefault, int? v .Should().BeEquivalentTo(expected); } + + [Theory] + [InlineData(false, false, 0, "")] + [InlineData(false, false, 1, "-v")] // default but not skipped + [InlineData(false, false, 2, "-v -v")] + [InlineData(false, true, 2, "-vv")] + [InlineData(false, false, 3, "-v -v -v")] + [InlineData(false, true, 3, "-vvv")] + [InlineData(true, false, 1, "")] // default, skipped + public static void UnParsing_instance_with_flag_counter(bool skipDefault, bool groupSwitches, int value, string expected) + { + var options = new Option_FlagCounter { VerboseLevel = value }; + var result = new Parser() + .FormatCommandLine(options, x => { x.SkipDefault = skipDefault; x.GroupSwitches = groupSwitches; }) + .Should().BeEquivalentTo(expected); + } + [Theory] [InlineData(Shapes.Circle, "--shape Circle")] [InlineData(Shapes.Square, "--shape Square")] @@ -311,6 +328,11 @@ class Option_Int [Option('v', Default = 1)] public int VerboseLevel { get; set; } } + class Option_FlagCounter + { + [Option('v', Default = 1, FlagCounter=true)] + public int VerboseLevel { get; set; } + } class Option_Nullable_Bool { [Option('v')] From 570d7b77695b0506e15fe9d5d723f3ffcf03728b Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Wed, 19 Aug 2020 10:02:06 +0700 Subject: [PATCH 182/198] Add GetoptMode parser setting and implementation Turning on Getopt mode automatically turns on the EnableDashDash and AllowMultiInstance settings as well, but they can be disabled by explicitly setting them to false in the parser settings. --- src/CommandLine/Core/GetoptTokenizer.cs | 219 ++++++++++++++ .../Infrastructure/StringExtensions.cs | 20 +- src/CommandLine/Parser.cs | 9 +- src/CommandLine/ParserSettings.cs | 40 ++- .../Fakes/Simple_Options_With_ExtraArgs.cs | 27 ++ .../Unit/Core/GetoptTokenizerTests.cs | 126 ++++++++ .../Unit/Core/TokenizerTests.cs | 3 +- .../Unit/GetoptParserTests.cs | 284 ++++++++++++++++++ tests/CommandLine.Tests/Unit/ParserTests.cs | 3 + 9 files changed, 720 insertions(+), 11 deletions(-) create mode 100644 src/CommandLine/Core/GetoptTokenizer.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_ExtraArgs.cs create mode 100644 tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs create mode 100644 tests/CommandLine.Tests/Unit/GetoptParserTests.cs diff --git a/src/CommandLine/Core/GetoptTokenizer.cs b/src/CommandLine/Core/GetoptTokenizer.cs new file mode 100644 index 00000000..d9fe63fc --- /dev/null +++ b/src/CommandLine/Core/GetoptTokenizer.cs @@ -0,0 +1,219 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using CommandLine.Infrastructure; +using CSharpx; +using RailwaySharp.ErrorHandling; +using System.Text.RegularExpressions; + +namespace CommandLine.Core +{ + static class GetoptTokenizer + { + public static Result, Error> Tokenize( + IEnumerable arguments, + Func nameLookup) + { + return GetoptTokenizer.Tokenize(arguments, nameLookup, ignoreUnknownArguments:false, allowDashDash:true, posixlyCorrect:false); + } + + public static Result, Error> Tokenize( + IEnumerable arguments, + Func nameLookup, + bool ignoreUnknownArguments, + bool allowDashDash, + bool posixlyCorrect) + { + var errors = new List(); + Action onBadFormatToken = arg => errors.Add(new BadFormatTokenError(arg)); + Action unknownOptionError = name => errors.Add(new UnknownOptionError(name)); + Action doNothing = name => {}; + Action onUnknownOption = ignoreUnknownArguments ? doNothing : unknownOptionError; + + int consumeNext = 0; + Action onConsumeNext = (n => consumeNext = consumeNext + n); + bool forceValues = false; + + var tokens = new List(); + + var enumerator = arguments.GetEnumerator(); + while (enumerator.MoveNext()) + { + switch (enumerator.Current) { + case null: + break; + + case string arg when forceValues: + tokens.Add(Token.ValueForced(arg)); + break; + + case string arg when consumeNext > 0: + tokens.Add(Token.Value(arg)); + consumeNext = consumeNext - 1; + break; + + case "--" when allowDashDash: + forceValues = true; + break; + + case "--": + tokens.Add(Token.Value("--")); + if (posixlyCorrect) forceValues = true; + break; + + case "-": + // A single hyphen is always a value (it usually means "read from stdin" or "write to stdout") + tokens.Add(Token.Value("-")); + if (posixlyCorrect) forceValues = true; + break; + + case string arg when arg.StartsWith("--"): + tokens.AddRange(TokenizeLongName(arg, nameLookup, onBadFormatToken, onUnknownOption, onConsumeNext)); + break; + + case string arg when arg.StartsWith("-"): + tokens.AddRange(TokenizeShortName(arg, nameLookup, onUnknownOption, onConsumeNext)); + break; + + case string arg: + // If we get this far, it's a plain value + tokens.Add(Token.Value(arg)); + if (posixlyCorrect) forceValues = true; + break; + } + } + + return Result.Succeed, Error>(tokens.AsEnumerable(), errors.AsEnumerable()); + } + + public static Result, Error> ExplodeOptionList( + Result, Error> tokenizerResult, + Func> optionSequenceWithSeparatorLookup) + { + var tokens = tokenizerResult.SucceededWith().Memoize(); + + var replaces = tokens.Select((t, i) => + optionSequenceWithSeparatorLookup(t.Text) + .MapValueOrDefault(sep => Tuple.Create(i + 1, sep), + Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0).Memoize(); + + var exploded = tokens.Select((t, i) => + replaces.FirstOrDefault(x => x.Item1 == i).ToMaybe() + .MapValueOrDefault(r => t.Text.Split(r.Item2).Select(Token.Value), + Enumerable.Empty().Concat(new[] { t }))); + + var flattened = exploded.SelectMany(x => x); + + return Result.Succeed(flattened, tokenizerResult.SuccessMessages()); + } + + public static Func< + IEnumerable, + IEnumerable, + Result, Error>> + ConfigureTokenizer( + StringComparer nameComparer, + bool ignoreUnknownArguments, + bool enableDashDash, + bool posixlyCorrect) + { + return (arguments, optionSpecs) => + { + var tokens = GetoptTokenizer.Tokenize(arguments, name => NameLookup.Contains(name, optionSpecs, nameComparer), ignoreUnknownArguments, enableDashDash, posixlyCorrect); + var explodedTokens = GetoptTokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer)); + return explodedTokens; + }; + } + + private static IEnumerable TokenizeShortName( + string arg, + Func nameLookup, + Action onUnknownOption, + Action onConsumeNext) + { + + // First option char that requires a value means we swallow the rest of the string as the value + // But if there is no rest of the string, then instead we swallow the next argument + string chars = arg.Substring(1); + int len = chars.Length; + if (len > 0 && Char.IsDigit(chars[0])) + { + // Assume it's a negative number + yield return Token.Value(arg); + yield break; + } + for (int i = 0; i < len; i++) + { + var s = new String(chars[i], 1); + switch(nameLookup(s)) + { + case NameLookupResult.OtherOptionFound: + yield return Token.Name(s); + + if (i+1 < len) + { + // Rest of this is the value (e.g. "-sfoo" where "-s" is a string-consuming arg) + yield return Token.Value(chars.Substring(i+1)); + yield break; + } + else + { + // Value is in next param (e.g., "-s foo") + onConsumeNext(1); + } + break; + + case NameLookupResult.NoOptionFound: + onUnknownOption(s); + break; + + default: + yield return Token.Name(s); + break; + } + } + } + + private static IEnumerable TokenizeLongName( + string arg, + Func nameLookup, + Action onBadFormatToken, + Action onUnknownOption, + Action onConsumeNext) + { + string[] parts = arg.Substring(2).Split(new char[] { '=' }, 2); + string name = parts[0]; + string value = (parts.Length > 1) ? parts[1] : null; + // A parameter like "--stringvalue=" is acceptable, and makes stringvalue be the empty string + if (String.IsNullOrWhiteSpace(name) || name.Contains(" ")) + { + onBadFormatToken(arg); + yield break; + } + switch(nameLookup(name)) + { + case NameLookupResult.NoOptionFound: + onUnknownOption(name); + yield break; + + case NameLookupResult.OtherOptionFound: + yield return Token.Name(name); + if (value == null) // NOT String.IsNullOrEmpty + { + onConsumeNext(1); + } + else + { + yield return Token.Value(value); + } + break; + + default: + yield return Token.Name(name); + break; + } + } + } +} diff --git a/src/CommandLine/Infrastructure/StringExtensions.cs b/src/CommandLine/Infrastructure/StringExtensions.cs index 7bfab66a..db8aa0bd 100644 --- a/src/CommandLine/Infrastructure/StringExtensions.cs +++ b/src/CommandLine/Infrastructure/StringExtensions.cs @@ -73,5 +73,23 @@ public static bool ToBoolean(this string value) { return value.Equals("true", StringComparison.OrdinalIgnoreCase); } + + public static bool ToBooleanLoose(this string value) + { + if ((string.IsNullOrEmpty(value)) || + (value == "0") || + (value.Equals("f", StringComparison.OrdinalIgnoreCase)) || + (value.Equals("n", StringComparison.OrdinalIgnoreCase)) || + (value.Equals("no", StringComparison.OrdinalIgnoreCase)) || + (value.Equals("off", StringComparison.OrdinalIgnoreCase)) || + (value.Equals("false", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + else + { + return true; + } + } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 10c9b4e1..4301aa52 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -185,8 +185,13 @@ private static Result, Error> Tokenize( IEnumerable optionSpecs, ParserSettings settings) { - return - Tokenizer.ConfigureTokenizer( + return settings.GetoptMode + ? GetoptTokenizer.ConfigureTokenizer( + settings.NameComparer, + settings.IgnoreUnknownArguments, + settings.EnableDashDash, + settings.PosixlyCorrect)(arguments, optionSpecs) + : Tokenizer.ConfigureTokenizer( settings.NameComparer, settings.IgnoreUnknownArguments, settings.EnableDashDash)(arguments, optionSpecs); diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 95a4cd81..5ed73f30 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -5,6 +5,7 @@ using System.IO; using CommandLine.Infrastructure; +using CSharpx; namespace CommandLine { @@ -23,9 +24,11 @@ public class ParserSettings : IDisposable private bool autoHelp; private bool autoVersion; private CultureInfo parsingCulture; - private bool enableDashDash; + private Maybe enableDashDash; private int maximumDisplayWidth; - private bool allowMultiInstance; + private Maybe allowMultiInstance; + private bool getoptMode; + private Maybe posixlyCorrect; /// /// Initializes a new instance of the class. @@ -38,6 +41,10 @@ public ParserSettings() autoVersion = true; parsingCulture = CultureInfo.InvariantCulture; maximumDisplayWidth = GetWindowWidth(); + getoptMode = false; + enableDashDash = Maybe.Nothing(); + allowMultiInstance = Maybe.Nothing(); + posixlyCorrect = Maybe.Nothing(); } private int GetWindowWidth() @@ -159,11 +166,12 @@ public bool AutoVersion /// /// Gets or sets a value indicating whether enable double dash '--' syntax, /// that forces parsing of all subsequent tokens as values. + /// If GetoptMode is true, this defaults to true, but can be turned off by explicitly specifying EnableDashDash = false. /// public bool EnableDashDash { - get { return enableDashDash; } - set { PopsicleSetter.Set(Consumed, ref enableDashDash, value); } + get => enableDashDash.MatchJust(out bool value) ? value : getoptMode; + set => PopsicleSetter.Set(Consumed, ref enableDashDash, Maybe.Just(value)); } /// @@ -177,11 +185,31 @@ public int MaximumDisplayWidth /// /// Gets or sets a value indicating whether options are allowed to be specified multiple times. + /// If GetoptMode is true, this defaults to true, but can be turned off by explicitly specifying AllowMultiInstance = false. /// public bool AllowMultiInstance { - get => allowMultiInstance; - set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, value); + get => allowMultiInstance.MatchJust(out bool value) ? value : getoptMode; + set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, Maybe.Just(value)); + } + + /// + /// Whether strict getopt-like processing is applied to option values; if true, AllowMultiInstance and EnableDashDash will default to true as well. + /// + public bool GetoptMode + { + get => getoptMode; + set => PopsicleSetter.Set(Consumed, ref getoptMode, value); + } + + /// + /// Whether getopt-like processing should follow the POSIX rules (the equivalent of using the "+" prefix in the C getopt() call). + /// If not explicitly set, will default to false unless the POSIXLY_CORRECT environment variable is set, in which case it will default to true. + /// + public bool PosixlyCorrect + { + get => posixlyCorrect.MapValueOrDefault(val => val, () => Environment.GetEnvironmentVariable("POSIXLY_CORRECT").ToBooleanLoose()); + set => PopsicleSetter.Set(Consumed, ref posixlyCorrect, Maybe.Just(value)); } internal StringComparer NameComparer diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_ExtraArgs.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_ExtraArgs.cs new file mode 100644 index 00000000..bb276fa5 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_ExtraArgs.cs @@ -0,0 +1,27 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_WithExtraArgs + { + [Option(HelpText = "Define a string value here.")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.")] + public string ShortAndLong { get; set; } + + [Option('i', Min = 3, Max = 4, Separator = ',', HelpText = "Define a int sequence here.")] + public IEnumerable IntSequence { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + + [Value(0, HelpText = "Define a long value here.")] + public long LongValue { get; set; } + + [Value(1, HelpText = "Extra args get collected here.")] + public IEnumerable ExtraArgs { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs new file mode 100644 index 00000000..337a9a3f --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs @@ -0,0 +1,126 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using FluentAssertions; +using CSharpx; +using RailwaySharp.ErrorHandling; +using CommandLine.Core; + +namespace CommandLine.Tests.Unit.Core +{ + public class GetoptTokenizerTests + { + [Fact] + public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() + { + // Fixture setup + var expectedTokens = new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), + Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; + var specs = new[] { new OptionSpecification(string.Empty, "string-seq", + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; + + // Exercize system + var result = + GetoptTokenizer.ExplodeOptionList( + Result.Succeed( + Enumerable.Empty().Concat(new[] { Token.Name("i"), Token.Value("10"), + Token.Name("string-seq"), Token.Value("aaa,bb,cccc"), Token.Name("switch") }), + Enumerable.Empty()), + optionName => NameLookup.HavingSeparator(optionName, specs, StringComparer.Ordinal)); + // Verify outcome + ((Ok, Error>)result).Success.Should().BeEquivalentTo(expectedTokens); + + // Teardown + } + + [Fact] + public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() + { + // Fixture setup + var expectedTokens = new[] { Token.Name("x"), Token.Name("string-seq"), + Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; + var specs = new[] { new OptionSpecification(string.Empty, "string-seq", + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; + + // Exercize system + var result = + GetoptTokenizer.ExplodeOptionList( + Result.Succeed( + Enumerable.Empty().Concat(new[] { Token.Name("x"), + Token.Name("string-seq"), Token.Value("aaa,bb,cccc"), Token.Name("switch") }), + Enumerable.Empty()), + optionName => NameLookup.HavingSeparator(optionName, specs, StringComparer.Ordinal)); + + // Verify outcome + ((Ok, Error>)result).Success.Should().BeEquivalentTo(expectedTokens); + + // Teardown + } + + [Fact] + public void Should_properly_parse_option_with_equals_in_value() + { + /** + * This is how the arg. would look in `static void Main(string[] args)` + * if passed from the command-line and the option-value wrapped in quotes. + * Ex.) ./app --connectionString="Server=localhost;Data Source..." + */ + var args = new[] { "--connectionString=Server=localhost;Data Source=(LocalDB)\v12.0;Initial Catalog=temp;" }; + + var result = GetoptTokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound); + + var tokens = result.SucceededWith(); + + Assert.NotNull(tokens); + Assert.Equal(2, tokens.Count()); + Assert.Equal("connectionString", tokens.First().Text); + Assert.Equal("Server=localhost;Data Source=(LocalDB)\v12.0;Initial Catalog=temp;", tokens.Last().Text); + } + + [Fact] + public void Should_return_error_if_option_format_with_equals_is_not_correct() + { + var args = new[] { "--option1 = fail", "--option2= succeed" }; + + var result = GetoptTokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound); + + var errors = result.SuccessMessages(); + + Assert.NotNull(errors); + Assert.Equal(1, errors.Count()); + Assert.Equal(ErrorType.BadFormatTokenError, errors.First().Tag); + + var tokens = result.SucceededWith(); + Assert.NotNull(tokens); + Assert.Equal(2, tokens.Count()); + Assert.Equal(TokenType.Name, tokens.First().Tag); + Assert.Equal(TokenType.Value, tokens.Last().Tag); + Assert.Equal("option2", tokens.First().Text); + Assert.Equal(" succeed", tokens.Last().Text); + } + + + [Theory] + [InlineData(new[] { "-a", "-" }, 2,"a","-")] + [InlineData(new[] { "--file", "-" }, 2,"file","-")] + [InlineData(new[] { "-f-" }, 2,"f", "-")] + [InlineData(new[] { "--file=-" }, 2, "file", "-")] + [InlineData(new[] { "-a", "--" }, 2, "a", "--")] + public void Single_dash_as_a_value(string[] args, int countExcepted,string first,string last) + { + //Arrange + //Act + var result = GetoptTokenizer.Tokenize(args, name => NameLookupResult.OtherOptionFound); + var tokens = result.SucceededWith().ToList(); + //Assert + tokens.Should().NotBeNull(); + tokens.Count.Should().Be(countExcepted); + tokens.First().Text.Should().Be(first); + tokens.Last().Text.Should().Be(last); + } + } + +} diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index f14eea51..a489b741 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -124,14 +124,13 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() Assert.Equal(ErrorType.BadFormatTokenError, tokens.Last().Tag); } - [Theory] [InlineData(new[] { "-a", "-" }, 2,"a","-")] [InlineData(new[] { "--file", "-" }, 2,"file","-")] [InlineData(new[] { "-f-" }, 2,"f", "-")] [InlineData(new[] { "--file=-" }, 2, "file", "-")] [InlineData(new[] { "-a", "--" }, 1, "a", "a")] - public void single_dash_as_a_value(string[] args, int countExcepted,string first,string last) + public void Single_dash_as_a_value(string[] args, int countExcepted,string first,string last) { //Arrange //Act diff --git a/tests/CommandLine.Tests/Unit/GetoptParserTests.cs b/tests/CommandLine.Tests/Unit/GetoptParserTests.cs new file mode 100644 index 00000000..cd2a1577 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/GetoptParserTests.cs @@ -0,0 +1,284 @@ +using System; +using Xunit; +using FluentAssertions; +using CommandLine.Core; +using CommandLine.Tests.Fakes; +using System.IO; +using System.Linq; +using System.Collections.Generic; + +namespace CommandLine.Tests.Unit +{ + public class GetoptParserTests + { + public GetoptParserTests() + { + } + + public class SimpleArgsData : TheoryData + { + public SimpleArgsData() + { + // Options and values can be mixed by default + Add(new string [] { "--stringvalue=foo", "-x", "256" }, + new Simple_Options_WithExtraArgs { + IntSequence = Enumerable.Empty(), + ShortAndLong = null, + StringValue = "foo", + BoolValue = true, + LongValue = 256, + ExtraArgs = Enumerable.Empty(), + }); + Add(new string [] { "256", "--stringvalue=foo", "-x" }, + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + IntSequence = Enumerable.Empty(), + BoolValue = true, + LongValue = 256, + ExtraArgs = Enumerable.Empty(), + }); + Add(new string [] {"--stringvalue=foo", "256", "-x" }, + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + IntSequence = Enumerable.Empty(), + BoolValue = true, + LongValue = 256, + ExtraArgs = Enumerable.Empty(), + }); + + // Sequences end at first non-value arg even if they haven't yet consumed their max + Add(new string [] {"--stringvalue=foo", "-i1", "2", "3", "-x", "256" }, + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + IntSequence = new[] { 1, 2, 3 }, + BoolValue = true, + LongValue = 256, + ExtraArgs = Enumerable.Empty(), + }); + // Sequences also end after consuming their max even if there would be more parameters + Add(new string [] {"--stringvalue=foo", "-i1", "2", "3", "4", "256", "-x" }, + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + IntSequence = new[] { 1, 2, 3, 4 }, + BoolValue = true, + LongValue = 256, + ExtraArgs = Enumerable.Empty(), + }); + + // The special -- option, if not consumed, turns off further option processing + Add(new string [] {"--stringvalue", "foo", "256", "-x", "-sbar" }, + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = "bar", + BoolValue = true, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + Add(new string [] {"--stringvalue", "foo", "--", "256", "-x", "-sbar" }, + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + BoolValue = false, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = new [] { "-x", "-sbar" }, + }); + + // But if -- is specified as a value following an equal sign, it has no special meaning + Add(new string [] {"--stringvalue=--", "256", "-x", "-sbar" }, + new Simple_Options_WithExtraArgs { + StringValue = "--", + ShortAndLong = "bar", + BoolValue = true, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + + // Options that take values will take the next arg whatever it looks like + Add(new string [] {"--stringvalue", "-x", "256" }, + new Simple_Options_WithExtraArgs { + StringValue = "-x", + BoolValue = false, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + Add(new string [] {"--stringvalue", "-x", "-x", "256" }, + new Simple_Options_WithExtraArgs { + StringValue = "-x", + BoolValue = true, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + + // That applies even if the next arg is -- which would normally stop option processing: if it's after an option that takes a value, it's consumed as the value + Add(new string [] {"--stringvalue", "--", "256", "-x", "-sbar" }, + new Simple_Options_WithExtraArgs { + StringValue = "--", + ShortAndLong = "bar", + BoolValue = true, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + + // Options that take values will not swallow the next arg if a value was specified with = + Add(new string [] {"--stringvalue=-x", "256" }, + new Simple_Options_WithExtraArgs { + StringValue = "-x", + BoolValue = false, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + Add(new string [] {"--stringvalue=-x", "-x", "256" }, + new Simple_Options_WithExtraArgs { + StringValue = "-x", + BoolValue = true, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = Enumerable.Empty(), + }); + } + } + + [Theory] + [ClassData(typeof(SimpleArgsData))] + public void Getopt_parser_without_posixly_correct_allows_mixed_options_and_nonoptions(string[] args, Simple_Options_WithExtraArgs expected) + { + // Arrange + var sut = new Parser(config => { + config.GetoptMode = true; + config.PosixlyCorrect = false; + }); + + // Act + var result = sut.ParseArguments(args); + + // Assert + if (result is Parsed parsed) { + parsed.Value.Should().BeEquivalentTo(expected); + } else if (result is NotParsed notParsed) { + Console.WriteLine(String.Join(", ", notParsed.Errors.Select(err => err.Tag.ToString()))); + } + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + public class SimpleArgsDataWithPosixlyCorrect : TheoryData + { + public SimpleArgsDataWithPosixlyCorrect() + { + Add(new string [] { "--stringvalue=foo", "-x", "256" }, + // Parses all options + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + IntSequence = Enumerable.Empty(), + BoolValue = true, + LongValue = 256, + ExtraArgs = Enumerable.Empty(), + }); + Add(new string [] { "256", "--stringvalue=foo", "-x" }, + // Stops parsing after "256", so StringValue and BoolValue not set + new Simple_Options_WithExtraArgs { + StringValue = null, + ShortAndLong = null, + IntSequence = Enumerable.Empty(), + BoolValue = false, + LongValue = 256, + ExtraArgs = new string[] { "--stringvalue=foo", "-x" }, + }); + Add(new string [] {"--stringvalue=foo", "256", "-x" }, + // Stops parsing after "256", so StringValue is set but BoolValue is not + new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + IntSequence = Enumerable.Empty(), + BoolValue = false, + LongValue = 256, + ExtraArgs = new string[] { "-x" }, + }); + } + } + + [Theory] + [ClassData(typeof(SimpleArgsDataWithPosixlyCorrect))] + public void Getopt_parser_with_posixly_correct_stops_parsing_at_first_nonoption(string[] args, Simple_Options_WithExtraArgs expected) + { + // Arrange + var sut = new Parser(config => { + config.GetoptMode = true; + config.PosixlyCorrect = true; + config.EnableDashDash = true; + }); + + // Act + var result = sut.ParseArguments(args); + + // Assert + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + [Fact] + public void Getopt_mode_defaults_to_EnableDashDash_being_true() + { + // Arrange + var sut = new Parser(config => { + config.GetoptMode = true; + config.PosixlyCorrect = false; + }); + var args = new string [] {"--stringvalue", "foo", "256", "--", "-x", "-sbar" }; + var expected = new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = null, + BoolValue = false, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = new [] { "-x", "-sbar" }, + }; + + // Act + var result = sut.ParseArguments(args); + + // Assert + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + [Fact] + public void Getopt_mode_can_have_EnableDashDash_expicitly_disabled() + { + // Arrange + var sut = new Parser(config => { + config.GetoptMode = true; + config.PosixlyCorrect = false; + config.EnableDashDash = false; + }); + var args = new string [] {"--stringvalue", "foo", "256", "--", "-x", "-sbar" }; + var expected = new Simple_Options_WithExtraArgs { + StringValue = "foo", + ShortAndLong = "bar", + BoolValue = true, + LongValue = 256, + IntSequence = Enumerable.Empty(), + ExtraArgs = new [] { "--" }, + }; + + // Act + var result = sut.ParseArguments(args); + + // Assert + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + } +} diff --git a/tests/CommandLine.Tests/Unit/ParserTests.cs b/tests/CommandLine.Tests/Unit/ParserTests.cs index f8593cf3..b079ce0f 100644 --- a/tests/CommandLine.Tests/Unit/ParserTests.cs +++ b/tests/CommandLine.Tests/Unit/ParserTests.cs @@ -136,6 +136,7 @@ public void Parse_repeated_options_with_default_parser() // Verify outcome Assert.IsType>(result); + // NOTE: Once GetoptMode becomes the default, it will imply MultiInstance and the above check will fail because it will be Parsed. // Teardown } @@ -298,6 +299,7 @@ public void Parse_repeated_options_with_default_parser_in_verbs_scenario() // Verify outcome Assert.IsType>(result); + // NOTE: Once GetoptMode becomes the default, it will imply MultiInstance and the above check will fail because it will be Parsed. // Teardown } @@ -973,6 +975,7 @@ public void Parse_repeated_options_in_verbs_scenario_with_multi_instance() [Fact] public void Parse_repeated_options_in_verbs_scenario_without_multi_instance() { + // NOTE: Once GetoptMode becomes the default, it will imply MultiInstance and this test will fail because the parser result will be Parsed. using (var sut = new Parser(settings => settings.AllowMultiInstance = false)) { var longVal1 = 100; From 47618e0ddf43a380baaa54484b188118b30d8a6c Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 20 Aug 2020 17:43:18 +0700 Subject: [PATCH 183/198] Fix enumerable options grabbing too many values Fixes #687 Fixes #619 Fixes #617 Fixes #510 Fixes #454 Fixes #420 Fixes #396 Fixes #91 --- src/CommandLine/Core/GetoptTokenizer.cs | 35 +++-- src/CommandLine/Core/Token.cs | 34 ++++- src/CommandLine/Core/TokenPartitioner.cs | 20 +++ src/CommandLine/Core/Tokenizer.cs | 35 +++-- ...th_Sequence_Having_Separator_And_Values.cs | 74 +++++++++ .../Fakes/Options_With_Similar_Names.cs | 31 ++++ .../Unit/SequenceParsingTests.cs | 140 ++++++++++++++++++ 7 files changed, 339 insertions(+), 30 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Separator_And_Values.cs create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Similar_Names.cs create mode 100644 tests/CommandLine.Tests/Unit/SequenceParsingTests.cs diff --git a/src/CommandLine/Core/GetoptTokenizer.cs b/src/CommandLine/Core/GetoptTokenizer.cs index d9fe63fc..b8c97fc2 100644 --- a/src/CommandLine/Core/GetoptTokenizer.cs +++ b/src/CommandLine/Core/GetoptTokenizer.cs @@ -94,19 +94,28 @@ public static Result, Error> ExplodeOptionList( { var tokens = tokenizerResult.SucceededWith().Memoize(); - var replaces = tokens.Select((t, i) => - optionSequenceWithSeparatorLookup(t.Text) - .MapValueOrDefault(sep => Tuple.Create(i + 1, sep), - Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0).Memoize(); - - var exploded = tokens.Select((t, i) => - replaces.FirstOrDefault(x => x.Item1 == i).ToMaybe() - .MapValueOrDefault(r => t.Text.Split(r.Item2).Select(Token.Value), - Enumerable.Empty().Concat(new[] { t }))); - - var flattened = exploded.SelectMany(x => x); - - return Result.Succeed(flattened, tokenizerResult.SuccessMessages()); + var exploded = new List(tokens is ICollection coll ? coll.Count : tokens.Count()); + var nothing = Maybe.Nothing(); // Re-use same Nothing instance for efficiency + var separator = nothing; + foreach (var token in tokens) { + if (token.IsName()) { + separator = optionSequenceWithSeparatorLookup(token.Text); + exploded.Add(token); + } else { + // Forced values are never considered option values, so they should not be split + if (separator.MatchJust(out char sep) && sep != '\0' && !token.IsValueForced()) { + if (token.Text.Contains(sep)) { + exploded.AddRange(token.Text.Split(sep).Select(Token.ValueFromSeparator)); + } else { + exploded.Add(token); + } + } else { + exploded.Add(token); + } + separator = nothing; // Only first value after a separator can possibly be split + } + } + return Result.Succeed(exploded as IEnumerable, tokenizerResult.SuccessMessages()); } public static Func< diff --git a/src/CommandLine/Core/Token.cs b/src/CommandLine/Core/Token.cs index c8641bdd..90bbe54d 100644 --- a/src/CommandLine/Core/Token.cs +++ b/src/CommandLine/Core/Token.cs @@ -34,7 +34,12 @@ public static Token Value(string text, bool explicitlyAssigned) public static Token ValueForced(string text) { - return new Value(text, false, true); + return new Value(text, false, true, false); + } + + public static Token ValueFromSeparator(string text) + { + return new Value(text, false, false, true); } public TokenType Tag @@ -86,29 +91,45 @@ class Value : Token, IEquatable { private readonly bool explicitlyAssigned; private readonly bool forced; + private readonly bool fromSeparator; public Value(string text) - : this(text, false, false) + : this(text, false, false, false) { } public Value(string text, bool explicitlyAssigned) - : this(text, explicitlyAssigned, false) + : this(text, explicitlyAssigned, false, false) { } - public Value(string text, bool explicitlyAssigned, bool forced) + public Value(string text, bool explicitlyAssigned, bool forced, bool fromSeparator) : base(TokenType.Value, text) { this.explicitlyAssigned = explicitlyAssigned; this.forced = forced; + this.fromSeparator = fromSeparator; } + /// + /// Whether this value came from a long option with "=" separating the name from the value + /// public bool ExplicitlyAssigned { get { return explicitlyAssigned; } } + /// + /// Whether this value came from a sequence specified with a separator (e.g., "--files a.txt,b.txt,c.txt") + /// + public bool FromSeparator + { + get { return fromSeparator; } + } + + /// + /// Whether this value came from args after the -- separator (when EnableDashDash = true) + /// public bool Forced { get { return forced; } @@ -153,6 +174,11 @@ public static bool IsValue(this Token token) return token.Tag == TokenType.Value; } + public static bool IsValueFromSeparator(this Token token) + { + return token.IsValue() && ((Value)token).FromSeparator; + } + public static bool IsValueForced(this Token token) { return token.IsValue() && ((Value)token).Forced; diff --git a/src/CommandLine/Core/TokenPartitioner.cs b/src/CommandLine/Core/TokenPartitioner.cs index d6fe97e1..4dc25f7f 100644 --- a/src/CommandLine/Core/TokenPartitioner.cs +++ b/src/CommandLine/Core/TokenPartitioner.cs @@ -47,15 +47,18 @@ public static Tuple, IEnumerable, IEnumerable, var count = new Dictionary(); var max = new Dictionary>(); var state = SequenceState.TokenSearch; + var separatorSeen = false; Token nameToken = null; foreach (var token in tokens) { if (token.IsValueForced()) { + separatorSeen = false; nonOptionTokens.Add(token); } else if (token.IsName()) { + separatorSeen = false; if (typeLookup(token.Text).MatchJust(out var info)) { switch (info.TargetType) @@ -96,12 +99,14 @@ public static Tuple, IEnumerable, IEnumerable, case SequenceState.TokenSearch: case SequenceState.ScalarTokenFound when nameToken == null: case SequenceState.SequenceTokenFound when nameToken == null: + separatorSeen = false; nameToken = null; nonOptionTokens.Add(token); state = SequenceState.TokenSearch; break; case SequenceState.ScalarTokenFound: + separatorSeen = false; nameToken = null; scalarTokens.Add(token); state = SequenceState.TokenSearch; @@ -116,6 +121,20 @@ public static Tuple, IEnumerable, IEnumerable, nonOptionTokens.Add(token); state = SequenceState.TokenSearch; } + else if (token.IsValueFromSeparator()) + { + separatorSeen = true; + sequence.Add(token); + count[nameToken]++; + } + else if (separatorSeen) + { + // Previous token came from a separator but this one didn't: sequence is completed + separatorSeen = false; + nameToken = null; + nonOptionTokens.Add(token); + state = SequenceState.TokenSearch; + } else { sequence.Add(token); @@ -125,6 +144,7 @@ public static Tuple, IEnumerable, IEnumerable, else { // Should never get here, but just in case: + separatorSeen = false; sequences[nameToken] = new List(new[] { token }); count[nameToken] = 0; max[nameToken] = Maybe.Nothing(); diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index e35edc9d..b71ec7e2 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -62,19 +62,28 @@ public static Result, Error> ExplodeOptionList( { var tokens = tokenizerResult.SucceededWith().Memoize(); - var replaces = tokens.Select((t, i) => - optionSequenceWithSeparatorLookup(t.Text) - .MapValueOrDefault(sep => Tuple.Create(i + 1, sep), - Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0).Memoize(); - - var exploded = tokens.Select((t, i) => - replaces.FirstOrDefault(x => x.Item1 == i).ToMaybe() - .MapValueOrDefault(r => t.Text.Split(r.Item2).Select(Token.Value), - Enumerable.Empty().Concat(new[] { t }))); - - var flattened = exploded.SelectMany(x => x); - - return Result.Succeed(flattened, tokenizerResult.SuccessMessages()); + var exploded = new List(tokens is ICollection coll ? coll.Count : tokens.Count()); + var nothing = Maybe.Nothing(); // Re-use same Nothing instance for efficiency + var separator = nothing; + foreach (var token in tokens) { + if (token.IsName()) { + separator = optionSequenceWithSeparatorLookup(token.Text); + exploded.Add(token); + } else { + // Forced values are never considered option values, so they should not be split + if (separator.MatchJust(out char sep) && sep != '\0' && !token.IsValueForced()) { + if (token.Text.Contains(sep)) { + exploded.AddRange(token.Text.Split(sep).Select(Token.ValueFromSeparator)); + } else { + exploded.Add(token); + } + } else { + exploded.Add(token); + } + separator = nothing; // Only first value after a separator can possibly be split + } + } + return Result.Succeed(exploded as IEnumerable, tokenizerResult.SuccessMessages()); } public static IEnumerable Normalize( diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Separator_And_Values.cs b/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Separator_And_Values.cs new file mode 100644 index 00000000..6099b5b5 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Sequence_Having_Separator_And_Values.cs @@ -0,0 +1,74 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Options_For_Issue_91 + { + [Value(0, Required = true)] + public string InputFileName { get; set; } + + [Option('o', "output")] + public string OutputFileName { get; set; } + + [Option('i', "include", Separator = ',')] + public IEnumerable Included { get; set; } + + [Option('e', "exclude", Separator = ',')] + public IEnumerable Excluded { get; set; } + } + + public class Options_For_Issue_454 + { + [Option('c', "channels", Required = true, Separator = ':', HelpText = "Channel names")] + public IEnumerable Channels { get; set; } + + [Value(0, Required = true, MetaName = "file_path", HelpText = "Path of archive to be processed")] + public string ArchivePath { get; set; } + } + + public class Options_For_Issue_510 + { + [Option('a', "aa", Required = false, Separator = ',')] + public IEnumerable A { get; set; } + + [Option('b', "bb", Required = false)] + public string B { get; set; } + + [Value(0, Required = true)] + public string C { get; set; } + } + + public enum FMode { C, D, S }; + + public class Options_For_Issue_617 + { + [Option("fm", Separator=',', Default = new[] { FMode.S })] + public IEnumerable Mode { get; set; } + + [Option('q')] + public bool q { get;set; } + + [Value(0)] + public IList Files { get; set; } + } + + public class Options_For_Issue_619 + { + [Option("verbose", Required = false, Default = false, HelpText = "Generate process tracing information")] + public bool Verbose { get; set; } + + [Option("outdir", Required = false, Default = ".", HelpText = "Directory to look for object file")] + public string OutDir { get; set; } + + [Option("modules", Required = true, Separator = ',', HelpText = "Directories to look for module file")] + public IEnumerable ModuleDirs { get; set; } + + [Option("ignore", Required = false, Separator = ' ', HelpText = "List of additional module name references to ignore")] + public IEnumerable Ignores { get; set; } + + [Value(0, Required = true, HelpText = "List of source files to process")] + public IEnumerable Srcs { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Similar_Names.cs b/tests/CommandLine.Tests/Fakes/Options_With_Similar_Names.cs new file mode 100644 index 00000000..781d29cb --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Similar_Names.cs @@ -0,0 +1,31 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Similar_Names + { + [Option("deploy", Separator = ',', HelpText= "Projects to deploy")] + public IEnumerable Deploys { get; set; } + + [Option("profile", Required = true, HelpText = "Profile to use when restoring and publishing")] + public string Profile { get; set; } + + [Option("configure-profile", Required = true, HelpText = "Profile to use for Configure")] + public string ConfigureProfile { get; set; } + } + + public class Options_With_Similar_Names_And_Separator + { + [Option('f', "flag", HelpText = "Flag")] + public bool Flag { get; set; } + + [Option('c', "categories", Required = false, Separator = ',', HelpText = "Categories")] + public IEnumerable Categories { get; set; } + + [Option('j', "jobId", Required = true, HelpText = "Texts.ExplainJob")] + public int JobId { get; set; } + } + +} diff --git a/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs b/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs new file mode 100644 index 00000000..cb65e42f --- /dev/null +++ b/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Linq; +using System; +using Xunit; +using CommandLine.Text; +using CommandLine.Tests.Fakes; +using FluentAssertions; +using CommandLine.Core; +using System.Reflection; +using CSharpx; +using RailwaySharp.ErrorHandling; + +namespace CommandLine.Tests.Unit +{ + // Reference: PR #684 + public class SequenceParsingTests + { + // Issue #91 + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Enumerable_with_separator_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + { + var args = "--exclude=a,b InputFile.txt".Split(); + var expected = new Options_For_Issue_91 { + Excluded = new[] { "a", "b" }, + Included = Enumerable.Empty(), + InputFileName = "InputFile.txt", + }; + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var result = sut.ParseArguments(args); + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + // Issue #396 + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void Options_with_similar_names_are_not_ambiguous(bool useGetoptMode) + { + var args = new[] { "--configure-profile", "deploy", "--profile", "local" }; + var expected = new Options_With_Similar_Names { ConfigureProfile = "deploy", Profile = "local", Deploys = Enumerable.Empty() }; + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var result = sut.ParseArguments(args); + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + // Issue #420 + [Fact] + + public static void Values_with_same_name_as_sequence_option_do_not_cause_later_values_to_split_on_separators() + { + var args = new[] { "c", "x,y" }; + var tokensExpected = new[] { Token.Value("c"), Token.Value("x,y") }; + var typeInfo = typeof(Options_With_Similar_Names_And_Separator); + + var specProps = typeInfo.GetSpecifications(pi => SpecificationProperty.Create( + Specification.FromProperty(pi), pi, Maybe.Nothing())) + .Select(sp => sp.Specification) + .OfType(); + + var tokenizerResult = Tokenizer.ConfigureTokenizer(StringComparer.InvariantCulture, false, false)(args, specProps); + var tokens = tokenizerResult.SucceededWith(); + tokens.Should().BeEquivalentTo(tokensExpected); + } + + // Issue #454 + [Theory] + [InlineData(false)] + [InlineData(true)] + + public static void Enumerable_with_colon_separator_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + { + var args = "-c chanA:chanB file.hdf5".Split(); + var expected = new Options_For_Issue_454 { + Channels = new[] { "chanA", "chanB" }, + ArchivePath = "file.hdf5", + }; + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var result = sut.ParseArguments(args); + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + // Issue #510 + [Theory] + [InlineData(false)] + [InlineData(true)] + + public static void Enumerable_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + { + var args = new[] { "-a", "1,2", "c" }; + var expected = new Options_For_Issue_510 { A = new[] { "1", "2" }, C = "c" }; + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var result = sut.ParseArguments(args); + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + // Issue #617 + [Theory] + [InlineData(false)] + [InlineData(true)] + + public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + { + var args = "--fm D,C a.txt".Split(); + var expected = new Options_For_Issue_617 { + Mode = new[] { FMode.D, FMode.C }, + Files = new[] { "a.txt" }, + }; + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var result = sut.ParseArguments(args); + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + + // Issue #619 + [Theory] + [InlineData(false)] + [InlineData(true)] + + public static void Separator_just_before_values_does_not_try_to_parse_values(bool useGetoptMode) + { + var args = "--outdir ./x64/Debug --modules ../utilities/x64/Debug,../auxtool/x64/Debug m_xfunit.f03 m_xfunit_assertion.f03".Split(); + var expected = new Options_For_Issue_619 { + OutDir = "./x64/Debug", + ModuleDirs = new[] { "../utilities/x64/Debug", "../auxtool/x64/Debug" }, + Ignores = Enumerable.Empty(), + Srcs = new[] { "m_xfunit.f03", "m_xfunit_assertion.f03" }, + }; + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var result = sut.ParseArguments(args); + result.Should().BeOfType>(); + result.As>().Value.Should().BeEquivalentTo(expected); + } + } +} From b7102d89a90acd2d9356637ead5a0a64b58b131a Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 21 Aug 2020 08:10:39 +0700 Subject: [PATCH 184/198] Address review comments * Share common code between Tokenizer and GetoptTokenizer * Use enum for ParserMode as we might add more in the future --- src/CommandLine/Core/GetoptTokenizer.cs | 32 +------------ src/CommandLine/Parser.cs | 20 ++++++-- src/CommandLine/ParserSettings.cs | 45 +++++++++++++---- .../Unit/Core/GetoptTokenizerTests.cs | 6 +-- .../Unit/GetoptParserTests.cs | 8 ++-- .../Unit/SequenceParsingTests.cs | 48 +++++++++---------- 6 files changed, 83 insertions(+), 76 deletions(-) diff --git a/src/CommandLine/Core/GetoptTokenizer.cs b/src/CommandLine/Core/GetoptTokenizer.cs index b8c97fc2..1d97125f 100644 --- a/src/CommandLine/Core/GetoptTokenizer.cs +++ b/src/CommandLine/Core/GetoptTokenizer.cs @@ -88,36 +88,6 @@ public static Result, Error> Tokenize( return Result.Succeed, Error>(tokens.AsEnumerable(), errors.AsEnumerable()); } - public static Result, Error> ExplodeOptionList( - Result, Error> tokenizerResult, - Func> optionSequenceWithSeparatorLookup) - { - var tokens = tokenizerResult.SucceededWith().Memoize(); - - var exploded = new List(tokens is ICollection coll ? coll.Count : tokens.Count()); - var nothing = Maybe.Nothing(); // Re-use same Nothing instance for efficiency - var separator = nothing; - foreach (var token in tokens) { - if (token.IsName()) { - separator = optionSequenceWithSeparatorLookup(token.Text); - exploded.Add(token); - } else { - // Forced values are never considered option values, so they should not be split - if (separator.MatchJust(out char sep) && sep != '\0' && !token.IsValueForced()) { - if (token.Text.Contains(sep)) { - exploded.AddRange(token.Text.Split(sep).Select(Token.ValueFromSeparator)); - } else { - exploded.Add(token); - } - } else { - exploded.Add(token); - } - separator = nothing; // Only first value after a separator can possibly be split - } - } - return Result.Succeed(exploded as IEnumerable, tokenizerResult.SuccessMessages()); - } - public static Func< IEnumerable, IEnumerable, @@ -131,7 +101,7 @@ public static Func< return (arguments, optionSpecs) => { var tokens = GetoptTokenizer.Tokenize(arguments, name => NameLookup.Contains(name, optionSpecs, nameComparer), ignoreUnknownArguments, enableDashDash, posixlyCorrect); - var explodedTokens = GetoptTokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer)); + var explodedTokens = Tokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer)); return explodedTokens; }; } diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 4301aa52..44953c58 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -185,16 +185,28 @@ private static Result, Error> Tokenize( IEnumerable optionSpecs, ParserSettings settings) { - return settings.GetoptMode - ? GetoptTokenizer.ConfigureTokenizer( + switch (settings.ParserMode) + { + case ParserMode.Legacy: + return Tokenizer.ConfigureTokenizer( + settings.NameComparer, + settings.IgnoreUnknownArguments, + settings.EnableDashDash)(arguments, optionSpecs); + + case ParserMode.Getopt: + return GetoptTokenizer.ConfigureTokenizer( settings.NameComparer, settings.IgnoreUnknownArguments, settings.EnableDashDash, - settings.PosixlyCorrect)(arguments, optionSpecs) - : Tokenizer.ConfigureTokenizer( + settings.PosixlyCorrect)(arguments, optionSpecs); + + // No need to test ParserMode.Default, as it should always be one of the above modes + default: + return Tokenizer.ConfigureTokenizer( settings.NameComparer, settings.IgnoreUnknownArguments, settings.EnableDashDash)(arguments, optionSpecs); + } } private static ParserResult MakeParserResult(ParserResult parserResult, ParserSettings settings) diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index 5ed73f30..c1b1ca77 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -9,6 +9,14 @@ namespace CommandLine { + public enum ParserMode + { + Legacy, + Getopt, + + Default = Legacy + } + /// /// Provides settings for . Once consumed cannot be reused. /// @@ -27,7 +35,7 @@ public class ParserSettings : IDisposable private Maybe enableDashDash; private int maximumDisplayWidth; private Maybe allowMultiInstance; - private bool getoptMode; + private ParserMode parserMode; private Maybe posixlyCorrect; /// @@ -41,7 +49,7 @@ public ParserSettings() autoVersion = true; parsingCulture = CultureInfo.InvariantCulture; maximumDisplayWidth = GetWindowWidth(); - getoptMode = false; + parserMode = ParserMode.Default; enableDashDash = Maybe.Nothing(); allowMultiInstance = Maybe.Nothing(); posixlyCorrect = Maybe.Nothing(); @@ -166,11 +174,11 @@ public bool AutoVersion /// /// Gets or sets a value indicating whether enable double dash '--' syntax, /// that forces parsing of all subsequent tokens as values. - /// If GetoptMode is true, this defaults to true, but can be turned off by explicitly specifying EnableDashDash = false. + /// Normally defaults to false. If ParserMode = ParserMode.Getopt, this defaults to true, but can be turned off by explicitly specifying EnableDashDash = false. /// public bool EnableDashDash { - get => enableDashDash.MatchJust(out bool value) ? value : getoptMode; + get => enableDashDash.MatchJust(out bool value) ? value : (parserMode == ParserMode.Getopt); set => PopsicleSetter.Set(Consumed, ref enableDashDash, Maybe.Just(value)); } @@ -185,21 +193,38 @@ public int MaximumDisplayWidth /// /// Gets or sets a value indicating whether options are allowed to be specified multiple times. - /// If GetoptMode is true, this defaults to true, but can be turned off by explicitly specifying AllowMultiInstance = false. + /// If ParserMode = ParserMode.Getopt, this defaults to true, but can be turned off by explicitly specifying AllowMultiInstance = false. /// public bool AllowMultiInstance { - get => allowMultiInstance.MatchJust(out bool value) ? value : getoptMode; + get => allowMultiInstance.MatchJust(out bool value) ? value : (parserMode == ParserMode.Getopt); set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, Maybe.Just(value)); } /// - /// Whether strict getopt-like processing is applied to option values; if true, AllowMultiInstance and EnableDashDash will default to true as well. + /// Set this to change how the parser processes command-line arguments. Currently valid values are: + /// + /// + /// Legacy + /// Uses - for short options and -- for long options. + /// Values of long options can only start with a - character if the = syntax is used. + /// E.g., "--string-option -x" will consider "-x" to be an option, not the value of "--string-option", + /// but "--string-option=-x" will consider "-x" to be the value of "--string-option". + /// + /// + /// Getopt + /// Strict getopt-like processing is applied to option values. + /// Mostly like legacy mode, except that option values with = and with space are more consistent. + /// After an option that takes a value, and whose value was not specified with "=", the next argument will be considered the value even if it starts with "-". + /// E.g., both "--string-option=-x" and "--string-option -x" will consider "-x" to be the value of "--string-option". + /// If this mode is chosen, AllowMultiInstance and EnableDashDash will default to true as well, though they can be explicitly turned off if desired. + /// + /// /// - public bool GetoptMode + public ParserMode ParserMode { - get => getoptMode; - set => PopsicleSetter.Set(Consumed, ref getoptMode, value); + get => parserMode; + set => PopsicleSetter.Set(Consumed, ref parserMode, value); } /// diff --git a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs index 337a9a3f..d4669a66 100644 --- a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs @@ -24,7 +24,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() // Exercize system var result = - GetoptTokenizer.ExplodeOptionList( + Tokenizer.ExplodeOptionList( Result.Succeed( Enumerable.Empty().Concat(new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa,bb,cccc"), Token.Name("switch") }), @@ -47,7 +47,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() // Exercize system var result = - GetoptTokenizer.ExplodeOptionList( + Tokenizer.ExplodeOptionList( Result.Succeed( Enumerable.Empty().Concat(new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa,bb,cccc"), Token.Name("switch") }), @@ -90,7 +90,7 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() var errors = result.SuccessMessages(); Assert.NotNull(errors); - Assert.Equal(1, errors.Count()); + Assert.NotEmpty(errors); Assert.Equal(ErrorType.BadFormatTokenError, errors.First().Tag); var tokens = result.SucceededWith(); diff --git a/tests/CommandLine.Tests/Unit/GetoptParserTests.cs b/tests/CommandLine.Tests/Unit/GetoptParserTests.cs index cd2a1577..94bc94df 100644 --- a/tests/CommandLine.Tests/Unit/GetoptParserTests.cs +++ b/tests/CommandLine.Tests/Unit/GetoptParserTests.cs @@ -155,7 +155,7 @@ public void Getopt_parser_without_posixly_correct_allows_mixed_options_and_nonop { // Arrange var sut = new Parser(config => { - config.GetoptMode = true; + config.ParserMode = ParserMode.Getopt; config.PosixlyCorrect = false; }); @@ -215,7 +215,7 @@ public void Getopt_parser_with_posixly_correct_stops_parsing_at_first_nonoption( { // Arrange var sut = new Parser(config => { - config.GetoptMode = true; + config.ParserMode = ParserMode.Getopt; config.PosixlyCorrect = true; config.EnableDashDash = true; }); @@ -233,7 +233,7 @@ public void Getopt_mode_defaults_to_EnableDashDash_being_true() { // Arrange var sut = new Parser(config => { - config.GetoptMode = true; + config.ParserMode = ParserMode.Getopt; config.PosixlyCorrect = false; }); var args = new string [] {"--stringvalue", "foo", "256", "--", "-x", "-sbar" }; @@ -259,7 +259,7 @@ public void Getopt_mode_can_have_EnableDashDash_expicitly_disabled() { // Arrange var sut = new Parser(config => { - config.GetoptMode = true; + config.ParserMode = ParserMode.Getopt; config.PosixlyCorrect = false; config.EnableDashDash = false; }); diff --git a/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs b/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs index cb65e42f..a498af6f 100644 --- a/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs +++ b/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs @@ -17,9 +17,9 @@ public class SequenceParsingTests { // Issue #91 [Theory] - [InlineData(false)] - [InlineData(true)] - public static void Enumerable_with_separator_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + [InlineData(ParserMode.Legacy)] + [InlineData(ParserMode.Getopt)] + public static void Enumerable_with_separator_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) { var args = "--exclude=a,b InputFile.txt".Split(); var expected = new Options_For_Issue_91 { @@ -27,7 +27,7 @@ public static void Enumerable_with_separator_before_values_does_not_try_to_parse Included = Enumerable.Empty(), InputFileName = "InputFile.txt", }; - var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -35,13 +35,13 @@ public static void Enumerable_with_separator_before_values_does_not_try_to_parse // Issue #396 [Theory] - [InlineData(false)] - [InlineData(true)] - public static void Options_with_similar_names_are_not_ambiguous(bool useGetoptMode) + [InlineData(ParserMode.Legacy)] + [InlineData(ParserMode.Getopt)] + public static void Options_with_similar_names_are_not_ambiguous(ParserMode parserMode) { var args = new[] { "--configure-profile", "deploy", "--profile", "local" }; var expected = new Options_With_Similar_Names { ConfigureProfile = "deploy", Profile = "local", Deploys = Enumerable.Empty() }; - var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -68,17 +68,17 @@ public static void Values_with_same_name_as_sequence_option_do_not_cause_later_v // Issue #454 [Theory] - [InlineData(false)] - [InlineData(true)] + [InlineData(ParserMode.Legacy)] + [InlineData(ParserMode.Getopt)] - public static void Enumerable_with_colon_separator_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + public static void Enumerable_with_colon_separator_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) { var args = "-c chanA:chanB file.hdf5".Split(); var expected = new Options_For_Issue_454 { Channels = new[] { "chanA", "chanB" }, ArchivePath = "file.hdf5", }; - var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -86,14 +86,14 @@ public static void Enumerable_with_colon_separator_before_values_does_not_try_to // Issue #510 [Theory] - [InlineData(false)] - [InlineData(true)] + [InlineData(ParserMode.Legacy)] + [InlineData(ParserMode.Getopt)] - public static void Enumerable_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + public static void Enumerable_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) { var args = new[] { "-a", "1,2", "c" }; var expected = new Options_For_Issue_510 { A = new[] { "1", "2" }, C = "c" }; - var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -101,17 +101,17 @@ public static void Enumerable_before_values_does_not_try_to_parse_too_much(bool // Issue #617 [Theory] - [InlineData(false)] - [InlineData(true)] + [InlineData(ParserMode.Legacy)] + [InlineData(ParserMode.Getopt)] - public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) + public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) { var args = "--fm D,C a.txt".Split(); var expected = new Options_For_Issue_617 { Mode = new[] { FMode.D, FMode.C }, Files = new[] { "a.txt" }, }; - var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -119,10 +119,10 @@ public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_ // Issue #619 [Theory] - [InlineData(false)] - [InlineData(true)] + [InlineData(ParserMode.Legacy)] + [InlineData(ParserMode.Getopt)] - public static void Separator_just_before_values_does_not_try_to_parse_values(bool useGetoptMode) + public static void Separator_just_before_values_does_not_try_to_parse_values(ParserMode parserMode) { var args = "--outdir ./x64/Debug --modules ../utilities/x64/Debug,../auxtool/x64/Debug m_xfunit.f03 m_xfunit_assertion.f03".Split(); var expected = new Options_For_Issue_619 { @@ -131,7 +131,7 @@ public static void Separator_just_before_values_does_not_try_to_parse_values(boo Ignores = Enumerable.Empty(), Srcs = new[] { "m_xfunit.f03", "m_xfunit_assertion.f03" }, }; - var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); + var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); From 34ab560ba5af72e1124bd2e75840f69f9548a38a Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 25 Aug 2020 17:20:21 +0300 Subject: [PATCH 185/198] Revert "Address review comments" This reverts commit b7102d89a90acd2d9356637ead5a0a64b58b131a. --- src/CommandLine/Core/GetoptTokenizer.cs | 32 ++++++++++++- src/CommandLine/Parser.cs | 20 ++------ src/CommandLine/ParserSettings.cs | 45 ++++------------- .../Unit/Core/GetoptTokenizerTests.cs | 6 +-- .../Unit/GetoptParserTests.cs | 8 ++-- .../Unit/SequenceParsingTests.cs | 48 +++++++++---------- 6 files changed, 76 insertions(+), 83 deletions(-) diff --git a/src/CommandLine/Core/GetoptTokenizer.cs b/src/CommandLine/Core/GetoptTokenizer.cs index 1d97125f..b8c97fc2 100644 --- a/src/CommandLine/Core/GetoptTokenizer.cs +++ b/src/CommandLine/Core/GetoptTokenizer.cs @@ -88,6 +88,36 @@ public static Result, Error> Tokenize( return Result.Succeed, Error>(tokens.AsEnumerable(), errors.AsEnumerable()); } + public static Result, Error> ExplodeOptionList( + Result, Error> tokenizerResult, + Func> optionSequenceWithSeparatorLookup) + { + var tokens = tokenizerResult.SucceededWith().Memoize(); + + var exploded = new List(tokens is ICollection coll ? coll.Count : tokens.Count()); + var nothing = Maybe.Nothing(); // Re-use same Nothing instance for efficiency + var separator = nothing; + foreach (var token in tokens) { + if (token.IsName()) { + separator = optionSequenceWithSeparatorLookup(token.Text); + exploded.Add(token); + } else { + // Forced values are never considered option values, so they should not be split + if (separator.MatchJust(out char sep) && sep != '\0' && !token.IsValueForced()) { + if (token.Text.Contains(sep)) { + exploded.AddRange(token.Text.Split(sep).Select(Token.ValueFromSeparator)); + } else { + exploded.Add(token); + } + } else { + exploded.Add(token); + } + separator = nothing; // Only first value after a separator can possibly be split + } + } + return Result.Succeed(exploded as IEnumerable, tokenizerResult.SuccessMessages()); + } + public static Func< IEnumerable, IEnumerable, @@ -101,7 +131,7 @@ public static Func< return (arguments, optionSpecs) => { var tokens = GetoptTokenizer.Tokenize(arguments, name => NameLookup.Contains(name, optionSpecs, nameComparer), ignoreUnknownArguments, enableDashDash, posixlyCorrect); - var explodedTokens = Tokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer)); + var explodedTokens = GetoptTokenizer.ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer)); return explodedTokens; }; } diff --git a/src/CommandLine/Parser.cs b/src/CommandLine/Parser.cs index 44953c58..4301aa52 100644 --- a/src/CommandLine/Parser.cs +++ b/src/CommandLine/Parser.cs @@ -185,28 +185,16 @@ private static Result, Error> Tokenize( IEnumerable optionSpecs, ParserSettings settings) { - switch (settings.ParserMode) - { - case ParserMode.Legacy: - return Tokenizer.ConfigureTokenizer( - settings.NameComparer, - settings.IgnoreUnknownArguments, - settings.EnableDashDash)(arguments, optionSpecs); - - case ParserMode.Getopt: - return GetoptTokenizer.ConfigureTokenizer( + return settings.GetoptMode + ? GetoptTokenizer.ConfigureTokenizer( settings.NameComparer, settings.IgnoreUnknownArguments, settings.EnableDashDash, - settings.PosixlyCorrect)(arguments, optionSpecs); - - // No need to test ParserMode.Default, as it should always be one of the above modes - default: - return Tokenizer.ConfigureTokenizer( + settings.PosixlyCorrect)(arguments, optionSpecs) + : Tokenizer.ConfigureTokenizer( settings.NameComparer, settings.IgnoreUnknownArguments, settings.EnableDashDash)(arguments, optionSpecs); - } } private static ParserResult MakeParserResult(ParserResult parserResult, ParserSettings settings) diff --git a/src/CommandLine/ParserSettings.cs b/src/CommandLine/ParserSettings.cs index c1b1ca77..5ed73f30 100644 --- a/src/CommandLine/ParserSettings.cs +++ b/src/CommandLine/ParserSettings.cs @@ -9,14 +9,6 @@ namespace CommandLine { - public enum ParserMode - { - Legacy, - Getopt, - - Default = Legacy - } - /// /// Provides settings for . Once consumed cannot be reused. /// @@ -35,7 +27,7 @@ public class ParserSettings : IDisposable private Maybe enableDashDash; private int maximumDisplayWidth; private Maybe allowMultiInstance; - private ParserMode parserMode; + private bool getoptMode; private Maybe posixlyCorrect; /// @@ -49,7 +41,7 @@ public ParserSettings() autoVersion = true; parsingCulture = CultureInfo.InvariantCulture; maximumDisplayWidth = GetWindowWidth(); - parserMode = ParserMode.Default; + getoptMode = false; enableDashDash = Maybe.Nothing(); allowMultiInstance = Maybe.Nothing(); posixlyCorrect = Maybe.Nothing(); @@ -174,11 +166,11 @@ public bool AutoVersion /// /// Gets or sets a value indicating whether enable double dash '--' syntax, /// that forces parsing of all subsequent tokens as values. - /// Normally defaults to false. If ParserMode = ParserMode.Getopt, this defaults to true, but can be turned off by explicitly specifying EnableDashDash = false. + /// If GetoptMode is true, this defaults to true, but can be turned off by explicitly specifying EnableDashDash = false. /// public bool EnableDashDash { - get => enableDashDash.MatchJust(out bool value) ? value : (parserMode == ParserMode.Getopt); + get => enableDashDash.MatchJust(out bool value) ? value : getoptMode; set => PopsicleSetter.Set(Consumed, ref enableDashDash, Maybe.Just(value)); } @@ -193,38 +185,21 @@ public int MaximumDisplayWidth /// /// Gets or sets a value indicating whether options are allowed to be specified multiple times. - /// If ParserMode = ParserMode.Getopt, this defaults to true, but can be turned off by explicitly specifying AllowMultiInstance = false. + /// If GetoptMode is true, this defaults to true, but can be turned off by explicitly specifying AllowMultiInstance = false. /// public bool AllowMultiInstance { - get => allowMultiInstance.MatchJust(out bool value) ? value : (parserMode == ParserMode.Getopt); + get => allowMultiInstance.MatchJust(out bool value) ? value : getoptMode; set => PopsicleSetter.Set(Consumed, ref allowMultiInstance, Maybe.Just(value)); } /// - /// Set this to change how the parser processes command-line arguments. Currently valid values are: - /// - /// - /// Legacy - /// Uses - for short options and -- for long options. - /// Values of long options can only start with a - character if the = syntax is used. - /// E.g., "--string-option -x" will consider "-x" to be an option, not the value of "--string-option", - /// but "--string-option=-x" will consider "-x" to be the value of "--string-option". - /// - /// - /// Getopt - /// Strict getopt-like processing is applied to option values. - /// Mostly like legacy mode, except that option values with = and with space are more consistent. - /// After an option that takes a value, and whose value was not specified with "=", the next argument will be considered the value even if it starts with "-". - /// E.g., both "--string-option=-x" and "--string-option -x" will consider "-x" to be the value of "--string-option". - /// If this mode is chosen, AllowMultiInstance and EnableDashDash will default to true as well, though they can be explicitly turned off if desired. - /// - /// + /// Whether strict getopt-like processing is applied to option values; if true, AllowMultiInstance and EnableDashDash will default to true as well. /// - public ParserMode ParserMode + public bool GetoptMode { - get => parserMode; - set => PopsicleSetter.Set(Consumed, ref parserMode, value); + get => getoptMode; + set => PopsicleSetter.Set(Consumed, ref getoptMode, value); } /// diff --git a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs index d4669a66..337a9a3f 100644 --- a/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/GetoptTokenizerTests.cs @@ -24,7 +24,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() // Exercize system var result = - Tokenizer.ExplodeOptionList( + GetoptTokenizer.ExplodeOptionList( Result.Succeed( Enumerable.Empty().Concat(new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa,bb,cccc"), Token.Name("switch") }), @@ -47,7 +47,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() // Exercize system var result = - Tokenizer.ExplodeOptionList( + GetoptTokenizer.ExplodeOptionList( Result.Succeed( Enumerable.Empty().Concat(new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa,bb,cccc"), Token.Name("switch") }), @@ -90,7 +90,7 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() var errors = result.SuccessMessages(); Assert.NotNull(errors); - Assert.NotEmpty(errors); + Assert.Equal(1, errors.Count()); Assert.Equal(ErrorType.BadFormatTokenError, errors.First().Tag); var tokens = result.SucceededWith(); diff --git a/tests/CommandLine.Tests/Unit/GetoptParserTests.cs b/tests/CommandLine.Tests/Unit/GetoptParserTests.cs index 94bc94df..cd2a1577 100644 --- a/tests/CommandLine.Tests/Unit/GetoptParserTests.cs +++ b/tests/CommandLine.Tests/Unit/GetoptParserTests.cs @@ -155,7 +155,7 @@ public void Getopt_parser_without_posixly_correct_allows_mixed_options_and_nonop { // Arrange var sut = new Parser(config => { - config.ParserMode = ParserMode.Getopt; + config.GetoptMode = true; config.PosixlyCorrect = false; }); @@ -215,7 +215,7 @@ public void Getopt_parser_with_posixly_correct_stops_parsing_at_first_nonoption( { // Arrange var sut = new Parser(config => { - config.ParserMode = ParserMode.Getopt; + config.GetoptMode = true; config.PosixlyCorrect = true; config.EnableDashDash = true; }); @@ -233,7 +233,7 @@ public void Getopt_mode_defaults_to_EnableDashDash_being_true() { // Arrange var sut = new Parser(config => { - config.ParserMode = ParserMode.Getopt; + config.GetoptMode = true; config.PosixlyCorrect = false; }); var args = new string [] {"--stringvalue", "foo", "256", "--", "-x", "-sbar" }; @@ -259,7 +259,7 @@ public void Getopt_mode_can_have_EnableDashDash_expicitly_disabled() { // Arrange var sut = new Parser(config => { - config.ParserMode = ParserMode.Getopt; + config.GetoptMode = true; config.PosixlyCorrect = false; config.EnableDashDash = false; }); diff --git a/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs b/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs index a498af6f..cb65e42f 100644 --- a/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs +++ b/tests/CommandLine.Tests/Unit/SequenceParsingTests.cs @@ -17,9 +17,9 @@ public class SequenceParsingTests { // Issue #91 [Theory] - [InlineData(ParserMode.Legacy)] - [InlineData(ParserMode.Getopt)] - public static void Enumerable_with_separator_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) + [InlineData(false)] + [InlineData(true)] + public static void Enumerable_with_separator_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) { var args = "--exclude=a,b InputFile.txt".Split(); var expected = new Options_For_Issue_91 { @@ -27,7 +27,7 @@ public static void Enumerable_with_separator_before_values_does_not_try_to_parse Included = Enumerable.Empty(), InputFileName = "InputFile.txt", }; - var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -35,13 +35,13 @@ public static void Enumerable_with_separator_before_values_does_not_try_to_parse // Issue #396 [Theory] - [InlineData(ParserMode.Legacy)] - [InlineData(ParserMode.Getopt)] - public static void Options_with_similar_names_are_not_ambiguous(ParserMode parserMode) + [InlineData(false)] + [InlineData(true)] + public static void Options_with_similar_names_are_not_ambiguous(bool useGetoptMode) { var args = new[] { "--configure-profile", "deploy", "--profile", "local" }; var expected = new Options_With_Similar_Names { ConfigureProfile = "deploy", Profile = "local", Deploys = Enumerable.Empty() }; - var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -68,17 +68,17 @@ public static void Values_with_same_name_as_sequence_option_do_not_cause_later_v // Issue #454 [Theory] - [InlineData(ParserMode.Legacy)] - [InlineData(ParserMode.Getopt)] + [InlineData(false)] + [InlineData(true)] - public static void Enumerable_with_colon_separator_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) + public static void Enumerable_with_colon_separator_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) { var args = "-c chanA:chanB file.hdf5".Split(); var expected = new Options_For_Issue_454 { Channels = new[] { "chanA", "chanB" }, ArchivePath = "file.hdf5", }; - var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -86,14 +86,14 @@ public static void Enumerable_with_colon_separator_before_values_does_not_try_to // Issue #510 [Theory] - [InlineData(ParserMode.Legacy)] - [InlineData(ParserMode.Getopt)] + [InlineData(false)] + [InlineData(true)] - public static void Enumerable_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) + public static void Enumerable_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) { var args = new[] { "-a", "1,2", "c" }; var expected = new Options_For_Issue_510 { A = new[] { "1", "2" }, C = "c" }; - var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -101,17 +101,17 @@ public static void Enumerable_before_values_does_not_try_to_parse_too_much(Parse // Issue #617 [Theory] - [InlineData(ParserMode.Legacy)] - [InlineData(ParserMode.Getopt)] + [InlineData(false)] + [InlineData(true)] - public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_much(ParserMode parserMode) + public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_much(bool useGetoptMode) { var args = "--fm D,C a.txt".Split(); var expected = new Options_For_Issue_617 { Mode = new[] { FMode.D, FMode.C }, Files = new[] { "a.txt" }, }; - var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); @@ -119,10 +119,10 @@ public static void Enumerable_with_enum_before_values_does_not_try_to_parse_too_ // Issue #619 [Theory] - [InlineData(ParserMode.Legacy)] - [InlineData(ParserMode.Getopt)] + [InlineData(false)] + [InlineData(true)] - public static void Separator_just_before_values_does_not_try_to_parse_values(ParserMode parserMode) + public static void Separator_just_before_values_does_not_try_to_parse_values(bool useGetoptMode) { var args = "--outdir ./x64/Debug --modules ../utilities/x64/Debug,../auxtool/x64/Debug m_xfunit.f03 m_xfunit_assertion.f03".Split(); var expected = new Options_For_Issue_619 { @@ -131,7 +131,7 @@ public static void Separator_just_before_values_does_not_try_to_parse_values(Par Ignores = Enumerable.Empty(), Srcs = new[] { "m_xfunit.f03", "m_xfunit_assertion.f03" }, }; - var sut = new Parser(parserSettings => { parserSettings.ParserMode = parserMode; }); + var sut = new Parser(parserSettings => { parserSettings.GetoptMode = useGetoptMode; }); var result = sut.ParseArguments(args); result.Should().BeOfType>(); result.As>().Value.Should().BeEquivalentTo(expected); From 2e1fff3657ef52a1633d9586edd866fca07e0538 Mon Sep 17 00:00:00 2001 From: Misha Gorshenin Date: Sat, 22 May 2021 22:23:01 +0500 Subject: [PATCH 186/198] Add cast support for resource types --- .gitignore | 1 + src/CommandLine/CastExtensions.cs | 100 ++++++++++++++++++ src/CommandLine/CommandLine.csproj | 1 + .../LocalizableAttributeProperty.cs | 12 +-- .../CommandLine.Tests/Fakes/ResourceFakes.cs | 63 +++++++++++ .../Unit/BaseAttributeTests.cs | 10 +- 6 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 src/CommandLine/CastExtensions.cs diff --git a/.gitignore b/.gitignore index 55f900a1..7cccb220 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,6 @@ artifacts/* *.DotSettings.user # Visual Studio 2015 cache/options directory .vs/ +.idea/ [R|r]elease/** diff --git a/src/CommandLine/CastExtensions.cs b/src/CommandLine/CastExtensions.cs new file mode 100644 index 00000000..828a18e4 --- /dev/null +++ b/src/CommandLine/CastExtensions.cs @@ -0,0 +1,100 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace CommandLine +{ + public static class CastExtensions + { + private const string ImplicitCastMethodName = "op_Implicit"; + private const string ExplicitCastMethodName = "op_Explicit"; + + public static bool CanCast(this Type baseType) + { + return baseType.CanImplicitCast() || baseType.CanExplicitCast(); + } + + public static bool CanCast(this object obj) + { + var objType = obj.GetType(); + return objType.CanCast(); + } + + public static T Cast(this object obj) + { + try + { + return (T) obj; + } + catch (InvalidCastException) + { + if (obj.CanImplicitCast()) + return obj.ImplicitCast(); + if (obj.CanExplicitCast()) + return obj.ExplicitCast(); + else + throw; + } + } + + private static bool CanImplicitCast(this Type baseType) + { + return baseType.CanCast(ImplicitCastMethodName); + } + + private static bool CanImplicitCast(this object obj) + { + var baseType = obj.GetType(); + return baseType.CanImplicitCast(); + } + + private static bool CanExplicitCast(this Type baseType) + { + return baseType.CanCast(ExplicitCastMethodName); + } + + private static bool CanExplicitCast(this object obj) + { + var baseType = obj.GetType(); + return baseType.CanExplicitCast(); + } + + private static bool CanCast(this Type baseType, string castMethodName) + { + var targetType = typeof(T); + return baseType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(mi => mi.Name == castMethodName && mi.ReturnType == targetType) + .Any(mi => + { + ParameterInfo pi = mi.GetParameters().FirstOrDefault(); + return pi != null && pi.ParameterType == baseType; + }); + } + + private static T ImplicitCast(this object obj) + { + return obj.Cast(ImplicitCastMethodName); + } + + private static T ExplicitCast(this object obj) + { + return obj.Cast(ExplicitCastMethodName); + } + + private static T Cast(this object obj, string castMethodName) + { + var objType = obj.GetType(); + MethodInfo conversionMethod = objType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(mi => mi.Name == castMethodName && mi.ReturnType == typeof(T)) + .SingleOrDefault(mi => + { + ParameterInfo pi = mi.GetParameters().FirstOrDefault(); + return pi != null && pi.ParameterType == objType; + }); + if (conversionMethod != null) + return (T) conversionMethod.Invoke(null, new[] {obj}); + else + throw new InvalidCastException($"No method to cast {objType.FullName} to {typeof(T).FullName}"); + } + } +} diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index 04496eb8..b2e65413 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -27,6 +27,7 @@ 8.0 true snupkg + 5.0.0 diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index 9edd621b..26714cca 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; namespace CommandLine.Infrastructure { @@ -43,15 +40,16 @@ private string GetLocalizedValue() return _value; if (_localizationPropertyInfo == null) { - // Static class IsAbstract + // Static class IsAbstract if (!_type.IsVisible) throw new ArgumentException($"Invalid resource type '{_type.FullName}'! {_type.Name} is not visible for the parser! Change resources 'Access Modifier' to 'Public'", _propertyName); - PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); - if (propertyInfo == null || !propertyInfo.CanRead || propertyInfo.PropertyType != typeof(string)) + PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.Static); + if (propertyInfo == null || !propertyInfo.CanRead || (propertyInfo.PropertyType != typeof(string) && !propertyInfo.PropertyType.CanCast())) throw new ArgumentException("Invalid resource property name! Localized value: {_value}", _propertyName); _localizationPropertyInfo = propertyInfo; } - return (string)_localizationPropertyInfo.GetValue(null, null); + + return _localizationPropertyInfo.GetValue(null, null).Cast(); } } diff --git a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs index f7b46bac..1b18da6e 100644 --- a/tests/CommandLine.Tests/Fakes/ResourceFakes.cs +++ b/tests/CommandLine.Tests/Fakes/ResourceFakes.cs @@ -3,6 +3,10 @@ public static class StaticResource { public static string HelpText { get { return "Localized HelpText"; } } + public static TypeWithImplicitCast ImplicitCastHelpText => new TypeWithImplicitCast("Localized HelpText"); + public static TypeWithExplicitCast ExplicitCastHelpText => new TypeWithExplicitCast("Localized HelpText"); + public static TypeWithWrongImplicitCast WrongImplicitCastHelpText => new TypeWithWrongImplicitCast(); + public static TypeWithWrongExplicitCast WrongExplicitCastHelpText => new TypeWithWrongExplicitCast(); } public class NonStaticResource @@ -10,6 +14,10 @@ public class NonStaticResource public static string HelpText { get { return "Localized HelpText"; } } public static string WriteOnlyText { set { value?.ToString(); } } private static string PrivateHelpText { get { return "Localized HelpText"; } } + public static TypeWithImplicitCast ImplicitCastHelpText => new TypeWithImplicitCast("Localized HelpText"); + public static TypeWithExplicitCast ExplicitCastHelpText => new TypeWithExplicitCast("Localized HelpText"); + public static TypeWithWrongImplicitCast WrongImplicitCastHelpText => new TypeWithWrongImplicitCast(); + public static TypeWithWrongExplicitCast WrongExplicitCastHelpText => new TypeWithWrongExplicitCast(); } public class NonStaticResource_WithNonStaticProperty @@ -22,4 +30,59 @@ internal class InternalResource public static string HelpText { get { return "Localized HelpText"; } } } + public class TypeWithImplicitCast + { + private string value; + + public TypeWithImplicitCast(string value) + { + this.value = value; + } + + public static implicit operator string(TypeWithImplicitCast obj) + { + return obj.value; + } + + public static implicit operator int(TypeWithImplicitCast obj) + { + return 0; + } + } + + public class TypeWithWrongImplicitCast + { + public static implicit operator int(TypeWithWrongImplicitCast obj) + { + return 0; + } + } + + public class TypeWithExplicitCast + { + private string value; + + public TypeWithExplicitCast(string value) + { + this.value = value; + } + + public static explicit operator string(TypeWithExplicitCast obj) + { + return obj.value; + } + + public static explicit operator int(TypeWithExplicitCast obj) + { + return 0; + } + } + + public class TypeWithWrongExplicitCast + { + public static explicit operator int(TypeWithWrongExplicitCast obj) + { + return 0; + } + } } diff --git a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs index 3c2bfbbd..ab566255 100644 --- a/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs +++ b/tests/CommandLine.Tests/Unit/BaseAttributeTests.cs @@ -21,12 +21,16 @@ public static void Default(object defaultValue) [InlineData("Help text", null, "Help text")] [InlineData("HelpText", typeof(Fakes.StaticResource), "Localized HelpText")] [InlineData("HelpText", typeof(Fakes.NonStaticResource), "Localized HelpText")] + [InlineData("ImplicitCastHelpText", typeof(Fakes.StaticResource), "Localized HelpText")] + [InlineData("ImplicitCastHelpText", typeof(Fakes.NonStaticResource), "Localized HelpText")] + [InlineData("ExplicitCastHelpText", typeof(Fakes.StaticResource), "Localized HelpText")] + [InlineData("ExplicitCastHelpText", typeof(Fakes.NonStaticResource), "Localized HelpText")] public static void HelpText(string helpText, Type resourceType, string expected) { TestBaseAttribute baseAttribute = new TestBaseAttribute(); baseAttribute.HelpText = helpText; baseAttribute.ResourceType = resourceType; - + Assert.Equal(expected, baseAttribute.HelpText); } @@ -35,6 +39,10 @@ public static void HelpText(string helpText, Type resourceType, string expected) [InlineData("WriteOnlyText", typeof(Fakes.NonStaticResource))] [InlineData("PrivateOnlyText", typeof(Fakes.NonStaticResource))] [InlineData("HelpText", typeof(Fakes.InternalResource))] + [InlineData("WrongImplicitCastHelpText", typeof(Fakes.StaticResource))] + [InlineData("WrongExplicitCastHelpText", typeof(Fakes.StaticResource))] + [InlineData("WrongImplicitCastHelpText", typeof(Fakes.NonStaticResource))] + [InlineData("WrongExplicitCastHelpText", typeof(Fakes.NonStaticResource))] public void ThrowsHelpText(string helpText, Type resourceType) { TestBaseAttribute baseAttribute = new TestBaseAttribute(); From 5b693d5386de984c855586b0a1b608275314dbd2 Mon Sep 17 00:00:00 2001 From: Misha Gorshenin Date: Sat, 22 May 2021 22:35:45 +0500 Subject: [PATCH 187/198] Reset nuget version --- src/CommandLine/CommandLine.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommandLine/CommandLine.csproj b/src/CommandLine/CommandLine.csproj index b2e65413..04496eb8 100644 --- a/src/CommandLine/CommandLine.csproj +++ b/src/CommandLine/CommandLine.csproj @@ -27,7 +27,6 @@ 8.0 true snupkg - 5.0.0 From 31895f1693234476297ad79841456caea751d6fc Mon Sep 17 00:00:00 2001 From: NN Date: Sat, 26 Jun 2021 13:19:42 +0300 Subject: [PATCH 188/198] Remove System.Linq.Expressions dependency. --- src/CommandLine/Core/ReflectionExtensions.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/CommandLine/Core/ReflectionExtensions.cs b/src/CommandLine/Core/ReflectionExtensions.cs index a4529871..622e1e6e 100644 --- a/src/CommandLine/Core/ReflectionExtensions.cs +++ b/src/CommandLine/Core/ReflectionExtensions.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using CommandLine.Infrastructure; using CommandLine.Text; @@ -121,11 +120,7 @@ public static object CreateEmptyArray(this Type type) public static object GetDefaultValue(this Type type) { - var e = Expression.Lambda>( - Expression.Convert( - Expression.Default(type), - typeof(object))); - return e.Compile()(); + return type.IsValueType ? Activator.CreateInstance(type) : null; } public static bool IsMutable(this Type type) From 705b421b6f0bc93a1de72af0c1266e2a51593925 Mon Sep 17 00:00:00 2001 From: Mike Perrin Date: Mon, 8 Nov 2021 12:33:21 +0000 Subject: [PATCH 189/198] added link to Option groups --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68b10659..79a16fa7 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ __This library provides _hassle free_ command line parsing with a constantly upd - Support Mutable and Immutable types. - Support HelpText localization. - Support ordering of options in HelpText. -- Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and Options groups. +- Support [Mutually Exclusive Options](https://github.com/commandlineparser/commandline/wiki/Mutually-Exclusive-Options) and [Option groups](https://github.com/commandlineparser/commandline/wiki/Option-Groups). - Support named and value options. - Support Asynchronous programming with async and await. - Unparsing support: `CommandLine.Parser.Default.FormatCommandLine(T options)`. From efc1e740529af8c99840474da125e00c25ca6bd0 Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Thu, 30 Dec 2021 10:06:53 +0100 Subject: [PATCH 190/198] Add Rider cache folder to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 55f900a1..4969d4de 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,7 @@ artifacts/* *.DotSettings.user # Visual Studio 2015 cache/options directory .vs/ +# Rider +.idea/ [R|r]elease/** From ff825e52d032c72014ff3d0dd8c5e938ab34e1be Mon Sep 17 00:00:00 2001 From: Bastian Schmidt Date: Thu, 30 Dec 2021 10:24:40 +0100 Subject: [PATCH 191/198] Fixes #776 --- src/CommandLine/Core/Tokenizer.cs | 29 ++++++++------- .../Unit/Core/TokenizerTests.cs | 36 ++++++++++++++++--- tests/CommandLine.Tests/Unit/Issue776Tests.cs | 36 +++++++++++++++++++ 3 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 tests/CommandLine.Tests/Unit/Issue776Tests.cs diff --git a/src/CommandLine/Core/Tokenizer.cs b/src/CommandLine/Core/Tokenizer.cs index b71ec7e2..fe94fc61 100644 --- a/src/CommandLine/Core/Tokenizer.cs +++ b/src/CommandLine/Core/Tokenizer.cs @@ -86,29 +86,32 @@ public static Result, Error> ExplodeOptionList( return Result.Succeed(exploded as IEnumerable, tokenizerResult.SuccessMessages()); } + /// + /// Normalizes the given . + /// + /// The given minus all names, and their value if one was present, that are not found using . public static IEnumerable Normalize( IEnumerable tokens, Func nameLookup) { - var indexes = + var toExclude = from i in tokens.Select( (t, i) => { - var prev = tokens.ElementAtOrDefault(i - 1).ToMaybe(); - return t.IsValue() && ((Value)t).ExplicitlyAssigned - && prev.MapValueOrDefault(p => p.IsName() && !nameLookup(p.Text), false) - ? Maybe.Just(i) - : Maybe.Nothing(); + if (t.IsName() == false + || nameLookup(t.Text)) + { + return Maybe.Nothing>(); + } + + var next = tokens.ElementAtOrDefault(i + 1).ToMaybe(); + var removeValue = next.MatchJust(out var nextValue) + && next.MapValueOrDefault(p => p.IsValue() && ((Value)p).ExplicitlyAssigned, false); + return Maybe.Just(new Tuple(t, removeValue ? nextValue : null)); }).Where(i => i.IsJust()) select i.FromJustOrFail(); - var toExclude = - from t in - tokens.Select((t, i) => indexes.Contains(i) ? Maybe.Just(t) : Maybe.Nothing()) - .Where(t => t.IsJust()) - select t.FromJustOrFail(); - - var normalized = tokens.Where(t => toExclude.Contains(t) == false); + var normalized = tokens.Where(t => toExclude.Any(e => ReferenceEquals(e.Item1, t) || ReferenceEquals(e.Item2, t)) == false); return normalized; } diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index a489b741..ea7268be 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -62,12 +62,12 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() } [Fact] - public void Normalize_should_remove_all_value_with_explicit_assignment_of_existing_name() + public void Normalize_should_remove_all_names_and_values_with_explicit_assignment_of_non_existing_names() { // Fixture setup var expectedTokens = new[] { - Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), - Token.Name("unknown"), Token.Name("switch") }; + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), + Token.Name("switch") }; Func nameLookup = name => name.Equals("x") || name.Equals("string-seq") || name.Equals("switch"); @@ -78,7 +78,7 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi Enumerable.Empty() .Concat( new[] { - Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), Token.Name("unknown"), Token.Value("value0", true), Token.Name("switch") }) //,Enumerable.Empty()), , nameLookup); @@ -89,6 +89,34 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi // Teardown } + [Fact] + public void Normalize_should_remove_all_names_of_non_existing_names() + { + // Fixture setup + var expectedTokens = new[] { + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), + Token.Name("switch") }; + Func nameLookup = + name => name.Equals("x") || name.Equals("string-seq") || name.Equals("switch"); + + // Exercize system + var result = + Tokenizer.Normalize( + //Result.Succeed( + Enumerable.Empty() + .Concat( + new[] { + Token.Name("x"), Token.Name("string-seq"), Token.Value("value0", true), Token.Value("bb"), + Token.Name("unknown"), Token.Name("switch") }) + //,Enumerable.Empty()), + , nameLookup); + + // Verify outcome + result.Should().BeEquivalentTo(expectedTokens); + + // Teardown + } + [Fact] public void Should_properly_parse_option_with_equals_in_value() { diff --git a/tests/CommandLine.Tests/Unit/Issue776Tests.cs b/tests/CommandLine.Tests/Unit/Issue776Tests.cs new file mode 100644 index 00000000..2e247f9b --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue776Tests.cs @@ -0,0 +1,36 @@ +using FluentAssertions; +using Xunit; + +// Issue #776 and #797 +// When IgnoreUnknownArguments is used and there are unknown arguments with explicitly assigned values, other arguments with explicit assigned values should not be influenced. +// The bug only occured when the value was the same for a known and an unknown argument. + +namespace CommandLine.Tests.Unit +{ + public class Issue776Tests + { + [Theory] + [InlineData("3")] + [InlineData("4")] + public void IgnoreUnknownArguments_should_work_for_all_values(string dummyValue) + { + var arguments = new[] { "--cols=4", $"--dummy={dummyValue}" }; + var result = new Parser(with => { with.IgnoreUnknownArguments = true; }) + .ParseArguments(arguments); + + Assert.Empty(result.Errors); + Assert.Equal(ParserResultType.Parsed, result.Tag); + + result.WithParsed(options => + { + options.Cols.Should().Be(4); + }); + } + + private class Options + { + [Option("cols", Required = false)] + public int Cols { get; set; } + } + } +} From 87316e978b08fe6ab8ce48f1838a57eac093bad6 Mon Sep 17 00:00:00 2001 From: Marcin Otorowski Date: Mon, 7 Feb 2022 22:42:11 +0100 Subject: [PATCH 192/198] Add missing interpolation character This fixes unhelpful exception message, if localizable text cannot be found in the associated resource dictionary. --- src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs index 9edd621b..33db2640 100644 --- a/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs +++ b/src/CommandLine/Infrastructure/LocalizableAttributeProperty.cs @@ -48,7 +48,7 @@ private string GetLocalizedValue() throw new ArgumentException($"Invalid resource type '{_type.FullName}'! {_type.Name} is not visible for the parser! Change resources 'Access Modifier' to 'Public'", _propertyName); PropertyInfo propertyInfo = _type.GetProperty(_value, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static); if (propertyInfo == null || !propertyInfo.CanRead || propertyInfo.PropertyType != typeof(string)) - throw new ArgumentException("Invalid resource property name! Localized value: {_value}", _propertyName); + throw new ArgumentException($"Invalid resource property name! Localized value: {_value}", _propertyName); _localizationPropertyInfo = propertyInfo; } return (string)_localizationPropertyInfo.GetValue(null, null); From dffb7517a7b7e0a46a5b6cc17259a0220fc4fe28 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Thu, 12 May 2022 16:26:27 -0400 Subject: [PATCH 193/198] update nuget api key --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 91a36832..30fc9fe9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -55,7 +55,7 @@ deploy: - provider: NuGet api_key: - secure: e2gJJ3r6Uls5trJwryaudAZd49QniNfIjax/A+tfywlchSnIQVOzOQCO9tTSNccI + secure: llMIgYMuLHh9thyKMEAmkWraTaA9Zvcm1F8/yRwm0HCiPIt/ehR/GI4kJKyMTPyf artifact: /.*(\.|\.s)nupkg/ on: APPVEYOR_REPO_TAG: true From b0b0ec9f5470bacc0823c013a599ba3b4e115900 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Thu, 12 May 2022 17:07:06 -0400 Subject: [PATCH 194/198] update appveyor, remove github deployment for now, bump version to 2.9.1 --- appveyor.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 30fc9fe9..d11d6abf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ #version should be only changed with RELEASE eminent, see RELEASE.md -version: 2.9.0-ci-{build} +version: 2.9.1-ci-{build} image: Visual Studio 2019 @@ -44,15 +44,6 @@ on_failure: appveyor PushArtifact .\files.lst -DeploymentName "Failed Build File Listing" deploy: -- provider: GitHub - auth_token: - secure: hVyVwHl0JiVq0VxXB4VMRWbUtrGclIzadfnWFcWCQBLvbgMLahLBnWlwGglT63pZ - artifact: /.*(\.|\.s)nupkg/ - prerelease: false - force_update: true #fsharp package runs as separate build job, so have to force_update to add fsharp.nuget added - on: - APPVEYOR_REPO_TAG: true - - provider: NuGet api_key: secure: llMIgYMuLHh9thyKMEAmkWraTaA9Zvcm1F8/yRwm0HCiPIt/ehR/GI4kJKyMTPyf From 5ece267c5bf988a1135be49dac76c2c81c73bff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BE=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D0=BD=20=D0=9C?= =?UTF-8?q?=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=28Gorshenin=5FMV=29?= Date: Tue, 7 Jun 2022 18:29:11 +0500 Subject: [PATCH 195/198] mark CastExtensions as internal class --- src/CommandLine/CastExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommandLine/CastExtensions.cs b/src/CommandLine/CastExtensions.cs index 828a18e4..fa34928c 100644 --- a/src/CommandLine/CastExtensions.cs +++ b/src/CommandLine/CastExtensions.cs @@ -4,7 +4,7 @@ namespace CommandLine { - public static class CastExtensions + internal static class CastExtensions { private const string ImplicitCastMethodName = "op_Implicit"; private const string ExplicitCastMethodName = "op_Explicit"; From 72677c5858f7b41117f31f366e8cdb03ccafd940 Mon Sep 17 00:00:00 2001 From: Bjarte Aarmo Lund Date: Tue, 20 Dec 2022 15:33:20 +0100 Subject: [PATCH 196/198] A minor spelling mistake (with few items => with fewer items) --- demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs | 2 +- demo/ReadText.LocalizedDemo/Properties/Resources.resx | 2 +- src/CommandLine/Text/SentenceBuilder.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs b/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs index cc414359..f2ca4b31 100644 --- a/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs +++ b/demo/ReadText.LocalizedDemo/Properties/Resources.Designer.cs @@ -331,7 +331,7 @@ public static string SentenceSequenceOutOfRangeErrorOption { } /// - /// Looks up a localized string similar to A sequence value not bound to option name is defined with few items than required.. + /// Looks up a localized string similar to A sequence value not bound to option name is defined with fewer items than required.. /// public static string SentenceSequenceOutOfRangeErrorValue { get { diff --git a/demo/ReadText.LocalizedDemo/Properties/Resources.resx b/demo/ReadText.LocalizedDemo/Properties/Resources.resx index afdea3d0..b002fc43 100644 --- a/demo/ReadText.LocalizedDemo/Properties/Resources.resx +++ b/demo/ReadText.LocalizedDemo/Properties/Resources.resx @@ -208,7 +208,7 @@ A sequence option '{0}' is defined with fewer or more items than required. - A sequence value not bound to option name is defined with few items than required. + A sequence value not bound to option name is defined with fewer items than required. Error setting value to option '{0}': {1} diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index c8537542..842ae675 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -138,7 +138,7 @@ public override Func FormatError case ErrorType.SequenceOutOfRangeError: var seqOutRange = ((SequenceOutOfRangeError)error); return seqOutRange.NameInfo.Equals(NameInfo.EmptyName) - ? "A sequence value not bound to option name is defined with few items than required." + ? "A sequence value not bound to option name is defined with fewer items than required." : "A sequence option '".JoinTo(seqOutRange.NameInfo.NameText, "' is defined with fewer or more items than required."); case ErrorType.BadVerbSelectedError: From 6514645610d6f56ebb143e69516a986ec2aefe78 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Tue, 7 Mar 2023 13:41:45 -0500 Subject: [PATCH 197/198] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..29c526ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Fork from this fiddle and paste link here: https://dotnetfiddle.net/mh9CjX + +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. From 1e3607b97af6141743edb3c434c06d5b492f6fb3 Mon Sep 17 00:00:00 2001 From: Eric Newton Date: Tue, 7 Mar 2023 13:42:40 -0500 Subject: [PATCH 198/198] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 29c526ef..c6020604 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,7 +11,9 @@ assignees: '' A clear and concise description of what the bug is. **To Reproduce** -Fork from this fiddle and paste link here: https://dotnetfiddle.net/mh9CjX +Either fork from this fiddle and paste link here: https://dotnetfiddle.net/mh9CjX + +or Steps to reproduce the behavior: 1. Go to '...'