From ff45102768aa0f26e25cc9fee42e82545786400e Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Sun, 25 Aug 2024 14:57:59 +0200 Subject: [PATCH 1/3] Parsing argument names with direct equals --- src/Nethermind/Nethermind.Runner/Program.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index e400cb17ce8..e948e475d4a 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -581,11 +581,27 @@ static HashSet> GetValidArguments(CommandLineApplication ap return validArguments; } - static ReadOnlyMemory GetArgumentName(string arg) => arg.StartsWith("--") ? arg.AsMemory(2) : arg.StartsWith('-') ? arg.AsMemory(1) : ReadOnlyMemory.Empty; + static IEnumerable> GetArgumentName(string arg) + { + ReadOnlyMemory argument = arg.StartsWith("--") ? arg.AsMemory(2) : + arg.StartsWith('-') ? arg.AsMemory(1) : ReadOnlyMemory.Empty; + + int index = argument.Span.IndexOf('='); + if (index == -1) + { + yield return argument; + } + else + { + yield return argument.Slice(0, index); + yield return argument.Length > index ? argument.Slice(index + 1) : ReadOnlyMemory.Empty; + } + } + static IEnumerable> GetArgumentNames(IEnumerable args) { bool lastWasArgument = false; - foreach (ReadOnlyMemory potentialArgument in args.Select(GetArgumentName)) + foreach (ReadOnlyMemory potentialArgument in args.SelectMany(GetArgumentName)) { if (!lastWasArgument) { From 97c53f4831a3e72f51d3b32c56e0736cece352de Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Mon, 26 Aug 2024 10:28:41 +0200 Subject: [PATCH 2/3] Use default validation for arguments --- src/Nethermind/Nethermind.Config/ExitCodes.cs | 5 +- src/Nethermind/Nethermind.Runner/Program.cs | 130 ++++++------------ 2 files changed, 42 insertions(+), 93 deletions(-) diff --git a/src/Nethermind/Nethermind.Config/ExitCodes.cs b/src/Nethermind/Nethermind.Config/ExitCodes.cs index c6a4e1c7eec..dd6a4381bee 100644 --- a/src/Nethermind/Nethermind.Config/ExitCodes.cs +++ b/src/Nethermind/Nethermind.Config/ExitCodes.cs @@ -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 diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index e948e475d4a..56b46bb8010 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -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"; @@ -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); @@ -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 switch + { + _ when matches.Length == 0 => "", + _ when matches.Length == 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) @@ -250,6 +283,9 @@ private static void Run(string[] args) } } + [GeneratedRegex("^Unexpected value '(?.+)' for option '(?.+)'", RegexOptions.Singleline)] + private static partial Regex GetUnexpectedConfigValueRegex(); + private static IntPtr OnResolvingUnmanagedDll(Assembly _, string nativeLibraryName) { var alternativePath = nativeLibraryName switch @@ -560,92 +596,4 @@ private static string GetProductInfo() return info.ToString(); } - - private static bool ValidateArguments(string[] args, CommandLineApplication app) - { - static HashSet> GetValidArguments(CommandLineApplication app) - { - HashSet> validArguments = new(new MemoryContentsComparer()); - 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 IEnumerable> GetArgumentName(string arg) - { - ReadOnlyMemory argument = arg.StartsWith("--") ? arg.AsMemory(2) : - arg.StartsWith('-') ? arg.AsMemory(1) : ReadOnlyMemory.Empty; - - int index = argument.Span.IndexOf('='); - if (index == -1) - { - yield return argument; - } - else - { - yield return argument.Slice(0, index); - yield return argument.Length > index ? argument.Slice(index + 1) : ReadOnlyMemory.Empty; - } - } - - static IEnumerable> GetArgumentNames(IEnumerable args) - { - bool lastWasArgument = false; - foreach (ReadOnlyMemory potentialArgument in args.SelectMany(GetArgumentName)) - { - if (!lastWasArgument) - { - bool isCurrentArgument = lastWasArgument = !potentialArgument.IsEmpty; - if (isCurrentArgument) - { - yield return potentialArgument; - } - } - else - { - lastWasArgument = false; - } - } - } - - static IEnumerable> GetDuplicateArguments(IEnumerable> argumentsNames) => - argumentsNames.GroupBy(n => n, new MemoryContentsComparer()) - .Where(g => g.Count() > 1) - .Select(g => g.Key); - - // Get all valid options from the configuration files - HashSet> validArguments = GetValidArguments(app); - - IEnumerable> argumentsNamesProvided = GetArgumentNames(args); - foreach (ReadOnlyMemory 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; - } } From 534a9ab017f91f7280cd9467a3afc5bedd6df74a Mon Sep 17 00:00:00 2001 From: "lukasz.rozmej" Date: Mon, 26 Aug 2024 15:08:07 +0200 Subject: [PATCH 3/3] simplify switch --- src/Nethermind/Nethermind.Runner/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index 56b46bb8010..e6d0b5780a0 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -233,10 +233,10 @@ private static void Run(string[] args) catch (UnrecognizedCommandParsingException e) { string[] matches = e.NearestMatches.Take(3).ToArray(); - string suggestion = matches switch + string suggestion = matches.Length switch { - _ when matches.Length == 0 => "", - _ when matches.Length == 1 => $" Did you mean {matches[0]}", + 0 => "", + 1 => $" Did you mean {matches[0]}", _ => $" Did you mean one of: {string.Join(", ", matches)}" }; _logger.Error($"{e.Message}.{suggestion}");