Use file scoped namespace declarations

This commit is contained in:
Patrik Svensson
2021-12-21 11:06:46 +01:00
committed by Phil Scott
parent 1dbaf50935
commit ec1188b837
607 changed files with 28739 additions and 29245 deletions

View File

@ -3,23 +3,22 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
namespace Spectre.Console.Cli
{
internal sealed class CommandArgument : CommandParameter
{
public string Value { get; }
public int Position { get; set; }
namespace Spectre.Console.Cli;
public CommandArgument(
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
TypeConverterAttribute? converter, DefaultValueAttribute? defaultValue,
CommandArgumentAttribute argument, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators)
: base(parameterType, parameterKind, property, description, converter, defaultValue,
null, valueProvider, validators, argument.IsRequired, false)
{
Value = argument.ValueName;
Position = argument.Position;
}
internal sealed class CommandArgument : CommandParameter
{
public string Value { get; }
public int Position { get; set; }
public CommandArgument(
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
TypeConverterAttribute? converter, DefaultValueAttribute? defaultValue,
CommandArgumentAttribute argument, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators)
: base(parameterType, parameterKind, property, description, converter, defaultValue,
null, valueProvider, validators, argument.IsRequired, false)
{
Value = argument.ValueName;
Position = argument.Position;
}
}
}

View File

@ -1,21 +1,20 @@
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandContainerExtensions
{
internal static class CommandContainerExtensions
public static CommandInfo? FindCommand(this ICommandContainer root, string name, CaseSensitivity sensitivity)
{
public static CommandInfo? FindCommand(this ICommandContainer root, string name, CaseSensitivity sensitivity)
var result = root.Commands.FirstOrDefault(
c => c.Name.Equals(name, sensitivity.GetStringComparison(CommandPart.CommandName)));
if (result == null)
{
var result = root.Commands.FirstOrDefault(
c => c.Name.Equals(name, sensitivity.GetStringComparison(CommandPart.CommandName)));
if (result == null)
{
result = root.Commands.FirstOrDefault(
c => c.Aliases.Contains(name, sensitivity.GetStringComparer(CommandPart.CommandName)));
}
return result;
result = root.Commands.FirstOrDefault(
c => c.Aliases.Contains(name, sensitivity.GetStringComparer(CommandPart.CommandName)));
}
return result;
}
}
}

View File

@ -4,67 +4,66 @@ using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal sealed class CommandInfo : ICommandContainer
{
internal sealed class CommandInfo : ICommandContainer
public string Name { get; }
public HashSet<string> Aliases { get; }
public string? Description { get; }
public object? Data { get; }
public Type? CommandType { get; }
public Type SettingsType { get; }
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
public bool IsDefaultCommand { get; }
public bool IsHidden { get; }
public CommandInfo? Parent { get; }
public IList<CommandInfo> Children { get; }
public IList<CommandParameter> Parameters { get; }
public IList<string[]> Examples { get; }
public bool IsBranch => CommandType == null && Delegate == null;
IList<CommandInfo> ICommandContainer.Commands => Children;
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
{
public string Name { get; }
public HashSet<string> Aliases { get; }
public string? Description { get; }
public object? Data { get; }
public Type? CommandType { get; }
public Type SettingsType { get; }
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
public bool IsDefaultCommand { get; }
public bool IsHidden { get; }
public CommandInfo? Parent { get; }
public IList<CommandInfo> Children { get; }
public IList<CommandParameter> Parameters { get; }
public IList<string[]> Examples { get; }
Parent = parent;
public bool IsBranch => CommandType == null && Delegate == null;
IList<CommandInfo> ICommandContainer.Commands => Children;
Name = prototype.Name;
Aliases = new HashSet<string>(prototype.Aliases);
Description = prototype.Description;
Data = prototype.Data;
CommandType = prototype.CommandType;
SettingsType = prototype.SettingsType;
Delegate = prototype.Delegate;
IsDefaultCommand = prototype.IsDefaultCommand;
IsHidden = prototype.IsHidden;
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
Children = new List<CommandInfo>();
Parameters = new List<CommandParameter>();
Examples = prototype.Examples ?? new List<string[]>();
if (CommandType != null && string.IsNullOrWhiteSpace(Description))
{
Parent = parent;
Name = prototype.Name;
Aliases = new HashSet<string>(prototype.Aliases);
Description = prototype.Description;
Data = prototype.Data;
CommandType = prototype.CommandType;
SettingsType = prototype.SettingsType;
Delegate = prototype.Delegate;
IsDefaultCommand = prototype.IsDefaultCommand;
IsHidden = prototype.IsHidden;
Children = new List<CommandInfo>();
Parameters = new List<CommandParameter>();
Examples = prototype.Examples ?? new List<string[]>();
if (CommandType != null && string.IsNullOrWhiteSpace(Description))
var description = CommandType.GetCustomAttribute<DescriptionAttribute>();
if (description != null)
{
var description = CommandType.GetCustomAttribute<DescriptionAttribute>();
if (description != null)
{
Description = description.Description;
}
Description = description.Description;
}
}
public List<CommandInfo> Flatten()
{
var result = new Stack<CommandInfo>();
var current = this;
while (current != null)
{
result.Push(current);
current = current.Parent;
}
return result.ToList();
}
}
}
public List<CommandInfo> Flatten()
{
var result = new Stack<CommandInfo>();
var current = this;
while (current != null)
{
result.Push(current);
current = current.Parent;
}
return result.ToList();
}
}

