mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Refactor
This commit is contained in:
@@ -51,6 +51,8 @@ namespace CliFx.Tests
|
|||||||
console.ResetColor();
|
console.ResetColor();
|
||||||
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||||
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||||
|
console.CursorLeft = 42;
|
||||||
|
console.CursorTop = 24;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
stdInData.Should().Be("input");
|
stdInData.Should().Be("input");
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Exceptions;
|
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests
|
||||||
{
|
{
|
||||||
@@ -89,16 +85,16 @@ namespace CliFx.Tests
|
|||||||
[Command("cmd-with-enum-args")]
|
[Command("cmd-with-enum-args")]
|
||||||
private class EnumArgumentsCommand : ICommand
|
private class EnumArgumentsCommand : ICommand
|
||||||
{
|
{
|
||||||
public enum TestEnum { Value1, Value2, Value3 };
|
public enum CustomEnum { Value1, Value2, Value3 };
|
||||||
|
|
||||||
[CommandParameter(0, Name = "value", Description = "Enum parameter.")]
|
[CommandParameter(0, Name = "value", Description = "Enum parameter.")]
|
||||||
public TestEnum ParamA { get; set; }
|
public CustomEnum ParamA { get; set; }
|
||||||
|
|
||||||
[CommandOption("value", Description = "Enum option.", IsRequired = true)]
|
[CommandOption("value", Description = "Enum option.", IsRequired = true)]
|
||||||
public TestEnum OptionA { get; set; } = TestEnum.Value1;
|
public CustomEnum OptionA { get; set; } = CustomEnum.Value1;
|
||||||
|
|
||||||
[CommandOption("nullable-value", Description = "Nullable enum option.")]
|
[CommandOption("nullable-value", Description = "Nullable enum option.")]
|
||||||
public TestEnum? OptionB { get; set; }
|
public CustomEnum? OptionB { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
@@ -116,8 +112,10 @@ namespace CliFx.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Command("cmd-with-defaults")]
|
[Command("cmd-with-defaults")]
|
||||||
private class DefaultArgumentsCommand : ICommand
|
private class ArgumentsWithDefaultValuesCommand : ICommand
|
||||||
{
|
{
|
||||||
|
public enum CustomEnum { Value1, Value2, Value3 };
|
||||||
|
|
||||||
[CommandOption(nameof(Object))]
|
[CommandOption(nameof(Object))]
|
||||||
public object? Object { get; set; } = 42;
|
public object? Object { get; set; } = 42;
|
||||||
|
|
||||||
@@ -127,98 +125,29 @@ namespace CliFx.Tests
|
|||||||
[CommandOption(nameof(EmptyString))]
|
[CommandOption(nameof(EmptyString))]
|
||||||
public string EmptyString { get; set; } = "";
|
public string EmptyString { get; set; } = "";
|
||||||
|
|
||||||
[CommandOption(nameof(WhiteSpaceString))]
|
|
||||||
public string WhiteSpaceString { get; set; } = " ";
|
|
||||||
|
|
||||||
[CommandOption(nameof(Bool))]
|
[CommandOption(nameof(Bool))]
|
||||||
public bool Bool { get; set; } = true;
|
public bool Bool { get; set; } = true;
|
||||||
|
|
||||||
[CommandOption(nameof(Char))]
|
[CommandOption(nameof(Char))]
|
||||||
public char Char { get; set; } = 't';
|
public char Char { get; set; } = 't';
|
||||||
|
|
||||||
[CommandOption(nameof(Sbyte))]
|
|
||||||
public sbyte Sbyte { get; set; } = -0b11;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Byte))]
|
|
||||||
public byte Byte { get; set; } = 0b11;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Short))]
|
|
||||||
public short Short { get; set; } = -1234;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Ushort))]
|
|
||||||
public short Ushort { get; set; } = 1234;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Int))]
|
[CommandOption(nameof(Int))]
|
||||||
public int Int { get; set; } = 1337;
|
public int Int { get; set; } = 1337;
|
||||||
|
|
||||||
[CommandOption(nameof(Uint))]
|
|
||||||
public uint Uint { get; set; } = 2345;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Long))]
|
|
||||||
public long Long { get; set; } = -1234567;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Ulong))]
|
|
||||||
public ulong Ulong { get; set; } = 12345678;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Float))]
|
|
||||||
public float Float { get; set; } = 123.4567F;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Double))]
|
|
||||||
public double Double { get; set; } = 420.1337;
|
|
||||||
|
|
||||||
[CommandOption(nameof(Decimal))]
|
|
||||||
public decimal Decimal { get; set; } = 1337.420M;
|
|
||||||
|
|
||||||
[CommandOption(nameof(DateTime))]
|
|
||||||
public DateTime DateTime { get; set; } = new DateTime(2020, 4, 20);
|
|
||||||
|
|
||||||
[CommandOption(nameof(DateTimeOffset))]
|
|
||||||
public DateTimeOffset DateTimeOffset { get; set; } =
|
|
||||||
new DateTimeOffset(2008, 5, 1, 0, 0, 0, new TimeSpan(0, 1, 0, 0, 0));
|
|
||||||
|
|
||||||
[CommandOption(nameof(TimeSpan))]
|
[CommandOption(nameof(TimeSpan))]
|
||||||
public TimeSpan TimeSpan { get; set; } = TimeSpan.FromMinutes(123);
|
public TimeSpan TimeSpan { get; set; } = TimeSpan.FromMinutes(123);
|
||||||
|
|
||||||
public enum TestEnum { Value1, Value2, Value3 };
|
[CommandOption(nameof(Enum))]
|
||||||
|
public CustomEnum Enum { get; set; } = CustomEnum.Value2;
|
||||||
[CommandOption(nameof(CustomEnum))]
|
|
||||||
public TestEnum CustomEnum { get; set; } = TestEnum.Value2;
|
|
||||||
|
|
||||||
[CommandOption(nameof(IntNullable))]
|
[CommandOption(nameof(IntNullable))]
|
||||||
public int? IntNullable { get; set; } = 1337;
|
public int? IntNullable { get; set; } = 1337;
|
||||||
|
|
||||||
[CommandOption(nameof(CustomEnumNullable))]
|
|
||||||
public TestEnum? CustomEnumNullable { get; set; } = TestEnum.Value2;
|
|
||||||
|
|
||||||
[CommandOption(nameof(TimeSpanNullable))]
|
|
||||||
public TimeSpan? TimeSpanNullable { get; set; } = TimeSpan.FromMinutes(234);
|
|
||||||
|
|
||||||
[CommandOption(nameof(ObjectArray))]
|
|
||||||
public object[]? ObjectArray { get; set; } = new object[] { "123", 4, 3.14 };
|
|
||||||
|
|
||||||
[CommandOption(nameof(StringArray))]
|
[CommandOption(nameof(StringArray))]
|
||||||
public string[]? StringArray { get; set; } = new[] { "foo", "bar", "baz" };
|
public string[]? StringArray { get; set; } = { "foo", "bar", "baz" };
|
||||||
|
|
||||||
[CommandOption(nameof(IntArray))]
|
[CommandOption(nameof(IntArray))]
|
||||||
public int[]? IntArray { get; set; } = new[] { 1, 2, 3 };
|
public int[]? IntArray { get; set; } = { 1, 2, 3 };
|
||||||
|
|
||||||
[CommandOption(nameof(CustomEnumArray))]
|
|
||||||
public TestEnum[]? CustomEnumArray { get; set; } = new[] { TestEnum.Value1, TestEnum.Value3 };
|
|
||||||
|
|
||||||
[CommandOption(nameof(IntNullableArray))]
|
|
||||||
public int?[]? IntNullableArray { get; set; } = new int?[] { 2, 3, 4, null, 5 };
|
|
||||||
|
|
||||||
[CommandOption(nameof(EnumerableNullable))]
|
|
||||||
public IEnumerable? EnumerableNullable { get; set; } = Enumerable.Repeat("foo", 3);
|
|
||||||
|
|
||||||
[CommandOption(nameof(StringEnumerable))]
|
|
||||||
public IEnumerable<string>? StringEnumerable { get; set; } = Enumerable.Repeat("bar", 3);
|
|
||||||
|
|
||||||
[CommandOption(nameof(StringReadOnlyList))]
|
|
||||||
public IReadOnlyList<string>? StringReadOnlyList { get; set; } = new[] { "foo", "bar", "baz" };
|
|
||||||
|
|
||||||
[CommandOption(nameof(StringList))]
|
|
||||||
public List<string>? StringList { get; set; } = new List<string>() { "foo", "bar", "baz" };
|
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ namespace CliFx.Tests
|
|||||||
var console = new VirtualConsole(output: stdOut);
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var application = new CliApplicationBuilder()
|
||||||
.AddCommand(typeof(DefaultArgumentsCommand))
|
.AddCommand(typeof(ArgumentsWithDefaultValuesCommand))
|
||||||
.UseConsole(console)
|
.UseConsole(console)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -317,38 +317,17 @@ namespace CliFx.Tests
|
|||||||
"Usage",
|
"Usage",
|
||||||
"cmd-with-defaults", "[options]",
|
"cmd-with-defaults", "[options]",
|
||||||
"Options",
|
"Options",
|
||||||
"--Object", "(Default: 42)",
|
"--Object", "Default: \"42\"",
|
||||||
"--String", "(Default: foo)",
|
"--String", "Default: \"foo\"",
|
||||||
"--EmptyString", "(Default: \"\"",
|
"--EmptyString", "Default: \"\"",
|
||||||
"--WhiteSpaceString", "(Default: \" \"",
|
"--Bool", "Default: \"True\"",
|
||||||
"--Bool", "(Default: True)",
|
"--Char", "Default: \"t\"",
|
||||||
"--Char", "(Default: t)",
|
"--Int", "Default: \"1337\"",
|
||||||
"--Sbyte", "(Default: -3)",
|
"--TimeSpan", "Default: \"02:03:00\"",
|
||||||
"--Byte", "(Default: 3)",
|
"--Enum", "Default: \"Value2\"",
|
||||||
"--Short", "(Default: -1234)",
|
"--IntNullable", "Default: \"1337\"",
|
||||||
"--Ushort", "(Default: 1234)",
|
"--StringArray", "Default: \"foo\" \"bar\" \"baz\"",
|
||||||
"--Int", "(Default: 1337)",
|
"--IntArray", "Default: \"1\" \"2\" \"3\""
|
||||||
"--Uint", "(Default: 2345)",
|
|
||||||
"--Long", "(Default: -1234567)",
|
|
||||||
"--Ulong", "(Default: 12345678)",
|
|
||||||
"--Float", "(Default: 123.4567)",
|
|
||||||
"--Double", "(Default: 420.1337)",
|
|
||||||
"--Decimal", "(Default: 1337.420)",
|
|
||||||
"--DateTime", $"(Default: {new DateTime(2020, 4, 20)}",
|
|
||||||
"--DateTimeOffset", $"(Default: {new DateTimeOffset(2008, 5, 1, 0, 0, 0, new TimeSpan(0, 1, 0, 0, 0))}",
|
|
||||||
"--TimeSpan", "(Default: 02:03:00)",
|
|
||||||
"--IntNullable", "(Default: 1337)",
|
|
||||||
"--CustomEnumNullable", "(Default: Value2)",
|
|
||||||
"--TimeSpanNullable", "(Default: 03:54:00)",
|
|
||||||
"--ObjectArray", "(Default: 123 4 3.14)",
|
|
||||||
"--StringArray", "(Default: foo bar baz)",
|
|
||||||
"--IntArray", "(Default: 1 2 3)",
|
|
||||||
"--CustomEnumArray", "(Default: Value1 Value3)",
|
|
||||||
"--IntNullableArray", "(Default: 2 3 4 5)",
|
|
||||||
"--EnumerableNullable", "(Default: foo foo foo)",
|
|
||||||
"--StringEnumerable", "(Default: bar bar bar)",
|
|
||||||
"--StringReadOnlyList", "(Default: foo bar baz)",
|
|
||||||
"--StringList", "(Default: foo bar baz)"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
_output.WriteLine(stdOutData);
|
_output.WriteLine(stdOutData);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Domain;
|
using CliFx.Domain;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
|
using CliFx.Internal;
|
||||||
|
|
||||||
namespace CliFx
|
namespace CliFx
|
||||||
{
|
{
|
||||||
@@ -33,7 +34,7 @@ namespace CliFx
|
|||||||
_console = console;
|
_console = console;
|
||||||
_typeActivator = typeActivator;
|
_typeActivator = typeActivator;
|
||||||
|
|
||||||
_helpTextWriter = new HelpTextWriter(metadata, console);
|
_helpTextWriter = new HelpTextWriter(metadata, console, typeActivator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<int?> HandleDebugDirectiveAsync(CommandLineInput commandLineInput)
|
private async ValueTask<int?> HandleDebugDirectiveAsync(CommandLineInput commandLineInput)
|
||||||
@@ -42,8 +43,10 @@ namespace CliFx
|
|||||||
if (!isDebugMode)
|
if (!isDebugMode)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var processId = ProcessEx.GetCurrentProcessId();
|
||||||
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Green, () =>
|
_console.WithForegroundColor(ConsoleColor.Green, () =>
|
||||||
_console.Output.WriteLine($"Attach debugger to PID {Process.GetCurrentProcess().Id} to continue."));
|
_console.Output.WriteLine($"Attach debugger to PID {processId} to continue."));
|
||||||
|
|
||||||
while (!Debugger.IsAttached)
|
while (!Debugger.IsAttached)
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
@@ -124,7 +127,7 @@ namespace CliFx
|
|||||||
// Get the command schema that matches the input or use a dummy default command as a fallback
|
// Get the command schema that matches the input or use a dummy default command as a fallback
|
||||||
var commandSchema =
|
var commandSchema =
|
||||||
applicationSchema.TryFindCommand(commandLineInput) ??
|
applicationSchema.TryFindCommand(commandLineInput) ??
|
||||||
CommandSchema.StubDefaultCommand;
|
CommandSchema.StubDefaultCommand.Schema;
|
||||||
|
|
||||||
_helpTextWriter.Write(applicationSchema, commandSchema);
|
_helpTextWriter.Write(applicationSchema, commandSchema);
|
||||||
|
|
||||||
@@ -143,17 +146,13 @@ namespace CliFx
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private int HandleCliFxException(IReadOnlyList<string> commandLineArguments, CliFxException ex)
|
||||||
/// Handle <see cref="CommandException"/>s differently from the rest because we want to
|
|
||||||
/// display it different based on whether we are showing the help text or not.
|
|
||||||
/// </summary>
|
|
||||||
private int HandleCliFxException(IReadOnlyList<string> commandLineArguments, CliFxException cfe)
|
|
||||||
{
|
{
|
||||||
var showHelp = cfe.ShowHelp;
|
var showHelp = ex.ShowHelp;
|
||||||
|
|
||||||
var errorMessage = cfe.HasMessage
|
var errorMessage = ex.HasMessage
|
||||||
? cfe.Message
|
? ex.Message
|
||||||
: cfe.ToString();
|
: ex.ToString();
|
||||||
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(errorMessage));
|
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(errorMessage));
|
||||||
|
|
||||||
@@ -162,11 +161,11 @@ namespace CliFx
|
|||||||
var applicationSchema = ApplicationSchema.Resolve(_configuration.CommandTypes);
|
var applicationSchema = ApplicationSchema.Resolve(_configuration.CommandTypes);
|
||||||
var commandLineInput = CommandLineInput.Parse(commandLineArguments);
|
var commandLineInput = CommandLineInput.Parse(commandLineArguments);
|
||||||
var commandSchema = applicationSchema.TryFindCommand(commandLineInput) ??
|
var commandSchema = applicationSchema.TryFindCommand(commandLineInput) ??
|
||||||
CommandSchema.StubDefaultCommand;
|
CommandSchema.StubDefaultCommand.Schema;
|
||||||
_helpTextWriter.Write(applicationSchema, commandSchema);
|
_helpTextWriter.Write(applicationSchema, commandSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfe.ExitCode;
|
return ex.ExitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -188,16 +187,15 @@ namespace CliFx
|
|||||||
HandleHelpOption(applicationSchema, commandLineInput) ??
|
HandleHelpOption(applicationSchema, commandLineInput) ??
|
||||||
await HandleCommandExecutionAsync(applicationSchema, commandLineInput, environmentVariables);
|
await HandleCommandExecutionAsync(applicationSchema, commandLineInput, environmentVariables);
|
||||||
}
|
}
|
||||||
catch (CliFxException cfe)
|
catch (CliFxException ex)
|
||||||
{
|
{
|
||||||
// We want to catch exceptions in order to print errors and return correct exit codes.
|
// Some exceptions may specify exit code or request help
|
||||||
// Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions.
|
return HandleCliFxException(commandLineArguments, ex);
|
||||||
var exitCode = HandleCliFxException(commandLineArguments, cfe);
|
|
||||||
return exitCode;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// For all other errors, we just write the entire thing to stderr.
|
// We want to catch all exceptions in order to print errors and return correct exit codes.
|
||||||
|
// Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions.
|
||||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.ToString()));
|
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.ToString()));
|
||||||
return ex.HResult;
|
return ex.HResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ namespace CliFx
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object CreateInstance(Type type) =>
|
public object CreateInstance(Type type) =>
|
||||||
_func(type) ?? throw CliFxException.DelegateActivatorReceivedNull(type);
|
_func(type) ?? throw CliFxException.DelegateActivatorReturnedNull(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,8 +15,6 @@ namespace CliFx.Domain
|
|||||||
|
|
||||||
public string? Description { get; }
|
public string? Description { get; }
|
||||||
|
|
||||||
public abstract string DisplayName { get; }
|
|
||||||
|
|
||||||
public bool IsScalar => TryGetEnumerableArgumentUnderlyingType() == null;
|
public bool IsScalar => TryGetEnumerableArgumentUnderlyingType() == null;
|
||||||
|
|
||||||
protected CommandArgumentSchema(PropertyInfo property, string? description)
|
protected CommandArgumentSchema(PropertyInfo property, string? description)
|
||||||
@@ -51,17 +49,17 @@ namespace CliFx.Domain
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
// String-constructable
|
// String-constructable
|
||||||
var stringConstructor = GetStringConstructor(targetType);
|
var stringConstructor = targetType.GetConstructor(new[] {typeof(string)});
|
||||||
if (stringConstructor != null)
|
if (stringConstructor != null)
|
||||||
return stringConstructor.Invoke(new object[] {value!});
|
return stringConstructor.Invoke(new object[] {value!});
|
||||||
|
|
||||||
// String-parseable (with format provider)
|
// String-parseable (with format provider)
|
||||||
var parseMethodWithFormatProvider = GetStaticParseMethodWithFormatProvider(targetType);
|
var parseMethodWithFormatProvider = targetType.GetStaticParseMethod(true);
|
||||||
if (parseMethodWithFormatProvider != null)
|
if (parseMethodWithFormatProvider != null)
|
||||||
return parseMethodWithFormatProvider.Invoke(null, new object[] {value!, ConversionFormatProvider});
|
return parseMethodWithFormatProvider.Invoke(null, new object[] {value!, FormatProvider});
|
||||||
|
|
||||||
// String-parseable (without format provider)
|
// String-parseable (without format provider)
|
||||||
var parseMethod = GetStaticParseMethod(targetType);
|
var parseMethod = targetType.GetStaticParseMethod();
|
||||||
if (parseMethod != null)
|
if (parseMethod != null)
|
||||||
return parseMethod.Invoke(null, new object[] {value!});
|
return parseMethod.Invoke(null, new object[] {value!});
|
||||||
}
|
}
|
||||||
@@ -117,11 +115,62 @@ namespace CliFx.Domain
|
|||||||
|
|
||||||
public void Inject(ICommand command, params string[] values) =>
|
public void Inject(ICommand command, params string[] values) =>
|
||||||
Inject(command, (IReadOnlyList<string>) values);
|
Inject(command, (IReadOnlyList<string>) values);
|
||||||
|
|
||||||
|
public IReadOnlyList<string> GetValidValues()
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
|
||||||
|
// Some arguments may have this as null due to a hack that enables built-in options
|
||||||
|
// TODO fix this
|
||||||
|
if (Property == null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var underlyingType =
|
||||||
|
Property.PropertyType.GetNullableUnderlyingType() ?? Property.PropertyType;
|
||||||
|
|
||||||
|
// Enum
|
||||||
|
if (underlyingType.IsEnum)
|
||||||
|
result.AddRange(Enum.GetNames(underlyingType));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? TryGetDefaultValue(ICommand instance)
|
||||||
|
{
|
||||||
|
// Some arguments may have this as null due to a hack that enables built-in options
|
||||||
|
// TODO fix this
|
||||||
|
if (Property == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var rawDefaultValue = Property.GetValue(instance);
|
||||||
|
|
||||||
|
if (!(rawDefaultValue is string) && rawDefaultValue is IEnumerable rawDefaultValues)
|
||||||
|
{
|
||||||
|
var elementType = rawDefaultValues.GetType().GetEnumerableUnderlyingType() ?? typeof(object);
|
||||||
|
|
||||||
|
return elementType.IsToStringOverriden()
|
||||||
|
? rawDefaultValues
|
||||||
|
.Cast<object?>()
|
||||||
|
.Where(o => o != null)
|
||||||
|
.Select(o => o!.ToFormattableString(FormatProvider).Quote())
|
||||||
|
.JoinToString(" ")
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawDefaultValue != null && !Equals(rawDefaultValue, rawDefaultValue.GetType().GetDefaultValue()))
|
||||||
|
{
|
||||||
|
return rawDefaultValue.GetType().IsToStringOverriden()
|
||||||
|
? rawDefaultValue.ToFormattableString(FormatProvider).Quote()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandArgumentSchema
|
internal partial class CommandArgumentSchema
|
||||||
{
|
{
|
||||||
private static readonly IFormatProvider ConversionFormatProvider = CultureInfo.InvariantCulture;
|
private static readonly IFormatProvider FormatProvider = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
private static readonly IReadOnlyDictionary<Type, Func<string?, object?>> PrimitiveConverters =
|
private static readonly IReadOnlyDictionary<Type, Func<string?, object?>> PrimitiveConverters =
|
||||||
new Dictionary<Type, Func<string?, object?>>
|
new Dictionary<Type, Func<string?, object?>>
|
||||||
@@ -130,112 +179,20 @@ namespace CliFx.Domain
|
|||||||
[typeof(string)] = v => v,
|
[typeof(string)] = v => v,
|
||||||
[typeof(bool)] = v => string.IsNullOrWhiteSpace(v) || bool.Parse(v),
|
[typeof(bool)] = v => string.IsNullOrWhiteSpace(v) || bool.Parse(v),
|
||||||
[typeof(char)] = v => v.Single(),
|
[typeof(char)] = v => v.Single(),
|
||||||
[typeof(sbyte)] = v => sbyte.Parse(v, ConversionFormatProvider),
|
[typeof(sbyte)] = v => sbyte.Parse(v, FormatProvider),
|
||||||
[typeof(byte)] = v => byte.Parse(v, ConversionFormatProvider),
|
[typeof(byte)] = v => byte.Parse(v, FormatProvider),
|
||||||
[typeof(short)] = v => short.Parse(v, ConversionFormatProvider),
|
[typeof(short)] = v => short.Parse(v, FormatProvider),
|
||||||
[typeof(ushort)] = v => ushort.Parse(v, ConversionFormatProvider),
|
[typeof(ushort)] = v => ushort.Parse(v, FormatProvider),
|
||||||
[typeof(int)] = v => int.Parse(v, ConversionFormatProvider),
|
[typeof(int)] = v => int.Parse(v, FormatProvider),
|
||||||
[typeof(uint)] = v => uint.Parse(v, ConversionFormatProvider),
|
[typeof(uint)] = v => uint.Parse(v, FormatProvider),
|
||||||
[typeof(long)] = v => long.Parse(v, ConversionFormatProvider),
|
[typeof(long)] = v => long.Parse(v, FormatProvider),
|
||||||
[typeof(ulong)] = v => ulong.Parse(v, ConversionFormatProvider),
|
[typeof(ulong)] = v => ulong.Parse(v, FormatProvider),
|
||||||
[typeof(float)] = v => float.Parse(v, ConversionFormatProvider),
|
[typeof(float)] = v => float.Parse(v, FormatProvider),
|
||||||
[typeof(double)] = v => double.Parse(v, ConversionFormatProvider),
|
[typeof(double)] = v => double.Parse(v, FormatProvider),
|
||||||
[typeof(decimal)] = v => decimal.Parse(v, ConversionFormatProvider),
|
[typeof(decimal)] = v => decimal.Parse(v, FormatProvider),
|
||||||
[typeof(DateTime)] = v => DateTime.Parse(v, ConversionFormatProvider),
|
[typeof(DateTime)] = v => DateTime.Parse(v, FormatProvider),
|
||||||
[typeof(DateTimeOffset)] = v => DateTimeOffset.Parse(v, ConversionFormatProvider),
|
[typeof(DateTimeOffset)] = v => DateTimeOffset.Parse(v, FormatProvider),
|
||||||
[typeof(TimeSpan)] = v => TimeSpan.Parse(v, ConversionFormatProvider),
|
[typeof(TimeSpan)] = v => TimeSpan.Parse(v, FormatProvider),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static ConstructorInfo? GetStringConstructor(Type type) =>
|
|
||||||
type.GetConstructor(new[] {typeof(string)});
|
|
||||||
|
|
||||||
private static MethodInfo? GetStaticParseMethod(Type type) =>
|
|
||||||
type.GetMethod("Parse",
|
|
||||||
BindingFlags.Public | BindingFlags.Static,
|
|
||||||
null, new[] {typeof(string)}, null);
|
|
||||||
|
|
||||||
private static MethodInfo? GetStaticParseMethodWithFormatProvider(Type type) =>
|
|
||||||
type.GetMethod("Parse",
|
|
||||||
BindingFlags.Public | BindingFlags.Static,
|
|
||||||
null, new[] {typeof(string), typeof(IFormatProvider)}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default and valid value handling.
|
|
||||||
internal partial class CommandArgumentSchema
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves the valid values of this command argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string collection of this command's valid values.</returns>
|
|
||||||
public IReadOnlyList<string> GetValidValues()
|
|
||||||
{
|
|
||||||
var result = new List<string>();
|
|
||||||
|
|
||||||
// Some arguments may have this as null due to a hack that enables built-in options
|
|
||||||
if (Property == null)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
var underlyingPropertyType =
|
|
||||||
Property.PropertyType.GetNullableUnderlyingType() ?? Property.PropertyType;
|
|
||||||
|
|
||||||
// Enum
|
|
||||||
if (underlyingPropertyType.IsEnum)
|
|
||||||
result.AddRange(Enum.GetNames(underlyingPropertyType));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the default value of this command argument.
|
|
||||||
/// Returns null if there's no default value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="instance">A dummy instance of the command
|
|
||||||
/// this command argument belongs to.</param>
|
|
||||||
/// <returns>The string representation of the default value.
|
|
||||||
/// If there's no default value, it returns null.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// We need a dummy instance in order to implement this because
|
|
||||||
/// we cannot retrieve it from a PropertyInfo.
|
|
||||||
/// </remarks>
|
|
||||||
public string? GetDefaultValue(ICommand? instance)
|
|
||||||
{
|
|
||||||
if (Property is null || instance is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var propertyName = Property?.Name;
|
|
||||||
string? defaultValue = null;
|
|
||||||
// Get the current culture so that the default value string
|
|
||||||
// matches the user's culture for cultured information like
|
|
||||||
// DateTimes and TimeSpans.
|
|
||||||
var culture = CultureInfo.CurrentCulture;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(propertyName))
|
|
||||||
{
|
|
||||||
var instanceProperty = instance.GetType().GetProperty(propertyName);
|
|
||||||
var value = instanceProperty.GetValue(instance);
|
|
||||||
|
|
||||||
if (value.OverridesToStringMethod())
|
|
||||||
{
|
|
||||||
// Wrap empty or whitespace strings in quotes so that they're not
|
|
||||||
// just an ugly blank in the output.
|
|
||||||
defaultValue = value.ToCulturedString(culture)
|
|
||||||
.WrapWithQuotesIfEmptyOrWhiteSpace();
|
|
||||||
}
|
|
||||||
else if (value is IEnumerable values)
|
|
||||||
{
|
|
||||||
// Cast 'values' to IEnumerable<object> so we can use LINQ on it.
|
|
||||||
defaultValue =
|
|
||||||
string.Join(" ",
|
|
||||||
values.Cast<object>()
|
|
||||||
.Where(v => v != null)
|
|
||||||
.Select(v => v.ToCulturedString(culture)
|
|
||||||
.WrapWithQuotesIfEmptyOrWhiteSpace()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,7 @@ namespace CliFx.Domain
|
|||||||
|
|
||||||
public bool IsPreviewDirective => string.Equals(Name, "preview", StringComparison.OrdinalIgnoreCase);
|
public bool IsPreviewDirective => string.Equals(Name, "preview", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public CommandDirectiveInput(string name)
|
public CommandDirectiveInput(string name) => Name = name;
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => $"[{Name}]";
|
public override string ToString() => $"[{Name}]";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ namespace CliFx.Domain
|
|||||||
{
|
{
|
||||||
public string Alias { get; }
|
public string Alias { get; }
|
||||||
|
|
||||||
public string DisplayAlias =>
|
public string RawAlias => Alias.Length > 1
|
||||||
Alias.Length > 1
|
? $"--{Alias}"
|
||||||
? $"--{Alias}"
|
: $"-{Alias}";
|
||||||
: $"-{Alias}";
|
|
||||||
|
|
||||||
public IReadOnlyList<string> Values { get; }
|
public IReadOnlyList<string> Values { get; }
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ namespace CliFx.Domain
|
|||||||
{
|
{
|
||||||
var buffer = new StringBuilder();
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
buffer.Append(DisplayAlias);
|
buffer.Append(RawAlias);
|
||||||
|
|
||||||
foreach (var value in Values)
|
foreach (var value in Values)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ namespace CliFx.Domain
|
|||||||
|
|
||||||
public char? ShortName { get; }
|
public char? ShortName { get; }
|
||||||
|
|
||||||
public override string DisplayName => !string.IsNullOrWhiteSpace(Name)
|
|
||||||
? $"--{Name}"
|
|
||||||
: $"-{ShortName}";
|
|
||||||
|
|
||||||
public string? EnvironmentVariableName { get; }
|
public string? EnvironmentVariableName { get; }
|
||||||
|
|
||||||
public bool IsRequired { get; }
|
public bool IsRequired { get; }
|
||||||
@@ -51,27 +47,35 @@ namespace CliFx.Domain
|
|||||||
!string.IsNullOrWhiteSpace(EnvironmentVariableName) &&
|
!string.IsNullOrWhiteSpace(EnvironmentVariableName) &&
|
||||||
string.Equals(EnvironmentVariableName, environmentVariableName, StringComparison.OrdinalIgnoreCase);
|
string.Equals(EnvironmentVariableName, environmentVariableName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public override string ToString()
|
public string GetUserFacingDisplayString()
|
||||||
{
|
{
|
||||||
var buffer = new StringBuilder();
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Name))
|
if (!string.IsNullOrWhiteSpace(Name))
|
||||||
{
|
{
|
||||||
buffer.Append("--");
|
buffer
|
||||||
buffer.Append(Name);
|
.Append("--")
|
||||||
|
.Append(Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Name) && ShortName != null)
|
if (!string.IsNullOrWhiteSpace(Name) && ShortName != null)
|
||||||
|
{
|
||||||
buffer.Append('|');
|
buffer.Append('|');
|
||||||
|
}
|
||||||
|
|
||||||
if (ShortName != null)
|
if (ShortName != null)
|
||||||
{
|
{
|
||||||
buffer.Append('-');
|
buffer
|
||||||
buffer.Append(ShortName);
|
.Append('-')
|
||||||
|
.Append(ShortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetInternalDisplayString() => $"{Property.Name} ('{GetUserFacingDisplayString()}')";
|
||||||
|
|
||||||
|
public override string ToString() => GetInternalDisplayString();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandOptionSchema
|
internal partial class CommandOptionSchema
|
||||||
|
|||||||
@@ -8,31 +8,30 @@ namespace CliFx.Domain
|
|||||||
{
|
{
|
||||||
public int Order { get; }
|
public int Order { get; }
|
||||||
|
|
||||||
public string? Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public override string DisplayName =>
|
public CommandParameterSchema(PropertyInfo property, int order, string name, string? description)
|
||||||
!string.IsNullOrWhiteSpace(Name)
|
|
||||||
? Name
|
|
||||||
: Property.Name.ToLowerInvariant();
|
|
||||||
|
|
||||||
public CommandParameterSchema(PropertyInfo property, int order, string? name, string? description)
|
|
||||||
: base(property, description)
|
: base(property, description)
|
||||||
{
|
{
|
||||||
Order = order;
|
Order = order;
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public string GetUserFacingDisplayString()
|
||||||
{
|
{
|
||||||
var buffer = new StringBuilder();
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
.Append('<')
|
.Append('<')
|
||||||
.Append(DisplayName)
|
.Append(Name)
|
||||||
.Append('>');
|
.Append('>');
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetInternalDisplayString() => $"{Property.Name} ([{Order}] {GetUserFacingDisplayString()})";
|
||||||
|
|
||||||
|
public override string ToString() => GetInternalDisplayString();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandParameterSchema
|
internal partial class CommandParameterSchema
|
||||||
@@ -43,10 +42,12 @@ namespace CliFx.Domain
|
|||||||
if (attribute == null)
|
if (attribute == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var name = attribute.Name ?? property.Name.ToLowerInvariant();
|
||||||
|
|
||||||
return new CommandParameterSchema(
|
return new CommandParameterSchema(
|
||||||
property,
|
property,
|
||||||
attribute.Order,
|
attribute.Order,
|
||||||
attribute.Name,
|
name,
|
||||||
attribute.Description
|
attribute.Description
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Internal;
|
using CliFx.Internal;
|
||||||
@@ -173,27 +173,11 @@ namespace CliFx.Domain
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public string GetUserFacingDisplayString() => Name ?? "";
|
||||||
{
|
|
||||||
var buffer = new StringBuilder();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Name))
|
public string GetInternalDisplayString() => $"{Type.FullName} ('{GetUserFacingDisplayString()}')";
|
||||||
buffer.Append(Name);
|
|
||||||
|
|
||||||
foreach (var parameter in Parameters)
|
public override string ToString() => GetInternalDisplayString();
|
||||||
{
|
|
||||||
buffer.AppendIfNotEmpty(' ');
|
|
||||||
buffer.Append(parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var option in Options)
|
|
||||||
{
|
|
||||||
buffer.AppendIfNotEmpty(' ');
|
|
||||||
buffer.Append(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandSchema
|
internal partial class CommandSchema
|
||||||
@@ -233,7 +217,13 @@ namespace CliFx.Domain
|
|||||||
|
|
||||||
internal partial class CommandSchema
|
internal partial class CommandSchema
|
||||||
{
|
{
|
||||||
public static CommandSchema StubDefaultCommand { get; } =
|
// TODO: won't work with dep injection
|
||||||
new CommandSchema(null!, null, null, new CommandParameterSchema[0], new CommandOptionSchema[0]);
|
[Command]
|
||||||
|
public class StubDefaultCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
|
||||||
|
public static CommandSchema Schema { get; } = TryResolve(typeof(StubDefaultCommand))!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,7 @@
|
|||||||
{
|
{
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
public CommandUnboundArgumentInput(string value)
|
public CommandUnboundArgumentInput(string value) => Value = value;
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => Value;
|
public override string ToString() => Value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CliFx.Internal;
|
using CliFx.Internal;
|
||||||
|
|
||||||
@@ -8,353 +9,342 @@ namespace CliFx.Domain
|
|||||||
{
|
{
|
||||||
private readonly ApplicationMetadata _metadata;
|
private readonly ApplicationMetadata _metadata;
|
||||||
private readonly IConsole _console;
|
private readonly IConsole _console;
|
||||||
|
private readonly ITypeActivator _typeActivator;
|
||||||
|
|
||||||
public HelpTextWriter(ApplicationMetadata metadata, IConsole console)
|
private int _column;
|
||||||
|
private int _row;
|
||||||
|
|
||||||
|
private bool IsEmpty => _column == 0 && _row == 0;
|
||||||
|
|
||||||
|
public HelpTextWriter(ApplicationMetadata metadata, IConsole console, ITypeActivator typeActivator)
|
||||||
{
|
{
|
||||||
_metadata = metadata;
|
_metadata = metadata;
|
||||||
_console = console;
|
_console = console;
|
||||||
|
_typeActivator = typeActivator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(ApplicationSchema applicationSchema, CommandSchema command)
|
private void Write(char value)
|
||||||
{
|
{
|
||||||
var column = 0;
|
_console.Output.Write(value);
|
||||||
var row = 0;
|
_column++;
|
||||||
|
}
|
||||||
|
|
||||||
var childCommands = applicationSchema.GetChildCommands(command.Name);
|
private void Write(string value)
|
||||||
|
{
|
||||||
|
_console.Output.Write(value);
|
||||||
|
_column += value.Length;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsEmpty() => column == 0 && row == 0;
|
private void Write(ConsoleColor foregroundColor, string value)
|
||||||
|
{
|
||||||
|
_console.WithForegroundColor(foregroundColor, () => Write(value));
|
||||||
|
}
|
||||||
|
|
||||||
void Render(string text)
|
private void WriteLine()
|
||||||
|
{
|
||||||
|
_console.Output.WriteLine();
|
||||||
|
_column = 0;
|
||||||
|
_row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteVerticalMargin(int size = 1)
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < size; i++)
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteHorizontalMargin(int size = 2)
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < size; i++)
|
||||||
|
Write(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteHorizontalColumnMargin(int columnSize = 20, int offsetSize = 2)
|
||||||
|
{
|
||||||
|
if (_column + offsetSize < columnSize)
|
||||||
|
WriteHorizontalMargin(columnSize - _column);
|
||||||
|
else
|
||||||
|
WriteHorizontalMargin(offsetSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteHeader(string text)
|
||||||
|
{
|
||||||
|
Write(ConsoleColor.Magenta, text);
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteApplicationInfo(CommandSchema commandSchema)
|
||||||
|
{
|
||||||
|
if (!commandSchema.IsDefault)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Title and version
|
||||||
|
Write(ConsoleColor.Yellow, _metadata.Title);
|
||||||
|
Write(' ');
|
||||||
|
Write(ConsoleColor.Yellow, _metadata.VersionText);
|
||||||
|
WriteLine();
|
||||||
|
|
||||||
|
// Description
|
||||||
|
if (!string.IsNullOrWhiteSpace(_metadata.Description))
|
||||||
{
|
{
|
||||||
_console.Output.Write(text);
|
WriteHorizontalMargin();
|
||||||
|
Write(_metadata.Description);
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
column += text.Length;
|
private void WriteCommandDescription(CommandSchema commandSchema)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(commandSchema.Description))
|
||||||
|
return;
|
||||||
|
|
||||||
|
WriteVerticalMargin();
|
||||||
|
WriteHeader("Description");
|
||||||
|
|
||||||
|
WriteHorizontalMargin();
|
||||||
|
Write(commandSchema.Description);
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCommandUsage(
|
||||||
|
CommandSchema commandSchema,
|
||||||
|
IReadOnlyList<CommandSchema> childCommandSchemas)
|
||||||
|
{
|
||||||
|
WriteVerticalMargin();
|
||||||
|
WriteHeader("Usage");
|
||||||
|
|
||||||
|
// Exe name
|
||||||
|
WriteHorizontalMargin();
|
||||||
|
Write(_metadata.ExecutableName);
|
||||||
|
|
||||||
|
// Command name
|
||||||
|
if (!string.IsNullOrWhiteSpace(commandSchema.Name))
|
||||||
|
{
|
||||||
|
Write(' ');
|
||||||
|
Write(ConsoleColor.Cyan, commandSchema.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderNewLine()
|
// Child command placeholder
|
||||||
|
if (childCommandSchemas.Any())
|
||||||
{
|
{
|
||||||
_console.Output.WriteLine();
|
Write(' ');
|
||||||
|
Write(ConsoleColor.Cyan, "[command]");
|
||||||
column = 0;
|
|
||||||
row++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderMargin(int lines = 1)
|
// Parameters
|
||||||
|
foreach (var parameterSchema in commandSchema.Parameters)
|
||||||
{
|
{
|
||||||
if (!IsEmpty())
|
Write(' ');
|
||||||
|
Write(parameterSchema.IsScalar
|
||||||
|
? $"<{parameterSchema.Name}>"
|
||||||
|
: $"<{parameterSchema.Name}...>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required options
|
||||||
|
foreach (var optionSchema in commandSchema.Options.Where(o => o.IsRequired))
|
||||||
|
{
|
||||||
|
Write(' ');
|
||||||
|
Write(ConsoleColor.White, !string.IsNullOrWhiteSpace(optionSchema.Name)
|
||||||
|
? $"--{optionSchema.Name}"
|
||||||
|
: $"-{optionSchema.ShortName}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Write(' ');
|
||||||
|
Write(optionSchema.IsScalar
|
||||||
|
? "<value>"
|
||||||
|
: "<values...>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options placeholder
|
||||||
|
Write(' ');
|
||||||
|
Write(ConsoleColor.White, "[options]");
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCommandParameters(CommandSchema commandSchema)
|
||||||
|
{
|
||||||
|
if (!commandSchema.Parameters.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
WriteVerticalMargin();
|
||||||
|
WriteHeader("Parameters");
|
||||||
|
|
||||||
|
foreach (var parameterSchema in commandSchema.Parameters.OrderBy(p => p.Order))
|
||||||
|
{
|
||||||
|
Write(ConsoleColor.Red, "* ");
|
||||||
|
Write(ConsoleColor.White, $"{parameterSchema.Name}");
|
||||||
|
|
||||||
|
WriteHorizontalColumnMargin();
|
||||||
|
|
||||||
|
// Description
|
||||||
|
if (!string.IsNullOrWhiteSpace(parameterSchema.Description))
|
||||||
{
|
{
|
||||||
for (var i = 0; i < lines; i++)
|
Write(parameterSchema.Description);
|
||||||
RenderNewLine();
|
Write(' ');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void RenderIndent(int spaces = 2)
|
// Valid values
|
||||||
{
|
var validValues = parameterSchema.GetValidValues();
|
||||||
Render(' '.Repeat(spaces));
|
if (validValues.Any())
|
||||||
}
|
|
||||||
|
|
||||||
void RenderColumnIndent(int spaces = 20, int margin = 2)
|
|
||||||
{
|
|
||||||
if (column + margin < spaces)
|
|
||||||
{
|
{
|
||||||
RenderIndent(spaces - column);
|
Write($"Valid values: {string.Join(", ", validValues)}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCommandOptions(CommandSchema commandSchema, ICommand command)
|
||||||
|
{
|
||||||
|
WriteVerticalMargin();
|
||||||
|
WriteHeader("Options");
|
||||||
|
|
||||||
|
var actualOptionSchemas = commandSchema.Options
|
||||||
|
.OrderByDescending(o => o.IsRequired)
|
||||||
|
.Concat(commandSchema.GetBuiltInOptions());
|
||||||
|
|
||||||
|
foreach (var optionSchema in actualOptionSchemas)
|
||||||
|
{
|
||||||
|
if (optionSchema.IsRequired)
|
||||||
|
{
|
||||||
|
Write(ConsoleColor.Red, "* ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RenderIndent(margin);
|
WriteHorizontalMargin();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void RenderWithColor(string text, ConsoleColor foregroundColor)
|
// Short name
|
||||||
{
|
if (optionSchema.ShortName != null)
|
||||||
_console.WithForegroundColor(foregroundColor, () => Render(text));
|
{
|
||||||
}
|
Write(ConsoleColor.White, $"-{optionSchema.ShortName}");
|
||||||
|
}
|
||||||
|
|
||||||
void RenderHeader(string text)
|
// Delimiter
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(optionSchema.Name) && optionSchema.ShortName != null)
|
||||||
RenderWithColor(text, ConsoleColor.Magenta);
|
{
|
||||||
RenderNewLine();
|
Write('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderApplicationInfo()
|
// Name
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(optionSchema.Name))
|
||||||
if (!command.IsDefault)
|
{
|
||||||
return;
|
Write(ConsoleColor.White, $"--{optionSchema.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
// Title and version
|
WriteHorizontalColumnMargin();
|
||||||
RenderWithColor(_metadata.Title, ConsoleColor.Yellow);
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor(_metadata.VersionText, ConsoleColor.Yellow);
|
|
||||||
RenderNewLine();
|
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if (!string.IsNullOrWhiteSpace(_metadata.Description))
|
if (!string.IsNullOrWhiteSpace(optionSchema.Description))
|
||||||
{
|
{
|
||||||
Render(_metadata.Description);
|
Write(optionSchema.Description);
|
||||||
RenderNewLine();
|
Write(' ');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void RenderDescription()
|
// Valid values
|
||||||
|
var validValues = optionSchema.GetValidValues();
|
||||||
|
if (validValues.Any())
|
||||||
|
{
|
||||||
|
Write($"Valid values: {validValues.Select(v => v.Quote()).JoinToString(", ")}.");
|
||||||
|
Write(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment variable
|
||||||
|
if (!string.IsNullOrWhiteSpace(optionSchema.EnvironmentVariableName))
|
||||||
|
{
|
||||||
|
Write($"Environment variable: \"{optionSchema.EnvironmentVariableName}\".");
|
||||||
|
Write(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default value
|
||||||
|
if (!optionSchema.IsRequired)
|
||||||
|
{
|
||||||
|
// TODO: move quoting logic here?
|
||||||
|
var defaultValue = optionSchema.TryGetDefaultValue(command);
|
||||||
|
if (defaultValue != null)
|
||||||
|
{
|
||||||
|
Write($"Default: {defaultValue}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCommandChildren(
|
||||||
|
CommandSchema commandSchema,
|
||||||
|
IReadOnlyList<CommandSchema> childCommandSchemas)
|
||||||
|
{
|
||||||
|
if (!childCommandSchemas.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
WriteVerticalMargin();
|
||||||
|
WriteHeader("Commands");
|
||||||
|
|
||||||
|
foreach (var childCommandSchema in childCommandSchemas)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(command.Description))
|
var relativeCommandName = !string.IsNullOrWhiteSpace(commandSchema.Name)
|
||||||
return;
|
? childCommandSchema.Name!.Substring(commandSchema.Name.Length + 1)
|
||||||
|
: childCommandSchema.Name!;
|
||||||
|
|
||||||
RenderMargin();
|
// Name
|
||||||
RenderHeader("Description");
|
WriteHorizontalMargin();
|
||||||
|
Write(ConsoleColor.Cyan, relativeCommandName);
|
||||||
|
|
||||||
RenderIndent();
|
// Description
|
||||||
Render(command.Description);
|
if (!string.IsNullOrWhiteSpace(childCommandSchema.Description))
|
||||||
RenderNewLine();
|
{
|
||||||
|
WriteHorizontalColumnMargin();
|
||||||
|
Write(childCommandSchema.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderUsage()
|
// Child command help tip
|
||||||
|
WriteVerticalMargin();
|
||||||
|
Write("You can run `");
|
||||||
|
Write(_metadata.ExecutableName);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(commandSchema.Name))
|
||||||
{
|
{
|
||||||
RenderMargin();
|
Write(' ');
|
||||||
RenderHeader("Usage");
|
Write(ConsoleColor.Cyan, commandSchema.Name);
|
||||||
|
|
||||||
// Exe name
|
|
||||||
RenderIndent();
|
|
||||||
Render(_metadata.ExecutableName);
|
|
||||||
|
|
||||||
// Command name
|
|
||||||
if (!string.IsNullOrWhiteSpace(command.Name))
|
|
||||||
{
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor(command.Name, ConsoleColor.Cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Child command placeholder
|
|
||||||
if (childCommands.Any())
|
|
||||||
{
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor("[command]", ConsoleColor.Cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameters
|
|
||||||
foreach (var parameter in command.Parameters)
|
|
||||||
{
|
|
||||||
Render(" ");
|
|
||||||
Render(parameter.IsScalar
|
|
||||||
? $"<{parameter.DisplayName}>"
|
|
||||||
: $"<{parameter.DisplayName}...>");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required options
|
|
||||||
var requiredOptionSchemas = command.Options
|
|
||||||
.Where(o => o.IsRequired)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var option in requiredOptionSchemas)
|
|
||||||
{
|
|
||||||
Render(" ");
|
|
||||||
if (!string.IsNullOrWhiteSpace(option.Name))
|
|
||||||
{
|
|
||||||
RenderWithColor($"--{option.Name}", ConsoleColor.White);
|
|
||||||
Render(" ");
|
|
||||||
Render(option.IsScalar
|
|
||||||
? "<value>"
|
|
||||||
: "<values...>");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RenderWithColor($"-{option.ShortName}", ConsoleColor.White);
|
|
||||||
Render(" ");
|
|
||||||
Render(option.IsScalar
|
|
||||||
? "<value>"
|
|
||||||
: "<values...>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options placeholder
|
|
||||||
if (command.Options.Count != requiredOptionSchemas.Length)
|
|
||||||
{
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor("[options]", ConsoleColor.White);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderNewLine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderParameters()
|
Write(' ');
|
||||||
{
|
Write(ConsoleColor.Cyan, "[command]");
|
||||||
if (!command.Parameters.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
RenderMargin();
|
Write(' ');
|
||||||
RenderHeader("Parameters");
|
Write(ConsoleColor.White, "--help");
|
||||||
|
|
||||||
var parameters = command.Parameters
|
Write("` to show help on a specific command.");
|
||||||
.OrderBy(p => p.Order)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var parameter in parameters)
|
WriteLine();
|
||||||
{
|
}
|
||||||
RenderWithColor("* ", ConsoleColor.Red);
|
|
||||||
RenderWithColor($"{parameter.DisplayName}", ConsoleColor.White);
|
|
||||||
|
|
||||||
RenderColumnIndent();
|
public void Write(ApplicationSchema applicationSchema, CommandSchema commandSchema)
|
||||||
|
{
|
||||||
// Description
|
var childCommandSchemas = applicationSchema.GetChildCommands(commandSchema.Name);
|
||||||
if (!string.IsNullOrWhiteSpace(parameter.Description))
|
var command = (ICommand) _typeActivator.CreateInstance(commandSchema.Type);
|
||||||
{
|
|
||||||
Render(parameter.Description);
|
|
||||||
Render(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid values
|
|
||||||
var validValues = parameter.GetValidValues();
|
|
||||||
if (validValues.Any())
|
|
||||||
{
|
|
||||||
Render($"Valid values: {string.Join(", ", validValues)}.");
|
|
||||||
Render(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderNewLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderOptions()
|
|
||||||
{
|
|
||||||
RenderMargin();
|
|
||||||
RenderHeader("Options");
|
|
||||||
|
|
||||||
// Instantiate a temporary instance of the command so we can get default values from it.
|
|
||||||
ICommand? tempInstance = command.Type is null ? null : Activator.CreateInstance(command.Type) as ICommand;
|
|
||||||
|
|
||||||
var options = command.Options
|
|
||||||
.OrderByDescending(o => o.IsRequired)
|
|
||||||
.Concat(command.GetBuiltInOptions())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var option in options)
|
|
||||||
{
|
|
||||||
if (option.IsRequired)
|
|
||||||
{
|
|
||||||
RenderWithColor("* ", ConsoleColor.Red);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RenderIndent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short name
|
|
||||||
if (option.ShortName != null)
|
|
||||||
{
|
|
||||||
RenderWithColor($"-{option.ShortName}", ConsoleColor.White);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delimiter
|
|
||||||
if (!string.IsNullOrWhiteSpace(option.Name) && option.ShortName != null)
|
|
||||||
{
|
|
||||||
Render("|");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name
|
|
||||||
if (!string.IsNullOrWhiteSpace(option.Name))
|
|
||||||
{
|
|
||||||
RenderWithColor($"--{option.Name}", ConsoleColor.White);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderColumnIndent();
|
|
||||||
|
|
||||||
// Description
|
|
||||||
if (!string.IsNullOrWhiteSpace(option.Description))
|
|
||||||
{
|
|
||||||
Render(option.Description);
|
|
||||||
Render(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid values
|
|
||||||
var validValues = option.GetValidValues();
|
|
||||||
if (validValues.Any())
|
|
||||||
{
|
|
||||||
Render($"Valid values: {string.Join(", ", validValues)}.");
|
|
||||||
Render(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment variable
|
|
||||||
if (!string.IsNullOrWhiteSpace(option.EnvironmentVariableName))
|
|
||||||
{
|
|
||||||
Render($"(Environment variable: {option.EnvironmentVariableName})");
|
|
||||||
Render(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default value
|
|
||||||
if (!option.IsRequired)
|
|
||||||
{
|
|
||||||
var defaultValue = option.GetDefaultValue(tempInstance);
|
|
||||||
// If 'defaultValue' is null, it means there's no default value.
|
|
||||||
if (defaultValue is object)
|
|
||||||
{
|
|
||||||
Render($"(Default: {defaultValue})");
|
|
||||||
Render(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderNewLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderChildCommands()
|
|
||||||
{
|
|
||||||
if (!childCommands.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
RenderMargin();
|
|
||||||
RenderHeader("Commands");
|
|
||||||
|
|
||||||
foreach (var childCommand in childCommands)
|
|
||||||
{
|
|
||||||
var relativeCommandName =
|
|
||||||
!string.IsNullOrWhiteSpace(command.Name)
|
|
||||||
? childCommand.Name!.Substring(command.Name.Length + 1)
|
|
||||||
: childCommand.Name!;
|
|
||||||
|
|
||||||
// Name
|
|
||||||
RenderIndent();
|
|
||||||
RenderWithColor(relativeCommandName, ConsoleColor.Cyan);
|
|
||||||
|
|
||||||
// Description
|
|
||||||
if (!string.IsNullOrWhiteSpace(childCommand.Description))
|
|
||||||
{
|
|
||||||
RenderColumnIndent();
|
|
||||||
Render(childCommand.Description);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderNewLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderMargin();
|
|
||||||
|
|
||||||
// Child command help tip
|
|
||||||
Render("You can run `");
|
|
||||||
Render(_metadata.ExecutableName);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(command.Name))
|
|
||||||
{
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor(command.Name, ConsoleColor.Cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor("[command]", ConsoleColor.Cyan);
|
|
||||||
|
|
||||||
Render(" ");
|
|
||||||
RenderWithColor("--help", ConsoleColor.White);
|
|
||||||
|
|
||||||
Render("` to show help on a specific command.");
|
|
||||||
|
|
||||||
RenderNewLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
_console.ResetColor();
|
_console.ResetColor();
|
||||||
RenderApplicationInfo();
|
|
||||||
RenderDescription();
|
WriteApplicationInfo(commandSchema);
|
||||||
RenderUsage();
|
WriteCommandDescription(commandSchema);
|
||||||
RenderParameters();
|
WriteCommandUsage(commandSchema, childCommandSchemas);
|
||||||
RenderOptions();
|
WriteCommandParameters(commandSchema);
|
||||||
RenderChildCommands();
|
WriteCommandOptions(commandSchema, command);
|
||||||
|
WriteCommandChildren(commandSchema, childCommandSchemas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Domain;
|
using CliFx.Domain;
|
||||||
|
using CliFx.Internal;
|
||||||
|
|
||||||
namespace CliFx.Exceptions
|
namespace CliFx.Exceptions
|
||||||
{
|
{
|
||||||
@@ -35,14 +36,6 @@ namespace CliFx.Exceptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int ExitCode { get; }
|
public int ExitCode { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of <see cref="CliFxException"/>.
|
|
||||||
/// </summary>
|
|
||||||
public CliFxException(string? message, bool showHelp = false)
|
|
||||||
: this(message, null, showHelp: showHelp)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CliFxException"/>.
|
/// Initializes an instance of <see cref="CliFxException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -52,9 +45,18 @@ namespace CliFx.Exceptions
|
|||||||
ExitCode = exitCode != 0
|
ExitCode = exitCode != 0
|
||||||
? exitCode
|
? exitCode
|
||||||
: throw new ArgumentException("Exit code must not be zero in order to signify failure.");
|
: throw new ArgumentException("Exit code must not be zero in order to signify failure.");
|
||||||
|
|
||||||
HasMessage = !string.IsNullOrWhiteSpace(message);
|
HasMessage = !string.IsNullOrWhiteSpace(message);
|
||||||
ShowHelp = showHelp;
|
ShowHelp = showHelp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CliFxException"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CliFxException(string? message, bool showHelp = false)
|
||||||
|
: this(message, null, showHelp: showHelp)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mid-user-facing exceptions
|
// Mid-user-facing exceptions
|
||||||
@@ -75,7 +77,7 @@ Refer to the readme to learn how to integrate a dependency container of your cho
|
|||||||
return new CliFxException(message.Trim(), innerException);
|
return new CliFxException(message.Trim(), innerException);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException DelegateActivatorReceivedNull(Type type)
|
internal static CliFxException DelegateActivatorReturnedNull(Type type)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Failed to create an instance of type '{type.FullName}', received <null> instead.
|
Failed to create an instance of type '{type.FullName}', received <null> instead.
|
||||||
@@ -112,12 +114,11 @@ If you're experiencing problems, please refer to the readme for a quickstart exa
|
|||||||
return new CliFxException(message.Trim());
|
return new CliFxException(message.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandsTooManyDefaults(
|
internal static CliFxException CommandsTooManyDefaults(IReadOnlyList<CommandSchema> invalidCommandSchemas)
|
||||||
IReadOnlyList<CommandSchema> invalidCommands)
|
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Application configuration is invalid because there are {invalidCommands.Count} default commands:
|
Application configuration is invalid because there are {invalidCommandSchemas.Count} default commands:
|
||||||
{string.Join(Environment.NewLine, invalidCommands.Select(p => p.Type.FullName))}
|
{invalidCommandSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
There can only be one default command (i.e. command with no name) in an application.
|
There can only be one default command (i.e. command with no name) in an application.
|
||||||
Other commands must have unique non-empty names that identify them.";
|
Other commands must have unique non-empty names that identify them.";
|
||||||
@@ -127,11 +128,11 @@ Other commands must have unique non-empty names that identify them.";
|
|||||||
|
|
||||||
internal static CliFxException CommandsDuplicateName(
|
internal static CliFxException CommandsDuplicateName(
|
||||||
string name,
|
string name,
|
||||||
IReadOnlyList<CommandSchema> invalidCommands)
|
IReadOnlyList<CommandSchema> invalidCommandSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Application configuration is invalid because there are {invalidCommands.Count} commands with the same name ('{name}'):
|
Application configuration is invalid because there are {invalidCommandSchemas.Count} commands with the same name ('{name}'):
|
||||||
{string.Join(Environment.NewLine, invalidCommands.Select(p => p.Type.FullName))}
|
{invalidCommandSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Commands must have unique names.
|
Commands must have unique names.
|
||||||
Names are not case-sensitive.";
|
Names are not case-sensitive.";
|
||||||
@@ -140,13 +141,13 @@ Names are not case-sensitive.";
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandParametersDuplicateOrder(
|
internal static CliFxException CommandParametersDuplicateOrder(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
int order,
|
int order,
|
||||||
IReadOnlyList<CommandParameterSchema> invalidParameters)
|
IReadOnlyList<CommandParameterSchema> invalidParameterSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains {invalidParameters.Count} parameters with the same order ({order}):
|
Command '{commandSchema.Type.FullName}' is invalid because it contains {invalidParameterSchemas.Count} parameters with the same order ({order}):
|
||||||
{string.Join(Environment.NewLine, invalidParameters.Select(p => p.Property.Name))}
|
{invalidParameterSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Parameters must have unique order.";
|
Parameters must have unique order.";
|
||||||
|
|
||||||
@@ -154,13 +155,13 @@ Parameters must have unique order.";
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandParametersDuplicateName(
|
internal static CliFxException CommandParametersDuplicateName(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
string name,
|
string name,
|
||||||
IReadOnlyList<CommandParameterSchema> invalidParameters)
|
IReadOnlyList<CommandParameterSchema> invalidParameterSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains {invalidParameters.Count} parameters with the same name ('{name}'):
|
Command '{commandSchema.Type.FullName}' is invalid because it contains {invalidParameterSchemas.Count} parameters with the same name ('{name}'):
|
||||||
{string.Join(Environment.NewLine, invalidParameters.Select(p => p.Property.Name))}
|
{invalidParameterSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Parameters must have unique names to avoid potential confusion in the help text.
|
Parameters must have unique names to avoid potential confusion in the help text.
|
||||||
Names are not case-sensitive.";
|
Names are not case-sensitive.";
|
||||||
@@ -169,12 +170,12 @@ Names are not case-sensitive.";
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandParametersTooManyNonScalar(
|
internal static CliFxException CommandParametersTooManyNonScalar(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
IReadOnlyList<CommandParameterSchema> invalidParameters)
|
IReadOnlyList<CommandParameterSchema> invalidParameterSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains {invalidParameters.Count} non-scalar parameters:
|
Command '{commandSchema.Type.FullName}' is invalid because it contains {invalidParameterSchemas.Count} non-scalar parameters:
|
||||||
{string.Join(Environment.NewLine, invalidParameters.Select(p => p.Property.Name))}
|
{invalidParameterSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Non-scalar parameter is such that is bound from more than one value (e.g. array or a complex object).
|
Non-scalar parameter is such that is bound from more than one value (e.g. array or a complex object).
|
||||||
Only one parameter in a command may be non-scalar and it must be the last one in order.
|
Only one parameter in a command may be non-scalar and it must be the last one in order.
|
||||||
@@ -185,12 +186,12 @@ If it's not feasible to fit into these constraints, consider using options inste
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandParametersNonLastNonScalar(
|
internal static CliFxException CommandParametersNonLastNonScalar(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
CommandParameterSchema invalidParameter)
|
CommandParameterSchema invalidParameterSchema)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains a non-scalar parameter which is not the last in order:
|
Command '{commandSchema.Type.FullName}' is invalid because it contains a non-scalar parameter which is not the last in order:
|
||||||
{invalidParameter.Property.Name}
|
{invalidParameterSchema}
|
||||||
|
|
||||||
Non-scalar parameter is such that is bound from more than one value (e.g. array or a complex object).
|
Non-scalar parameter is such that is bound from more than one value (e.g. array or a complex object).
|
||||||
Only one parameter in a command may be non-scalar and it must be the last one in order.
|
Only one parameter in a command may be non-scalar and it must be the last one in order.
|
||||||
@@ -201,12 +202,12 @@ If it's not feasible to fit into these constraints, consider using options inste
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandOptionsNoName(
|
internal static CliFxException CommandOptionsNoName(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
IReadOnlyList<CommandOptionSchema> invalidOptions)
|
IReadOnlyList<CommandOptionSchema> invalidOptionSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains one or more options without a name:
|
Command '{commandSchema.Type.FullName}' is invalid because it contains one or more options without a name:
|
||||||
{string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
|
{invalidOptionSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Options must have either a name or a short name or both.";
|
Options must have either a name or a short name or both.";
|
||||||
|
|
||||||
@@ -214,12 +215,12 @@ Options must have either a name or a short name or both.";
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandOptionsInvalidLengthName(
|
internal static CliFxException CommandOptionsInvalidLengthName(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
IReadOnlyList<CommandOptionSchema> invalidOptions)
|
IReadOnlyList<CommandOptionSchema> invalidOptionSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains one or more options whose names are too short:
|
Command '{commandSchema.Type.FullName}' is invalid because it contains one or more options whose names are too short:
|
||||||
{string.Join(Environment.NewLine, invalidOptions.Select(o => $"{o.Property.Name} ('{o.DisplayName}')"))}
|
{invalidOptionSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Option names must be at least 2 characters long to avoid confusion with short names.
|
Option names must be at least 2 characters long to avoid confusion with short names.
|
||||||
If you intended to set the short name instead, use the attribute overload that accepts a char.";
|
If you intended to set the short name instead, use the attribute overload that accepts a char.";
|
||||||
@@ -228,30 +229,28 @@ If you intended to set the short name instead, use the attribute overload that a
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandOptionsDuplicateName(
|
internal static CliFxException CommandOptionsDuplicateName(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
string name,
|
string name,
|
||||||
IReadOnlyList<CommandOptionSchema> invalidOptions)
|
IReadOnlyList<CommandOptionSchema> invalidOptionSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same name ('{name}'):
|
Command '{commandSchema.Type.FullName}' is invalid because it contains {invalidOptionSchemas.Count} options with the same name ('{name}'):
|
||||||
{string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
|
{invalidOptionSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Options must have unique names, because that's what identifies them.
|
Options must have unique names.
|
||||||
Names are not case-sensitive.
|
Names are not case-sensitive.";
|
||||||
|
|
||||||
To fix this, ensure that all options have different names.";
|
|
||||||
|
|
||||||
return new CliFxException(message.Trim());
|
return new CliFxException(message.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandOptionsDuplicateShortName(
|
internal static CliFxException CommandOptionsDuplicateShortName(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
char shortName,
|
char shortName,
|
||||||
IReadOnlyList<CommandOptionSchema> invalidOptions)
|
IReadOnlyList<CommandOptionSchema> invalidOptionSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same short name ('{shortName}'):
|
Command '{commandSchema.Type.FullName}' is invalid because it contains {invalidOptionSchemas.Count} options with the same short name ('{shortName}'):
|
||||||
{string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
|
{invalidOptionSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Options must have unique short names.
|
Options must have unique short names.
|
||||||
Short names are case-sensitive (i.e. 'a' and 'A' are different short names).";
|
Short names are case-sensitive (i.e. 'a' and 'A' are different short names).";
|
||||||
@@ -260,13 +259,13 @@ Short names are case-sensitive (i.e. 'a' and 'A' are different short names).";
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CommandOptionsDuplicateEnvironmentVariableName(
|
internal static CliFxException CommandOptionsDuplicateEnvironmentVariableName(
|
||||||
CommandSchema command,
|
CommandSchema commandSchema,
|
||||||
string environmentVariableName,
|
string environmentVariableName,
|
||||||
IReadOnlyList<CommandOptionSchema> invalidOptions)
|
IReadOnlyList<CommandOptionSchema> invalidOptionSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same fallback environment variable name ('{environmentVariableName}'):
|
Command '{commandSchema.Type.FullName}' is invalid because it contains {invalidOptionSchemas.Count} options with the same fallback environment variable name ('{environmentVariableName}'):
|
||||||
{string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
|
{invalidOptionSchemas.JoinToString(Environment.NewLine)}
|
||||||
|
|
||||||
Options cannot share the same environment variable as a fallback.
|
Options cannot share the same environment variable as a fallback.
|
||||||
Environment variable names are not case-sensitive.";
|
Environment variable names are not case-sensitive.";
|
||||||
@@ -283,92 +282,148 @@ Environment variable names are not case-sensitive.";
|
|||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Can't find a command that matches the following arguments:
|
Can't find a command that matches the following arguments:
|
||||||
{string.Join(" ", input.UnboundArguments.Select(a => a.Value))}";
|
{input.UnboundArguments.JoinToString(" ")}";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException CannotConvertMultipleValuesToNonScalar(
|
internal static CliFxException CannotConvertMultipleValuesToNonScalar(
|
||||||
CommandArgumentSchema argument,
|
CommandParameterSchema parameterSchema,
|
||||||
IReadOnlyList<string> values)
|
IReadOnlyList<string> values)
|
||||||
{
|
{
|
||||||
var argumentDisplayText = argument is CommandParameterSchema
|
|
||||||
? $"Parameter <{argument.DisplayName}>"
|
|
||||||
: $"Option '{argument.DisplayName}'";
|
|
||||||
|
|
||||||
var message = $@"
|
var message = $@"
|
||||||
{argumentDisplayText} expects a single value, but provided with multiple:
|
Parameter {parameterSchema.GetUserFacingDisplayString()} expects a single value, but provided with multiple:
|
||||||
{string.Join(", ", values.Select(v => $"'{v}'"))}";
|
{values.Select(v => v.Quote()).JoinToString(" ")}";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static CliFxException CannotConvertMultipleValuesToNonScalar(
|
||||||
|
CommandOptionSchema optionSchema,
|
||||||
|
IReadOnlyList<string> values)
|
||||||
|
{
|
||||||
|
var message = $@"
|
||||||
|
Option {optionSchema.GetUserFacingDisplayString()} expects a single value, but provided with multiple:
|
||||||
|
{values.Select(v => v.Quote()).JoinToString(" ")}";
|
||||||
|
|
||||||
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static CliFxException CannotConvertMultipleValuesToNonScalar(
|
||||||
|
CommandArgumentSchema argumentSchema,
|
||||||
|
IReadOnlyList<string> values) => argumentSchema switch
|
||||||
|
{
|
||||||
|
CommandParameterSchema parameterSchema => CannotConvertMultipleValuesToNonScalar(parameterSchema, values),
|
||||||
|
CommandOptionSchema optionSchema => CannotConvertMultipleValuesToNonScalar(optionSchema, values),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(argumentSchema))
|
||||||
|
};
|
||||||
|
|
||||||
internal static CliFxException CannotConvertToType(
|
internal static CliFxException CannotConvertToType(
|
||||||
CommandArgumentSchema argument,
|
CommandParameterSchema parameterSchema,
|
||||||
string? value,
|
string? value,
|
||||||
Type type,
|
Type type,
|
||||||
Exception? innerException = null)
|
Exception? innerException = null)
|
||||||
{
|
{
|
||||||
var argumentDisplayText = argument is CommandParameterSchema
|
|
||||||
? $"parameter <{argument.DisplayName}>"
|
|
||||||
: $"option '{argument.DisplayName}'";
|
|
||||||
|
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Can't convert value '{value ?? "<null>"}' to type '{type.FullName}' for {argumentDisplayText}.
|
Can't convert value ""{value ?? "<null>"}"" to type '{type.Name}' for parameter {parameterSchema.GetUserFacingDisplayString()}.
|
||||||
{innerException?.Message ?? "This type is not supported."}";
|
{innerException?.Message ?? "This type is not supported."}";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), innerException, showHelp: true);
|
return new CliFxException(message.Trim(), innerException, showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static CliFxException CannotConvertToType(
|
||||||
|
CommandOptionSchema optionSchema,
|
||||||
|
string? value,
|
||||||
|
Type type,
|
||||||
|
Exception? innerException = null)
|
||||||
|
{
|
||||||
|
var message = $@"
|
||||||
|
Can't convert value ""{value ?? "<null>"}"" to type '{type.Name}' for option {optionSchema.GetUserFacingDisplayString()}.
|
||||||
|
{innerException?.Message ?? "This type is not supported."}";
|
||||||
|
|
||||||
|
return new CliFxException(message.Trim(), innerException, showHelp: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static CliFxException CannotConvertToType(
|
||||||
|
CommandArgumentSchema argumentSchema,
|
||||||
|
string? value,
|
||||||
|
Type type,
|
||||||
|
Exception? innerException = null) => argumentSchema switch
|
||||||
|
{
|
||||||
|
CommandParameterSchema parameterSchema => CannotConvertToType(parameterSchema, value, type, innerException),
|
||||||
|
CommandOptionSchema optionSchema => CannotConvertToType(optionSchema, value, type, innerException),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(argumentSchema))
|
||||||
|
};
|
||||||
|
|
||||||
internal static CliFxException CannotConvertNonScalar(
|
internal static CliFxException CannotConvertNonScalar(
|
||||||
CommandArgumentSchema argument,
|
CommandParameterSchema parameterSchema,
|
||||||
IReadOnlyList<string> values,
|
IReadOnlyList<string> values,
|
||||||
Type type)
|
Type type)
|
||||||
{
|
{
|
||||||
var argumentDisplayText = argument is CommandParameterSchema
|
|
||||||
? $"parameter <{argument.DisplayName}>"
|
|
||||||
: $"option '{argument.DisplayName}'";
|
|
||||||
|
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Can't convert provided values to type '{type.FullName}' for {argumentDisplayText}:
|
Can't convert provided values to type '{type.Name}' for parameter {parameterSchema.GetUserFacingDisplayString()}:
|
||||||
{string.Join(", ", values.Select(v => $"'{v}'"))}
|
{values.Select(v => v.Quote()).JoinToString(" ")}
|
||||||
|
|
||||||
Target type is not assignable from array and doesn't have a public constructor that takes an array.";
|
Target type is not assignable from array and doesn't have a public constructor that takes an array.";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException ParameterNotSet(CommandParameterSchema parameter)
|
internal static CliFxException CannotConvertNonScalar(
|
||||||
|
CommandOptionSchema optionSchema,
|
||||||
|
IReadOnlyList<string> values,
|
||||||
|
Type type)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Missing value for parameter <{parameter.DisplayName}>.";
|
Can't convert provided values to type '{type.Name}' for option {optionSchema.GetUserFacingDisplayString()}:
|
||||||
|
{values.Select(v => v.Quote()).JoinToString(" ")}
|
||||||
|
|
||||||
|
Target type is not assignable from array and doesn't have a public constructor that takes an array.";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException RequiredOptionsNotSet(IReadOnlyList<CommandOptionSchema> options)
|
internal static CliFxException CannotConvertNonScalar(
|
||||||
|
CommandArgumentSchema argumentSchema,
|
||||||
|
IReadOnlyList<string> values,
|
||||||
|
Type type) => argumentSchema switch
|
||||||
|
{
|
||||||
|
CommandParameterSchema parameterSchema => CannotConvertNonScalar(parameterSchema, values, type),
|
||||||
|
CommandOptionSchema optionSchema => CannotConvertNonScalar(optionSchema, values, type),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(argumentSchema))
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static CliFxException ParameterNotSet(CommandParameterSchema parameterSchema)
|
||||||
|
{
|
||||||
|
var message = $@"
|
||||||
|
Missing value for parameter {parameterSchema.GetUserFacingDisplayString()}.";
|
||||||
|
|
||||||
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static CliFxException RequiredOptionsNotSet(IReadOnlyList<CommandOptionSchema> optionSchemas)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Missing values for one or more required options:
|
Missing values for one or more required options:
|
||||||
{string.Join(Environment.NewLine, options.Select(o => o.DisplayName))}";
|
{optionSchemas.Select(o => o.GetUserFacingDisplayString()).JoinToString(Environment.NewLine)}";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException UnrecognizedParametersProvided(IReadOnlyList<CommandUnboundArgumentInput> inputs)
|
internal static CliFxException UnrecognizedParametersProvided(IReadOnlyList<CommandUnboundArgumentInput> argumentInputs)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Unrecognized parameters provided:
|
Unrecognized parameters provided:
|
||||||
{string.Join(Environment.NewLine, inputs.Select(i => $"<{i.Value}>"))}";
|
{argumentInputs.Select(a => a.Value.Quote()).JoinToString(" ")}";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static CliFxException UnrecognizedOptionsProvided(IReadOnlyList<CommandOptionInput> inputs)
|
internal static CliFxException UnrecognizedOptionsProvided(IReadOnlyList<CommandOptionInput> optionInputs)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
Unrecognized options provided:
|
Unrecognized options provided:
|
||||||
{string.Join(Environment.NewLine, inputs.Select(i => i.DisplayAlias))}";
|
{optionInputs.Select(o => o.RawAlias).JoinToString(Environment.NewLine)}";
|
||||||
|
|
||||||
return new CliFxException(message.Trim(), showHelp: true);
|
return new CliFxException(message.Trim(), showHelp: true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,15 @@ namespace CliFx.Exceptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandException(string? message, Exception? innerException,
|
public CommandException(string? message, Exception? innerException,
|
||||||
int exitCode = DefaultExitCode, bool showHelp = false)
|
int exitCode = DefaultExitCode, bool showHelp = false)
|
||||||
: base(message, innerException, exitCode, showHelp)
|
: base(message, innerException, exitCode, showHelp)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandException"/>.
|
/// Initializes an instance of <see cref="CommandException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandException(string? message, int exitCode = DefaultExitCode, bool showHelp = false)
|
public CommandException(string? message, int exitCode = DefaultExitCode, bool showHelp = false)
|
||||||
: this(message, null, exitCode, showHelp)
|
: this(message, null, exitCode, showHelp)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace CliFx
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the command using the specified implementation of <see cref="IConsole"/>.
|
/// Executes the command using the specified implementation of <see cref="IConsole"/>.
|
||||||
/// This is the method that's called when the command is invoked by a user through command line interface.
|
/// This is the method that's called when the command is invoked by a user through command line.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>If the execution of the command is not asynchronous, simply end the method with <code>return default;</code></remarks>
|
/// <remarks>If the execution of the command is not asynchronous, simply end the method with <code>return default;</code></remarks>
|
||||||
ValueTask ExecuteAsync(IConsole console);
|
ValueTask ExecuteAsync(IConsole console);
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
namespace CliFx
|
namespace CliFx
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstraction for a service can initialize objects at runtime.
|
/// Abstraction for a service that can initialize objects at runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITypeActivator
|
public interface ITypeActivator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of specified type.
|
/// Creates an instance of the specified type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
object CreateInstance(Type type);
|
object CreateInstance(Type type);
|
||||||
}
|
}
|
||||||
|
|||||||
13
CliFx/Internal/ProcessEx.cs
Normal file
13
CliFx/Internal/ProcessEx.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace CliFx.Internal
|
||||||
|
{
|
||||||
|
internal static class ProcessEx
|
||||||
|
{
|
||||||
|
public static int GetCurrentProcessId()
|
||||||
|
{
|
||||||
|
using var process = Process.GetCurrentProcess();
|
||||||
|
return process.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace CliFx.Internal
|
namespace CliFx.Internal
|
||||||
@@ -10,14 +10,17 @@ namespace CliFx.Internal
|
|||||||
|
|
||||||
public static string AsString(this char c) => c.Repeat(1);
|
public static string AsString(this char c) => c.Repeat(1);
|
||||||
|
|
||||||
|
public static string Quote(this string str) => $"\"{str}\"";
|
||||||
|
|
||||||
|
public static string JoinToString<T>(this IEnumerable<T> source, string separator) => string.Join(separator, source);
|
||||||
|
|
||||||
public static StringBuilder AppendIfNotEmpty(this StringBuilder builder, char value) =>
|
public static StringBuilder AppendIfNotEmpty(this StringBuilder builder, char value) =>
|
||||||
builder.Length > 0 ? builder.Append(value) : builder;
|
builder.Length > 0 ? builder.Append(value) : builder;
|
||||||
|
|
||||||
public static bool IsEmptyOrWhiteSpace(this string s) => s is object && string.IsNullOrWhiteSpace(s);
|
public static string ToFormattableString(this object obj,
|
||||||
|
IFormatProvider? formatProvider = null, string? format = null) =>
|
||||||
public static string WrapWithQuotesIfEmptyOrWhiteSpace(this string s) =>
|
obj is IFormattable formattable
|
||||||
s.IsEmptyOrWhiteSpace() ? $"\"{s}\"" : s;
|
? formattable.ToString(format, formatProvider)
|
||||||
|
: obj.ToString();
|
||||||
public static string ToCulturedString(this object obj, CultureInfo culture) => Convert.ToString(obj, culture);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,17 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace CliFx.Internal
|
namespace CliFx.Internal
|
||||||
{
|
{
|
||||||
internal static class TypeExtensions
|
internal static class TypeExtensions
|
||||||
{
|
{
|
||||||
|
public static object? GetDefaultValue(this Type type) =>
|
||||||
|
type.IsValueType
|
||||||
|
? Activator.CreateInstance(type)
|
||||||
|
: null;
|
||||||
|
|
||||||
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
|
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
|
||||||
|
|
||||||
public static Type? GetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type);
|
public static Type? GetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type);
|
||||||
@@ -22,13 +28,30 @@ namespace CliFx.Internal
|
|||||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||||
return type.GetGenericArguments().FirstOrDefault();
|
return type.GetGenericArguments().FirstOrDefault();
|
||||||
|
|
||||||
return type.GetInterfaces()
|
return type
|
||||||
|
.GetInterfaces()
|
||||||
.Select(GetEnumerableUnderlyingType)
|
.Select(GetEnumerableUnderlyingType)
|
||||||
.Where(t => t != null)
|
.Where(t => t != null)
|
||||||
.OrderByDescending(t => t != typeof(object)) // prioritize more specific types
|
.OrderByDescending(t => t != typeof(object)) // prioritize more specific types
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MethodInfo GetToStringMethod(this Type type) => type.GetMethod(nameof(ToString), Type.EmptyTypes);
|
||||||
|
|
||||||
|
public static bool IsToStringOverriden(this Type type) => type.GetToStringMethod() != typeof(object).GetToStringMethod();
|
||||||
|
|
||||||
|
public static MethodInfo GetStaticParseMethod(this Type type, bool withFormatProvider = false)
|
||||||
|
{
|
||||||
|
var argumentTypes = withFormatProvider
|
||||||
|
? new[] {typeof(string), typeof(IFormatProvider)}
|
||||||
|
: new[] {typeof(string)};
|
||||||
|
|
||||||
|
return type.GetMethod("Parse",
|
||||||
|
BindingFlags.Public | BindingFlags.Static,
|
||||||
|
null, argumentTypes, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static Array ToNonGenericArray<T>(this IEnumerable<T> source, Type elementType)
|
public static Array ToNonGenericArray<T>(this IEnumerable<T> source, Type elementType)
|
||||||
{
|
{
|
||||||
var sourceAsCollection = source as ICollection ?? source.ToArray();
|
var sourceAsCollection = source as ICollection ?? source.ToArray();
|
||||||
@@ -38,7 +61,5 @@ namespace CliFx.Internal
|
|||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool OverridesToStringMethod(this object obj) => obj?.ToString() != obj?.GetType().ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user