Skip to content
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();

Check warning on line 59 in src/MGR.CommandLineParser/Arguments.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a constructor overloads that allows a more meaningful exception message to be provided.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttc0O7s3WKsGdMDTp&open=AZ_Ttc0O7s3WKsGdMDTp&pullRequest=168
}
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 @@

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) =>

Check failure on line 27 in src/MGR.CommandLineParser/Parser.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename parameter 'args' to 'arguments' to match the interface declaration.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttcx77s3WKsGdMDTk&open=AZ_Ttcx77s3WKsGdMDTk&pullRequest=168
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) =>

Check failure on line 30 in src/MGR.CommandLineParser/Parser.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename parameter 'args' to 'arguments' to match the interface declaration.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttcx77s3WKsGdMDTj&open=AZ_Ttcx77s3WKsGdMDTj&pullRequest=168
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) =>

Check failure on line 33 in src/MGR.CommandLineParser/Parser.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename parameter 'args' to 'arguments' to match the interface declaration.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttcx77s3WKsGdMDTl&open=AZ_Ttcx77s3WKsGdMDTl&pullRequest=168
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 @@
_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

Check warning on line 24 in src/MGR.CommandLineParser/ParserEngine.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

All 'Parse' method overloads should be adjacent.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttc0G7s3WKsGdMDTm&open=AZ_Ttc0G7s3WKsGdMDTm&pullRequest=168
{
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> 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 @@
}

_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 @@
}
return new ParsingResult(commandObjectBuilder.GenerateCommandObject(), null, CommandParsingResultCode.Success);
}
private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, IEnumerator<string> argumentsEnumerator)

private ICommandObjectBuilder ExtractCommandLineOptions(ICommandType commandType, Arguments arguments)

Check failure on line 123 in src/MGR.CommandLineParser/ParserEngine.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 22 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttc0G7s3WKsGdMDTn&open=AZ_Ttc0G7s3WKsGdMDTn&pullRequest=168
{
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 @@

if (option.ShouldProvideValue)
{
value = value ?? argumentsEnumerator.GetNextCommandLineItem();
if (value == null)

Check warning on line 172 in src/MGR.CommandLineParser/ParserEngine.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this if statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=mgrosperrin_commandlineparser&issues=AZ_Ttc0G7s3WKsGdMDTo&open=AZ_Ttc0G7s3WKsGdMDTo&pullRequest=168
{
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