mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
asd
This commit is contained in:
@@ -22,7 +22,8 @@ public class LibraryProvider
|
|||||||
|
|
||||||
var data = File.ReadAllText(StorageFilePath);
|
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) =>
|
public Book? TryGetBook(string title) =>
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ namespace CliFx.Attributes;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Annotates a type that defines a command.
|
/// 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 the command is named, then the user must provide its name through the
|
||||||
/// If a command is not named, then it is treated as the application's default command and is executed when no other
|
/// command-line arguments in order to execute it.
|
||||||
/// command is specified.
|
/// 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>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Only one default command is allowed per application.
|
/// Only one default command is allowed per application.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
/// Binds a property to the help option of a command.
|
/// Binds a property to the help option of a command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <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>
|
/// </remarks>
|
||||||
public class CommandHelpOptionAttribute : CommandOptionAttribute
|
public class CommandHelpOptionAttribute : CommandOptionAttribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Extensibility;
|
|
||||||
|
|
||||||
namespace CliFx.Attributes;
|
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.
|
/// Binds a property to a command option — a command-line input that is identified by a name and/or a short name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <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>
|
/// </remarks>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
public class CommandOptionAttribute : CommandInputAttribute
|
public class CommandOptionAttribute : CommandInputAttribute
|
||||||
@@ -51,7 +51,7 @@ public class CommandOptionAttribute : CommandInputAttribute
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this option is required (default: <c>false</c>).
|
/// 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>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// You can use the <c>required</c> keyword on the property (introduced in C# 11) to implicitly
|
/// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CliFx.Extensibility;
|
|
||||||
|
|
||||||
namespace CliFx.Attributes;
|
namespace CliFx.Attributes;
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ namespace CliFx.Attributes;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// All parameters in a command must have unique order values.
|
/// 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.
|
/// then it must have the highest order in the command.
|
||||||
/// Only one sequential parameter is allowed per command.
|
/// Only one sequential parameter is allowed per command.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
@@ -24,7 +23,7 @@ public class CommandParameterAttribute(int order) : CommandInputAttribute
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this parameter is required (default: <c>true</c>).
|
/// 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>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Parameter marked as non-required must have the highest order in the command.
|
/// 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.
|
/// Binds a property to the version option of a command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <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>
|
/// </remarks>
|
||||||
public class CommandVersionOptionAttribute : CommandOptionAttribute
|
public class CommandVersionOptionAttribute : CommandOptionAttribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Exceptions;
|
namespace CliFx.Exceptions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exception thrown when there is an error during application execution.
|
/// Exception thrown within <see cref="CliFx" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CliFxException(
|
public partial class CliFxException(
|
||||||
string message,
|
string message,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class BoolBindingConverter : BindingConverter<bool>
|
public class BoolBindingConverter : BindingConverter<bool>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class ConvertibleBindingConverter<T> : BindingConverter<T>
|
public class ConvertibleBindingConverter<T> : BindingConverter<T>
|
||||||
where T : IConvertible
|
where T : IConvertible
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class DateTimeOffsetBindingConverter : BindingConverter<DateTimeOffset>
|
public class DateTimeOffsetBindingConverter : BindingConverter<DateTimeOffset>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class DelegateBindingConverter<T>(Func<string?, IFormatProvider?, T> convert)
|
public class DelegateBindingConverter<T>(Func<string?, IFormatProvider?, T> convert)
|
||||||
: BindingConverter<T>
|
: BindingConverter<T>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class EnumBindingConverter<T> : BindingConverter<T>
|
public class EnumBindingConverter<T> : BindingConverter<T>
|
||||||
where T : struct, Enum
|
where T : struct, Enum
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converter for binding command inputs to properties without any conversion.
|
/// Converter for activating command inputs bound to properties without performing any conversion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NoopBindingConverter : IBindingConverter
|
public class NoopBindingConverter : IBindingConverter
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class NullableBindingConverter<T>(BindingConverter<T> innerConverter) : BindingConverter<T?>
|
public class NullableBindingConverter<T>(BindingConverter<T> innerConverter) : BindingConverter<T?>
|
||||||
where T : struct
|
where T : struct
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace CliFx.Extensibility;
|
namespace CliFx.Extensibility;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class TimeSpanBindingConverter : BindingConverter<TimeSpan>
|
public class TimeSpanBindingConverter : BindingConverter<TimeSpan>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -307,48 +306,14 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
|||||||
if (defaultValue is null)
|
if (defaultValue is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Non-Scalar
|
if (schema.Property.Type.IsToStringOverriden())
|
||||||
if (defaultValue is not string && defaultValue is IEnumerable defaultValues)
|
|
||||||
{
|
{
|
||||||
var elementType =
|
Write(ConsoleColor.White, "Default: ");
|
||||||
schema.Property.Type.TryGetEnumerableUnderlyingType() ?? typeof(object);
|
|
||||||
|
|
||||||
if (elementType.IsToStringOverriden())
|
Write('"');
|
||||||
{
|
Write(defaultValue.ToString(CultureInfo.InvariantCulture));
|
||||||
Write(ConsoleColor.White, "Default: ");
|
Write('"');
|
||||||
|
Write('.');
|
||||||
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('.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
// Both the underlying stream AND the stream reader must be synchronized!
|
// Both the underlying stream AND the stream reader must be synchronized!
|
||||||
// https://github.com/Tyrrrz/CliFx/issues/123
|
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using CliFx.Utils;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
// Both the underlying stream AND the stream writer must be synchronized!
|
// Both the underlying stream AND the stream writer must be synchronized!
|
||||||
// https://github.com/Tyrrrz/CliFx/issues/123
|
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using CliFx.Exceptions;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class DefaultTypeActivator : ITypeActivator
|
public class DefaultTypeActivator : ITypeActivator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using CliFx.Exceptions;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeActivator
|
public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeActivator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using CliFx.Exceptions;
|
|||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstraction for a service that can instantiate objects at run-time.
|
/// Abstraction for a service that can instantiate types at run-time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITypeActivator
|
public interface ITypeActivator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,23 +14,21 @@ namespace CliFx.Schema;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class CommandInputSchema(
|
public abstract class CommandInputSchema(
|
||||||
PropertyBinding property,
|
PropertyBinding property,
|
||||||
|
bool isSequence,
|
||||||
string? description,
|
string? description,
|
||||||
IBindingConverter converter,
|
IBindingConverter converter,
|
||||||
IReadOnlyList<IBindingValidator> validators
|
IReadOnlyList<IBindingValidator> validators
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
internal abstract string Kind { get; }
|
|
||||||
|
|
||||||
internal abstract string FormattedIdentifier { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CLR property to which this input is bound.
|
/// CLR property to which this input is bound.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PropertyBinding Property { get; } = property;
|
public PropertyBinding Property { get; } = property;
|
||||||
|
|
||||||
internal bool IsSequence { get; } =
|
/// <summary>
|
||||||
property.Type != typeof(string)
|
/// Whether this input is a sequence (i.e. multiple values can be provided).
|
||||||
&& property.Type.TryGetEnumerableUnderlyingType() is not null;
|
/// </summary>
|
||||||
|
public bool IsSequence { get; } = isSequence;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input description, used in the help text.
|
/// Input description, used in the help text.
|
||||||
@@ -51,14 +49,14 @@ public abstract class CommandInputSchema(
|
|||||||
{
|
{
|
||||||
var errors = Validators
|
var errors = Validators
|
||||||
.Select(validator => validator.Validate(value))
|
.Select(validator => validator.Validate(value))
|
||||||
.OfType<BindingValidationError>()
|
.WhereNotNull()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
if (errors.Any())
|
if (errors.Any())
|
||||||
{
|
{
|
||||||
throw CliFxException.UserError(
|
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):
|
Error(s):
|
||||||
{errors.Select(e => "- " + e.Message).JoinToString(Environment.NewLine)}
|
{errors.Select(e => "- " + e.Message).JoinToString(Environment.NewLine)}
|
||||||
"""
|
"""
|
||||||
@@ -72,7 +70,7 @@ public abstract class CommandInputSchema(
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Multiple values expected, single or multiple values provided
|
// Sequential input; zero or more values provided
|
||||||
if (IsSequence)
|
if (IsSequence)
|
||||||
{
|
{
|
||||||
var values = rawValues.Select(v => Converter.Convert(v, formatProvider)).ToArray();
|
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
|
// TODO: cast array to the proper type
|
||||||
|
|
||||||
Validate(values);
|
Validate(values);
|
||||||
|
Property.SetValue(instance, values);
|
||||||
Property.Set(instance, values);
|
|
||||||
}
|
}
|
||||||
// Single value expected, single value provided
|
// Non-sequential input; zero or one value provided
|
||||||
else if (rawValues.Count <= 1)
|
else if (rawValues.Count <= 1)
|
||||||
{
|
{
|
||||||
var value = Converter.Convert(rawValues.SingleOrDefault(), formatProvider);
|
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
|
else
|
||||||
{
|
{
|
||||||
throw CliFxException.UserError(
|
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(" ")}
|
{rawValues.Select(v => '<' + v + '>').JoinToString(" ")}
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
@@ -106,7 +103,7 @@ public abstract class CommandInputSchema(
|
|||||||
{
|
{
|
||||||
throw CliFxException.UserError(
|
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(" ")}
|
{rawValues.Select(v => '<' + v + '>').JoinToString(" ")}
|
||||||
Error: {ex.Message}
|
Error: {ex.Message}
|
||||||
""",
|
""",
|
||||||
@@ -117,7 +114,7 @@ public abstract class CommandInputSchema(
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override string ToString() => FormattedIdentifier;
|
public override string ToString() => this.GetFormattedIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="CommandInputSchema" />
|
/// <inheritdoc cref="CommandInputSchema" />
|
||||||
@@ -134,8 +131,41 @@ public abstract class CommandInputSchema<
|
|||||||
TProperty
|
TProperty
|
||||||
>(
|
>(
|
||||||
PropertyBinding<TCommand, TProperty> property,
|
PropertyBinding<TCommand, TProperty> property,
|
||||||
|
bool isSequence,
|
||||||
string? description,
|
string? description,
|
||||||
BindingConverter<TProperty> converter,
|
BindingConverter<TProperty> converter,
|
||||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||||
) : CommandInputSchema(property, description, converter, validators)
|
) : CommandInputSchema(property, isSequence, description, converter, validators)
|
||||||
where TCommand : ICommand;
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text;
|
|
||||||
using CliFx.Extensibility;
|
using CliFx.Extensibility;
|
||||||
|
|
||||||
namespace CliFx.Schema;
|
namespace CliFx.Schema;
|
||||||
@@ -11,6 +10,7 @@ namespace CliFx.Schema;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommandOptionSchema(
|
public class CommandOptionSchema(
|
||||||
PropertyBinding property,
|
PropertyBinding property,
|
||||||
|
bool isSequence,
|
||||||
string? name,
|
string? name,
|
||||||
char? shortName,
|
char? shortName,
|
||||||
string? environmentVariable,
|
string? environmentVariable,
|
||||||
@@ -18,38 +18,8 @@ public class CommandOptionSchema(
|
|||||||
string? description,
|
string? description,
|
||||||
IBindingConverter converter,
|
IBindingConverter converter,
|
||||||
IReadOnlyList<IBindingValidator> validators
|
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>
|
/// <summary>
|
||||||
/// Option name.
|
/// Option name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -99,6 +69,7 @@ public class CommandOptionSchema<
|
|||||||
TProperty
|
TProperty
|
||||||
>(
|
>(
|
||||||
PropertyBinding<TCommand, TProperty> property,
|
PropertyBinding<TCommand, TProperty> property,
|
||||||
|
bool isSequence,
|
||||||
string? name,
|
string? name,
|
||||||
char? shortName,
|
char? shortName,
|
||||||
string? environmentVariable,
|
string? environmentVariable,
|
||||||
@@ -109,6 +80,7 @@ public class CommandOptionSchema<
|
|||||||
)
|
)
|
||||||
: CommandOptionSchema(
|
: CommandOptionSchema(
|
||||||
property,
|
property,
|
||||||
|
isSequence,
|
||||||
name,
|
name,
|
||||||
shortName,
|
shortName,
|
||||||
environmentVariable,
|
environmentVariable,
|
||||||
|
|||||||
@@ -9,20 +9,17 @@ namespace CliFx.Schema;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommandParameterSchema(
|
public class CommandParameterSchema(
|
||||||
PropertyBinding property,
|
PropertyBinding property,
|
||||||
|
bool isSequence,
|
||||||
int order,
|
int order,
|
||||||
string name,
|
string name,
|
||||||
bool isRequired,
|
bool isRequired,
|
||||||
string? description,
|
string? description,
|
||||||
IBindingConverter converter,
|
IBindingConverter converter,
|
||||||
IReadOnlyList<IBindingValidator> validators
|
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>
|
/// <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>
|
/// </summary>
|
||||||
public int Order { get; } = order;
|
public int Order { get; } = order;
|
||||||
|
|
||||||
@@ -51,11 +48,22 @@ public class CommandParameterSchema<
|
|||||||
TProperty
|
TProperty
|
||||||
>(
|
>(
|
||||||
PropertyBinding<TCommand, TProperty> property,
|
PropertyBinding<TCommand, TProperty> property,
|
||||||
|
bool isSequence,
|
||||||
int order,
|
int order,
|
||||||
string name,
|
string name,
|
||||||
bool isRequired,
|
bool isRequired,
|
||||||
string? description,
|
string? description,
|
||||||
BindingConverter<TProperty> converter,
|
BindingConverter<TProperty> converter,
|
||||||
IReadOnlyList<BindingValidator<TProperty>> validators
|
IReadOnlyList<BindingValidator<TProperty>> validators
|
||||||
) : CommandParameterSchema(property, order, name, isRequired, description, converter, validators)
|
)
|
||||||
|
: CommandParameterSchema(
|
||||||
|
property,
|
||||||
|
isSequence,
|
||||||
|
order,
|
||||||
|
name,
|
||||||
|
isRequired,
|
||||||
|
description,
|
||||||
|
converter,
|
||||||
|
validators
|
||||||
|
)
|
||||||
where TCommand : ICommand;
|
where TCommand : ICommand;
|
||||||
|
|||||||
@@ -62,24 +62,8 @@ public class CommandSchema(
|
|||||||
? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase)
|
? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase)
|
||||||
: string.IsNullOrWhiteSpace(name);
|
: string.IsNullOrWhiteSpace(name);
|
||||||
|
|
||||||
internal IReadOnlyDictionary<CommandInputSchema, object?> GetValues(ICommand instance)
|
internal IReadOnlyDictionary<CommandInputSchema, object?> GetValues(ICommand instance) =>
|
||||||
{
|
Inputs.ToDictionary(input => input, input => input.Property.GetValue(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ActivateParameters(ICommand instance, CommandArguments arguments)
|
private void ActivateParameters(ICommand instance, CommandArguments arguments)
|
||||||
{
|
{
|
||||||
@@ -91,21 +75,20 @@ public class CommandSchema(
|
|||||||
|
|
||||||
foreach (var parameter in Parameters.OrderBy(p => p.Order))
|
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)
|
if (position >= arguments.Parameters.Count)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Sequence: take all remaining inputs starting from the current position
|
// Sequential: take all remaining tokens starting from the current position
|
||||||
if (parameter.IsSequence)
|
if (parameter.IsSequence)
|
||||||
{
|
{
|
||||||
var parameterTokens = arguments.Parameters.Skip(position).ToArray();
|
var parameterTokens = arguments.Parameters.Skip(position).ToArray();
|
||||||
|
|
||||||
parameter.Activate(instance, parameterTokens.Select(p => p.Value).ToArray());
|
parameter.Activate(instance, parameterTokens.Select(p => p.Value).ToArray());
|
||||||
|
|
||||||
position += parameterTokens.Length;
|
position += parameterTokens.Length;
|
||||||
remainingParameterTokens.RemoveRange(parameterTokens);
|
remainingParameterTokens.RemoveRange(parameterTokens);
|
||||||
}
|
}
|
||||||
// Non-sequence: take one input at the current position
|
// Non-sequential: take one token at the current position
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var parameterToken = arguments.Parameters[position];
|
var parameterToken = arguments.Parameters[position];
|
||||||
@@ -132,9 +115,9 @@ public class CommandSchema(
|
|||||||
{
|
{
|
||||||
throw CliFxException.UserError(
|
throw CliFxException.UserError(
|
||||||
$"""
|
$"""
|
||||||
Missing equired parameter(s):
|
Missing required parameter(s):
|
||||||
{remainingRequiredParameters
|
{remainingRequiredParameters
|
||||||
.Select(p => p.FormattedIdentifier)
|
.Select(p => p.GetFormattedIdentifier())
|
||||||
.JoinToString(" ")}
|
.JoinToString(" ")}
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
@@ -153,7 +136,7 @@ public class CommandSchema(
|
|||||||
|
|
||||||
foreach (var option in Options)
|
foreach (var option in Options)
|
||||||
{
|
{
|
||||||
var optionToken = arguments
|
var optionTokens = arguments
|
||||||
.Options.Where(o => option.MatchesIdentifier(o.Identifier))
|
.Options.Where(o => option.MatchesIdentifier(o.Identifier))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -161,10 +144,10 @@ public class CommandSchema(
|
|||||||
option.MatchesEnvironmentVariable(v.Key)
|
option.MatchesEnvironmentVariable(v.Key)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Direct input
|
// From arguments
|
||||||
if (optionToken.Any())
|
if (optionTokens.Any())
|
||||||
{
|
{
|
||||||
var rawValues = optionToken.SelectMany(o => o.Values).ToArray();
|
var rawValues = optionTokens.SelectMany(o => o.Values).ToArray();
|
||||||
|
|
||||||
option.Activate(instance, rawValues);
|
option.Activate(instance, rawValues);
|
||||||
|
|
||||||
@@ -172,7 +155,7 @@ public class CommandSchema(
|
|||||||
if (rawValues.Any())
|
if (rawValues.Any())
|
||||||
remainingRequiredOptions.Remove(option);
|
remainingRequiredOptions.Remove(option);
|
||||||
}
|
}
|
||||||
// Environment variable
|
// From environment
|
||||||
else if (!string.IsNullOrEmpty(environmentVariable.Value))
|
else if (!string.IsNullOrEmpty(environmentVariable.Value))
|
||||||
{
|
{
|
||||||
var rawValues = !option.IsSequence
|
var rawValues = !option.IsSequence
|
||||||
@@ -194,7 +177,7 @@ public class CommandSchema(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingOptionTokens.RemoveRange(optionToken);
|
remainingOptionTokens.RemoveRange(optionTokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingOptionTokens.Any())
|
if (remainingOptionTokens.Any())
|
||||||
@@ -213,7 +196,7 @@ public class CommandSchema(
|
|||||||
$"""
|
$"""
|
||||||
Missing required option(s):
|
Missing required option(s):
|
||||||
{remainingRequiredOptions
|
{remainingRequiredOptions
|
||||||
.Select(o => o.FormattedIdentifier)
|
.Select(o => o.GetFormattedIdentifier())
|
||||||
.JoinToString(", ")}
|
.JoinToString(", ")}
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ public class PropertyBinding(
|
|||||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||||
)]
|
)]
|
||||||
Type type,
|
Type type,
|
||||||
Func<object, object?> get,
|
Func<object, object?> getValue,
|
||||||
Action<object, object?> set
|
Action<object, object?> setValue
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,12 +28,12 @@ public class PropertyBinding(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current value of the property on the specified instance.
|
/// Gets the current value of the property on the specified instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object? Get(object instance) => get(instance);
|
public object? GetValue(object instance) => getValue(instance);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the current value of the property on the specified instance.
|
/// Sets the current value of the property on the specified instance.
|
||||||
/// </summary>
|
/// </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()
|
internal IReadOnlyList<object?>? TryGetValidValues()
|
||||||
{
|
{
|
||||||
@@ -67,9 +67,9 @@ public class PropertyBinding<
|
|||||||
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
|
||||||
)]
|
)]
|
||||||
TProperty
|
TProperty
|
||||||
>(Func<TObject, TProperty?> get, Action<TObject, TProperty?> set)
|
>(Func<TObject, TProperty?> getValue, Action<TObject, TProperty?> setValue)
|
||||||
: PropertyBinding(
|
: PropertyBinding(
|
||||||
typeof(TProperty),
|
typeof(TProperty),
|
||||||
o => get((TObject)o),
|
o => getValue((TObject)o),
|
||||||
(o, v) => set((TObject)o, (TProperty?)v)
|
(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;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace CliFx.Utils.Extensions;
|
namespace CliFx.Utils.Extensions;
|
||||||
|
|
||||||
internal static class TypeExtensions
|
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(
|
public static bool IsToStringOverriden(
|
||||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user