diff --git a/CliFx.Tests/ErrorReportingSpecs.Commands.cs b/CliFx.Tests/ErrorReportingSpecs.Commands.cs index 455dba7..28475b7 100644 --- a/CliFx.Tests/ErrorReportingSpecs.Commands.cs +++ b/CliFx.Tests/ErrorReportingSpecs.Commands.cs @@ -20,7 +20,7 @@ namespace CliFx.Tests private class CommandExceptionCommand : ICommand { [CommandOption("code", 'c')] - public int ExitCode { get; set; } = 1337; + public int ExitCode { get; set; } = 133; [CommandOption("msg", 'm')] public string? Message { get; set; } diff --git a/CliFx/CliApplication.cs b/CliFx/CliApplication.cs index d8c84b7..1e46cfd 100644 --- a/CliFx/CliApplication.cs +++ b/CliFx/CliApplication.cs @@ -263,7 +263,7 @@ namespace CliFx public static int FromException(Exception ex) => ex is CommandException cmdEx ? cmdEx.ExitCode - : ex.HResult; + : 1; } [Command] diff --git a/CliFx/Exceptions/CommandException.cs b/CliFx/Exceptions/CommandException.cs index 844f2b7..a748888 100644 --- a/CliFx/Exceptions/CommandException.cs +++ b/CliFx/Exceptions/CommandException.cs @@ -9,12 +9,14 @@ namespace CliFx.Exceptions /// public class CommandException : Exception { - private const int DefaultExitCode = -1; + private const int DefaultExitCode = 1; private readonly bool _isMessageSet; /// /// Returns an exit code associated with this exception. + /// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255 + /// otherwise it may overflow and yield unexpected results. /// public int ExitCode { get; } @@ -26,6 +28,14 @@ namespace CliFx.Exceptions /// /// Initializes an instance of . /// + /// The exception message. + /// The inner exception. + /// + /// The exit code associated with this exception. + /// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255 + /// otherwise it may overflow and yield unexpected results. + /// + /// Whether to show the help text after handling this exception. public CommandException(string? message, Exception? innerException, int exitCode = DefaultExitCode, bool showHelp = false) : base(message, innerException) { @@ -39,6 +49,13 @@ namespace CliFx.Exceptions /// /// Initializes an instance of . /// + /// The exception message. + /// + /// The exit code associated with this exception. + /// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255 + /// otherwise it may overflow and yield unexpected results. + /// + /// Whether to show the help text after handling this exception. public CommandException(string? message, int exitCode = DefaultExitCode, bool showHelp = false) : this(message, null, exitCode, showHelp) { @@ -47,6 +64,12 @@ namespace CliFx.Exceptions /// /// Initializes an instance of . /// + /// + /// The exit code associated with this exception. + /// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255 + /// otherwise it may overflow and yield unexpected results. + /// + /// Whether to show the help text after handling this exception. public CommandException(int exitCode = DefaultExitCode, bool showHelp = false) : this(null, exitCode, showHelp) { diff --git a/Readme.md b/Readme.md index edd70ed..0bbeb55 100644 --- a/Readme.md +++ b/Readme.md @@ -396,10 +396,12 @@ You can run `myapp.exe cmd1 [command] --help` to show help on a specific command You may have noticed that commands in CliFx don't return exit codes. This is by design as exit codes are considered a higher-level concern and thus handled by `CliApplication`, not by individual commands. -Commands can report execution failure simply by throwing exceptions just like any other C# code. When an exception is thrown, `CliApplication` will catch it, print the error, and return an appropriate exit code to the calling process. +Commands can report execution failure simply by throwing exceptions just like any other C# code. When an exception is thrown, `CliApplication` will catch it, print the error, and return an exit code `1` to the calling process. If you want to communicate a specific error through exit code, you can instead throw an instance of `CommandException` which takes an exit code as a parameter. When a command throws an exception of type `CommandException`, it is assumed that this was a result of a handled error and, as such, only the exception message will be printed to the error stream. If a command throws an exception of any other type, the full stack trace will be printed as well. +> Please note that on Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between `1` and `255` otherwise it may overflow and yield unexpected results. + ```c# [Command] public class DivideCommand : ICommand @@ -414,8 +416,8 @@ public class DivideCommand : ICommand { if (Math.Abs(Divisor) < double.Epsilon) { - // This will print the error and set exit code to 1337 - throw new CommandException("Division by zero is not supported.", 1337); + // This will print the error and set exit code to 133 + throw new CommandException("Division by zero is not supported.", 133); } var result = Dividend / Divisor; @@ -434,7 +436,7 @@ Division by zero is not supported. > $LastExitCode -1337 +133 ``` You can also specify the `showHelp` parameter to instruct whether to show the help text for the current command after printing the error: