diff --git a/CliFx/Internal/Extensions.cs b/CliFx/Internal/Extensions.cs index 9184c88..0e4c65a 100644 --- a/CliFx/Internal/Extensions.cs +++ b/CliFx/Internal/Extensions.cs @@ -44,7 +44,7 @@ namespace CliFx.Internal return type.GetInterfaces() .Select(GetEnumerableUnderlyingType) .ExceptNull() - .OrderByDescending(t => t != typeof(object)) + .OrderByDescending(t => t != typeof(object)) // prioritize more specific types .FirstOrDefault(); } diff --git a/CliFx/Services/CommandInitializer.cs b/CliFx/Services/CommandInitializer.cs index be4c258..c934257 100644 --- a/CliFx/Services/CommandInitializer.cs +++ b/CliFx/Services/CommandInitializer.cs @@ -35,19 +35,24 @@ namespace CliFx.Services schema.GuardNotNull(nameof(schema)); input.GuardNotNull(nameof(input)); + // Keep track of unset required options to report an error at a later stage var unsetRequiredOptions = schema.Options.Where(o => o.IsRequired).ToList(); // Set command options - foreach (var option in input.Options) + foreach (var optionInput in input.Options) { - var optionSchema = schema.Options.FindByAlias(option.Alias); - + // Find matching option schema for this option input + var optionSchema = schema.Options.FindByAlias(optionInput.Alias); if (optionSchema == null) continue; - var convertedValue = _commandOptionInputConverter.ConvertOption(option, optionSchema.Property.PropertyType); + // Convert option to the type of the underlying property + var convertedValue = _commandOptionInputConverter.ConvertOption(optionInput, optionSchema.Property.PropertyType); + + // Set value of the underlying property optionSchema.Property.SetValue(command, convertedValue); + // Mark this required option as set if (optionSchema.IsRequired) unsetRequiredOptions.Remove(optionSchema); } diff --git a/CliFx/Services/CommandOptionInputConverter.cs b/CliFx/Services/CommandOptionInputConverter.cs index b8f8611..273690d 100644 --- a/CliFx/Services/CommandOptionInputConverter.cs +++ b/CliFx/Services/CommandOptionInputConverter.cs @@ -150,7 +150,10 @@ namespace CliFx.Services // Multiple values else { + // Determine underlying type of elements inside the target collection type var underlyingType = targetType.GetEnumerableUnderlyingType() ?? typeof(object); + + // Convert values to that type var convertedValues = option.Values.Select(v => ConvertValue(v, underlyingType)).ToNonGenericArray(underlyingType); var convertedValuesType = convertedValues.GetType(); @@ -158,7 +161,7 @@ namespace CliFx.Services if (targetType.IsAssignableFrom(convertedValuesType)) return convertedValues; - // Can be constructed from array of values (e.g. HashSet, List) + // Has a constructor that accepts an array of values (e.g. HashSet, List) var arrayConstructor = targetType.GetConstructor(new[] {convertedValuesType}); if (arrayConstructor != null) return arrayConstructor.Invoke(new object[] {convertedValues}); diff --git a/CliFx/Services/CommandSchemaResolver.cs b/CliFx/Services/CommandSchemaResolver.cs index 579e93d..e0c1def 100644 --- a/CliFx/Services/CommandSchemaResolver.cs +++ b/CliFx/Services/CommandSchemaResolver.cs @@ -31,6 +31,7 @@ namespace CliFx.Services { commandType.GuardNotNull(nameof(commandType)); + // Attribute is optional for commands in order to reduce runtime rule complexity var attribute = commandType.GetCustomAttribute(); var options = commandType.GetProperties().Select(GetCommandOptionSchema).ExceptNull().ToArray();