This commit is contained in:
Tyrrrz
2024-08-11 22:34:50 +03:00
parent 651146c97b
commit 2d3c221b48
7 changed files with 98 additions and 82 deletions

View File

@@ -305,7 +305,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
} }
} }
private void WriteDefaultValue(InputSchema schema) private void WriteDefaultValue(CommandInputSchema schema)
{ {
var defaultValue = context.CommandDefaultValues.GetValueOrDefault(schema); var defaultValue = context.CommandDefaultValues.GetValueOrDefault(schema);
if (defaultValue is null) if (defaultValue is null)

View File

@@ -7,7 +7,7 @@ internal class HelpContext(
ApplicationMetadata applicationMetadata, ApplicationMetadata applicationMetadata,
ApplicationSchema applicationSchema, ApplicationSchema applicationSchema,
CommandSchema commandSchema, CommandSchema commandSchema,
IReadOnlyDictionary<InputSchema, object?> commandDefaultValues IReadOnlyDictionary<CommandInputSchema, object?> commandDefaultValues
) )
{ {
public ApplicationMetadata ApplicationMetadata { get; } = applicationMetadata; public ApplicationMetadata ApplicationMetadata { get; } = applicationMetadata;
@@ -16,6 +16,6 @@ internal class HelpContext(
public CommandSchema CommandSchema { get; } = commandSchema; public CommandSchema CommandSchema { get; } = commandSchema;
public IReadOnlyDictionary<InputSchema, object?> CommandDefaultValues { get; } = public IReadOnlyDictionary<CommandInputSchema, object?> CommandDefaultValues { get; } =
commandDefaultValues; commandDefaultValues;
} }

View File

@@ -12,13 +12,17 @@ namespace CliFx.Schema;
/// <summary> /// <summary>
/// Describes an input of a command. /// Describes an input of a command.
/// </summary> /// </summary>
public abstract class InputSchema( public abstract class CommandInputSchema(
PropertyBinding property, PropertyBinding property,
string? description, string? description,
IBindingConverter converter, IBindingConverter converter,
IReadOnlyList<IBindingValidator> validators IReadOnlyList<IBindingValidator> validators
) )
{ {
internal abstract string Kind { get; }
internal abstract string FormattedIdentifier { get; }
internal bool IsSequence { get; } = internal bool IsSequence { get; } =
property.Type != typeof(string) property.Type != typeof(string)
&& property.Type.TryGetEnumerableUnderlyingType() is not null; && property.Type.TryGetEnumerableUnderlyingType() is not null;
@@ -43,10 +47,6 @@ public abstract class InputSchema(
/// </summary> /// </summary>
public IReadOnlyList<IBindingValidator> Validators { get; } = validators; public IReadOnlyList<IBindingValidator> Validators { get; } = validators;
internal abstract string GetKind();
internal abstract string GetFormattedIdentifier();
private void Validate(object? value) private void Validate(object? value)
{ {
var errors = Validators var errors = Validators
@@ -58,7 +58,7 @@ public abstract class InputSchema(
{ {
throw CliFxException.UserError( throw CliFxException.UserError(
$""" $"""
{GetKind()} {GetFormattedIdentifier()} has been provided with an invalid value. {Kind} {FormattedIdentifier} 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)}
""" """
@@ -93,7 +93,7 @@ public abstract class InputSchema(
{ {
throw CliFxException.UserError( throw CliFxException.UserError(
$""" $"""
{GetKind()} {GetFormattedIdentifier()} expects a single argument, but provided with multiple: {Kind} {FormattedIdentifier} expects a single argument, but provided with multiple:
{rawInputs.Select(v => '<' + v + '>').JoinToString(" ")} {rawInputs.Select(v => '<' + v + '>').JoinToString(" ")}
""" """
); );
@@ -103,7 +103,7 @@ public abstract class InputSchema(
{ {
throw CliFxException.UserError( throw CliFxException.UserError(
$""" $"""
{GetKind()} {GetFormattedIdentifier()} cannot be set from the provided argument(s): {Kind} {FormattedIdentifier} cannot be set from the provided argument(s):
{rawInputs.Select(v => '<' + v + '>').JoinToString(" ")} {rawInputs.Select(v => '<' + v + '>').JoinToString(" ")}
Error: {ex.Message} Error: {ex.Message}
""", """,
@@ -111,13 +111,18 @@ public abstract class InputSchema(
); );
} }
} }
/// <inheritdoc />
public override string ToString() => FormattedIdentifier;
} }
// Generic version of the type is used to simplify initialization from the source-generated code /// <inheritdoc cref="CommandInputSchema" />
// and to enforce static references to all the types used in the binding. /// <remarks>
// The non-generic version is used internally by the framework when operating in a dynamic context. /// Generic version of the type is used to simplify initialization from source-generated code and
/// <inheritdoc cref="InputSchema" /> /// to enforce static references to all types used in the binding.
public abstract class InputSchema< /// The non-generic version is used internally by the framework when operating in a dynamic context.
/// </remarks>
public abstract class CommandInputSchema<
TCommand, TCommand,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
@@ -128,5 +133,5 @@ public abstract class InputSchema<
string? description, string? description,
BindingConverter<TProperty> converter, BindingConverter<TProperty> converter,
IReadOnlyList<BindingValidator<TProperty>> validators IReadOnlyList<BindingValidator<TProperty>> validators
) : InputSchema(property, description, converter, validators) ) : CommandInputSchema(property, description, converter, validators)
where TCommand : ICommand; where TCommand : ICommand;

View File

@@ -9,7 +9,7 @@ namespace CliFx.Schema;
/// <summary> /// <summary>
/// Describes an option input of a command. /// Describes an option input of a command.
/// </summary> /// </summary>
public class OptionSchema( public class CommandOptionSchema(
PropertyBinding property, PropertyBinding property,
string? name, string? name,
char? shortName, char? shortName,
@@ -18,8 +18,38 @@ public class OptionSchema(
string? description, string? description,
IBindingConverter converter, IBindingConverter converter,
IReadOnlyList<IBindingValidator> validators IReadOnlyList<IBindingValidator> validators
) : InputSchema(property,description, converter, validators) ) : CommandInputSchema(property,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>
@@ -53,40 +83,15 @@ public class OptionSchema(
internal bool MatchesEnvironmentVariable(string environmentVariableName) => internal bool MatchesEnvironmentVariable(string environmentVariableName) =>
!string.IsNullOrWhiteSpace(EnvironmentVariable) !string.IsNullOrWhiteSpace(EnvironmentVariable)
&& string.Equals(EnvironmentVariable, environmentVariableName, StringComparison.Ordinal); && string.Equals(EnvironmentVariable, environmentVariableName, StringComparison.Ordinal);
internal override string GetKind() => "Option";
internal override string GetFormattedIdentifier()
{
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();
}
} }
// Generic version of the type is used to simplify initialization from the source-generated code /// <inheritdoc cref="CommandOptionSchema" />
// and to enforce static references to all the types used in the binding. /// <remarks>
// The non-generic version is used internally by the framework when operating in a dynamic context. /// Generic version of the type is used to simplify initialization from source-generated code and
/// <inheritdoc cref="OptionSchema" /> /// to enforce static references to all types used in the binding.
public class OptionSchema< /// The non-generic version is used internally by the framework when operating in a dynamic context.
/// </remarks>
public class CommandOptionSchema<
TCommand, TCommand,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
@@ -102,7 +107,7 @@ public class OptionSchema<
BindingConverter<TProperty> converter, BindingConverter<TProperty> converter,
IReadOnlyList<BindingValidator<TProperty>> validators IReadOnlyList<BindingValidator<TProperty>> validators
) )
: OptionSchema( : CommandOptionSchema(
property, property,
name, name,
shortName, shortName,

View File

@@ -7,7 +7,7 @@ namespace CliFx.Schema;
/// <summary> /// <summary>
/// Describes a parameter input of a command. /// Describes a parameter input of a command.
/// </summary> /// </summary>
public class ParameterSchema( public class CommandParameterSchema(
PropertyBinding property, PropertyBinding property,
int order, int order,
string name, string name,
@@ -15,8 +15,12 @@ public class ParameterSchema(
string? description, string? description,
IBindingConverter converter, IBindingConverter converter,
IReadOnlyList<IBindingValidator> validators IReadOnlyList<IBindingValidator> validators
) : InputSchema(property, description, converter, validators) ) : CommandInputSchema(property, 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 bound from the command-line arguments.
/// </summary> /// </summary>
@@ -31,17 +35,15 @@ public class ParameterSchema(
/// Whether the parameter is required. /// Whether the parameter is required.
/// </summary> /// </summary>
public bool IsRequired { get; } = isRequired; public bool IsRequired { get; } = isRequired;
internal override string GetKind() => "Parameter";
internal override string GetFormattedIdentifier() => IsSequence ? $"<{Name}>" : $"<{Name}...>";
} }
// Generic version of the type is used to simplify initialization from the source-generated code /// <inheritdoc cref="CommandParameterSchema" />
// and to enforce static references to all the types used in the binding. /// <remarks>
// The non-generic version is used internally by the framework when operating in a dynamic context. /// Generic version of the type is used to simplify initialization from source-generated code and
/// <inheritdoc cref="ParameterSchema" /> /// to enforce static references to all types used in the binding.
public class ParameterSchema< /// The non-generic version is used internally by the framework when operating in a dynamic context.
/// </remarks>
public class CommandParameterSchema<
TCommand, TCommand,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods
@@ -55,5 +57,5 @@ public class ParameterSchema<
string? description, string? description,
BindingConverter<TProperty> converter, BindingConverter<TProperty> converter,
IReadOnlyList<BindingValidator<TProperty>> validators IReadOnlyList<BindingValidator<TProperty>> validators
) : ParameterSchema(property, order, name, isRequired, description, converter, validators) ) : CommandParameterSchema(property, order, name, isRequired, description, converter, validators)
where TCommand : ICommand; where TCommand : ICommand;

View File

@@ -9,13 +9,13 @@ using CliFx.Utils.Extensions;
namespace CliFx.Schema; namespace CliFx.Schema;
/// <summary> /// <summary>
/// Describes an individual command, along with its parameter and option inputs. /// Describes an individual command, along with its inputs.
/// </summary> /// </summary>
public class CommandSchema( public class CommandSchema(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type,
string? name, string? name,
string? description, string? description,
IReadOnlyList<InputSchema> inputs IReadOnlyList<CommandInputSchema> inputs
) )
{ {
/// <summary> /// <summary>
@@ -40,29 +40,29 @@ public class CommandSchema(
public string? Description { get; } = description; public string? Description { get; } = description;
/// <summary> /// <summary>
/// Inputs (parameters and options) of the command. /// Command inputs.
/// </summary> /// </summary>
public IReadOnlyList<InputSchema> Inputs { get; } = inputs; public IReadOnlyList<CommandInputSchema> Inputs { get; } = inputs;
/// <summary> /// <summary>
/// Parameter inputs of the command. /// Parameter inputs of the command.
/// </summary> /// </summary>
public IReadOnlyList<ParameterSchema> Parameters { get; } = public IReadOnlyList<CommandParameterSchema> Parameters { get; } =
inputs.OfType<ParameterSchema>().ToArray(); inputs.OfType<CommandParameterSchema>().ToArray();
/// <summary> /// <summary>
/// Option inputs of the command. /// Option inputs of the command.
/// </summary> /// </summary>
public IReadOnlyList<OptionSchema> Options { get; } = inputs.OfType<OptionSchema>().ToArray(); public IReadOnlyList<CommandOptionSchema> Options { get; } = inputs.OfType<CommandOptionSchema>().ToArray();
internal bool MatchesName(string? name) => internal bool MatchesName(string? name) =>
!string.IsNullOrWhiteSpace(Name) !string.IsNullOrWhiteSpace(Name)
? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase) ? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase)
: string.IsNullOrWhiteSpace(name); : string.IsNullOrWhiteSpace(name);
internal IReadOnlyDictionary<InputSchema, object?> GetValues(ICommand instance) internal IReadOnlyDictionary<CommandInputSchema, object?> GetValues(ICommand instance)
{ {
var result = new Dictionary<InputSchema, object?>(); var result = new Dictionary<CommandInputSchema, object?>();
foreach (var parameterSchema in Parameters) foreach (var parameterSchema in Parameters)
{ {
@@ -135,7 +135,7 @@ public class CommandSchema(
$""" $"""
Missing required parameter(s): Missing required parameter(s):
{remainingRequiredParameterSchemas {remainingRequiredParameterSchemas
.Select(p => p.GetFormattedIdentifier()) .Select(p => p.FormattedIdentifier)
.JoinToString(" ")} .JoinToString(" ")}
""" """
); );
@@ -208,7 +208,7 @@ public class CommandSchema(
$""" $"""
Missing required option(s): Missing required option(s):
{remainingRequiredOptionSchemas {remainingRequiredOptionSchemas
.Select(o => o.GetFormattedIdentifier()) .Select(o => o.FormattedIdentifier)
.JoinToString(", ")} .JoinToString(", ")}
""" """
); );
@@ -222,12 +222,14 @@ public class CommandSchema(
} }
} }
// Generic version of the type is used to simplify initialization from the source-generated code
// and to enforce static references to all the types used in the binding.
// The non-generic version is used internally by the framework when operating in a dynamic context.
/// <inheritdoc cref="CommandSchema" /> /// <inheritdoc cref="CommandSchema" />
/// <remarks>
/// Generic version of the type is used to simplify initialization from source-generated code and
/// to enforce static references to all types used in the binding.
/// The non-generic version is used internally by the framework when operating in a dynamic context.
/// </remarks>
public class CommandSchema< public class CommandSchema<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommand [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommand
>(string? name, string? description, IReadOnlyList<InputSchema> inputs) >(string? name, string? description, IReadOnlyList<CommandInputSchema> inputs)
: CommandSchema(typeof(TCommand), name, description, inputs) : CommandSchema(typeof(TCommand), name, description, inputs)
where TCommand : ICommand; where TCommand : ICommand;

View File

@@ -55,10 +55,12 @@ public class PropertyBinding(
} }
} }
// Generic version of the type is used to simplify initialization from the source-generated code
// and to enforce static references to all the types used in the binding.
// The non-generic version is used internally by the framework when operating in a dynamic context.
/// <inheritdoc cref="PropertyBinding" /> /// <inheritdoc cref="PropertyBinding" />
/// <remarks>
/// Generic version of the type is used to simplify initialization from source-generated code and
/// to enforce static references to all types used in the binding.
/// The non-generic version is used internally by the framework when operating in a dynamic context.
/// </remarks>
public class PropertyBinding< public class PropertyBinding<
TObject, TObject,
[DynamicallyAccessedMembers( [DynamicallyAccessedMembers(