Skip to content

Commit 3289d65

Browse files
committed
bind FileSystemInfo to FileInfo or DirectoryInfo
1 parent bdeb64f commit 3289d65

File tree

4 files changed

+168
-22
lines changed

4 files changed

+168
-22
lines changed
Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using System.Collections.Generic;
5-
64
namespace System.CommandLine.Tests.Binding
75
{
86
public class BindingTestCase
97
{
108
private readonly Action<object> _assertBoundValue;
11-
12-
public BindingTestCase(
9+
10+
private BindingTestCase(
1311
string commandLine,
1412
Type parameterType,
15-
Action<object> assertBoundValue)
13+
Action<object> assertBoundValue,
14+
string variationName)
1615
{
1716
_assertBoundValue = assertBoundValue;
17+
VariationName = variationName;
1818
CommandLine = commandLine;
1919
ParameterType = parameterType;
2020
}
@@ -23,23 +23,21 @@ public BindingTestCase(
2323

2424
public Type ParameterType { get; }
2525

26+
public string VariationName { get; }
27+
2628
public void AssertBoundValue(object value)
2729
{
2830
_assertBoundValue(value);
2931
}
3032

3133
public static BindingTestCase Create<T>(
3234
string commandLine,
33-
Action<T> assertBoundValue) =>
35+
Action<T> assertBoundValue,
36+
string variationName = null) =>
3437
new BindingTestCase(
3538
commandLine,
3639
typeof(T),
37-
o => assertBoundValue((T)o)
38-
);
39-
}
40-
41-
public class BindingTestSet : Dictionary<Type, BindingTestCase>
42-
{
43-
public void Add(BindingTestCase testCase) => Add(testCase.ParameterType, testCase);
40+
o => assertBoundValue((T) o),
41+
variationName);
4442
}
45-
}
43+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
6+
namespace System.CommandLine.Tests.Binding
7+
{
8+
public class BindingTestSet : Dictionary<(Type type, string variationName), BindingTestCase>
9+
{
10+
public void Add(BindingTestCase testCase)
11+
{
12+
Add((testCase.ParameterType, testCase.VariationName), testCase);
13+
}
14+
15+
public BindingTestCase this[Type type] => base[(type, null)];
16+
}
17+
}

src/System.CommandLine.Tests/Binding/ModelBindingCommandHandlerTests.cs

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Reflection;
1111
using System.Threading.Tasks;
1212
using FluentAssertions;
13+
using FluentAssertions.Execution;
1314
using Xunit;
1415

1516
namespace System.CommandLine.Tests.Binding
@@ -260,10 +261,22 @@ public async Task When_argument_type_is_not_known_until_binding_then_int_paramet
260261
[InlineData(typeof(ClassWithCtorParameter<string>), true)]
261262
[InlineData(typeof(ClassWithSetter<string>), false)]
262263
[InlineData(typeof(ClassWithSetter<string>), true)]
264+
263265
[InlineData(typeof(FileInfo), false)]
264266
[InlineData(typeof(FileInfo), true)]
265267
[InlineData(typeof(FileInfo[]), false)]
266268
[InlineData(typeof(FileInfo[]), true)]
269+
270+
[InlineData(typeof(DirectoryInfo), false)]
271+
[InlineData(typeof(DirectoryInfo), true)]
272+
[InlineData(typeof(DirectoryInfo[]), false)]
273+
[InlineData(typeof(DirectoryInfo[]), true)]
274+
275+
[InlineData(typeof(FileSystemInfo), true, nameof(ExistingFile))]
276+
[InlineData(typeof(FileSystemInfo), true, nameof(ExistingDirectory))]
277+
[InlineData(typeof(FileSystemInfo), true, nameof(NonexistentPathWithTrailingSlash))]
278+
[InlineData(typeof(FileSystemInfo), true, nameof(NonexistentPathWithoutTrailingSlash))]
279+
267280
[InlineData(typeof(string[]), false)]
268281
[InlineData(typeof(string[]), true)]
269282
[InlineData(typeof(List<string>), false)]
@@ -274,9 +287,10 @@ public async Task When_argument_type_is_not_known_until_binding_then_int_paramet
274287
[InlineData(typeof(List<int>), true)]
275288
public async Task Handler_method_receives_option_arguments_bound_to_the_specified_type(
276289
Type type,
277-
bool useDelegate)
290+
bool useDelegate,
291+
string variation = null)
278292
{
279-
var testCase = _bindingCases[type];
293+
var testCase = _bindingCases[(type, variation)];
280294

281295
ICommandHandler handler;
282296
if (!useDelegate)
@@ -318,7 +332,7 @@ public async Task Handler_method_receives_option_arguments_bound_to_the_specifie
318332

319333
var boundValue = ((BoundValueCapturer)invocationContext.InvocationResult).BoundValue;
320334

321-
boundValue.Should().BeOfType(testCase.ParameterType);
335+
boundValue.Should().BeAssignableTo(testCase.ParameterType);
322336

323337
testCase.AssertBoundValue(boundValue);
324338
}
@@ -443,14 +457,82 @@ public void Apply(InvocationContext context)
443457
o => o.Value.Should().Be("123")),
444458

