diff --git a/CliFx.Demo/Domain/LibraryJsonContext.cs b/CliFx.Demo/Domain/LibraryJsonContext.cs index 833e67e..b64e1c7 100644 --- a/CliFx.Demo/Domain/LibraryJsonContext.cs +++ b/CliFx.Demo/Domain/LibraryJsonContext.cs @@ -3,4 +3,4 @@ namespace CliFx.Demo.Domain; [JsonSerializable(typeof(Library))] -public partial class LibraryJsonContext : JsonSerializerContext; \ No newline at end of file +public partial class LibraryJsonContext : JsonSerializerContext; diff --git a/CliFx.Demo/Domain/LibraryProvider.cs b/CliFx.Demo/Domain/LibraryProvider.cs index 9db7f8d..edd8b91 100644 --- a/CliFx.Demo/Domain/LibraryProvider.cs +++ b/CliFx.Demo/Domain/LibraryProvider.cs @@ -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) => diff --git a/CliFx/Attributes/CommandAttribute.cs b/CliFx/Attributes/CommandAttribute.cs index 16024fe..d08a4be 100644 --- a/CliFx/Attributes/CommandAttribute.cs +++ b/CliFx/Attributes/CommandAttribute.cs @@ -4,9 +4,10 @@ namespace CliFx.Attributes; /// /// 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. /// /// /// Only one default command is allowed per application. diff --git a/CliFx/Attributes/CommandHelpOptionAttribute.cs b/CliFx/Attributes/CommandHelpOptionAttribute.cs index fa0fd71..d8a024a 100644 --- a/CliFx/Attributes/CommandHelpOptionAttribute.cs +++ b/CliFx/Attributes/CommandHelpOptionAttribute.cs @@ -4,7 +4,7 @@ /// Binds a property to the help option of a command. /// /// -/// 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. /// public class CommandHelpOptionAttribute : CommandOptionAttribute { diff --git a/CliFx/Attributes/CommandOptionAttribute.cs b/CliFx/Attributes/CommandOptionAttribute.cs index 6dd90a7..c114c8b 100644 --- a/CliFx/Attributes/CommandOptionAttribute.cs +++ b/CliFx/Attributes/CommandOptionAttribute.cs @@ -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. /// /// -/// 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). /// [AttributeUsage(AttributeTargets.Property)] public class CommandOptionAttribute : CommandInputAttribute @@ -51,7 +51,7 @@ public class CommandOptionAttribute : CommandInputAttribute /// /// Whether this option is required (default: false). - /// 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. /// /// /// You can use the required keyword on the property (introduced in C# 11) to implicitly diff --git a/CliFx/Attributes/CommandParameterAttribute.cs b/CliFx/Attributes/CommandParameterAttribute.cs index 9cc1f5e..7a8790b 100644 --- a/CliFx/Attributes/CommandParameterAttribute.cs +++ b/CliFx/Attributes/CommandParameterAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using CliFx.Extensibility; namespace CliFx.Attributes; @@ -10,7 +9,7 @@ namespace CliFx.Attributes; /// /// /// 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, ; except ), +/// If a parameter is bound to a property whose type is a sequence (i.e. implements ; except ), /// then it must have the highest order in the command. /// Only one sequential parameter is allowed per command. /// @@ -24,7 +23,7 @@ public class CommandParameterAttribute(int order) : CommandInputAttribute /// /// Whether this parameter is required (default: true). - /// 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. /// /// /// Parameter marked as non-required must have the highest order in the command. diff --git a/CliFx/Attributes/CommandVersionOptionAttribute.cs b/CliFx/Attributes/CommandVersionOptionAttribute.cs index 0169d28..072a1c9 100644 --- a/CliFx/Attributes/CommandVersionOptionAttribute.cs +++ b/CliFx/Attributes/CommandVersionOptionAttribute.cs @@ -4,7 +4,7 @@ /// Binds a property to the version option of a command. /// /// -/// 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. /// public class CommandVersionOptionAttribute : CommandOptionAttribute { diff --git a/CliFx/Exceptions/CliFxException.cs b/CliFx/Exceptions/CliFxException.cs index 5924188..76f053a 100644 --- a/CliFx/Exceptions/CliFxException.cs +++ b/CliFx/Exceptions/CliFxException.cs @@ -3,7 +3,7 @@ namespace CliFx.Exceptions; /// -/// Exception thrown when there is an error during application execution. +/// Exception thrown within . /// public partial class CliFxException( string message, diff --git a/CliFx/Extensibility/BoolBindingConverter.cs b/CliFx/Extensibility/BoolBindingConverter.cs index 400973a..493d557 100644 --- a/CliFx/Extensibility/BoolBindingConverter.cs +++ b/CliFx/Extensibility/BoolBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties of type . +/// Converter for activating command inputs bound to properties of type . /// public class BoolBindingConverter : BindingConverter { diff --git a/CliFx/Extensibility/ConvertibleBindingConverter.cs b/CliFx/Extensibility/ConvertibleBindingConverter.cs index 073367a..bb23f59 100644 --- a/CliFx/Extensibility/ConvertibleBindingConverter.cs +++ b/CliFx/Extensibility/ConvertibleBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties whose types implement . +/// Converter for activating command inputs bound to properties whose types implement . /// public class ConvertibleBindingConverter : BindingConverter where T : IConvertible diff --git a/CliFx/Extensibility/DateTimeOffsetBindingConverter.cs b/CliFx/Extensibility/DateTimeOffsetBindingConverter.cs index 546ed40..5c9aedc 100644 --- a/CliFx/Extensibility/DateTimeOffsetBindingConverter.cs +++ b/CliFx/Extensibility/DateTimeOffsetBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties of type . +/// Converter for activating command inputs bound to properties of type . /// public class DateTimeOffsetBindingConverter : BindingConverter { diff --git a/CliFx/Extensibility/DelegateBindingConverter.cs b/CliFx/Extensibility/DelegateBindingConverter.cs index db334bf..6383072 100644 --- a/CliFx/Extensibility/DelegateBindingConverter.cs +++ b/CliFx/Extensibility/DelegateBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties using a custom delegate. +/// Converter for activating command inputs bound to properties using a custom delegate. /// public class DelegateBindingConverter(Func convert) : BindingConverter diff --git a/CliFx/Extensibility/EnumBindingConverter.cs b/CliFx/Extensibility/EnumBindingConverter.cs index 02a62ac..a73d10c 100644 --- a/CliFx/Extensibility/EnumBindingConverter.cs +++ b/CliFx/Extensibility/EnumBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties of type . +/// Converter for activating command inputs bound to properties of type . /// public class EnumBindingConverter : BindingConverter where T : struct, Enum diff --git a/CliFx/Extensibility/NoopBindingConverter.cs b/CliFx/Extensibility/NoopBindingConverter.cs index 92537cd..cd86a4c 100644 --- a/CliFx/Extensibility/NoopBindingConverter.cs +++ b/CliFx/Extensibility/NoopBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties without any conversion. +/// Converter for activating command inputs bound to properties without performing any conversion. /// public class NoopBindingConverter : IBindingConverter { diff --git a/CliFx/Extensibility/NullableBindingConverter.cs b/CliFx/Extensibility/NullableBindingConverter.cs index 3150e2b..ea367e4 100644 --- a/CliFx/Extensibility/NullableBindingConverter.cs +++ b/CliFx/Extensibility/NullableBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties of type . +/// Converter for activating command inputs bound to properties of type . /// public class NullableBindingConverter(BindingConverter innerConverter) : BindingConverter where T : struct diff --git a/CliFx/Extensibility/TimeSpanBindingConverter.cs b/CliFx/Extensibility/TimeSpanBindingConverter.cs index f9fd4f2..adb8248 100644 --- a/CliFx/Extensibility/TimeSpanBindingConverter.cs +++ b/CliFx/Extensibility/TimeSpanBindingConverter.cs @@ -3,7 +3,7 @@ namespace CliFx.Extensibility; /// -/// Converter for binding command inputs to properties of type . +/// Converter for activating command inputs bound to properties of type . /// public class TimeSpanBindingConverter : BindingConverter { diff --git a/CliFx/Formatting/HelpConsoleFormatter.cs b/CliFx/Formatting/HelpConsoleFormatter.cs index 2a8cce4..c240e36 100644 --- a/CliFx/Formatting/HelpConsoleFormatter.cs +++ b/CliFx/Formatting/HelpConsoleFormatter.cs @@ -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('.'); } } diff --git a/CliFx/Infrastructure/ConsoleReader.cs b/CliFx/Infrastructure/ConsoleReader.cs index c0f0f28..ad1f293 100644 --- a/CliFx/Infrastructure/ConsoleReader.cs +++ b/CliFx/Infrastructure/ConsoleReader.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace CliFx.Infrastructure; /// -/// Implements a for reading characters or binary data from a console stream. +/// Implements a for reading characters or binary data from a console stream. /// // Both the underlying stream AND the stream reader must be synchronized! // https://github.com/Tyrrrz/CliFx/issues/123 diff --git a/CliFx/Infrastructure/ConsoleWriter.cs b/CliFx/Infrastructure/ConsoleWriter.cs index 516945e..bed047b 100644 --- a/CliFx/Infrastructure/ConsoleWriter.cs +++ b/CliFx/Infrastructure/ConsoleWriter.cs @@ -8,7 +8,7 @@ using CliFx.Utils; namespace CliFx.Infrastructure; /// -/// Implements a for writing characters or binary data to a console stream. +/// Implements a for writing characters or binary data to a console stream. /// // Both the underlying stream AND the stream writer must be synchronized! // https://github.com/Tyrrrz/CliFx/issues/123 diff --git a/CliFx/Infrastructure/DefaultTypeActivator.cs b/CliFx/Infrastructure/DefaultTypeActivator.cs index b2ddab3..09917b3 100644 --- a/CliFx/Infrastructure/DefaultTypeActivator.cs +++ b/CliFx/Infrastructure/DefaultTypeActivator.cs @@ -5,7 +5,7 @@ using CliFx.Exceptions; namespace CliFx.Infrastructure; /// -/// Implementation of that instantiates an object by using its parameterless constructor. +/// Implementation of that instantiates a type by using its parameterless constructor. /// public class DefaultTypeActivator : ITypeActivator { diff --git a/CliFx/Infrastructure/DelegateTypeActivator.cs b/CliFx/Infrastructure/DelegateTypeActivator.cs index 772a460..dfcd288 100644 --- a/CliFx/Infrastructure/DelegateTypeActivator.cs +++ b/CliFx/Infrastructure/DelegateTypeActivator.cs @@ -5,7 +5,7 @@ using CliFx.Exceptions; namespace CliFx.Infrastructure; /// -/// Implementation of that instantiates an object by using a predefined delegate. +/// Implementation of that instantiates a type by using a predefined delegate. /// public class DelegateTypeActivator(Func createInstance) : ITypeActivator { diff --git a/CliFx/Infrastructure/ITypeActivator.cs b/CliFx/Infrastructure/ITypeActivator.cs index d01d65f..a5faf5e 100644 --- a/CliFx/Infrastructure/ITypeActivator.cs +++ b/CliFx/Infrastructure/ITypeActivator.cs @@ -5,7 +5,7 @@ using CliFx.Exceptions; namespace CliFx.Infrastructure; /// -/// Abstraction for a service that can instantiate objects at run-time. +/// Abstraction for a service that can instantiate types at run-time. /// public interface ITypeActivator { diff --git a/CliFx/Schema/CommandInputSchema.cs b/CliFx/Schema/CommandInputSchema.cs index 93b45ec..0e59c20 100644 --- a/CliFx/Schema/CommandInputSchema.cs +++ b/CliFx/Schema/CommandInputSchema.cs @@ -14,23 +14,21 @@ namespace CliFx.Schema; /// public abstract class CommandInputSchema( PropertyBinding property, + bool isSequence, string? description, IBindingConverter converter, IReadOnlyList validators ) { - internal abstract string Kind { get; } - - internal abstract string FormattedIdentifier { get; } - /// /// CLR property to which this input is bound. /// public PropertyBinding Property { get; } = property; - internal bool IsSequence { get; } = - property.Type != typeof(string) - && property.Type.TryGetEnumerableUnderlyingType() is not null; + /// + /// Whether this input is a sequence (i.e. multiple values can be provided). + /// + public bool IsSequence { get; } = isSequence; /// /// Input description, used in the help text. @@ -51,14 +49,14 @@ public abstract class CommandInputSchema( { var errors = Validators .Select(validator => validator.Validate(value)) - .OfType() + .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( /// [ExcludeFromCodeCoverage] - public override string ToString() => FormattedIdentifier; + public override string ToString() => this.GetFormattedIdentifier(); } /// @@ -134,8 +131,41 @@ public abstract class CommandInputSchema< TProperty >( PropertyBinding property, + bool isSequence, string? description, BindingConverter converter, IReadOnlyList> 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)) + }; +} diff --git a/CliFx/Schema/CommandOptionSchema.cs b/CliFx/Schema/CommandOptionSchema.cs index 025c509..16f7faa 100644 --- a/CliFx/Schema/CommandOptionSchema.cs +++ b/CliFx/Schema/CommandOptionSchema.cs @@ -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; /// 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 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(); - } - } - /// /// Option name. /// @@ -99,6 +69,7 @@ public class CommandOptionSchema< TProperty >( PropertyBinding property, + bool isSequence, string? name, char? shortName, string? environmentVariable, @@ -109,6 +80,7 @@ public class CommandOptionSchema< ) : CommandOptionSchema( property, + isSequence, name, shortName, environmentVariable, diff --git a/CliFx/Schema/CommandParameterSchema.cs b/CliFx/Schema/CommandParameterSchema.cs index 9fd9c27..5daf9c4 100644 --- a/CliFx/Schema/CommandParameterSchema.cs +++ b/CliFx/Schema/CommandParameterSchema.cs @@ -9,20 +9,17 @@ namespace CliFx.Schema; /// public class CommandParameterSchema( PropertyBinding property, + bool isSequence, int order, string name, bool isRequired, string? description, IBindingConverter converter, IReadOnlyList validators -) : CommandInputSchema(property, description, converter, validators) +) : CommandInputSchema(property, isSequence, description, converter, validators) { - internal override string Kind => "Parameter"; - - internal override string FormattedIdentifier => IsSequence ? $"<{Name}>" : $"<{Name}...>"; - /// - /// Order, in which the parameter is bound from the command-line arguments. + /// Order, in which the parameter is activated from the command-line arguments. /// public int Order { get; } = order; @@ -51,11 +48,22 @@ public class CommandParameterSchema< TProperty >( PropertyBinding property, + bool isSequence, int order, string name, bool isRequired, string? description, BindingConverter converter, IReadOnlyList> validators -) : CommandParameterSchema(property, order, name, isRequired, description, converter, validators) +) + : CommandParameterSchema( + property, + isSequence, + order, + name, + isRequired, + description, + converter, + validators + ) where TCommand : ICommand; diff --git a/CliFx/Schema/CommandSchema.cs b/CliFx/Schema/CommandSchema.cs index 7d8b56e..5ced0aa 100644 --- a/CliFx/Schema/CommandSchema.cs +++ b/CliFx/Schema/CommandSchema.cs @@ -62,24 +62,8 @@ public class CommandSchema( ? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase) : string.IsNullOrWhiteSpace(name); - internal IReadOnlyDictionary GetValues(ICommand instance) - { - var result = new Dictionary(); - - 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 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(", ")} """ ); diff --git a/CliFx/Schema/PropertyBinding.cs b/CliFx/Schema/PropertyBinding.cs index 3c9a945..36130a8 100644 --- a/CliFx/Schema/PropertyBinding.cs +++ b/CliFx/Schema/PropertyBinding.cs @@ -13,8 +13,8 @@ public class PropertyBinding( DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods )] Type type, - Func get, - Action set + Func getValue, + Action setValue ) { /// @@ -28,12 +28,12 @@ public class PropertyBinding( /// /// Gets the current value of the property on the specified instance. /// - public object? Get(object instance) => get(instance); + public object? GetValue(object instance) => getValue(instance); /// /// Sets the current value of the property on the specified instance. /// - public void Set(object instance, object? value) => set(instance, value); + public void SetValue(object instance, object? value) => setValue(instance, value); internal IReadOnlyList? TryGetValidValues() { @@ -67,9 +67,9 @@ public class PropertyBinding< DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods )] TProperty ->(Func get, Action set) +>(Func getValue, Action 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) ); diff --git a/CliFx/Utils/Extensions/PropertyExtensions.cs b/CliFx/Utils/Extensions/PropertyExtensions.cs deleted file mode 100644 index 3b013ed..0000000 --- a/CliFx/Utils/Extensions/PropertyExtensions.cs +++ /dev/null @@ -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 - ) - ); -} diff --git a/CliFx/Utils/Extensions/TypeExtensions.cs b/CliFx/Utils/Extensions/TypeExtensions.cs index 4592778..1040ab5 100644 --- a/CliFx/Utils/Extensions/TypeExtensions.cs +++ b/CliFx/Utils/Extensions/TypeExtensions.cs @@ -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 implements IEnumerable (which is essentially IEnumerable), - // so we try to get a more specific underlying type. Still, if the type only implements - // IEnumerable and nothing else, then we'll just return that. - .MaxBy(t => t != typeof(object)); - public static bool IsToStringOverriden( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type )