Добавьте файлы проекта.
This commit is contained in:
30
ArgumentsToolkit.Core/ArgumentsToolkit.Core.csproj
Normal file
30
ArgumentsToolkit.Core/ArgumentsToolkit.Core.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Целевая платформа -->
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<!-- Информация о пакете -->
|
||||
<PackageId>ArgumentsToolkit.Core</PackageId>
|
||||
<Version>0.1.0</Version>
|
||||
<Authors>FrigaT</Authors>
|
||||
<Company>FrigaT</Company>
|
||||
<Description>Ядро библиотеки ArgumentsToolkit: парсинг аргументов, генерация строки, обязательные параметры, базовые конвертеры.</Description>
|
||||
<PackageTags>cli arguments parser toolkit core</PackageTags>
|
||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/ArgumentsToolkit</RepositoryUrl>
|
||||
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/ArgumentsToolkit</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
<!-- Сборка и публикация -->
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
|
||||
<!-- Документация и качество кода -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
49
ArgumentsToolkit.Core/Attributes/OptionAttribute.cs
Normal file
49
ArgumentsToolkit.Core/Attributes/OptionAttribute.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Атрибут для описания параметра командной строки.
|
||||
/// Позволяет задать имя, короткое имя, описание, обязательность и значение по умолчанию.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Поолное название для --Name
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Краткое название для -ShortName
|
||||
/// </summary>
|
||||
public string? ShortName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Описание параметра
|
||||
/// </summary>
|
||||
public string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Обязательный параметр
|
||||
/// </summary>
|
||||
public bool Required { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение по умолчанию
|
||||
/// </summary>
|
||||
public object? DefaultValue { get; }
|
||||
|
||||
public OptionAttribute(
|
||||
string name,
|
||||
string? shortName = null,
|
||||
string? description = null,
|
||||
bool required = false,
|
||||
object? defaultValue = null
|
||||
)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
ShortName = shortName;
|
||||
Description = description;
|
||||
Required = required;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
15
ArgumentsToolkit.Core/Attributes/OptionConverterAttribute.cs
Normal file
15
ArgumentsToolkit.Core/Attributes/OptionConverterAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Атрибут для указания кастомного конвертера для свойства.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class OptionConverterAttribute : Attribute
|
||||
{
|
||||
public Type ConverterType { get; }
|
||||
|
||||
public OptionConverterAttribute(Type converterType)
|
||||
{
|
||||
ConverterType = converterType;
|
||||
}
|
||||
}
|
||||
93
ArgumentsToolkit.Core/Converters/Converters.cs
Normal file
93
ArgumentsToolkit.Core/Converters/Converters.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
public class DateTimeOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(DateTime);
|
||||
public object Convert(Type targetType, string value) => DateTime.Parse(value);
|
||||
}
|
||||
|
||||
public class TimeSpanOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(TimeSpan);
|
||||
public object Convert(Type targetType, string value) => TimeSpan.Parse(value);
|
||||
}
|
||||
|
||||
public class JsonOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => true;
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
return System.Text.Json.JsonSerializer.Deserialize(value, targetType)!;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(bool);
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
// когда указали просто флаг без значения
|
||||
if (string.IsNullOrEmpty(value)) { return true; }
|
||||
|
||||
if (bool.TryParse(value, out var b)) { return b; }
|
||||
|
||||
if (value == "0" || value.Equals("false", StringComparison.OrdinalIgnoreCase)) { return true; }
|
||||
if (value == "1" || value.Equals("true", StringComparison.OrdinalIgnoreCase)) { return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class EnumOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType.IsEnum;
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
return Enum.Parse(targetType, value, ignoreCase: true);
|
||||
}
|
||||
}
|
||||
|
||||
public class IntOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(int);
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
return int.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public class LongOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(long);
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
return long.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public class DoubleOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(double);
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
return double.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public class DecimalOptionConverter : IOptionConverter
|
||||
{
|
||||
public bool CanConvert(Type targetType) => targetType == typeof(decimal);
|
||||
|
||||
public object Convert(Type targetType, string value)
|
||||
{
|
||||
return decimal.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
17
ArgumentsToolkit.Core/Converters/IOptionConverter.cs
Normal file
17
ArgumentsToolkit.Core/Converters/IOptionConverter.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для конвертера значений аргументов.
|
||||
/// </summary>
|
||||
public interface IOptionConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Проверяет, может ли данный конвертер обработать указанный тип.
|
||||
/// </summary>
|
||||
bool CanConvert(Type targetType);
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует строковое значение в указанный тип.
|
||||
/// </summary>
|
||||
object Convert(Type targetType, string value);
|
||||
}
|
||||
43
ArgumentsToolkit.Core/Converters/TypeConverters.cs
Normal file
43
ArgumentsToolkit.Core/Converters/TypeConverters.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
internal static class TypeConverters
|
||||
{
|
||||
public static bool TryConvert(string input, Type targetType, out object? value)
|
||||
{
|
||||
value = null;
|
||||
try
|
||||
{
|
||||
if (targetType == typeof(string)) { value = input; return true; }
|
||||
if (targetType == typeof(bool))
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) { value = true; return true; }
|
||||
if (bool.TryParse(input, out var b)) { value = b; return true; }
|
||||
if (input == "0" || input.Equals("false", StringComparison.OrdinalIgnoreCase)) { value = false; return true; }
|
||||
if (input == "1" || input.Equals("true", StringComparison.OrdinalIgnoreCase)) { value = true; return true; }
|
||||
return false;
|
||||
}
|
||||
if (targetType.IsEnum) { value = Enum.Parse(targetType, input, ignoreCase: true); return true; }
|
||||
if (targetType == typeof(int)) { value = int.Parse(input, CultureInfo.InvariantCulture); return true; }
|
||||
if (targetType == typeof(long)) { value = long.Parse(input, CultureInfo.InvariantCulture); return true; }
|
||||
if (targetType == typeof(double)) { value = double.Parse(input, CultureInfo.InvariantCulture); return true; }
|
||||
if (targetType == typeof(decimal)) { value = decimal.Parse(input, CultureInfo.InvariantCulture); return true; }
|
||||
if (targetType == typeof(Uri)) { value = new Uri(input, UriKind.RelativeOrAbsolute); return true; }
|
||||
|
||||
// Nullable<T>
|
||||
if (Nullable.GetUnderlyingType(targetType) is Type underlying)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) { value = null; return true; }
|
||||
return TryConvert(input, underlying, out value);
|
||||
}
|
||||
|
||||
// Custom types with string ctor
|
||||
var ctor = targetType.GetConstructor(new[] { typeof(string) });
|
||||
if (ctor != null) { value = ctor.Invoke(new object[] { input }); return true; }
|
||||
|
||||
return false;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
3
ArgumentsToolkit.Core/Errors/ArgumentError.cs
Normal file
3
ArgumentsToolkit.Core/Errors/ArgumentError.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
public record ArgumentError(ErrorCode Code, string Message, string? Option = null);
|
||||
14
ArgumentsToolkit.Core/Errors/ErrorCode.cs
Normal file
14
ArgumentsToolkit.Core/Errors/ErrorCode.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Ошибки парсера
|
||||
/// </summary>
|
||||
public enum ErrorCode
|
||||
{
|
||||
UnknownOption,
|
||||
MissingValue,
|
||||
InvalidValue,
|
||||
MissingRequired,
|
||||
DuplicateOption,
|
||||
ConversionFailed,
|
||||
}
|
||||
10
ArgumentsToolkit.Core/Exceptions.cs
Normal file
10
ArgumentsToolkit.Core/Exceptions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Исключение для отсутствующих обязательных параметров.
|
||||
/// </summary>
|
||||
public class MissingRequiredOptionException : Exception
|
||||
{
|
||||
public MissingRequiredOptionException(string optionName)
|
||||
: base($"Обязательный параметр '--{optionName}' не указан.") { }
|
||||
}
|
||||
308
ArgumentsToolkit.Core/Parsers/ArgumentsParser.cs
Normal file
308
ArgumentsToolkit.Core/Parsers/ArgumentsParser.cs
Normal file
@@ -0,0 +1,308 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Основной парсер аргументов командной строки.
|
||||
/// Преобразует массив строковых аргументов в объект указанного типа.
|
||||
/// </summary>
|
||||
public static class ArgumentsParser
|
||||
{
|
||||
private static readonly List<IOptionConverter> _converters = new()
|
||||
{
|
||||
new DateTimeOptionConverter(),
|
||||
new TimeSpanOptionConverter(),
|
||||
new BoolOptionConverter(),
|
||||
new IntOptionConverter(),
|
||||
new LongOptionConverter(),
|
||||
new DoubleOptionConverter(),
|
||||
new DecimalOptionConverter(),
|
||||
new EnumOptionConverter(),
|
||||
};
|
||||
|
||||
public static void RegisterConverter(IOptionConverter converter) => _converters.Add(converter);
|
||||
public static void RegisterConverter<TConverter>()
|
||||
where TConverter : IOptionConverter, new()
|
||||
=> _converters.Add(new TConverter());
|
||||
|
||||
/// <summary>
|
||||
/// Парсит массив аргументов <paramref name="args"/> в объект типа <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип модели опций, содержащий свойства с атрибутами <see cref="OptionAttribute"/>.</typeparam>
|
||||
/// <param name="args">Массив аргументов командной строки.</param>
|
||||
/// <returns>Результат парсинга, содержащий объект и список ошибок.</returns>
|
||||
|
||||
public static ParseResult<T> Parse<T>(string[] args)
|
||||
where T : class, new()
|
||||
{
|
||||
var result = new ParseResult<T> { Success = true, Value = new T() };
|
||||
|
||||
var props = typeof(T).GetProperties();
|
||||
|
||||
var optionMap = BuildOptionMap(typeof(T));
|
||||
var valuesByProperty = new Dictionary<PropertyInfo, object?>();
|
||||
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
var token = args[i];
|
||||
|
||||
if (!IsOptionToken(token))
|
||||
{
|
||||
result.Errors.Add(new ArgumentError(ErrorCode.UnknownOption, $"Не является токеном '{token}'"));
|
||||
result.Success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!optionMap.TryGetValue(token, out var prop))
|
||||
{
|
||||
result.Errors.Add(new ArgumentError(ErrorCode.UnknownOption, $"Неизвестный токен '{token}'", token));
|
||||
result.Success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetType = prop.PropertyType;
|
||||
|
||||
// Значение аргумента
|
||||
string? raw = null;
|
||||
if (targetType == typeof(bool) || targetType == typeof(bool?))
|
||||
{
|
||||
// Флаг: --flag или --flag=true
|
||||
if (i + 1 < args.Length && !IsOptionToken(args[i + 1]))
|
||||
raw = args[++i];
|
||||
else
|
||||
raw = string.Empty; // true
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i + 1 >= args.Length || IsOptionToken(args[i + 1]))
|
||||
{
|
||||
result.Errors.Add(new ArgumentError(ErrorCode.MissingValue, $"Токен '{token}' ожидает значение"));
|
||||
result.Success = false;
|
||||
continue;
|
||||
}
|
||||
raw = args[++i];
|
||||
}
|
||||
|
||||
// Выбор конвертера
|
||||
var converterAttr = prop.GetCustomAttribute<OptionConverterAttribute>();
|
||||
var converter = converterAttr != null ? (IOptionConverter)Activator.CreateInstance(converterAttr.ConverterType)! : null;
|
||||
|
||||
|
||||
if (TryParse(targetType, raw, converter, out var converted))
|
||||
{
|
||||
//TODO: Generic List / Dictionary
|
||||
if (valuesByProperty.ContainsKey(prop))
|
||||
{
|
||||
result.Errors.Add(new ArgumentError(ErrorCode.DuplicateOption, $"Токен '{token}' указан несколько раз"));
|
||||
result.Success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
valuesByProperty[prop] = converted;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Errors.Add(new ArgumentError(ErrorCode.ConversionFailed, $"Не найден конвертер для {targetType.Name}", token));
|
||||
result.Success = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Применение значений по умолчанию и проверка обязательных
|
||||
foreach (var prop in optionMap.Values.Distinct())
|
||||
{
|
||||
var opt = prop.GetCustomAttribute<OptionAttribute>()!;
|
||||
if (!valuesByProperty.TryGetValue(prop, out var value))
|
||||
{
|
||||
if (opt.Required)
|
||||
{
|
||||
result.Errors.Add(new ArgumentError(ErrorCode.MissingRequired, $"Отсутствует обязательный токен '--{opt.Name}'", opt.Name));
|
||||
result.Success = false;
|
||||
}
|
||||
else if (opt.DefaultValue is not null)
|
||||
{
|
||||
valuesByProperty[prop] = opt.DefaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Заполнение объекта
|
||||
foreach (var kv in valuesByProperty)
|
||||
{
|
||||
kv.Key.SetValue(result.Value, kv.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ToArguments<T>(T options)
|
||||
{
|
||||
var props = typeof(T).GetProperties();
|
||||
var parts = new List<string>();
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var opt = prop.GetCustomAttribute<OptionAttribute>();
|
||||
if (opt == null) continue;
|
||||
|
||||
var value = prop.GetValue(options);
|
||||
if (value == null) continue;
|
||||
|
||||
string prefix = "--" + opt.Name;
|
||||
|
||||
if (prop.PropertyType == typeof(bool))
|
||||
{
|
||||
if ((bool)value)
|
||||
parts.Add(prefix);
|
||||
}
|
||||
else if (prop.PropertyType.IsGenericType &&
|
||||
prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
var list = (System.Collections.IList)value;
|
||||
if (list.Count > 0)
|
||||
parts.Add($"{prefix} {string.Join(",", list.Cast<object>())}");
|
||||
}
|
||||
else if (prop.PropertyType.IsGenericType &&
|
||||
prop.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
var dict = (System.Collections.IDictionary)value;
|
||||
var items = new List<string>();
|
||||
foreach (var k in dict.Keys)
|
||||
items.Add($"{k}={dict[k]}");
|
||||
if (items.Count > 0)
|
||||
parts.Add($"{prefix} {string.Join(",", items)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
parts.Add($"{prefix} {value}");
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(" ", parts);
|
||||
}
|
||||
|
||||
private static bool TryParse(Type targetType, string input, IOptionConverter? converterAttr, out object? value)
|
||||
{
|
||||
if (converterAttr != null && converterAttr.CanConvert(targetType))
|
||||
{
|
||||
try
|
||||
{
|
||||
value = converterAttr.Convert(targetType, input);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Nullable<T>
|
||||
if (Nullable.GetUnderlyingType(targetType) is Type underlying)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) { value = input; return true; }
|
||||
return TryParse(underlying, input, converterAttr, out value);
|
||||
}
|
||||
|
||||
var converter = _converters.LastOrDefault(c => c.CanConvert(targetType));
|
||||
if (converter != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = converter.Convert(targetType, input);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Пользовательские типы с string ctor
|
||||
var ctor = targetType.GetConstructor([typeof(string)]);
|
||||
if (ctor != null) { value = ctor.Invoke([input]); return true; }
|
||||
|
||||
|
||||
//TODO: Generic types
|
||||
/*
|
||||
if (targetType.IsGenericType)
|
||||
{
|
||||
var genType = targetType.GetGenericTypeDefinition();
|
||||
|
||||
if (genType == typeof(List<>))
|
||||
{
|
||||
var elementType = targetType.GetGenericArguments()[0];
|
||||
var list = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType))!;
|
||||
foreach (var item in value.Split(','))
|
||||
list.Add(Convert.ChangeType(item.Trim(), elementType));
|
||||
return list;
|
||||
}
|
||||
|
||||
if (genType == typeof(Dictionary<,>))
|
||||
{
|
||||
var keyType = targetType.GetGenericArguments()[0];
|
||||
var valueType = targetType.GetGenericArguments()[1];
|
||||
var dict = (System.Collections.IDictionary)Activator.CreateInstance(targetType)!;
|
||||
|
||||
foreach (var pair in value.Split(','))
|
||||
{
|
||||
var kv = pair.Split('=');
|
||||
if (kv.Length == 2)
|
||||
{
|
||||
var k = Convert.ChangeType(kv[0].Trim(), keyType);
|
||||
var v = Convert.ChangeType(kv[1].Trim(), valueType);
|
||||
dict.Add(k, v);
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
value = Convert.ChangeType(input, targetType);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, является ли токен аргументом (начинается с '-' или '--').
|
||||
/// </summary>
|
||||
private static bool IsOptionToken(string token) => token.StartsWith("-");
|
||||
|
||||
/// <summary>
|
||||
/// Строит карту имён аргументов к свойствам модели.
|
||||
/// </summary>
|
||||
private static Dictionary<string, PropertyInfo> BuildOptionMap(Type optionsType)
|
||||
{
|
||||
var map = new Dictionary<string, PropertyInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var prop in optionsType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
var opt = prop.GetCustomAttribute<OptionAttribute>();
|
||||
if (opt == null) continue;
|
||||
|
||||
void add(string key)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(key))
|
||||
map[key] = prop;
|
||||
}
|
||||
|
||||
add("--" + opt.Name);
|
||||
add(("-" + opt.ShortName) ?? string.Empty);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
9
ArgumentsToolkit.Core/Parsers/ParseResult.cs
Normal file
9
ArgumentsToolkit.Core/Parsers/ParseResult.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ArgumentsToolkit;
|
||||
|
||||
public class ParseResult<T> where T : class, new()
|
||||
{
|
||||
public T? Value { get; internal set; }
|
||||
public bool Success { get; internal set; }
|
||||
public List<ArgumentError> Errors { get; } = new();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user