Custom value converters (#81)

This commit is contained in:
Oleksandr Shustov
2020-10-16 14:22:42 +03:00
committed by GitHub
parent 5e53107def
commit 6a38c04c11
10 changed files with 147 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using CliFx.Tests.Commands;
using CliFx.Tests.Commands.Converters;
using CliFx.Tests.Internal;
using FluentAssertions;
using Xunit;
@@ -1348,5 +1349,70 @@ namespace CliFx.Tests
_output.WriteLine(stdErr.GetString());
}
[Fact]
public async Task Property_of_custom_type_is_bound_when_the_valid_converter_type_is_specified()
{
// Arrange
const string foo = "foo";
var (console, stdOut, _) = VirtualConsole.CreateBuffered();
var application = new CliApplicationBuilder()
.AddCommand<CommandWithParameterOfCustomType>()
.UseConsole(console)
.Build();
// Act
var exitCode = await application.RunAsync(new[]
{
"cmd", "--prop", foo
});
// Assert
exitCode.Should().Be(0);
var commandInstance = stdOut.GetString().DeserializeJson<CommandWithParameterOfCustomType>();
commandInstance.Should().BeEquivalentTo(new CommandWithParameterOfCustomType()
{
MyProperty = (CustomType) new CustomTypeConverter().ConvertFrom(foo)
});
}
[Fact]
public async Task Enumerable_of_the_custom_type_is_bound_when_the_valid_converter_type_is_specified()
{
// Arrange
string foo = "foo";
string bar = "bar";
var (console, stdOut, _) = VirtualConsole.CreateBuffered();
var application = new CliApplicationBuilder()
.AddCommand<CommandWithEnumerableOfParametersOfCustomType>()
.UseConsole(console)
.Build();
// Act
var exitCode = await application.RunAsync(new[]
{
"cmd", "--prop", foo, bar
});
// Assert
exitCode.Should().Be(0);
var commandInstance = stdOut.GetString().DeserializeJson<CommandWithEnumerableOfParametersOfCustomType>();
commandInstance.Should().BeEquivalentTo(new CommandWithEnumerableOfParametersOfCustomType()
{
MyProperties = new List<CustomType>
{
(CustomType) new CustomTypeConverter().ConvertFrom(foo),
(CustomType) new CustomTypeConverter().ConvertFrom(bar)
}
});
}
}
}

View File

@@ -0,0 +1,27 @@
using CliFx.Attributes;
using System;
using System.Threading.Tasks;
using CliFx.Tests.Commands.Converters;
using System.Collections.Generic;
namespace CliFx.Tests.Commands
{
public class CustomType
{
public int SomeValue { get; set; }
}
[Command("cmd")]
public class CommandWithParameterOfCustomType : SelfSerializeCommandBase
{
[CommandOption("prop", Converter = typeof(CustomTypeConverter))]
public CustomType? MyProperty { get; set; }
}
[Command("cmd")]
public class CommandWithEnumerableOfParametersOfCustomType : SelfSerializeCommandBase
{
[CommandOption("prop", Converter = typeof(CustomTypeConverter))]
public List<CustomType>? MyProperties { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace CliFx.Tests.Commands.Converters
{
public class CustomTypeConverter : IArgumentValueConverter
{
public object ConvertFrom(string value) =>
new CustomType { SomeValue = value.Length };
}
}

View File

@@ -37,6 +37,11 @@ namespace CliFx.Attributes
/// </summary>
public string? EnvironmentVariableName { get; set; }
/// <summary>
/// Type of a converter to use for the option value evaluating.
/// </summary>
public Type? Converter { get; set; }
/// <summary>
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
/// </summary>

View File

@@ -26,6 +26,11 @@ namespace CliFx.Attributes
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Type of a converter to use for the parameter value evaluating.
/// </summary>
public Type? Converter { get; set; }
/// <summary>
/// Initializes an instance of <see cref="CommandParameterAttribute"/>.
/// </summary>

View File

@@ -17,10 +17,13 @@ namespace CliFx.Domain
public bool IsScalar => TryGetEnumerableArgumentUnderlyingType() == null;
protected CommandArgumentSchema(PropertyInfo? property, string? description)
protected Type? Converter { get; set; }
protected CommandArgumentSchema(PropertyInfo? property, string? description, Type? converter = null)
{
Property = property;
Description = description;
Converter = converter;
}
private Type? TryGetEnumerableArgumentUnderlyingType() =>
@@ -62,6 +65,9 @@ namespace CliFx.Domain
var parseMethod = targetType.GetStaticParseMethod();
if (parseMethod != null)
return parseMethod.Invoke(null, new object[] {value!});
if (Converter != null)
return Converter.InstanceOf<IArgumentValueConverter>().ConvertFrom(value!);
}
catch (Exception ex)
{

View File

@@ -23,8 +23,9 @@ namespace CliFx.Domain
char? shortName,
string? environmentVariableName,
bool isRequired,
string? description)
: base(property, description)
string? description,
Type? converter = null)
: base(property, description, converter)
{
Name = name;
ShortName = shortName;
@@ -97,7 +98,8 @@ namespace CliFx.Domain
attribute.ShortName,
attribute.EnvironmentVariableName,
attribute.IsRequired,
attribute.Description
attribute.Description,
attribute.Converter
);
}
}

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using CliFx.Attributes;
@@ -11,8 +12,8 @@ namespace CliFx.Domain
public string Name { get; }
public CommandParameterSchema(PropertyInfo? property, int order, string name, string? description)
: base(property, description)
public CommandParameterSchema(PropertyInfo? property, int order, string name, string? description, Type? converter = null)
: base(property, description, converter)
{
Order = order;
Name = name;
@@ -50,7 +51,8 @@ namespace CliFx.Domain
property,
attribute.Order,
name,
attribute.Description
attribute.Description,
attribute.Converter
);
}
}

View File

@@ -0,0 +1,13 @@
namespace CliFx
{
/// <summary>
/// Used as an interface for implementing custom parameter/option converters.
/// </summary>
public interface IArgumentValueConverter
{
/// <summary>
/// Converts an input value to object of required type.
/// </summary>
public object ConvertFrom(string value);
}
}

View File

@@ -56,5 +56,10 @@ namespace CliFx.Internal.Extensions
return array;
}
public static T InstanceOf<T>(this Type type) =>
type.Implements(typeof(T))
? (T) Activator.CreateInstance(type)
: throw new ArgumentException();
}
}