mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
asd
This commit is contained in:
@@ -6,7 +6,7 @@ namespace CliFx.Attributes;
|
|||||||
/// Annotates a type that defines a command.
|
/// Annotates a type that defines a command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
public sealed class CommandAttribute : Attribute
|
public class CommandAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandAttribute" />.
|
/// Initializes an instance of <see cref="CommandAttribute" />.
|
||||||
|
|||||||
16
CliFx/Attributes/CommandHelpOptionAttribute.cs
Normal file
16
CliFx/Attributes/CommandHelpOptionAttribute.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace CliFx.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Annotates a property that defines the help option for a command.
|
||||||
|
/// </summary>
|
||||||
|
public class CommandHelpOptionAttribute : CommandOptionAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandHelpOptionAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandHelpOptionAttribute()
|
||||||
|
: base("help", 'h')
|
||||||
|
{
|
||||||
|
Description = "Show help for this command.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace CliFx.Attributes;
|
|||||||
/// Annotates a property that defines a command option.
|
/// Annotates a property that defines a command option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
public sealed class CommandOptionAttribute : Attribute
|
public class CommandOptionAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
/// Initializes an instance of <see cref="CommandOptionAttribute" />.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace CliFx.Attributes;
|
|||||||
/// Annotates a property that defines a command parameter.
|
/// Annotates a property that defines a command parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
public sealed class CommandParameterAttribute(int order) : Attribute
|
public class CommandParameterAttribute(int order) : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameter order.
|
/// Parameter order.
|
||||||
|
|||||||
16
CliFx/Attributes/CommandVersionOptionAttribute.cs
Normal file
16
CliFx/Attributes/CommandVersionOptionAttribute.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace CliFx.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Annotates a property that defines the version option for a command.
|
||||||
|
/// </summary>
|
||||||
|
public class CommandVersionOptionAttribute : CommandOptionAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandVersionOptionAttribute" />.
|
||||||
|
/// </summary>
|
||||||
|
public CommandVersionOptionAttribute()
|
||||||
|
: base("version")
|
||||||
|
{
|
||||||
|
Description = "Show application version.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,16 +41,6 @@ public class CliApplication(
|
|||||||
private bool IsPreviewModeEnabled(CommandInput commandInput) =>
|
private bool IsPreviewModeEnabled(CommandInput commandInput) =>
|
||||||
Configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified;
|
Configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified;
|
||||||
|
|
||||||
private bool ShouldShowHelpText(CommandSchema commandSchema, CommandInput commandInput) =>
|
|
||||||
commandSchema.IsHelpOptionAvailable && commandInput.IsHelpOptionSpecified
|
|
||||||
||
|
|
||||||
// Show help text also if the fallback default command is executed without any arguments
|
|
||||||
commandSchema == FallbackDefaultCommand.Schema
|
|
||||||
&& !commandInput.HasArguments;
|
|
||||||
|
|
||||||
private bool ShouldShowVersionText(CommandSchema commandSchema, CommandInput commandInput) =>
|
|
||||||
commandSchema.IsVersionOptionAvailable && commandInput.IsVersionOptionSpecified;
|
|
||||||
|
|
||||||
private async ValueTask PromptDebuggerAsync()
|
private async ValueTask PromptDebuggerAsync()
|
||||||
{
|
{
|
||||||
using (console.WithForegroundColor(ConsoleColor.Green))
|
using (console.WithForegroundColor(ConsoleColor.Green))
|
||||||
@@ -119,30 +109,36 @@ public class CliApplication(
|
|||||||
commandSchema.GetValues(commandInstance)
|
commandSchema.GetValues(commandInstance)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle the help option
|
|
||||||
if (ShouldShowHelpText(commandSchema, commandInput))
|
|
||||||
{
|
|
||||||
console.WriteHelpText(helpContext);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the version option
|
|
||||||
if (ShouldShowVersionText(commandSchema, commandInput))
|
|
||||||
{
|
|
||||||
console.WriteLine(Metadata.Version);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting from this point, we may produce exceptions that are meant for the
|
// Starting from this point, we may produce exceptions that are meant for the
|
||||||
// end-user of the application (i.e. invalid input, command exception, etc).
|
// end-user of the application (i.e. invalid input, command exception, etc).
|
||||||
// Catch these exceptions here, print them to the console, and don't let them
|
// Catch these exceptions here, print them to the console, and don't let them
|
||||||
// propagate further.
|
// propagate further.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Bind and execute the command
|
// Bind the command input to the command instance
|
||||||
_commandBinder.Bind(commandInput, commandSchema, commandInstance);
|
_commandBinder.Bind(commandInput, commandSchema, commandInstance);
|
||||||
await commandInstance.ExecuteAsync(console);
|
|
||||||
|
|
||||||
|
// Handle the version option
|
||||||
|
if (commandInstance is ICommandWithVersionOption { IsVersionRequested: true })
|
||||||
|
{
|
||||||
|
console.WriteLine(Metadata.Version);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the help option
|
||||||
|
if (
|
||||||
|
commandInstance
|
||||||
|
is ICommandWithHelpOption { IsHelpRequested: true }
|
||||||
|
// Fallback default command always shows help, even if the option is not specified
|
||||||
|
or FallbackDefaultCommand
|
||||||
|
)
|
||||||
|
{
|
||||||
|
console.WriteHelpText(helpContext);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the command
|
||||||
|
await commandInstance.ExecuteAsync(console);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
catch (CliFxException ex)
|
catch (CliFxException ex)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public partial class CliApplicationBuilder
|
|||||||
[UnconditionalSuppressMessage(
|
[UnconditionalSuppressMessage(
|
||||||
"SingleFile",
|
"SingleFile",
|
||||||
"IL3000:Avoid accessing Assembly file path when publishing as a single file",
|
"IL3000:Avoid accessing Assembly file path when publishing as a single file",
|
||||||
Justification = "The return value of the method is checked to ensure the assembly location is available."
|
Justification = "The file path is checked to ensure the assembly location is available."
|
||||||
)]
|
)]
|
||||||
private static string GetDefaultExecutableName()
|
private static string GetDefaultExecutableName()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">true</IsTrimmable>
|
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">true</IsTrimmable>
|
||||||
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
|
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
|
|||||||
{
|
{
|
||||||
private readonly IFormatProvider _formatProvider = CultureInfo.InvariantCulture;
|
private readonly IFormatProvider _formatProvider = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
private object? ConvertSingle(IInputSchema inputSchema, string? rawValue, Type targetType)
|
private object? ConvertSingle(InputSchema inputSchema, string? rawValue, Type targetType)
|
||||||
{
|
{
|
||||||
// Custom converter
|
// Custom converter
|
||||||
if (inputSchema.Converter is not null)
|
if (inputSchema.Converter is not null)
|
||||||
@@ -103,7 +103,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private object? ConvertMultiple(
|
private object? ConvertMultiple(
|
||||||
IInputSchema inputSchema,
|
InputSchema inputSchema,
|
||||||
IReadOnlyList<string> rawValues,
|
IReadOnlyList<string> rawValues,
|
||||||
Type targetEnumerableType,
|
Type targetEnumerableType,
|
||||||
Type targetElementType
|
Type targetElementType
|
||||||
@@ -137,7 +137,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private object? ConvertMember(IInputSchema inputSchema, IReadOnlyList<string> rawValues)
|
private object? ConvertMember(InputSchema inputSchema, IReadOnlyList<string> rawValues)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -192,7 +192,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateMember(IInputSchema inputSchema, object? convertedValue)
|
private void ValidateMember(InputSchema inputSchema, object? convertedValue)
|
||||||
{
|
{
|
||||||
var errors = new List<BindingValidationError>();
|
var errors = new List<BindingValidationError>();
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void BindMember(
|
private void BindMember(
|
||||||
IInputSchema inputSchema,
|
InputSchema inputSchema,
|
||||||
ICommand commandInstance,
|
ICommand commandInstance,
|
||||||
IReadOnlyList<string> rawValues
|
IReadOnlyList<string> rawValues
|
||||||
)
|
)
|
||||||
@@ -335,7 +335,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
|
|||||||
// Environment variable
|
// Environment variable
|
||||||
else if (environmentVariableInput is not null)
|
else if (environmentVariableInput is not null)
|
||||||
{
|
{
|
||||||
var rawValues = optionSchema.IsScalar
|
var rawValues = optionSchema.IsSequence
|
||||||
? [environmentVariableInput.Value]
|
? [environmentVariableInput.Value]
|
||||||
: environmentVariableInput.SplitValues();
|
: environmentVariableInput.SplitValues();
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,14 @@ namespace CliFx;
|
|||||||
// Fallback command used when the application doesn't have one configured.
|
// Fallback command used when the application doesn't have one configured.
|
||||||
// This command is only used as a stub for help text.
|
// This command is only used as a stub for help text.
|
||||||
[Command]
|
[Command]
|
||||||
internal partial class FallbackDefaultCommand : ICommand
|
internal partial class FallbackDefaultCommand : ICommandWithHelpOption, ICommandWithVersionOption
|
||||||
{
|
{
|
||||||
|
[CommandHelpOption]
|
||||||
|
public bool IsHelpRequested { get; init; }
|
||||||
|
|
||||||
|
[CommandVersionOption]
|
||||||
|
public bool IsVersionRequested { get; init; }
|
||||||
|
|
||||||
// Never actually executed
|
// Never actually executed
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
|||||||
{
|
{
|
||||||
Write(
|
Write(
|
||||||
ConsoleColor.DarkCyan,
|
ConsoleColor.DarkCyan,
|
||||||
parameter.Property.IsScalar() ? $"<{parameter.Name}>" : $"<{parameter.Name}...>"
|
parameter.IsSequence ? $"<{parameter.Name}...>" : $"<{parameter.Name}>"
|
||||||
);
|
);
|
||||||
Write(' ');
|
Write(' ');
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
|||||||
);
|
);
|
||||||
Write(' ');
|
Write(' ');
|
||||||
|
|
||||||
Write(ConsoleColor.White, option.Property.IsScalar() ? "<value>" : "<values...>");
|
Write(ConsoleColor.White, option.IsSequence ? "<values...>" : "<value>");
|
||||||
Write(' ');
|
Write(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +170,8 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Valid values
|
// Valid values
|
||||||
var validValues = parameterSchema.Property.GetValidValues();
|
var validValues = parameterSchema.Property.TryGetValidValues();
|
||||||
if (validValues.Any())
|
if (validValues?.Any() == true)
|
||||||
{
|
{
|
||||||
Write(ConsoleColor.White, "Choices: ");
|
Write(ConsoleColor.White, "Choices: ");
|
||||||
|
|
||||||
@@ -257,8 +257,8 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Valid values
|
// Valid values
|
||||||
var validValues = optionSchema.Property.GetValidValues();
|
var validValues = optionSchema.Property.TryGetValidValues();
|
||||||
if (validValues.Any())
|
if (validValues?.Any() == true)
|
||||||
{
|
{
|
||||||
Write(ConsoleColor.White, "Choices: ");
|
Write(ConsoleColor.White, "Choices: ");
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteDefaultValue(IMemberSchema schema)
|
private void WriteDefaultValue(InputSchema schema)
|
||||||
{
|
{
|
||||||
var defaultValue = context.CommandDefaultValues.GetValueOrDefault(schema);
|
var defaultValue = context.CommandDefaultValues.GetValueOrDefault(schema);
|
||||||
if (defaultValue is not null)
|
if (defaultValue is not null)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ internal class HelpContext(
|
|||||||
ApplicationMetadata applicationMetadata,
|
ApplicationMetadata applicationMetadata,
|
||||||
ApplicationSchema applicationSchema,
|
ApplicationSchema applicationSchema,
|
||||||
CommandSchema commandSchema,
|
CommandSchema commandSchema,
|
||||||
IReadOnlyDictionary<IInputSchema, object?> commandDefaultValues
|
IReadOnlyDictionary<InputSchema, 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<IInputSchema, object?> CommandDefaultValues { get; } =
|
public IReadOnlyDictionary<InputSchema, object?> CommandDefaultValues { get; } =
|
||||||
commandDefaultValues;
|
commandDefaultValues;
|
||||||
}
|
}
|
||||||
|
|||||||
12
CliFx/ICommandWithHelpOption.cs
Normal file
12
CliFx/ICommandWithHelpOption.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CliFx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command definition that includes the help option.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICommandWithHelpOption : ICommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the user requested help for this command (via the `-h|--help` option).
|
||||||
|
/// </summary>
|
||||||
|
bool IsHelpRequested { get; }
|
||||||
|
}
|
||||||
12
CliFx/ICommandWithVersionOption.cs
Normal file
12
CliFx/ICommandWithVersionOption.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CliFx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command definition that includes the version option.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICommandWithVersionOption : ICommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the user requested the version information (via the `--version` option).
|
||||||
|
/// </summary>
|
||||||
|
bool IsVersionRequested { get; }
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ public class CommandSchema(
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command's CLR type.
|
/// Underlying CLR type of the command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
|
||||||
public Type Type { get; } = type;
|
public Type Type { get; } = type;
|
||||||
@@ -26,6 +26,11 @@ public class CommandSchema(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Name { get; } = name;
|
public string? Name { get; } = name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this command is the application's default command.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDefault { get; } = string.IsNullOrWhiteSpace(name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command description.
|
/// Command description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,19 +46,14 @@ public class CommandSchema(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<OptionSchema> Options { get; } = options;
|
public IReadOnlyList<OptionSchema> Options { get; } = options;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this command is the application's default command.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDefault { get; } = string.IsNullOrWhiteSpace(name);
|
|
||||||
|
|
||||||
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<IInputSchema, object?> GetValues(ICommand instance)
|
internal IReadOnlyDictionary<InputSchema, object?> GetValues(ICommand instance)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<IInputSchema, object?>();
|
var result = new Dictionary<InputSchema, object?>();
|
||||||
|
|
||||||
foreach (var parameterSchema in Parameters)
|
foreach (var parameterSchema in Parameters)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using CliFx.Extensibility;
|
|
||||||
|
|
||||||
namespace CliFx.Schema;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes an input of a command, which can be either a parameter or an option.
|
|
||||||
/// </summary>
|
|
||||||
public interface IInputSchema
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Describes the binding of this input to a CLR property.
|
|
||||||
/// </summary>
|
|
||||||
PropertyBinding Property { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional binding converter for this input.
|
|
||||||
/// </summary>
|
|
||||||
IBindingConverter? Converter { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional binding validator(s) for this input.
|
|
||||||
/// </summary>
|
|
||||||
IReadOnlyList<IBindingValidator> Validators { get; }
|
|
||||||
}
|
|
||||||
35
CliFx/Schema/InputSchema.cs
Normal file
35
CliFx/Schema/InputSchema.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CliFx.Extensibility;
|
||||||
|
|
||||||
|
namespace CliFx.Schema;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes an input of a command, which can be either a parameter or an option.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class InputSchema(
|
||||||
|
PropertyBinding property,
|
||||||
|
bool isSequence,
|
||||||
|
IBindingConverter? converter,
|
||||||
|
IReadOnlyList<IBindingValidator> validators
|
||||||
|
)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CLR property to which this input is bound.
|
||||||
|
/// </summary>
|
||||||
|
public PropertyBinding Property { get; } = property;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this input can accept more than one value.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSequence { get; } = isSequence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional binding converter for this input.
|
||||||
|
/// </summary>
|
||||||
|
public IBindingConverter? Converter { get; } = converter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional binding validator(s) for this input.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<IBindingValidator> Validators { get; } = validators;
|
||||||
|
}
|
||||||
@@ -6,12 +6,11 @@ using CliFx.Extensibility;
|
|||||||
namespace CliFx.Schema;
|
namespace CliFx.Schema;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a command's option.
|
/// Describes an option input of a command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OptionSchema(
|
public class OptionSchema(
|
||||||
PropertyBinding property,
|
PropertyBinding property,
|
||||||
bool isScalar,
|
bool isSequence,
|
||||||
IReadOnlyList<object?>? validValues,
|
|
||||||
string? name,
|
string? name,
|
||||||
char? shortName,
|
char? shortName,
|
||||||
string? environmentVariable,
|
string? environmentVariable,
|
||||||
@@ -19,17 +18,8 @@ public class OptionSchema(
|
|||||||
string? description,
|
string? description,
|
||||||
IBindingConverter? converter,
|
IBindingConverter? converter,
|
||||||
IReadOnlyList<IBindingValidator> validators
|
IReadOnlyList<IBindingValidator> validators
|
||||||
) : IInputSchema
|
) : InputSchema(property, isSequence, converter, validators)
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public PropertyBinding Property { get; } = property;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsScalar { get; } = isScalar;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IReadOnlyList<object?>? ValidValues { get; } = validValues;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option name.
|
/// Option name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -55,12 +45,6 @@ public class OptionSchema(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Description { get; } = description;
|
public string? Description { get; } = description;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IBindingConverter? Converter { get; } = converter;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IReadOnlyList<IBindingValidator> Validators { get; } = validators;
|
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -4,29 +4,19 @@ using CliFx.Extensibility;
|
|||||||
namespace CliFx.Schema;
|
namespace CliFx.Schema;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a command's parameter.
|
/// Describes a parameter input of a command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ParameterSchema(
|
public class ParameterSchema(
|
||||||
PropertyBinding property,
|
PropertyBinding property,
|
||||||
bool isScalar,
|
bool isSequence,
|
||||||
IReadOnlyList<object?>? validValues,
|
|
||||||
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
|
||||||
) : IInputSchema
|
) : InputSchema(property, isSequence, converter, validators)
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
public PropertyBinding Property { get; } = property;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsScalar { get; } = isScalar;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IReadOnlyList<object?>? ValidValues { get; } = validValues;
|
|
||||||
|
|
||||||
/// <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>
|
||||||
@@ -47,12 +37,5 @@ public class ParameterSchema(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Description { get; } = description;
|
public string? Description { get; } = description;
|
||||||
|
|
||||||
/// <inheritdoc />
|
internal string GetFormattedIdentifier() => IsSequence ? $"<{Name}>" : $"<{Name}...>";
|
||||||
public IBindingConverter? Converter { get; } = converter;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IReadOnlyList<IBindingValidator> Validators { get; } = validators;
|
|
||||||
|
|
||||||
internal string GetFormattedIdentifier() =>
|
|
||||||
IsScalar ? $"<{Name}>" : $"<{Name}...>";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ using System.Linq;
|
|||||||
namespace CliFx.Schema;
|
namespace CliFx.Schema;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a CLR property.
|
/// Represents a binding to a CLR property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PropertyBinding(
|
public class PropertyBinding(
|
||||||
Type propertyType,
|
Type type,
|
||||||
Func<object, object?> getValue,
|
Func<object, object?> getValue,
|
||||||
Action<object, object?> setValue
|
Action<object, object?> setValue
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Underlying property type.
|
/// Underlying CLR type of the property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type PropertyType { get; } = propertyType;
|
public Type Type { get; } = type;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current value of the property on the specified instance.
|
/// Gets the current value of the property on the specified instance.
|
||||||
@@ -27,12 +27,23 @@ public class PropertyBinding(
|
|||||||
/// Sets the value of the property on the specified instance.
|
/// Sets the value of the property on the specified instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetValue(object instance, object? value) => setValue(instance, value);
|
public void SetValue(object instance, object? value) => setValue(instance, value);
|
||||||
|
|
||||||
|
internal IReadOnlyList<object?>? TryGetValidValues()
|
||||||
|
{
|
||||||
|
if (Type.IsEnum)
|
||||||
|
{
|
||||||
|
var values =
|
||||||
|
#if NET7_0_OR_GREATER
|
||||||
|
Type.GetEnumValuesAsUnderlyingType();
|
||||||
|
#else
|
||||||
|
// AOT-compatible APIs are not available here, but it's unlikely
|
||||||
|
// someone will be AOT-compiling a net6.0 or older app anyway.
|
||||||
|
Type.GetEnumValues();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return values.Cast<object?>().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class PropertyBindingExtensions
|
return null;
|
||||||
{
|
}
|
||||||
public static IReadOnlyList<object?>? TryGetValidValues(this PropertyBinding binding) =>
|
|
||||||
binding.PropertyType.IsEnum
|
|
||||||
? binding.PropertyType.GetEnumValuesAsUnderlyingType().Cast<object?>().ToArray()
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
@@ -8,13 +9,17 @@ namespace CliFx.Utils.Extensions;
|
|||||||
|
|
||||||
internal static class TypeExtensions
|
internal static class TypeExtensions
|
||||||
{
|
{
|
||||||
public static bool Implements(this Type type, Type interfaceType) =>
|
public static bool Implements(
|
||||||
type.GetInterfaces().Contains(interfaceType);
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type,
|
||||||
|
Type interfaceType
|
||||||
|
) => type.GetInterfaces().Contains(interfaceType);
|
||||||
|
|
||||||
public static Type? TryGetNullableUnderlyingType(this Type type) =>
|
public static Type? TryGetNullableUnderlyingType(this Type type) =>
|
||||||
Nullable.GetUnderlyingType(type);
|
Nullable.GetUnderlyingType(type);
|
||||||
|
|
||||||
public static Type? TryGetEnumerableUnderlyingType(this Type type)
|
public static Type? TryGetEnumerableUnderlyingType(
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (type.IsPrimitive)
|
if (type.IsPrimitive)
|
||||||
return null;
|
return null;
|
||||||
@@ -35,24 +40,20 @@ internal static class TypeExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static MethodInfo? TryGetStaticParseMethod(
|
public static MethodInfo? TryGetStaticParseMethod(
|
||||||
this Type type,
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type,
|
||||||
bool withFormatProvider = false
|
bool withFormatProvider = false
|
||||||
)
|
) =>
|
||||||
{
|
type.GetMethod(
|
||||||
var argumentTypes = withFormatProvider
|
|
||||||
? new[] { typeof(string), typeof(IFormatProvider) }
|
|
||||||
: new[] { typeof(string) };
|
|
||||||
|
|
||||||
return type.GetMethod(
|
|
||||||
"Parse",
|
"Parse",
|
||||||
BindingFlags.Public | BindingFlags.Static,
|
BindingFlags.Public | BindingFlags.Static,
|
||||||
null,
|
null,
|
||||||
argumentTypes,
|
withFormatProvider ? [typeof(string), typeof(IFormatProvider)] : [typeof(string)],
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsToStringOverriden(this Type type)
|
public static bool IsToStringOverriden(
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var toStringMethod = type.GetMethod(nameof(ToString), Type.EmptyTypes);
|
var toStringMethod = type.GetMethod(nameof(ToString), Type.EmptyTypes);
|
||||||
return toStringMethod?.GetBaseDefinition()?.DeclaringType != toStringMethod?.DeclaringType;
|
return toStringMethod?.GetBaseDefinition()?.DeclaringType != toStringMethod?.DeclaringType;
|
||||||
|
|||||||
Reference in New Issue
Block a user