/// <summary> /// Runs the application with the specified command line arguments and environment variables. /// Returns an exit code which indicates whether the application completed successfully. /// </summary> /// <remarks> /// When running WITHOUT debugger (i.e. in production), this method swallows all exceptions and /// reports them to the console. /// </remarks> public async ValueTask <int> RunAsync( IReadOnlyList <string> commandLineArguments, IReadOnlyDictionary <string, string> environmentVariables) { try { // Console colors may have already been overriden by the parent process, // so we need to reset it to make sure that everything we write looks properly. _console.ResetColor(); var applicationSchema = ApplicationSchema.Resolve(Configuration.CommandTypes); var commandInput = CommandInput.Parse( commandLineArguments, environmentVariables, applicationSchema.GetCommandNames() ); return(await RunAsync(applicationSchema, commandInput)); } // To prevent the app from showing the annoying troubleshooting dialog on Windows, // we handle all exceptions ourselves and print them to the console. // // We only want to do that if the app is running in production, which we infer // based on whether a debugger is attached to the process. // // When not running in production, we want the IDE to show exceptions to the // developer, so we don't swallow them in that case. catch (Exception ex) when(!Debugger.IsAttached) { _console.Error.WriteException(ex); return(1); } }
public void Positive_ReadMoreNamedParameters() { var keyword = "keyword"; var parameter1Name = "name"; var parameter1Value = "value"; var parameter2Name = "name"; var parameter2Value = "value"; var parameter3Name = "name"; var parameter3Value = "value"; var line = $"{keyword} -{parameter1Name}:{parameter1Value} -{parameter2Name}:{parameter2Value} -{parameter3Name}:{parameter3Value} "; var result = CommandInput.Parse(line); Assert.IsNotNull(result); Assert.AreEqual(keyword, result.Keyword); Assert.IsNotNull(result.Parameters); Assert.AreEqual(3, result.Parameters.Count); Assert.IsTrue(result.Parameters[0].IsNamed); Assert.AreEqual(parameter1Name, result.Parameters[0].Name); Assert.AreEqual(parameter1Value, result.Parameters[0].Value); Assert.IsTrue(result.Parameters[1].IsNamed); Assert.AreEqual(parameter2Name, result.Parameters[1].Name); Assert.AreEqual(parameter2Value, result.Parameters[1].Value); Assert.IsTrue(result.Parameters[2].IsNamed); Assert.AreEqual(parameter3Name, result.Parameters[2].Name); Assert.AreEqual(parameter3Value, result.Parameters[2].Value); }
internal void Directive_can_be_enabled_by_specifying_its_name_in_square_brackets(IReadOnlyList <string> arguments, CommandInput expectedInput) { // Arrange var commandNames = Array.Empty <string>(); // Act var input = CommandInput.Parse(arguments, commandNames); // Assert input.Should().BeEquivalentTo(expectedInput); }
internal void Command_name_is_matched_from_arguments_that_come_before_parameters( IReadOnlyList <string> commandNames, IReadOnlyList <string> arguments, CommandInput expectedInput) { // Act var input = CommandInput.Parse(arguments, commandNames); // Assert input.Should().BeEquivalentTo(expectedInput); }
internal void Parameter_can_be_set_by_specifying_the_value_directly(IReadOnlyList <string> arguments, CommandInput expectedInput) { // Arrange var commandNames = Array.Empty <string>(); // Act var input = CommandInput.Parse(arguments, commandNames); // Assert input.Should().BeEquivalentTo(expectedInput); }
internal void Option_can_be_set_by_specifying_its_short_name_after_a_single_dash(IReadOnlyList <string> arguments, CommandInput expectedInput) { // Arrange var commandNames = Array.Empty <string>(); // Act var input = CommandInput.Parse(arguments, commandNames); // Assert input.Should().BeEquivalentTo(expectedInput); }
public void Positive_ReadOnlyKeyword() { var keyword = "keyword"; var result = CommandInput.Parse(keyword); Assert.IsNotNull(result); Assert.AreEqual(keyword, result.Keyword); Assert.IsNotNull(result.Parameters); Assert.AreEqual(0, result.Parameters.Count); }
public void Positive_ReadEmpty() { var line = ""; var result = CommandInput.Parse(line); Assert.IsNotNull(result); Assert.AreEqual(string.Empty, result.Keyword); Assert.IsNotNull(result.Parameters); Assert.AreEqual(0, result.Parameters.Count); }
public static bool TestLine <T>(TypedSignature <T> sig, string line) where T : class, new() { var ci = CommandInput.Parse(line); var can = sig.CanRun(ci); var run = false; if (can) { run = sig.Run(ci, new List <CommandError>()); } return(can && run); }
public void Input_is_empty_if_no_arguments_are_provided() { // Arrange var arguments = Array.Empty <string>(); var commandNames = Array.Empty <string>(); // Act var input = CommandInput.Parse(arguments, commandNames); // Assert input.Should().BeEquivalentTo(CommandInput.Empty); }
public void Positive_ReadSingleIndexParameter() { var keyword = "keyword"; var parameterValue = "value"; var line = $"{keyword} {parameterValue}"; var result = CommandInput.Parse(line); Assert.IsNotNull(result); Assert.AreEqual(keyword, result.Keyword); Assert.IsNotNull(result.Parameters); Assert.AreEqual(1, result.Parameters.Count); Assert.IsFalse(result.Parameters[0].IsNamed); Assert.AreEqual(parameterValue, result.Parameters[0].Value); }
public void Positive_ReadSingleSwitchParameters() { var keyword = "keyword"; var parameter1Name = "name"; var line = $"{keyword} -{parameter1Name}"; var result = CommandInput.Parse(line); Assert.IsNotNull(result); Assert.AreEqual(keyword, result.Keyword); Assert.IsNotNull(result.Parameters); Assert.AreEqual(1, result.Parameters.Count); Assert.IsTrue(result.Parameters[0].IsNamed); Assert.AreEqual(null, result.Parameters[0].Value); }
/// <summary> /// Runs the application with specified command line arguments and environment variables, and returns the exit code. /// </summary> /// <remarks> /// If a <see cref="CommandException"/> is thrown during command execution, it will be handled and routed to the console. /// Additionally, if the debugger is not attached (i.e. the app is running in production), all other exceptions thrown within /// this method will be handled and routed to the console as well. /// </remarks> public async ValueTask <int> RunAsync( IReadOnlyList <string> commandLineArguments, IReadOnlyDictionary <string, string> environmentVariables) { try { var root = RootSchema.Resolve(_configuration.CommandTypes); var input = CommandInput.Parse(commandLineArguments, root.GetCommandNames()); // Debug mode if (_configuration.IsDebugModeAllowed && input.IsDebugDirectiveSpecified) { await LaunchAndWaitForDebuggerAsync(); } // Preview mode if (_configuration.IsPreviewModeAllowed && input.IsPreviewDirectiveSpecified) { WriteCommandLineInput(input); return(ExitCode.Success); } // Try to get the command matching the input or fallback to default var command = root.TryFindCommand(input.CommandName) ?? root.TryFindDefaultCommand() ?? StubDefaultCommand.Schema; // Version option if (command.IsVersionOptionAvailable && input.IsVersionOptionSpecified) { _console.Output.WriteLine(_metadata.VersionText); return(ExitCode.Success); } // Get command instance (also used in help text) var instance = GetCommandInstance(command); // To avoid instantiating the command twice, we need to get default values // before the arguments are bound to the properties var defaultValues = command.GetArgumentValues(instance); // Help option if (command.IsHelpOptionAvailable && input.IsHelpOptionSpecified || command == StubDefaultCommand.Schema && !input.Parameters.Any() && !input.Options.Any()) { _helpTextWriter.Write(root, command, defaultValues); return(ExitCode.Success); } // Bind arguments try { command.Bind(instance, input, environmentVariables); } // This may throw exceptions which are useful only to the end-user catch (CliFxException ex) { WriteError(ex.ToString()); _helpTextWriter.Write(root, command, defaultValues); return(ExitCode.FromException(ex)); } // Execute the command try { await instance.ExecuteAsync(_console); return(ExitCode.Success); } // Swallow command exceptions and route them to the console catch (CommandException ex) { WriteError(ex.ToString()); if (ex.ShowHelp) { _helpTextWriter.Write(root, command, defaultValues); } return(ex.ExitCode); } } // To prevent the app from showing the annoying Windows troubleshooting dialog, // we handle all exceptions and route them to the console nicely. // However, we don't want to swallow unhandled exceptions when the debugger is attached, // because we still want the IDE to show them to the developer. catch (Exception ex) when(!Debugger.IsAttached) { WriteError(ex.ToString()); return(ExitCode.FromException(ex)); } }