Open
Description
The following tests fail in my nl_NL
locale:
- Values_can_be_correctly_converted_to_decimal_without_the_parser_specifying_a_custom_converter
- Values_can_be_correctly_converted_to_double_without_the_parser_specifying_a_custom_converter
- Values_can_be_correctly_converted_to_float_without_the_parser_specifying_a_custom_converter
- Values_can_be_correctly_converted_to_nullable_decimal_without_the_parser_specifying_a_custom_converter
- Values_can_be_correctly_converted_to_nullable_double_without_the_parser_specifying_a_custom_converter
- Values_can_be_correctly_converted_to_nullable_float_without_the_parser_specifying_a_custom_converter
This is because in Europe we write 123.456,78
as opposed to the US 123,456.78
format.
I have forked the project and made an attempt to fix this by accepting an IFormatProvider
in the TryConvertString
delegate; I'm quite sure this is the "correct" way to go (or at least something similar to this), but that turned out to be a bit more of a rabbithole than I currently have time for.
Here's my proposed change in `ArgumentConverter.StringConverters.cs`
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using System.IO;
namespace System.CommandLine.Binding;
internal static partial class ArgumentConverter
{
private delegate bool TryConvertString(string token, IFormatProvider formatProvider, out object? value);
private static readonly Dictionary<Type, TryConvertString> _stringConverters = new()
{
[typeof(bool)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (bool.TryParse(token, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(DateTime)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (DateTime.TryParse(input, formatProvider, DateTimeStyles.AssumeLocal, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(DateTimeOffset)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (DateTimeOffset.TryParse(input, formatProvider, DateTimeStyles.AssumeLocal, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(decimal)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (decimal.TryParse(input, NumberStyles.Float, formatProvider, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(DirectoryInfo)] = (string path, IFormatProvider formatProvider, out object? value) =>
{
if (string.IsNullOrEmpty(path))
{
value = default;
return false;
}
value = new DirectoryInfo(path);
return true;
},
[typeof(double)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (double.TryParse(input, NumberStyles.Float, formatProvider, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(FileInfo)] = (string path, IFormatProvider formatProvider, out object? value) =>
{
if (string.IsNullOrEmpty(path))
{
value = default;
return false;
}
value = new FileInfo(path);
return true;
},
[typeof(FileSystemInfo)] = (string path, IFormatProvider formatProvider, out object? value) =>
{
if (string.IsNullOrEmpty(path))
{
value = default;
return false;
}
if (Directory.Exists(path))
{
value = new DirectoryInfo(path);
}
else if (path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) ||
path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
{
value = new DirectoryInfo(path);
}
else
{
value = new FileInfo(path);
}
return true;
},
[typeof(float)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (float.TryParse(input, NumberStyles.Float, formatProvider, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(Guid)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (Guid.TryParse(input, out var parsed))
{
value = parsed;
return true;
}
value = default;
return false;
},
[typeof(int)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (int.TryParse(token, NumberStyles.Integer, formatProvider, out var intValue))
{
value = intValue;
return true;
}
value = default;
return false;
},
[typeof(long)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (long.TryParse(token, NumberStyles.Integer, formatProvider, out var longValue))
{
value = longValue;
return true;
}
value = default;
return false;
},
[typeof(short)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (short.TryParse(token, NumberStyles.Integer, formatProvider, out var shortValue))
{
value = shortValue;
return true;
}
value = default;
return false;
},
[typeof(uint)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (uint.TryParse(token, NumberStyles.Integer, formatProvider, out var uintValue))
{
value = uintValue;
return true;
}
value = default;
return false;
},
[typeof(sbyte)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (sbyte.TryParse(token, NumberStyles.Integer, formatProvider, out var sbyteValue))
{
value = sbyteValue;
return true;
}
value = default;
return false;
},
[typeof(byte)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (byte.TryParse(token, NumberStyles.Integer, formatProvider, out var byteValue))
{
value = byteValue;
return true;
}
value = default;
return false;
},
[typeof(string)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
value = input;
return true;
},
[typeof(ulong)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (ulong.TryParse(token, NumberStyles.Integer, formatProvider, out var ulongValue))
{
value = ulongValue;
return true;
}
value = default;
return false;
},
[typeof(ushort)] = (string token, IFormatProvider formatProvider, out object? value) =>
{
if (ushort.TryParse(token, NumberStyles.Integer, formatProvider, out var ushortValue))
{
value = ushortValue;
return true;
}
value = default;
return false;
},
[typeof(Uri)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (Uri.TryCreate(input, UriKind.RelativeOrAbsolute, out var uri))
{
value = uri;
return true;
}
value = default;
return false;
},
[typeof(TimeSpan)] = (string input, IFormatProvider formatProvider, out object? value) =>
{
if (TimeSpan.TryParse(input, formatProvider, out var timeSpan))
{
value = timeSpan;
return true;
}
value = default;
return false;
},
};
}
I think the above can serve as a startingpoint.