/// <summary> /// Runs the application with specified command line arguments and environment variables, and returns the exit code. /// </summary> public async ValueTask <int> RunAsync( IReadOnlyList <string> commandLineArguments, IReadOnlyDictionary <string, string> environmentVariables) { try { var applicationSchema = ApplicationSchema.Resolve(_configuration.CommandTypes); var commandLineInput = CommandLineInput.Parse(commandLineArguments); return (await HandleDebugDirectiveAsync(commandLineInput) ?? HandlePreviewDirective(applicationSchema, commandLineInput) ?? HandleVersionOption(commandLineInput) ?? HandleHelpOption(applicationSchema, commandLineInput) ?? await HandleCommandExecutionAsync(applicationSchema, commandLineInput, environmentVariables)); } catch (Exception ex) { // We want to catch 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. // Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException var errorMessage = !string.IsNullOrWhiteSpace(ex.Message) && (ex is CliFxException || ex is CommandException) ? ex.Message : ex.ToString(); _console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(errorMessage)); return(ex is CommandException commandException ? commandException.ExitCode : ex.HResult); } }
/// <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 Resolve_Negative_Test(IReadOnlyList <Type> commandTypes) { // Act & Assert var ex = Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); Console.WriteLine(ex.Message); }
/// <summary> /// Runs the application with specified command line arguments and environment variables, and returns the exit code. /// </summary> public async ValueTask <int> RunAsync( IReadOnlyList <string> commandLineArguments, IReadOnlyDictionary <string, string> environmentVariables) { try { var applicationSchema = ApplicationSchema.Resolve(_configuration.CommandTypes); var commandLineInput = CommandLineInput.Parse(commandLineArguments); return (await HandleDebugDirectiveAsync(commandLineInput) ?? HandlePreviewDirective(applicationSchema, commandLineInput) ?? HandleVersionOption(commandLineInput) ?? HandleHelpOption(applicationSchema, commandLineInput) ?? await HandleCommandExecutionAsync(applicationSchema, commandLineInput, environmentVariables)); } catch (CliFxException cfe) { // We want to catch 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. var exitCode = HandleCliFxException(commandLineArguments, cfe); return(exitCode); } catch (Exception ex) { // For all other errors, we just write the entire thing to stderr. _console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.ToString())); return(ex.HResult); } }
public void Command_parameters_must_have_unique_names() { // Arrange var commandTypes = new[] { typeof(DuplicateParameterNameCommand) }; // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void Command_parameter_can_be_non_scalar_only_if_no_other_such_parameter_is_present() { // Arrange var commandTypes = new[] { typeof(MultipleNonScalarParametersCommand) }; // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void Command_options_must_have_unique_environment_variable_names() { // Arrange var commandTypes = new[] { typeof(DuplicateOptionEnvironmentVariableNamesCommand) }; // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void Command_parameter_can_be_non_scalar_only_if_it_is_the_last_in_order() { // Arrange var commandTypes = new[] { typeof(NonLastNonScalarParameterCommand) }; // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void At_least_one_command_must_be_defined_in_an_application() { // Arrange var commandTypes = Array.Empty <Type>(); // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void Commands_must_implement_the_corresponding_interface() { // Arrange var commandTypes = new[] { typeof(NonImplementedCommand) }; // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void Commands_must_be_annotated_by_an_attribute() { // Arrange var commandTypes = new[] { typeof(NonAnnotatedCommand) }; // Act & assert Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes)); }
public void Resolve_Test( IReadOnlyList <Type> commandTypes, IReadOnlyList <CommandSchema> expectedCommandSchemas) { // Act var applicationSchema = ApplicationSchema.Resolve(commandTypes); // Assert applicationSchema.Commands.Should().BeEquivalentTo(expectedCommandSchemas); }
private async ValueTask <int> HandleCommandExecutionAsync( ApplicationSchema applicationSchema, CommandLineInput commandLineInput, IReadOnlyDictionary <string, string> environmentVariables) { await applicationSchema .InitializeEntryPoint(commandLineInput, environmentVariables, _typeActivator) .ExecuteAsync(_console); return(0); }
public HelpContext( ApplicationMetadata applicationMetadata, ApplicationSchema applicationSchema, CommandSchema commandSchema, IReadOnlyDictionary <IMemberSchema, object?> commandDefaultValues) { ApplicationMetadata = applicationMetadata; ApplicationSchema = applicationSchema; CommandSchema = commandSchema; CommandDefaultValues = commandDefaultValues; }
public void Property_of_non_nullable_type_can_only_be_bound_if_the_argument_value_is_set() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.Int)) .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void Property_annotated_as_parameter_must_always_be_bound_to_some_value() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(ParametersCommand) }); var input = new CommandLineInputBuilder() .AddUnboundArgument("foo") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void Property_annotated_as_a_required_option_must_always_be_bound_to_some_value() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(RequiredOptionCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(RequiredOptionCommand.OptionA), "foo") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void Property_of_custom_type_that_implements_IEnumerable_can_only_be_bound_if_that_type_has_a_constructor_accepting_an_array() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(UnsupportedEnumerablePropertyTypeCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(UnsupportedEnumerablePropertyTypeCommand.Option), "foo", "bar") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void Property_must_have_a_type_supported_by_the_framework_in_order_to_be_bound() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(UnsupportedPropertyTypeCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(UnsupportedPropertyTypeCommand.Option), "foo") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void Property_must_have_a_type_that_implements_IEnumerable_in_order_to_be_bound_from_multiple_argument_values() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.Int), "1", "2", "3") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void All_provided_option_arguments_must_be_bound_to_corresponding_properties() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption("not-a-real-option", "boom") .AddOption("fake-option", "poof") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
private int?HandlePreviewDirective(ApplicationSchema applicationSchema, CommandLineInput commandLineInput) { var isPreviewMode = _configuration.IsPreviewModeAllowed && commandLineInput.IsPreviewDirectiveSpecified; if (!isPreviewMode) { return(null); } var commandSchema = applicationSchema.TryFindCommand(commandLineInput, out var argumentOffset); _console.Output.WriteLine("Parser preview:"); // Command name if (commandSchema != null && argumentOffset > 0) { _console.WithForegroundColor(ConsoleColor.Cyan, () => _console.Output.Write(commandSchema.Name)); _console.Output.Write(' '); } // Parameters foreach (var parameter in commandLineInput.Arguments.Skip(argumentOffset)) { _console.Output.Write('<'); _console.WithForegroundColor(ConsoleColor.White, () => _console.Output.Write(parameter)); _console.Output.Write('>'); _console.Output.Write(' '); } // Options foreach (var option in commandLineInput.Options) { _console.Output.Write('['); _console.WithForegroundColor(ConsoleColor.White, () => _console.Output.Write(option)); _console.Output.Write(']'); _console.Output.Write(' '); } _console.Output.WriteLine(); return(0); }
public void All_provided_parameter_arguments_must_be_bound_to_corresponding_properties() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(NoParameterCommand) }); var input = new CommandLineInputBuilder() .AddUnboundArgument("boom") .AddUnboundArgument("poof") .AddOption(nameof(NoParameterCommand.OptionA), "foo") .AddOption(nameof(NoParameterCommand.OptionB), "bar") .Build(); // Act & assert Assert.Throws <CliFxException>(() => schema.InitializeEntryPoint(input)); }
public void Property_of_type_nullable_TimeSpan_is_bound_by_parsing_the_argument_value_if_it_is_set() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.TimeSpanNullable), "00:14:59") .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { TimeSpanNullable = new TimeSpan(00, 14, 59) }); }
public void Property_of_a_nullable_enum_type_is_bound_as_null_if_the_argument_value_is_not_set() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.CustomEnumNullable)) .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { CustomEnumNullable = null }); }
public void Property_of_an_enum_array_type_is_bound_by_parsing_the_argument_values_as_either_names_or_ids() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.CustomEnumArray), "value1", "3") .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { CustomEnumArray = new[] { CustomEnum.Value1, CustomEnum.Value3 } }); }
public void Property_of_an_array_of_type_that_has_a_constructor_accepting_a_string_is_bound_by_invoking_the_constructor_with_the_argument_values() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.TestStringConstructableArray), "foo", "bar") .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { TestStringConstructableArray = new[] { new StringConstructable("foo"), new StringConstructable("bar") } }); }
public void Property_of_a_type_that_has_a_static_Parse_method_accepting_a_string_and_format_provider_is_bound_by_invoking_the_method() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.TestStringParseableWithFormatProvider), "foobar") .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { TestStringParseableWithFormatProvider = StringParseableWithFormatProvider.Parse("foobar", CultureInfo.InvariantCulture) }); }
public void Property_of_type_string_array_is_bound_directly_from_the_argument_values() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.StringArray), "foo", "bar") .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { StringArray = new[] { "foo", "bar" } }); }
public void Property_of_type_DateTimeOffset_is_bound_by_parsing_the_argument_value() { // Arrange var schema = ApplicationSchema.Resolve(new[] { typeof(AllSupportedTypesCommand) }); var input = new CommandLineInputBuilder() .AddOption(nameof(AllSupportedTypesCommand.DateTimeOffset), "28 Apr 1995") .Build(); // Act var command = schema.InitializeEntryPoint(input); // Assert command.Should().BeEquivalentTo(new AllSupportedTypesCommand { DateTimeOffset = new DateTime(1995, 04, 28) }); }