Skip to content

Commit 8dbeeeb

Browse files
committed
Refactor the arguments enumeration
1 parent dd8676e commit 8dbeeeb

File tree

9 files changed

+146
-136
lines changed

9 files changed

+146
-136
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
5+
namespace MGR.CommandLineParser
6+
{
7+
internal class Arguments
8+
{
9+
private readonly List<string> _arguments;
10+
private int _currentIndex = -1;
11+
public Arguments(IEnumerable<string> args)
12+
{
13+
_arguments = new List<string>(args);
14+
}
15+
16+
public void Revert()
17+
{
18+
_currentIndex = Math.Max(-1, _currentIndex - 1);
19+
}
20+
21+
public bool Advance()
22+
{
23+
if (_arguments.Count == _currentIndex + 1)
24+
{
25+
return false;
26+
}
27+
_currentIndex++;
28+
ReplaceRspFileInCurrentPosition();
29+
return true;
30+
}
31+
32+
private void ReplaceRspFileInCurrentPosition()
33+
{
34+
var current = GetCurrent();
35+
if (current.StartsWith("@", StringComparison.CurrentCulture))
36+
{
37+
if (!current.StartsWith("@@", StringComparison.CurrentCulture))
38+
{
39+
var responseFileName = current.Remove(0, 1);
40+
if (Path.GetExtension(responseFileName) == ".rsp" && File.Exists(responseFileName))
41+
{
42+
var responseFileContent = File.ReadAllLines(responseFileName);
43+
_arguments.RemoveAt(_currentIndex);
44+
_arguments.InsertRange(_currentIndex, responseFileContent);
45+
ReplaceRspFileInCurrentPosition();
46+
return;
47+
}
48+
}
49+
var currentWithoutAt = current.Remove(0, 1);
50+
_arguments[_currentIndex] = currentWithoutAt;
51+
}
52+
}
53+
54+
55+
public string GetCurrent()
56+
{
57+
if (_currentIndex < 0 || _currentIndex >= _arguments.Count)
58+
{
59+
throw new ArgumentOutOfRangeException();
60+
}
61+
return _arguments[_currentIndex];
62+
}
63+
}
64+
}

src/MGR.CommandLineParser/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ internal static class ExceptionMessages
1919

2020
internal static readonly Func<object, string, string> FormatParserOptionNotFoundForCommand =
2121
(commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "There is no option '{1}' for the command '{0}'.", commandName, optionName);
22+
internal static readonly Func<object, string, string> FormatParserOptionValueNotFoundForCommand =
23+
(commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "A value should be provided for option '{1}' for the command '{0}'.", commandName, optionName);
2224

2325
internal static readonly Func<object, string, string> FormatParserOptionValueRequired =
2426
(commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "You should specified a value for the option '{1}' of the command '{0}'.", commandName, optionName);

src/MGR.CommandLineParser/Extensions/EnumerableExtensions.cs

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/MGR.CommandLineParser/Extensions/EnumeratorExtensions.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/MGR.CommandLineParser/Parser.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,31 @@ internal Parser(ParserOptions parserOptions, IServiceProvider serviceProvider)
2424

2525
public string CommandLineName => _parserOptions.CommandLineName;
2626

27-
public async Task<ParsingResult> Parse<TCommand>(IEnumerable<string> arguments) where TCommand : class, ICommand => await ParseArguments(arguments, (parserEngine, argumentsEnumerator) =>
28-
parserEngine.Parse<TCommand>(argumentsEnumerator));
27+
public async Task<ParsingResult> Parse<TCommand>(IEnumerable<string> args) where TCommand : class, ICommand => await ParseArguments(args, (parserEngine, arguments) =>
28+
parserEngine.Parse<TCommand>(arguments));
2929

30-
public async Task<ParsingResult> Parse(IEnumerable<string> arguments) => await ParseArguments(arguments, (parserEngine, argumentsEnumerator) =>
31-
parserEngine.Parse(argumentsEnumerator));
30+
public async Task<ParsingResult> Parse(IEnumerable<string> args) => await ParseArguments(args, (parserEngine, arguments) =>
31+
parserEngine.Parse(arguments));
3232

33-
public async Task<ParsingResult> ParseWithDefaultCommand<TCommand>(IEnumerable<string> arguments) where TCommand : class, ICommand => await ParseArguments(arguments, (parserEngine, argumentsEnumerator) =>
34-
parserEngine.ParseWithDefaultCommand<TCommand>(argumentsEnumerator));
33+
public async Task<ParsingResult> ParseWithDefaultCommand<TCommand>(IEnumerable<string> args) where TCommand : class, ICommand => await ParseArguments(args, (parserEngine, arguments) =>
34+
parserEngine.ParseWithDefaultCommand<TCommand>(arguments));
3535

