Skip to content

Commit 1f65c5d

Browse files
authored
✨Create generic version of converter attributes (#171)
fixes #150 (MINOR)
1 parent 5fb663b commit 1f65c5d

14 files changed

+180
-3
lines changed

docs/class-based/customize-class-based-option.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ You can customize the behavior of the options with some annotations (the parser
1111
- `Name` and `ShortName` to respectively change the long and short form of the option
1212
- `Description` to define a description in the auto-generated help for the option,
1313
- Use any of the `System.ComponentModel.DataAnnotations.ValidationAttribute` derived class to validate the value provided for the option.
14+
15+
If the type of your option is not covered by one of the [built-in converters](../extensibility/built-in-converters.md),
16+
you can create and register [your own](../extensibility/converter.md).

src/MGR.CommandLineParser/Command/ConverterAttribute.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
namespace MGR.CommandLineParser.Command
55
{
66
/// <summary>
7-
/// Defines the converter type for a dictionary property.
7+
/// Defines the converter type for a property.
88
/// </summary>
99
[AttributeUsage(AttributeTargets.Property)]
10+
[Obsolete("Use the generic version of the MGR.CommandLineParser.Command.ConverterAttribute if you use C#11+. This attribute will be removed in a future version.")]
1011
public sealed class ConverterAttribute : Attribute
1112
{
1213
/// <summary>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using MGR.CommandLineParser.Extensibility.Converters;
3+
4+
namespace MGR.CommandLineParser.Command;
5+
6+
/// <summary>
7+
/// Defines the converter type for a property.
8+
/// </summary>
9+
[AttributeUsage(AttributeTargets.Property)]
10+
public sealed class ConverterAttribute<TConverter> : Attribute
11+
where TConverter : IConverter
12+
{ }

src/MGR.CommandLineParser/Command/ConverterKeyValueAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace MGR.CommandLineParser.Command
77
/// Defines the key and the value converter types for a dictionary property.
88
/// </summary>
99
[AttributeUsage(AttributeTargets.Property)]
10+
[Obsolete("Use the generic version of the MGR.CommandLineParser.Command.ConverterKeyValueAttribute if you use C#11+. This attribute will be removed in a future version.")]
1011
public sealed class ConverterKeyValueAttribute : Attribute
1112
{
1213
/// <summary>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using MGR.CommandLineParser.Extensibility.Converters;
3+
4+
namespace MGR.CommandLineParser.Command;
5+
6+
/// <summary>
7+
/// Defines the converter type for a dictionary property.
8+
/// </summary>
9+
[AttributeUsage(AttributeTargets.Property)]
10+
public sealed class ConverterKeyValueAttribute<TKeyConverter> : ConverterKeyValueAttribute<TKeyConverter, StringConverter>
11+
where TKeyConverter : IConverter
12+
{ }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using MGR.CommandLineParser.Extensibility.Converters;
3+
4+
namespace MGR.CommandLineParser.Command;
5+
6+
/// <summary>
7+
/// Defines the key and the value converter types for a dictionary property.
8+
/// </summary>
9+
[AttributeUsage(AttributeTargets.Property)]
10+
public class ConverterKeyValueAttribute<TKeyConverter, TValueConverter> : Attribute
11+
where TKeyConverter : IConverter
12+
where TValueConverter : IConverter
13+
{ }

src/MGR.CommandLineParser/Extensions/ConverterAttributeExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,25 @@ namespace MGR.CommandLineParser.Command
66
{
77
internal static class ConverterAttributeExtensions
88
{
9+
#pragma warning disable CS0618 // Type or member is obsolete
910
internal static IConverter BuildConverter(this ConverterAttribute source)
11+
#pragma warning restore CS0618 // Type or member is obsolete
1012
{
1113
Guard.NotNull(source, nameof(source));
1214

1315
return Activator.CreateInstance(source.ConverterType) as IConverter;
1416
}
17+
#pragma warning disable CS0618 // Type or member is obsolete
1518
internal static IConverter BuildKeyConverter(this ConverterKeyValueAttribute source)
19+
#pragma warning restore CS0618 // Type or member is obsolete
1620
{
1721
Guard.NotNull(source, nameof(source));
1822

1923
return Activator.CreateInstance(source.KeyConverterType) as IConverter;
2024
}
25+
#pragma warning disable CS0618 // Type or member is obsolete
2126
internal static IConverter BuildValueConverter(this ConverterKeyValueAttribute source)
27+
#pragma warning restore CS0618 // Type or member is obsolete
2228
{
2329
Guard.NotNull(source, nameof(source));
2430

src/MGR.CommandLineParser/Extensions/PropertyInfoExtensions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,21 @@ private static IConverter FindKeyConverter(PropertyInfo propertyInfo, IEnumerabl
7070

7171
private static IConverter GetConverterFromAttribute(PropertyInfo propertyInfo, string commandName)
7272
{
73+
var genericConverterAttribute = propertyInfo.GetCustomAttributes(typeof(ConverterAttribute<>), true).FirstOrDefault();
74+
if (genericConverterAttribute != null)
75+
{
76+
var converterType = genericConverterAttribute.GetType().GetGenericArguments()[0];
77+
var converter = Activator.CreateInstance(converterType) as IConverter;
78+
79+
if (!converter.CanConvertTo(propertyInfo.PropertyType))
80+
{
81+
throw new CommandLineParserException(Constants.ExceptionMessages.ParserSpecifiedConverterNotValid(propertyInfo.Name, commandName, propertyInfo.PropertyType, converter.TargetType));
82+
}
83+
return converter;
84+
}
85+
#pragma warning disable CS0618 // Type or member is obsolete
7386
var converterAttribute = propertyInfo.GetCustomAttributes(typeof(ConverterAttribute), true).FirstOrDefault() as ConverterAttribute;
87+
#pragma warning restore CS0618 // Type or member is obsolete
7488
if (converterAttribute != null)
7589
{
7690
var converter = converterAttribute.BuildConverter();
@@ -86,7 +100,24 @@ private static IConverter GetConverterFromAttribute(PropertyInfo propertyInfo, s
86100

87101
private static IConverter GetKeyValueConverterFromAttribute(PropertyInfo propertyInfo, string optionName, string commandName)
88102
{
103+
var genericConverterKeyValuePairAttribute = propertyInfo.GetCustomAttributes(typeof(ConverterKeyValueAttribute<,>), true).FirstOrDefault();
104+
if (genericConverterKeyValuePairAttribute != null)
105+
{
106+
if (!propertyInfo.PropertyType.IsDictionaryType())
107+
{
108+
throw new CommandLineParserException(Constants.ExceptionMessages.ParserExtractConverterKeyValueConverterIsForIDictionaryProperty(optionName, commandName));
109+
}
110+
var genericArguments = genericConverterKeyValuePairAttribute.GetType().GetGenericArguments();
111+
var keyConverterType = genericArguments[0];
112+
var keyConverter = Activator.CreateInstance(keyConverterType) as IConverter;
113+
var valueConverterType = genericArguments.Length == 1 ? typeof(MGR.CommandLineParser.Extensibility.Converters.StringConverter) : genericConverterKeyValuePairAttribute.GetType().GetGenericArguments()[1];
114+
var valueConverter = Activator.CreateInstance(valueConverterType) as IConverter;
115+
116+
return new KeyValueConverter(keyConverter, valueConverter);
117+
}
118+
#pragma warning disable CS0618 // Type or member is obsolete
89119
var converterKeyValuePairAttribute = propertyInfo.GetCustomAttributes(typeof(ConverterKeyValueAttribute), true).FirstOrDefault() as ConverterKeyValueAttribute;
120+
#pragma warning restore CS0618 // Type or member is obsolete
90121
if (converterKeyValuePairAttribute != null)
91122
{
92123
if (!propertyInfo.PropertyType.IsDictionaryType())
@@ -101,7 +132,9 @@ private static IConverter GetKeyValueConverterFromAttribute(PropertyInfo propert
101132
return null;
102133
}
103134

135+
#pragma warning disable CS0618 // Type or member is obsolete
104136
private static IConverter GetValueConverter(PropertyInfo propertyInfo, string optionName, string commandName, ConverterKeyValueAttribute converterKeyValuePairAttribute)
137+
#pragma warning restore CS0618 // Type or member is obsolete
105138
{
106139
var valueType = propertyInfo.PropertyType.GetUnderlyingDictionaryType(false);
107140
var valueConverter = converterKeyValuePairAttribute.BuildValueConverter();
@@ -112,7 +145,9 @@ private static IConverter GetValueConverter(PropertyInfo propertyInfo, string op
112145
return valueConverter;
113146
}
114147

148+
#pragma warning disable CS0618 // Type or member is obsolete
115149
private static IConverter GetKeyConverter(PropertyInfo propertyInfo, string optionName, string commandName, ConverterKeyValueAttribute converterKeyValuePairAttribute)
150+
#pragma warning restore CS0618 // Type or member is obsolete
116151
{
117152
var keyType = propertyInfo.PropertyType.GetUnderlyingDictionaryType(true);
118153
var keyConverter = converterKeyValuePairAttribute.BuildKeyConverter();

tests/MGR.CommandLineParser.IntegrationTests/Help/AsOptionsTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Collections.Generic;
2-
using System.Linq;
32
using System.Threading.Tasks;
43
using MGR.CommandLineParser.Extensibility.ClassBased;
54
using MGR.CommandLineParser.Tests.Commands;

tests/MGR.CommandLineParser.UnitTests/Extensibility/ClassBased/ClassBasedCommandTypeTests.CreateCommand.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public void PropertyWithNoConverterException()
4343
}
4444
private class TestBadConverterCommand : ICommand
4545
{
46+
#pragma warning disable CS0618 // Type or member is obsolete
4647
[Converter(typeof(BooleanConverter))]
48+
#pragma warning restore CS0618 // Type or member is obsolete
4749
// ReSharper disable once UnusedMember.Local
4850
// ReSharper disable once UnusedAutoPropertyAccessor.Local
4951
public int PropertySimpleWithBadConverter { get; set; }

0 commit comments

Comments
 (0)