445459
BindingTestCase.Create<FileInfo>(
446-
Path.Combine(Directory.GetCurrentDirectory(), "file1.txt"),
447-
o => o.FullName.Should().Be(Path.Combine(Directory.GetCurrentDirectory(), "file1.txt"))),
460+
Path.Combine(ExistingDirectory(), "file1.txt"),
461+
o => o.FullName
462+
.Should()
463+
.Be(Path.Combine(ExistingDirectory(), "file1.txt"))),
448464

449465
BindingTestCase.Create<FileInfo[]>(
450-
$"{Path.Combine(Directory.GetCurrentDirectory(), "file1.txt")} {Path.Combine(Directory.GetCurrentDirectory(), "file2.txt")}",
466+
$"{Path.Combine(ExistingDirectory(), "file1.txt")} {Path.Combine(ExistingDirectory(), "file2.txt")}",
451467
o => o.Select(f => f.FullName)
452468
.Should()
453-
.BeEquivalentTo(new[] { Path.Combine(Directory.GetCurrentDirectory(), "file1.txt"), Path.Combine(Directory.GetCurrentDirectory(), "file2.txt") })),
469+
.BeEquivalentTo(new[]
470+
{
471+
Path.Combine(ExistingDirectory(), "file1.txt"),
472+
Path.Combine(ExistingDirectory(), "file2.txt")
473+
})),
474+
475+
BindingTestCase.Create<DirectoryInfo>(
476+
ExistingDirectory(),
477+
fsi => fsi.Should()
478+
.BeOfType<DirectoryInfo>()
479+
.Which
480+
.FullName
481+
.Should()
482+
.Be(ExistingDirectory())),
483+
484+
BindingTestCase.Create<DirectoryInfo[]>(
485+
$"{ExistingDirectory()} {ExistingDirectory()}",
486+
fsi => fsi.Should()
487+
.BeAssignableTo<IEnumerable<DirectoryInfo>>()
488+
.Which
489+
.Select(d => d.FullName)
490+
.Should()
491+
.BeEquivalentTo(new[]
492+
{
493+
ExistingDirectory(),
494+
ExistingDirectory()
495+
})),
496+
497+
BindingTestCase.Create<FileSystemInfo>(
498+
ExistingFile(),
499+
fsi => fsi.Should()
500+
.BeOfType<FileInfo>()
501+
.Which
502+
.FullName
503+
.Should()
504+
.Be(ExistingFile()),
505+
variationName: nameof(ExistingFile)),
506+
507+
BindingTestCase.Create<FileSystemInfo>(
508+
ExistingDirectory(),
509+
fsi => fsi.Should()
510+
.BeOfType<DirectoryInfo>()
511+
.Which
512+
.FullName
513+
.Should()
514+
.Be(ExistingDirectory()),
515+
variationName: nameof(ExistingDirectory)),
516+
517+
BindingTestCase.Create<FileSystemInfo>(
518+
NonexistentPathWithTrailingSlash(),
519+
fsi => fsi.Should()
520+
.BeOfType<DirectoryInfo>()
521+
.Which
522+
.FullName
523+
.Should()
524+
.Be(NonexistentPathWithTrailingSlash()),
525+
variationName: nameof(NonexistentPathWithTrailingSlash)),
526+
527+
BindingTestCase.Create<FileSystemInfo>(
528+
NonexistentPathWithoutTrailingSlash(),
529+
fsi => fsi.Should()
530+
.BeOfType<FileInfo>()
531+
.Which
532+
.FullName
533+
.Should()
534+
.Be(NonexistentPathWithoutTrailingSlash()),
535+
variationName: nameof(NonexistentPathWithoutTrailingSlash)),
454536