36-
private async Task<ParsingResult> ParseArguments(IEnumerable<string> arguments, Func<ParserEngine, IEnumerator<string>, Task<ParsingResult>> callParse)
36+
private async Task<ParsingResult> ParseArguments(IEnumerable<string> args, Func<ParserEngine, Arguments, Task<ParsingResult>> callParse)
3737
{
38-
if (arguments == null)
38+
if (args == null)
3939
{
4040
return new ParsingResult(null, null, CommandParsingResultCode.NoArgumentsProvided);
4141
}
4242

43+
var arguments = new Arguments(args);
4344
var loggerFactory = _serviceProvider.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
4445
var logger = loggerFactory.CreateLogger<LoggerCategory.Parser>();
4546
using (logger.BeginParsingArguments(Guid.NewGuid().ToString()))
4647
{
4748
logger.CreationOfParserEngine();
4849
var parserEngine = new ParserEngine(_serviceProvider, loggerFactory);
49-
var argumentsEnumerator = arguments.GetArgumentsEnumerator();
5050

51-
var result = await callParse(parserEngine, argumentsEnumerator);
51+
var result = await callParse(parserEngine, arguments);
5252
return result;
5353
}
5454
}

src/MGR.CommandLineParser/ParserEngine.cs

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ internal ParserEngine(IServiceProvider serviceProvider, ILoggerFactory loggerFac
2121
_logger = loggerFactory.CreateLogger<LoggerCategory.Parser>();
2222
}
2323

