diff --git a/CliFx.Tests/Services/CommandInitializerTests.cs b/CliFx.Tests/Services/CommandInitializerTests.cs index c4a5d26..30923d9 100644 --- a/CliFx.Tests/Services/CommandInitializerTests.cs +++ b/CliFx.Tests/Services/CommandInitializerTests.cs @@ -130,7 +130,7 @@ namespace CliFx.Tests.Services // Act & Assert initializer.Invoking(i => i.InitializeCommand(command, commandSchema, commandInput)) - .Should().ThrowExactly(); + .Should().ThrowExactly(); } } } \ No newline at end of file diff --git a/CliFx.Tests/Services/CommandOptionInputConverterTests.cs b/CliFx.Tests/Services/CommandOptionInputConverterTests.cs index 76b6464..f42ec7d 100644 --- a/CliFx.Tests/Services/CommandOptionInputConverterTests.cs +++ b/CliFx.Tests/Services/CommandOptionInputConverterTests.cs @@ -301,7 +301,7 @@ namespace CliFx.Tests.Services // Act & Assert converter.Invoking(c => c.ConvertOptionInput(optionInput, targetType)) - .Should().ThrowExactly(); + .Should().ThrowExactly(); } } } \ No newline at end of file diff --git a/CliFx.Tests/Services/CommandSchemaResolverTests.cs b/CliFx.Tests/Services/CommandSchemaResolverTests.cs index 38a5bec..eab7d6c 100644 --- a/CliFx.Tests/Services/CommandSchemaResolverTests.cs +++ b/CliFx.Tests/Services/CommandSchemaResolverTests.cs @@ -103,7 +103,7 @@ namespace CliFx.Tests.Services // Act & Assert resolver.Invoking(r => r.GetCommandSchemas(commandTypes)) - .Should().ThrowExactly(); + .Should().ThrowExactly(); } } } \ No newline at end of file diff --git a/CliFx/CliApplication.cs b/CliFx/CliApplication.cs index 6729762..91cf4b1 100644 --- a/CliFx/CliApplication.cs +++ b/CliFx/CliApplication.cs @@ -204,19 +204,27 @@ namespace CliFx catch (Exception ex) { // We want to catch exceptions in order to print errors and return correct exit codes. - // Also, by doing this we get rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions. + // Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions. - // In case we catch a CliFx-specific exception, we want to just show the error message, not the stack trace. - // Stack trace isn't very useful to the user if the exception is not really coming from their code. + // Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException + if (!ex.Message.IsNullOrWhiteSpace() && (ex is CliFxException || ex is CommandException)) + { + _console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.Message)); + } + else + { + _console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex)); + } - // CommandException is the same, but it also lets users specify exit code so we want to return that instead of default. - - var message = ex is CliFxException && !ex.Message.IsNullOrWhiteSpace() ? ex.Message : ex.ToString(); - var exitCode = ex is CommandException commandEx ? commandEx.ExitCode : ex.HResult; - - _console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(message)); - - return exitCode; + // Return exit code if it was specified via CommandException + if (ex is CommandException commandException) + { + return commandException.ExitCode; + } + else + { + return ex.HResult; + } } } } diff --git a/CliFx/Exceptions/CliFxException.cs b/CliFx/Exceptions/CliFxException.cs index 82d4bc6..5bbba9b 100644 --- a/CliFx/Exceptions/CliFxException.cs +++ b/CliFx/Exceptions/CliFxException.cs @@ -5,12 +5,12 @@ namespace CliFx.Exceptions /// /// Domain exception thrown within CliFx. /// - public abstract class CliFxException : Exception + public class CliFxException : Exception { /// /// Initializes an instance of . /// - protected CliFxException(string message) + public CliFxException(string message) : base(message) { } @@ -18,7 +18,7 @@ namespace CliFx.Exceptions /// /// Initializes an instance of . /// - protected CliFxException(string message, Exception innerException) + public CliFxException(string message, Exception innerException) : base(message, innerException) { } diff --git a/CliFx/Exceptions/CommandException.cs b/CliFx/Exceptions/CommandException.cs index c7288d3..4b44994 100644 --- a/CliFx/Exceptions/CommandException.cs +++ b/CliFx/Exceptions/CommandException.cs @@ -8,7 +8,7 @@ namespace CliFx.Exceptions /// Use this exception if you want to report an error that occured during execution of a command. /// This exception also allows specifying exit code which will be returned to the calling process. /// - public class CommandException : CliFxException + public class CommandException : Exception { private const int DefaultExitCode = -100; diff --git a/CliFx/Exceptions/InvalidCommandOptionInputException.cs b/CliFx/Exceptions/InvalidCommandOptionInputException.cs deleted file mode 100644 index 02320be..0000000 --- a/CliFx/Exceptions/InvalidCommandOptionInputException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace CliFx.Exceptions -{ - /// - /// Thrown when a command option can't be converted to target type specified in its schema. - /// - public class InvalidCommandOptionInputException : CliFxException - { - /// - /// Initializes an instance of . - /// - public InvalidCommandOptionInputException(string message) - : base(message) - { - } - - /// - /// Initializes an instance of . - /// - public InvalidCommandOptionInputException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} \ No newline at end of file diff --git a/CliFx/Exceptions/MissingCommandOptionInputException.cs b/CliFx/Exceptions/MissingCommandOptionInputException.cs deleted file mode 100644 index c669914..0000000 --- a/CliFx/Exceptions/MissingCommandOptionInputException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace CliFx.Exceptions -{ - /// - /// Thrown when a required command option was not set. - /// - public class MissingCommandOptionInputException : CliFxException - { - /// - /// Initializes an instance of . - /// - public MissingCommandOptionInputException(string message) - : base(message) - { - } - - /// - /// Initializes an instance of . - /// - public MissingCommandOptionInputException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} \ No newline at end of file diff --git a/CliFx/Exceptions/SchemaValidationException.cs b/CliFx/Exceptions/SchemaValidationException.cs deleted file mode 100644 index 3209163..0000000 --- a/CliFx/Exceptions/SchemaValidationException.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace CliFx.Exceptions -{ - /// - /// Thrown when a command schema fails validation. - /// - public class SchemaValidationException : CliFxException - { - /// - /// Initializes an instance of . - /// - public SchemaValidationException(string message) - : base(message) - { - } - - /// - /// Initializes an instance of . - /// - public SchemaValidationException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} \ No newline at end of file diff --git a/CliFx/Models/CommandInput.cs b/CliFx/Models/CommandInput.cs index 85eda82..9945ebe 100644 --- a/CliFx/Models/CommandInput.cs +++ b/CliFx/Models/CommandInput.cs @@ -67,6 +67,12 @@ namespace CliFx.Models if (!CommandName.IsNullOrWhiteSpace()) buffer.Append(CommandName); + foreach (var directive in Directives) + { + buffer.AppendIfNotEmpty(' '); + buffer.Append(directive); + } + foreach (var option in Options) { buffer.AppendIfNotEmpty(' '); diff --git a/CliFx/Services/CommandInitializer.cs b/CliFx/Services/CommandInitializer.cs index 0145f96..7866604 100644 --- a/CliFx/Services/CommandInitializer.cs +++ b/CliFx/Services/CommandInitializer.cs @@ -61,7 +61,7 @@ namespace CliFx.Services if (unsetRequiredOptions.Any()) { var unsetRequiredOptionNames = unsetRequiredOptions.Select(o => o.GetAliases().FirstOrDefault()).JoinToString(", "); - throw new MissingCommandOptionInputException($"One or more required options were not set: {unsetRequiredOptionNames}."); + throw new CliFxException($"One or more required options were not set: {unsetRequiredOptionNames}."); } } } diff --git a/CliFx/Services/CommandOptionInputConverter.cs b/CliFx/Services/CommandOptionInputConverter.cs index b8ea819..467c59a 100644 --- a/CliFx/Services/CommandOptionInputConverter.cs +++ b/CliFx/Services/CommandOptionInputConverter.cs @@ -127,11 +127,11 @@ namespace CliFx.Services if (parseMethod != null) return parseMethod.Invoke(null, new object[] {value}); - throw new InvalidCommandOptionInputException($"Can't convert value [{value}] to type [{targetType}]."); + throw new CliFxException($"Can't convert value [{value}] to type [{targetType}]."); } catch (Exception ex) { - throw new InvalidCommandOptionInputException($"Can't convert value [{value}] to type [{targetType}].", ex); + throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].", ex); } } @@ -166,7 +166,7 @@ namespace CliFx.Services if (arrayConstructor != null) return arrayConstructor.Invoke(new object[] {convertedValues}); - throw new InvalidCommandOptionInputException( + throw new CliFxException( $"Can't convert sequence of values [{optionInput.Values.JoinToString(", ")}] to type [{targetType}]."); } } diff --git a/CliFx/Services/CommandSchemaResolver.cs b/CliFx/Services/CommandSchemaResolver.cs index fbb1b34..fe61790 100644 --- a/CliFx/Services/CommandSchemaResolver.cs +++ b/CliFx/Services/CommandSchemaResolver.cs @@ -40,7 +40,7 @@ namespace CliFx.Services if (existingOptionWithSameName != null) { - throw new SchemaValidationException( + throw new CliFxException( $"Command type [{commandType}] has options defined with the same name: " + $"[{existingOptionWithSameName.Property}] and [{optionSchema.Property}]."); } @@ -52,7 +52,7 @@ namespace CliFx.Services if (existingOptionWithSameShortName != null) { - throw new SchemaValidationException( + throw new CliFxException( $"Command type [{commandType}] has options defined with the same short name: " + $"[{existingOptionWithSameShortName.Property}] and [{optionSchema.Property}]."); } @@ -72,7 +72,7 @@ namespace CliFx.Services // Make sure there's at least one command defined if (!commandTypes.Any()) { - throw new SchemaValidationException("There are no commands defined."); + throw new CliFxException("There are no commands defined."); } var result = new List(); @@ -82,8 +82,7 @@ namespace CliFx.Services // Make sure command type implements ICommand. if (!commandType.Implements(typeof(ICommand))) { - throw new SchemaValidationException( - $"Command type [{commandType}] must implement {typeof(ICommand)}."); + throw new CliFxException($"Command type [{commandType}] must implement {typeof(ICommand)}."); } // Get attribute @@ -92,8 +91,7 @@ namespace CliFx.Services // Make sure attribute is set if (attribute == null) { - throw new SchemaValidationException( - $"Command type [{commandType}] must be annotated with [{typeof(CommandAttribute)}]."); + throw new CliFxException($"Command type [{commandType}] must be annotated with [{typeof(CommandAttribute)}]."); } // Get option schemas @@ -111,7 +109,7 @@ namespace CliFx.Services if (existingCommandWithSameName != null) { - throw new SchemaValidationException( + throw new CliFxException( $"Command type [{existingCommandWithSameName.Type}] has the same name as another command type [{commandType}]."); }