This commit is contained in:
Alexey Golub
2020-07-19 18:11:54 +03:00
parent 004f906148
commit 69c24c8dfc
18 changed files with 35 additions and 100 deletions

View File

@@ -1,12 +1,6 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Tests.Internal;
using CliWrap;
using CliWrap.Buffered;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
@@ -14,30 +8,6 @@ namespace CliFx.Tests
{ {
public partial class DirectivesSpecs public partial class DirectivesSpecs
{ {
[Fact]
public async Task Debug_directive_can_be_specified_to_have_the_application_wait_until_debugger_is_attached()
{
// We can't actually attach a debugger in tests, so instead just cancel execution after some time
// Arrange
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var stdOut = new StringBuilder();
var command = Cli.Wrap("dotnet")
.WithArguments(a => a
.Add(Dummy.Program.Location)
.Add("[debug]"))
.WithEnvironmentVariables(e => e
.Set("ENV_TARGET", "Mars")) | stdOut;
// Act
await command.ExecuteAsync(cts.Token).Task.IgnoreCancellation();
var stdOutData = stdOut.ToString();
// Assert
stdOutData.Should().Contain("Attach debugger to");
}
[Fact] [Fact]
public async Task Preview_directive_can_be_specified_to_print_provided_arguments_as_they_were_parsed() public async Task Preview_directive_can_be_specified_to_print_provided_arguments_as_they_were_parsed()
{ {

View File

@@ -1,6 +1,4 @@
using System; using System.IO;
using System.Globalization;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;

View File

@@ -17,30 +17,23 @@ namespace CliFx
/// Whether debug mode is allowed in this application. /// Whether debug mode is allowed in this application.
/// </summary> /// </summary>
public bool IsDebugModeAllowed { get; } public bool IsDebugModeAllowed { get; }
/// <summary> /// <summary>
/// Whether preview mode is allowed in this application. /// Whether preview mode is allowed in this application.
/// </summary> /// </summary>
public bool IsPreviewModeAllowed { get; } public bool IsPreviewModeAllowed { get; }
/// <summary>
/// Prompt debugger launch when application is in debug mode
/// </summary>
public bool PromptDebuggerLaunchInDebugMode { get; }
/// <summary> /// <summary>
/// Initializes an instance of <see cref="ApplicationConfiguration"/>. /// Initializes an instance of <see cref="ApplicationConfiguration"/>.
/// </summary> /// </summary>
public ApplicationConfiguration( public ApplicationConfiguration(
IReadOnlyList<Type> commandTypes, IReadOnlyList<Type> commandTypes,
bool isDebugModeAllowed, bool isDebugModeAllowed,
bool isPreviewModeAllowed, bool isPreviewModeAllowed)
bool promptDebuggerLaunchInDebugMode)
{ {
CommandTypes = commandTypes; CommandTypes = commandTypes;
IsDebugModeAllowed = isDebugModeAllowed; IsDebugModeAllowed = isDebugModeAllowed;
IsPreviewModeAllowed = isPreviewModeAllowed; IsPreviewModeAllowed = isPreviewModeAllowed;
PromptDebuggerLaunchInDebugMode = promptDebuggerLaunchInDebugMode;
} }
} }
} }

View File

@@ -41,13 +41,15 @@ namespace CliFx
private void WriteError(string message) => _console.WithForegroundColor(ConsoleColor.Red, () => private void WriteError(string message) => _console.WithForegroundColor(ConsoleColor.Red, () =>
_console.Error.WriteLine(message)); _console.Error.WriteLine(message));
private async ValueTask WaitForDebuggerAsync() private async ValueTask LaunchAndWaitForDebuggerAsync()
{ {
var processId = ProcessEx.GetCurrentProcessId(); var processId = ProcessEx.GetCurrentProcessId();
_console.WithForegroundColor(ConsoleColor.Green, () => _console.WithForegroundColor(ConsoleColor.Green, () =>
_console.Output.WriteLine($"Attach debugger to PID {processId} to continue.")); _console.Output.WriteLine($"Attach debugger to PID {processId} to continue."));
Debugger.Launch();
while (!Debugger.IsAttached) while (!Debugger.IsAttached)
await Task.Delay(100); await Task.Delay(100);
} }
@@ -125,19 +127,7 @@ namespace CliFx
// Debug mode // Debug mode
if (_configuration.IsDebugModeAllowed && input.IsDebugDirectiveSpecified) if (_configuration.IsDebugModeAllowed && input.IsDebugDirectiveSpecified)
{ {
if (_configuration.PromptDebuggerLaunchInDebugMode) await LaunchAndWaitForDebuggerAsync();
{
// Prompt debugger launcher dialog
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
}
else
{
// Ensure debugger is attached and continue
await WaitForDebuggerAsync();
}
} }
// Preview mode // Preview mode

View File

@@ -4,7 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using CliFx.Domain; using CliFx.Domain;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx namespace CliFx
{ {
@@ -16,7 +16,6 @@ namespace CliFx
private readonly HashSet<Type> _commandTypes = new HashSet<Type>(); private readonly HashSet<Type> _commandTypes = new HashSet<Type>();
private bool _isDebugModeAllowed = true; private bool _isDebugModeAllowed = true;
private bool _promptDebuggerLaunch = false;
private bool _isPreviewModeAllowed = true; private bool _isPreviewModeAllowed = true;
private string? _title; private string? _title;
private string? _executableName; private string? _executableName;
@@ -79,10 +78,9 @@ namespace CliFx
/// <summary> /// <summary>
/// Specifies whether debug mode (enabled with [debug] directive) is allowed in the application. /// Specifies whether debug mode (enabled with [debug] directive) is allowed in the application.
/// </summary> /// </summary>
public CliApplicationBuilder AllowDebugMode(bool isAllowed = true, bool promptDebuggerLaunch = false) public CliApplicationBuilder AllowDebugMode(bool isAllowed = true)
{ {
_isDebugModeAllowed = isAllowed; _isDebugModeAllowed = isAllowed;
_promptDebuggerLaunch = promptDebuggerLaunch;
return this; return this;
} }
@@ -168,7 +166,7 @@ namespace CliFx
_typeActivator ??= new DefaultTypeActivator(); _typeActivator ??= new DefaultTypeActivator();
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description); var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed, _promptDebuggerLaunch); var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
return new CliApplication(metadata, configuration, _console, _typeActivator); return new CliApplication(metadata, configuration, _console, _typeActivator);
} }

