Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

### Fixes
* [@claude]: Validate dotnet path exists before returning from `TryFindDotNetExePath` ([#600])
* [@claude]: Fix nullable type lookup bypassing custom value parsers registered via `AddOrReplace` ([#559])

[#559]: https://github.com/natemcmaster/CommandLineUtils/issues/559
[#560]: https://github.com/natemcmaster/CommandLineUtils/pull/560
[#600]: https://github.com/natemcmaster/CommandLineUtils/issues/600

Expand Down
10 changes: 5 additions & 5 deletions src/CommandLineUtils/Abstractions/ValueParserProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ public IValueParser GetParser(Type type)
return EnumParser.Create(type);
}

if (_defaultValueParserFactory.TryGetParser<T>(out parser))
{
return parser;
}

if (ReflectionHelper.IsNullableType(type, out var wrappedType))
{
if (wrappedType.IsEnum)
Expand All @@ -118,6 +113,11 @@ public IValueParser GetParser(Type type)
}
}

if (_defaultValueParserFactory.TryGetParser<T>(out parser))
{
return parser;
}

if (ReflectionHelper.IsSpecialValueTupleType(type, out var wrappedType2))
{
var innerParser = GetParser(wrappedType2);
Expand Down
1 change: 1 addition & 0 deletions src/CommandLineUtils/releasenotes.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Features:

Fixes:
* @claude: Validate dotnet path exists before returning from TryFindDotNetExePath (#600)
* @claude: Fix nullable type lookup bypassing custom value parsers registered via AddOrReplace (#559)
</PackageReleaseNotes>
<PackageReleaseNotes Condition="$(VersionPrefix.StartsWith('5.0.'))">
Changes since 4.1:
Expand Down
66 changes: 66 additions & 0 deletions test/CommandLineUtils.Tests/ValueParserProviderCustomTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,71 @@ public void AddOrReplaceThrowsIfNullparser()

Assert.Contains("parser", ex.Message);
}

private class CustomTimeSpanParser : IValueParser<TimeSpan>
{
public Type TargetType => typeof(TimeSpan);

public TimeSpan Parse(string? argName, string? value, CultureInfo culture)
{
if (value != null && value.EndsWith("s"))
{
var seconds = int.Parse(value.Substring(0, value.Length - 1), culture);
return TimeSpan.FromSeconds(seconds);
}

return TimeSpan.Parse(value!, culture);
}

object? IValueParser.Parse(string? argName, string? value, CultureInfo culture)
=> Parse(argName, value, culture);
}

private class NullableTimeSpanOptionProgram
{
[Option("--timeout", CommandOptionType.SingleValue)]
public TimeSpan? Timeout { get; set; }
}

private class NonNullableTimeSpanOptionProgram
{
[Option("--timeout", CommandOptionType.SingleValue)]
public TimeSpan Timeout { get; set; }
}

[Fact]
public void CustomParserWorksForNullableBuiltInType()
{
var app = new CommandLineApplication<NullableTimeSpanOptionProgram>();
app.ValueParsers.AddOrReplace(new CustomTimeSpanParser());
app.Conventions.UseDefaultConventions();

app.Parse("--timeout", "15s");

Assert.Equal(TimeSpan.FromSeconds(15), app.Model.Timeout);
}

[Fact]
public void CustomParserWorksForNonNullableBuiltInType()
{
var app = new CommandLineApplication<NonNullableTimeSpanOptionProgram>();
app.ValueParsers.AddOrReplace(new CustomTimeSpanParser());
app.Conventions.UseDefaultConventions();

app.Parse("--timeout", "15s");

Assert.Equal(TimeSpan.FromSeconds(15), app.Model.Timeout);
}

[Fact]
public void NullableBuiltInTypeUsesDefaultParserWhenNoCustomParser()
{
var app = new CommandLineApplication<NullableTimeSpanOptionProgram>();
app.Conventions.UseDefaultConventions();

app.Parse("--timeout", "00:00:15");

Assert.Equal(TimeSpan.FromSeconds(15), app.Model.Timeout);
}
}
}
Loading