Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Reflection;
using NetArgumentParser.Options;
using NetArgumentParser.Subcommands;

namespace NetArgumentParser.Attributes.Extensions;

Expand Down Expand Up @@ -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<OptionMarkAttribute>();
}

internal static ICommonOption? CreateOption(this PropertyInfo propertyInfo, object source)
{
ExtendedArgumentNullException.ThrowIfNull(propertyInfo, nameof(propertyInfo));
Expand All @@ -59,4 +66,18 @@ internal static bool HasSubcommandAttribute(this PropertyInfo propertyInfo)

return attribute?.CreateOption(source, propertyInfo);
}

internal static IEnumerable<OptionMark> 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<OptionMarkAttribute>()
.Select(t => new OptionMark(t.Name, option, parserQuantum));
}
}
19 changes: 19 additions & 0 deletions Core/NetArgumentParser/Attributes/OptionMarkAttribute.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
17 changes: 17 additions & 0 deletions Core/NetArgumentParser/Extensions/CollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;

namespace NetArgumentParser.Extensions;

internal static class CollectionExtensions
{
internal static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
{
ExtendedArgumentNullException.ThrowIfNull(collection, nameof(collection));
ExtendedArgumentNullException.ThrowIfNull(items, nameof(items));

foreach (T item in items)
{
collection.Add(item);
}
}
}
56 changes: 47 additions & 9 deletions Core/NetArgumentParser/Generators/ArgumentParserGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<OptionMark> optionMarks)
{
ExtendedArgumentNullException.ThrowIfNull(parser, nameof(parser));
ExtendedArgumentNullException.ThrowIfNull(config, nameof(config));

Type configType = config.GetType();

bool isSuitableConfig = configType.CustomAttributes
Expand All @@ -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<KeyValuePair<PropertyInfo, ICommonOption>> options = ConfigureOptions(
parser,
config,
out IList<OptionMark> rootOptionMarks);

IEnumerable<ArgumentParserGeneratorQuantum> subcommands = ConfigureSubcommands(
parser,
config,
out IList<OptionMark> nestedOptionMarks);

optionMarks = [];
optionMarks.AddRange(rootOptionMarks);
optionMarks.AddRange(nestedOptionMarks);

var rootQuantum = new ArgumentParserGeneratorQuantum(options, subcommands);

ConfigureMutuallyExclusiveOptionGroups(parser, rootQuantum);
}

protected virtual Dictionary<PropertyInfo, ICommonOption> ConfigureOptions(
ParserQuantum parserQuantum,
object config)
object config,
out IList<OptionMark> optionMarks)
{
ExtendedArgumentNullException.ThrowIfNull(parserQuantum, nameof(parserQuantum));
ExtendedArgumentNullException.ThrowIfNull(config, nameof(config));
Expand All @@ -50,6 +75,10 @@ protected virtual Dictionary<PropertyInfo, ICommonOption> ConfigureOptions(

ConfigureOptionGroups(parserQuantum, optionMap);

optionMarks = [.. optionMap
.Where(t => t.Key.HasOptionAttribute())
.SelectMany(t => t.Key.CreateOptionMarks(t.Value, parserQuantum))];

return optionMap;
}

Expand Down Expand Up @@ -134,7 +163,8 @@ protected virtual void ConfigureMutuallyExclusiveOptionGroups(

protected virtual IList<ArgumentParserGeneratorQuantum> ConfigureSubcommands(
ParserQuantum parserQuantum,
object config)
object config,
out IList<OptionMark> optionMarks)
{
ExtendedArgumentNullException.ThrowIfNull(parserQuantum, nameof(parserQuantum));
ExtendedArgumentNullException.ThrowIfNull(config, nameof(config));
Expand All @@ -145,6 +175,7 @@ protected virtual IList<ArgumentParserGeneratorQuantum> ConfigureSubcommands(
.GetProperties()
.Where(t => t.HasSubcommandAttribute() && t.CanRead);

optionMarks = [];
List<ArgumentParserGeneratorQuantum> quantums = [];

foreach (PropertyInfo propertyInfo in subcommandProperties)
Expand All @@ -162,11 +193,18 @@ protected virtual IList<ArgumentParserGeneratorQuantum> ConfigureSubcommands(
object subcommandConfig = propertyInfo.GetValue(config)
?? throw new NullSubcommandConfigException(null, propertyInfo);

Dictionary<PropertyInfo, ICommonOption> optionMap =
ConfigureOptions(subcommand, subcommandConfig);
Dictionary<PropertyInfo, ICommonOption> optionMap = ConfigureOptions(
subcommand,
subcommandConfig,
out IList<OptionMark> rootOptionMarks);

IList<ArgumentParserGeneratorQuantum> optionMapFromSubcommands = ConfigureSubcommands(
subcommand,
subcommandConfig,
out IList<OptionMark> nestedOptionMarks);

IList<ArgumentParserGeneratorQuantum> optionMapFromSubcommands =
ConfigureSubcommands(subcommand, subcommandConfig);
optionMarks.AddRange(rootOptionMarks);
optionMarks.AddRange(nestedOptionMarks);

var quantum = new ArgumentParserGeneratorQuantum(
optionMap,
Expand Down
2 changes: 1 addition & 1 deletion Core/NetArgumentParser/NetArgumentParser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<PackageId>NetArgumentParser</PackageId>
<Version>1.1.1</Version>
<Version>1.2.0</Version>
<Product>NetArgumentParser</Product>
<Title>NetArgumentParser</Title>
<Authors>yakovypg</Authors>
Expand Down
5 changes: 5 additions & 0 deletions Core/NetArgumentParser/Options/OptionMark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using NetArgumentParser.Subcommands;

namespace NetArgumentParser.Options;

public record OptionMark(string Name, ICommonOption Option, ParserQuantum ParserQuantum);
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<TargetFrameworks>
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
Expand Down
70 changes: 70 additions & 0 deletions Documentation/ParserGenerationUsingAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<string>("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<OptionMark> optionMarks);
IEnumerable<OptionMark> pathOptionMarks = optionMarks.Where(t => t.Name == "path");

foreach (OptionMark optionMark in pathOptionMarks)
{
if (optionMark.Option is ValueOption<string> pathOption)
pathOption.Converter = new ValueConverter<string>(Path.GetFullPath);
}

[ParserConfig]
internal class CustomParserConfig
{
[ValueOption<string>("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<string>("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.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\NetArgumentParser\NetArgumentParser.csproj" />
</ItemGroup>

</Project>
Loading
Loading