24-
internal async Task<ParsingResult> Parse<TCommand>(IEnumerator<string> argumentsEnumerator) where TCommand : class, ICommand
24+
internal async Task<ParsingResult> Parse<TCommand>(Arguments arguments) where TCommand : class, ICommand
2525
{
2626
using (_logger.BeginParsingForSpecificCommandType(typeof(TCommand)))
2727
{
2828
var commandTypeProviders = _serviceProvider.GetServices<ICommandTypeProvider>();
2929
var commandType = await commandTypeProviders.GetCommandType<TCommand>();
30-
var parsingResult = ParseImpl(argumentsEnumerator, commandType);
30+
var parsingResult = ParseImpl(arguments, commandType);
3131
if (parsingResult.ParsingResultCode == CommandParsingResultCode.NoCommandFound)
3232
{
3333
_logger.NoCommandFoundAfterSpecificParsing();
@@ -42,46 +42,47 @@ internal async Task<ParsingResult> Parse<TCommand>(IEnumerator<string> arguments
4242
}
4343
}
4444

45-
internal async Task<ParsingResult> ParseWithDefaultCommand<TCommand>(IEnumerator<string> argumentsEnumerator)
45+
internal async Task<ParsingResult> ParseWithDefaultCommand<TCommand>(Arguments arguments)
4646
where TCommand : class, ICommand
4747
{
4848
using (_logger.BeginParsingWithDefaultCommandType(typeof(TCommand)))
4949
{
50-
var commandName = argumentsEnumerator.GetNextCommandLineItem();
51-
if (commandName != null)
50+
if (arguments.Advance())
5251
{
52+
var commandName = arguments.GetCurrent();
5353
_logger.ArgumentProvidedWithDefaultCommandType(commandName);
5454
var commandTypeProviders = _serviceProvider.GetServices<ICommandTypeProvider>();
5555
var commandType = await commandTypeProviders.GetCommandType(commandName);
5656
if (commandType != null)
5757
{
5858
_logger.CommandTypeFoundWithDefaultCommandType(commandName);
59-
return ParseImpl(argumentsEnumerator, commandType);
59+
return ParseImpl(arguments, commandType);
6060
}
6161

6262
_logger.NoCommandTypeFoundWithDefaultCommandType(commandName, typeof(TCommand));
63-
var withArgumentsCommandResult = await Parse<TCommand>(argumentsEnumerator.PrefixWith(commandName));
63+
arguments.Revert();
64+
var withArgumentsCommandResult = await Parse<TCommand>(arguments);
6465
return withArgumentsCommandResult;
6566

6667
}
6768

6869
_logger.NoArgumentProvidedWithDefaultCommandType(typeof(TCommand));
69-
var noArgumentsCommandResult = await Parse<TCommand>(argumentsEnumerator);
70+
var noArgumentsCommandResult = await Parse<TCommand>(arguments);
7071
return noArgumentsCommandResult;
7172
}
7273
}
7374

74-
internal async Task<ParsingResult> Parse(IEnumerator<string> argumentsEnumerator)
75+
internal async Task<ParsingResult> Parse(Arguments arguments)
7576
{
7677
_logger.ParseForNotAlreadyKnownCommand();
77-
var commandName = argumentsEnumerator.GetNextCommandLineItem();
78-
if (commandName == null)
78+
if (!arguments.Advance())
7979
{
8080
_logger.NoCommandNameForNotAlreadyKnownCommand();
8181
var helpWriter = _serviceProvider.GetRequiredService<IHelpWriter>();
8282
await helpWriter.WriteCommandListing();
8383
return new ParsingResult(null, null, CommandParsingResultCode.NoCommandNameProvided);
8484
}
85+
var commandName = arguments.GetCurrent();
8586

8687
using (_logger.BeginParsingUsingCommandName(commandName))
8788
{
@@ -96,14 +97,14 @@ internal async Task<ParsingResult> Parse(IEnumerator<string> argumentsEnumerator
9697
}
9798

9899
_logger.CommandTypeFoundForNotAlreadyKnownCommand(commandName);
99-
return ParseImpl(argumentsEnumerator, commandType);
100+
return ParseImpl(arguments, commandType);
100101
}
101102

102103
}
103104

104-
private ParsingResult ParseImpl(IEnumerator<string> argumentsEnumerator, ICommandType commandType)
105+
private ParsingResult ParseImpl(Arguments arguments, ICommandType commandType)
105106
{
106-
var commandObjectBuilder = ExtractCommandLineOptions(commandType, argumentsEnumerator);
107+
var commandObjectBuilder = ExtractCommandLineOptions(commandType, arguments);
107108
if (commandObjectBuilder == null)
108109
{
109110
return new ParsingResult(null, null, CommandParsingResultCode.CommandParametersNotValid);
@@ -118,21 +119,18 @@ private ParsingResult ParseImpl(IEnumerator<string> argumentsEnumerator, IComman
118119
}
119120
return new ParsingResult(commandObjectBuilder.GenerateCommandObject(), null, CommandParsingResultCode.Success);
120121
}
121-
private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, IEnumerator<string> argumentsEnumerator)
122+
123+
private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, Arguments arguments)
122124
{
123125
var commandObjectBuilder = commandType.CreateCommandObjectBuilder(_serviceProvider);
124126
if (commandObjectBuilder == null)
125127
{
126128
return null;
127129
}
128130
var alwaysPutInArgumentList = false;
129-
while (true)
131+
while (arguments.Advance())
130132
{
131-
var argument = argumentsEnumerator.GetNextCommandLineItem();
132-
if (argument == null)
133-
{
134-
break;
135-
}
133+
var argument = arguments.GetCurrent();
136134
if (argument.Equals(Constants.EndOfOptions))
137135
{
138136
alwaysPutInArgumentList = true;
@@ -171,7 +169,17 @@ private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType
171169

172170
if (option.ShouldProvideValue)
173171
{
174-
value = value ?? argumentsEnumerator.GetNextCommandLineItem();
172+
if (value == null)
173+
{
174+
if (!arguments.Advance())
175+
{
176+
var console = _serviceProvider.GetRequiredService<IConsole>();
177+
console.WriteLineError(Constants.ExceptionMessages.FormatParserOptionValueNotFoundForCommand(commandType.Metadata.Name, optionText));
178+
return null;
179+
}
180+
181+
value = arguments.GetCurrent();
182+
}
175183
}
176184

177185
option.AssignValue(value);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using MGR.CommandLineParser.Extensibility.ClassBased;
5+
using MGR.CommandLineParser.Tests.Commands;
6+
using Xunit;
7+
8+
namespace MGR.CommandLineParser.IntegrationTests.DefaultCommand
9+
{
10+
public class SimpleOptionsTests : ConsoleLoggingTestsBase
11+
{
12+
[Fact]
13+
public async Task ParseWithValidArgs()
14+
{
15+
// Arrange
16+
IEnumerable<string> args = new[] { "--str-value:custom value", "-i", "42", "Custom argument value", "-b" };
17+
var expectedReturnCode = CommandParsingResultCode.Success;
18+
var expectedStrValue = "custom value";
19+
var expectedNbOfArguments = 1;
20+
var expectedArgumentsValue = "Custom argument value";
21+
var expectedIntValue = 42;
22+
23+
// Act
24+
var actual = await CallParseWithDefaultCommand<IntTestCommand>(args);
25+
26+
// Assert
27+
Assert.True(actual.IsValid);
28+
Assert.Equal(expectedReturnCode, actual.ParsingResultCode);
29+
Assert.IsAssignableFrom<IClassBasedCommandObject>(actual.CommandObject);
30+
Assert.IsType<IntTestCommand>(((IClassBasedCommandObject)actual.CommandObject).Command);
31+
var rawCommand = (IntTestCommand)((IClassBasedCommandObject)actual.CommandObject).Command;
32+
Assert.Equal(expectedStrValue, rawCommand.StrValue);
33+
Assert.Equal(expectedIntValue, rawCommand.IntValue);
34+
Assert.Null(rawCommand.IntListValue);
35+
Assert.Equal(expectedNbOfArguments, rawCommand.Arguments.Count);
36+
Assert.Equal(expectedArgumentsValue, rawCommand.Arguments.Single());
37+
Assert.True(rawCommand.BoolValue);
38+
}
39+
}
40+
}

tests/MGR.CommandLineParser.UnitTests/Extensions/EnumeratorExtensionsTests.PrependWith.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)