mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52:50 +08:00
195 lines
6.4 KiB
C#
195 lines
6.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Spectre.Console.Cli
|
|
{
|
|
internal static class CommandModelValidator
|
|
{
|
|
public static void Validate(CommandModel model, CommandAppSettings settings)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|