455537
BindingTestCase.Create<string[]>(
456538
"one two",
@@ -468,5 +550,21 @@ public void Apply(InvocationContext context)
468550
"1 2",
469551
o => o.Should().BeEquivalentTo(new List<int> { 1, 2 }))
470552
};
553+
554+
private static string NonexistentPathWithoutTrailingSlash()
555+
{
556+
return Path.Combine(
557+
ExistingDirectory(),
558+
"does-not-exist");
559+
}
560+
561+
private static string NonexistentPathWithTrailingSlash() =>
562+
NonexistentPathWithoutTrailingSlash() + Path.DirectorySeparatorChar;
563+
564+
private static string ExistingFile() =>
565+
Directory.GetFiles(ExistingDirectory()).FirstOrDefault() ??
566+
throw new AssertionFailedException("No files found in current directory");
567+
568+
private static string ExistingDirectory() => Directory.GetCurrentDirectory();
471569
}
472570
}

src/System.CommandLine/Binding/ArgumentConverter.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.CommandLine.Parsing;
77
using System.ComponentModel;
8+
using System.IO;
89
using System.Linq;
910
using System.Reflection;
1011
using static System.CommandLine.Binding.ArgumentConversionResult;
@@ -13,6 +14,24 @@ namespace System.CommandLine.Binding
1314
{
1415
internal static class ArgumentConverter
1516
{
17+
private static readonly Dictionary<Type, Func<string, object>> _converters = new Dictionary<Type, Func<string, object>>
18+
{
19+
[typeof(FileSystemInfo)] = value =>
20+
{
21+
if (Directory.Exists(value))
22+
{
23+
return new DirectoryInfo(value);
24+
}
25+
26+
if (value.EndsWith(Path.DirectorySeparatorChar.ToString()))
27+
{
28+
return new DirectoryInfo(value);
29+
}
30+
31+
return new FileInfo(value);
32+
}
33+
};
34+
1635
internal static ArgumentConversionResult ConvertObject(
1736
IArgument argument,
1837
Type type,
@@ -54,7 +73,7 @@ private static ArgumentConversionResult ConvertString(
5473
{
5574
type ??= typeof(string);
5675

57-
if (TypeDescriptor.GetConverter(type) is TypeConverter typeConverter)
76+
if (TypeDescriptor.GetConverter(type) is { } typeConverter)
5877
{
5978
if (typeConverter.CanConvertFrom(typeof(string)))
6079
{
@@ -71,6 +90,20 @@ private static ArgumentConversionResult ConvertString(
7190
}
7291
}
7392

93+
if (_converters.TryGetValue(type, out var convert))
94+
{
95+
try
96+
{
97+
return Success(
98+
argument,
99+
convert(value));
100+
}
101+
catch (Exception)
102+
{
103+
return Failure(argument, type, value);
104+
}
105+
}
106+
74107
if (type.TryFindConstructorWithSingleParameterOfType(
75108
typeof(string), out (ConstructorInfo ctor, ParameterDescriptor parameterDescriptor) tuple))
76109
{

0 commit comments

Comments
 (0)