mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
asd
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
namespace CliFx.Demo.Domain;
|
||||
|
||||
[JsonSerializable(typeof(Library))]
|
||||
public partial class LibraryJsonContext : JsonSerializerContext;
|
||||
public partial class LibraryJsonContext : JsonSerializerContext;
|
||||
|
||||
@@ -22,7 +22,8 @@ public class LibraryProvider
|
||||
|
||||
var data = File.ReadAllText(StorageFilePath);
|
||||
|
||||
return JsonSerializer.Deserialize(data, LibraryJsonContext.Default.Library) ?? Library.Empty;
|
||||
return JsonSerializer.Deserialize(data, LibraryJsonContext.Default.Library)
|
||||
?? Library.Empty;
|
||||
}
|
||||
|
||||
public Book? TryGetBook(string title) =>
|
||||
|
||||
@@ -4,9 +4,10 @@ namespace CliFx.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Annotates a type that defines a command.
|
||||
/// If a command is named, then the user must provide its name through the command-line arguments in order to execute it.
|
||||
/// If a command is not named, then it is treated as the application's default command and is executed when no other
|
||||
/// command is specified.
|
||||
/// If the command is named, then the user must provide its name through the
|
||||
/// command-line arguments in order to execute it.
|
||||
/// If the command is not named, then it is treated as the application's
|
||||
/// default command and is executed whenever the user does not provide a command name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only one default command is allowed per application.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// Binds a property to the help option of a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute is applied automatically by the framework and should not be used explicitly.
|
||||
/// This attribute is applied automatically by the framework and should not need to be used explicitly.
|
||||
/// </remarks>
|
||||
public class CommandHelpOptionAttribute : CommandOptionAttribute
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
namespace CliFx.Attributes;
|
||||
|
||||
@@ -7,7 +6,8 @@ namespace CliFx.Attributes;
|
||||
/// Binds a property to a command option — a command-line input that is identified by a name and/or a short name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All options in a command must have unique names (comparison IS NOT case-sensitive) and short names (comparison IS case-sensitive).
|
||||
/// All options in a command must have unique names (comparison IS NOT case-sensitive)
|
||||
/// and short names (comparison IS case-sensitive).
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class CommandOptionAttribute : CommandInputAttribute
|
||||
@@ -51,7 +51,7 @@ public class CommandOptionAttribute : CommandInputAttribute
|
||||
|
||||
/// <summary>
|
||||
/// Whether this option is required (default: <c>false</c>).
|
||||
/// If an option is required, the user will get an error if they don't set it.
|
||||
/// If an option is required, the user will get an error when they don't set it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You can use the <c>required</c> keyword on the property (introduced in C# 11) to implicitly
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
namespace CliFx.Attributes;
|
||||
|
||||
@@ -10,7 +9,7 @@ namespace CliFx.Attributes;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All parameters in a command must have unique order values.
|
||||
/// If a parameter is bound to a property whose type is a sequence (e.g. Array, <see cref="List{T}" />; except <see cref="string" />),
|
||||
/// If a parameter is bound to a property whose type is a sequence (i.e. implements <see cref="IEnumerable{T}"/>; except <see cref="string" />),
|
||||
/// then it must have the highest order in the command.
|
||||
/// Only one sequential parameter is allowed per command.
|
||||
/// </remarks>
|
||||
@@ -24,7 +23,7 @@ public class CommandParameterAttribute(int order) : CommandInputAttribute
|
||||
|
||||
/// <summary>
|
||||
/// Whether this parameter is required (default: <c>true</c>).
|
||||
/// If a parameter is required, the user will get an error if they don't set it.
|
||||
/// If a parameter is required, the user will get an error when they don't set it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Parameter marked as non-required must have the highest order in the command.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// Binds a property to the version option of a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute is applied automatically by the framework and should not be used explicitly.
|
||||
/// This attribute is applied automatically by the framework and should not need to be used explicitly.
|
||||
/// </remarks>
|
||||
public class CommandVersionOptionAttribute : CommandOptionAttribute
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when there is an error during application execution.
|
||||
/// Exception thrown within <see cref="CliFx" />.
|
||||
/// </summary>
|
||||
public partial class CliFxException(
|
||||
string message,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties of type <see cref="bool" />.
|
||||
/// Converter for activating command inputs bound to properties of type <see cref="bool" />.
|
||||
/// </summary>
|
||||
public class BoolBindingConverter : BindingConverter<bool>
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties whose types implement <see cref="IConvertible" />.
|
||||
/// Converter for activating command inputs bound to properties whose types implement <see cref="IConvertible" />.
|
||||
/// </summary>
|
||||
public class ConvertibleBindingConverter<T> : BindingConverter<T>
|
||||
where T : IConvertible
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties of type <see cref="DateTimeOffset" />.
|
||||
/// Converter for activating command inputs bound to properties of type <see cref="DateTimeOffset" />.
|
||||
/// </summary>
|
||||
public class DateTimeOffsetBindingConverter : BindingConverter<DateTimeOffset>
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties using a custom delegate.
|
||||
/// Converter for activating command inputs bound to properties using a custom delegate.
|
||||
/// </summary>
|
||||
public class DelegateBindingConverter<T>(Func<string?, IFormatProvider?, T> convert)
|
||||
: BindingConverter<T>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties of type <see cref="Enum" />.
|
||||
/// Converter for activating command inputs bound to properties of type <see cref="Enum" />.
|
||||
/// </summary>
|
||||
public class EnumBindingConverter<T> : BindingConverter<T>
|
||||
where T : struct, Enum
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties without any conversion.
|
||||
/// Converter for activating command inputs bound to properties without performing any conversion.
|
||||
/// </summary>
|
||||
public class NoopBindingConverter : IBindingConverter
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties of type <see cref="Nullable{T}" />.
|
||||
/// Converter for activating command inputs bound to properties of type <see cref="Nullable{T}" />.
|
||||
/// </summary>
|
||||
public class NullableBindingConverter<T>(BindingConverter<T> innerConverter) : BindingConverter<T?>
|
||||
where T : struct
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace CliFx.Extensibility;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for binding command inputs to properties of type <see cref="TimeSpan" />.
|
||||
/// Converter for activating command inputs bound to properties of type <see cref="TimeSpan" />.
|
||||
/// </summary>
|
||||
public class TimeSpanBindingConverter : BindingConverter<TimeSpan>
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -307,48 +306,14 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
||||
if (defaultValue is null)
|
||||
return;
|
||||
|
||||
// Non-Scalar
|
||||
if (defaultValue is not string && defaultValue is IEnumerable defaultValues)
|
||||
if (schema.Property.Type.IsToStringOverriden())
|
||||
{
|
||||
var elementType =
|
||||
schema.Property.Type.TryGetEnumerableUnderlyingType() ?? typeof(object);
|
||||
Write(ConsoleColor.White, "Default: ");
|
||||
|
||||
if (elementType.IsToStringOverriden())
|
||||
{
|
||||
Write(ConsoleColor.White, "Default: ");
|
||||
|
||||
var isFirst = true;
|
||||
|
||||
foreach (var element in defaultValues)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write('"');
|
||||
Write(element.ToString(CultureInfo.InvariantCulture));
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write('.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (schema.Property.Type.IsToStringOverriden())
|
||||
{
|
||||
Write(ConsoleColor.White, "Default: ");
|
||||
|
||||
Write('"');
|
||||
Write(defaultValue.ToString(CultureInfo.InvariantCulture));
|
||||
Write('"');
|
||||
Write('.');
|
||||
}
|
||||
Write('"');
|
||||
Write(defaultValue.ToString(CultureInfo.InvariantCulture));
|
||||
Write('"');
|
||||
Write('.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
||||
namespace CliFx.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a <see cref="TextReader" /> for reading characters or binary data from a console stream.
|
||||
/// Implements a <see cref="StreamReader" /> for reading characters or binary data from a console stream.
|
||||
/// </summary>
|
||||
// Both the underlying stream AND the stream reader must be synchronized!
|
||||
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||
|
||||
@@ -8,7 +8,7 @@ using CliFx.Utils;
|
||||
namespace CliFx.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a <see cref="TextWriter" /> for writing characters or binary data to a console stream.
|
||||
/// Implements a <see cref="StreamWriter" /> for writing characters or binary data to a console stream.
|
||||
/// </summary>
|
||||
// Both the underlying stream AND the stream writer must be synchronized!
|
||||
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||
|
||||
@@ -5,7 +5,7 @@ using CliFx.Exceptions;
|
||||
namespace CliFx.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ITypeActivator" /> that instantiates an object by using its parameterless constructor.
|
||||
/// Implementation of <see cref="ITypeActivator" /> that instantiates a type by using its parameterless constructor.
|
||||
/// </summary>
|
||||
public class DefaultTypeActivator : ITypeActivator
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using CliFx.Exceptions;
|
||||
namespace CliFx.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ITypeActivator" /> that instantiates an object by using a predefined delegate.
|
||||
/// Implementation of <see cref="ITypeActivator" /> that instantiates a type by using a predefined delegate.
|
||||
/// </summary>
|
||||
public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeActivator
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using CliFx.Exceptions;
|
||||
namespace CliFx.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for a service that can instantiate objects at run-time.
|
||||
/// Abstraction for a service that can instantiate types at run-time.
|
||||
/// </summary>
|
||||
public interface ITypeActivator
|
||||
{
|
||||
|
||||
@@ -14,23 +14,21 @@ namespace CliFx.Schema;
|
||||
/// </summary>
|
||||
public abstract class CommandInputSchema(
|
||||
PropertyBinding property,
|
||||
bool isSequence,
|
||||
string? description,
|
||||
IBindingConverter converter,
|
||||
IReadOnlyList<IBindingValidator> validators
|
||||
)
|
||||
{
|
||||
internal abstract string Kind { get; }
|
||||
|
||||
internal abstract string FormattedIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// CLR property to which this input is bound.
|
||||
/// </summary>
|
||||
public PropertyBinding Property { get; } = property;
|
||||
|
||||
internal bool IsSequence { get; } =
|
||||
property.Type != typeof(string)
|
||||
&& property.Type.TryGetEnumerableUnderlyingType() is not null;
|
||||
/// <summary>
|
||||
/// Whether this input is a sequence (i.e. multiple values can be provided).
|
||||
/// </summary>
|
||||
public bool IsSequence { get; } = isSequence;
|
||||
|
||||
/// <summary>
|
||||
/// Input description, used in the help text.
|
||||
@@ -51,14 +49,14 @@ public abstract class CommandInputSchema(
|
||||
{
|
||||
var errors = Validators
|
||||
.Select(validator => validator.Validate(value))
|
||||
.OfType<BindingValidationError>()
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
throw CliFxException.UserError(
|
||||
$"""
|
||||
{Kind} {FormattedIdentifier} has been provided with an invalid value.
|
||||
{this.GetKind()} {this.GetFormattedIdentifier()} has been provided with an invalid value.
|
||||
Error(s):
|
||||
{errors.Select(e => "- " + e.Message).JoinToString(Environment.NewLine)}
|
||||
"""
|
||||
@@ -72,7 +70,7 @@ public abstract class CommandInputSchema(
|
||||
|
||||
try
|
||||
{
|
||||
// Multiple values expected, single or multiple values provided
|
||||
// Sequential input; zero or more values provided
|
||||
if (IsSequence)
|
||||
{
|
||||
var values = rawValues.Select(v => Converter.Convert(v, formatProvider)).ToArray();
|
||||
@@ -80,23 +78,22 @@ public abstract class CommandInputSchema(
|
||||
// TODO: cast array to the proper type
|
||||
|
||||
Validate(values);
|
||||
|
||||
Property.Set(instance, values);
|
||||
Property.SetValue(instance, values);
|
||||
}
|
||||
// Single value expected, single value provided
|
||||
// Non-sequential input; zero or one value provided
|
||||
else if (rawValues.Count <= 1)
|
||||
{
|
||||
var value = Converter.Convert(rawValues.SingleOrDefault(), formatProvider);
|
||||
Validate(value);
|
||||
|
||||
Property.Set(instance, value);
|
||||
Validate(value);
|
||||
Property.SetValue(instance, value);
|
||||
}
|
||||
// Single value expected, multiple values provided
|
||||
// Non-sequential input; more than one value provided
|
||||
else
|
||||
{
|
||||
throw CliFxException.UserError(
|
||||
$"""
|
||||
{Kind} {FormattedIdentifier} expects a single value, but provided with multiple:
|
||||
{this.GetKind()} {this.GetFormattedIdentifier()} expects a single value, but provided with multiple:
|
||||
{rawValues.Select(v => '<' + v + '>').JoinToString(" ")}
|
||||
"""
|
||||
);
|
||||
@@ -106,7 +103,7 @@ public abstract class CommandInputSchema(
|
||||
{
|
||||
throw CliFxException.UserError(
|
||||
$"""
|
||||
{Kind} {FormattedIdentifier} cannot be set from the provided value(s):
|
||||
{this.GetKind()} {this.GetFormattedIdentifier()} cannot be set from the provided value(s):
|
||||
{rawValues.Select(v => '<' + v + '>').JoinToString(" ")}
|
||||
Error: {ex.Message}
|
||||
""",
|
||||
@@ -117,7 +114,7 @@ public abstract class CommandInputSchema(
|
||||
|
||||
/// <inheritdoc />
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override string ToString() => FormattedIdentifier;
|
||||
public override string ToString() => this.GetFormattedIdentifier();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CommandInputSchema" />
|
||||
@@ -134,8 +131,41 @@ public abstract class CommandInputSchema<
|
||||
TProperty
|
||||
>(
|
||||
PropertyBinding<TCommand, TProperty> property,
|
||||
bool isSequence,
|
||||
string? description,
|
||||
BindingConverter<TProperty> converter,
|
||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||
) : CommandInputSchema(property, description, converter, validators)
|
||||
) : CommandInputSchema(property, isSequence, description, converter, validators)
|
||||
where TCommand : ICommand;
|
||||
|
||||
// Define these as extension methods to avoid exposing them as protected members (i.e. essentially public API)
|
||||
internal static class CommandInputSchemaExtensions
|
||||
{
|
||||
public static string GetKind(this CommandInputSchema schema) =>
|
||||
schema switch
|
||||
{
|
||||
CommandParameterSchema => "Parameter",
|
||||
CommandOptionSchema => "Option",
|
||||
_ => throw new InvalidOperationException("Unknown input schema type.")
|
||||
};
|
||||
|
||||
public static string GetFormattedIdentifier(this CommandInputSchema schema) =>
|
||||
schema switch
|
||||
{
|
||||
CommandParameterSchema parameter
|
||||
=> parameter.IsSequence ? $"<{parameter.Name}>" : $"<{parameter.Name}...>",
|
||||
CommandOptionSchema option
|
||||
=> option switch
|
||||
{
|
||||
{ Name: not null, ShortName: not null }
|
||||
=> $"-{option.ShortName}|--{option.Name}",
|
||||
{ Name: not null } => $"--{option.Name}",
|
||||
{ ShortName: not null } => $"-{option.ShortName}",
|
||||
_
|
||||
=> throw new InvalidOperationException(
|
||||
"Option must have a name or a short name."
|
||||
)
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(schema))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
namespace CliFx.Schema;
|
||||
@@ -11,6 +10,7 @@ namespace CliFx.Schema;
|
||||
/// </summary>
|
||||
public class CommandOptionSchema(
|
||||
PropertyBinding property,
|
||||
bool isSequence,
|
||||
string? name,
|
||||
char? shortName,
|
||||
string? environmentVariable,
|
||||
@@ -18,38 +18,8 @@ public class CommandOptionSchema(
|
||||
string? description,
|
||||
IBindingConverter converter,
|
||||
IReadOnlyList<IBindingValidator> validators
|
||||
) : CommandInputSchema(property, description, converter, validators)
|
||||
) : CommandInputSchema(property, isSequence, description, converter, validators)
|
||||
{
|
||||
internal override string Kind => "Option";
|
||||
|
||||
internal override string FormattedIdentifier
|
||||
{
|
||||
get
|
||||
{
|
||||
var buffer = new StringBuilder();
|
||||
|
||||
// Short name
|
||||
if (ShortName is not null)
|
||||
{
|
||||
buffer.Append('-').Append(ShortName);
|
||||
}
|
||||
|
||||
// Separator
|
||||
if (!string.IsNullOrWhiteSpace(Name) && ShortName is not null)
|
||||
{
|
||||
buffer.Append('|');
|
||||
}
|
||||
|
||||
// Name
|
||||
if (!string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
buffer.Append("--").Append(Name);
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Option name.
|
||||
/// </summary>
|
||||
@@ -99,6 +69,7 @@ public class CommandOptionSchema<
|
||||
TProperty
|
||||
>(
|
||||
PropertyBinding<TCommand, TProperty> property,
|
||||
bool isSequence,
|
||||
string? name,
|
||||
char? shortName,
|
||||
string? environmentVariable,
|
||||
@@ -109,6 +80,7 @@ public class CommandOptionSchema<
|
||||
)
|
||||
: CommandOptionSchema(
|
||||
property,
|
||||
isSequence,
|
||||
name,
|
||||
shortName,
|
||||
environmentVariable,
|
||||
|
||||
@@ -9,20 +9,17 @@ namespace CliFx.Schema;
|
||||
/// </summary>
|
||||
public class CommandParameterSchema(
|
||||
PropertyBinding property,
|
||||
bool isSequence,
|
||||
int order,
|
||||
string name,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
IBindingConverter converter,
|
||||
IReadOnlyList<IBindingValidator> validators
|
||||
) : CommandInputSchema(property, description, converter, validators)
|
||||
) : CommandInputSchema(property, isSequence, description, converter, validators)
|
||||
{
|
||||
internal override string Kind => "Parameter";
|
||||
|
||||
internal override string FormattedIdentifier => IsSequence ? $"<{Name}>" : $"<{Name}...>";
|
||||
|
||||
/// <summary>
|
||||
/// Order, in which the parameter is bound from the command-line arguments.
|
||||
/// Order, in which the parameter is activated from the command-line arguments.
|
||||
/// </summary>
|
||||
public int Order { get; } = order;
|
||||
|
||||
@@ -51,11 +48,22 @@ public class CommandParameterSchema<
|
||||
TProperty
|
||||
>(
|
||||
PropertyBinding<TCommand, TProperty> property,
|
||||
bool isSequence,
|
||||
int order,
|
||||
string name,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
BindingConverter<TProperty> converter,
|
||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||
) : CommandParameterSchema(property, order, name, isRequired, description, converter, validators)
|
||||
)
|
||||
: CommandParameterSchema(
|
||||
property,
|
||||
isSequence,
|
||||
order,
|
||||
name,
|
||||
isRequired,
|
||||
description,
|
||||
converter,
|
||||
validators
|
||||
)
|
||||
where TCommand : ICommand;
|
||||
|
||||
@@ -62,24 +62,8 @@ public class CommandSchema(
|
||||
? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase)
|
||||
: string.IsNullOrWhiteSpace(name);
|
||||
|
||||
internal IReadOnlyDictionary<CommandInputSchema, object?> GetValues(ICommand instance)
|
||||
{
|
||||
var result = new Dictionary<CommandInputSchema, object?>();
|
||||
|
||||
foreach (var parameter in Parameters)
|
||||
{
|
||||
var value = parameter.Property.Get(instance);
|
||||
result[parameter] = value;
|
||||
}
|
||||
|
||||
foreach (var option in Options)
|
||||
{
|
||||
var value = option.Property.Get(instance);
|
||||
result[option] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
internal IReadOnlyDictionary<CommandInputSchema, object?> GetValues(ICommand instance) =>
|
||||
Inputs.ToDictionary(input => input, input => input.Property.GetValue(instance));
|
||||
|
||||
private void ActivateParameters(ICommand instance, CommandArguments arguments)
|
||||
{
|
||||
@@ -91,21 +75,20 @@ public class CommandSchema(
|
||||
|
||||
foreach (var parameter in Parameters.OrderBy(p => p.Order))
|
||||
{
|
||||
// Break when there are no remaining inputs
|
||||
// Break when there are no remaining tokens
|
||||
if (position >= arguments.Parameters.Count)
|
||||
break;
|
||||
|
||||
// Sequence: take all remaining inputs starting from the current position
|
||||
// Sequential: take all remaining tokens starting from the current position
|
||||
if (parameter.IsSequence)
|
||||
{
|
||||
var parameterTokens = arguments.Parameters.Skip(position).ToArray();
|
||||
|
||||
parameter.Activate(instance, parameterTokens.Select(p => p.Value).ToArray());
|
||||
|
||||
position += parameterTokens.Length;
|
||||
remainingParameterTokens.RemoveRange(parameterTokens);
|
||||
}
|
||||
// Non-sequence: take one input at the current position
|
||||
// Non-sequential: take one token at the current position
|
||||
else
|
||||
{
|
||||
var parameterToken = arguments.Parameters[position];
|
||||
@@ -132,9 +115,9 @@ public class CommandSchema(
|
||||
{
|
||||
throw CliFxException.UserError(
|
||||
$"""
|
||||
Missing equired parameter(s):
|
||||
Missing required parameter(s):
|
||||
{remainingRequiredParameters
|
||||
.Select(p => p.FormattedIdentifier)
|
||||
.Select(p => p.GetFormattedIdentifier())
|
||||
.JoinToString(" ")}
|
||||
"""
|
||||
);
|
||||
@@ -153,7 +136,7 @@ public class CommandSchema(
|
||||
|
||||
foreach (var option in Options)
|
||||
{
|
||||
var optionToken = arguments
|
||||
var optionTokens = arguments
|
||||
.Options.Where(o => option.MatchesIdentifier(o.Identifier))
|
||||
.ToArray();
|
||||
|
||||
@@ -161,10 +144,10 @@ public class CommandSchema(
|
||||
option.MatchesEnvironmentVariable(v.Key)
|
||||
);
|
||||
|
||||
// Direct input
|
||||
if (optionToken.Any())
|
||||
// From arguments
|
||||
if (optionTokens.Any())
|
||||
{
|
||||
var rawValues = optionToken.SelectMany(o => o.Values).ToArray();
|
||||
var rawValues = optionTokens.SelectMany(o => o.Values).ToArray();
|
||||
|
||||
option.Activate(instance, rawValues);
|
||||
|
||||
@@ -172,7 +155,7 @@ public class CommandSchema(
|
||||
if (rawValues.Any())
|
||||
remainingRequiredOptions.Remove(option);
|
||||
}
|
||||
// Environment variable
|
||||
// From environment
|
||||
else if (!string.IsNullOrEmpty(environmentVariable.Value))
|
||||
{
|
||||
var rawValues = !option.IsSequence
|
||||
@@ -194,7 +177,7 @@ public class CommandSchema(
|
||||
continue;
|
||||
}
|
||||
|
||||
remainingOptionTokens.RemoveRange(optionToken);
|
||||
remainingOptionTokens.RemoveRange(optionTokens);
|
||||
}
|
||||
|
||||
if (remainingOptionTokens.Any())
|
||||
@@ -213,7 +196,7 @@ public class CommandSchema(
|
||||
$"""
|
||||
Missing required option(s):
|
||||
{remainingRequiredOptions
|
||||
.Select(o => o.FormattedIdentifier)
|
||||
.Select(o => o.GetFormattedIdentifier())
|
||||
.JoinToString(", ")}
|
||||
"""
|
||||
);
|
||||
|
||||
@@ -13,8 +13,8 @@ public class PropertyBinding(
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
Type type,
|
||||
Func<object, object?> get,
|
||||
Action<object, object?> set
|
||||
Func<object, object?> getValue,
|
||||
Action<object, object?> setValue
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
@@ -28,12 +28,12 @@ public class PropertyBinding(
|
||||
/// <summary>
|
||||
/// Gets the current value of the property on the specified instance.
|
||||
/// </summary>
|
||||
public object? Get(object instance) => get(instance);
|
||||
public object? GetValue(object instance) => getValue(instance);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current value of the property on the specified instance.
|
||||
/// </summary>
|
||||
public void Set(object instance, object? value) => set(instance, value);
|
||||
public void SetValue(object instance, object? value) => setValue(instance, value);
|
||||
|
||||
internal IReadOnlyList<object?>? TryGetValidValues()
|
||||
{
|
||||
@@ -67,9 +67,9 @@ public class PropertyBinding<
|
||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||
)]
|
||||
TProperty
|
||||
>(Func<TObject, TProperty?> get, Action<TObject, TProperty?> set)
|
||||
>(Func<TObject, TProperty?> getValue, Action<TObject, TProperty?> setValue)
|
||||
: PropertyBinding(
|
||||
typeof(TProperty),
|
||||
o => get((TObject)o),
|
||||
(o, v) => set((TObject)o, (TProperty?)v)
|
||||
o => getValue((TObject)o),
|
||||
(o, v) => setValue((TObject)o, (TProperty?)v)
|
||||
);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CliFx.Utils.Extensions;
|
||||
|
||||
internal static class PropertyExtensions
|
||||
{
|
||||
public static bool IsRequired(this PropertyInfo propertyInfo) =>
|
||||
// Match attribute by name to avoid depending on .NET 7.0+ and to allow polyfilling
|
||||
propertyInfo
|
||||
.GetCustomAttributes()
|
||||
.Any(a =>
|
||||
string.Equals(
|
||||
a.GetType().FullName,
|
||||
"System.Runtime.CompilerServices.RequiredMemberAttribute",
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,33 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace CliFx.Utils.Extensions;
|
||||
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
public static Type? TryGetEnumerableUnderlyingType(
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type
|
||||
) =>
|
||||
type.GetInterfaces()
|
||||
.Select(i =>
|
||||
{
|
||||
if (i == typeof(IEnumerable))
|
||||
return typeof(object);
|
||||
|
||||
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
return i.GetGenericArguments().FirstOrDefault();
|
||||
|
||||
return null;
|
||||
})
|
||||
.WhereNotNull()
|
||||
// Every IEnumerable<T> implements IEnumerable (which is essentially IEnumerable<object>),
|
||||
// so we try to get a more specific underlying type. Still, if the type only implements
|
||||
// IEnumerable<object> and nothing else, then we'll just return that.
|
||||
.MaxBy(t => t != typeof(object));
|
||||
|
||||
public static bool IsToStringOverriden(
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user