Skip to content

Commit

Permalink
Removes custom args validation and uses library one (#7359)
Browse files Browse the repository at this point in the history
  • Loading branch information
LukaszRozmej authored Aug 26, 2024
1 parent e187cbd commit ebdf27a
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 77 deletions.
5 changes: 3 additions & 2 deletions src/Nethermind/Nethermind.Config/ExitCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public static class ExitCodes
public const int TooLongExtraData = 102;
public const int ConflictingConfigurations = 103;
public const int LowDiskSpace = 104;
public const int DuplicatedArguments = 105;
public const int UnrecognizedArgument = 106;
public const int DuplicatedOption = 105;
public const int UnrecognizedOption = 106;
public const int ForbiddenOptionValue = 107;

// Posix exit code
// https://tldp.org/LDP/abs/html/exitcodes.html
Expand Down
114 changes: 39 additions & 75 deletions src/Nethermind/Nethermind.Runner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

namespace Nethermind.Runner;

public static class Program
public static partial class Program
{
private const string FailureString = "Failure";
private const string DefaultConfigsDirectory = "configs";
Expand Down Expand Up @@ -152,8 +152,6 @@ private static void Run(string[] args)

BuildOptionsFromConfigFiles(app);

if (!ValidateArguments(args, app)) return;

app.OnExecute(async () =>
{
IConfigProvider configProvider = BuildConfigProvider(app, loggerConfigSource, logLevelOverride, configsDirectory, configFile);
Expand Down Expand Up @@ -232,6 +230,41 @@ private static void Run(string[] args)
{
Environment.ExitCode = app.Execute(args);
}
catch (UnrecognizedCommandParsingException e)
{
string[] matches = e.NearestMatches.Take(3).ToArray();
string suggestion = matches.Length switch
{
0 => "",
1 => $" Did you mean {matches[0]}",
_ => $" Did you mean one of: {string.Join(", ", matches)}"
};
_logger.Error($"{e.Message}.{suggestion}");
Environment.ExitCode = ExitCodes.UnrecognizedOption;
}
catch (CommandParsingException e)
{
Regex regex = GetUnexpectedConfigValueRegex();
Match match = regex.Match(e.Message);
if (match.Success)
{
string option = match.Groups["Name"].Value;
CommandOption? optionInfo = app.GetOptions().FirstOrDefault(o => o.ShortName == option || o.LongName == option);
switch (optionInfo?.OptionType)
{
case CommandOptionType.SingleValue or CommandOptionType.SingleOrNoValue:
_logger.Error($"Duplicated option '{option}'");
Environment.ExitCode = ExitCodes.DuplicatedOption;
return;
case CommandOptionType.NoValue:
_logger.Error($"Value {match.Groups["Value"].Value} passed for value-less option '{option}'");
Environment.ExitCode = ExitCodes.ForbiddenOptionValue;
return;
}
}

_logger.Error($"{e.Message}");
}
catch (Exception e)
{
if (e is IExceptionWithExitCode withExit)
Expand All @@ -250,6 +283,9 @@ private static void Run(string[] args)
}
}

[GeneratedRegex("^Unexpected value '(?<Value>.+)' for option '(?<Name>.+)'", RegexOptions.Singleline)]
private static partial Regex GetUnexpectedConfigValueRegex();

private static IntPtr OnResolvingUnmanagedDll(Assembly _, string nativeLibraryName)
{
var alternativePath = nativeLibraryName switch
Expand Down Expand Up @@ -560,76 +596,4 @@ private static string GetProductInfo()

return info.ToString();
}

private static bool ValidateArguments(string[] args, CommandLineApplication app)
{
static HashSet<ReadOnlyMemory<char>> GetValidArguments(CommandLineApplication app)
{
HashSet<ReadOnlyMemory<char>> validArguments = new(new MemoryContentsComparer<char>());
foreach (var option in app.GetOptions())
{
if (!string.IsNullOrEmpty(option.LongName))
{
validArguments.Add(option.LongName.AsMemory());
}
if (!string.IsNullOrEmpty(option.ShortName))
{
validArguments.Add(option.ShortName.AsMemory());
}
}

return validArguments;
}

static ReadOnlyMemory<char> GetArgumentName(string arg) => arg.StartsWith("--") ? arg.AsMemory(2) : arg.StartsWith('-') ? arg.AsMemory(1) : ReadOnlyMemory<char>.Empty;
static IEnumerable<ReadOnlyMemory<char>> GetArgumentNames(IEnumerable<string> args)
{
bool lastWasArgument = false;
foreach (ReadOnlyMemory<char> potentialArgument in args.Select(GetArgumentName))
{
if (!lastWasArgument)
{
bool isCurrentArgument = lastWasArgument = !potentialArgument.IsEmpty;
if (isCurrentArgument)
{
yield return potentialArgument;
}
}
else
{
lastWasArgument = false;
}
}
}

static IEnumerable<ReadOnlyMemory<char>> GetDuplicateArguments(IEnumerable<ReadOnlyMemory<char>> argumentsNames) =>
argumentsNames.GroupBy(n => n, new MemoryContentsComparer<char>())
.Where(g => g.Count() > 1)
.Select(g => g.Key);

// Get all valid options from the configuration files
HashSet<ReadOnlyMemory<char>> validArguments = GetValidArguments(app);

IEnumerable<ReadOnlyMemory<char>> argumentsNamesProvided = GetArgumentNames(args);
foreach (ReadOnlyMemory<char> argumentName in argumentsNamesProvided)
{
// Check if the argument provided is a valid option/argument
if (!validArguments.Contains(argumentName))
{
_logger.Error($"Failed due to unrecognized argument - [{argumentName}].\nRun --help for a list of available options and commands.");
Environment.ExitCode = ExitCodes.UnrecognizedArgument;
return false;
}
}

string duplicateArgumentsList = string.Join(", ", GetDuplicateArguments(argumentsNamesProvided));
if (!string.IsNullOrEmpty(duplicateArgumentsList))
{
_logger.Error($"Failed due to duplicated arguments - [{duplicateArgumentsList}] passed while execution");
Environment.ExitCode = ExitCodes.DuplicatedArguments;
return false;
}

return true;
}
}

0 comments on commit ebdf27a

Please sign in to comment.