mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d9c7e942c | ||
|
|
0f3abb9db4 | ||
|
|
896482821c | ||
|
|
aa3094ee54 | ||
|
|
712580e3d7 | ||
|
|
c08102f85f | ||
|
|
5e684c8b36 | ||
|
|
300ae70564 | ||
|
|
76f0c77f1e | ||
|
|
0f7cea4ed1 | ||
|
|
32ee0b2bd6 | ||
|
|
4ff1e1d3e1 | ||
|
|
8e96d2701d | ||
|
|
8e307df231 | ||
|
|
ff38f4916a | ||
|
|
7cbbb220b4 | ||
|
|
ae2d4299f0 | ||
|
|
21bc69d116 |
14
Changelog.md
14
Changelog.md
@@ -1,3 +1,17 @@
|
|||||||
|
### v2.2.2 (30-Jan-2022)
|
||||||
|
|
||||||
|
- Fixed an issue where `ConsoleWriter` and `ConsoleReader` were not properly thread-safe.
|
||||||
|
- Fixed an issue where the analyzer failed to load under certain circumstances when running inside Visual Studio.
|
||||||
|
|
||||||
|
### v2.2.1 (16-Jan-2022)
|
||||||
|
|
||||||
|
- Fixed an issue which caused help text to not show default values for optional parameters. (Thanks [@AliReZa Sabouri](https://github.com/alirezanet))
|
||||||
|
|
||||||
|
### v2.2 (11-Jan-2022)
|
||||||
|
|
||||||
|
- Added support for optional parameters. A parameter can be marked as optional by setting `IsRequired = false` on the attribute. Only one parameter is allowed to be optional and such parameter must be the last in order. (Thanks [@AliReZa Sabouri](https://github.com/alirezanet))
|
||||||
|
- Fixed an issue where parameters and options bound to properties implemented as default interface members were not working correctly. (Thanks [@AliReZa Sabouri](https://github.com/alirezanet))
|
||||||
|
|
||||||
### v2.1 (04-Jan-2022)
|
### v2.1 (04-Jan-2022)
|
||||||
|
|
||||||
- Added `IConsole.Clear()` with corresponding implementations in `SystemConsole`, `FakeConsole`, and `FakeInMemoryConsole`. (Thanks [@Alex Rosenfeld](https://github.com/alexrosenfeld10))
|
- Added `IConsole.Clear()` with corresponding implementations in `SystemConsole`, `FakeConsole`, and `FakeInMemoryConsole`. (Thanks [@Alex Rosenfeld](https://github.com/alexrosenfeld10))
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<IsTestProject>true</IsTestProject>
|
|
||||||
<CollectCoverage>true</CollectCoverage>
|
<CollectCoverage>true</CollectCoverage>
|
||||||
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -15,7 +13,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
|
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
||||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
<PackageReference Include="FluentAssertions" Version="6.3.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(null)]
|
[CommandOption(null)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"")]
|
[CommandOption(""bar"")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('F')]
|
[CommandOption('F')]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", Converter = typeof(MyConverter))]
|
[CommandOption(""foo"", Converter = typeof(MyConverter))]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", Converter = typeof(MyConverter))]
|
[CommandOption(""foo"", Converter = typeof(MyConverter))]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""f"")]
|
[CommandOption(""f"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""1foo"")]
|
[CommandOption(""1foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('1')]
|
[CommandOption('1')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", Validators = new[] {typeof(MyValidator)})]
|
[CommandOption(""foo"", Validators = new[] {typeof(MyValidator)})]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", Validators = new[] {typeof(MyValidator)})]
|
[CommandOption(""foo"", Validators = new[] {typeof(MyValidator)})]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using CliFx.Analyzers.Tests.Utils;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Analyzers.Tests;
|
||||||
|
|
||||||
|
public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs
|
||||||
|
{
|
||||||
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonRequiredAnalyzer();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_reports_an_error_if_a_non_required_parameter_is_not_the_last_in_order()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, Name = ""foo"", IsRequired = false)]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, Name = ""bar"")]
|
||||||
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_does_not_report_an_error_if_a_non_required_parameter_is_the_last_in_order()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, Name = ""foo"")]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, Name = ""bar"", IsRequired = false)]
|
||||||
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_does_not_report_an_error_if_no_non_required_parameters_are_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, Name = ""foo"")]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, Name = ""bar"", IsRequired = true)]
|
||||||
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
|
|||||||
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer();
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order()
|
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_the_last_in_order()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string[] Foo { get; set; }
|
public string[] Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ public class MyCommand : ICommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_last_in_order()
|
public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_the_last_in_order()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
// language=cs
|
// language=cs
|
||||||
@@ -41,7 +41,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string[] Bar { get; set; }
|
public string[] Bar { get; set; }
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using CliFx.Analyzers.Tests.Utils;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Analyzers.Tests;
|
||||||
|
|
||||||
|
public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs
|
||||||
|
{
|
||||||
|
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonRequiredAnalyzer();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_reports_an_error_if_more_than_one_non_required_parameters_are_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, IsRequired = false)]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, IsRequired = false)]
|
||||||
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().ProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_does_not_report_an_error_if_only_one_non_required_parameter_is_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0)]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, IsRequired = false)]
|
||||||
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_does_not_report_an_error_if_no_non_required_parameters_are_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0)]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, IsRequired = true)]
|
||||||
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// language=cs
|
||||||
|
const string code = @"
|
||||||
|
[Command]
|
||||||
|
public class MyCommand : ICommand
|
||||||
|
{
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Analyzer.Should().NotProduceDiagnostics(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string[] Foo { get; set; }
|
public string[] Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string[] Bar { get; set; }
|
public string[] Bar { get; set; }
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string[] Bar { get; set; }
|
public string[] Bar { get; set; }
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Name = ""foo"")]
|
[CommandParameter(0, Name = ""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1, Name = ""foo"")]
|
[CommandParameter(1, Name = ""foo"")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Name = ""foo"")]
|
[CommandParameter(0, Name = ""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1, Name = ""bar"")]
|
[CommandParameter(1, Name = ""bar"")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Converter = typeof(MyConverter))]
|
[CommandParameter(0, Converter = typeof(MyConverter))]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Converter = typeof(MyConverter))]
|
[CommandParameter(0, Converter = typeof(MyConverter))]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Validators = new[] {typeof(MyValidator)})]
|
[CommandParameter(0, Validators = new[] {typeof(MyValidator)})]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Validators = new[] {typeof(MyValidator)})]
|
[CommandParameter(0, Validators = new[] {typeof(MyValidator)})]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public class MyCommand : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}";
|
}";
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<Nullable>annotations</Nullable>
|
<Nullable>annotations</Nullable>
|
||||||
<NoWarn>$(NoWarn);RS1025;RS1026</NoWarn>
|
<NoWarn>$(NoWarn);RS1025;RS1026</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -71,10 +71,9 @@ internal partial class CommandOptionSymbol
|
|||||||
{
|
{
|
||||||
var attribute = TryGetOptionAttribute(property);
|
var attribute = TryGetOptionAttribute(property);
|
||||||
|
|
||||||
if (attribute is null)
|
return attribute is not null
|
||||||
return null;
|
? FromAttribute(attribute)
|
||||||
|
: null;
|
||||||
return FromAttribute(attribute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsOptionProperty(IPropertySymbol property) =>
|
public static bool IsOptionProperty(IPropertySymbol property) =>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ internal partial class CommandParameterSymbol
|
|||||||
|
|
||||||
public string? Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
|
public bool? IsRequired { get; }
|
||||||
|
|
||||||
public ITypeSymbol? ConverterType { get; }
|
public ITypeSymbol? ConverterType { get; }
|
||||||
|
|
||||||
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
|
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
|
||||||
@@ -18,11 +20,13 @@ internal partial class CommandParameterSymbol
|
|||||||
public CommandParameterSymbol(
|
public CommandParameterSymbol(
|
||||||
int order,
|
int order,
|
||||||
string? name,
|
string? name,
|
||||||
|
bool? isRequired,
|
||||||
ITypeSymbol? converterType,
|
ITypeSymbol? converterType,
|
||||||
IReadOnlyList<ITypeSymbol> validatorTypes)
|
IReadOnlyList<ITypeSymbol> validatorTypes)
|
||||||
{
|
{
|
||||||
Order = order;
|
Order = order;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
IsRequired = isRequired;
|
||||||
ConverterType = converterType;
|
ConverterType = converterType;
|
||||||
ValidatorTypes = validatorTypes;
|
ValidatorTypes = validatorTypes;
|
||||||
}
|
}
|
||||||
@@ -37,7 +41,7 @@ internal partial class CommandParameterSymbol
|
|||||||
|
|
||||||
private static CommandParameterSymbol FromAttribute(AttributeData attribute)
|
private static CommandParameterSymbol FromAttribute(AttributeData attribute)
|
||||||
{
|
{
|
||||||
var order = (int) attribute
|
var order = (int)attribute
|
||||||
.ConstructorArguments
|
.ConstructorArguments
|
||||||
.Select(a => a.Value)
|
.Select(a => a.Value)
|
||||||
.First()!;
|
.First()!;
|
||||||
@@ -48,6 +52,12 @@ internal partial class CommandParameterSymbol
|
|||||||
.Select(a => a.Value.Value)
|
.Select(a => a.Value.Value)
|
||||||
.FirstOrDefault() as string;
|
.FirstOrDefault() as string;
|
||||||
|
|
||||||
|
var isRequired = attribute
|
||||||
|
.NamedArguments
|
||||||
|
.Where(a => a.Key == "IsRequired")
|
||||||
|
.Select(a => a.Value.Value)
|
||||||
|
.FirstOrDefault() as bool?;
|
||||||
|
|
||||||
var converter = attribute
|
var converter = attribute
|
||||||
.NamedArguments
|
.NamedArguments
|
||||||
.Where(a => a.Key == "Converter")
|
.Where(a => a.Key == "Converter")
|
||||||
@@ -63,17 +73,16 @@ internal partial class CommandParameterSymbol
|
|||||||
.Cast<ITypeSymbol>()
|
.Cast<ITypeSymbol>()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new CommandParameterSymbol(order, name, converter, validators);
|
return new CommandParameterSymbol(order, name, isRequired, converter, validators);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
|
public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
|
||||||
{
|
{
|
||||||
var attribute = TryGetParameterAttribute(property);
|
var attribute = TryGetParameterAttribute(property);
|
||||||
|
|
||||||
if (attribute is null)
|
return attribute is not null
|
||||||
return null;
|
? FromAttribute(attribute)
|
||||||
|
: null;
|
||||||
return FromAttribute(attribute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsParameterProperty(IPropertySymbol property) =>
|
public static bool IsParameterProperty(IPropertySymbol property) =>
|
||||||
|
|||||||
60
CliFx.Analyzers/ParameterMustBeLastIfNonRequiredAnalyzer.cs
Normal file
60
CliFx.Analyzers/ParameterMustBeLastIfNonRequiredAnalyzer.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using CliFx.Analyzers.ObjectModel;
|
||||||
|
using CliFx.Analyzers.Utils.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustBeLastIfNonRequiredAnalyzer : AnalyzerBase
|
||||||
|
{
|
||||||
|
public ParameterMustBeLastIfNonRequiredAnalyzer()
|
||||||
|
: base(
|
||||||
|
"Parameters marked as non-required must be the last in order",
|
||||||
|
"This parameter is non-required so it must be the last in order (its order must be highest within the command).")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Analyze(
|
||||||
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
|
if (parameter is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parameter.IsRequired != false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
|
{
|
||||||
|
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherParameter is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (otherParameter.Order > parameter.Order)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,8 @@ public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
|
|||||||
{
|
{
|
||||||
public ParameterMustBeLastIfNonScalarAnalyzer()
|
public ParameterMustBeLastIfNonScalarAnalyzer()
|
||||||
: base(
|
: base(
|
||||||
"Parameters of non-scalar types must be last in order",
|
"Parameters of non-scalar types must be the last in order",
|
||||||
"This parameter has a non-scalar type so it must be last in order (its order must be highest within the command).")
|
"This parameter has a non-scalar type so it must be the last in order (its order must be highest within the command).")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using CliFx.Analyzers.ObjectModel;
|
||||||
|
using CliFx.Analyzers.Utils.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
|
||||||
|
namespace CliFx.Analyzers;
|
||||||
|
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public class ParameterMustBeSingleIfNonRequiredAnalyzer : AnalyzerBase
|
||||||
|
{
|
||||||
|
public ParameterMustBeSingleIfNonRequiredAnalyzer()
|
||||||
|
: base(
|
||||||
|
"Parameters marked as non-required are limited to one per command",
|
||||||
|
"This parameter is non-required so it must be the only such parameter in the command.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Analyze(
|
||||||
|
SyntaxNodeAnalysisContext context,
|
||||||
|
PropertyDeclarationSyntax propertyDeclaration,
|
||||||
|
IPropertySymbol property)
|
||||||
|
{
|
||||||
|
if (property.ContainingType is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parameter = CommandParameterSymbol.TryResolve(property);
|
||||||
|
if (parameter is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parameter.IsRequired != false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var otherProperties = property
|
||||||
|
.ContainingType
|
||||||
|
.GetMembers()
|
||||||
|
.OfType<IPropertySymbol>()
|
||||||
|
.Where(m => !m.Equals(property, SymbolEqualityComparer.Default))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var otherProperty in otherProperties)
|
||||||
|
{
|
||||||
|
var otherParameter = CommandParameterSymbol.TryResolve(otherProperty);
|
||||||
|
if (otherParameter is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (otherParameter.IsRequired == false)
|
||||||
|
{
|
||||||
|
context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
base.Initialize(context);
|
||||||
|
context.HandlePropertyDeclaration(Analyze);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<PackageReference Include="clipr" Version="1.6.1" />
|
<PackageReference Include="clipr" Version="1.6.1" />
|
||||||
<PackageReference Include="Cocona" Version="1.6.0" />
|
<PackageReference Include="Cocona" Version="1.6.0" />
|
||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.1.0" />
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.0" />
|
||||||
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<IsTestProject>true</IsTestProject>
|
|
||||||
<CollectCoverage>true</CollectCoverage>
|
<CollectCoverage>true</CollectCoverage>
|
||||||
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -14,8 +12,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
|
<PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" />
|
||||||
<PackageReference Include="CliWrap" Version="3.3.3" />
|
<PackageReference Include="CliWrap" Version="3.4.0" />
|
||||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
<PackageReference Include="FluentAssertions" Version="6.3.0" />
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public bool Foo { get; set; }
|
public bool Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public bool Bar { get; set; }
|
public bool Bar { get; set; }
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public int? Foo { get; set; }
|
public int? Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public int? Bar { get; set; }
|
public int? Bar { get; set; }
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public CustomEnum? Foo { get; set; }
|
public CustomEnum? Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public CustomEnum? Bar { get; set; }
|
public CustomEnum? Bar { get; set; }
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ public class Command : ICommand
|
|||||||
public class CustomType
|
public class CustomType
|
||||||
{
|
{
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
public CustomType(string value) => Value = value;
|
public CustomType(string value) => Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +480,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public CustomType Foo { get; set; }
|
public CustomType Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo.Value);
|
console.Output.WriteLine(Foo.Value);
|
||||||
@@ -516,9 +516,9 @@ public class Command : ICommand
|
|||||||
public class CustomTypeA
|
public class CustomTypeA
|
||||||
{
|
{
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
private CustomTypeA(string value) => Value = value;
|
private CustomTypeA(string value) => Value = value;
|
||||||
|
|
||||||
public static CustomTypeA Parse(string value) =>
|
public static CustomTypeA Parse(string value) =>
|
||||||
new CustomTypeA(value);
|
new CustomTypeA(value);
|
||||||
}
|
}
|
||||||
@@ -526,9 +526,9 @@ public class CustomTypeA
|
|||||||
public class CustomTypeB
|
public class CustomTypeB
|
||||||
{
|
{
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
private CustomTypeB(string value) => Value = value;
|
private CustomTypeB(string value) => Value = value;
|
||||||
|
|
||||||
public static CustomTypeB Parse(string value, IFormatProvider formatProvider) =>
|
public static CustomTypeB Parse(string value, IFormatProvider formatProvider) =>
|
||||||
new CustomTypeB(value);
|
new CustomTypeB(value);
|
||||||
}
|
}
|
||||||
@@ -538,10 +538,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public CustomTypeA Foo { get; set; }
|
public CustomTypeA Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public CustomTypeB Bar { get; set; }
|
public CustomTypeB Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo.Value);
|
console.Output.WriteLine(""Foo = "" + Foo.Value);
|
||||||
@@ -590,7 +590,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f', Converter = typeof(CustomConverter))]
|
[CommandOption('f', Converter = typeof(CustomConverter))]
|
||||||
public int Foo { get; set; }
|
public int Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -628,7 +628,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string[] Foo { get; set; }
|
public string[] Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -672,7 +672,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -716,7 +716,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public List<string> Foo { get; set; }
|
public List<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -760,7 +760,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public int[] Foo { get; set; }
|
public int[] Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -804,7 +804,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public int Foo { get; set; }
|
public int Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -840,7 +840,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public CustomType Foo { get; set; }
|
public CustomType Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -872,7 +872,7 @@ public class Command : ICommand
|
|||||||
public class CustomType : IEnumerable<object>
|
public class CustomType : IEnumerable<object>
|
||||||
{
|
{
|
||||||
public IEnumerator<object> GetEnumerator() => Enumerable.Empty<object>().GetEnumerator();
|
public IEnumerator<object> GetEnumerator() => Enumerable.Empty<object>().GetEnumerator();
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,7 +881,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public CustomType Foo { get; set; }
|
public CustomType Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -925,7 +925,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f', Validators = new[] {typeof(ValidatorA), typeof(ValidatorB)})]
|
[CommandOption('f', Validators = new[] {typeof(ValidatorA), typeof(ValidatorB)})]
|
||||||
public int Foo { get; set; }
|
public int Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -957,9 +957,9 @@ public class Command : ICommand
|
|||||||
public class CustomType
|
public class CustomType
|
||||||
{
|
{
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
private CustomType(string value) => Value = value;
|
private CustomType(string value) => Value = value;
|
||||||
|
|
||||||
public static CustomType Parse(string value) => throw new Exception(""Hello world"");
|
public static CustomType Parse(string value) => throw new Exception(""Hello world"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,7 +968,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public CustomType Foo { get; set; }
|
public CustomType Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", IsRequired = true, EnvironmentVariable = ""ENV_FOO"")]
|
[CommandOption(""foo"", IsRequired = true, EnvironmentVariable = ""ENV_FOO"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -73,7 +73,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -115,12 +115,12 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
console.Output.WriteLine(i);
|
console.Output.WriteLine(i);
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -204,7 +204,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
|
|||||||
@@ -339,13 +339,13 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
[CommandParameter(2)]
|
[CommandParameter(2)]
|
||||||
public IReadOnlyList<string> Baz { get; set; }
|
public IReadOnlyList<string> Baz { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -396,7 +396,7 @@ public class Command : CommandBase
|
|||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
public override ValueTask ExecuteAsync(IConsole console) => default;
|
public override ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -434,13 +434,13 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", IsRequired = true)]
|
[CommandOption(""foo"", IsRequired = true)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"")]
|
[CommandOption(""bar"")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
[CommandOption(""baz"", IsRequired = true)]
|
[CommandOption(""baz"", IsRequired = true)]
|
||||||
public IReadOnlyList<string> Baz { get; set; }
|
public IReadOnlyList<string> Baz { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -478,10 +478,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0, Name = ""foo"", Description = ""Description of foo."")]
|
[CommandParameter(0, Name = ""foo"", Description = ""Description of foo."")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"", Description = ""Description of bar."")]
|
[CommandOption(""bar"", Description = ""Description of bar."")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -597,10 +597,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public CustomEnum Foo { get; set; }
|
public CustomEnum Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"")]
|
[CommandOption(""bar"")]
|
||||||
public CustomEnum Bar { get; set; }
|
public CustomEnum Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -732,10 +732,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
[CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")]
|
||||||
public CustomEnum Foo { get; set; }
|
public CustomEnum Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"", EnvironmentVariable = ""ENV_BAR"")]
|
[CommandOption(""bar"", EnvironmentVariable = ""ENV_BAR"")]
|
||||||
public CustomEnum Bar { get; set; }
|
public CustomEnum Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
@@ -794,10 +794,10 @@ public class Command : ICommand
|
|||||||
|
|
||||||
[CommandOption(""lol"")]
|
[CommandOption(""lol"")]
|
||||||
public CustomEnum Lol { get; set; } = CustomEnum.Two;
|
public CustomEnum Lol { get; set; } = CustomEnum.Two;
|
||||||
|
|
||||||
[CommandOption(""hmm"", IsRequired = true)]
|
[CommandOption(""hmm"", IsRequired = true)]
|
||||||
public string Hmm { get; set; } = ""not printed"";
|
public string Hmm { get; set; } = ""not printed"";
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
");
|
");
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public bool Foo { get; set; }
|
public bool Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -66,7 +66,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public bool Foo { get; set; }
|
public bool Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -104,10 +104,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"")]
|
[CommandOption(""bar"")]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo);
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
@@ -150,10 +150,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo);
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
@@ -196,10 +196,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption('b')]
|
[CommandOption('b')]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo);
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
@@ -242,7 +242,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""Foo"")]
|
[CommandOption(""Foo"")]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -286,7 +286,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -330,7 +330,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -374,7 +374,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -418,7 +418,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", 'f')]
|
[CommandOption(""foo"", 'f')]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
foreach (var i in Foo)
|
foreach (var i in Foo)
|
||||||
@@ -462,10 +462,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandOption(""bar"")]
|
[CommandOption(""bar"")]
|
||||||
public string Bar { get; set; } = ""hello"";
|
public string Bar { get; set; } = ""hello"";
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo);
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
@@ -496,6 +496,83 @@ public class Command : ICommand
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Option_binding_supports_multiple_inheritance_through_default_interface_members()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
|
public static class SharedContext
|
||||||
|
{
|
||||||
|
public static int Foo { get; set; }
|
||||||
|
|
||||||
|
public static bool Bar { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHasFoo : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption(""foo"")]
|
||||||
|
public int Foo
|
||||||
|
{
|
||||||
|
get => SharedContext.Foo;
|
||||||
|
set => SharedContext.Foo = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHasBar : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption(""bar"")]
|
||||||
|
public bool Bar
|
||||||
|
{
|
||||||
|
get => SharedContext.Bar;
|
||||||
|
set => SharedContext.Bar = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHasBaz : ICommand
|
||||||
|
{
|
||||||
|
public string Baz { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
public class Command : IHasFoo, IHasBar, IHasBaz
|
||||||
|
{
|
||||||
|
[CommandOption(""baz"")]
|
||||||
|
public string Baz { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
console.Output.WriteLine(""Foo = "" + SharedContext.Foo);
|
||||||
|
console.Output.WriteLine(""Bar = "" + SharedContext.Bar);
|
||||||
|
console.Output.WriteLine(""Baz = "" + Baz);
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(commandType)
|
||||||
|
.UseConsole(FakeConsole)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] { "--foo", "42", "--bar", "--baz", "xyz" }
|
||||||
|
);
|
||||||
|
|
||||||
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOut.Should().ConsistOfLines(
|
||||||
|
"Foo = 42",
|
||||||
|
"Bar = True",
|
||||||
|
"Baz = xyz"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
|
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
|
||||||
{
|
{
|
||||||
@@ -508,7 +585,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(Foo);
|
console.Output.WriteLine(Foo);
|
||||||
@@ -547,7 +624,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", IsRequired = true)]
|
[CommandOption(""foo"", IsRequired = true)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
@@ -581,7 +658,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", IsRequired = true)]
|
[CommandOption(""foo"", IsRequired = true)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
@@ -615,7 +692,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"", IsRequired = true)]
|
[CommandOption(""foo"", IsRequired = true)]
|
||||||
public IReadOnlyList<string> Foo { get; set; }
|
public IReadOnlyList<string> Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
@@ -649,7 +726,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
@@ -683,7 +760,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandOption(""foo"")]
|
[CommandOption(""foo"")]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo);
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
console.Output.WriteLine(""Bar = "" + Bar);
|
console.Output.WriteLine(""Bar = "" + Bar);
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
@@ -73,13 +73,13 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
[CommandParameter(2)]
|
[CommandParameter(2)]
|
||||||
public IReadOnlyList<string> Baz { get; set; }
|
public IReadOnlyList<string> Baz { get; set; }
|
||||||
|
|
||||||
[CommandOption(""boo"")]
|
[CommandOption(""boo"")]
|
||||||
public string Boo { get; set; }
|
public string Boo { get; set; }
|
||||||
|
|
||||||
@@ -87,10 +87,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
console.Output.WriteLine(""Foo = "" + Foo);
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
console.Output.WriteLine(""Bar = "" + Bar);
|
console.Output.WriteLine(""Bar = "" + Bar);
|
||||||
|
|
||||||
foreach (var i in Baz)
|
foreach (var i in Baz)
|
||||||
console.Output.WriteLine(""Baz = "" + i);
|
console.Output.WriteLine(""Baz = "" + i);
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
@@ -120,7 +120,7 @@ public class Command : ICommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Parameter_binding_fails_if_one_of_the_parameters_has_not_been_provided()
|
public async Task Parameter_is_not_bound_if_there_are_no_arguments_matching_its_order()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var commandType = DynamicCommandBuilder.Compile(
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
@@ -131,7 +131,53 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, IsRequired = false)]
|
||||||
|
public string Bar { get; set; } = ""xyz"";
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
console.Output.WriteLine(""Foo = "" + Foo);
|
||||||
|
console.Output.WriteLine(""Bar = "" + Bar);
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(commandType)
|
||||||
|
.UseConsole(FakeConsole)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"abc"},
|
||||||
|
new Dictionary<string, string>()
|
||||||
|
);
|
||||||
|
|
||||||
|
var stdOut = FakeConsole.ReadOutputString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOut.Should().ConsistOfLines(
|
||||||
|
"Foo = abc",
|
||||||
|
"Bar = xyz"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Parameter_binding_fails_if_a_required_parameter_has_not_been_provided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandType = DynamicCommandBuilder.Compile(
|
||||||
|
// language=cs
|
||||||
|
@"
|
||||||
|
[Command]
|
||||||
|
public class Command : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0)]
|
||||||
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
@@ -153,7 +199,7 @@ public class Command : ICommand
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing parameter(s)");
|
stdErr.Should().Contain("Missing required parameter(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -168,10 +214,10 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public IReadOnlyList<string> Bar { get; set; }
|
public IReadOnlyList<string> Bar { get; set; }
|
||||||
|
|
||||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}");
|
}");
|
||||||
|
|
||||||
@@ -190,7 +236,7 @@ public class Command : ICommand
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
exitCode.Should().NotBe(0);
|
exitCode.Should().NotBe(0);
|
||||||
stdErr.Should().Contain("Missing parameter(s)");
|
stdErr.Should().Contain("Missing required parameter(s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -205,7 +251,7 @@ public class Command : ICommand
|
|||||||
{
|
{
|
||||||
[CommandParameter(0)]
|
[CommandParameter(0)]
|
||||||
public string Foo { get; set; }
|
public string Foo { get; set; }
|
||||||
|
|
||||||
[CommandParameter(1)]
|
[CommandParameter(1)]
|
||||||
public string Bar { get; set; }
|
public string Bar { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public sealed class CommandOptionAttribute : Attribute
|
|||||||
public char? ShortName { get; }
|
public char? ShortName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this option is required.
|
/// Whether this option is required (default: <c>false</c>).
|
||||||
/// If an option is required, the user will get an error if they don't set it.
|
/// If an option is required, the user will get an error if they don't set it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRequired { get; set; }
|
public bool IsRequired { get; set; }
|
||||||
|
|||||||
@@ -11,18 +11,26 @@ public sealed class CommandParameterAttribute : Attribute
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameter order.
|
/// Parameter order.
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Higher order means the parameter appears later, lower order means
|
/// Higher order means the parameter appears later, lower order means
|
||||||
/// it appears earlier.
|
/// it appears earlier.
|
||||||
///
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
/// All parameters in a command must have unique order.
|
/// All parameters in a command must have unique order.
|
||||||
///
|
|
||||||
/// Parameter whose type is a non-scalar (e.g. array), must always be the last in order.
|
/// Parameter whose type is a non-scalar (e.g. array), must always be the last in order.
|
||||||
/// Only one non-scalar parameter is allowed in a command.
|
/// Only one non-scalar parameter is allowed in a command.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public int Order { get; }
|
public int Order { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this parameter is required (default: <c>true</c>).
|
||||||
|
/// If a parameter is required, the user will get an error if they don't set it.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Parameter marked as non-required must always be the last in order.
|
||||||
|
/// Only one non-required parameter is allowed in a command.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsRequired { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parameter name.
|
/// Parameter name.
|
||||||
/// This is shown to the user in the help text.
|
/// This is shown to the user in the help text.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFrameworks>netstandard2.1;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.1;netstandard2.0</TargetFrameworks>
|
||||||
<Authors>$(Company)</Authors>
|
<Authors>$(Company)</Authors>
|
||||||
<Description>Declarative framework for building command line applications</Description>
|
<Description>Declarative framework for building command line applications</Description>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
<PackageTags>command line executable interface framework parser arguments cli app application net core</PackageTags>
|
<PackageTags>command line executable interface framework parser arguments cli app application net core</PackageTags>
|
||||||
<PackageProjectUrl>https://github.com/Tyrrrz/CliFx</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Tyrrrz/CliFx</PackageProjectUrl>
|
||||||
<PackageReleaseNotes>https://github.com/Tyrrrz/CliFx/blob/master/Changelog.md</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/Tyrrrz/CliFx/blob/master/Changelog.md</PackageReleaseNotes>
|
||||||
@@ -34,6 +35,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../CliFx.Analyzers/CliFx.Analyzers.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
|
<ProjectReference Include="../CliFx.Analyzers/CliFx.Analyzers.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
|
||||||
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/CliFx.Analyzers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/CliFx.Analyzers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/Microsoft.CodeAnalysis.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/Microsoft.CodeAnalysis.CSharp.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Buffers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Collections.Immutable.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Memory.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Numerics.Vectors.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Reflection.Metadata.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Text.Encoding.CodePages.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
|
<None Include="../CliFx.Analyzers/bin/$(Configuration)/netstandard2.0/System.Threading.Tasks.Extensions.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -43,12 +43,6 @@ internal class CommandBinder
|
|||||||
return string.IsNullOrWhiteSpace(rawValue) || bool.Parse(rawValue);
|
return string.IsNullOrWhiteSpace(rawValue) || bool.Parse(rawValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// IConvertible primitives (int, double, char, etc)
|
|
||||||
if (targetType.IsConvertible())
|
|
||||||
{
|
|
||||||
return Convert.ChangeType(rawValue, targetType, _formatProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for DateTimeOffset
|
// Special case for DateTimeOffset
|
||||||
if (targetType == typeof(DateTimeOffset))
|
if (targetType == typeof(DateTimeOffset))
|
||||||
{
|
{
|
||||||
@@ -68,6 +62,12 @@ internal class CommandBinder
|
|||||||
return Enum.Parse(targetType, rawValue!, true);
|
return Enum.Parse(targetType, rawValue!, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convertible primitives (int, double, char, etc)
|
||||||
|
if (targetType.Implements(typeof(IConvertible)))
|
||||||
|
{
|
||||||
|
return Convert.ChangeType(rawValue, targetType, _formatProvider);
|
||||||
|
}
|
||||||
|
|
||||||
// Nullable<T>
|
// Nullable<T>
|
||||||
var nullableUnderlyingType = targetType.TryGetNullableUnderlyingType();
|
var nullableUnderlyingType = targetType.TryGetNullableUnderlyingType();
|
||||||
if (nullableUnderlyingType is not null)
|
if (nullableUnderlyingType is not null)
|
||||||
@@ -223,7 +223,7 @@ internal class CommandBinder
|
|||||||
{
|
{
|
||||||
// Ensure there are no unexpected parameters and that all parameters are provided
|
// Ensure there are no unexpected parameters and that all parameters are provided
|
||||||
var remainingParameterInputs = commandInput.Parameters.ToList();
|
var remainingParameterInputs = commandInput.Parameters.ToList();
|
||||||
var remainingParameterSchemas = commandSchema.Parameters.ToList();
|
var remainingRequiredParameterSchemas = commandSchema.Parameters.Where(p => p.IsRequired).ToList();
|
||||||
|
|
||||||
var position = 0;
|
var position = 0;
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ internal class CommandBinder
|
|||||||
remainingParameterInputs.RemoveRange(parameterInputs);
|
remainingParameterInputs.RemoveRange(parameterInputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingParameterSchemas.Remove(parameterSchema);
|
remainingRequiredParameterSchemas.Remove(parameterSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingParameterInputs.Any())
|
if (remainingParameterInputs.Any())
|
||||||
@@ -272,12 +272,12 @@ internal class CommandBinder
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingParameterSchemas.Any())
|
if (remainingRequiredParameterSchemas.Any())
|
||||||
{
|
{
|
||||||
throw CliFxException.UserError(
|
throw CliFxException.UserError(
|
||||||
"Missing parameter(s):" +
|
"Missing required parameter(s):" +
|
||||||
Environment.NewLine +
|
Environment.NewLine +
|
||||||
remainingParameterSchemas
|
remainingRequiredParameterSchemas
|
||||||
.Select(o => o.GetFormattedIdentifier())
|
.Select(o => o.GetFormattedIdentifier())
|
||||||
.JoinToString(" ")
|
.JoinToString(" ")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -157,7 +157,15 @@ internal class HelpConsoleFormatter : ConsoleFormatter
|
|||||||
|
|
||||||
foreach (var parameterSchema in _context.CommandSchema.Parameters.OrderBy(p => p.Order))
|
foreach (var parameterSchema in _context.CommandSchema.Parameters.OrderBy(p => p.Order))
|
||||||
{
|
{
|
||||||
Write(ConsoleColor.Red, "* ");
|
if (parameterSchema.IsRequired)
|
||||||
|
{
|
||||||
|
Write(ConsoleColor.Red, "* ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteHorizontalMargin();
|
||||||
|
}
|
||||||
|
|
||||||
Write(ConsoleColor.DarkCyan, $"{parameterSchema.Name}");
|
Write(ConsoleColor.DarkCyan, $"{parameterSchema.Name}");
|
||||||
|
|
||||||
WriteColumnMargin();
|
WriteColumnMargin();
|
||||||
@@ -198,6 +206,12 @@ internal class HelpConsoleFormatter : ConsoleFormatter
|
|||||||
Write('.');
|
Write('.');
|
||||||
Write(' ');
|
Write(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default value
|
||||||
|
if (!parameterSchema.IsRequired)
|
||||||
|
{
|
||||||
|
WriteDefaultValue(parameterSchema);
|
||||||
|
}
|
||||||
|
|
||||||
WriteLine();
|
WriteLine();
|
||||||
}
|
}
|
||||||
@@ -290,60 +304,65 @@ internal class HelpConsoleFormatter : ConsoleFormatter
|
|||||||
// Default value
|
// Default value
|
||||||
if (!optionSchema.IsRequired)
|
if (!optionSchema.IsRequired)
|
||||||
{
|
{
|
||||||
var defaultValue = _context.CommandDefaultValues.GetValueOrDefault(optionSchema);
|
WriteDefaultValue(optionSchema);
|
||||||
if (defaultValue is not null)
|
|
||||||
{
|
|
||||||
// Non-Scalar
|
|
||||||
if (defaultValue is not string && defaultValue is IEnumerable defaultValues)
|
|
||||||
{
|
|
||||||
var elementType =
|
|
||||||
defaultValues.GetType().TryGetEnumerableUnderlyingType() ??
|
|
||||||
typeof(object);
|
|
||||||
|
|
||||||
if (elementType.IsToStringOverriden())
|
|
||||||
{
|
|
||||||
Write(ConsoleColor.White, "Default: ");
|
|
||||||
|
|
||||||
var isFirst = true;
|
|
||||||
|
|
||||||
foreach (var element in defaultValues)
|
|
||||||
{
|
|
||||||
if (isFirst)
|
|
||||||
{
|
|
||||||
isFirst = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Write(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
Write('"');
|
|
||||||
Write(element.ToString(CultureInfo.InvariantCulture));
|
|
||||||
Write('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
Write('.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (defaultValue.GetType().IsToStringOverriden())
|
|
||||||
{
|
|
||||||
Write(ConsoleColor.White, "Default: ");
|
|
||||||
|
|
||||||
Write('"');
|
|
||||||
Write(defaultValue.ToString(CultureInfo.InvariantCulture));
|
|
||||||
Write('"');
|
|
||||||
Write('.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLine();
|
WriteLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteDefaultValue(IMemberSchema schema)
|
||||||
|
{
|
||||||
|
var defaultValue = _context.CommandDefaultValues.GetValueOrDefault(schema);
|
||||||
|
if (defaultValue is not null)
|
||||||
|
{
|
||||||
|
// Non-Scalar
|
||||||
|
if (defaultValue is not string && defaultValue is IEnumerable defaultValues)
|
||||||
|
{
|
||||||
|
var elementType =
|
||||||
|
defaultValues.GetType().TryGetEnumerableUnderlyingType() ??
|
||||||
|
typeof(object);
|
||||||
|
|
||||||
|
if (elementType.IsToStringOverriden())
|
||||||
|
{
|
||||||
|
Write(ConsoleColor.White, "Default: ");
|
||||||
|
|
||||||
|
var isFirst = true;
|
||||||
|
|
||||||
|
foreach (var element in defaultValues)
|
||||||
|
{
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Write('"');
|
||||||
|
Write(element.ToString(CultureInfo.InvariantCulture));
|
||||||
|
Write('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
Write('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (defaultValue.GetType().IsToStringOverriden())
|
||||||
|
{
|
||||||
|
Write(ConsoleColor.White, "Default: ");
|
||||||
|
|
||||||
|
Write('"');
|
||||||
|
Write(defaultValue.ToString(CultureInfo.InvariantCulture));
|
||||||
|
Write('"');
|
||||||
|
Write('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void WriteCommandChildren()
|
private void WriteCommandChildren()
|
||||||
{
|
{
|
||||||
var childCommandSchemas = _context
|
var childCommandSchemas = _context
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
using System.IO;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements a <see cref="TextReader"/> for reading characters from a console stream.
|
/// Implements a <see cref="TextReader"/> for reading characters from a console stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
// Both the underlying stream AND the stream reader must be synchronized!
|
||||||
|
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||||
public partial class ConsoleReader : StreamReader
|
public partial class ConsoleReader : StreamReader
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -29,6 +34,77 @@ public partial class ConsoleReader : StreamReader
|
|||||||
: this(console, stream, System.Console.InputEncoding)
|
: this(console, stream, System.Console.InputEncoding)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following overrides are required to establish thread-safe behavior
|
||||||
|
// in methods deriving from StreamReader.
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override int Peek() => base.Peek();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override int Read() => base.Read();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override int Read(char[] buffer, int index, int count) =>
|
||||||
|
base.Read(buffer, index, count);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override int ReadBlock(char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
return base.ReadBlock(buffer, index, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override string? ReadLine() => base.ReadLine();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override string ReadToEnd() => base.ReadToEnd();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task<int> ReadAsync(char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
return Task.FromResult(Read(buffer, index, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
return Task.FromResult(ReadBlock(buffer, index, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task<string?> ReadLineAsync()
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
return Task.FromResult(ReadLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task<string> ReadToEndAsync()
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
return Task.FromResult(ReadToEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Close() => base.Close();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
protected override void Dispose(bool disposing) => base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ConsoleReader
|
public partial class ConsoleReader
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System.IO;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CliFx.Utils;
|
using CliFx.Utils;
|
||||||
|
|
||||||
namespace CliFx.Infrastructure;
|
namespace CliFx.Infrastructure;
|
||||||
@@ -7,6 +10,8 @@ namespace CliFx.Infrastructure;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements a <see cref="TextWriter"/> for writing characters to a console stream.
|
/// Implements a <see cref="TextWriter"/> for writing characters to a console stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
// Both the underlying stream AND the stream writer must be synchronized!
|
||||||
|
// https://github.com/Tyrrrz/CliFx/issues/123
|
||||||
public partial class ConsoleWriter : StreamWriter
|
public partial class ConsoleWriter : StreamWriter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -30,6 +35,239 @@ public partial class ConsoleWriter : StreamWriter
|
|||||||
: this(console, stream, System.Console.OutputEncoding)
|
: this(console, stream, System.Console.OutputEncoding)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following overrides are required to establish thread-safe behavior
|
||||||
|
// in methods deriving from StreamWriter.
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(char[] buffer, int index, int count) => base.Write(buffer, index, count);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(char[] buffer) => base.Write(buffer);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(char value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(string? value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(string format, object? arg0) => base.Write(format, arg0);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(string format, object? arg0, object? arg1) =>
|
||||||
|
base.Write(format, arg0, arg1);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(string format, object? arg0, object? arg1, object? arg2) =>
|
||||||
|
base.Write(format, arg0, arg1, arg2);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(string format, params object?[] arg) => base.Write(format, arg);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(bool value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(int value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(long value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(uint value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(ulong value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(float value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(double value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(decimal value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Write(object? value) => base.Write(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteAsync(char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
Write(buffer, index, count);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteAsync(char value)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
Write(value);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteAsync(string? value)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
Write(value);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine() => base.WriteLine();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(char[] buffer, int index, int count) =>
|
||||||
|
base.WriteLine(buffer, index, count);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(char[] buffer) => base.WriteLine(buffer);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(char value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(string? value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(string format, object? arg0) => base.WriteLine(format, arg0);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(string format, object? arg0, object? arg1) =>
|
||||||
|
base.WriteLine(format, arg0, arg1);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(string format, object? arg0, object? arg1, object? arg2) =>
|
||||||
|
base.WriteLine(format, arg0, arg1, arg2);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(string format, params object?[] arg) =>
|
||||||
|
base.WriteLine(format, arg);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(bool value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(int value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(long value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(uint value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(ulong value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(float value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(double value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(decimal value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void WriteLine(object? value) => base.WriteLine(value);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteLineAsync()
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
WriteLine();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteLineAsync(char value)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
WriteLine(value);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteLineAsync(char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
WriteLine(buffer, index, count);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task WriteLineAsync(string? value)
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
WriteLine(value);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Flush() => base.Flush();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override Task FlushAsync()
|
||||||
|
{
|
||||||
|
// Must be non-async to work with locks
|
||||||
|
Flush();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
public override void Close() => base.Close();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
|
||||||
|
protected override void Dispose(bool disposing) => base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ConsoleWriter
|
public partial class ConsoleWriter
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public interface IConsole
|
|||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
CancellationToken RegisterCancellationHandler();
|
CancellationToken RegisterCancellationHandler();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the console buffer and corresponding console window of display information.
|
/// Clears the console buffer and corresponding console window of display information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ internal class BindablePropertyDescriptor : IPropertyDescriptor
|
|||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
var underlyingType = GetUnderlyingType(Type);
|
var underlyingType = GetUnderlyingType(Type);
|
||||||
|
|
||||||
// We can only get valid values for enums
|
// We can only get valid values for enums
|
||||||
|
|||||||
@@ -87,12 +87,27 @@ internal partial class CommandSchema
|
|||||||
? new[] {OptionSchema.HelpOption, OptionSchema.VersionOption}
|
? new[] {OptionSchema.HelpOption, OptionSchema.VersionOption}
|
||||||
: new[] {OptionSchema.HelpOption};
|
: new[] {OptionSchema.HelpOption};
|
||||||
|
|
||||||
var parameterSchemas = type.GetProperties()
|
var properties = type
|
||||||
|
// Get properties directly on command type
|
||||||
|
.GetProperties()
|
||||||
|
// Get non-abstract properties on interfaces (to support default interfaces members)
|
||||||
|
.Union(type
|
||||||
|
.GetInterfaces()
|
||||||
|
// Only interfaces implementing ICommand for explicitness
|
||||||
|
.Where(i => typeof(ICommand).IsAssignableFrom(i) && i != typeof(ICommand))
|
||||||
|
.SelectMany(i => i
|
||||||
|
.GetProperties()
|
||||||
|
.Where(p => !p.GetMethod.IsAbstract && !p.SetMethod.IsAbstract)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var parameterSchemas = properties
|
||||||
.Select(ParameterSchema.TryResolve)
|
.Select(ParameterSchema.TryResolve)
|
||||||
.WhereNotNull()
|
.WhereNotNull()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var optionSchemas = type.GetProperties()
|
var optionSchemas = properties
|
||||||
.Select(OptionSchema.TryResolve)
|
.Select(OptionSchema.TryResolve)
|
||||||
.WhereNotNull()
|
.WhereNotNull()
|
||||||
.Concat(implicitOptionSchemas)
|
.Concat(implicitOptionSchemas)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ internal partial class ParameterSchema : IMemberSchema
|
|||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
public bool IsRequired { get; }
|
||||||
|
|
||||||
public string? Description { get; }
|
public string? Description { get; }
|
||||||
|
|
||||||
public Type? ConverterType { get; }
|
public Type? ConverterType { get; }
|
||||||
@@ -23,6 +25,7 @@ internal partial class ParameterSchema : IMemberSchema
|
|||||||
IPropertyDescriptor property,
|
IPropertyDescriptor property,
|
||||||
int order,
|
int order,
|
||||||
string name,
|
string name,
|
||||||
|
bool isRequired,
|
||||||
string? description,
|
string? description,
|
||||||
Type? converterType,
|
Type? converterType,
|
||||||
IReadOnlyList<Type> validatorTypes)
|
IReadOnlyList<Type> validatorTypes)
|
||||||
@@ -30,6 +33,7 @@ internal partial class ParameterSchema : IMemberSchema
|
|||||||
Property = property;
|
Property = property;
|
||||||
Order = order;
|
Order = order;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
IsRequired = isRequired;
|
||||||
Description = description;
|
Description = description;
|
||||||
ConverterType = converterType;
|
ConverterType = converterType;
|
||||||
ValidatorTypes = validatorTypes;
|
ValidatorTypes = validatorTypes;
|
||||||
@@ -55,6 +59,7 @@ internal partial class ParameterSchema
|
|||||||
new BindablePropertyDescriptor(property),
|
new BindablePropertyDescriptor(property),
|
||||||
attribute.Order,
|
attribute.Order,
|
||||||
name,
|
name,
|
||||||
|
attribute.IsRequired,
|
||||||
description,
|
description,
|
||||||
attribute.Converter,
|
attribute.Converter,
|
||||||
attribute.Validators
|
attribute.Validators
|
||||||
|
|||||||
@@ -59,27 +59,4 @@ internal static class TypeExtensions
|
|||||||
var toStringMethod = type.GetMethod(nameof(ToString), Type.EmptyTypes);
|
var toStringMethod = type.GetMethod(nameof(ToString), Type.EmptyTypes);
|
||||||
return toStringMethod?.GetBaseDefinition()?.DeclaringType != toStringMethod?.DeclaringType;
|
return toStringMethod?.GetBaseDefinition()?.DeclaringType != toStringMethod?.DeclaringType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types supported by `Convert.ChangeType(...)`
|
|
||||||
private static readonly HashSet<Type> ConvertibleTypes = new()
|
|
||||||
{
|
|
||||||
typeof(bool),
|
|
||||||
typeof(char),
|
|
||||||
typeof(sbyte),
|
|
||||||
typeof(byte),
|
|
||||||
typeof(short),
|
|
||||||
typeof(ushort),
|
|
||||||
typeof(int),
|
|
||||||
typeof(uint),
|
|
||||||
typeof(long),
|
|
||||||
typeof(ulong),
|
|
||||||
typeof(float),
|
|
||||||
typeof(double),
|
|
||||||
typeof(decimal),
|
|
||||||
typeof(DateTime),
|
|
||||||
typeof(string),
|
|
||||||
typeof(object)
|
|
||||||
};
|
|
||||||
|
|
||||||
public static bool IsConvertible(this Type type) => ConvertibleTypes.Contains(type);
|
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ namespace CliFx.Utils;
|
|||||||
// https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/Common/src/System/Text/ConsoleEncoding.cs
|
// https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/Common/src/System/Text/ConsoleEncoding.cs
|
||||||
// Also see:
|
// Also see:
|
||||||
// https://source.dot.net/#System.Console/ConsoleEncoding.cs,5eedd083a4a4f4a2
|
// https://source.dot.net/#System.Console/ConsoleEncoding.cs,5eedd083a4a4f4a2
|
||||||
|
// Majority of overrides are just proxy calls to avoid potentially more expensive base behavior.
|
||||||
|
// The important part is the GetPreamble() method that has been overriden to return an empty array.
|
||||||
internal class NoPreambleEncoding : Encoding
|
internal class NoPreambleEncoding : Encoding
|
||||||
{
|
{
|
||||||
private readonly Encoding _underlyingEncoding;
|
private readonly Encoding _underlyingEncoding;
|
||||||
@@ -72,20 +74,20 @@ internal class NoPreambleEncoding : Encoding
|
|||||||
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) =>
|
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) =>
|
||||||
_underlyingEncoding.GetBytes(chars, charIndex, charCount, bytes, byteIndex);
|
_underlyingEncoding.GetBytes(chars, charIndex, charCount, bytes, byteIndex);
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
|
||||||
public override byte[] GetBytes(char[] chars) => _underlyingEncoding.GetBytes(chars);
|
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override byte[] GetBytes(char[] chars, int index, int count) =>
|
public override byte[] GetBytes(char[] chars, int index, int count) =>
|
||||||
_underlyingEncoding.GetBytes(chars, index, count);
|
_underlyingEncoding.GetBytes(chars, index, count);
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override byte[] GetBytes(string s) => _underlyingEncoding.GetBytes(s);
|
public override byte[] GetBytes(char[] chars) => _underlyingEncoding.GetBytes(chars);
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override int GetBytes(string s, int charIndex, int charCount, byte[] bytes, int byteIndex) =>
|
public override int GetBytes(string s, int charIndex, int charCount, byte[] bytes, int byteIndex) =>
|
||||||
_underlyingEncoding.GetBytes(s, charIndex, charCount, bytes, byteIndex);
|
_underlyingEncoding.GetBytes(s, charIndex, charCount, bytes, byteIndex);
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override byte[] GetBytes(string s) => _underlyingEncoding.GetBytes(s);
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override int GetCharCount(byte[] bytes, int index, int count) =>
|
public override int GetCharCount(byte[] bytes, int index, int count) =>
|
||||||
_underlyingEncoding.GetCharCount(bytes, index, count);
|
_underlyingEncoding.GetCharCount(bytes, index, count);
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ internal static partial class PolyfillExtensions
|
|||||||
stream.Write(buffer, 0, buffer.Length);
|
stream.Write(buffer, 0, buffer.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace System.Linq
|
namespace System.Linq
|
||||||
{
|
{
|
||||||
internal static class PolyfillExtensions
|
internal static class PolyfillExtensions
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>2.1</Version>
|
<Version>2.2.2</Version>
|
||||||
<Company>Tyrrrz</Company>
|
<Company>Tyrrrz</Company>
|
||||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
<Copyright>Copyright (C) Oleksii Holub</Copyright>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Disable nullability warnings on older frameworks because there is no nullability info for BCL -->
|
<!-- Disable nullability warnings on older frameworks because there is no nullability info for BCL -->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019-2022 Alexey Golub
|
Copyright (c) 2019-2022 Oleksii Holub
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
19
Readme.md
19
Readme.md
@@ -168,7 +168,7 @@ In case the user forgets to specify the `value` parameter, the application will
|
|||||||
```sh
|
```sh
|
||||||
> dotnet myapp.dll -b 10
|
> dotnet myapp.dll -b 10
|
||||||
|
|
||||||
Missing parameter(s):
|
Missing required parameter(s):
|
||||||
<value>
|
<value>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -193,11 +193,12 @@ OPTIONS
|
|||||||
|
|
||||||
Overall, parameters and options are both used to consume input from the command line, but they differ in a few important ways:
|
Overall, parameters and options are both used to consume input from the command line, but they differ in a few important ways:
|
||||||
|
|
||||||
- Parameters are identified by their relative order. Options are identified by their name or a single-character short name.
|
| | Parameters | Options |
|
||||||
- Parameters technically also have a name, but it's only used in the help text.
|
|--------------------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
|
||||||
- Parameters are always required. Options are normally optional, but can also be configured to require a value.
|
| **Identification** | Positional (by relative order). | Named (by name or short name). |
|
||||||
- Options can be configured to use an environment variable as a fallback.
|
| **Requiredness** | Required by default. Only the last parameter can be configured to be optional. | Optional by default. Any option can be configured to be required without limitations. |
|
||||||
- Both parameters and options can take multiple values, but there can only be one such parameter in a command and it must be the last in order. Options are not limited in this regard.
|
| **Arity** | Depends on the property type. Only the last parameter can be bound to a non-scalar type (i.e. array). | Depends on the property type. Any option can be bound to a non-scalar type without limitations. |
|
||||||
|
| **Fallback** | — | Can be configured to use an environment variable as fallback, in case the option isn't set. |
|
||||||
|
|
||||||
As a general guideline, it's recommended to use parameters for required inputs that the command can't function without.
|
As a general guideline, it's recommended to use parameters for required inputs that the command can't function without.
|
||||||
Use options for all other non-required inputs or when specifying the name explicitly makes the usage clearer.
|
Use options for all other non-required inputs or when specifying the name explicitly makes the usage clearer.
|
||||||
@@ -226,7 +227,7 @@ Similarly, unseparated arguments in the form of `myapp -ofile` will be treated a
|
|||||||
|
|
||||||
Because of these rules, order of arguments is semantically important and must always follow this pattern:
|
Because of these rules, order of arguments is semantically important and must always follow this pattern:
|
||||||
|
|
||||||
```ini
|
```txt
|
||||||
[directives] [command name] [parameters] [options]
|
[directives] [command name] [parameters] [options]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -596,7 +597,7 @@ public async Task ConcatCommand_executes_successfully()
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
await command.ExecuteAsync(console);
|
await command.ExecuteAsync(console);
|
||||||
|
|
||||||
var stdOut = console.ReadOutputString();
|
var stdOut = console.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -624,7 +625,7 @@ public async Task ConcatCommand_executes_successfully()
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
await app.RunAsync(args, envVars);
|
await app.RunAsync(args, envVars);
|
||||||
|
|
||||||
var stdOut = console.ReadOutputString();
|
var stdOut = console.ReadOutputString();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|||||||
Reference in New Issue
Block a user