diff --git a/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs b/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs index 3011dcc..f30defc 100644 --- a/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs +++ b/Core/NetArgumentParser/Attributes/Extensions/PropertyInfoExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using NetArgumentParser.Options; +using NetArgumentParser.Subcommands; namespace NetArgumentParser.Attributes.Extensions; @@ -50,6 +51,12 @@ internal static bool HasSubcommandAttribute(this PropertyInfo propertyInfo) .Any(t => t.AttributeType == typeof(SubcommandAttribute)); } + internal static bool HasOptionMarkAttribute(this PropertyInfo propertyInfo) + { + ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); + return propertyInfo.HasMultipleAttribute(); + } + internal static ICommonOption? CreateOption(this PropertyInfo propertyInfo, object source) { ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); @@ -59,4 +66,18 @@ internal static bool HasSubcommandAttribute(this PropertyInfo propertyInfo) return attribute?.CreateOption(source, propertyInfo); } + + internal static IEnumerable CreateOptionMarks( + this PropertyInfo propertyInfo, + ICommonOption option, + ParserQuantum parserQuantum) + { + ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo)); + ExtendedArgumentNullException.ThrowIfNull(option, nameof(option)); + ExtendedArgumentNullException.ThrowIfNull(parserQuantum, nameof(parserQuantum)); + + return propertyInfo + .GetCustomAttributes() + .Select(t => new OptionMark(t.Name, option, parserQuantum)); + } } diff --git a/Core/NetArgumentParser/Attributes/OptionMarkAttribute.cs b/Core/NetArgumentParser/Attributes/OptionMarkAttribute.cs new file mode 100644 index 0000000..6d7499b --- /dev/null +++ b/Core/NetArgumentParser/Attributes/OptionMarkAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace NetArgumentParser.Attributes; + +[AttributeUsage( + AttributeTargets.Property, + AllowMultiple = true, + Inherited = false) +] +public class OptionMarkAttribute : Attribute +{ + public OptionMarkAttribute(string name = nameof(OptionMarkAttribute)) + { + ExtendedArgumentNullException.ThrowIfNull(name, nameof(name)); + Name = name; + } + + public string Name { get; } +} diff --git a/Core/NetArgumentParser/Extensions/CollectionExtensions.cs b/Core/NetArgumentParser/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000..5d321bb --- /dev/null +++ b/Core/NetArgumentParser/Extensions/CollectionExtensions.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace NetArgumentParser.Extensions; + +internal static class CollectionExtensions +{ + internal static void AddRange(this ICollection collection, IEnumerable items) + { + ExtendedArgumentNullException.ThrowIfNull(collection, nameof(collection)); + ExtendedArgumentNullException.ThrowIfNull(items, nameof(items)); + + foreach (T item in items) + { + collection.Add(item); + } + } +} diff --git a/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs b/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs index 9c02cbe..b863b6a 100644 --- a/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs +++ b/Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs @@ -4,6 +4,7 @@ using System.Reflection; using NetArgumentParser.Attributes; using NetArgumentParser.Attributes.Extensions; +using NetArgumentParser.Extensions; using NetArgumentParser.Options; using NetArgumentParser.Options.Collections; using NetArgumentParser.Subcommands; @@ -17,6 +18,17 @@ public void ConfigureParser(ArgumentParser parser, object config) ExtendedArgumentNullException.ThrowIfNull(parser, nameof(parser)); ExtendedArgumentNullException.ThrowIfNull(config, nameof(config)); + ConfigureParser(parser, config, out _); + } + + public void ConfigureParser( + ArgumentParser parser, + object config, + out IList optionMarks) + { + ExtendedArgumentNullException.ThrowIfNull(parser, nameof(parser)); + ExtendedArgumentNullException.ThrowIfNull(config, nameof(config)); + Type configType = config.GetType(); bool isSuitableConfig = configType.CustomAttributes @@ -25,16 +37,29 @@ public void ConfigureParser(ArgumentParser parser, object config) if (!isSuitableConfig) throw new UnsupportedParserConfigException(null, config); - var rootQuantum = new ArgumentParserGeneratorQuantum( - options: ConfigureOptions(parser, config), - subcommands: ConfigureSubcommands(parser, config)); + IEnumerable> options = ConfigureOptions( + parser, + config, + out IList rootOptionMarks); + + IEnumerable subcommands = ConfigureSubcommands( + parser, + config, + out IList nestedOptionMarks); + + optionMarks = []; + optionMarks.AddRange(rootOptionMarks); + optionMarks.AddRange(nestedOptionMarks); + + var rootQuantum = new ArgumentParserGeneratorQuantum(options, subcommands); ConfigureMutuallyExclusiveOptionGroups(parser, rootQuantum); } protected virtual Dictionary ConfigureOptions( ParserQuantum parserQuantum, - object config) + object config, + out IList optionMarks) { ExtendedArgumentNullException.ThrowIfNull(parserQuantum, nameof(parserQuantum)); ExtendedArgumentNullException.ThrowIfNull(config, nameof(config)); @@ -50,6 +75,10 @@ protected virtual Dictionary ConfigureOptions( ConfigureOptionGroups(parserQuantum, optionMap); + optionMarks = [.. optionMap + .Where(t => t.Key.HasOptionAttribute()) + .SelectMany(t => t.Key.CreateOptionMarks(t.Value, parserQuantum))]; + return optionMap; } @@ -134,7 +163,8 @@ protected virtual void ConfigureMutuallyExclusiveOptionGroups( protected virtual IList ConfigureSubcommands( ParserQuantum parserQuantum, - object config) + object config, + out IList optionMarks) { ExtendedArgumentNullException.ThrowIfNull(parserQuantum, nameof(parserQuantum)); ExtendedArgumentNullException.ThrowIfNull(config, nameof(config)); @@ -145,6 +175,7 @@ protected virtual IList ConfigureSubcommands( .GetProperties() .Where(t => t.HasSubcommandAttribute() && t.CanRead); + optionMarks = []; List quantums = []; foreach (PropertyInfo propertyInfo in subcommandProperties) @@ -162,11 +193,18 @@ protected virtual IList ConfigureSubcommands( object subcommandConfig = propertyInfo.GetValue(config) ?? throw new NullSubcommandConfigException(null, propertyInfo); - Dictionary optionMap = - ConfigureOptions(subcommand, subcommandConfig); + Dictionary optionMap = ConfigureOptions( + subcommand, + subcommandConfig, + out IList rootOptionMarks); + + IList optionMapFromSubcommands = ConfigureSubcommands( + subcommand, + subcommandConfig, + out IList nestedOptionMarks); - IList optionMapFromSubcommands = - ConfigureSubcommands(subcommand, subcommandConfig); + optionMarks.AddRange(rootOptionMarks); + optionMarks.AddRange(nestedOptionMarks); var quantum = new ArgumentParserGeneratorQuantum( optionMap, diff --git a/Core/NetArgumentParser/NetArgumentParser.csproj b/Core/NetArgumentParser/NetArgumentParser.csproj index 4d0195e..041dded 100644 --- a/Core/NetArgumentParser/NetArgumentParser.csproj +++ b/Core/NetArgumentParser/NetArgumentParser.csproj @@ -2,7 +2,7 @@ NetArgumentParser - 1.1.1 + 1.2.0 NetArgumentParser NetArgumentParser yakovypg diff --git a/Core/NetArgumentParser/Options/OptionMark.cs b/Core/NetArgumentParser/Options/OptionMark.cs new file mode 100644 index 0000000..a0a0e6b --- /dev/null +++ b/Core/NetArgumentParser/Options/OptionMark.cs @@ -0,0 +1,5 @@ +using NetArgumentParser.Subcommands; + +namespace NetArgumentParser.Options; + +public record OptionMark(string Name, ICommonOption Option, ParserQuantum ParserQuantum); diff --git a/Directory.Build.props b/Directory.Build.props index b33d20c..8922912 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - net5.0;net6.0;net7.0;net8.0; + net5.0;net6.0;net7.0;net8.0;net9.0;net10.0; netcoreapp3.0;netcoreapp3.1; netstandard2.0;netstandard2.1; net47;net471;net472;net48;net481 diff --git a/Documentation/ParserGenerationUsingAttributes.md b/Documentation/ParserGenerationUsingAttributes.md index 0e763f5..beac9b7 100644 --- a/Documentation/ParserGenerationUsingAttributes.md +++ b/Documentation/ParserGenerationUsingAttributes.md @@ -6,6 +6,7 @@ You can generate `ArgumentParser` using special class provided parser configurat * [Configuration Attributes](#configuration-attributes) * [Option Attributes](#option-attributes) * [Add value restriction](#add-value-restriction) + * [Option Mark Attributes](#option-mark-attributes) * [Group Attributes](#group-attributes) * [Subcommand Attributes](#subcommand-attributes) * [Argument Parser Generation](#argument-parser-generation) @@ -253,6 +254,75 @@ internal class CustomParserConfig } ``` +### Option Mark Attributes +Options can be marked with the `OptionMark` attribute. It can be useful if you want to separate some options for future configuration. For example, suppose you want to configure options for storing paths. You can mark the corresponding options as follows. +```cs +[ParserConfig] +internal class CustomParserConfig +{ + [ValueOption("input")] + [OptionMark("path")] + public string? InputFilePath { get; set; } +} +``` + +You can see that the `OptionMark` attribute with the name `path` was used. Next, you can get all marked options using a `ConfigureParser()` overload, find the right option(s) by name, and then configure found options as you wish. + +```cs +var generator = new ArgumentParserGenerator(); +var parser = new ArgumentParser(); +var config = new CustomParserConfig(); + +generator.ConfigureParser(parser, config, out IList optionMarks); +IEnumerable pathOptionMarks = optionMarks.Where(t => t.Name == "path"); + +foreach (OptionMark optionMark in pathOptionMarks) +{ + if (optionMark.Option is ValueOption pathOption) + pathOption.Converter = new ValueConverter(Path.GetFullPath); +} + +[ParserConfig] +internal class CustomParserConfig +{ + [ValueOption("input")] + [OptionMark("path")] + public string? InputFilePath { get; set; } +} +``` + +For convenience, you can create a class with option mark names, or create a class inherited from `OptionMarkAttribute`. It should also be noted that one option can have multiple marks. + +```cs +internal static class OptionMarkNames +{ + public const string UppercaseDescription = nameof(UppercaseDescription); +} + +[AttributeUsage( + AttributeTargets.Property, + AllowMultiple = false, + Inherited = true) +] +internal sealed class FilePathOptionMarkAttribute : OptionMarkAttribute +{ + public const string DefaultName = nameof(FilePathOptionMarkAttribute); + + public FilePathOptionMarkAttribute() + : base(DefaultName) + { } +} + +[ParserConfig] +internal class CustomParserConfig +{ + [ValueOption("input")] + [FilePathOptionMarkAttribute] + [OptionMark(OptionMarkNames.UppercaseDescription)] + public string? InputFilePath { get; set; } +} +``` + ### Group Attributes Goups can be configured using `OptionGroupAttribute` attribute. In addition to specifying the group header and description, you should specify the group ID. It is necessary for the correct placement of options, since groups can have the same header. Options that you want to put in the same group must be marked with an attribute with the same ID. You should't specify header and description for all group attributes with same id. It is enough to do this for only one attribute. diff --git a/Examples/NetArgumentParser.Examples.OptionMarks/NetArgumentParser.Examples.OptionMarks.csproj b/Examples/NetArgumentParser.Examples.OptionMarks/NetArgumentParser.Examples.OptionMarks.csproj new file mode 100644 index 0000000..44dcb4e --- /dev/null +++ b/Examples/NetArgumentParser.Examples.OptionMarks/NetArgumentParser.Examples.OptionMarks.csproj @@ -0,0 +1,12 @@ + + + + Exe + net6.0 + + + + + + + diff --git a/Examples/NetArgumentParser.Examples.OptionMarks/Program.cs b/Examples/NetArgumentParser.Examples.OptionMarks/Program.cs new file mode 100644 index 0000000..af893aa --- /dev/null +++ b/Examples/NetArgumentParser.Examples.OptionMarks/Program.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NetArgumentParser; +using NetArgumentParser.Attributes; +using NetArgumentParser.Converters; +using NetArgumentParser.Generators; +using NetArgumentParser.Options; +using NetArgumentParser.Options.Configuration; +using NetArgumentParser.Options.Context; + +var generator = new ArgumentParserGenerator(); +var parser = new ArgumentParser(); +var config = new CustomParserConfig(); + +generator.ConfigureParser(parser, config, out IList optionMarks); + +IEnumerable filePathOptionMarks = optionMarks + .Where(t => t.Name == FilePathOptionMarkAttribute.DefaultName); + +foreach (OptionMark optionMark in filePathOptionMarks) +{ + if (optionMark.Option is MultipleValueOption filePathOption) + filePathOption.Converter = new MultipleValueConverter(Path.GetFullPath); +} + +IEnumerable directoryPathOptionMarks = optionMarks + .Where(t => t.Name == OptionMarkNames.DirectoryPath); + +foreach (OptionMark optionMark in directoryPathOptionMarks) +{ + if (optionMark.Option is ValueOption directoryPathOption) + { + string defaultPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + directoryPathOption.DefaultValue = new DefaultOptionValue(defaultPath); + } +} + +IEnumerable uppercaseDescriptionOptionMarks = optionMarks + .Where(t => t.Name == OptionMarkNames.UppercaseDescription); + +foreach (OptionMark optionMark in uppercaseDescriptionOptionMarks) +{ + if (optionMark.Option is ValueOption uppercaseDescriptionOption) + uppercaseDescriptionOption.Description = uppercaseDescriptionOption.Description.ToUpperInvariant(); +} + +_ = parser.FindFirstOptionByLongName( + CustomParserConfig.OutputDirectoryLongName, + false, + out ICommonOption? outputDirectoryOption); + +Console.WriteLine($"Description: {outputDirectoryOption?.Description}"); + +try +{ + _ = parser.Parse(args); +} +catch (Exception ex) +{ + Console.WriteLine($"Error: {ex.Message}"); + return; +} + +Console.WriteLine($"Angle: {config.Angle}"); +Console.WriteLine($"InputFiles: {string.Join(" ", config.InputFiles)}"); +Console.WriteLine($"OutputDirectoryPath: {config.OutputDirectory}"); + +#pragma warning disable +internal static class OptionMarkNames +{ + public const string DirectoryPath = nameof(DirectoryPath); + public const string UppercaseDescription = nameof(UppercaseDescription); +} + +[AttributeUsage( + AttributeTargets.Property, + AllowMultiple = false, + Inherited = true) +] +internal sealed class FilePathOptionMarkAttribute : OptionMarkAttribute +{ + public const string DefaultName = nameof(FilePathOptionMarkAttribute); + + public FilePathOptionMarkAttribute() + : base(DefaultName) + { } +} + +[ParserConfig] +internal class CustomParserConfig +{ + public const string OutputDirectoryLongName = "output"; + + [ValueOption(longName: "angle")] + public double Angle { get; set; } + + [MultipleValueOption( + longName: "input", + contextCaptureType: ContextCaptureType.OneOrMore)] + [FilePathOptionMark] + public List InputFiles { get; set; } = []; + + [ValueOption( + longName: OutputDirectoryLongName, + description: "description")] + [OptionMark(OptionMarkNames.DirectoryPath)] + [OptionMark(OptionMarkNames.UppercaseDescription)] + public string? OutputDirectory { get; set; } +} +#pragma warning restore + +/* +./NetArgumentParser.Examples.OptionMarks \ + --input ./text.txt dir/image.jpg \ + --angle 0.5 +*/ diff --git a/Examples/NetArgumentParser.Examples.OptionalArguments/Program.cs b/Examples/NetArgumentParser.Examples.OptionalArguments/Program.cs index 9039aea..3dc9f04 100644 --- a/Examples/NetArgumentParser.Examples.OptionalArguments/Program.cs +++ b/Examples/NetArgumentParser.Examples.OptionalArguments/Program.cs @@ -141,3 +141,14 @@ internal sealed class ResultValues public string? Name { get; set; } } #pragma warning restore + +/* +./NetArgumentParser.Examples.OptionalArguments \ + --legacy-verbose \ + --quick \ + --input ./NetArgumentParser.Examples.OptionalArguments.dll \ + --date 2026 03 05 \ + -a 180 \ + -V -V -V \ + --nick "my nickname" +*/ diff --git a/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs b/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs index e386d36..0c3cc76 100644 --- a/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs +++ b/Examples/NetArgumentParser.Examples.ParserGenerationUsingAttributes/Program.cs @@ -264,8 +264,8 @@ internal record Point(double X, double Y, double Z); #pragma warning restore /* -./NetArgumentParser.Examples.ParserGenerationUsingAttributes - --files 1.txt 2.txt 3.txt - --mode Create +./NetArgumentParser.Examples.ParserGenerationUsingAttributes \ + --files 1.txt 2.txt 3.txt \ + --mode Create \ status update --scale 0.5 */ diff --git a/Examples/NetArgumentParser.Examples.Simple/Program.cs b/Examples/NetArgumentParser.Examples.Simple/Program.cs index 52d4494..081e655 100644 --- a/Examples/NetArgumentParser.Examples.Simple/Program.cs +++ b/Examples/NetArgumentParser.Examples.Simple/Program.cs @@ -51,3 +51,9 @@ Console.WriteLine($"Angle: {angle}"); Console.WriteLine($"File mode: {fileMode}"); Console.WriteLine($"Input files: {string.Join(" ", inputFiles)}"); + +/* +./NetArgumentParser.Examples.Simple \ + -i 1.jpg 2.png 3.bmp \ + -a 45 +*/ diff --git a/NetArgumentParser.slnx b/NetArgumentParser.slnx index 7fd395f..c30f15b 100644 --- a/NetArgumentParser.slnx +++ b/NetArgumentParser.slnx @@ -9,6 +9,7 @@ + diff --git a/README.md b/README.md index 96dff21..9debe23 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ license - version + version csharp

diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/ComplexOptionMarksParserGeneratorConfig.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/ComplexOptionMarksParserGeneratorConfig.cs new file mode 100644 index 0000000..b6fe396 --- /dev/null +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/ComplexOptionMarksParserGeneratorConfig.cs @@ -0,0 +1,38 @@ +using System; +using NetArgumentParser.Attributes; + +namespace NetArgumentParser.Tests.Models.Configurations.Marks; + +[ParserConfig] +internal class ComplexOptionMarksParserGeneratorConfig +{ + public const string FlagOptionLongName = "flag"; + public const string EnumOptionLongName = "enum"; + public const string ValueOptionLongName = "value"; + public const string MultipleValueOptionLongName = "multiple"; + + public const string MarksSubcommandName = "marks"; + public const string MarksSubcommandDescription = "marks description"; + + public ComplexOptionMarksParserGeneratorConfig() + { + Marks = new OptionMarksParserGeneratorConfig(); + } + + [FlagOption(FlagOptionLongName)] + public bool Flag { get; set; } + + [EnumValueOption(EnumOptionLongName)] + [OptionMark(OptionMarkNames.Enum)] + public StringSplitOptions Enum { get; set; } + + [ValueOption(ValueOptionLongName)] + [OptionMark(OptionMarkNames.Value)] + public int Value { get; set; } + + [Subcommand(MarksSubcommandName, MarksSubcommandDescription)] + public OptionMarksParserGeneratorConfig Marks { get; } + + [Subcommand("ignored", "i")] + protected int IgnoredByProtectedModifierSubcommand { get; } +} diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/FlagOptionMarkAttribute.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/FlagOptionMarkAttribute.cs new file mode 100644 index 0000000..e0abe12 --- /dev/null +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/FlagOptionMarkAttribute.cs @@ -0,0 +1,18 @@ +using System; +using NetArgumentParser.Attributes; + +namespace NetArgumentParser.Tests.Models.Configurations.Marks; + +[AttributeUsage( + AttributeTargets.Property, + AllowMultiple = false, + Inherited = true) +] +internal sealed class FlagOptionMarkAttribute : OptionMarkAttribute +{ + internal const string DefaultName = nameof(FlagOptionMarkAttribute); + + internal FlagOptionMarkAttribute() + : base(DefaultName) + { } +} diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/OptionMarkNames.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/OptionMarkNames.cs new file mode 100644 index 0000000..4cc7168 --- /dev/null +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/OptionMarkNames.cs @@ -0,0 +1,7 @@ +namespace NetArgumentParser.Tests.Models.Configurations.Marks; + +internal static class OptionMarkNames +{ + internal const string Enum = "enum"; + internal const string Value = "value"; +} diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/OptionMarksParserGeneratorConfig.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/OptionMarksParserGeneratorConfig.cs new file mode 100644 index 0000000..c32c8c5 --- /dev/null +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/Marks/OptionMarksParserGeneratorConfig.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using NetArgumentParser.Attributes; + +namespace NetArgumentParser.Tests.Models.Configurations.Marks; + +[ParserConfig] +internal class OptionMarksParserGeneratorConfig +{ + public const string FlagOptionLongName = "flag"; + public const string CounterOptionLongName = "counter"; + public const string ValueOptionLongName = "value"; + public const string MultipleValueOptionLongName = "multiple"; + + [FlagOption(FlagOptionLongName)] + [FlagOptionMark] + public bool Flag { get; set; } + + [CounterOption(CounterOptionLongName)] + public int Counter { get; set; } + + [ValueOption(ValueOptionLongName)] + [OptionMark(OptionMarkNames.Value)] + public int Value { get; set; } + + [MultipleValueOption(MultipleValueOptionLongName)] + [OptionMark(OptionMarkNames.Value)] + public List Values { get; set; } = []; + + [FlagOption("ignored")] + [OptionMark("ignored mark")] + protected int IgnoredByProtectedModifierOption { get; } +} diff --git a/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs b/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs index 6491f16..2cb7f10 100644 --- a/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs +++ b/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs @@ -13,6 +13,7 @@ using NetArgumentParser.Subcommands; using NetArgumentParser.Tests.Models; using NetArgumentParser.Tests.Models.Configurations; +using NetArgumentParser.Tests.Models.Configurations.Marks; namespace NetArgumentParser.Tests; @@ -57,6 +58,96 @@ public void ConfigureParser_SubcommandsOnlyConfig_ConfiguredCorrectly() VerifySubcommandsOnlyParserGeneratorConfigQuantum(argumentParser); } + [Fact] + public void ConfigureParser_ConfigWithOptionMarks_ConfiguredCorrectly() + { + var generator = new ArgumentParserGenerator(); + var argumentParser = new ArgumentParser(); + var config = new ComplexOptionMarksParserGeneratorConfig(); + + generator.ConfigureParser(argumentParser, config, out IList optionMarks); + + bool enumOptionFound = argumentParser.FindFirstOptionByLongName( + ComplexOptionMarksParserGeneratorConfig.EnumOptionLongName, + false, + out ICommonOption? enumOption); + + bool valueOptionFound = argumentParser.FindFirstOptionByLongName( + ComplexOptionMarksParserGeneratorConfig.ValueOptionLongName, + false, + out ICommonOption? valueOption); + + Assert.True(enumOptionFound); + Assert.True(valueOptionFound); + + Assert.NotNull(enumOption); + Assert.NotNull(valueOption); + + Subcommand? subcommand = argumentParser.Subcommands.FirstOrDefault( + t => t.Name == ComplexOptionMarksParserGeneratorConfig.MarksSubcommandName); + + Assert.NotNull(subcommand); + + bool subcommandFlagOptionFound = subcommand.FindFirstOptionByLongName( + OptionMarksParserGeneratorConfig.FlagOptionLongName, + false, + out ICommonOption? subcommandFlagOption); + + bool subcommandValueOptionFound = subcommand.FindFirstOptionByLongName( + OptionMarksParserGeneratorConfig.ValueOptionLongName, + false, + out ICommonOption? subcommandValueOption); + + bool subcommandMultipleValueOptionFound = subcommand.FindFirstOptionByLongName( + OptionMarksParserGeneratorConfig.MultipleValueOptionLongName, + false, + out ICommonOption? subcommandMultipleValueOption); + + Assert.True(subcommandFlagOptionFound); + Assert.True(subcommandValueOptionFound); + Assert.True(subcommandMultipleValueOptionFound); + + Assert.NotNull(subcommandFlagOption); + Assert.NotNull(subcommandValueOption); + Assert.NotNull(subcommandMultipleValueOption); + + OptionMark[] expectedOptionMarks = + [ + new OptionMark( + OptionMarkNames.Enum, + enumOption, + argumentParser), + + new OptionMark( + OptionMarkNames.Value, + valueOption, + argumentParser), + + new OptionMark( + FlagOptionMarkAttribute.DefaultName, + subcommandFlagOption, + subcommand), + + new OptionMark( + OptionMarkNames.Value, + subcommandValueOption, + subcommand), + + new OptionMark( + OptionMarkNames.Value, + subcommandMultipleValueOption, + subcommand) + ]; + + Assert.Equal(expectedOptionMarks.Length, optionMarks.Count); + + foreach (OptionMark optionMark in expectedOptionMarks) + { + bool optionMarkFound = optionMarks.Contains(optionMark); + Assert.True(optionMarkFound); + } + } + [Fact] public void ConfigureParser_ComplexConfig_ConfiguredCorrectly() {