Skip to content

Commit

Permalink
Refactor the arguments enumeration
Browse files Browse the repository at this point in the history
  • Loading branch information
mgrosperrin committed Mar 8, 2024
1 parent dd8676e commit 8dbeeeb
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 136 deletions.
64 changes: 64 additions & 0 deletions src/MGR.CommandLineParser/Arguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace MGR.CommandLineParser
{
internal class Arguments
{
private readonly List<string> _arguments;
private int _currentIndex = -1;
public Arguments(IEnumerable<string> args)
{
_arguments = new List<string>(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];
}
}
}
2 changes: 2 additions & 0 deletions src/MGR.CommandLineParser/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal static class ExceptionMessages

internal static readonly Func<object, string, string> FormatParserOptionNotFoundForCommand =
(commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "There is no option '{1}' for the command '{0}'.", commandName, optionName);
internal static readonly Func<object, string, string> 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<object, string, string> FormatParserOptionValueRequired =
(commandName, optionName) => string.Format(CultureInfo.InvariantCulture, "You should specified a value for the option '{1}' of the command '{0}'.", commandName, optionName);
Expand Down
35 changes: 0 additions & 35 deletions src/MGR.CommandLineParser/Extensions/EnumerableExtensions.cs

This file was deleted.

26 changes: 0 additions & 26 deletions src/MGR.CommandLineParser/Extensions/EnumeratorExtensions.cs

This file was deleted.

20 changes: 10 additions & 10 deletions src/MGR.CommandLineParser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,31 @@ internal Parser(ParserOptions parserOptions, IServiceProvider serviceProvider)

public string CommandLineName => _parserOptions.CommandLineName;

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

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

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

private async Task<ParsingResult> ParseArguments(IEnumerable<string> arguments, Func<ParserEngine, IEnumerator<string>, Task<ParsingResult>> callParse)
private async Task<ParsingResult> ParseArguments(IEnumerable<string> args, Func<ParserEngine, Arguments, Task<ParsingResult>> callParse)
{
if (arguments == null)
if (args == null)
{
return new ParsingResult(null, null, CommandParsingResultCode.NoArgumentsProvided);
}

var arguments = new Arguments(args);
var loggerFactory = _serviceProvider.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
var logger = loggerFactory.CreateLogger<LoggerCategory.Parser>();
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;
}
}
Expand Down
52 changes: 30 additions & 22 deletions src/MGR.CommandLineParser/ParserEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ internal ParserEngine(IServiceProvider serviceProvider, ILoggerFactory loggerFac
_logger = loggerFactory.CreateLogger<LoggerCategory.Parser>();
}

internal async Task<ParsingResult> Parse<TCommand>(IEnumerator<string> argumentsEnumerator) where TCommand : class, ICommand
internal async Task<ParsingResult> Parse<TCommand>(Arguments arguments) where TCommand : class, ICommand
{
using (_logger.BeginParsingForSpecificCommandType(typeof(TCommand)))
{
var commandTypeProviders = _serviceProvider.GetServices<ICommandTypeProvider>();
var commandType = await commandTypeProviders.GetCommandType<TCommand>();
var parsingResult = ParseImpl(argumentsEnumerator, commandType);
var parsingResult = ParseImpl(arguments, commandType);
if (parsingResult.ParsingResultCode == CommandParsingResultCode.NoCommandFound)
{
_logger.NoCommandFoundAfterSpecificParsing();
Expand All @@ -42,46 +42,47 @@ internal async Task<ParsingResult> Parse<TCommand>(IEnumerator<string> arguments
}
}

internal async Task<ParsingResult> ParseWithDefaultCommand<TCommand>(IEnumerator<string> argumentsEnumerator)
internal async Task<ParsingResult> ParseWithDefaultCommand<TCommand>(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<ICommandTypeProvider>();
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<TCommand>(argumentsEnumerator.PrefixWith(commandName));
arguments.Revert();
var withArgumentsCommandResult = await Parse<TCommand>(arguments);
return withArgumentsCommandResult;

}

_logger.NoArgumentProvidedWithDefaultCommandType(typeof(TCommand));
var noArgumentsCommandResult = await Parse<TCommand>(argumentsEnumerator);
var noArgumentsCommandResult = await Parse<TCommand>(arguments);
return noArgumentsCommandResult;
}
}

internal async Task<ParsingResult> Parse(IEnumerator<string> argumentsEnumerator)
internal async Task<ParsingResult> Parse(Arguments arguments)
{
_logger.ParseForNotAlreadyKnownCommand();
var commandName = argumentsEnumerator.GetNextCommandLineItem();
if (commandName == null)
if (!arguments.Advance())
{
_logger.NoCommandNameForNotAlreadyKnownCommand();
var helpWriter = _serviceProvider.GetRequiredService<IHelpWriter>();
await helpWriter.WriteCommandListing();
return new ParsingResult(null, null, CommandParsingResultCode.NoCommandNameProvided);
}
var commandName = arguments.GetCurrent();

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

_logger.CommandTypeFoundForNotAlreadyKnownCommand(commandName);
return ParseImpl(argumentsEnumerator, commandType);
return ParseImpl(arguments, commandType);
}

}

private ParsingResult ParseImpl(IEnumerator<string> 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);
Expand All @@ -118,21 +119,18 @@ private ParsingResult ParseImpl(IEnumerator<string> argumentsEnumerator, IComman
}
return new ParsingResult(commandObjectBuilder.GenerateCommandObject(), null, CommandParsingResultCode.Success);
}
private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, IEnumerator<string> argumentsEnumerator)

private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, Arguments arguments)
{
var commandObjectBuilder = commandType.CreateCommandObjectBuilder(_serviceProvider);
if (commandObjectBuilder == null)
{
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;
Expand Down Expand Up @@ -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<IConsole>();
console.WriteLineError(Constants.ExceptionMessages.FormatParserOptionValueNotFoundForCommand(commandType.Metadata.Name, optionText));
return null;
}

value = arguments.GetCurrent();
}
}

option.AssignValue(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string> 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<IntTestCommand>(args);

// Assert
Assert.True(actual.IsValid);
Assert.Equal(expectedReturnCode, actual.ParsingResultCode);
Assert.IsAssignableFrom<IClassBasedCommandObject>(actual.CommandObject);
Assert.IsType<IntTestCommand>(((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);
}
}
}

This file was deleted.

Loading

0 comments on commit 8dbeeeb

Please sign in to comment.