Skip to content

Refactor the arguments enumeration #168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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