diff --git a/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoEnumerableExtensions.cs b/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoEnumerableExtensions.cs index 6d563a5..17a9cb7 100644 --- a/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoEnumerableExtensions.cs +++ b/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoEnumerableExtensions.cs @@ -15,7 +15,7 @@ internal static class PropertyInfoEnumerableExtensions return properties.GroupBy(t => t.GetCustomAttribute()); } - internal static T? FindOptionGroupAttributeWithBestParameters( + internal static T? FindOptionGroupSingleAttributeWithBestParameters( this IEnumerable properties, string groupId) where T : OptionGroupBaseAttribute @@ -28,20 +28,22 @@ internal static class PropertyInfoEnumerableExtensions .Where(t => t is not null && t.Id == groupId) .ToList()!; - bool isGroupWithInfoExists = candidates.Any(t => - { - return !string.IsNullOrEmpty(t.Header) - || !string.IsNullOrEmpty(t.Description); - }); + return properties.FindOptionGroupAttributeWithBestParameters(candidates); + } - if (!isGroupWithInfoExists) - return candidates.FirstOrDefault(); + internal static T? FindOptionGroupMultipleAttributeWithBestParameters( + this IEnumerable properties, + string groupId) + where T : OptionGroupBaseAttribute + { + ExtendedArgumentNullException.ThrowIfNull(properties, nameof(properties)); + ExtendedArgumentNullException.ThrowIfNull(groupId, nameof(groupId)); - T? bestGroupByHeader = candidates - .FirstOrDefault(t => !string.IsNullOrEmpty(t.Header)); + List candidates = [.. properties + .SelectMany(t => t.GetCustomAttributes()) + .Where(t => t is not null && t.Id == groupId)]; - return bestGroupByHeader ?? candidates - .FirstOrDefault(t => !string.IsNullOrEmpty(t.Description)); + return properties.FindOptionGroupAttributeWithBestParameters(candidates); } internal static Dictionary CreateOptions( @@ -55,4 +57,30 @@ internal static Dictionary CreateOptions( t => t, t => t.CreateOption(source) ?? throw new UnsupportedOptionConfigException(null, t)); } + + private static T? FindOptionGroupAttributeWithBestParameters( + this IEnumerable properties, + IEnumerable candidates) + where T : OptionGroupBaseAttribute + { + ExtendedArgumentNullException.ThrowIfNull(properties, nameof(properties)); + ExtendedArgumentNullException.ThrowIfNull(candidates, nameof(candidates)); + + candidates = [.. candidates]; + + bool isGroupWithInfoExists = candidates.Any(t => + { + return !string.IsNullOrEmpty(t.Header) + || !string.IsNullOrEmpty(t.Description); + }); + + if (!isGroupWithInfoExists) + return candidates.FirstOrDefault(); + + T? bestGroupByHeader = candidates + .FirstOrDefault(t => !string.IsNullOrEmpty(t.Header)); + + return bestGroupByHeader ?? candidates + .FirstOrDefault(t => !string.IsNullOrEmpty(t.Description)); + } } diff --git a/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs b/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs index cb6e537..3011dcc 100644 --- a/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs +++ b/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using NetArgumentParser.Options; @@ -7,29 +8,38 @@ namespace NetArgumentParser.Attributes.Extensions; internal static class PropertyInfoExtensions { - internal static bool HasAttribute(this PropertyInfo propertyInfo) + internal static bool HasSingleAttribute(this PropertyInfo propertyInfo) where T : Attribute { ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); return propertyInfo.GetCustomAttribute() is not null; } + internal static bool HasMultipleAttribute(this PropertyInfo propertyInfo) + where T : Attribute + { + ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); + + IEnumerable attributes = propertyInfo.GetCustomAttributes(); + return attributes is not null && attributes.Any(); + } + internal static bool HasOptionAttribute(this PropertyInfo propertyInfo) { ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); - return propertyInfo.HasAttribute(); + return propertyInfo.HasSingleAttribute(); } internal static bool HasOptionGroupAttribute(this PropertyInfo propertyInfo) { ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); - return propertyInfo.HasAttribute(); + return propertyInfo.HasSingleAttribute(); } internal static bool HasMutuallyExclusiveOptionGroupAttribute(this PropertyInfo propertyInfo) { ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); - return propertyInfo.HasAttribute(); + return propertyInfo.HasMultipleAttribute(); } internal static bool HasSubcommandAttribute(this PropertyInfo propertyInfo) diff --git a/Core/NetArgumentParser/Attributes/MutuallyExclusiveOptionGroupAttribute.cs b/Core/NetArgumentParser/Attributes/MutuallyExclusiveOptionGroupAttribute.cs index 2ffd5c6..f72d776 100644 --- a/Core/NetArgumentParser/Attributes/MutuallyExclusiveOptionGroupAttribute.cs +++ b/Core/NetArgumentParser/Attributes/MutuallyExclusiveOptionGroupAttribute.cs @@ -4,7 +4,7 @@ namespace NetArgumentParser.Attributes; [AttributeUsage( AttributeTargets.Property, - AllowMultiple = false, + AllowMultiple = true, Inherited = false) ] public sealed class MutuallyExclusiveOptionGroupAttribute : OptionGroupBaseAttribute diff --git a/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs b/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs index dfe4aa0..9c02cbe 100644 --- a/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs +++ b/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs @@ -70,7 +70,7 @@ protected virtual void ConfigureOptionGroups( if (attribute is not null) { attribute = optionMap.Keys - .FindOptionGroupAttributeWithBestParameters(attribute.Id); + .FindOptionGroupSingleAttributeWithBestParameters(attribute.Id); } OptionGroup? optionGroup = attribute is not null @@ -96,14 +96,18 @@ protected virtual void ConfigureMutuallyExclusiveOptionGroups( ExtendedArgumentNullException.ThrowIfNull(argumentParser, nameof(argumentParser)); ExtendedArgumentNullException.ThrowIfNull(rootQuantum, nameof(rootQuantum)); - IEnumerable> optionsWithGroup = + IEnumerable> optionsWithPropertyInfo = rootQuantum.FindOptions(t => t.Key.HasMutuallyExclusiveOptionGroupAttribute(), true); - var mutuallyExclusiveOptionGroups = optionsWithGroup.GroupBy(t => + var optionInfoWithGroupAttribute = optionsWithPropertyInfo.SelectMany(kv => { - return t.Key.GetCustomAttribute(); + return kv.Key + .GetCustomAttributes() + .Select(attr => new { OptionInfo = kv, GroupAttribute = attr }); }); + var mutuallyExclusiveOptionGroups = optionInfoWithGroupAttribute.GroupBy(t => t.GroupAttribute); + foreach (var group in mutuallyExclusiveOptionGroups) { MutuallyExclusiveOptionGroupAttribute? attribute = group.Key; @@ -113,13 +117,13 @@ protected virtual void ConfigureMutuallyExclusiveOptionGroups( MutuallyExclusiveOptionGroupAttribute? attributeWithBestParameters = rootQuantum.Options .Select(t => t.Key) - .FindOptionGroupAttributeWithBestParameters( + .FindOptionGroupMultipleAttributeWithBestParameters( attribute.Id); if (attributeWithBestParameters is not null) attribute = attributeWithBestParameters; - IEnumerable groupOptions = group.Select(t => t.Value); + IEnumerable groupOptions = group.Select(t => t.OptionInfo.Value); _ = argumentParser.AddMutuallyExclusiveOptionGroup( attribute.Header, diff --git a/Documentation/ParserGenerationUsingAttributes.md b/Documentation/ParserGenerationUsingAttributes.md index 8404927..3605e0d 100644 --- a/Documentation/ParserGenerationUsingAttributes.md +++ b/Documentation/ParserGenerationUsingAttributes.md @@ -260,7 +260,7 @@ internal class CustomParserConfig } ``` -Attributes for mutually exclusive groups are set in a similar manner. But unlike regular groups, mutually exclusive groups can contain options from different levels of subcommands. +Attributes for mutually exclusive groups are set in a similar manner using `MutuallyExclusiveOptionGroup` attribute. But unlike regular groups, mutually exclusive groups can contain options from different levels of subcommands. In addition, one property can have multiple `MutuallyExclusiveOptionGroup` attributes. ```cs [ParserConfig] @@ -274,8 +274,29 @@ internal class CustomParserConfig [MutuallyExclusiveOptionGroup("id", "", "")] public bool ShowSecondName { get; set; } + [ValueOption("scale")] + [MutuallyExclusiveOptionGroup( + $"{nameof(ScaleFactor)}-{nameof(NewWidth)}", + $"{nameof(ScaleFactor)}-{nameof(NewWidth)}", + $"{nameof(ScaleFactor)} cannot be used with {nameof(NewWidth)}") + ] + [MutuallyExclusiveOptionGroup( + $"{nameof(ScaleFactor)}-{nameof(NewHeight)}", + $"{nameof(ScaleFactor)}-{nameof(NewHeight)}", + $"{nameof(ScaleFactor)} cannot be used with {nameof(NewHeight)}") + ] + public double ScaleFactor { get; set; } + + [ValueOption("width")] + [MutuallyExclusiveOptionGroup($"{nameof(ScaleFactor)}-{nameof(NewWidth)}", "", "")] + public int NewWidth { get; set; } + + [ValueOption("height")] + [MutuallyExclusiveOptionGroup($"{nameof(ScaleFactor)}-{nameof(NewHeight)}", "", "")] + public int NewHeight { get; set; } + [Subcommand("status", "description")] - public StatusSubcommand Status { get; } + public StatusSubcommand Status { get; } = new(); } internal class StatusSubcommand @@ -293,27 +314,17 @@ Subcommands can be configured using `SubcommandAttribute` attribute. The corresp [ParserConfig] internal class CustomParserConfig { - public CustomParserConfig() - { - Status = new(); - } - [Subcommand("status", "description")] - public StatusSubcommand Status { get; } + public StatusSubcommand Status { get; } = new(); } internal class StatusSubcommand { - public StatusSubcommand() - { - Update = new(); - } - [CounterOption("verbosity", "v")] public int Verbosity { get; set; } [Subcommand("update", "description")] - public UpdateSubcommand Update { get; } + public UpdateSubcommand Update { get; } = new(); } internal class UpdateSubcommand diff --git a/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs b/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs index 3b95d4a..e386d36 100644 --- a/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs +++ b/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs @@ -51,7 +51,15 @@ }; } -_ = parser.Parse(["--help"]); +try +{ + _ = parser.Parse(args); +} +catch (Exception ex) +{ + Console.WriteLine($"Error: {ex.Message}"); + return; +} #pragma warning disable [ParserConfig] @@ -229,7 +237,35 @@ internal class UpdateSubcommand { [FlagOption("remote", "r")] public bool Remote { get; set; } + + [ValueOption("scale")] + [MutuallyExclusiveOptionGroup( + $"{nameof(ScaleFactor)}-{nameof(NewWidth)}", + $"{nameof(ScaleFactor)}-{nameof(NewWidth)}", + $"{nameof(ScaleFactor)} cannot be used with {nameof(NewWidth)}") + ] + [MutuallyExclusiveOptionGroup( + $"{nameof(ScaleFactor)}-{nameof(NewHeight)}", + $"{nameof(ScaleFactor)}-{nameof(NewHeight)}", + $"{nameof(ScaleFactor)} cannot be used with {nameof(NewHeight)}") + ] + public double ScaleFactor { get; set; } + + [ValueOption("width")] + [MutuallyExclusiveOptionGroup($"{nameof(ScaleFactor)}-{nameof(NewWidth)}", "", "")] + public int NewWidth { get; set; } + + [ValueOption("height")] + [MutuallyExclusiveOptionGroup($"{nameof(ScaleFactor)}-{nameof(NewHeight)}", "", "")] + public int NewHeight { get; set; } } internal record Point(double X, double Y, double Z); #pragma warning restore + +/* +./NetArgumentParser.Examples.ParserGenerationUsingAttributes + --files 1.txt 2.txt 3.txt + --mode Create + status update --scale 0.5 +*/ diff --git a/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs b/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs index 0fa9e0b..050775e 100644 --- a/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs +++ b/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs @@ -2152,6 +2152,72 @@ public void Parse_MutuallyExclusiveOptions_ThrowsException() Assert.Null(ex); } + [Fact] + public void Parse_PairedMutuallyExclusiveOptions_ThrowsException() + { + var arguments = new string[] + { + "--scale", "0.5", + "-b", "15", + "--height", "1080", + "-d", "10", + "--width", "1920" + }; + + var scaleOption = new ValueOption("scale", string.Empty); + var widthOption = new ValueOption("width", string.Empty); + var heightOption = new ValueOption("height", string.Empty); + + var scaleWidthGroupOptions = new List() { scaleOption, widthOption }; + var scaleHeightGroupOptions = new List() { scaleOption, heightOption }; + + var options = new ICommonOption[] + { + scaleOption, + new ValueOption(string.Empty, "b"), + widthOption, + new ValueOption(string.Empty, "d"), + heightOption + }; + + var parser = new ArgumentParser() + { + UseDefaultHelpOption = false + }; + + parser.AddOptions(options); + + MutuallyExclusiveOptionGroup scaleWidthGroup = + parser.AddMutuallyExclusiveOptionGroup("scaleWidth", null, scaleWidthGroupOptions); + + MutuallyExclusiveOptionGroup scaleHeightGroup = + parser.AddMutuallyExclusiveOptionGroup("scaleHeight", null, scaleHeightGroupOptions); + + void VerifyConflict(ICommonOption expectedExistingOption, ICommonOption expectedNewOption) + { + var exception = Assert.Throws(() => + { + _ = parser.Parse(arguments); + }); + + Assert.Equal(expectedExistingOption, exception.ExistingOption); + Assert.Equal(expectedNewOption, exception.NewOption); + } + + VerifyConflict(scaleOption, heightOption); + + scaleHeightGroup.RemoveOption(heightOption); + parser.ResetOptionsHandledState(); + + VerifyConflict(scaleOption, widthOption); + + scaleWidthGroup.RemoveOption(widthOption); + parser.ResetOptionsHandledState(); + + Exception? ex = Record.Exception(() => parser.Parse(arguments)); + Assert.Null(ex); + } + [Fact] public void Parse_SeveralArguments_ArgumentsParseResultIsCorrect() { diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/ComplexParserGeneratorConfig.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/ComplexParserGeneratorConfig.cs index feaee3d..babb341 100644 --- a/Tests/NetArgumentParser.Tests/Models/Configurations/ComplexParserGeneratorConfig.cs +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/ComplexParserGeneratorConfig.cs @@ -39,6 +39,13 @@ internal class ComplexParserGeneratorConfig public const bool IgnoreCaseIsHidden = true; public const bool IgnoreCaseIsFinal = false; + public const string IgnoreLocaleLongName = "ignore-locale"; + public const string IgnoreLocaleShortName = "I"; + public const string IgnoreLocaleDescription = "ignore-locale description"; + public const bool IgnoreLocaleIsRequired = false; + public const bool IgnoreLocaleIsHidden = true; + public const bool IgnoreLocaleIsFinal = false; + public const string InputFilesLongName = "files"; public const string InputFilesShortName = "f"; public const string InputFilesDescription = "files description"; @@ -110,9 +117,13 @@ internal class ComplexParserGeneratorConfig public const string ValueOptionsGroupHeader = "ValueOptions"; public const string ValueOptionsGroupDescription = "ValueOptions description"; - public const string MutuallyExclusiveOptionGroupId = "group1Id"; - public const string MutuallyExclusiveOptionGroupHeader = "group1"; - public const string MutuallyExclusiveOptionGroupDescription = "group1 d"; + public const string MutuallyExclusiveOptionGroup1Id = "group1Id"; + public const string MutuallyExclusiveOptionGroup1Header = "group1"; + public const string MutuallyExclusiveOptionGroup1Description = "group1 d"; + + public const string MutuallyExclusiveOptionGroup2Id = "group2Id"; + public const string MutuallyExclusiveOptionGroup2Header = "group2"; + public const string MutuallyExclusiveOptionGroup2Description = "group2 d"; public ComplexParserGeneratorConfig() { @@ -122,6 +133,7 @@ public ComplexParserGeneratorConfig() public static IReadOnlyList VerbosityLevelAliases { get; } = []; public static IReadOnlyList ModeAliases { get; } = []; public static IReadOnlyList IgnoreCaseAliases { get; } = ["ig1"]; + public static IReadOnlyList IgnoreLocaleAliases { get; } = ["loc0", "loc1"]; public static IReadOnlyList InputFilesAliases { get; } = ["i1", "i2", "i3"]; public static IReadOnlyList NumbersAliases { get; } = ["nnn"]; public static IReadOnlyList MarginAliases { get; } = []; @@ -155,9 +167,13 @@ public ComplexParserGeneratorConfig() []) ] [MutuallyExclusiveOptionGroup( - MutuallyExclusiveOptionGroupId, - MutuallyExclusiveOptionGroupHeader, - MutuallyExclusiveOptionGroupDescription)] + MutuallyExclusiveOptionGroup1Id, + MutuallyExclusiveOptionGroup1Header, + MutuallyExclusiveOptionGroup1Description)] + [MutuallyExclusiveOptionGroup( + MutuallyExclusiveOptionGroup2Id, + MutuallyExclusiveOptionGroup2Header, + MutuallyExclusiveOptionGroup2Description)] public BigInteger? VerbosityLevel { get; set; } [EnumValueOption( @@ -197,11 +213,23 @@ public ComplexParserGeneratorConfig() FlagOptionsGroupHeader, FlagOptionsGroupDescription)] [MutuallyExclusiveOptionGroup( - MutuallyExclusiveOptionGroupId, - MutuallyExclusiveOptionGroupHeader, - MutuallyExclusiveOptionGroupDescription)] + MutuallyExclusiveOptionGroup1Id, + MutuallyExclusiveOptionGroup1Header, + MutuallyExclusiveOptionGroup1Description)] public bool? IgnoreCase { get; set; } + [FlagOption( + IgnoreLocaleLongName, + IgnoreLocaleShortName, + IgnoreLocaleDescription, + IgnoreLocaleIsRequired, + IgnoreLocaleIsHidden, + IgnoreLocaleIsFinal, + ["loc0", "loc1"])] + [OptionGroup(FlagOptionsGroupId, "", "")] + [MutuallyExclusiveOptionGroup(MutuallyExclusiveOptionGroup2Id, "", "")] + public bool? IgnoreLocale { get; set; } + [MultipleValueOption( InputFilesLongName, InputFilesShortName, diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/ParseSpecificParserGeneratorConfig.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/ParseSpecificParserGeneratorConfig.cs index 3f346df..29bfa94 100644 --- a/Tests/NetArgumentParser.Tests/Models/Configurations/ParseSpecificParserGeneratorConfig.cs +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/ParseSpecificParserGeneratorConfig.cs @@ -50,9 +50,9 @@ public ParseSpecificParserGeneratorConfig() ShowVersionIsHidden, ["v1"])] [MutuallyExclusiveOptionGroup( - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupId, - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupHeader, - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupDescription)] + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Id, + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Header, + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Description)] public bool ShowVersion { get; set; } [Subcommand(ComplexSubcommandName, ComplexSubcommandDescription)] diff --git a/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs b/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs index c0907ec..c084b58 100644 --- a/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs +++ b/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs @@ -66,38 +66,68 @@ public void ConfigureParser_ComplexConfig_ConfiguredCorrectly() generator.ConfigureParser(argumentParser, config); - Assert.Equal(1, argumentParser.MutuallyExclusiveOptionGroups.Count); + Assert.Equal(2, argumentParser.MutuallyExclusiveOptionGroups.Count); - var group = argumentParser.MutuallyExclusiveOptionGroups.FirstOrDefault(t => + var group1 = argumentParser.MutuallyExclusiveOptionGroups.FirstOrDefault(t => { - return t.Header == ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupHeader; + return t.Header == ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Header; }); - Assert.NotNull(group); - Assert.Equal(2, group.Options.Count); + Assert.NotNull(group1); + Assert.Equal(2, group1.Options.Count); Assert.Equal( - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupHeader, - group.Header); + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Header, + group1.Header); Assert.Equal( - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupDescription, - group.Description); + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Description, + group1.Description); - ICommonOption? verbosityLevelOption = group.Options.FirstOrDefault(t => + ICommonOption? verbosityLevelOptionFromGroup1 = group1.Options.FirstOrDefault(t => { return t.LongName == ComplexParserGeneratorConfig.VerbosityLevelLongName; }); - Assert.NotNull(verbosityLevelOption); + Assert.NotNull(verbosityLevelOptionFromGroup1); - ICommonOption? ignoreCaseOption = group.Options.FirstOrDefault(t => + ICommonOption? ignoreCaseOption = group1.Options.FirstOrDefault(t => { return t.LongName == ComplexParserGeneratorConfig.IgnoreCaseLongName; }); Assert.NotNull(ignoreCaseOption); + var group2 = argumentParser.MutuallyExclusiveOptionGroups.FirstOrDefault(t => + { + return t.Header == ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup2Header; + }); + + Assert.NotNull(group2); + Assert.Equal(2, group2.Options.Count); + + Assert.Equal( + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup2Header, + group2.Header); + + Assert.Equal( + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup2Description, + group2.Description); + + ICommonOption? verbosityLevelOptionFromGroup2 = group2.Options.FirstOrDefault(t => + { + return t.LongName == ComplexParserGeneratorConfig.VerbosityLevelLongName; + }); + + Assert.NotNull(verbosityLevelOptionFromGroup2); + + ICommonOption? ignoreLocaleOption = group2.Options.FirstOrDefault(t => + { + return t.LongName == ComplexParserGeneratorConfig.IgnoreLocaleLongName; + }); + + Assert.NotNull(ignoreLocaleOption); + VerifyComplexParserGeneratorConfigQuantum(argumentParser); } @@ -112,7 +142,7 @@ public void ConfigureParser_ParseSpecificConfig_ConfiguredCorrectly() VerifyParseSpecificParserGeneratorConfigQuantum(argumentParser); - Assert.Equal(2, argumentParser.MutuallyExclusiveOptionGroups.Count); + Assert.Equal(3, argumentParser.MutuallyExclusiveOptionGroups.Count); var finalGroup = argumentParser.MutuallyExclusiveOptionGroups.FirstOrDefault(t => { @@ -141,18 +171,18 @@ public void ConfigureParser_ParseSpecificConfig_ConfiguredCorrectly() var complexGroup = argumentParser.MutuallyExclusiveOptionGroups.FirstOrDefault(t => { return t.Header == ComplexParserGeneratorConfig - .MutuallyExclusiveOptionGroupHeader; + .MutuallyExclusiveOptionGroup1Header; }); Assert.NotNull(complexGroup); Assert.Equal(3, complexGroup.Options.Count); Assert.Equal( - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupHeader, + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Header, complexGroup.Header); Assert.Equal( - ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroupDescription, + ComplexParserGeneratorConfig.MutuallyExclusiveOptionGroup1Description, complexGroup.Description); ICommonOption? showVersionOption = complexGroup.Options.FirstOrDefault(t => @@ -868,7 +898,7 @@ private static void VerifyComplexParserGeneratorConfigQuantum(ParserQuantum quan }); Assert.NotNull(flagOptions); - Assert.Equal(1, flagOptions.Options.Count); + Assert.Equal(2, flagOptions.Options.Count); Assert.Equal( ComplexParserGeneratorConfig.FlagOptionsGroupHeader, @@ -885,6 +915,13 @@ private static void VerifyComplexParserGeneratorConfigQuantum(ParserQuantum quan Assert.NotNull(ignoreCaseOptionInFlagOptions); + ICommonOption? ignoreLocaleOptionInFlagOptions = flagOptions.Options.FirstOrDefault(t => + { + return t.LongName == ComplexParserGeneratorConfig.IgnoreLocaleLongName; + }); + + Assert.NotNull(ignoreLocaleOptionInFlagOptions); + OptionGroup? valueOptions = quantum.OptionGroups.FirstOrDefault(t => { return t.Header == ComplexParserGeneratorConfig.ValueOptionsGroupHeader; @@ -965,7 +1002,7 @@ private static void VerifyComplexParserGeneratorConfigQuantum(ParserQuantum quan VerifySubcommandsOnlyParserGeneratorConfigQuantum(subcommandsOnlySubcommand); - Assert.Equal(7, quantum.Options.Count); + Assert.Equal(8, quantum.Options.Count); ICommonOption? verbosityLevelOption = quantum.Options.FirstOrDefault(t => { @@ -1101,6 +1138,41 @@ private static void VerifyComplexParserGeneratorConfigQuantum(ParserQuantum quan ComplexParserGeneratorConfig.IgnoreCaseAliases, ignoreCaseOption.Aliases); + ICommonOption? ignoreLocaleOption = quantum.Options.FirstOrDefault(t => + { + return t.LongName == ComplexParserGeneratorConfig.IgnoreLocaleLongName; + }); + + Assert.NotNull(ignoreLocaleOption); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleLongName, + ignoreLocaleOption.LongName); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleShortName, + ignoreLocaleOption.ShortName); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleDescription, + ignoreLocaleOption.Description); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleIsRequired, + ignoreLocaleOption.IsRequired); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleIsHidden, + ignoreLocaleOption.IsHidden); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleIsFinal, + ignoreLocaleOption.IsFinal); + + Assert.Equal( + ComplexParserGeneratorConfig.IgnoreLocaleAliases, + ignoreLocaleOption.Aliases); + ICommonOption? inputFilesOptionBase = quantum.Options.FirstOrDefault(t => { return t.LongName == ComplexParserGeneratorConfig.InputFilesLongName;