View File

@ -1,79 +1,78 @@
using System.Linq;
namespace Spectre.Console.Cli
{
internal static class CommandInfoExtensions
{
public static bool HaveParentWithOption(this CommandInfo command, CommandOption option)
{
var parent = command?.Parent;
while (parent != null)
{
foreach (var parentOption in parent.Parameters.OfType<CommandOption>())
{
if (option.HaveSameBackingPropertyAs(parentOption))
{
return true;
}
}
namespace Spectre.Console.Cli;
parent = parent.Parent;
internal static class CommandInfoExtensions
{
public static bool HaveParentWithOption(this CommandInfo command, CommandOption option)
{
var parent = command?.Parent;
while (parent != null)
{
foreach (var parentOption in parent.Parameters.OfType<CommandOption>())
{
if (option.HaveSameBackingPropertyAs(parentOption))
{
return true;
}
}
return false;
parent = parent.Parent;
}
public static bool AllowParentOption(this CommandInfo command, CommandOption option)
{
// Got an immediate parent?
if (command?.Parent != null)
{
// Is the current node's settings type the same as the previous one?
if (command.SettingsType == command.Parent.SettingsType)
{
var parameters = command.Parent.Parameters.OfType<CommandOption>().ToArray();
if (parameters.Length > 0)
{
foreach (var parentOption in parameters)
{
// Is this the same one?
if (option.HaveSameBackingPropertyAs(parentOption))
{
// Is it part of the same settings class.
if (option.Property.DeclaringType == command.SettingsType)
{
// Allow it.
return true;
}
return false;
}
// Don't allow it.
return false;
public static bool AllowParentOption(this CommandInfo command, CommandOption option)
{
// Got an immediate parent?
if (command?.Parent != null)
{
// Is the current node's settings type the same as the previous one?
if (command.SettingsType == command.Parent.SettingsType)
{
var parameters = command.Parent.Parameters.OfType<CommandOption>().ToArray();
if (parameters.Length > 0)
{
foreach (var parentOption in parameters)
{
// Is this the same one?
if (option.HaveSameBackingPropertyAs(parentOption))
{
// Is it part of the same settings class.
if (option.Property.DeclaringType == command.SettingsType)
{
// Allow it.
return true;
}
// Don't allow it.
return false;
}
}
}
}
return false;
}
public static bool HaveParentWithArgument(this CommandInfo command, CommandArgument argument)
{
var parent = command?.Parent;
while (parent != null)
{
foreach (var parentOption in parent.Parameters.OfType<CommandArgument>())
{
if (argument.HaveSameBackingPropertyAs(parentOption))
{
return true;
}
}
return false;
}
parent = parent.Parent;
public static bool HaveParentWithArgument(this CommandInfo command, CommandArgument argument)
{
var parent = command?.Parent;
while (parent != null)
{
foreach (var parentOption in parent.Parameters.OfType<CommandArgument>())
{
if (argument.HaveSameBackingPropertyAs(parentOption))
{
return true;
}
}
return false;
parent = parent.Parent;
}
return false;
}
}
}

View File

@ -4,48 +4,47 @@ using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal sealed class CommandModel : ICommandContainer
{
internal sealed class CommandModel : ICommandContainer
public string? ApplicationName { get; }
public ParsingMode ParsingMode { get; }
public CommandInfo? DefaultCommand { get; }
public IList<CommandInfo> Commands { get; }
public IList<string[]> Examples { get; }
public CommandModel(
CommandAppSettings settings,
CommandInfo? defaultCommand,
IEnumerable<CommandInfo> commands,
IEnumerable<string[]> examples)
{
public string? ApplicationName { get; }
public ParsingMode ParsingMode { get; }
public CommandInfo? DefaultCommand { get; }
public IList<CommandInfo> Commands { get; }
public IList<string[]> Examples { get; }
public CommandModel(
CommandAppSettings settings,
CommandInfo? defaultCommand,
IEnumerable<CommandInfo> commands,
IEnumerable<string[]> examples)
{
ApplicationName = settings.ApplicationName;
ParsingMode = settings.ParsingMode;
DefaultCommand = defaultCommand;
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
}
public string GetApplicationName()
{
return
ApplicationName ??
Path.GetFileName(GetApplicationFile()) ?? // null is propagated by GetFileName
"?";
}
private static string? GetApplicationFile()
{
var location = Assembly.GetEntryAssembly()?.Location;
if (string.IsNullOrWhiteSpace(location))
{
// this is special case for single file executable
// (Assembly.Location returns empty string)
return Process.GetCurrentProcess().MainModule?.FileName;
}
return location;
}
ApplicationName = settings.ApplicationName;
ParsingMode = settings.ParsingMode;
DefaultCommand = defaultCommand;
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
}
}
public string GetApplicationName()
{
return
ApplicationName ??
Path.GetFileName(GetApplicationFile()) ?? // null is propagated by GetFileName
"?";
}
private static string? GetApplicationFile()
{
var location = Assembly.GetEntryAssembly()?.Location;
if (string.IsNullOrWhiteSpace(location))
{
// this is special case for single file executable
// (Assembly.Location returns empty string)
return Process.GetCurrentProcess().MainModule?.FileName;
}
return location;
}
}

View File

@ -4,250 +4,249 @@ using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandModelBuilder
{
internal static class CommandModelBuilder
// Consider removing this in favor for value tuples at some point.
private sealed class OrderedProperties
{
// Consider removing this in favor for value tuples at some point.
private sealed class OrderedProperties
{
public int Level { get; }
public int SortOrder { get; }
public PropertyInfo[] Properties { get; }
public int Level { get; }
public int SortOrder { get; }
public PropertyInfo[] Properties { get; }
public OrderedProperties(int level, int sortOrder, PropertyInfo[] properties)
{
Level = level;
SortOrder = sortOrder;
Properties = properties;
}
public OrderedProperties(int level, int sortOrder, PropertyInfo[] properties)
{
Level = level;
SortOrder = sortOrder;
Properties = properties;
}
}
public static CommandModel Build(IConfiguration configuration)
{
var result = new List<CommandInfo>();
foreach (var command in configuration.Commands)
{
result.Add(Build(null, command));
}
public static CommandModel Build(IConfiguration configuration)
var defaultCommand = default(CommandInfo);
if (configuration.DefaultCommand != null)
{
var result = new List<CommandInfo>();
foreach (var command in configuration.Commands)
{
result.Add(Build(null, command));
}
// Add the examples from the configuration to the default command.
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
var defaultCommand = default(CommandInfo);
if (configuration.DefaultCommand != null)
{
// Add the examples from the configuration to the default command.
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
// Build the default command.
defaultCommand = Build(null, configuration.DefaultCommand);
}
// Create the command model and validate it.
var model = new CommandModel(configuration.Settings, defaultCommand, result, configuration.Examples);
CommandModelValidator.Validate(model, configuration.Settings);
return model;
// Build the default command.
defaultCommand = Build(null, configuration.DefaultCommand);
}
private static CommandInfo Build(CommandInfo? parent, ConfiguredCommand command)
// Create the command model and validate it.
var model = new CommandModel(configuration.Settings, defaultCommand, result, configuration.Examples);
CommandModelValidator.Validate(model, configuration.Settings);
return model;
}
private static CommandInfo Build(CommandInfo? parent, ConfiguredCommand command)
{
var info = new CommandInfo(parent, command);
foreach (var parameter in GetParameters(info))
{
var info = new CommandInfo(parent, command);
foreach (var parameter in GetParameters(info))
{
info.Parameters.Add(parameter);
}
foreach (var childCommand in command.Children)
{
var child = Build(info, childCommand);
info.Children.Add(child);
}
// Normalize argument positions.
var index = 0;
foreach (var argument in info.Parameters.OfType<CommandArgument>()
.OrderBy(argument => argument.Position))
{
argument.Position = index;
index++;
}
return info;
info.Parameters.Add(parameter);
}
private static IEnumerable<CommandParameter> GetParameters(CommandInfo command)
foreach (var childCommand in command.Children)
{
var result = new List<CommandParameter>();
var argumentPosition = 0;
var child = Build(info, childCommand);
info.Children.Add(child);
}
// We need to get parameters in order of the class where they were defined.
// We assign each inheritance level a value that is used to properly sort the
// arguments when iterating over them.
IEnumerable<OrderedProperties> GetPropertiesInOrder()
// Normalize argument positions.
var index = 0;
foreach (var argument in info.Parameters.OfType<CommandArgument>()
.OrderBy(argument => argument.Position))
{
argument.Position = index;
index++;
}
return info;
}
private static IEnumerable<CommandParameter> GetParameters(CommandInfo command)
{
var result = new List<CommandParameter>();
var argumentPosition = 0;
// We need to get parameters in order of the class where they were defined.
// We assign each inheritance level a value that is used to properly sort the
// arguments when iterating over them.
IEnumerable<OrderedProperties> GetPropertiesInOrder()
{
var current = command.SettingsType;
var level = 0;
var sortOrder = 0;
while (current.BaseType != null)
{
var current = command.SettingsType;
var level = 0;
var sortOrder = 0;
while (current.BaseType != null)
yield return new OrderedProperties(level, sortOrder, current.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public));
current = current.BaseType;
// Things get a little bit complicated now.
// Only consider a setting's base type part of the
// setting, if there isn't a parent command that implements
// the setting's base type. This might come back to bite us :)
var currentCommand = command.Parent;
while (currentCommand != null)
{
yield return new OrderedProperties(level, sortOrder, current.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public));
current = current.BaseType;
// Things get a little bit complicated now.
// Only consider a setting's base type part of the
// setting, if there isn't a parent command that implements
// the setting's base type. This might come back to bite us :)
var currentCommand = command.Parent;
while (currentCommand != null)
if (currentCommand.SettingsType == current)
{
if (currentCommand.SettingsType == current)
{
level--;
break;
}
currentCommand = currentCommand.Parent;
level--;
break;
}
sortOrder--;
currentCommand = currentCommand.Parent;
}
sortOrder--;
}
}
var groups = GetPropertiesInOrder();
foreach (var group in groups.OrderBy(x => x.Level).ThenBy(x => x.SortOrder))
var groups = GetPropertiesInOrder();
foreach (var group in groups.OrderBy(x => x.Level).ThenBy(x => x.SortOrder))
{
var parameters = new List<CommandParameter>();
foreach (var property in group.Properties)
{
var parameters = new List<CommandParameter>();
foreach (var property in group.Properties)
if (property.IsDefined(typeof(CommandOptionAttribute)))
{
if (property.IsDefined(typeof(CommandOptionAttribute)))
var attribute = property.GetCustomAttribute<CommandOptionAttribute>();
if (attribute != null)
{
var attribute = property.GetCustomAttribute<CommandOptionAttribute>();
if (attribute != null)
{
var option = BuildOptionParameter(property, attribute);
var option = BuildOptionParameter(property, attribute);
// Any previous command has this option defined?
if (command.HaveParentWithOption(option))
// Any previous command has this option defined?
if (command.HaveParentWithOption(option))
{
// Do we allow it to exist on this command as well?
if (command.AllowParentOption(option))
{
// Do we allow it to exist on this command as well?
if (command.AllowParentOption(option))
{
option.IsShadowed = true;
parameters.Add(option);
}
}
else
{
// No parent have this option.
option.IsShadowed = true;
parameters.Add(option);
}
}
}
else if (property.IsDefined(typeof(CommandArgumentAttribute)))
{
var attribute = property.GetCustomAttribute<CommandArgumentAttribute>();
if (attribute != null)
else
{
var argument = BuildArgumentParameter(property, attribute);
// Any previous command has this argument defined?
// In that case, we should not assign the parameter to this command.
if (!command.HaveParentWithArgument(argument))
{
parameters.Add(argument);
}
// No parent have this option.
parameters.Add(option);
}
}
}
// Update the position for the parameters.
foreach (var argument in parameters.OfType<CommandArgument>().OrderBy(x => x.Position))
else if (property.IsDefined(typeof(CommandArgumentAttribute)))
{
argument.Position = argumentPosition++;
}
var attribute = property.GetCustomAttribute<CommandArgumentAttribute>();
if (attribute != null)
{
var argument = BuildArgumentParameter(property, attribute);
// Add all parameters to the result.
foreach (var groupResult in parameters)
{
result.Add(groupResult);
// Any previous command has this argument defined?
// In that case, we should not assign the parameter to this command.
if (!command.HaveParentWithArgument(argument))
{
parameters.Add(argument);
}
}
}
}
return result;
}
private static CommandOption BuildOptionParameter(PropertyInfo property, CommandOptionAttribute attribute)
{
var description = property.GetCustomAttribute<DescriptionAttribute>();
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var kind = GetOptionKind(property.PropertyType, attribute, deconstructor, converter);
if (defaultValue == null && property.PropertyType == typeof(bool))
// Update the position for the parameters.
foreach (var argument in parameters.OfType<CommandArgument>().OrderBy(x => x.Position))
{
defaultValue = new DefaultValueAttribute(false);
argument.Position = argumentPosition++;
}
return new CommandOption(property.PropertyType, kind,
property, description?.Description, converter, deconstructor,
attribute, valueProvider, validators, defaultValue,
attribute.ValueIsOptional);
// Add all parameters to the result.
foreach (var groupResult in parameters)
{
result.Add(groupResult);
}
}
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
{
var description = property.GetCustomAttribute<DescriptionAttribute>();
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
var kind = GetParameterKind(property.PropertyType);
return new CommandArgument(
property.PropertyType, kind, property,
description?.Description, converter,
defaultValue, attribute, valueProvider,
validators);
}
private static ParameterKind GetOptionKind(
Type type,
CommandOptionAttribute attribute,
PairDeconstructorAttribute? deconstructor,
TypeConverterAttribute? converter)
{
if (attribute.ValueIsOptional)
{
return ParameterKind.FlagWithValue;
}
if (type.IsPairDeconstructable() && (deconstructor != null || converter == null))
{
return ParameterKind.Pair;
}
return GetParameterKind(type);
}
private static ParameterKind GetParameterKind(Type type)
{
if (type == typeof(bool) || type == typeof(bool?))
{
return ParameterKind.Flag;
}
if (type.IsArray)
{
return ParameterKind.Vector;
}
return ParameterKind.Scalar;
}
return result;
}
}
private static CommandOption BuildOptionParameter(PropertyInfo property, CommandOptionAttribute attribute)
{
var description = property.GetCustomAttribute<DescriptionAttribute>();
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var kind = GetOptionKind(property.PropertyType, attribute, deconstructor, converter);
if (defaultValue == null && property.PropertyType == typeof(bool))
{
defaultValue = new DefaultValueAttribute(false);
}
return new CommandOption(property.PropertyType, kind,
property, description?.Description, converter, deconstructor,
attribute, valueProvider, validators, defaultValue,
attribute.ValueIsOptional);
}
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
{
var description = property.GetCustomAttribute<DescriptionAttribute>();
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
var validators = property.GetCustomAttributes<ParameterValidationAttribute>(true);
var kind = GetParameterKind(property.PropertyType);
return new CommandArgument(
property.PropertyType, kind, property,
description?.Description, converter,
defaultValue, attribute, valueProvider,
validators);
}
private static ParameterKind GetOptionKind(
Type type,
CommandOptionAttribute attribute,
PairDeconstructorAttribute? deconstructor,
TypeConverterAttribute? converter)
{
if (attribute.ValueIsOptional)
{
return ParameterKind.FlagWithValue;
}
if (type.IsPairDeconstructable() && (deconstructor != null || converter == null))
{
return ParameterKind.Pair;
}
return GetParameterKind(type);
}
private static ParameterKind GetParameterKind(Type type)
{
if (type == typeof(bool) || type == typeof(bool?))
{
return ParameterKind.Flag;
}
if (type.IsArray)
{
return ParameterKind.Vector;
}
return ParameterKind.Scalar;
}
}

View File

@ -2,193 +2,192 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandModelValidator
{
internal static class CommandModelValidator
public static void Validate(CommandModel model, CommandAppSettings settings)
{
public static void Validate(CommandModel model, CommandAppSettings settings)
if (model is null)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}
if (model.Commands.Count == 0 && model.DefaultCommand == null)
{
throw CommandConfigurationException.NoCommandConfigured();
}
foreach (var command in model.Commands)
{
// Alias collision?
foreach (var alias in command.Aliases)
{
if (model.Commands.Any(x => x.Name.Equals(alias, StringComparison.OrdinalIgnoreCase)))
{
throw CommandConfigurationException.CommandNameConflict(command, alias);
}
}
}
Validate(model.DefaultCommand);
foreach (var command in model.Commands)
{
Validate(command);
}
if (settings.ValidateExamples)
{
ValidateExamples(model, settings);
}
throw new ArgumentNullException(nameof(model));
}
private static void Validate(CommandInfo? command)
if (settings is null)
{
if (command == null)
{
return;
}
// Get duplicate options for command.
var duplicateOptions = GetDuplicates(command);
if (duplicateOptions.Length > 0)
{
throw CommandConfigurationException.DuplicateOption(command, duplicateOptions);
}
// No children?
if (command.IsBranch && command.Children.Count == 0)
{
throw CommandConfigurationException.BranchHasNoChildren(command);
}
var arguments = command.Parameters
.OfType<CommandArgument>()
.OrderBy(x => x.Position)
.ToList();
// vector arguments?
if (arguments.Any(x => x.ParameterKind == ParameterKind.Vector))
{
// Multiple vector arguments for command?
if (arguments.Count(x => x.ParameterKind == ParameterKind.Vector) > 1)
{
throw CommandConfigurationException.TooManyVectorArguments(command);
}
// Make sure that the vector argument is specified last.
if (arguments.Last().ParameterKind != ParameterKind.Vector)
{
throw CommandConfigurationException.VectorArgumentNotSpecifiedLast(command);
}
}
// Arguments
foreach (var argument in arguments)
{
if (argument.Required && argument.DefaultValue != null)
{
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
}
}
// Options
var options = command.Parameters.OfType<CommandOption>();
foreach (var option in options)
{
// Pair deconstructable?
if (option.Property.PropertyType.IsPairDeconstructable())
{
if (option.PairDeconstructor != null && option.Converter != null)
{
throw CommandConfigurationException.OptionBothHasPairDeconstructorAndTypeParameter(option);
}
}
else if (option.PairDeconstructor != null)
{
throw CommandConfigurationException.OptionTypeDoesNotSupportDeconstruction(option);
}
// Optional options that are not flags?
if (option.ParameterKind == ParameterKind.FlagWithValue && !option.IsFlagValue())
{
throw CommandConfigurationException.OptionalOptionValueMustBeFlagWithValue(option);
}
}
// Validate child commands.
foreach (var childCommand in command.Children)
{
Validate(childCommand);
}
throw new ArgumentNullException(nameof(settings));
}
private static void ValidateExamples(CommandModel model, CommandAppSettings settings)
if (model.Commands.Count == 0 && model.DefaultCommand == null)
{
var examples = new List<string[]>();
examples.AddRangeIfNotNull(model.Examples);
throw CommandConfigurationException.NoCommandConfigured();
}
// Get all examples.
var queue = new Queue<ICommandContainer>(new[] { model });
while (queue.Count > 0)
foreach (var command in model.Commands)
{
// Alias collision?
foreach (var alias in command.Aliases)
{
var current = queue.Dequeue();
foreach (var command in current.Commands)
if (model.Commands.Any(x => x.Name.Equals(alias, StringComparison.OrdinalIgnoreCase)))
{
examples.AddRangeIfNotNull(command.Examples);
queue.Enqueue(command);
}
}
// Validate all examples.
foreach (var example in examples)
{
try
{
var parser = new CommandTreeParser(model, settings, ParsingMode.Strict);
parser.Parse(example);
}
catch (Exception ex)
{
throw new CommandConfigurationException("Validation of examples failed.", ex);
throw CommandConfigurationException.CommandNameConflict(command, alias);
}
}
}
private static string[] GetDuplicates(CommandInfo command)
Validate(model.DefaultCommand);
foreach (var command in model.Commands)
{
var result = new Dictionary<string, int>(StringComparer.Ordinal);
Validate(command);
}
void AddToResult(IEnumerable<string> keys)
{
foreach (var key in keys)
{
if (!string.IsNullOrWhiteSpace(key))
{
if (!result.ContainsKey(key))
{
result.Add(key, 0);
}
result[key]++;
}
}
}
foreach (var option in command.Parameters.OfType<CommandOption>())
{
AddToResult(option.ShortNames);
AddToResult(option.LongNames);
}
return result.Where(x => x.Value > 1)
.Select(x => x.Key).ToArray();
if (settings.ValidateExamples)
{
ValidateExamples(model, settings);
}
}
}
private static void Validate(CommandInfo? command)
{
if (command == null)
{
return;
}
// Get duplicate options for command.
var duplicateOptions = GetDuplicates(command);
if (duplicateOptions.Length > 0)
{
throw CommandConfigurationException.DuplicateOption(command, duplicateOptions);
}
// No children?
if (command.IsBranch && command.Children.Count == 0)
{
throw CommandConfigurationException.BranchHasNoChildren(command);
}
var arguments = command.Parameters
.OfType<CommandArgument>()
.OrderBy(x => x.Position)
.ToList();
// vector arguments?
if (arguments.Any(x => x.ParameterKind == ParameterKind.Vector))
{
// Multiple vector arguments for command?
if (arguments.Count(x => x.ParameterKind == ParameterKind.Vector) > 1)
{
throw CommandConfigurationException.TooManyVectorArguments(command);
}
// Make sure that the vector argument is specified last.
if (arguments.Last().ParameterKind != ParameterKind.Vector)
{
throw CommandConfigurationException.VectorArgumentNotSpecifiedLast(command);
}
}
// Arguments
foreach (var argument in arguments)
{
if (argument.Required && argument.DefaultValue != null)
{
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
}
}
// Options
var options = command.Parameters.OfType<CommandOption>();
foreach (var option in options)
{
// Pair deconstructable?
if (option.Property.PropertyType.IsPairDeconstructable())
{
if (option.PairDeconstructor != null && option.Converter != null)
{
throw CommandConfigurationException.OptionBothHasPairDeconstructorAndTypeParameter(option);
}
}
else if (option.PairDeconstructor != null)
{
throw CommandConfigurationException.OptionTypeDoesNotSupportDeconstruction(option);
}
// Optional options that are not flags?
if (option.ParameterKind == ParameterKind.FlagWithValue && !option.IsFlagValue())
{
throw CommandConfigurationException.OptionalOptionValueMustBeFlagWithValue(option);
}
}
// Validate child commands.
foreach (var childCommand in command.Children)
{
Validate(childCommand);
}
}
private static void ValidateExamples(CommandModel model, CommandAppSettings settings)
{
var examples = new List<string[]>();
examples.AddRangeIfNotNull(model.Examples);
// Get all examples.
var queue = new Queue<ICommandContainer>(new[] { model });
while (queue.Count > 0)
{
var current = queue.Dequeue();
foreach (var command in current.Commands)
{
examples.AddRangeIfNotNull(command.Examples);
queue.Enqueue(command);
}
}
// Validate all examples.
foreach (var example in examples)
{
try
{
var parser = new CommandTreeParser(model, settings, ParsingMode.Strict);
parser.Parse(example);
}
catch (Exception ex)
{
throw new CommandConfigurationException("Validation of examples failed.", ex);
}
}
}
private static string[] GetDuplicates(CommandInfo command)
{
var result = new Dictionary<string, int>(StringComparer.Ordinal);
void AddToResult(IEnumerable<string> keys)
{
foreach (var key in keys)
{
if (!string.IsNullOrWhiteSpace(key))
{
if (!result.ContainsKey(key))
{
result.Add(key, 0);
}
result[key]++;
}
}
}
foreach (var option in command.Parameters.OfType<CommandOption>())
{
AddToResult(option.ShortNames);
AddToResult(option.LongNames);
}
return result.Where(x => x.Value > 1)
.Select(x => x.Key).ToArray();
}
}

View File

@ -3,34 +3,33 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal sealed class CommandOption : CommandParameter
{
internal sealed class CommandOption : CommandParameter
public IReadOnlyList<string> LongNames { get; }
public IReadOnlyList<string> ShortNames { get; }
public string? ValueName { get; }
public bool ValueIsOptional { get; }
public bool IsShadowed { get; set; }
public CommandOption(
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
TypeConverterAttribute? converter, PairDeconstructorAttribute? deconstructor,
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators,
DefaultValueAttribute? defaultValue, bool valueIsOptional)
: base(parameterType, parameterKind, property, description, converter,
defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden)
{
public IReadOnlyList<string> LongNames { get; }
public IReadOnlyList<string> ShortNames { get; }
public string? ValueName { get; }
public bool ValueIsOptional { get; }
public bool IsShadowed { get; set; }
LongNames = optionAttribute.LongNames;
ShortNames = optionAttribute.ShortNames;
ValueName = optionAttribute.ValueName;
ValueIsOptional = valueIsOptional;
}
public CommandOption(
Type parameterType, ParameterKind parameterKind, PropertyInfo property, string? description,
TypeConverterAttribute? converter, PairDeconstructorAttribute? deconstructor,
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators,
DefaultValueAttribute? defaultValue, bool valueIsOptional)
: base(parameterType, parameterKind, property, description, converter,
defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden)
{
LongNames = optionAttribute.LongNames;
ShortNames = optionAttribute.ShortNames;
ValueName = optionAttribute.ValueName;
ValueIsOptional = valueIsOptional;
}
public string GetOptionName()
{
return LongNames.Count > 0 ? LongNames[0] : ShortNames[0];
}
public string GetOptionName()
{
return LongNames.Count > 0 ? LongNames[0] : ShortNames[0];
}
}

View File

@ -4,153 +4,152 @@ using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal abstract class CommandParameter : ICommandParameterInfo
{
internal abstract class CommandParameter : ICommandParameterInfo
public Guid Id { get; }
public Type ParameterType { get; }
public ParameterKind ParameterKind { get; }
public PropertyInfo Property { get; }
public string? Description { get; }
public DefaultValueAttribute? DefaultValue { get; }
public TypeConverterAttribute? Converter { get; }
public PairDeconstructorAttribute? PairDeconstructor { get; }
public List<ParameterValidationAttribute> Validators { get; }
public ParameterValueProviderAttribute? ValueProvider { get; }
public bool Required { get; set; }
public bool IsHidden { get; }
public string PropertyName => Property.Name;
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
&& (PairDeconstructor != null || Converter == null);
protected CommandParameter(
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
string? description, TypeConverterAttribute? converter,
DefaultValueAttribute? defaultValue,
PairDeconstructorAttribute? deconstructor,
ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators, bool required, bool isHidden)
{
public Guid Id { get; }
public Type ParameterType { get; }
public ParameterKind ParameterKind { get; }
public PropertyInfo Property { get; }
public string? Description { get; }
public DefaultValueAttribute? DefaultValue { get; }
public TypeConverterAttribute? Converter { get; }
public PairDeconstructorAttribute? PairDeconstructor { get; }
public List<ParameterValidationAttribute> Validators { get; }
public ParameterValueProviderAttribute? ValueProvider { get; }
public bool Required { get; set; }
public bool IsHidden { get; }
public string PropertyName => Property.Name;
Id = Guid.NewGuid();
ParameterType = parameterType;
ParameterKind = parameterKind;
Property = property;
Description = description;
Converter = converter;
DefaultValue = defaultValue;
PairDeconstructor = deconstructor;
ValueProvider = valueProvider;
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
Required = required;
IsHidden = isHidden;
}
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
&& (PairDeconstructor != null || Converter == null);
public bool IsFlagValue()
{
return ParameterType.GetInterfaces().Any(i => i == typeof(IFlagValue));
}
protected CommandParameter(
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
string? description, TypeConverterAttribute? converter,
DefaultValueAttribute? defaultValue,
PairDeconstructorAttribute? deconstructor,
ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators, bool required, bool isHidden)
public bool HaveSameBackingPropertyAs(CommandParameter other)
{
return CommandParameterComparer.ByBackingProperty.Equals(this, other);
}
public void Assign(CommandSettings settings, ITypeResolver resolver, object? value)
{
// Is the property pair deconstructable?
// TODO: This needs to be better defined
if (Property.PropertyType.IsPairDeconstructable() && WantRawValue)
{
Id = Guid.NewGuid();
ParameterType = parameterType;
ParameterKind = parameterKind;
Property = property;
Description = description;
Converter = converter;
DefaultValue = defaultValue;
PairDeconstructor = deconstructor;
ValueProvider = valueProvider;
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
Required = required;
IsHidden = isHidden;
}
var genericTypes = Property.PropertyType.GetGenericArguments();
public bool IsFlagValue()
{
return ParameterType.GetInterfaces().Any(i => i == typeof(IFlagValue));
}
public bool HaveSameBackingPropertyAs(CommandParameter other)
{
return CommandParameterComparer.ByBackingProperty.Equals(this, other);
}
public void Assign(CommandSettings settings, ITypeResolver resolver, object? value)
{
// Is the property pair deconstructable?
// TODO: This needs to be better defined
if (Property.PropertyType.IsPairDeconstructable() && WantRawValue)
var multimap = (IMultiMap?)Property.GetValue(settings);
if (multimap == null)
{
var genericTypes = Property.PropertyType.GetGenericArguments();
var multimap = (IMultiMap?)Property.GetValue(settings);
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
if (multimap == null)
{
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
if (multimap == null)
{
throw new InvalidOperationException("Could not create multimap");
}
throw new InvalidOperationException("Could not create multimap");
}
// Create deconstructor.
var deconstructorType = PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
deconstructor = activatedDeconstructor;
}
// Deconstruct and add to multimap.
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
if (pair.Key != null)
{
multimap.Add(pair);
}
value = multimap;
}
else if (Property.PropertyType.IsArray)
// Create deconstructor.
var deconstructorType = PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
// Add a new item to the array
var array = (Array?)Property.GetValue(settings);
Array newArray;
var elementType = Property.PropertyType.GetElementType();
if (elementType == null)
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException("Could not get property type.");
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
if (array == null)
{
newArray = Array.CreateInstance(elementType, 1);
}
else
{
newArray = Array.CreateInstance(elementType, array.Length + 1);
array.CopyTo(newArray, 0);
}
newArray.SetValue(value, newArray.Length - 1);
value = newArray;
deconstructor = activatedDeconstructor;
}
else if (IsFlagValue())
// Deconstruct and add to multimap.
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
if (pair.Key != null)
{
var flagValue = (IFlagValue?)Property.GetValue(settings);
multimap.Add(pair);
}
value = multimap;
}
else if (Property.PropertyType.IsArray)
{
// Add a new item to the array
var array = (Array?)Property.GetValue(settings);
Array newArray;
var elementType = Property.PropertyType.GetElementType();
if (elementType == null)
{
throw new InvalidOperationException("Could not get property type.");
}
if (array == null)
{
newArray = Array.CreateInstance(elementType, 1);
}
else
{
newArray = Array.CreateInstance(elementType, array.Length + 1);
array.CopyTo(newArray, 0);
}
newArray.SetValue(value, newArray.Length - 1);
value = newArray;
}
else if (IsFlagValue())
{
var flagValue = (IFlagValue?)Property.GetValue(settings);
if (flagValue == null)
{
flagValue = (IFlagValue?)Activator.CreateInstance(ParameterType);
if (flagValue == null)
{
flagValue = (IFlagValue?)Activator.CreateInstance(ParameterType);
if (flagValue == null)
{
throw new InvalidOperationException("Could not create flag value.");
}
throw new InvalidOperationException("Could not create flag value.");
}
if (value != null)
{
// Null means set, but not with a valid value.
flagValue.Value = value;
}
// If the parameter was mapped, then it's set.
flagValue.IsSet = true;
value = flagValue;
}
Property.SetValue(settings, value);
if (value != null)
{
// Null means set, but not with a valid value.
flagValue.Value = value;
}
// If the parameter was mapped, then it's set.
flagValue.IsSet = true;
value = flagValue;
}
public object? Get(CommandSettings settings)
{
return Property.GetValue(settings);
}
Property.SetValue(settings, value);
}
public object? Get(CommandSettings settings)
{
return Property.GetValue(settings);
}
}

View File

@ -1,32 +1,31 @@
using System.Collections.Generic;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandParameterComparer
{
internal static class CommandParameterComparer
public static readonly ByBackingPropertyComparer ByBackingProperty = new ByBackingPropertyComparer();
public sealed class ByBackingPropertyComparer : IEqualityComparer<CommandParameter?>
{
public static readonly ByBackingPropertyComparer ByBackingProperty = new ByBackingPropertyComparer();
public sealed class ByBackingPropertyComparer : IEqualityComparer<CommandParameter?>
public bool Equals(CommandParameter? x, CommandParameter? y)
{
public bool Equals(CommandParameter? x, CommandParameter? y)
if (x is null || y is null)
{
if (x is null || y is null)
{
return false;
}
if (ReferenceEquals(x, y))
{
return true;
}
return x.Property.MetadataToken == y.Property.MetadataToken;
return false;
}
public int GetHashCode(CommandParameter? obj)
if (ReferenceEquals(x, y))
{
return obj?.Property?.MetadataToken.GetHashCode() ?? 0;
return true;
}
return x.Property.MetadataToken == y.Property.MetadataToken;
}
public int GetHashCode(CommandParameter? obj)
{
return obj?.Property?.MetadataToken.GetHashCode() ?? 0;
}
}
}
}

View File

@ -1,15 +1,14 @@
using System.Collections.Generic;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command container.
/// </summary>
internal interface ICommandContainer
{
/// <summary>
/// Represents a command container.
/// Gets all commands in the container.
/// </summary>
internal interface ICommandContainer
{
/// <summary>
/// Gets all commands in the container.
/// </summary>
IList<CommandInfo> Commands { get; }
}
}
IList<CommandInfo> Commands { get; }
}

View File

@ -1,22 +1,21 @@
using System.ComponentModel;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal enum ParameterKind
{
internal enum ParameterKind
{
[Description("flag")]
Flag = 0,
[Description("flag")]
Flag = 0,
[Description("scalar")]
Scalar = 1,
[Description("scalar")]
Scalar = 1,
[Description("vector")]
Vector = 2,
[Description("vector")]
Vector = 2,
[Description("flagvalue")]
FlagWithValue = 3,
[Description("flagvalue")]
FlagWithValue = 3,
[Description("pair")]
Pair = 4,
}
[Description("pair")]
Pair = 4,
}