diff --git a/src/MGR.CommandLineParser/Arguments.cs b/src/MGR.CommandLineParser/Arguments.cs new file mode 100644 index 0000000..a55fa05 --- /dev/null +++ b/src/MGR.CommandLineParser/Arguments.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace MGR.CommandLineParser +{ + internal class Arguments + { + private readonly List _arguments; + private int _currentIndex = -1; + public Arguments(IEnumerable args) + { + _arguments = new List(args); + } + + public void Revert() + { + _currentIndex = Math.Max(-1, _currentIndex - 1); + } + + public bool Advance() + { + if (_arguments.Count == _currentIndex + 1) + { + return false; + } + _currentIndex++; + ReplaceRspFileInCurrentPosition(); + return true; + } + + private void ReplaceRspFileInCurrentPosition() + { + var current = GetCurrent(); + if (current.StartsWith("@", StringComparison.CurrentCulture)) + { + if (!current.StartsWith("@@", StringComparison.CurrentCulture)) + { + var responseFileName = current.Remove(0, 1); + if (Path.GetExtension(responseFileName) == ".rsp" && File.Exists(responseFileName)) + { + var responseFileContent = File.ReadAllLines(responseFileName); + _arguments.RemoveAt(_currentIndex); + _arguments.InsertRange(_currentIndex, responseFileContent); + ReplaceRspFileInCurrentPosition(); + return; + } + } + var currentWithoutAt = current.Remove(0, 1); + _arguments[_currentIndex] = currentWithoutAt; + } + } + + + public string GetCurrent() + { + if (_currentIndex < 0 || _currentIndex >= _arguments.Count) + { + throw new ArgumentOutOfRangeException(); + } + return _arguments[_currentIndex]; + } + } +} diff --git a/src/MGR.CommandLineParser/Constants.cs b/src/MGR.CommandLineParser/Constants.cs index 8636329..5a94d9d 100644 --- a/src/MGR.CommandLineParser/Constants.cs +++ b/src/MGR.CommandLineParser/Constants.cs @@ -19,6 +19,8 @@ internal static class ExceptionMessages internal static readonly Func FormatParserOptionNotFoundForCommand = (commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "There is no option '{1}' for the command '{0}'.", commandName, optionName); + internal static readonly Func FormatParserOptionValueNotFoundForCommand = + (commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "A value should be provided for option '{1}' for the command '{0}'.", commandName, optionName); internal static readonly Func FormatParserOptionValueRequired = (commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "You should specified a value for the option '{1}' of the command '{0}'.", commandName, optionName); diff --git a/src/MGR.CommandLineParser/Extensions/EnumerableExtensions.cs b/src/MGR.CommandLineParser/Extensions/EnumerableExtensions.cs deleted file mode 100644 index 3e1c447..0000000 --- a/src/MGR.CommandLineParser/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.IO; -using System.Linq; -using JetBrains.Annotations; - -// ReSharper disable once CheckNamespace -namespace System.Collections.Generic -{ - internal static class EnumerableExtensions - { - internal static IEnumerator GetArgumentsEnumerator([NotNull]this IEnumerable arguments) - { - var enumerable = arguments as IList ?? arguments.ToList(); - var firstArgument = enumerable.FirstOrDefault(); - if (string.IsNullOrEmpty(firstArgument)) - { - return enumerable.GetEnumerator(); - } - if (firstArgument.StartsWith("@", StringComparison.CurrentCulture)) - { - if (!firstArgument.StartsWith("@@", StringComparison.CurrentCulture)) - { - var responseFileName = firstArgument.Remove(0, 1); - if (Path.GetExtension(responseFileName) == ".rsp" && File.Exists(responseFileName)) - { - var responseFileContent = File.ReadAllLines(responseFileName); - return responseFileContent.AsEnumerable().GetEnumerator(); - } - } - var firstArgumentWithoutAt = firstArgument.Remove(0, 1); - return new[] { firstArgumentWithoutAt }.Concat(enumerable.Skip(1)).GetEnumerator(); - } - return enumerable.GetEnumerator(); - } - } -} diff --git a/src/MGR.CommandLineParser/Extensions/EnumeratorExtensions.cs b/src/MGR.CommandLineParser/Extensions/EnumeratorExtensions.cs deleted file mode 100644 index 0b5f105..0000000 --- a/src/MGR.CommandLineParser/Extensions/EnumeratorExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ - -// ReSharper disable once CheckNamespace -namespace System.Collections.Generic -{ - internal static class EnumeratorExtensions - { - internal static string GetNextCommandLineItem(this IEnumerator argsEnumerator) - { - if (argsEnumerator == null || !argsEnumerator.MoveNext()) - { - return null; - } - return argsEnumerator.Current; - } - - internal static IEnumerator PrefixWith(this IEnumerator argsEnumerator, string prefix) - { - var list = new List {prefix}; - while (argsEnumerator != null && argsEnumerator.MoveNext()) - { - list.Add(argsEnumerator.Current); - } - return list.GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/MGR.CommandLineParser/Parser.cs b/src/MGR.CommandLineParser/Parser.cs index fa95758..2927072 100644 --- a/src/MGR.CommandLineParser/Parser.cs +++ b/src/MGR.CommandLineParser/Parser.cs @@ -24,31 +24,31 @@ internal Parser(ParserOptions parserOptions, IServiceProvider serviceProvider) public string CommandLineName => _parserOptions.CommandLineName; - public async Task Parse(IEnumerable arguments) where TCommand : class, ICommand => await ParseArguments(arguments, (parserEngine, argumentsEnumerator) => - parserEngine.Parse(argumentsEnumerator)); + public async Task Parse(IEnumerable args) where TCommand : class, ICommand => await ParseArguments(args, (parserEngine, arguments) => + parserEngine.Parse(arguments)); - public async Task Parse(IEnumerable arguments) => await ParseArguments(arguments, (parserEngine, argumentsEnumerator) => - parserEngine.Parse(argumentsEnumerator)); + public async Task Parse(IEnumerable args) => await ParseArguments(args, (parserEngine, arguments) => + parserEngine.Parse(arguments)); - public async Task ParseWithDefaultCommand(IEnumerable arguments) where TCommand : class, ICommand => await ParseArguments(arguments, (parserEngine, argumentsEnumerator) => - parserEngine.ParseWithDefaultCommand(argumentsEnumerator)); + public async Task ParseWithDefaultCommand(IEnumerable args) where TCommand : class, ICommand => await ParseArguments(args, (parserEngine, arguments) => + parserEngine.ParseWithDefaultCommand(arguments)); - private async Task ParseArguments(IEnumerable arguments, Func, Task> callParse) + private async Task ParseArguments(IEnumerable args, Func> callParse) { - if (arguments == null) + if (args == null) { return new ParsingResult(null, null, CommandParsingResultCode.NoArgumentsProvided); } + var arguments = new Arguments(args); var loggerFactory = _serviceProvider.GetService() ?? NullLoggerFactory.Instance; var logger = loggerFactory.CreateLogger(); using (logger.BeginParsingArguments(Guid.NewGuid().ToString())) { logger.CreationOfParserEngine(); var parserEngine = new ParserEngine(_serviceProvider, loggerFactory); - var argumentsEnumerator = arguments.GetArgumentsEnumerator(); - var result = await callParse(parserEngine, argumentsEnumerator); + var result = await callParse(parserEngine, arguments); return result; } } diff --git a/src/MGR.CommandLineParser/ParserEngine.cs b/src/MGR.CommandLineParser/ParserEngine.cs index 753f093..8834007 100644 --- a/src/MGR.CommandLineParser/ParserEngine.cs +++ b/src/MGR.CommandLineParser/ParserEngine.cs @@ -21,13 +21,13 @@ internal ParserEngine(IServiceProvider serviceProvider, ILoggerFactory loggerFac _logger = loggerFactory.CreateLogger(); } - internal async Task Parse(IEnumerator argumentsEnumerator) where TCommand : class, ICommand + internal async Task Parse(Arguments arguments) where TCommand : class, ICommand { using (_logger.BeginParsingForSpecificCommandType(typeof(TCommand))) { var commandTypeProviders = _serviceProvider.GetServices(); var commandType = await commandTypeProviders.GetCommandType(); - var parsingResult = ParseImpl(argumentsEnumerator, commandType); + var parsingResult = ParseImpl(arguments, commandType); if (parsingResult.ParsingResultCode == CommandParsingResultCode.NoCommandFound) { _logger.NoCommandFoundAfterSpecificParsing(); @@ -42,46 +42,47 @@ internal async Task Parse(IEnumerator arguments } } - internal async Task ParseWithDefaultCommand(IEnumerator argumentsEnumerator) + internal async Task ParseWithDefaultCommand(Arguments arguments) where TCommand : class, ICommand { using (_logger.BeginParsingWithDefaultCommandType(typeof(TCommand))) { - var commandName = argumentsEnumerator.GetNextCommandLineItem(); - if (commandName != null) + if (arguments.Advance()) { + var commandName = arguments.GetCurrent(); _logger.ArgumentProvidedWithDefaultCommandType(commandName); var commandTypeProviders = _serviceProvider.GetServices(); var commandType = await commandTypeProviders.GetCommandType(commandName); if (commandType != null) { _logger.CommandTypeFoundWithDefaultCommandType(commandName); - return ParseImpl(argumentsEnumerator, commandType); + return ParseImpl(arguments, commandType); } _logger.NoCommandTypeFoundWithDefaultCommandType(commandName, typeof(TCommand)); - var withArgumentsCommandResult = await Parse(argumentsEnumerator.PrefixWith(commandName)); + arguments.Revert(); + var withArgumentsCommandResult = await Parse(arguments); return withArgumentsCommandResult; } _logger.NoArgumentProvidedWithDefaultCommandType(typeof(TCommand)); - var noArgumentsCommandResult = await Parse(argumentsEnumerator); + var noArgumentsCommandResult = await Parse(arguments); return noArgumentsCommandResult; } } - internal async Task Parse(IEnumerator argumentsEnumerator) + internal async Task Parse(Arguments arguments) { _logger.ParseForNotAlreadyKnownCommand(); - var commandName = argumentsEnumerator.GetNextCommandLineItem(); - if (commandName == null) + if (!arguments.Advance()) { _logger.NoCommandNameForNotAlreadyKnownCommand(); var helpWriter = _serviceProvider.GetRequiredService(); await helpWriter.WriteCommandListing(); return new ParsingResult(null, null, CommandParsingResultCode.NoCommandNameProvided); } + var commandName = arguments.GetCurrent(); using (_logger.BeginParsingUsingCommandName(commandName)) { @@ -96,14 +97,14 @@ internal async Task Parse(IEnumerator argumentsEnumerator } _logger.CommandTypeFoundForNotAlreadyKnownCommand(commandName); - return ParseImpl(argumentsEnumerator, commandType); + return ParseImpl(arguments, commandType); } } - private ParsingResult ParseImpl(IEnumerator argumentsEnumerator, ICommandType commandType) + private ParsingResult ParseImpl(Arguments arguments, ICommandType commandType) { - var commandObjectBuilder = ExtractCommandLineOptions(commandType, argumentsEnumerator); + var commandObjectBuilder = ExtractCommandLineOptions(commandType, arguments); if (commandObjectBuilder == null) { return new ParsingResult(null, null, CommandParsingResultCode.CommandParametersNotValid); @@ -118,7 +119,8 @@ private ParsingResult ParseImpl(IEnumerator argumentsEnumerator, IComman } return new ParsingResult(commandObjectBuilder.GenerateCommandObject(), null, CommandParsingResultCode.Success); } - private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, IEnumerator argumentsEnumerator) + + private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, Arguments arguments) { var commandObjectBuilder = commandType.CreateCommandObjectBuilder(_serviceProvider); if (commandObjectBuilder == null) @@ -126,13 +128,9 @@ private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType return null; } var alwaysPutInArgumentList = false; - while (true) + while (arguments.Advance()) { - var argument = argumentsEnumerator.GetNextCommandLineItem(); - if (argument == null) - { - break; - } + var argument = arguments.GetCurrent(); if (argument.Equals(Constants.EndOfOptions)) { alwaysPutInArgumentList = true; @@ -171,7 +169,17 @@ private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType if (option.ShouldProvideValue) { - value = value ?? argumentsEnumerator.GetNextCommandLineItem(); + if (value == null) + { + if (!arguments.Advance()) + { + var console = _serviceProvider.GetRequiredService(); + console.WriteLineError(Constants.ExceptionMessages.FormatParserOptionValueNotFoundForCommand(commandType.Metadata.Name, optionText)); + return null; + } + + value = arguments.GetCurrent(); + } } option.AssignValue(value); diff --git a/tests/MGR.CommandLineParser.IntegrationTests/DefaultCommand/SimpleOptionsTests.cs b/tests/MGR.CommandLineParser.IntegrationTests/DefaultCommand/SimpleOptionsTests.cs new file mode 100644 index 0000000..3b5713a --- /dev/null +++ b/tests/MGR.CommandLineParser.IntegrationTests/DefaultCommand/SimpleOptionsTests.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MGR.CommandLineParser.Extensibility.ClassBased; +using MGR.CommandLineParser.Tests.Commands; +using Xunit; + +namespace MGR.CommandLineParser.IntegrationTests.DefaultCommand +{ + public class SimpleOptionsTests : ConsoleLoggingTestsBase + { + [Fact] + public async Task ParseWithValidArgs() + { + // Arrange + IEnumerable args = new[] { "--str-value:custom value", "-i", "42", "Custom argument value", "-b" }; + var expectedReturnCode = CommandParsingResultCode.Success; + var expectedStrValue = "custom value"; + var expectedNbOfArguments = 1; + var expectedArgumentsValue = "Custom argument value"; + var expectedIntValue = 42; + + // Act + var actual = await CallParseWithDefaultCommand(args); + + // Assert + Assert.True(actual.IsValid); + Assert.Equal(expectedReturnCode, actual.ParsingResultCode); + Assert.IsAssignableFrom(actual.CommandObject); + Assert.IsType(((IClassBasedCommandObject)actual.CommandObject).Command); + var rawCommand = (IntTestCommand)((IClassBasedCommandObject)actual.CommandObject).Command; + Assert.Equal(expectedStrValue, rawCommand.StrValue); + Assert.Equal(expectedIntValue, rawCommand.IntValue); + Assert.Null(rawCommand.IntListValue); + Assert.Equal(expectedNbOfArguments, rawCommand.Arguments.Count); + Assert.Equal(expectedArgumentsValue, rawCommand.Arguments.Single()); + Assert.True(rawCommand.BoolValue); + } + } +} \ No newline at end of file diff --git a/tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.PrependWith.cs b/tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.PrependWith.cs deleted file mode 100644 index 66c1c84..0000000 --- a/tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.PrependWith.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using Xunit; - -namespace MGR.CommandLineParser.UnitTests.Extensions -{ - public partial class EnumeratorExtensionsTests - { - public class PrependWith - { - [Fact] - public void Prepend_Correctly_Add_Before_Items() - { - // Arrange - var initialList = new List {"Should", "Be", "Prefixed"}; - var prefix = "This"; - var expected = new List {"This", "Should", "Be", "Prefixed"}.GetEnumerator(); - - // Act - var actual = initialList.GetEnumerator().PrefixWith(prefix); - - // Assert - while (true) - { - var shouldHaveNext = expected.MoveNext(); - Assert.Equal(shouldHaveNext, actual.MoveNext()); - Assert.Equal(expected.Current, actual.Current); - if (!shouldHaveNext) - { - break; - } - } - expected.Dispose(); - actual.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.cs b/tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.cs deleted file mode 100644 index a210067..0000000 --- a/tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MGR.CommandLineParser.UnitTests.Extensions -{ - public partial class EnumeratorExtensionsTests - { - } -} \ No newline at end of file