View File

@@ -4,7 +4,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx.Domain namespace CliFx.Domain
{ {

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx.Domain namespace CliFx.Domain
{ {

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx.Domain namespace CliFx.Domain
{ {

View File

@@ -6,7 +6,7 @@ using System.Reflection;
using System.Text; using System.Text;
using CliFx.Attributes; using CliFx.Attributes;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx.Domain namespace CliFx.Domain
{ {

View File

@@ -3,7 +3,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx.Domain namespace CliFx.Domain
{ {

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Internal; using CliFx.Internal.Extensions;
namespace CliFx.Domain namespace CliFx.Domain
{ {

View File

@@ -3,7 +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; using CliFx.Internal.Extensions;
namespace CliFx.Exceptions namespace CliFx.Exceptions
{ {

View File

@@ -14,9 +14,7 @@ namespace CliFx.Exceptions
private readonly bool _isMessageSet; private readonly bool _isMessageSet;
/// <summary> /// <summary>
/// Returns an exit code associated with this exception. /// Exit code returned by the application when this exception is handled.
/// 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.
/// </summary> /// </summary>
public int ExitCode { get; } public int ExitCode { get; }
@@ -28,14 +26,9 @@ namespace CliFx.Exceptions
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandException"/>. /// Initializes an instance of <see cref="CommandException"/>.
/// </summary> /// </summary>
/// <param name="message">The exception message.</param> /// <remarks>
/// <param name="innerException">The inner exception.</param> /// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use values between 1 and 255 to avoid overflow.
/// <param name="exitCode"> /// </remarks>
/// 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.
/// </param>
/// <param name="showHelp">Whether to show the help text after handling this exception.</param>
public CommandException(string? message, Exception? innerException, int exitCode = DefaultExitCode, bool showHelp = false) public CommandException(string? message, Exception? innerException, int exitCode = DefaultExitCode, bool showHelp = false)
: base(message, innerException) : base(message, innerException)
{ {
@@ -49,13 +42,9 @@ namespace CliFx.Exceptions
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandException"/>. /// Initializes an instance of <see cref="CommandException"/>.
/// </summary> /// </summary>
/// <param name="message">The exception message.</param> /// <remarks>
/// <param name="exitCode"> /// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use values between 1 and 255 to avoid overflow.
/// The exit code associated with this exception. /// </remarks>
/// 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.
/// </param>
/// <param name="showHelp">Whether to show the help text after handling this exception.</param>
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)
{ {
@@ -64,12 +53,9 @@ namespace CliFx.Exceptions
/// <summary> /// <summary>
/// Initializes an instance of <see cref="CommandException"/>. /// Initializes an instance of <see cref="CommandException"/>.
/// </summary> /// </summary>
/// <param name="exitCode"> /// <remarks>
/// 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 values between 1 and 255 to avoid overflow.
/// On Unix systems an exit code is 8-bit unsigned integer so it's strongly recommended to use exit codes between 1 and 255 /// </remarks>
/// otherwise it may overflow and yield unexpected results.
/// </param>
/// <param name="showHelp">Whether to show the help text after handling this exception.</param>
public CommandException(int exitCode = DefaultExitCode, bool showHelp = false) public CommandException(int exitCode = DefaultExitCode, bool showHelp = false)
: this(null, exitCode, showHelp) : this(null, exitCode, showHelp)
{ {

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace CliFx.Internal namespace CliFx.Internal.Extensions
{ {
internal static class CollectionExtensions internal static class CollectionExtensions
{ {

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace CliFx.Internal namespace CliFx.Internal.Extensions
{ {
internal static class StringExtensions internal static class StringExtensions
{ {

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace CliFx.Internal namespace CliFx.Internal.Extensions
{ {
internal static class TypeExtensions internal static class TypeExtensions
{ {

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace CliFx.Internal namespace CliFx.Internal.Extensions
{ {
internal static class VersionExtensions internal static class VersionExtensions
{ {

View File

@@ -396,11 +396,11 @@ 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. 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 exit code `1` 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 `1` as the exit code 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. 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. > Note: Unix systems rely on 8-bit unsigned integers for exit codes, so it's strongly recommended to use values between `1` and `255` to avoid potential overflow issues.
```c# ```c#
[Command] [Command]