Esempio n. 1
0
        /// <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);
            }
        }
Esempio n. 2
0
    /// <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);
        }
    }
Esempio n. 3
0
        public void Resolve_Negative_Test(IReadOnlyList <Type> commandTypes)
        {
            // Act & Assert
            var ex = Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes));

            Console.WriteLine(ex.Message);
        }
Esempio n. 4
0
        /// <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);
            }
        }
Esempio n. 5
0
        public void Command_parameters_must_have_unique_names()
        {
            // Arrange
            var commandTypes = new[] { typeof(DuplicateParameterNameCommand) };

            // Act & assert
            Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
        }
Esempio n. 6
0
        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));
        }
Esempio n. 7
0
        public void Command_options_must_have_unique_environment_variable_names()
        {
            // Arrange
            var commandTypes = new[] { typeof(DuplicateOptionEnvironmentVariableNamesCommand) };

            // Act & assert
            Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
        }
Esempio n. 8
0
        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));
        }
Esempio n. 9
0
        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));
        }
Esempio n. 10
0
        public void Commands_must_implement_the_corresponding_interface()
        {
            // Arrange
            var commandTypes = new[] { typeof(NonImplementedCommand) };

            // Act & assert
            Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
        }
Esempio n. 11
0
        public void Commands_must_be_annotated_by_an_attribute()
        {
            // Arrange
            var commandTypes = new[] { typeof(NonAnnotatedCommand) };

            // Act & assert
            Assert.Throws <CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
        }
Esempio n. 12
0
        public void Resolve_Test(
            IReadOnlyList <Type> commandTypes,
            IReadOnlyList <CommandSchema> expectedCommandSchemas)
        {
            // Act
            var applicationSchema = ApplicationSchema.Resolve(commandTypes);

            // Assert
            applicationSchema.Commands.Should().BeEquivalentTo(expectedCommandSchemas);
        }
Esempio n. 13
0
        private async ValueTask <int> HandleCommandExecutionAsync(
            ApplicationSchema applicationSchema,
            CommandLineInput commandLineInput,
            IReadOnlyDictionary <string, string> environmentVariables)
        {
            await applicationSchema
            .InitializeEntryPoint(commandLineInput, environmentVariables, _typeActivator)
            .ExecuteAsync(_console);

            return(0);
        }
Esempio n. 14
0
 public HelpContext(
     ApplicationMetadata applicationMetadata,
     ApplicationSchema applicationSchema,
     CommandSchema commandSchema,
     IReadOnlyDictionary <IMemberSchema, object?> commandDefaultValues)
 {
     ApplicationMetadata  = applicationMetadata;
     ApplicationSchema    = applicationSchema;
     CommandSchema        = commandSchema;
     CommandDefaultValues = commandDefaultValues;
 }
Esempio n. 15
0
        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));
        }
Esempio n. 16
0
        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));
        }
Esempio n. 17
0
        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));
        }
Esempio n. 18
0
        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));
        }
Esempio n. 19
0
        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));
        }
Esempio n. 20
0
        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));
        }
Esempio n. 21
0
        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));
        }
Esempio n. 22
0
        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);
        }
Esempio n. 23
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));
        }
Esempio n. 24
0
        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)
            });
        }
Esempio n. 25
0
        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
            });
        }
Esempio n. 26
0
        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 }
            });
        }
Esempio n. 27
0
        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") }
            });
        }
Esempio n. 28
0
        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)
            });
        }
Esempio n. 29
0
        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" }
            });
        }
Esempio n. 30
0
        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)
            });
        }