From 594a9ec3c7faac1784d81dcbd0029738d532f02d Mon Sep 17 00:00:00 2001 From: yakovypg Date: Wed, 25 Mar 2026 18:33:00 +0300 Subject: [PATCH 1/5] Fix string extensions --- .../Extensions/StringExtensions.cs | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/Core/NetArgumentParser/Extensions/StringExtensions.cs b/Core/NetArgumentParser/Extensions/StringExtensions.cs index da7fcb9..8e8aae2 100644 --- a/Core/NetArgumentParser/Extensions/StringExtensions.cs +++ b/Core/NetArgumentParser/Extensions/StringExtensions.cs @@ -1,24 +1,76 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Text; namespace NetArgumentParser.Extensions; internal static class StringExtensions { + private const StringComparison _defaultComparisonType = StringComparison.Ordinal; + #if !NET5_0_OR_GREATER && !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER - private const StringComparison _defaultComparisonType = StringComparison.CurrentCulture; + internal static string Replace( + this string text, + string oldValue, + string? newValue, + StringComparison comparisonType = _defaultComparisonType) + { + ExtendedArgumentNullException.ThrowIfNull(text, nameof(text)); + ExtendedArgumentException.ThrowIfNullOrEmpty(oldValue, nameof(oldValue)); + + string replacement = newValue ?? string.Empty; + + int currentPosition = 0; + int matchIndex = text.IndexOf(oldValue, currentPosition, comparisonType); + + if (matchIndex < 0) + return text; + + var builder = new StringBuilder(text.Length); + + while (matchIndex >= 0) + { + _ = builder + .Append(text, currentPosition, matchIndex - currentPosition) + .Append(replacement); + + currentPosition = matchIndex + oldValue.Length; + matchIndex = text.IndexOf(oldValue, currentPosition, comparisonType); + } + + return builder + .Append(text, currentPosition, text.Length - currentPosition) + .ToString(); + } -#pragma warning disable IDE0060 // Remove unused parameter internal static bool Contains( this string text, char value, StringComparison comparisonType = _defaultComparisonType) + { + return text.Contains($"{value}", comparisonType); + } + + internal static bool Contains( + this string text, + string? value, + StringComparison comparisonType = _defaultComparisonType) { ExtendedArgumentNullException.ThrowIfNull(text, nameof(text)); - return text.Contains($"{value}"); + + return value is not null + ? text.IndexOf(value, comparisonType) >= 0 + : false; } -#pragma warning restore IDE0060 // Remove unused parameter + + internal static int IndexOf( + this string text, + char value, + StringComparison comparisonType = _defaultComparisonType) + { + ExtendedArgumentNullException.ThrowIfNull(text, nameof(text)); + return text.IndexOf($"{value}", comparisonType); + } +#endif internal static bool StartsWith( this string text, @@ -29,15 +81,14 @@ internal static bool StartsWith( return text.StartsWith($"{value}", comparisonType); } - internal static int IndexOf( + internal static bool EndsWith( this string text, char value, StringComparison comparisonType = _defaultComparisonType) { ExtendedArgumentNullException.ThrowIfNull(text, nameof(text)); - return text.IndexOf($"{value}", comparisonType); + return text.EndsWith($"{value}", comparisonType); } -#endif internal static string RemoveLineBreakFromEnd(this string text) { @@ -49,7 +100,7 @@ internal static string RemoveLineBreakFromEnd(this string text, string lineBreak { ExtendedArgumentNullException.ThrowIfNull(text, nameof(text)); - while (text.EndsWith(lineBreak, StringComparison.CurrentCulture)) + while (text.EndsWith(lineBreak, StringComparison.Ordinal)) { int lineBreakLength = lineBreak.Length; text = text.Remove(text.Length - lineBreakLength); From bd5789fe613bac740b0eee0a8a77d627f32368d4 Mon Sep 17 00:00:00 2001 From: yakovypg Date: Wed, 25 Mar 2026 18:48:46 +0300 Subject: [PATCH 2/5] Fix culture specific operations --- Core/NetArgumentParser/Argument.cs | 2 +- .../Extensions/StringExtensions.cs | 7 ++- .../OptionValueRestrictionParser.cs | 18 +++---- .../Options/MultipleValueOption.cs | 2 +- Core/NetArgumentParser/Options/ValueOption.cs | 8 +-- .../Program.cs | 2 +- .../Program.cs | 5 +- .../ArgumentParserSubcommandTests.cs | 36 ++++++------- .../ArgumentParserTests.cs | 52 +++++++++---------- ...onValueRestrictionParserGeneratorConfig.cs | 2 +- .../NetArgumentParser.Tests/Models/Number.cs | 2 +- .../Models/PageFontSize.cs | 4 +- .../ParserGeneratorTests.cs | 2 +- 13 files changed, 72 insertions(+), 70 deletions(-) diff --git a/Core/NetArgumentParser/Argument.cs b/Core/NetArgumentParser/Argument.cs index 6a33291..e181991 100644 --- a/Core/NetArgumentParser/Argument.cs +++ b/Core/NetArgumentParser/Argument.cs @@ -12,7 +12,7 @@ namespace NetArgumentParser; public class Argument { - private const StringComparison _defaultStringComparison = StringComparison.CurrentCulture; + private const StringComparison _defaultStringComparison = StringComparison.Ordinal; public Argument(string argument, bool recognizeSlashAsOption = false) { diff --git a/Core/NetArgumentParser/Extensions/StringExtensions.cs b/Core/NetArgumentParser/Extensions/StringExtensions.cs index 8e8aae2..1bddbe5 100644 --- a/Core/NetArgumentParser/Extensions/StringExtensions.cs +++ b/Core/NetArgumentParser/Extensions/StringExtensions.cs @@ -96,11 +96,14 @@ internal static string RemoveLineBreakFromEnd(this string text) return RemoveLineBreakFromEnd(text, Environment.NewLine); } - internal static string RemoveLineBreakFromEnd(this string text, string lineBreak) + internal static string RemoveLineBreakFromEnd( + this string text, + string lineBreak, + StringComparison comparisonType = _defaultComparisonType) { ExtendedArgumentNullException.ThrowIfNull(text, nameof(text)); - while (text.EndsWith(lineBreak, StringComparison.Ordinal)) + while (text.EndsWith(lineBreak, comparisonType)) { int lineBreakLength = lineBreak.Length; text = text.Remove(text.Length - lineBreakLength); diff --git a/Core/NetArgumentParser/Options/Configuration/OptionValueRestrictionParser.cs b/Core/NetArgumentParser/Options/Configuration/OptionValueRestrictionParser.cs index eaed4f0..88cadb0 100644 --- a/Core/NetArgumentParser/Options/Configuration/OptionValueRestrictionParser.cs +++ b/Core/NetArgumentParser/Options/Configuration/OptionValueRestrictionParser.cs @@ -154,7 +154,7 @@ private static bool TryParseLogicalOperator(string data, out LogicalOperator log { ExtendedArgumentException.ThrowIfNullOrWhiteSpace(data, nameof(data)); - LogicalOperator? result = data.ToUpper(CultureInfo.CurrentCulture) switch + LogicalOperator? result = data.ToUpperInvariant() switch { "AND" or "&&" or "&" => LogicalOperator.And, "OR" or "||" or "|" => LogicalOperator.Or, @@ -172,7 +172,7 @@ private static Predicate ParseComparePredicate( ExtendedArgumentNullException.ThrowIfNull(parameters, nameof(parameters)); DefaultExceptions.ThrowIfNotEqual(parameters.Length, 1, nameof(parameters.Length)); - double rhs = double.Parse(parameters[0], CultureInfo.CurrentCulture); + double rhs = double.Parse(parameters[0], CultureInfo.InvariantCulture); return value => value is null || compareFunc(value, rhs); } @@ -217,8 +217,8 @@ private static Predicate ParseInRangePredicate(string[] parameters) ExtendedArgumentNullException.ThrowIfNull(parameters, nameof(parameters)); DefaultExceptions.ThrowIfNotEqual(parameters.Length, 2, nameof(parameters.Length)); - dynamic minValue = double.Parse(parameters[0], CultureInfo.CurrentCulture); - dynamic maxValue = double.Parse(parameters[1], CultureInfo.CurrentCulture); + dynamic minValue = double.Parse(parameters[0], CultureInfo.InvariantCulture); + dynamic maxValue = double.Parse(parameters[1], CultureInfo.InvariantCulture); return value => value is null || (value >= minValue && value <= maxValue); } @@ -294,7 +294,7 @@ private static Predicate ParseMaxFileSizePredicate(string[] parameters) ExtendedArgumentNullException.ThrowIfNull(parameters, nameof(parameters)); DefaultExceptions.ThrowIfNotEqual(parameters.Length, 1, nameof(parameters.Length)); - dynamic maxFileSizeInBytes = long.Parse(parameters[0], CultureInfo.CurrentCulture); + dynamic maxFileSizeInBytes = long.Parse(parameters[0], CultureInfo.InvariantCulture); return value => { @@ -324,11 +324,11 @@ private static Predicate ParseFileExtensionPredicate(string[] parameters) { const string periodPrefix = "."; - string extension = t.StartsWith(periodPrefix, StringComparison.CurrentCulture) + string extension = t.StartsWith(periodPrefix, StringComparison.Ordinal) ? t : $"{periodPrefix}{t}"; - return extension.ToUpper(CultureInfo.CurrentCulture); + return extension.ToUpperInvariant(); }); return value => @@ -340,7 +340,7 @@ private static Predicate ParseFileExtensionPredicate(string[] parameters) { string fileExtension = Path .GetExtension(filePath) - .ToUpper(CultureInfo.CurrentCulture); + .ToUpperInvariant(); return allowedExtensions.Contains(fileExtension); } @@ -394,7 +394,7 @@ private Predicate ParsePredicate(string[] data) string name = shouldNegatePredicate ? data[0].Substring(1) : data[0]; string[] parameters = [.. data.Skip(1)]; - Predicate predicate = name.ToUpper(CultureInfo.CurrentCulture) switch + Predicate predicate = name.ToUpperInvariant() switch { "EQUAL" or "==" or "=" => ParseEqualPredicate(parameters), "NOTEQUAL" or "!=" or "<>" => ParseNotEqualPredicate(parameters), diff --git a/Core/NetArgumentParser/Options/MultipleValueOption.cs b/Core/NetArgumentParser/Options/MultipleValueOption.cs index 0a6190c..4ccdd2c 100644 --- a/Core/NetArgumentParser/Options/MultipleValueOption.cs +++ b/Core/NetArgumentParser/Options/MultipleValueOption.cs @@ -137,7 +137,7 @@ protected override bool IsValueSatisfyChoices(IList value) return choices.Any(t => { - var stringComparer = new StringEqualityComparer(StringComparison.OrdinalIgnoreCase); + var stringComparer = new StringEqualityComparer(StringComparison.CurrentCultureIgnoreCase); return enumerableComparer.Invoke(t, castedValue!, stringComparer); }); } diff --git a/Core/NetArgumentParser/Options/ValueOption.cs b/Core/NetArgumentParser/Options/ValueOption.cs index 77194c4..886797f 100644 --- a/Core/NetArgumentParser/Options/ValueOption.cs +++ b/Core/NetArgumentParser/Options/ValueOption.cs @@ -326,7 +326,7 @@ protected string GetDefaultMetaVariable() string prefferedName = GetPrefferedName(); char[] transformedName = prefferedName - .Select(t => char.IsLetter(t) ? char.ToUpper(t, CultureInfo.CurrentCulture) : '_') + .Select(t => char.IsLetter(t) ? char.ToUpperInvariant(t) : '_') .ToArray(); return new string(transformedName); @@ -393,7 +393,7 @@ protected virtual bool IsValueSatisfyChoices(T value) if (IgnoreCaseInChoices && typeof(T) == typeof(string)) { IEnumerable choices = _choices.Cast(); - StringEqualityComparer comparer = new(StringComparison.OrdinalIgnoreCase); + StringEqualityComparer comparer = new(StringComparison.CurrentCultureIgnoreCase); return choices.Contains(value as string, comparer); } @@ -409,8 +409,8 @@ protected virtual bool IsBeforeParseValueSatisfyChoices(string value) return true; StringComparison stringComparison = IgnoreCaseInChoices - ? StringComparison.OrdinalIgnoreCase - : StringComparison.Ordinal; + ? StringComparison.CurrentCultureIgnoreCase + : StringComparison.CurrentCulture; var comparer = new StringEqualityComparer(stringComparison); diff --git a/Examples/NetArgumentParser.Examples.AllUseCases/Program.cs b/Examples/NetArgumentParser.Examples.AllUseCases/Program.cs index 32662f4..c78596b 100644 --- a/Examples/NetArgumentParser.Examples.AllUseCases/Program.cs +++ b/Examples/NetArgumentParser.Examples.AllUseCases/Program.cs @@ -32,7 +32,7 @@ var nameOption = new ValueOption("name", "n", afterValueParsingAction: t => resultValues.Name = t) { - Converter = new ValueConverter(t => t.ToUpper(CultureInfo.CurrentCulture)) + Converter = new ValueConverter(t => t.ToUpperInvariant()) }; var nickOption = new ValueOption("nick", afterValueParsingAction: t => resultValues.Name = t); diff --git a/Examples/NetArgumentParser.Examples.CustomConverter/Program.cs b/Examples/NetArgumentParser.Examples.CustomConverter/Program.cs index dc6f6ea..537f489 100644 --- a/Examples/NetArgumentParser.Examples.CustomConverter/Program.cs +++ b/Examples/NetArgumentParser.Examples.CustomConverter/Program.cs @@ -1,14 +1,13 @@ using System; -using System.Globalization; using NetArgumentParser; using NetArgumentParser.Converters; using NetArgumentParser.Options; var toUpperStringConverter = new ValueConverter( - t => t.ToUpper(CultureInfo.CurrentCulture)); + t => t.ToUpperInvariant()); var toLowerStringConverter = new ValueConverter( - t => t.ToLower(CultureInfo.CurrentCulture)); + t => t.ToUpperInvariant()); string firstName = string.Empty; string secondName = string.Empty; diff --git a/Tests/NetArgumentParser.Tests/ArgumentParserSubcommandTests.cs b/Tests/NetArgumentParser.Tests/ArgumentParserSubcommandTests.cs index 8b8069f..ae382f7 100644 --- a/Tests/NetArgumentParser.Tests/ArgumentParserSubcommandTests.cs +++ b/Tests/NetArgumentParser.Tests/ArgumentParserSubcommandTests.cs @@ -54,7 +54,7 @@ public void Parse_FirstDepthSubcommands_OptionsHandledCorrectly() "--date", dateTimeValue, "-m", leftMargin, topMargin, rightMargin, bottomMargin, expectedExtraArguments[4], - "-a", angleValue.ToString(CultureInfo.CurrentCulture), + "-a", angleValue.ToString(CultureInfo.InvariantCulture), expectedExtraArguments[5] }; @@ -127,14 +127,14 @@ public void Parse_FirstDepthSubcommands_OptionsHandledCorrectly() _ = parser.ParseKnownArguments(arguments, out IList extraArguments); var expectedMargin = new Margin( - int.Parse(leftMargin, CultureInfo.CurrentCulture), - int.Parse(topMargin, CultureInfo.CurrentCulture), - int.Parse(rightMargin, CultureInfo.CurrentCulture), - int.Parse(bottomMargin, CultureInfo.CurrentCulture)); + int.Parse(leftMargin, CultureInfo.InvariantCulture), + int.Parse(topMargin, CultureInfo.InvariantCulture), + int.Parse(rightMargin, CultureInfo.InvariantCulture), + int.Parse(bottomMargin, CultureInfo.InvariantCulture)); Assert.True(savaLog); Assert.Equal(expectedSplitOptions, splitOption); - Assert.Equal(DateTime.Parse(dateTimeValue, CultureInfo.CurrentCulture), recievedDateTime); + Assert.Equal(DateTime.Parse(dateTimeValue, CultureInfo.InvariantCulture), recievedDateTime); Assert.Equal(expectedMargin, margin); Assert.Equal(expectedAngle, angle); @@ -176,14 +176,14 @@ public void Parse_SecondDepthSubcommands_OptionsHandledCorrectly() { expectedExtraArguments[0], expectedExtraArguments[1], - "-A", angleValue.ToString(CultureInfo.CurrentCulture), + "-A", angleValue.ToString(CultureInfo.InvariantCulture), expectedExtraArguments[2], subcommand2Name, expectedExtraArguments[3], - "-W", widthValue.ToString(CultureInfo.CurrentCulture), + "-W", widthValue.ToString(CultureInfo.InvariantCulture), subsubcommand2Name, expectedExtraArguments[4], - "-H", heightValue.ToString(CultureInfo.CurrentCulture), + "-H", heightValue.ToString(CultureInfo.InvariantCulture), expectedExtraArguments[5] }; @@ -356,17 +356,17 @@ public void Parse_ThirdDepthSubcommands_OptionsHandledCorrectly() { expectedExtraArguments[0], expectedExtraArguments[1], - "-A", angleValue.ToString(CultureInfo.CurrentCulture), + "-A", angleValue.ToString(CultureInfo.InvariantCulture), expectedExtraArguments[2], subcommand2Name, expectedExtraArguments[3], - "-W", widthValue.ToString(CultureInfo.CurrentCulture), + "-W", widthValue.ToString(CultureInfo.InvariantCulture), subsubcommand3Name, expectedExtraArguments[4], - "-H", heightValue.ToString(CultureInfo.CurrentCulture), + "-H", heightValue.ToString(CultureInfo.InvariantCulture), expectedExtraArguments[5], subsubsubcommand1Name, - "-O", opacityValue.ToString(CultureInfo.CurrentCulture), + "-O", opacityValue.ToString(CultureInfo.InvariantCulture), expectedExtraArguments[6] }; @@ -987,7 +987,7 @@ public void Parse_FinalOption_OtherArgumentsSkipped() subcommand1Name, "--angle", "100.5", subcommand2Name, - "--final", expectedfinalOptionValue.ToString(CultureInfo.CurrentCulture), + "--final", expectedfinalOptionValue.ToString(CultureInfo.InvariantCulture), "-s", StringSplitOptions.RemoveEmptyEntries.ToString(), "-f", "file1", "file2", "file3" }; @@ -1639,7 +1639,7 @@ public void Parse_FinalOption_ArgumentsParseResultIsCorrect() subcommand1Name, "--angle", "100.5", subcommand2Name, - "--final", expectedFinalOptionValue.ToString(CultureInfo.CurrentCulture), + "--final", expectedFinalOptionValue.ToString(CultureInfo.InvariantCulture), "-s", StringSplitOptions.RemoveEmptyEntries.ToString(), subcommand3Name, "--final", "123", @@ -1938,14 +1938,14 @@ public void Parse_SeveralArguments_ArgumentsParseResultIsCorrect() { extraArguments[0], extraArguments[1], - "-A", angleValue.ToString(CultureInfo.CurrentCulture), + "-A", angleValue.ToString(CultureInfo.InvariantCulture), extraArguments[2], subcommand2Name, extraArguments[3], - "-W", widthValue.ToString(CultureInfo.CurrentCulture), + "-W", widthValue.ToString(CultureInfo.InvariantCulture), subsubcommand2Name, extraArguments[4], - "-H", heightValue.ToString(CultureInfo.CurrentCulture), + "-H", heightValue.ToString(CultureInfo.InvariantCulture), extraArguments[5] }; diff --git a/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs b/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs index 050775e..0e109ee 100644 --- a/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs +++ b/Tests/NetArgumentParser.Tests/ArgumentParserTests.cs @@ -190,20 +190,20 @@ public void Parse_ValueOptions_OptionsHandledCorrectly() _ = parser.ParseKnownArguments(arguments, out IList extraArguments); Assert.Equal(bool.Parse(boolValue), recievedBool); - Assert.Equal(byte.Parse(byteValue, CultureInfo.CurrentCulture), recievedByte); + Assert.Equal(byte.Parse(byteValue, CultureInfo.InvariantCulture), recievedByte); Assert.Equal(char.Parse(charValue), recievedChar); - Assert.Equal(DateTime.Parse(dateTimeValue, CultureInfo.CurrentCulture), recievedDateTime); - Assert.Equal(decimal.Parse(decimalValue, CultureInfo.CurrentCulture), recievedDecimal); - Assert.Equal(double.Parse(doubleValue, CultureInfo.CurrentCulture), recievedDouble); - Assert.Equal(short.Parse(shortValue, CultureInfo.CurrentCulture), recievedShort); - Assert.Equal(int.Parse(intValue, CultureInfo.CurrentCulture), recievedInt); - Assert.Equal(long.Parse(longValue, CultureInfo.CurrentCulture), recievedLong); - Assert.Equal(sbyte.Parse(sbyteValue, CultureInfo.CurrentCulture), recievedSByte); - Assert.Equal(float.Parse(floatValue, CultureInfo.CurrentCulture), recievedFloat); + Assert.Equal(DateTime.Parse(dateTimeValue, CultureInfo.InvariantCulture), recievedDateTime); + Assert.Equal(decimal.Parse(decimalValue, CultureInfo.InvariantCulture), recievedDecimal); + Assert.Equal(double.Parse(doubleValue, CultureInfo.InvariantCulture), recievedDouble); + Assert.Equal(short.Parse(shortValue, CultureInfo.InvariantCulture), recievedShort); + Assert.Equal(int.Parse(intValue, CultureInfo.InvariantCulture), recievedInt); + Assert.Equal(long.Parse(longValue, CultureInfo.InvariantCulture), recievedLong); + Assert.Equal(sbyte.Parse(sbyteValue, CultureInfo.InvariantCulture), recievedSByte); + Assert.Equal(float.Parse(floatValue, CultureInfo.InvariantCulture), recievedFloat); Assert.Equal(stringValue, recievedString); - Assert.Equal(ushort.Parse(ushortValue, CultureInfo.CurrentCulture), recievedUShort); - Assert.Equal(uint.Parse(uintValue, CultureInfo.CurrentCulture), recievedUInt); - Assert.Equal(ulong.Parse(ulongValue, CultureInfo.CurrentCulture), recievedULong); + Assert.Equal(ushort.Parse(ushortValue, CultureInfo.InvariantCulture), recievedUShort); + Assert.Equal(uint.Parse(uintValue, CultureInfo.InvariantCulture), recievedUInt); + Assert.Equal(ulong.Parse(ulongValue, CultureInfo.InvariantCulture), recievedULong); Assert.Empty(extraArguments); } @@ -264,14 +264,14 @@ public void Parse_MultipleValueOptions_OptionsHandledCorrectly() _ = parser.ParseKnownArguments(arguments, out IList extraArguments); var expectedMargin = new Margin( - int.Parse(leftMargin, CultureInfo.CurrentCulture), - int.Parse(topMargin, CultureInfo.CurrentCulture), - int.Parse(rightMargin, CultureInfo.CurrentCulture), - int.Parse(bottomMargin, CultureInfo.CurrentCulture)); + int.Parse(leftMargin, CultureInfo.InvariantCulture), + int.Parse(topMargin, CultureInfo.InvariantCulture), + int.Parse(rightMargin, CultureInfo.InvariantCulture), + int.Parse(bottomMargin, CultureInfo.InvariantCulture)); var expectedPoint = new Point( - double.Parse(pointX, CultureInfo.CurrentCulture), - double.Parse(pointY, CultureInfo.CurrentCulture)); + double.Parse(pointX, CultureInfo.InvariantCulture), + double.Parse(pointY, CultureInfo.InvariantCulture)); List expectedFiles = [file1, file2, file3]; @@ -302,7 +302,7 @@ public void Parse_ValueOptions_SpecialConverterApplied() var arguments = new string[] { "-m", $"{leftMargin},{topMargin},{rightMargin},{bottomMargin}", - "-a", inputAngle.ToString(CultureInfo.CurrentCulture), + "-a", inputAngle.ToString(CultureInfo.InvariantCulture), "-p", $"({pointX};{pointY})" }; @@ -332,7 +332,7 @@ public void Parse_ValueOptions_SpecialConverterApplied() }), new ValueConverter( - t => Math.Abs(int.Parse(t, CultureInfo.CurrentCulture))) + t => Math.Abs(int.Parse(t, CultureInfo.InvariantCulture))) }; var parser = new ArgumentParser() @@ -488,7 +488,7 @@ public void Parse_ValueOptions_DefaultValueApplied() var arguments = new string[] { "-n", expectedName, - "-w", expectedWidth.ToString(CultureInfo.CurrentCulture) + "-w", expectedWidth.ToString(CultureInfo.InvariantCulture) }; var options = new ICommonOption[] @@ -1272,7 +1272,7 @@ public void Parse_OptionsWithAliases_OptionsHandledCorrectly() var arguments = new string[] { - "-A", expectedAngle.ToString(CultureInfo.CurrentCulture), + "-A", expectedAngle.ToString(CultureInfo.InvariantCulture), "--binding", expectedBindMode.ToString() }; @@ -1531,7 +1531,7 @@ public void Parse_SlashBasedOptionsEnabled_SlashBasedOptionsRecognized() var arguments = new string[] { "/l", - "/W", expectedWidth.ToString(CultureInfo.CurrentCulture), + "/W", expectedWidth.ToString(CultureInfo.InvariantCulture), "/Split", expectedSplitOption.ToString(), "--files", file1, file2, file3, file4 }; @@ -2005,7 +2005,7 @@ public void Parse_FinalOption_OtherArgumentsSkipped() { "-v", "--angle", "100.5", - "--final", expectedFinalOptionValue.ToString(CultureInfo.CurrentCulture), + "--final", expectedFinalOptionValue.ToString(CultureInfo.InvariantCulture), "-s", StringSplitOptions.RemoveEmptyEntries.ToString(), "-f", "file1", "file2", "file3" }; @@ -2287,7 +2287,7 @@ public void Parse_SeveralArguments_ArgumentsParseResultIsCorrect() longName: "abs-angle") { Converter = new ValueConverter( - t => Math.Abs(double.Parse(t, CultureInfo.CurrentCulture))) + t => Math.Abs(double.Parse(t, CultureInfo.InvariantCulture))) }; var usedOptions = new ICommonOption[] @@ -2457,7 +2457,7 @@ public void Parse_SeveralUseCases_ArgumentsParsedCorrectly() afterValueParsingAction: t => absAngle = t) { Converter = new ValueConverter( - t => Math.Abs(double.Parse(t, CultureInfo.CurrentCulture))) + t => Math.Abs(double.Parse(t, CultureInfo.InvariantCulture))) }; var options = new ICommonOption[] diff --git a/Tests/NetArgumentParser.Tests/Models/Configurations/OptionValueRestrictionParserGeneratorConfig.cs b/Tests/NetArgumentParser.Tests/Models/Configurations/OptionValueRestrictionParserGeneratorConfig.cs index 6095c2f..15291ce 100644 --- a/Tests/NetArgumentParser.Tests/Models/Configurations/OptionValueRestrictionParserGeneratorConfig.cs +++ b/Tests/NetArgumentParser.Tests/Models/Configurations/OptionValueRestrictionParserGeneratorConfig.cs @@ -104,7 +104,7 @@ internal partial class OptionValueRestrictionParserGeneratorConfig try { - string extension = Path.GetExtension(t).ToUpper(CultureInfo.InvariantCulture); + string extension = Path.GetExtension(t).ToUpperInvariant(); return allowedExtensions.Contains(extension); } catch diff --git a/Tests/NetArgumentParser.Tests/Models/Number.cs b/Tests/NetArgumentParser.Tests/Models/Number.cs index f5fcd5d..57d8f57 100644 --- a/Tests/NetArgumentParser.Tests/Models/Number.cs +++ b/Tests/NetArgumentParser.Tests/Models/Number.cs @@ -67,6 +67,6 @@ public override int GetHashCode() public override string ToString() { - return Value.ToString(CultureInfo.CurrentCulture); + return Value.ToString(CultureInfo.InvariantCulture); } } diff --git a/Tests/NetArgumentParser.Tests/Models/PageFontSize.cs b/Tests/NetArgumentParser.Tests/Models/PageFontSize.cs index bc36cce..992e151 100644 --- a/Tests/NetArgumentParser.Tests/Models/PageFontSize.cs +++ b/Tests/NetArgumentParser.Tests/Models/PageFontSize.cs @@ -31,8 +31,8 @@ public static PageFontSize Parse(string data) nameof(data)); } - int pageNumber = int.Parse(parts[0], CultureInfo.CurrentCulture); - double fontSize = double.Parse(parts[1], CultureInfo.CurrentCulture); + int pageNumber = int.Parse(parts[0], CultureInfo.InvariantCulture); + double fontSize = double.Parse(parts[1], CultureInfo.InvariantCulture); return new PageFontSize(pageNumber, fontSize); } diff --git a/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs b/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs index 18196e0..f028294 100644 --- a/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs +++ b/Tests/NetArgumentParser.Tests/ParserGeneratorTests.cs @@ -387,7 +387,7 @@ public void ConfigureParser_AllOptionTypes_OptionHandledActionsConfiguredCorrect [ ParseSpecificParserGeneratorConfig.ComplexSubcommandName, $"--{ComplexParserGeneratorConfig.AngleLongName}", - expectedAngle.ToString(CultureInfo.CurrentCulture), + expectedAngle.ToString(CultureInfo.InvariantCulture), $"--{ComplexParserGeneratorConfig.VerbosityLevelLongName}", $"-{ComplexParserGeneratorConfig.MarginShortName}", expectedMargin.ToString(), From 7410ae1efa2c6ca2a6c773c55ab0cb64575c7ebe Mon Sep 17 00:00:00 2001 From: yakovypg Date: Wed, 10 Jun 2026 20:14:38 +0300 Subject: [PATCH 3/5] Fix default exceptions --- Core/NetArgumentParser/DefaultExceptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/NetArgumentParser/DefaultExceptions.cs b/Core/NetArgumentParser/DefaultExceptions.cs index c16f074..602b8b2 100644 --- a/Core/NetArgumentParser/DefaultExceptions.cs +++ b/Core/NetArgumentParser/DefaultExceptions.cs @@ -109,13 +109,13 @@ private static void ThrowLessEqual(T value, T other, string? paramName) private static void ThrowEqual(T value, T other, string? paramName) { - string message = $"{paramName} ('{value}') must be equal to {other}."; + string message = $"{paramName} ('{value}') must not be equal to {other}."; throw new ArgumentOutOfRangeException(paramName, value, message); } private static void ThrowNotEqual(T value, T other, string? paramName) { - string message = $"{paramName} ('{value}') must not be equal to {other}."; + string message = $"{paramName} ('{value}') must be equal to {other}."; throw new ArgumentOutOfRangeException(paramName, value, message); } } From 8d9c528c06b0fc9aed3c158422933fa4a17758e8 Mon Sep 17 00:00:00 2001 From: yakovypg Date: Wed, 10 Jun 2026 20:17:04 +0300 Subject: [PATCH 4/5] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f2715e0..d232fef 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

## About -**NetArgumentParser** is a cross-platform, free and open source library for parsing command-line options, arguments and subcommands. This library contains the main features of popular argument parsers such as `argparse`, as well as many of its own. +**NetArgumentParser** is a cross-platform, free, open source library for parsing command-line options, arguments and subcommands. This library contains the main features of popular argument parsers such as `argparse`, as well as many of its own. **NetArgumentParser** supports many frameworks, so you can use it in most of your projects. Moreover, you can find clear examples of using this library [here](Examples). @@ -23,8 +23,8 @@ ## Table of contents * [Main Features](#main-features) * [Quick Start](#quick-start) - * [Build Project](#build-project) - * [Test Project](#test-project) + * [Build From Source](#build-from-source) + * [Run Tests](#run-tests) * [Connect Project](#connect-project) * [Documentation](#documentation) * [Development](#development) @@ -49,15 +49,15 @@ Many other features with examples you can find in [documentation](#documentation ## Quick Start To start working with the library you need to [connect](#connect-project) it to your project. If you are going to connect a library cloned from a repository, you may want to [build](#build-project) and [test](#test-project) it before doing so. -### Build Project -You can [build](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) the library with the following command, which should be run from the root of the library project. -``` +### Build From Source +To [build](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) the library, run the following command from the project root. +```bash dotnet build ``` -### Test Project -You can [test](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test) the library with the following command, which should be run from the root of the library project. -``` +### Run Tests +To [test](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test) the library, run the following command from the project root. +```bash dotnet test ``` From 9fb36990d8ff7dadaf23af46250e899d7641ce2b Mon Sep 17 00:00:00 2001 From: yakovypg Date: Wed, 10 Jun 2026 20:17:13 +0300 Subject: [PATCH 5/5] Update documentation --- Documentation/ConnectProject.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/ConnectProject.md b/Documentation/ConnectProject.md index 1359825..8bafbd6 100644 --- a/Documentation/ConnectProject.md +++ b/Documentation/ConnectProject.md @@ -24,7 +24,7 @@ Let's consider this step-by-step instructions for creating a sample project and ``` cd ~/Repos ``` -- Step 2: Create folder for your project and go to it. +- Step 2: Create folder for your project and move to it. ``` mkdir MyProject && cd MyProject ``` @@ -48,7 +48,7 @@ mkdir Vendor && cd Vendor ``` git clone https://github.com/yakovypg/NetArgumentParser.git ``` -- Step 8: Go back to the root folder. +- Step 8: Move back to the root folder. ``` cd .. ``` @@ -56,7 +56,7 @@ cd .. ``` dotnet sln add Vendor/NetArgumentParser/Core/NetArgumentParser ``` -- Step 10: Go to your project folder. +- Step 10: Move to your project folder. ``` cd MyProject ``` @@ -91,4 +91,4 @@ dotnet build -c Release - Step 14: Run the created application. ``` dotnet run --angle 45 A -``` \ No newline at end of file +```