mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70bfe0bf91 | ||
|
|
9690c380d3 | ||
|
|
85caa275ae | ||
|
|
32026e59c0 | ||
|
|
486ccb9685 | ||
|
|
7b766f70f3 | ||
|
|
f73e96488f | ||
|
|
af63fa5a1f | ||
|
|
e8f53c9463 | ||
|
|
9564cd5d30 | ||
|
|
ed458c3980 | ||
|
|
25538f99db | ||
|
|
36436e7a4b | ||
|
|
a6070332c9 | ||
|
|
25cbfdb4b8 | ||
|
|
d1b5107c2c | ||
|
|
03873d63cd | ||
|
|
89aba39964 | ||
|
|
ab57a103d1 | ||
|
|
d0b2ebc061 |
27
.github/workflows/CD.yml
vendored
Normal file
27
.github/workflows/CD.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: 3.0.100
|
||||||
|
|
||||||
|
- name: Pack
|
||||||
|
run: dotnet pack CliFx --configuration Release
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
dotnet nuget push CliFx/bin/Release/*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
|
||||||
|
dotnet nuget push CliFx/bin/Release/*.snupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
|
||||||
22
.github/workflows/CI.yml
vendored
Normal file
22
.github/workflows/CI.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: 3.0.100
|
||||||
|
|
||||||
|
- name: Build & test
|
||||||
|
run: dotnet test --configuration Release
|
||||||
|
|
||||||
|
- name: Coverage
|
||||||
|
run: curl -s https://codecov.io/bash | bash -s -- -f CliFx.Tests/bin/Release/Coverage.xml -t ${{secrets.CODECOV_TOKEN}} -Z
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -143,6 +143,7 @@ _TeamCity*
|
|||||||
_NCrunch_*
|
_NCrunch_*
|
||||||
.*crunch*.local.xml
|
.*crunch*.local.xml
|
||||||
nCrunchTemp_*
|
nCrunchTemp_*
|
||||||
|
.ncrunchsolution
|
||||||
|
|
||||||
# MightyMoose
|
# MightyMoose
|
||||||
*.mm.*
|
*.mm.*
|
||||||
|
|||||||
BIN
.screenshots/help.png
Normal file
BIN
.screenshots/help.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -8,7 +8,7 @@ namespace CliFx.Benchmarks
|
|||||||
[RankColumn]
|
[RankColumn]
|
||||||
public class Benchmark
|
public class Benchmark
|
||||||
{
|
{
|
||||||
private static readonly string[] Arguments = { "--str", "hello world", "-i", "13", "-b" };
|
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
|
||||||
|
|
||||||
[Benchmark(Description = "CliFx", Baseline = true)]
|
[Benchmark(Description = "CliFx", Baseline = true)]
|
||||||
public Task<int> ExecuteWithCliFx() => new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments);
|
public Task<int> ExecuteWithCliFx() => new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments);
|
||||||
@@ -19,16 +19,17 @@ namespace CliFx.Benchmarks
|
|||||||
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
|
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
|
||||||
public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments);
|
public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments);
|
||||||
|
|
||||||
// Skipped because this benchmark freezes after a couple of iterations
|
[Benchmark(Description = "CommandLineParser")]
|
||||||
// Probably wasn't designed to run multiple times in single process execution
|
|
||||||
//[Benchmark(Description = "CommandLineParser")]
|
|
||||||
public void ExecuteWithCommandLineParser()
|
public void ExecuteWithCommandLineParser()
|
||||||
{
|
{
|
||||||
var parsed = CommandLine.Parser.Default.ParseArguments(Arguments, typeof(CommandLineParserCommand));
|
var parsed = new CommandLine.Parser().ParseArguments(Arguments, typeof(CommandLineParserCommand));
|
||||||
CommandLine.ParserResultExtensions.WithParsed<CommandLineParserCommand>(parsed, c => c.Execute());
|
CommandLine.ParserResultExtensions.WithParsed<CommandLineParserCommand>(parsed, c => c.Execute());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Description = "PowerArgs")]
|
[Benchmark(Description = "PowerArgs")]
|
||||||
public void ExecuteWithPowerArgs() => PowerArgs.Args.InvokeMain<PowerArgsCommand>(Arguments);
|
public void ExecuteWithPowerArgs() => PowerArgs.Args.InvokeMain<PowerArgsCommand>(Arguments);
|
||||||
|
|
||||||
|
[Benchmark(Description = "Clipr")]
|
||||||
|
public void ExecuteWithClipr() => clipr.CliParser.Parse<CliprCommand>(Arguments).Execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
|
||||||
|
<PackageReference Include="clipr" Version="1.6.1" />
|
||||||
<PackageReference Include="CommandLineParser" Version="2.6.0" />
|
<PackageReference Include="CommandLineParser" Version="2.6.0" />
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
|
||||||
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class CliFxCommand : ICommand
|
public class CliFxCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("str", 's')]
|
[CommandOption("str", 's')]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[CommandOption("int", 'i')]
|
[CommandOption("int", 'i')]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
20
CliFx.Benchmarks/Commands/CliprCommand.cs
Normal file
20
CliFx.Benchmarks/Commands/CliprCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using clipr;
|
||||||
|
|
||||||
|
namespace CliFx.Benchmarks.Commands
|
||||||
|
{
|
||||||
|
public class CliprCommand
|
||||||
|
{
|
||||||
|
[NamedArgument('s', "str")]
|
||||||
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
|
[NamedArgument('i', "int")]
|
||||||
|
public int IntOption { get; set; }
|
||||||
|
|
||||||
|
[NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)]
|
||||||
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class CommandLineParserCommand
|
public class CommandLineParserCommand
|
||||||
{
|
{
|
||||||
[Option('s', "str")]
|
[Option('s', "str")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[Option('i', "int")]
|
[Option('i', "int")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class McMasterCommand
|
public class McMasterCommand
|
||||||
{
|
{
|
||||||
[Option("--str|-s")]
|
[Option("--str|-s")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[Option("--int|-i")]
|
[Option("--int|-i")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class PowerArgsCommand
|
public class PowerArgsCommand
|
||||||
{
|
{
|
||||||
[ArgShortcut("--str"), ArgShortcut("-s")]
|
[ArgShortcut("--str"), ArgShortcut("-s")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[ArgShortcut("--int"), ArgShortcut("-i")]
|
[ArgShortcut("--int"), ArgShortcut("-i")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
{
|
{
|
||||||
new Option(new[] {"--str", "-s"})
|
new Option(new[] {"--str", "-s"})
|
||||||
{
|
{
|
||||||
Argument = new Argument<string>()
|
Argument = new Argument<string?>()
|
||||||
},
|
},
|
||||||
new Option(new[] {"--int", "-i"})
|
new Option(new[] {"--int", "-i"})
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace CliFx.Demo.Commands
|
|||||||
public DateTimeOffset Published { get; set; }
|
public DateTimeOffset Published { get; set; }
|
||||||
|
|
||||||
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
||||||
public Isbn Isbn { get; set; }
|
public Isbn? Isbn { get; set; }
|
||||||
|
|
||||||
public BookAddCommand(LibraryService libraryService)
|
public BookAddCommand(LibraryService libraryService)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CliFx.Demo.Commands;
|
using CliFx.Demo.Commands;
|
||||||
using CliFx.Demo.Services;
|
using CliFx.Demo.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -7,7 +8,7 @@ namespace CliFx.Demo
|
|||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static Task<int> Main(string[] args)
|
private static IServiceProvider ConfigureServices()
|
||||||
{
|
{
|
||||||
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
@@ -21,7 +22,12 @@ namespace CliFx.Demo
|
|||||||
services.AddTransient<BookRemoveCommand>();
|
services.AddTransient<BookRemoveCommand>();
|
||||||
services.AddTransient<BookListCommand>();
|
services.AddTransient<BookListCommand>();
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<int> Main(string[] args)
|
||||||
|
{
|
||||||
|
var serviceProvider = ConfigureServices();
|
||||||
|
|
||||||
return new CliApplicationBuilder()
|
return new CliApplicationBuilder()
|
||||||
.AddCommandsFromThisAssembly()
|
.AddCommandsFromThisAssembly()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using CliFx.Services;
|
using CliFx.Services;
|
||||||
|
using CliFx.Tests.Stubs;
|
||||||
using CliFx.Tests.TestCommands;
|
using CliFx.Tests.TestCommands;
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests
|
||||||
{
|
{
|
||||||
@@ -30,7 +31,9 @@ namespace CliFx.Tests
|
|||||||
.UseVersionText("test")
|
.UseVersionText("test")
|
||||||
.UseDescription("test")
|
.UseDescription("test")
|
||||||
.UseConsole(new VirtualConsole(TextWriter.Null))
|
.UseConsole(new VirtualConsole(TextWriter.Null))
|
||||||
.UseCommandFactory(schema => (ICommand) Activator.CreateInstance(schema.Type))
|
.UseCommandFactory(schema => (ICommand) Activator.CreateInstance(schema.Type!)!)
|
||||||
|
.UseCommandOptionInputConverter(new CommandOptionInputConverter())
|
||||||
|
.UseEnvironmentVariablesProvider(new EnvironmentVariablesProviderStub())
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using System;
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Services;
|
using CliFx.Services;
|
||||||
|
using CliFx.Tests.Stubs;
|
||||||
using CliFx.Tests.TestCommands;
|
using CliFx.Tests.TestCommands;
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests
|
namespace CliFx.Tests
|
||||||
{
|
{
|
||||||
@@ -21,13 +23,13 @@ namespace CliFx.Tests
|
|||||||
new string[0],
|
new string[0],
|
||||||
"Hello world."
|
"Hello world."
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(ConcatCommand)},
|
new[] {typeof(ConcatCommand)},
|
||||||
new[] {"concat", "-i", "foo", "-i", "bar", "-s", " "},
|
new[] {"concat", "-i", "foo", "-i", "bar", "-s", " "},
|
||||||
"foo bar"
|
"foo bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(ConcatCommand)},
|
new[] {typeof(ConcatCommand)},
|
||||||
new[] {"concat", "-i", "one", "two", "three", "-s", ", "},
|
new[] {"concat", "-i", "one", "two", "three", "-s", ", "},
|
||||||
@@ -51,7 +53,7 @@ namespace CliFx.Tests
|
|||||||
new[] {"--version"},
|
new[] {"--version"},
|
||||||
TestVersionText
|
TestVersionText
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(HelloWorldDefaultCommand)},
|
new[] {typeof(HelloWorldDefaultCommand)},
|
||||||
new[] {"-h"},
|
new[] {"-h"},
|
||||||
@@ -63,13 +65,13 @@ namespace CliFx.Tests
|
|||||||
new[] {"--help"},
|
new[] {"--help"},
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(ConcatCommand)},
|
new[] {typeof(ConcatCommand)},
|
||||||
new string[0],
|
new string[0],
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(ConcatCommand)},
|
new[] {typeof(ConcatCommand)},
|
||||||
new[] {"-h"},
|
new[] {"-h"},
|
||||||
@@ -81,7 +83,7 @@ namespace CliFx.Tests
|
|||||||
new[] {"--help"},
|
new[] {"--help"},
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(ConcatCommand)},
|
new[] {typeof(ConcatCommand)},
|
||||||
new[] {"concat", "-h"},
|
new[] {"concat", "-h"},
|
||||||
@@ -150,13 +152,13 @@ namespace CliFx.Tests
|
|||||||
new[] {"exc"},
|
new[] {"exc"},
|
||||||
null, null
|
null, null
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(CommandExceptionCommand)},
|
new[] {typeof(CommandExceptionCommand)},
|
||||||
new[] {"exc", "-m", "foo bar"},
|
new[] {"exc", "-m", "foo bar"},
|
||||||
"foo bar", null
|
"foo bar", null
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(CommandExceptionCommand)},
|
new[] {typeof(CommandExceptionCommand)},
|
||||||
new[] {"exc", "-m", "foo bar", "-c", "666"},
|
new[] {"exc", "-m", "foo bar", "-c", "666"},
|
||||||
@@ -167,64 +169,92 @@ namespace CliFx.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
[TestCaseSource(nameof(GetTestCases_RunAsync))]
|
[TestCaseSource(nameof(GetTestCases_RunAsync))]
|
||||||
public async Task RunAsync_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments,
|
public async Task RunAsync_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments,
|
||||||
string expectedStdOut = null)
|
string? expectedStdOut = null)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using (var stdoutStream = new StringWriter())
|
await using var stdoutStream = new StringWriter();
|
||||||
{
|
|
||||||
var console = new VirtualConsole(stdoutStream);
|
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var console = new VirtualConsole(stdoutStream);
|
||||||
.AddCommands(commandTypes)
|
var environmentVariablesProvider = new EnvironmentVariablesProviderStub();
|
||||||
.UseVersionText(TestVersionText)
|
|
||||||
.UseConsole(console)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// Act
|
var application = new CliApplicationBuilder()
|
||||||
var exitCode = await application.RunAsync(commandLineArguments);
|
.AddCommands(commandTypes)
|
||||||
var stdOut = stdoutStream.ToString().Trim();
|
.UseVersionText(TestVersionText)
|
||||||
|
.UseConsole(console)
|
||||||
|
.UseEnvironmentVariablesProvider(environmentVariablesProvider)
|
||||||
|
.Build();
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
exitCode.Should().Be(0);
|
var exitCode = await application.RunAsync(commandLineArguments);
|
||||||
|
var stdOut = stdoutStream.ToString().Trim();
|
||||||
|
|
||||||
if (expectedStdOut != null)
|
// Assert
|
||||||
stdOut.Should().Be(expectedStdOut);
|
exitCode.Should().Be(0);
|
||||||
else
|
|
||||||
stdOut.Should().NotBeNullOrWhiteSpace();
|
if (expectedStdOut != null)
|
||||||
}
|
stdOut.Should().Be(expectedStdOut);
|
||||||
|
else
|
||||||
|
stdOut.Should().NotBeNullOrWhiteSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCaseSource(nameof(GetTestCases_RunAsync_Negative))]
|
[TestCaseSource(nameof(GetTestCases_RunAsync_Negative))]
|
||||||
public async Task RunAsync_Negative_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments,
|
public async Task RunAsync_Negative_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments,
|
||||||
string expectedStdErr = null, int? expectedExitCode = null)
|
string? expectedStdErr = null, int? expectedExitCode = null)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using (var stderrStream = new StringWriter())
|
await using var stderrStream = new StringWriter();
|
||||||
{
|
|
||||||
var console = new VirtualConsole(TextWriter.Null, stderrStream);
|
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
var console = new VirtualConsole(TextWriter.Null, stderrStream);
|
||||||
.AddCommands(commandTypes)
|
var environmentVariablesProvider = new EnvironmentVariablesProviderStub();
|
||||||
.UseVersionText(TestVersionText)
|
|
||||||
.UseConsole(console)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// Act
|
var application = new CliApplicationBuilder()
|
||||||
var exitCode = await application.RunAsync(commandLineArguments);
|
.AddCommands(commandTypes)
|
||||||
var stderr = stderrStream.ToString().Trim();
|
.UseVersionText(TestVersionText)
|
||||||
|
.UseEnvironmentVariablesProvider(environmentVariablesProvider)
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
if (expectedExitCode != null)
|
var exitCode = await application.RunAsync(commandLineArguments);
|
||||||
exitCode.Should().Be(expectedExitCode);
|
var stderr = stderrStream.ToString().Trim();
|
||||||
else
|
|
||||||
exitCode.Should().NotBe(0);
|
// Assert
|
||||||
|
if (expectedExitCode != null)
|
||||||
if (expectedStdErr != null)
|
exitCode.Should().Be(expectedExitCode);
|
||||||
stderr.Should().Be(expectedStdErr);
|
else
|
||||||
else
|
exitCode.Should().NotBe(0);
|
||||||
stderr.Should().NotBeNullOrWhiteSpace();
|
|
||||||
}
|
if (expectedStdErr != null)
|
||||||
|
stderr.Should().Be(expectedStdErr);
|
||||||
|
else
|
||||||
|
stderr.Should().NotBeNullOrWhiteSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task RunAsync_Cancellation_Test()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
await using var stdoutStream = new StringWriter();
|
||||||
|
|
||||||
|
var console = new VirtualConsole(stdoutStream, cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(CancellableCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
var args = new[] {"cancel"};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var runTask = application.RunAsync(args);
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
var exitCode = await runTask.ConfigureAwait(false);
|
||||||
|
var stdOut = stdoutStream.ToString().Trim();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(-2146233029);
|
||||||
|
stdOut.Should().Be("Printed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,21 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net46</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
<CollectCoverage>true</CollectCoverage>
|
<CollectCoverage>true</CollectCoverage>
|
||||||
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
||||||
<CoverletOutput>bin/$(Configuration)/Coverage.xml</CoverletOutput>
|
<CoverletOutput>bin/$(Configuration)/Coverage.xml</CoverletOutput>
|
||||||
<LangVersion>latest</LangVersion>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="5.8.0" />
|
<PackageReference Include="FluentAssertions" Version="5.9.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
<PackageReference Include="coverlet.msbuild" Version="2.6.3">
|
<PackageReference Include="coverlet.msbuild" Version="2.7.0" PrivateAssets="all" />
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
using System;
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
using CliFx.Services;
|
using CliFx.Services;
|
||||||
using CliFx.Tests.TestCommands;
|
using CliFx.Tests.TestCommands;
|
||||||
using FluentAssertions;
|
using CliFx.Tests.Stubs;
|
||||||
using NUnit.Framework;
|
using System.IO;
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
namespace CliFx.Tests.Services
|
||||||
{
|
{
|
||||||
@@ -14,7 +16,7 @@ namespace CliFx.Tests.Services
|
|||||||
public class CommandInitializerTests
|
public class CommandInitializerTests
|
||||||
{
|
{
|
||||||
private static CommandSchema GetCommandSchema(Type commandType) =>
|
private static CommandSchema GetCommandSchema(Type commandType) =>
|
||||||
new CommandSchemaResolver().GetCommandSchemas(new[] {commandType}).Single();
|
new CommandSchemaResolver().GetCommandSchemas(new[] { commandType }).Single();
|
||||||
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand()
|
private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand()
|
||||||
{
|
{
|
||||||
@@ -26,7 +28,7 @@ namespace CliFx.Tests.Services
|
|||||||
new CommandOptionInput("dividend", "13"),
|
new CommandOptionInput("dividend", "13"),
|
||||||
new CommandOptionInput("divisor", "8")
|
new CommandOptionInput("divisor", "8")
|
||||||
}),
|
}),
|
||||||
new DivideCommand {Dividend = 13, Divisor = 8}
|
new DivideCommand { Dividend = 13, Divisor = 8 }
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
@@ -37,7 +39,7 @@ namespace CliFx.Tests.Services
|
|||||||
new CommandOptionInput("dividend", "13"),
|
new CommandOptionInput("dividend", "13"),
|
||||||
new CommandOptionInput("d", "8")
|
new CommandOptionInput("d", "8")
|
||||||
}),
|
}),
|
||||||
new DivideCommand {Dividend = 13, Divisor = 8}
|
new DivideCommand { Dividend = 13, Divisor = 8 }
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
@@ -48,7 +50,7 @@ namespace CliFx.Tests.Services
|
|||||||
new CommandOptionInput("D", "13"),
|
new CommandOptionInput("D", "13"),
|
||||||
new CommandOptionInput("d", "8")
|
new CommandOptionInput("d", "8")
|
||||||
}),
|
}),
|
||||||
new DivideCommand {Dividend = 13, Divisor = 8}
|
new DivideCommand { Dividend = 13, Divisor = 8 }
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
@@ -58,7 +60,7 @@ namespace CliFx.Tests.Services
|
|||||||
{
|
{
|
||||||
new CommandOptionInput("i", new[] {"foo", " ", "bar"})
|
new CommandOptionInput("i", new[] {"foo", " ", "bar"})
|
||||||
}),
|
}),
|
||||||
new ConcatCommand {Inputs = new[] {"foo", " ", "bar"}}
|
new ConcatCommand { Inputs = new[] { "foo", " ", "bar" } }
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
@@ -69,7 +71,43 @@ namespace CliFx.Tests.Services
|
|||||||
new CommandOptionInput("i", new[] {"foo", "bar"}),
|
new CommandOptionInput("i", new[] {"foo", "bar"}),
|
||||||
new CommandOptionInput("s", " ")
|
new CommandOptionInput("s", " ")
|
||||||
}),
|
}),
|
||||||
new ConcatCommand {Inputs = new[] {"foo", "bar"}, Separator = " "}
|
new ConcatCommand { Inputs = new[] { "foo", "bar" }, Separator = " " }
|
||||||
|
);
|
||||||
|
|
||||||
|
//Will read a value from environment variables because none is supplied via CommandInput
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new EnvironmentVariableCommand(),
|
||||||
|
GetCommandSchema(typeof(EnvironmentVariableCommand)),
|
||||||
|
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
|
||||||
|
new EnvironmentVariableCommand { Option = "A" }
|
||||||
|
);
|
||||||
|
|
||||||
|
//Will read multiple values from environment variables because none is supplied via CommandInput
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new EnvironmentVariableWithMultipleValuesCommand(),
|
||||||
|
GetCommandSchema(typeof(EnvironmentVariableWithMultipleValuesCommand)),
|
||||||
|
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
|
||||||
|
new EnvironmentVariableWithMultipleValuesCommand { Option = new[] { "A", "B", "C" } }
|
||||||
|
);
|
||||||
|
|
||||||
|
//Will not read a value from environment variables because one is supplied via CommandInput
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new EnvironmentVariableCommand(),
|
||||||
|
GetCommandSchema(typeof(EnvironmentVariableCommand)),
|
||||||
|
new CommandInput(null, new[]
|
||||||
|
{
|
||||||
|
new CommandOptionInput("opt", new[] { "X" })
|
||||||
|
},
|
||||||
|
EnvironmentVariablesProviderStub.EnvironmentVariables),
|
||||||
|
new EnvironmentVariableCommand { Option = "X" }
|
||||||
|
);
|
||||||
|
|
||||||
|
//Will not split environment variable values because underlying property is not a collection
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new EnvironmentVariableWithoutCollectionPropertyCommand(),
|
||||||
|
GetCommandSchema(typeof(EnvironmentVariableWithoutCollectionPropertyCommand)),
|
||||||
|
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
|
||||||
|
new EnvironmentVariableWithoutCollectionPropertyCommand { Option = $"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System.Collections.Generic;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
using CliFx.Services;
|
using CliFx.Services;
|
||||||
using FluentAssertions;
|
using CliFx.Tests.Stubs;
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
namespace CliFx.Tests.Services
|
||||||
{
|
{
|
||||||
@@ -11,203 +12,238 @@ namespace CliFx.Tests.Services
|
|||||||
{
|
{
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput()
|
private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput()
|
||||||
{
|
{
|
||||||
yield return new TestCaseData(new string[0], CommandInput.Empty);
|
yield return new TestCaseData(new string[0], CommandInput.Empty, new EmptyEnvironmentVariablesProviderStub());
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--option", "value"},
|
new[] { "--option", "value" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option", "value")
|
new CommandOptionInput("option", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--option1", "value1", "--option2", "value2"},
|
new[] { "--option1", "value1", "--option2", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option1", "value1"),
|
new CommandOptionInput("option1", "value1"),
|
||||||
new CommandOptionInput("option2", "value2")
|
new CommandOptionInput("option2", "value2")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--option", "value1", "value2"},
|
new[] { "--option", "value1", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--option", "value1", "--option", "value2"},
|
new[] { "--option", "value1", "--option", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-a", "value"},
|
new[] { "-a", "value" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a", "value")
|
new CommandOptionInput("a", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-a", "value1", "-b", "value2"},
|
new[] { "-a", "value1", "-b", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a", "value1"),
|
new CommandOptionInput("a", "value1"),
|
||||||
new CommandOptionInput("b", "value2")
|
new CommandOptionInput("b", "value2")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-a", "value1", "value2"},
|
new[] { "-a", "value1", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-a", "value1", "-a", "value2"},
|
new[] { "-a", "value1", "-a", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--option1", "value1", "-b", "value2"},
|
new[] { "--option1", "value1", "-b", "value2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option1", "value1"),
|
new CommandOptionInput("option1", "value1"),
|
||||||
new CommandOptionInput("b", "value2")
|
new CommandOptionInput("b", "value2")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--switch"},
|
new[] { "--switch" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("switch")
|
new CommandOptionInput("switch")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"--switch1", "--switch2"},
|
new[] { "--switch1", "--switch2" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("switch1"),
|
new CommandOptionInput("switch1"),
|
||||||
new CommandOptionInput("switch2")
|
new CommandOptionInput("switch2")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-s"},
|
new[] { "-s" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("s")
|
new CommandOptionInput("s")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-a", "-b"},
|
new[] { "-a", "-b" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a"),
|
new CommandOptionInput("a"),
|
||||||
new CommandOptionInput("b")
|
new CommandOptionInput("b")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-ab"},
|
new[] { "-ab" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a"),
|
new CommandOptionInput("a"),
|
||||||
new CommandOptionInput("b")
|
new CommandOptionInput("b")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"-ab", "value"},
|
new[] { "-ab", "value" },
|
||||||
new CommandInput(new[]
|
new CommandInput(new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("a"),
|
new CommandOptionInput("a"),
|
||||||
new CommandOptionInput("b", "value")
|
new CommandOptionInput("b", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"command"},
|
new[] { "command" },
|
||||||
new CommandInput("command")
|
new CommandInput("command"),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"command", "--option", "value"},
|
new[] { "command", "--option", "value" },
|
||||||
new CommandInput("command", new[]
|
new CommandInput("command", new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option", "value")
|
new CommandOptionInput("option", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"long", "command", "name"},
|
new[] { "long", "command", "name" },
|
||||||
new CommandInput("long command name")
|
new CommandInput("long command name"),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"long", "command", "name", "--option", "value"},
|
new[] { "long", "command", "name", "--option", "value" },
|
||||||
new CommandInput("long command name", new[]
|
new CommandInput("long command name", new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("option", "value")
|
new CommandOptionInput("option", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"[debug]"},
|
new[] { "[debug]" },
|
||||||
new CommandInput(null,
|
new CommandInput(null,
|
||||||
new[] {"debug"},
|
new[] { "debug" },
|
||||||
new CommandOptionInput[0])
|
new CommandOptionInput[0]),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"[debug]", "[preview]"},
|
new[] { "[debug]", "[preview]" },
|
||||||
new CommandInput(null,
|
new CommandInput(null,
|
||||||
new[] {"debug", "preview"},
|
new[] { "debug", "preview" },
|
||||||
new CommandOptionInput[0])
|
new CommandOptionInput[0]),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"[debug]", "[preview]", "-o", "value"},
|
new[] { "[debug]", "[preview]", "-o", "value" },
|
||||||
new CommandInput(null,
|
new CommandInput(null,
|
||||||
new[] {"debug", "preview"},
|
new[] { "debug", "preview" },
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("o", "value")
|
new CommandOptionInput("o", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {"command", "[debug]", "[preview]", "-o", "value"},
|
new[] { "command", "[debug]", "[preview]", "-o", "value" },
|
||||||
new CommandInput("command",
|
new CommandInput("command",
|
||||||
new[] {"debug", "preview"},
|
new[] { "debug", "preview" },
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new CommandOptionInput("o", "value")
|
new CommandOptionInput("o", "value")
|
||||||
})
|
}),
|
||||||
|
new EmptyEnvironmentVariablesProviderStub()
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new[] { "command", "[debug]", "[preview]", "-o", "value" },
|
||||||
|
new CommandInput("command",
|
||||||
|
new[] { "debug", "preview" },
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new CommandOptionInput("o", "value")
|
||||||
|
},
|
||||||
|
EnvironmentVariablesProviderStub.EnvironmentVariables),
|
||||||
|
new EnvironmentVariablesProviderStub()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCaseSource(nameof(GetTestCases_ParseCommandInput))]
|
[TestCaseSource(nameof(GetTestCases_ParseCommandInput))]
|
||||||
public void ParseCommandInput_Test(IReadOnlyList<string> commandLineArguments,
|
public void ParseCommandInput_Test(IReadOnlyList<string> commandLineArguments,
|
||||||
CommandInput expectedCommandInput)
|
CommandInput expectedCommandInput, IEnvironmentVariablesProvider environmentVariablesProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var parser = new CommandInputParser();
|
var parser = new CommandInputParser(environmentVariablesProvider);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var commandInput = parser.ParseCommandInput(commandLineArguments);
|
var commandInput = parser.ParseCommandInput(commandLineArguments);
|
||||||
|
|||||||
@@ -214,6 +214,12 @@ namespace CliFx.Tests.Services
|
|||||||
new[] {47, 69}
|
new[] {47, 69}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new CommandOptionInput("option", new[] {"47"}),
|
||||||
|
typeof(int[]),
|
||||||
|
new[] {47}
|
||||||
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new CommandOptionInput("option", new[] {"value1", "value3"}),
|
new CommandOptionInput("option", new[] {"value1", "value3"}),
|
||||||
typeof(TestEnum[]),
|
typeof(TestEnum[]),
|
||||||
@@ -270,6 +276,16 @@ namespace CliFx.Tests.Services
|
|||||||
typeof(int)
|
typeof(int)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new CommandOptionInput("option", new[] {"123", "456"}),
|
||||||
|
typeof(int)
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return new TestCaseData(
|
||||||
|
new CommandOptionInput("option"),
|
||||||
|
typeof(int)
|
||||||
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new CommandOptionInput("option", "123"),
|
new CommandOptionInput("option", "123"),
|
||||||
typeof(TestNonStringParseable)
|
typeof(TestNonStringParseable)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
using CliFx.Services;
|
using CliFx.Services;
|
||||||
using CliFx.Tests.TestCommands;
|
using CliFx.Tests.TestCommands;
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
namespace CliFx.Tests.Services
|
||||||
{
|
{
|
||||||
@@ -15,30 +15,37 @@ namespace CliFx.Tests.Services
|
|||||||
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas()
|
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas()
|
||||||
{
|
{
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(DivideCommand), typeof(ConcatCommand)},
|
new[] { typeof(DivideCommand), typeof(ConcatCommand), typeof(EnvironmentVariableCommand) },
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new CommandSchema(typeof(DivideCommand), "div", "Divide one number by another.",
|
new CommandSchema(typeof(DivideCommand), "div", "Divide one number by another.",
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Dividend)),
|
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Dividend)),
|
||||||
"dividend", 'D', true, "The number to divide."),
|
"dividend", 'D', true, "The number to divide.", null),
|
||||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)),
|
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)),
|
||||||
"divisor", 'd', true, "The number to divide by.")
|
"divisor", 'd', true, "The number to divide by.", null)
|
||||||
}),
|
}),
|
||||||
new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.",
|
new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.",
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)),
|
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)),
|
||||||
null, 'i', true, "Input strings."),
|
null, 'i', true, "Input strings.", null),
|
||||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)),
|
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)),
|
||||||
null, 's', false, "String separator.")
|
null, 's', false, "String separator.", null)
|
||||||
})
|
}),
|
||||||
|
new CommandSchema(typeof(EnvironmentVariableCommand), null, "Reads option values from environment variables.",
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new CommandOptionSchema(typeof(EnvironmentVariableCommand).GetProperty(nameof(EnvironmentVariableCommand.Option)),
|
||||||
|
"opt", null, false, null, "ENV_SINGLE_VALUE")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new[] {typeof(HelloWorldDefaultCommand)},
|
new[] { typeof(HelloWorldDefaultCommand) },
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new CommandSchema(typeof(HelloWorldDefaultCommand), null, null, new CommandOptionSchema[0])
|
new CommandSchema(typeof(HelloWorldDefaultCommand), null, null, new CommandOptionSchema[0])
|
||||||
@@ -62,7 +69,7 @@ namespace CliFx.Tests.Services
|
|||||||
{
|
{
|
||||||
new[] {typeof(NonAnnotatedCommand)}
|
new[] {typeof(NonAnnotatedCommand)}
|
||||||
});
|
});
|
||||||
|
|
||||||
yield return new TestCaseData(new object[]
|
yield return new TestCaseData(new object[]
|
||||||
{
|
{
|
||||||
new[] {typeof(DuplicateOptionNamesCommand)}
|
new[] {typeof(DuplicateOptionNamesCommand)}
|
||||||
@@ -72,7 +79,7 @@ namespace CliFx.Tests.Services
|
|||||||
{
|
{
|
||||||
new[] {typeof(DuplicateOptionShortNamesCommand)}
|
new[] {typeof(DuplicateOptionShortNamesCommand)}
|
||||||
});
|
});
|
||||||
|
|
||||||
yield return new TestCaseData(new object[]
|
yield return new TestCaseData(new object[]
|
||||||
{
|
{
|
||||||
new[] {typeof(ExceptionCommand), typeof(CommandExceptionCommand)}
|
new[] {typeof(ExceptionCommand), typeof(CommandExceptionCommand)}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace CliFx.Tests.Services
|
|||||||
private static IEnumerable<TestCaseData> GetTestCases_CreateCommand()
|
private static IEnumerable<TestCaseData> GetTestCases_CreateCommand()
|
||||||
{
|
{
|
||||||
yield return new TestCaseData(
|
yield return new TestCaseData(
|
||||||
new Func<CommandSchema, ICommand>(schema => (ICommand) Activator.CreateInstance(schema.Type)),
|
new Func<CommandSchema, ICommand>(schema => (ICommand) Activator.CreateInstance(schema.Type!)!),
|
||||||
GetCommandSchema(typeof(HelloWorldDefaultCommand))
|
GetCommandSchema(typeof(HelloWorldDefaultCommand))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,17 +93,16 @@ namespace CliFx.Tests.Services
|
|||||||
IReadOnlyList<string> expectedSubstrings)
|
IReadOnlyList<string> expectedSubstrings)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using (var stdout = new StringWriter())
|
using var stdout = new StringWriter();
|
||||||
{
|
|
||||||
var console = new VirtualConsole(stdout);
|
|
||||||
var renderer = new HelpTextRenderer();
|
|
||||||
|
|
||||||
// Act
|
var console = new VirtualConsole(stdout);
|
||||||
renderer.RenderHelpText(console, source);
|
var renderer = new HelpTextRenderer();
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
stdout.ToString().Should().ContainAll(expectedSubstrings);
|
renderer.RenderHelpText(console, source);
|
||||||
}
|
|
||||||
|
// Assert
|
||||||
|
stdout.ToString().Should().ContainAll(expectedSubstrings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,30 +14,29 @@ namespace CliFx.Tests.Services
|
|||||||
public void All_Smoke_Test()
|
public void All_Smoke_Test()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using (var stdin = new StringReader("hello world"))
|
using var stdin = new StringReader("hello world");
|
||||||
using (var stdout = new StringWriter())
|
using var stdout = new StringWriter();
|
||||||
using (var stderr = new StringWriter())
|
using var stderr = new StringWriter();
|
||||||
{
|
|
||||||
var console = new VirtualConsole(stdin, stdout, stderr);
|
|
||||||
|
|
||||||
// Act
|
var console = new VirtualConsole(stdin, stdout, stderr);
|
||||||
console.ResetColor();
|
|
||||||
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
|
||||||
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
console.Input.Should().BeSameAs(stdin);
|
console.ResetColor();
|
||||||
console.Input.Should().NotBeSameAs(Console.In);
|
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||||
console.IsInputRedirected.Should().BeTrue();
|
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||||
console.Output.Should().BeSameAs(stdout);
|
|
||||||
console.Output.Should().NotBeSameAs(Console.Out);
|
// Assert
|
||||||
console.IsOutputRedirected.Should().BeTrue();
|
console.Input.Should().BeSameAs(stdin);
|
||||||
console.Error.Should().BeSameAs(stderr);
|
console.Input.Should().NotBeSameAs(Console.In);
|
||||||
console.Error.Should().NotBeSameAs(Console.Error);
|
console.IsInputRedirected.Should().BeTrue();
|
||||||
console.IsErrorRedirected.Should().BeTrue();
|
console.Output.Should().BeSameAs(stdout);
|
||||||
console.ForegroundColor.Should().NotBe(Console.ForegroundColor);
|
console.Output.Should().NotBeSameAs(Console.Out);
|
||||||
console.BackgroundColor.Should().NotBe(Console.BackgroundColor);
|
console.IsOutputRedirected.Should().BeTrue();
|
||||||
}
|
console.Error.Should().BeSameAs(stderr);
|
||||||
|
console.Error.Should().NotBeSameAs(Console.Error);
|
||||||
|
console.IsErrorRedirected.Should().BeTrue();
|
||||||
|
console.ForegroundColor.Should().NotBe(Console.ForegroundColor);
|
||||||
|
console.BackgroundColor.Should().NotBe(Console.BackgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
CliFx.Tests/Stubs/EmptyEnvironmentVariablesProviderStub.cs
Normal file
10
CliFx.Tests/Stubs/EmptyEnvironmentVariablesProviderStub.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CliFx.Services;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.Stubs
|
||||||
|
{
|
||||||
|
public class EmptyEnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
|
||||||
|
{
|
||||||
|
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
CliFx.Tests/Stubs/EnvironmentVariablesProviderStub.cs
Normal file
18
CliFx.Tests/Stubs/EnvironmentVariablesProviderStub.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using CliFx.Services;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.Stubs
|
||||||
|
{
|
||||||
|
public class EnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
|
||||||
|
{
|
||||||
|
public static readonly Dictionary<string, string> EnvironmentVariables = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["ENV_SINGLE_VALUE"] = "A",
|
||||||
|
["ENV_MULTIPLE_VALUES"] = $"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}",
|
||||||
|
["ENV_ESCAPED_MULTIPLE_VALUES"] = $"\"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}\""
|
||||||
|
};
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => EnvironmentVariables;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
CliFx.Tests/TestCommands/CancellableCommand.cs
Normal file
22
CliFx.Tests/TestCommands/CancellableCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Services;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.TestCommands
|
||||||
|
{
|
||||||
|
[Command("cancel")]
|
||||||
|
public class CancellableCommand : ICommand
|
||||||
|
{
|
||||||
|
public async Task ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
console.Output.WriteLine("Printed");
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1), console.GetCancellationToken()).ConfigureAwait(false);
|
||||||
|
|
||||||
|
console.Output.WriteLine("Never printed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public int ExitCode { get; set; } = 1337;
|
public int ExitCode { get; set; } = 1337;
|
||||||
|
|
||||||
[CommandOption("msg", 'm')]
|
[CommandOption("msg", 'm')]
|
||||||
public string Message { get; set; }
|
public string? Message { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode);
|
public Task ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public class DuplicateOptionNamesCommand : ICommand
|
public class DuplicateOptionNamesCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("fruits")]
|
[CommandOption("fruits")]
|
||||||
public string Apples { get; set; }
|
public string? Apples { get; set; }
|
||||||
|
|
||||||
[CommandOption("fruits")]
|
[CommandOption("fruits")]
|
||||||
public string Oranges { get; set; }
|
public string? Oranges { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public class DuplicateOptionShortNamesCommand : ICommand
|
public class DuplicateOptionShortNamesCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Apples { get; set; }
|
public string? Apples { get; set; }
|
||||||
|
|
||||||
[CommandOption('f')]
|
[CommandOption('f')]
|
||||||
public string Oranges { get; set; }
|
public string? Oranges { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
15
CliFx.Tests/TestCommands/EnvironmentVariableCommand.cs
Normal file
15
CliFx.Tests/TestCommands/EnvironmentVariableCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Services;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.TestCommands
|
||||||
|
{
|
||||||
|
[Command(Description = "Reads option values from environment variables.")]
|
||||||
|
public class EnvironmentVariableCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("opt", EnvironmentVariableName = "ENV_SINGLE_VALUE")]
|
||||||
|
public string? Option { get; set; }
|
||||||
|
|
||||||
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Services;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.TestCommands
|
||||||
|
{
|
||||||
|
[Command(Description = "Reads multiple option values from environment variables.")]
|
||||||
|
public class EnvironmentVariableWithMultipleValuesCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
|
||||||
|
public IEnumerable<string>? Option { get; set; }
|
||||||
|
|
||||||
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Services;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.TestCommands
|
||||||
|
{
|
||||||
|
[Command(Description = "Reads one option value from environment variables because target property is not a collection.")]
|
||||||
|
public class EnvironmentVariableWithoutCollectionPropertyCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
|
||||||
|
public string? Option { get; set; }
|
||||||
|
|
||||||
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public class ExceptionCommand : ICommand
|
public class ExceptionCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("msg", 'm')]
|
[CommandOption("msg", 'm')]
|
||||||
public string Message { get; set; }
|
public string? Message { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => throw new Exception(Message);
|
public Task ExecuteAsync(IConsole console) => throw new Exception(Message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public class HelpDefaultCommand : ICommand
|
public class HelpDefaultCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("option-a", 'a', Description = "OptionA description.")]
|
[CommandOption("option-a", 'a', Description = "OptionA description.")]
|
||||||
public string OptionA { get; set; }
|
public string? OptionA { get; set; }
|
||||||
|
|
||||||
[CommandOption("option-b", 'b', Description = "OptionB description.")]
|
[CommandOption("option-b", 'b', Description = "OptionB description.")]
|
||||||
public string OptionB { get; set; }
|
public string? OptionB { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public class HelpNamedCommand : ICommand
|
public class HelpNamedCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("option-c", 'c', Description = "OptionC description.")]
|
[CommandOption("option-c", 'c', Description = "OptionC description.")]
|
||||||
public string OptionC { get; set; }
|
public string? OptionC { get; set; }
|
||||||
|
|
||||||
[CommandOption("option-d", 'd', Description = "OptionD description.")]
|
[CommandOption("option-d", 'd', Description = "OptionD description.")]
|
||||||
public string OptionD { get; set; }
|
public string? OptionD { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace CliFx.Tests.TestCommands
|
|||||||
public class HelpSubCommand : ICommand
|
public class HelpSubCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("option-e", 'e', Description = "OptionE description.")]
|
[CommandOption("option-e", 'e', Description = "OptionE description.")]
|
||||||
public string OptionE { get; set; }
|
public string? OptionE { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,41 +17,39 @@ namespace CliFx.Tests.Utilities
|
|||||||
// Arrange
|
// Arrange
|
||||||
var formatProvider = CultureInfo.InvariantCulture;
|
var formatProvider = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
using (var stdout = new StringWriter(formatProvider))
|
using var stdout = new StringWriter(formatProvider);
|
||||||
{
|
|
||||||
var console = new VirtualConsole(TextReader.Null, false, stdout, false, TextWriter.Null, false);
|
|
||||||
var ticker = console.CreateProgressTicker();
|
|
||||||
|
|
||||||
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
|
var console = new VirtualConsole(TextReader.Null, false, stdout, false, TextWriter.Null, false);
|
||||||
var progressStringValues = progressValues.Select(p => p.ToString("P2", formatProvider)).ToArray();
|
var ticker = console.CreateProgressTicker();
|
||||||
|
|
||||||
// Act
|
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
|
||||||
foreach (var progress in progressValues)
|
var progressStringValues = progressValues.Select(p => p.ToString("P2", formatProvider)).ToArray();
|
||||||
ticker.Report(progress);
|
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
stdout.ToString().Should().ContainAll(progressStringValues);
|
foreach (var progress in progressValues)
|
||||||
}
|
ticker.Report(progress);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdout.ToString().Should().ContainAll(progressStringValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Report_Redirected_Test()
|
public void Report_Redirected_Test()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using (var stdout = new StringWriter())
|
using var stdout = new StringWriter();
|
||||||
{
|
|
||||||
var console = new VirtualConsole(stdout);
|
|
||||||
var ticker = console.CreateProgressTicker();
|
|
||||||
|
|
||||||
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
|
var console = new VirtualConsole(stdout);
|
||||||
|
var ticker = console.CreateProgressTicker();
|
||||||
|
|
||||||
// Act
|
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
|
||||||
foreach (var progress in progressValues)
|
|
||||||
ticker.Report(progress);
|
|
||||||
|
|
||||||
// Assert
|
// Act
|
||||||
stdout.ToString().Should().BeEmpty();
|
foreach (var progress in progressValues)
|
||||||
}
|
ticker.Report(progress);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdout.ToString().Should().BeEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,27 +10,27 @@ namespace CliFx.Attributes
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command name.
|
/// Command name.
|
||||||
|
/// This can be null if this is the default command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command description, which is used in help text.
|
/// Command description, which is used in help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandAttribute(string name)
|
public CommandAttribute(string name)
|
||||||
{
|
{
|
||||||
Name = name; // can be null
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandAttribute()
|
public CommandAttribute()
|
||||||
: this(null)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ namespace CliFx.Attributes
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option name.
|
/// Option name.
|
||||||
|
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option short name.
|
/// Option short name.
|
||||||
|
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public char? ShortName { get; }
|
public char? ShortName { get; }
|
||||||
|
|
||||||
@@ -26,15 +28,20 @@ namespace CliFx.Attributes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option description, which is used in help text.
|
/// Option description, which is used in help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional environment variable name that will be used as fallback value if no option value is specified.
|
||||||
|
/// </summary>
|
||||||
|
public string? EnvironmentVariableName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionAttribute(string name, char? shortName)
|
private CommandOptionAttribute(string? name, char? shortName)
|
||||||
{
|
{
|
||||||
Name = name; // can be null
|
Name = name;
|
||||||
ShortName = shortName; // can be null
|
ShortName = shortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,7 +64,7 @@ namespace CliFx.Attributes
|
|||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionAttribute(char shortName)
|
public CommandOptionAttribute(char shortName)
|
||||||
: this(null, shortName)
|
: this(null, (char?) shortName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ namespace CliFx
|
|||||||
IConsole console, ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
|
IConsole console, ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
|
||||||
ICommandFactory commandFactory, ICommandInitializer commandInitializer, IHelpTextRenderer helpTextRenderer)
|
ICommandFactory commandFactory, ICommandInitializer commandInitializer, IHelpTextRenderer helpTextRenderer)
|
||||||
{
|
{
|
||||||
_metadata = metadata.GuardNotNull(nameof(metadata));
|
_metadata = metadata;
|
||||||
_configuration = configuration.GuardNotNull(nameof(configuration));
|
_configuration = configuration;
|
||||||
|
|
||||||
_console = console.GuardNotNull(nameof(console));
|
_console = console;
|
||||||
_commandInputParser = commandInputParser.GuardNotNull(nameof(commandInputParser));
|
_commandInputParser = commandInputParser;
|
||||||
_commandSchemaResolver = commandSchemaResolver.GuardNotNull(nameof(commandSchemaResolver));
|
_commandSchemaResolver = commandSchemaResolver;
|
||||||
_commandFactory = commandFactory.GuardNotNull(nameof(commandFactory));
|
_commandFactory = commandFactory;
|
||||||
_commandInitializer = commandInitializer.GuardNotNull(nameof(commandInitializer));
|
_commandInitializer = commandInitializer;
|
||||||
_helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer));
|
_helpTextRenderer = helpTextRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int?> HandleDebugDirectiveAsync(CommandInput commandInput)
|
private async Task<int?> HandleDebugDirectiveAsync(CommandInput commandInput)
|
||||||
@@ -117,7 +117,7 @@ namespace CliFx
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int? HandleHelpOption(CommandInput commandInput,
|
private int? HandleHelpOption(CommandInput commandInput,
|
||||||
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema targetCommandSchema)
|
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema? targetCommandSchema)
|
||||||
{
|
{
|
||||||
// Help should be rendered if it was requested, or when executing a command which isn't defined
|
// Help should be rendered if it was requested, or when executing a command which isn't defined
|
||||||
var shouldRenderHelp = commandInput.IsHelpOptionSpecified() || targetCommandSchema == null;
|
var shouldRenderHelp = commandInput.IsHelpOptionSpecified() || targetCommandSchema == null;
|
||||||
@@ -180,8 +180,6 @@ namespace CliFx
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
||||||
{
|
{
|
||||||
commandLineArguments.GuardNotNull(nameof(commandLineArguments));
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Parse command input from arguments
|
// Parse command input from arguments
|
||||||
@@ -199,7 +197,7 @@ namespace CliFx
|
|||||||
HandlePreviewDirective(commandInput) ??
|
HandlePreviewDirective(commandInput) ??
|
||||||
HandleVersionOption(commandInput) ??
|
HandleVersionOption(commandInput) ??
|
||||||
HandleHelpOption(commandInput, availableCommandSchemas, targetCommandSchema) ??
|
HandleHelpOption(commandInput, availableCommandSchemas, targetCommandSchema) ??
|
||||||
await HandleCommandExecutionAsync(commandInput, targetCommandSchema);
|
await HandleCommandExecutionAsync(commandInput, targetCommandSchema!);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -207,7 +205,7 @@ namespace CliFx
|
|||||||
// Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions.
|
// Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions.
|
||||||
|
|
||||||
// Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException
|
// Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException
|
||||||
if (!ex.Message.IsNullOrWhiteSpace() && (ex is CliFxException || ex is CommandException))
|
if (!string.IsNullOrWhiteSpace(ex.Message) && (ex is CliFxException || ex is CommandException))
|
||||||
{
|
{
|
||||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.Message));
|
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.Message));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,18 +19,18 @@ namespace CliFx
|
|||||||
|
|
||||||
private bool _isDebugModeAllowed = true;
|
private bool _isDebugModeAllowed = true;
|
||||||
private bool _isPreviewModeAllowed = true;
|
private bool _isPreviewModeAllowed = true;
|
||||||
private string _title;
|
private string? _title;
|
||||||
private string _executableName;
|
private string? _executableName;
|
||||||
private string _versionText;
|
private string? _versionText;
|
||||||
private string _description;
|
private string? _description;
|
||||||
private IConsole _console;
|
private IConsole? _console;
|
||||||
private ICommandFactory _commandFactory;
|
private ICommandFactory? _commandFactory;
|
||||||
|
private ICommandOptionInputConverter? _commandOptionInputConverter;
|
||||||
|
private IEnvironmentVariablesProvider? _environmentVariablesProvider;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder AddCommand(Type commandType)
|
public ICliApplicationBuilder AddCommand(Type commandType)
|
||||||
{
|
{
|
||||||
commandType.GuardNotNull(nameof(commandType));
|
|
||||||
|
|
||||||
_commandTypes.Add(commandType);
|
_commandTypes.Add(commandType);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -39,8 +39,6 @@ namespace CliFx
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder AddCommandsFrom(Assembly commandAssembly)
|
public ICliApplicationBuilder AddCommandsFrom(Assembly commandAssembly)
|
||||||
{
|
{
|
||||||
commandAssembly.GuardNotNull(nameof(commandAssembly));
|
|
||||||
|
|
||||||
var commandTypes = commandAssembly.ExportedTypes
|
var commandTypes = commandAssembly.ExportedTypes
|
||||||
.Where(t => t.Implements(typeof(ICommand)))
|
.Where(t => t.Implements(typeof(ICommand)))
|
||||||
.Where(t => t.IsDefined(typeof(CommandAttribute)))
|
.Where(t => t.IsDefined(typeof(CommandAttribute)))
|
||||||
@@ -69,42 +67,56 @@ namespace CliFx
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder UseTitle(string title)
|
public ICliApplicationBuilder UseTitle(string title)
|
||||||
{
|
{
|
||||||
_title = title.GuardNotNull(nameof(title));
|
_title = title;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder UseExecutableName(string executableName)
|
public ICliApplicationBuilder UseExecutableName(string executableName)
|
||||||
{
|
{
|
||||||
_executableName = executableName.GuardNotNull(nameof(executableName));
|
_executableName = executableName;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder UseVersionText(string versionText)
|
public ICliApplicationBuilder UseVersionText(string versionText)
|
||||||
{
|
{
|
||||||
_versionText = versionText.GuardNotNull(nameof(versionText));
|
_versionText = versionText;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder UseDescription(string description)
|
public ICliApplicationBuilder UseDescription(string? description)
|
||||||
{
|
{
|
||||||
_description = description; // can be null
|
_description = description;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder UseConsole(IConsole console)
|
public ICliApplicationBuilder UseConsole(IConsole console)
|
||||||
{
|
{
|
||||||
_console = console.GuardNotNull(nameof(console));
|
_console = console;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICliApplicationBuilder UseCommandFactory(ICommandFactory factory)
|
public ICliApplicationBuilder UseCommandFactory(ICommandFactory factory)
|
||||||
{
|
{
|
||||||
_commandFactory = factory.GuardNotNull(nameof(factory));
|
_commandFactory = factory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter)
|
||||||
|
{
|
||||||
|
_commandOptionInputConverter = converter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider)
|
||||||
|
{
|
||||||
|
_environmentVariablesProvider = environmentVariablesProvider;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,19 +124,21 @@ namespace CliFx
|
|||||||
public ICliApplication Build()
|
public ICliApplication Build()
|
||||||
{
|
{
|
||||||
// Use defaults for required parameters that were not configured
|
// Use defaults for required parameters that were not configured
|
||||||
_title = _title ?? GetDefaultTitle() ?? "App";
|
_title ??= GetDefaultTitle() ?? "App";
|
||||||
_executableName = _executableName ?? GetDefaultExecutableName() ?? "app";
|
_executableName ??= GetDefaultExecutableName() ?? "app";
|
||||||
_versionText = _versionText ?? GetDefaultVersionText() ?? "v1.0";
|
_versionText ??= GetDefaultVersionText() ?? "v1.0";
|
||||||
_console = _console ?? new SystemConsole();
|
_console ??= new SystemConsole();
|
||||||
_commandFactory = _commandFactory ?? new CommandFactory();
|
_commandFactory ??= new CommandFactory();
|
||||||
|
_commandOptionInputConverter ??= new CommandOptionInputConverter();
|
||||||
|
_environmentVariablesProvider ??= new EnvironmentVariablesProvider();
|
||||||
|
|
||||||
// Project parameters to expected types
|
// Project parameters to expected types
|
||||||
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
|
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
|
||||||
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
|
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
|
||||||
|
|
||||||
return new CliApplication(metadata, configuration,
|
return new CliApplication(metadata, configuration,
|
||||||
_console, new CommandInputParser(), new CommandSchemaResolver(),
|
_console, new CommandInputParser(_environmentVariablesProvider), new CommandSchemaResolver(),
|
||||||
_commandFactory, new CommandInitializer(), new HelpTextRenderer());
|
_commandFactory, new CommandInitializer(_commandOptionInputConverter, new EnvironmentVariablesParser()), new HelpTextRenderer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +149,7 @@ namespace CliFx
|
|||||||
// Entry assembly is null in tests
|
// Entry assembly is null in tests
|
||||||
private static Assembly EntryAssembly => LazyEntryAssembly.Value;
|
private static Assembly EntryAssembly => LazyEntryAssembly.Value;
|
||||||
|
|
||||||
private static string GetDefaultTitle() => EntryAssembly?.GetName().Name;
|
private static string GetDefaultTitle() => EntryAssembly?.GetName().Name ?? "";
|
||||||
|
|
||||||
private static string GetDefaultExecutableName()
|
private static string GetDefaultExecutableName()
|
||||||
{
|
{
|
||||||
@@ -151,6 +165,6 @@ namespace CliFx
|
|||||||
return Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
return Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDefaultVersionText() => EntryAssembly != null ? $"v{EntryAssembly.GetName().Version}" : null;
|
private static string GetDefaultVersionText() => EntryAssembly != null ? $"v{EntryAssembly.GetName().Version}" : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net45;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||||
<LangVersion>latest</LangVersion>
|
<Version>0.0.8</Version>
|
||||||
<Version>0.0.4</Version>
|
|
||||||
<Company>Tyrrrz</Company>
|
<Company>Tyrrrz</Company>
|
||||||
<Authors>$(Company)</Authors>
|
<Authors>$(Company)</Authors>
|
||||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
||||||
@@ -11,13 +10,27 @@
|
|||||||
<PackageTags>command line executable interface framework parser arguments net core</PackageTags>
|
<PackageTags>command line executable interface framework parser arguments 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>
|
||||||
<PackageIconUrl>https://raw.githubusercontent.com/Tyrrrz/CliFx/master/favicon.png</PackageIconUrl>
|
<PackageIcon>favicon.png</PackageIcon>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<RepositoryUrl>https://github.com/Tyrrrz/CliFx</RepositoryUrl>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
|
<PublishRepositoryUrl>True</PublishRepositoryUrl>
|
||||||
|
<EmbedUntrackedSources>True</EmbedUntrackedSources>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Nullable" Version="1.1.1" PrivateAssets="all" />
|
||||||
|
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../favicon.png" Pack="True" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace CliFx.Exceptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CliFxException"/>.
|
/// Initializes an instance of <see cref="CliFxException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CliFxException(string message)
|
public CliFxException(string? message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ namespace CliFx.Exceptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CliFxException"/>.
|
/// Initializes an instance of <see cref="CliFxException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CliFxException(string message, Exception innerException)
|
public CliFxException(string? message, Exception? innerException)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Exceptions
|
namespace CliFx.Exceptions
|
||||||
{
|
{
|
||||||
@@ -20,16 +19,16 @@ namespace CliFx.Exceptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandException"/>.
|
/// Initializes an instance of <see cref="CommandException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandException(string message, Exception innerException, int exitCode = DefaultExitCode)
|
public CommandException(string? message, Exception? innerException, int exitCode = DefaultExitCode)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{
|
{
|
||||||
ExitCode = exitCode.GuardNotZero(nameof(exitCode));
|
ExitCode = exitCode != 0 ? exitCode : throw new ArgumentException("Exit code cannot be zero because that signifies success.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandException"/>.
|
/// Initializes an instance of <see cref="CommandException"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandException(string message, int exitCode = DefaultExitCode)
|
public CommandException(string? message, int exitCode = DefaultExitCode)
|
||||||
: this(message, null, exitCode)
|
: this(message, null, exitCode)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using CliFx.Internal;
|
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
using CliFx.Services;
|
using CliFx.Services;
|
||||||
|
|
||||||
@@ -17,9 +16,6 @@ namespace CliFx
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static ICliApplicationBuilder AddCommands(this ICliApplicationBuilder builder, IReadOnlyList<Type> commandTypes)
|
public static ICliApplicationBuilder AddCommands(this ICliApplicationBuilder builder, IReadOnlyList<Type> commandTypes)
|
||||||
{
|
{
|
||||||
builder.GuardNotNull(nameof(builder));
|
|
||||||
commandTypes.GuardNotNull(nameof(commandTypes));
|
|
||||||
|
|
||||||
foreach (var commandType in commandTypes)
|
foreach (var commandType in commandTypes)
|
||||||
builder.AddCommand(commandType);
|
builder.AddCommand(commandType);
|
||||||
|
|
||||||
@@ -31,9 +27,6 @@ namespace CliFx
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static ICliApplicationBuilder AddCommandsFrom(this ICliApplicationBuilder builder, IReadOnlyList<Assembly> commandAssemblies)
|
public static ICliApplicationBuilder AddCommandsFrom(this ICliApplicationBuilder builder, IReadOnlyList<Assembly> commandAssemblies)
|
||||||
{
|
{
|
||||||
builder.GuardNotNull(nameof(builder));
|
|
||||||
commandAssemblies.GuardNotNull(nameof(commandAssemblies));
|
|
||||||
|
|
||||||
foreach (var commandAssembly in commandAssemblies)
|
foreach (var commandAssembly in commandAssemblies)
|
||||||
builder.AddCommandsFrom(commandAssembly);
|
builder.AddCommandsFrom(commandAssembly);
|
||||||
|
|
||||||
@@ -43,21 +36,13 @@ namespace CliFx
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds commands from calling assembly to the application.
|
/// Adds commands from calling assembly to the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ICliApplicationBuilder AddCommandsFromThisAssembly(this ICliApplicationBuilder builder)
|
public static ICliApplicationBuilder AddCommandsFromThisAssembly(this ICliApplicationBuilder builder) =>
|
||||||
{
|
builder.AddCommandsFrom(Assembly.GetCallingAssembly());
|
||||||
builder.GuardNotNull(nameof(builder));
|
|
||||||
return builder.AddCommandsFrom(Assembly.GetCallingAssembly());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures application to use specified factory method for creating new instances of <see cref="ICommand"/>.
|
/// Configures application to use specified factory method for creating new instances of <see cref="ICommand"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ICliApplicationBuilder UseCommandFactory(this ICliApplicationBuilder builder, Func<CommandSchema, ICommand> factoryMethod)
|
public static ICliApplicationBuilder UseCommandFactory(this ICliApplicationBuilder builder, Func<CommandSchema, ICommand> factoryMethod) =>
|
||||||
{
|
builder.UseCommandFactory(new DelegateCommandFactory(factoryMethod));
|
||||||
builder.GuardNotNull(nameof(builder));
|
|
||||||
factoryMethod.GuardNotNull(nameof(factoryMethod));
|
|
||||||
|
|
||||||
return builder.UseCommandFactory(new DelegateCommandFactory(factoryMethod));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ namespace CliFx
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets application description, which appears in the help text.
|
/// Sets application description, which appears in the help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ICliApplicationBuilder UseDescription(string description);
|
ICliApplicationBuilder UseDescription(string? description);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures application to use specified implementation of <see cref="IConsole"/>.
|
/// Configures application to use specified implementation of <see cref="IConsole"/>.
|
||||||
@@ -59,6 +59,16 @@ namespace CliFx
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ICliApplicationBuilder UseCommandFactory(ICommandFactory factory);
|
ICliApplicationBuilder UseCommandFactory(ICommandFactory factory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures application to use specified implementation of <see cref="ICommandOptionInputConverter"/>.
|
||||||
|
/// </summary>
|
||||||
|
ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures application to use specified implementation of <see cref="IEnvironmentVariablesProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of <see cref="ICliApplication"/> using configured parameters.
|
/// Creates an instance of <see cref="ICliApplication"/> using configured parameters.
|
||||||
/// Default values are used in place of parameters that were not specified.
|
/// Default values are used in place of parameters that were not specified.
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ namespace CliFx.Internal
|
|||||||
{
|
{
|
||||||
internal static class Extensions
|
internal static class Extensions
|
||||||
{
|
{
|
||||||
public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrWhiteSpace(s);
|
|
||||||
|
|
||||||
public static string Repeat(this char c, int count) => new string(c, count);
|
public static string Repeat(this char c, int count) => new string(c, count);
|
||||||
|
|
||||||
public static string AsString(this char c) => c.Repeat(1);
|
public static string AsString(this char c) => c.Repeat(1);
|
||||||
@@ -36,8 +34,13 @@ namespace CliFx.Internal
|
|||||||
|
|
||||||
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
|
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
|
||||||
|
|
||||||
public static Type GetEnumerableUnderlyingType(this Type type)
|
public static Type? GetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type);
|
||||||
|
|
||||||
|
public static Type? GetEnumerableUnderlyingType(this Type type)
|
||||||
{
|
{
|
||||||
|
if (type.IsPrimitive)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (type == typeof(IEnumerable))
|
if (type == typeof(IEnumerable))
|
||||||
return typeof(object);
|
return typeof(object);
|
||||||
|
|
||||||
@@ -60,5 +63,8 @@ namespace CliFx.Internal
|
|||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsCollection(this Type type) =>
|
||||||
|
type != typeof(string) && type.GetEnumerableUnderlyingType() != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace CliFx.Internal
|
|
||||||
{
|
|
||||||
internal static class Guards
|
|
||||||
{
|
|
||||||
public static T GuardNotNull<T>(this T o, string argName = null) where T : class =>
|
|
||||||
o ?? throw new ArgumentNullException(argName);
|
|
||||||
|
|
||||||
public static int GuardNotZero(this int i, string argName = null) =>
|
|
||||||
i != 0 ? i : throw new ArgumentException("Cannot be zero.", argName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Models
|
namespace CliFx.Models
|
||||||
{
|
{
|
||||||
@@ -30,7 +29,7 @@ namespace CliFx.Models
|
|||||||
public ApplicationConfiguration(IReadOnlyList<Type> commandTypes,
|
public ApplicationConfiguration(IReadOnlyList<Type> commandTypes,
|
||||||
bool isDebugModeAllowed, bool isPreviewModeAllowed)
|
bool isDebugModeAllowed, bool isPreviewModeAllowed)
|
||||||
{
|
{
|
||||||
CommandTypes = commandTypes.GuardNotNull(nameof(commandTypes));
|
CommandTypes = commandTypes;
|
||||||
IsDebugModeAllowed = isDebugModeAllowed;
|
IsDebugModeAllowed = isDebugModeAllowed;
|
||||||
IsPreviewModeAllowed = isPreviewModeAllowed;
|
IsPreviewModeAllowed = isPreviewModeAllowed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using CliFx.Internal;
|
namespace CliFx.Models
|
||||||
|
|
||||||
namespace CliFx.Models
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Metadata associated with an application.
|
/// Metadata associated with an application.
|
||||||
@@ -25,17 +23,17 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application description.
|
/// Application description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; }
|
public string? Description { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApplicationMetadata(string title, string executableName, string versionText, string description)
|
public ApplicationMetadata(string title, string executableName, string versionText, string? description)
|
||||||
{
|
{
|
||||||
Title = title.GuardNotNull(nameof(title));
|
Title = title;
|
||||||
ExecutableName = executableName.GuardNotNull(nameof(executableName));
|
ExecutableName = executableName;
|
||||||
VersionText = versionText.GuardNotNull(nameof(versionText));
|
VersionText = versionText;
|
||||||
Description = description; // can be null
|
Description = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace CliFx.Models
|
|||||||
/// Specified command name.
|
/// Specified command name.
|
||||||
/// Can be null if command was not specified.
|
/// Can be null if command was not specified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string CommandName { get; }
|
public string? CommandName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specified directives.
|
/// Specified directives.
|
||||||
@@ -25,20 +25,43 @@ namespace CliFx.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<CommandOptionInput> Options { get; }
|
public IReadOnlyList<CommandOptionInput> Options { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Environment variables available when the command was parsed
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> EnvironmentVariables { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandInput"/>.
|
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options)
|
public CommandInput(string? commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options,
|
||||||
|
IReadOnlyDictionary<string, string> environmentVariables)
|
||||||
{
|
{
|
||||||
CommandName = commandName; // can be null
|
CommandName = commandName;
|
||||||
Directives = directives.GuardNotNull(nameof(directives));
|
Directives = directives;
|
||||||
Options = options.GuardNotNull(nameof(options));
|
Options = options;
|
||||||
|
EnvironmentVariables = environmentVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandInput"/>.
|
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandInput(string commandName, IReadOnlyList<CommandOptionInput> options)
|
public CommandInput(string? commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options)
|
||||||
|
: this(commandName, directives, options, EmptyEnvironmentVariables)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CommandInput(string? commandName, IReadOnlyList<CommandOptionInput> options, IReadOnlyDictionary<string, string> environmentVariables)
|
||||||
|
: this(commandName, EmptyDirectives, options, environmentVariables)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CommandInput(string? commandName, IReadOnlyList<CommandOptionInput> options)
|
||||||
: this(commandName, EmptyDirectives, options)
|
: this(commandName, EmptyDirectives, options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -54,7 +77,7 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandInput"/>.
|
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandInput(string commandName)
|
public CommandInput(string? commandName)
|
||||||
: this(commandName, EmptyOptions)
|
: this(commandName, EmptyOptions)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -64,7 +87,7 @@ namespace CliFx.Models
|
|||||||
{
|
{
|
||||||
var buffer = new StringBuilder();
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
if (!CommandName.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(CommandName))
|
||||||
buffer.Append(CommandName);
|
buffer.Append(CommandName);
|
||||||
|
|
||||||
foreach (var directive in Directives)
|
foreach (var directive in Directives)
|
||||||
@@ -87,6 +110,7 @@ namespace CliFx.Models
|
|||||||
{
|
{
|
||||||
private static readonly IReadOnlyList<string> EmptyDirectives = new string[0];
|
private static readonly IReadOnlyList<string> EmptyDirectives = new string[0];
|
||||||
private static readonly IReadOnlyList<CommandOptionInput> EmptyOptions = new CommandOptionInput[0];
|
private static readonly IReadOnlyList<CommandOptionInput> EmptyOptions = new CommandOptionInput[0];
|
||||||
|
private static readonly IReadOnlyDictionary<string, string> EmptyEnvironmentVariables = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Empty input.
|
/// Empty input.
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ namespace CliFx.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionInput(string alias, IReadOnlyList<string> values)
|
public CommandOptionInput(string alias, IReadOnlyList<string> values)
|
||||||
{
|
{
|
||||||
Alias = alias.GuardNotNull(nameof(alias));
|
Alias = alias;
|
||||||
Values = values.GuardNotNull(nameof(values));
|
Values = values;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Models
|
namespace CliFx.Models
|
||||||
{
|
{
|
||||||
@@ -12,12 +11,12 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Underlying property.
|
/// Underlying property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PropertyInfo Property { get; }
|
public PropertyInfo? Property { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option name.
|
/// Option name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option short name.
|
/// Option short name.
|
||||||
@@ -32,18 +31,24 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option description.
|
/// Option description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; }
|
public string? Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional environment variable name that will be used as fallback value if no option value is specified.
|
||||||
|
/// </summary>
|
||||||
|
public string? EnvironmentVariableName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandOptionSchema"/>.
|
/// Initializes an instance of <see cref="CommandOptionSchema"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionSchema(PropertyInfo property, string name, char? shortName, bool isRequired, string description)
|
public CommandOptionSchema(PropertyInfo? property, string? name, char? shortName, bool isRequired, string? description, string? environmentVariableName)
|
||||||
{
|
{
|
||||||
Property = property; // can be null
|
Property = property;
|
||||||
Name = name; // can be null
|
Name = name;
|
||||||
ShortName = shortName; // can be null
|
ShortName = shortName;
|
||||||
IsRequired = isRequired;
|
IsRequired = isRequired;
|
||||||
Description = description; // can be null
|
Description = description;
|
||||||
|
EnvironmentVariableName = environmentVariableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -54,10 +59,10 @@ namespace CliFx.Models
|
|||||||
if (IsRequired)
|
if (IsRequired)
|
||||||
buffer.Append('*');
|
buffer.Append('*');
|
||||||
|
|
||||||
if (!Name.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(Name))
|
||||||
buffer.Append(Name);
|
buffer.Append(Name);
|
||||||
|
|
||||||
if (!Name.IsNullOrWhiteSpace() && ShortName != null)
|
if (!string.IsNullOrWhiteSpace(Name) && ShortName != null)
|
||||||
buffer.Append('|');
|
buffer.Append('|');
|
||||||
|
|
||||||
if (ShortName != null)
|
if (ShortName != null)
|
||||||
@@ -75,9 +80,9 @@ namespace CliFx.Models
|
|||||||
// ...in CliApplication (when reading) and HelpTextRenderer (when writing).
|
// ...in CliApplication (when reading) and HelpTextRenderer (when writing).
|
||||||
|
|
||||||
internal static CommandOptionSchema HelpOption { get; } =
|
internal static CommandOptionSchema HelpOption { get; } =
|
||||||
new CommandOptionSchema(null, "help", 'h', false, "Shows help text.");
|
new CommandOptionSchema(null, "help", 'h', false, "Shows help text.", null);
|
||||||
|
|
||||||
internal static CommandOptionSchema VersionOption { get; } =
|
internal static CommandOptionSchema VersionOption { get; } =
|
||||||
new CommandOptionSchema(null, "version", null, false, "Shows version information.");
|
new CommandOptionSchema(null, "version", null, false, "Shows version information.", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,17 +13,17 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Underlying type.
|
/// Underlying type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type Type { get; }
|
public Type? Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command name.
|
/// Command name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command description.
|
/// Command description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; }
|
public string? Description { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command options.
|
/// Command options.
|
||||||
@@ -33,12 +33,12 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandSchema"/>.
|
/// Initializes an instance of <see cref="CommandSchema"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandSchema(Type type, string name, string description, IReadOnlyList<CommandOptionSchema> options)
|
public CommandSchema(Type? type, string? name, string? description, IReadOnlyList<CommandOptionSchema> options)
|
||||||
{
|
{
|
||||||
Type = type; // can be null
|
Type = type;
|
||||||
Name = name; // can be null
|
Name = name;
|
||||||
Description = description; // can be null
|
Description = description;
|
||||||
Options = options.GuardNotNull(nameof(options));
|
Options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -46,7 +46,7 @@ namespace CliFx.Models
|
|||||||
{
|
{
|
||||||
var buffer = new StringBuilder();
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
if (!Name.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(Name))
|
||||||
buffer.Append(Name);
|
buffer.Append(Name);
|
||||||
|
|
||||||
foreach (var option in Options)
|
foreach (var option in Options)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using CliFx.Internal;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Models
|
namespace CliFx.Models
|
||||||
{
|
{
|
||||||
@@ -13,13 +13,11 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds a command that has specified name, or null if not found.
|
/// Finds a command that has specified name, or null if not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static CommandSchema FindByName(this IReadOnlyList<CommandSchema> commandSchemas, string commandName)
|
public static CommandSchema? FindByName(this IReadOnlyList<CommandSchema> commandSchemas, string? commandName)
|
||||||
{
|
{
|
||||||
commandSchemas.GuardNotNull(nameof(commandSchemas));
|
|
||||||
|
|
||||||
// If looking for default command, don't compare names directly
|
// If looking for default command, don't compare names directly
|
||||||
// ...because null and empty are both valid names for default command
|
// ...because null and empty are both valid names for default command
|
||||||
if (commandName.IsNullOrWhiteSpace())
|
if (string.IsNullOrWhiteSpace(commandName))
|
||||||
return commandSchemas.FirstOrDefault(c => c.IsDefault());
|
return commandSchemas.FirstOrDefault(c => c.IsDefault());
|
||||||
|
|
||||||
return commandSchemas.FirstOrDefault(c => string.Equals(c.Name, commandName, StringComparison.OrdinalIgnoreCase));
|
return commandSchemas.FirstOrDefault(c => string.Equals(c.Name, commandName, StringComparison.OrdinalIgnoreCase));
|
||||||
@@ -28,12 +26,10 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds parent command to the command that has specified name, or null if not found.
|
/// Finds parent command to the command that has specified name, or null if not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static CommandSchema FindParent(this IReadOnlyList<CommandSchema> commandSchemas, string commandName)
|
public static CommandSchema? FindParent(this IReadOnlyList<CommandSchema> commandSchemas, string? commandName)
|
||||||
{
|
{
|
||||||
commandSchemas.GuardNotNull(nameof(commandSchemas));
|
|
||||||
|
|
||||||
// If command has no name, it's the default command so it doesn't have a parent
|
// If command has no name, it's the default command so it doesn't have a parent
|
||||||
if (commandName.IsNullOrWhiteSpace())
|
if (string.IsNullOrWhiteSpace(commandName))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Repeatedly cut off individual words from the name until we find a command with that name
|
// Repeatedly cut off individual words from the name until we find a command with that name
|
||||||
@@ -56,12 +52,9 @@ namespace CliFx.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool MatchesAlias(this CommandOptionSchema optionSchema, string alias)
|
public static bool MatchesAlias(this CommandOptionSchema optionSchema, string alias)
|
||||||
{
|
{
|
||||||
optionSchema.GuardNotNull(nameof(optionSchema));
|
|
||||||
alias.GuardNotNull(nameof(alias));
|
|
||||||
|
|
||||||
// Compare against name. Case is ignored.
|
// Compare against name. Case is ignored.
|
||||||
var matchesByName =
|
var matchesByName =
|
||||||
!optionSchema.Name.IsNullOrWhiteSpace() &&
|
!string.IsNullOrWhiteSpace(optionSchema.Name) &&
|
||||||
string.Equals(optionSchema.Name, alias, StringComparison.OrdinalIgnoreCase);
|
string.Equals(optionSchema.Name, alias, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Compare against short name. Case is NOT ignored.
|
// Compare against short name. Case is NOT ignored.
|
||||||
@@ -71,17 +64,12 @@ namespace CliFx.Models
|
|||||||
|
|
||||||
return matchesByName || matchesByShortName;
|
return matchesByName || matchesByShortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds an option that matches specified alias, or null if not found.
|
|
||||||
/// </summary>
|
|
||||||
public static CommandOptionSchema FindByAlias(this IReadOnlyList<CommandOptionSchema> optionSchemas, string alias)
|
|
||||||
{
|
|
||||||
optionSchemas.GuardNotNull(nameof(optionSchemas));
|
|
||||||
alias.GuardNotNull(nameof(alias));
|
|
||||||
|
|
||||||
return optionSchemas.FirstOrDefault(o => o.MatchesAlias(alias));
|
/// <summary>
|
||||||
}
|
/// Finds an option input that matches the option schema specified, or null if not found.
|
||||||
|
/// </summary>
|
||||||
|
public static CommandOptionInput? FindByOptionSchema(this IReadOnlyList<CommandOptionInput> optionInputs, CommandOptionSchema optionSchema) =>
|
||||||
|
optionInputs.FirstOrDefault(o => optionSchema.MatchesAlias(o.Alias));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets valid aliases for the option.
|
/// Gets valid aliases for the option.
|
||||||
@@ -90,8 +78,8 @@ namespace CliFx.Models
|
|||||||
{
|
{
|
||||||
var result = new List<string>(2);
|
var result = new List<string>(2);
|
||||||
|
|
||||||
if (!optionSchema.Name.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(optionSchema.Name))
|
||||||
result.Add(optionSchema.Name);
|
result.Add(optionSchema.Name!);
|
||||||
|
|
||||||
if (optionSchema.ShortName != null)
|
if (optionSchema.ShortName != null)
|
||||||
result.Add(optionSchema.ShortName.Value.AsString());
|
result.Add(optionSchema.ShortName.Value.AsString());
|
||||||
@@ -102,37 +90,25 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether a command was specified in the input.
|
/// Gets whether a command was specified in the input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsCommandSpecified(this CommandInput commandInput)
|
public static bool IsCommandSpecified(this CommandInput commandInput) => !string.IsNullOrWhiteSpace(commandInput.CommandName);
|
||||||
{
|
|
||||||
commandInput.GuardNotNull(nameof(commandInput));
|
|
||||||
return !commandInput.CommandName.IsNullOrWhiteSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether debug directive was specified in the input.
|
/// Gets whether debug directive was specified in the input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsDebugDirectiveSpecified(this CommandInput commandInput)
|
public static bool IsDebugDirectiveSpecified(this CommandInput commandInput) =>
|
||||||
{
|
commandInput.Directives.Contains("debug", StringComparer.OrdinalIgnoreCase);
|
||||||
commandInput.GuardNotNull(nameof(commandInput));
|
|
||||||
return commandInput.Directives.Contains("debug", StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether preview directive was specified in the input.
|
/// Gets whether preview directive was specified in the input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsPreviewDirectiveSpecified(this CommandInput commandInput)
|
public static bool IsPreviewDirectiveSpecified(this CommandInput commandInput) =>
|
||||||
{
|
commandInput.Directives.Contains("preview", StringComparer.OrdinalIgnoreCase);
|
||||||
commandInput.GuardNotNull(nameof(commandInput));
|
|
||||||
return commandInput.Directives.Contains("preview", StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether help option was specified in the input.
|
/// Gets whether help option was specified in the input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsHelpOptionSpecified(this CommandInput commandInput)
|
public static bool IsHelpOptionSpecified(this CommandInput commandInput)
|
||||||
{
|
{
|
||||||
commandInput.GuardNotNull(nameof(commandInput));
|
|
||||||
|
|
||||||
var firstOption = commandInput.Options.FirstOrDefault();
|
var firstOption = commandInput.Options.FirstOrDefault();
|
||||||
return firstOption != null && CommandOptionSchema.HelpOption.MatchesAlias(firstOption.Alias);
|
return firstOption != null && CommandOptionSchema.HelpOption.MatchesAlias(firstOption.Alias);
|
||||||
}
|
}
|
||||||
@@ -142,8 +118,6 @@ namespace CliFx.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsVersionOptionSpecified(this CommandInput commandInput)
|
public static bool IsVersionOptionSpecified(this CommandInput commandInput)
|
||||||
{
|
{
|
||||||
commandInput.GuardNotNull(nameof(commandInput));
|
|
||||||
|
|
||||||
var firstOption = commandInput.Options.FirstOrDefault();
|
var firstOption = commandInput.Options.FirstOrDefault();
|
||||||
return firstOption != null && CommandOptionSchema.VersionOption.MatchesAlias(firstOption.Alias);
|
return firstOption != null && CommandOptionSchema.VersionOption.MatchesAlias(firstOption.Alias);
|
||||||
}
|
}
|
||||||
@@ -151,10 +125,6 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether this command is the default command, i.e. without a name.
|
/// Gets whether this command is the default command, i.e. without a name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsDefault(this CommandSchema commandSchema)
|
public static bool IsDefault(this CommandSchema commandSchema) => string.IsNullOrWhiteSpace(commandSchema.Name);
|
||||||
{
|
|
||||||
commandSchema.GuardNotNull(nameof(commandSchema));
|
|
||||||
return commandSchema.Name.IsNullOrWhiteSpace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Models
|
namespace CliFx.Models
|
||||||
{
|
{
|
||||||
@@ -30,9 +29,9 @@ namespace CliFx.Models
|
|||||||
IReadOnlyList<CommandSchema> availableCommandSchemas,
|
IReadOnlyList<CommandSchema> availableCommandSchemas,
|
||||||
CommandSchema targetCommandSchema)
|
CommandSchema targetCommandSchema)
|
||||||
{
|
{
|
||||||
ApplicationMetadata = applicationMetadata.GuardNotNull(nameof(applicationMetadata));
|
ApplicationMetadata = applicationMetadata;
|
||||||
AvailableCommandSchemas = availableCommandSchemas.GuardNotNull(nameof(availableCommandSchemas));
|
AvailableCommandSchemas = availableCommandSchemas;
|
||||||
TargetCommandSchema = targetCommandSchema.GuardNotNull(nameof(targetCommandSchema));
|
TargetCommandSchema = targetCommandSchema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Internal;
|
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
@@ -10,10 +9,6 @@ namespace CliFx.Services
|
|||||||
public class CommandFactory : ICommandFactory
|
public class CommandFactory : ICommandFactory
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICommand CreateCommand(CommandSchema commandSchema)
|
public ICommand CreateCommand(CommandSchema commandSchema) => (ICommand) Activator.CreateInstance(commandSchema.Type);
|
||||||
{
|
|
||||||
commandSchema.GuardNotNull(nameof(commandSchema));
|
|
||||||
return (ICommand) Activator.CreateInstance(commandSchema.Type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,42 +11,65 @@ namespace CliFx.Services
|
|||||||
public class CommandInitializer : ICommandInitializer
|
public class CommandInitializer : ICommandInitializer
|
||||||
{
|
{
|
||||||
private readonly ICommandOptionInputConverter _commandOptionInputConverter;
|
private readonly ICommandOptionInputConverter _commandOptionInputConverter;
|
||||||
|
private readonly IEnvironmentVariablesParser _environmentVariablesParser;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter)
|
public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter, IEnvironmentVariablesParser environmentVariablesParser)
|
||||||
|
{
|
||||||
|
_commandOptionInputConverter = commandOptionInputConverter;
|
||||||
|
_environmentVariablesParser = environmentVariablesParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CommandInitializer(IEnvironmentVariablesParser environmentVariablesParser)
|
||||||
|
: this(new CommandOptionInputConverter(), environmentVariablesParser)
|
||||||
{
|
{
|
||||||
_commandOptionInputConverter = commandOptionInputConverter.GuardNotNull(nameof(commandOptionInputConverter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandInitializer()
|
public CommandInitializer()
|
||||||
: this(new CommandOptionInputConverter())
|
: this(new CommandOptionInputConverter(), new EnvironmentVariablesParser())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void InitializeCommand(ICommand command, CommandSchema commandSchema, CommandInput commandInput)
|
public void InitializeCommand(ICommand command, CommandSchema commandSchema, CommandInput commandInput)
|
||||||
{
|
{
|
||||||
command.GuardNotNull(nameof(command));
|
|
||||||
commandSchema.GuardNotNull(nameof(commandSchema));
|
|
||||||
commandInput.GuardNotNull(nameof(commandInput));
|
|
||||||
|
|
||||||
// Keep track of unset required options to report an error at a later stage
|
// Keep track of unset required options to report an error at a later stage
|
||||||
var unsetRequiredOptions = commandSchema.Options.Where(o => o.IsRequired).ToList();
|
var unsetRequiredOptions = commandSchema.Options.Where(o => o.IsRequired).ToList();
|
||||||
|
|
||||||
// Set command options
|
//Set command options
|
||||||
foreach (var optionInput in commandInput.Options)
|
foreach (var optionSchema in commandSchema.Options)
|
||||||
{
|
{
|
||||||
// Find matching option schema for this option input
|
// Ignore special options that are not backed by a property
|
||||||
var optionSchema = commandSchema.Options.FindByAlias(optionInput.Alias);
|
if (optionSchema.Property == null)
|
||||||
if (optionSchema == null)
|
continue;
|
||||||
|
|
||||||
|
//Find matching option input
|
||||||
|
var optionInput = commandInput.Options.FindByOptionSchema(optionSchema);
|
||||||
|
|
||||||
|
//If no option input is available fall back to environment variable values
|
||||||
|
if (optionInput == null && !string.IsNullOrWhiteSpace(optionSchema.EnvironmentVariableName))
|
||||||
|
{
|
||||||
|
var fallbackEnvironmentVariableExists = commandInput.EnvironmentVariables.ContainsKey(optionSchema.EnvironmentVariableName!);
|
||||||
|
|
||||||
|
//If no environment variable is found or there is no valid value for this option skip it
|
||||||
|
if (!fallbackEnvironmentVariableExists || string.IsNullOrWhiteSpace(commandInput.EnvironmentVariables[optionSchema.EnvironmentVariableName!]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
optionInput = _environmentVariablesParser.GetCommandOptionInputFromEnvironmentVariable(commandInput.EnvironmentVariables[optionSchema.EnvironmentVariableName!], optionSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
//No fallback available and no option input was specified, skip option
|
||||||
|
if (optionInput == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Convert option to the type of the underlying property
|
|
||||||
var convertedValue = _commandOptionInputConverter.ConvertOptionInput(optionInput, optionSchema.Property.PropertyType);
|
var convertedValue = _commandOptionInputConverter.ConvertOptionInput(optionInput, optionSchema.Property.PropertyType);
|
||||||
|
|
||||||
// Set value of the underlying property
|
// Set value of the underlying property
|
||||||
|
|||||||
@@ -12,11 +12,27 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommandInputParser : ICommandInputParser
|
public class CommandInputParser : ICommandInputParser
|
||||||
{
|
{
|
||||||
|
private readonly IEnvironmentVariablesProvider _environmentVariablesProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandInputParser"/>
|
||||||
|
/// </summary>
|
||||||
|
public CommandInputParser(IEnvironmentVariablesProvider environmentVariablesProvider)
|
||||||
|
{
|
||||||
|
_environmentVariablesProvider = environmentVariablesProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="CommandInputParser"/>
|
||||||
|
/// </summary>
|
||||||
|
public CommandInputParser()
|
||||||
|
: this(new EnvironmentVariablesProvider())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CommandInput ParseCommandInput(IReadOnlyList<string> commandLineArguments)
|
public CommandInput ParseCommandInput(IReadOnlyList<string> commandLineArguments)
|
||||||
{
|
{
|
||||||
commandLineArguments.GuardNotNull(nameof(commandLineArguments));
|
|
||||||
|
|
||||||
var commandNameBuilder = new StringBuilder();
|
var commandNameBuilder = new StringBuilder();
|
||||||
var directives = new List<string>();
|
var directives = new List<string>();
|
||||||
var optionsDic = new Dictionary<string, List<string>>();
|
var optionsDic = new Dictionary<string, List<string>>();
|
||||||
@@ -51,7 +67,7 @@ namespace CliFx.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encountered directive or (part of) command name
|
// Encountered directive or (part of) command name
|
||||||
else if (lastOptionAlias.IsNullOrWhiteSpace())
|
else if (string.IsNullOrWhiteSpace(lastOptionAlias))
|
||||||
{
|
{
|
||||||
if (commandLineArgument.StartsWith("[", StringComparison.OrdinalIgnoreCase) &&
|
if (commandLineArgument.StartsWith("[", StringComparison.OrdinalIgnoreCase) &&
|
||||||
commandLineArgument.EndsWith("]", StringComparison.OrdinalIgnoreCase))
|
commandLineArgument.EndsWith("]", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -69,7 +85,7 @@ namespace CliFx.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encountered option value
|
// Encountered option value
|
||||||
else if (!lastOptionAlias.IsNullOrWhiteSpace())
|
else if (!string.IsNullOrWhiteSpace(lastOptionAlias))
|
||||||
{
|
{
|
||||||
optionsDic[lastOptionAlias].Add(commandLineArgument);
|
optionsDic[lastOptionAlias].Add(commandLineArgument);
|
||||||
}
|
}
|
||||||
@@ -78,7 +94,9 @@ namespace CliFx.Services
|
|||||||
var commandName = commandNameBuilder.Length > 0 ? commandNameBuilder.ToString() : null;
|
var commandName = commandNameBuilder.Length > 0 ? commandNameBuilder.ToString() : null;
|
||||||
var options = optionsDic.Select(p => new CommandOptionInput(p.Key, p.Value)).ToArray();
|
var options = optionsDic.Select(p => new CommandOptionInput(p.Key, p.Value)).ToArray();
|
||||||
|
|
||||||
return new CommandInput(commandName, directives, options);
|
var environmentVariables = _environmentVariablesProvider.GetEnvironmentVariables();
|
||||||
|
|
||||||
|
return new CommandInput(commandName, directives, options, environmentVariables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionInputConverter(IFormatProvider formatProvider)
|
public CommandOptionInputConverter(IFormatProvider formatProvider)
|
||||||
{
|
{
|
||||||
_formatProvider = formatProvider.GuardNotNull(nameof(formatProvider));
|
_formatProvider = formatProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,7 +31,10 @@ namespace CliFx.Services
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private object ConvertValue(string value, Type targetType)
|
/// <summary>
|
||||||
|
/// Converts a single string value to specified target type.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual object? ConvertValue(string value, Type targetType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,7 +44,7 @@ namespace CliFx.Services
|
|||||||
|
|
||||||
// Bool
|
// Bool
|
||||||
if (targetType == typeof(bool))
|
if (targetType == typeof(bool))
|
||||||
return value.IsNullOrWhiteSpace() || bool.Parse(value);
|
return string.IsNullOrWhiteSpace(value) || bool.Parse(value);
|
||||||
|
|
||||||
// Char
|
// Char
|
||||||
if (targetType == typeof(char))
|
if (targetType == typeof(char))
|
||||||
@@ -108,9 +111,9 @@ namespace CliFx.Services
|
|||||||
return Enum.Parse(targetType, value, true);
|
return Enum.Parse(targetType, value, true);
|
||||||
|
|
||||||
// Nullable
|
// Nullable
|
||||||
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
|
var nullableUnderlyingType = targetType.GetNullableUnderlyingType();
|
||||||
if (nullableUnderlyingType != null)
|
if (nullableUnderlyingType != null)
|
||||||
return !value.IsNullOrWhiteSpace() ? ConvertValue(value, nullableUnderlyingType) : null;
|
return !string.IsNullOrWhiteSpace(value) ? ConvertValue(value, nullableUnderlyingType) : null;
|
||||||
|
|
||||||
// Has a constructor that accepts a single string
|
// Has a constructor that accepts a single string
|
||||||
var stringConstructor = GetStringConstructor(targetType);
|
var stringConstructor = GetStringConstructor(targetType);
|
||||||
@@ -126,48 +129,63 @@ namespace CliFx.Services
|
|||||||
var parseMethod = GetStaticParseMethod(targetType);
|
var parseMethod = GetStaticParseMethod(targetType);
|
||||||
if (parseMethod != null)
|
if (parseMethod != null)
|
||||||
return parseMethod.Invoke(null, new object[] {value});
|
return parseMethod.Invoke(null, new object[] {value});
|
||||||
|
|
||||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
// Wrap and rethrow exceptions that occur when trying to convert the value
|
||||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].", ex);
|
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Throw if we can't find a way to convert the value
|
||||||
|
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object ConvertOptionInput(CommandOptionInput optionInput, Type targetType)
|
public virtual object? ConvertOptionInput(CommandOptionInput optionInput, Type targetType)
|
||||||
{
|
{
|
||||||
optionInput.GuardNotNull(nameof(optionInput));
|
// Get the underlying type of IEnumerable<T> if it's implemented by the target type.
|
||||||
targetType.GuardNotNull(nameof(targetType));
|
// Ignore string type because it's IEnumerable<T> but we don't treat it as such.
|
||||||
|
var enumerableUnderlyingType = targetType != typeof(string) ? targetType.GetEnumerableUnderlyingType() : null;
|
||||||
|
|
||||||
// Single value
|
// Convert to a non-enumerable type
|
||||||
if (optionInput.Values.Count <= 1)
|
if (enumerableUnderlyingType == null)
|
||||||
{
|
{
|
||||||
|
// Throw if provided with more than 1 value
|
||||||
|
if (optionInput.Values.Count > 1)
|
||||||
|
{
|
||||||
|
throw new CliFxException(
|
||||||
|
$"Can't convert a sequence of values [{optionInput.Values.JoinToString(", ")}] " +
|
||||||
|
$"to non-enumerable type [{targetType}].");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve a single value and convert
|
||||||
var value = optionInput.Values.SingleOrDefault();
|
var value = optionInput.Values.SingleOrDefault();
|
||||||
return ConvertValue(value, targetType);
|
return ConvertValue(value, targetType);
|
||||||
}
|
}
|
||||||
// Multiple values
|
// Convert to an enumerable type
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Determine underlying type of elements inside the target collection type
|
// Convert values to the underlying enumerable type and cast it to dynamic array
|
||||||
var underlyingType = targetType.GetEnumerableUnderlyingType() ?? typeof(object);
|
var convertedValues = optionInput.Values
|
||||||
|
.Select(v => ConvertValue(v, enumerableUnderlyingType))
|
||||||
|
.ToNonGenericArray(enumerableUnderlyingType);
|
||||||
|
|
||||||
// Convert values to that type
|
// Get the type of produced array
|
||||||
var convertedValues = optionInput.Values.Select(v => ConvertValue(v, underlyingType)).ToNonGenericArray(underlyingType);
|
|
||||||
var convertedValuesType = convertedValues.GetType();
|
var convertedValuesType = convertedValues.GetType();
|
||||||
|
|
||||||
// Assignable from array of values (e.g. T[], IReadOnlyList<T>, IEnumerable<T>)
|
// Try to assign the array (works for T[], IReadOnlyList<T>, IEnumerable<T>, etc)
|
||||||
if (targetType.IsAssignableFrom(convertedValuesType))
|
if (targetType.IsAssignableFrom(convertedValuesType))
|
||||||
return convertedValues;
|
return convertedValues;
|
||||||
|
|
||||||
// Has a constructor that accepts an array of values (e.g. HashSet<T>, List<T>)
|
// Try to inject the array into the constructor (works for HashSet<T>, List<T>, etc)
|
||||||
var arrayConstructor = targetType.GetConstructor(new[] {convertedValuesType});
|
var arrayConstructor = targetType.GetConstructor(new[] {convertedValuesType});
|
||||||
if (arrayConstructor != null)
|
if (arrayConstructor != null)
|
||||||
return arrayConstructor.Invoke(new object[] {convertedValues});
|
return arrayConstructor.Invoke(new object[] {convertedValues});
|
||||||
|
|
||||||
|
// Throw if we can't find a way to convert the values
|
||||||
throw new CliFxException(
|
throw new CliFxException(
|
||||||
$"Can't convert sequence of values [{optionInput.Values.JoinToString(", ")}] to type [{targetType}].");
|
$"Can't convert a sequence of values [{optionInput.Values.JoinToString(", ")}] " +
|
||||||
|
$"to type [{targetType}].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,12 @@ namespace CliFx.Services
|
|||||||
attribute.Name,
|
attribute.Name,
|
||||||
attribute.ShortName,
|
attribute.ShortName,
|
||||||
attribute.IsRequired,
|
attribute.IsRequired,
|
||||||
attribute.Description);
|
attribute.Description,
|
||||||
|
attribute.EnvironmentVariableName);
|
||||||
|
|
||||||
// Make sure there are no other options with the same name
|
// Make sure there are no other options with the same name
|
||||||
var existingOptionWithSameName = result
|
var existingOptionWithSameName = result
|
||||||
.Where(o => !o.Name.IsNullOrWhiteSpace())
|
.Where(o => !string.IsNullOrWhiteSpace(o.Name))
|
||||||
.FirstOrDefault(o => string.Equals(o.Name, optionSchema.Name, StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(o => string.Equals(o.Name, optionSchema.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (existingOptionWithSameName != null)
|
if (existingOptionWithSameName != null)
|
||||||
@@ -67,8 +68,6 @@ namespace CliFx.Services
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<CommandSchema> GetCommandSchemas(IReadOnlyList<Type> commandTypes)
|
public IReadOnlyList<CommandSchema> GetCommandSchemas(IReadOnlyList<Type> commandTypes)
|
||||||
{
|
{
|
||||||
commandTypes.GuardNotNull(nameof(commandTypes));
|
|
||||||
|
|
||||||
// Make sure there's at least one command defined
|
// Make sure there's at least one command defined
|
||||||
if (!commandTypes.Any())
|
if (!commandTypes.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Internal;
|
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
@@ -16,14 +15,10 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DelegateCommandFactory(Func<CommandSchema, ICommand> factoryMethod)
|
public DelegateCommandFactory(Func<CommandSchema, ICommand> factoryMethod)
|
||||||
{
|
{
|
||||||
_factoryMethod = factoryMethod.GuardNotNull(nameof(factoryMethod));
|
_factoryMethod = factoryMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ICommand CreateCommand(CommandSchema commandSchema)
|
public ICommand CreateCommand(CommandSchema commandSchema) => _factoryMethod(commandSchema);
|
||||||
{
|
|
||||||
commandSchema.GuardNotNull(nameof(commandSchema));
|
|
||||||
return _factoryMethod(commandSchema);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
27
CliFx/Services/EnvironmentVariablesParser.cs
Normal file
27
CliFx/Services/EnvironmentVariablesParser.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using CliFx.Internal;
|
||||||
|
using CliFx.Models;
|
||||||
|
|
||||||
|
namespace CliFx.Services
|
||||||
|
{
|
||||||
|
/// <inheritdoct />
|
||||||
|
public class EnvironmentVariablesParser : IEnvironmentVariablesParser
|
||||||
|
{
|
||||||
|
/// <inheritdoct />
|
||||||
|
public CommandOptionInput GetCommandOptionInputFromEnvironmentVariable(string environmentVariableValue, CommandOptionSchema targetOptionSchema)
|
||||||
|
{
|
||||||
|
//If the option is not a collection do not split environment variable values
|
||||||
|
var optionIsCollection = targetOptionSchema.Property != null && targetOptionSchema.Property.PropertyType.IsCollection();
|
||||||
|
|
||||||
|
if (!optionIsCollection) return new CommandOptionInput(targetOptionSchema.EnvironmentVariableName, environmentVariableValue);
|
||||||
|
|
||||||
|
//If the option is a collection split the values using System separator, empty values are discarded
|
||||||
|
var environmentVariableValues = environmentVariableValue.Split(Path.PathSeparator)
|
||||||
|
.Where(v => !string.IsNullOrWhiteSpace(v))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new CommandOptionInput(targetOptionSchema.EnvironmentVariableName, environmentVariableValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
CliFx/Services/EnvironmentVariablesProvider.cs
Normal file
36
CliFx/Services/EnvironmentVariablesProvider.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
namespace CliFx.Services
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class EnvironmentVariablesProvider : IEnvironmentVariablesProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyDictionary<string, string> GetEnvironmentVariables()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var environmentVariables = Environment.GetEnvironmentVariables();
|
||||||
|
|
||||||
|
//Constructing the dictionary manually allows to specify a key comparer that ignores case
|
||||||
|
//This allows to ignore casing when looking for a fallback environment variable of an option
|
||||||
|
var environmentVariablesAsDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
//Type DictionaryEntry must be explicitly used otherwise it will enumerate as a collection of objects
|
||||||
|
foreach (DictionaryEntry environmentVariable in environmentVariables)
|
||||||
|
{
|
||||||
|
environmentVariablesAsDictionary.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return environmentVariablesAsDictionary;
|
||||||
|
}
|
||||||
|
catch (SecurityException)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
{
|
{
|
||||||
@@ -13,9 +12,6 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void WithForegroundColor(this IConsole console, ConsoleColor foregroundColor, Action action)
|
public static void WithForegroundColor(this IConsole console, ConsoleColor foregroundColor, Action action)
|
||||||
{
|
{
|
||||||
console.GuardNotNull(nameof(console));
|
|
||||||
action.GuardNotNull(nameof(action));
|
|
||||||
|
|
||||||
var lastColor = console.ForegroundColor;
|
var lastColor = console.ForegroundColor;
|
||||||
console.ForegroundColor = foregroundColor;
|
console.ForegroundColor = foregroundColor;
|
||||||
|
|
||||||
@@ -29,9 +25,6 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void WithBackgroundColor(this IConsole console, ConsoleColor backgroundColor, Action action)
|
public static void WithBackgroundColor(this IConsole console, ConsoleColor backgroundColor, Action action)
|
||||||
{
|
{
|
||||||
console.GuardNotNull(nameof(console));
|
|
||||||
action.GuardNotNull(nameof(action));
|
|
||||||
|
|
||||||
var lastColor = console.BackgroundColor;
|
var lastColor = console.BackgroundColor;
|
||||||
console.BackgroundColor = backgroundColor;
|
console.BackgroundColor = backgroundColor;
|
||||||
|
|
||||||
@@ -43,12 +36,7 @@ namespace CliFx.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets console foreground and background colors, executes specified action, and sets the colors back to the original values.
|
/// Sets console foreground and background colors, executes specified action, and sets the colors back to the original values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void WithColors(this IConsole console, ConsoleColor foregroundColor, ConsoleColor backgroundColor, Action action)
|
public static void WithColors(this IConsole console, ConsoleColor foregroundColor, ConsoleColor backgroundColor, Action action) =>
|
||||||
{
|
|
||||||
console.GuardNotNull(nameof(console));
|
|
||||||
action.GuardNotNull(nameof(action));
|
|
||||||
|
|
||||||
console.WithForegroundColor(foregroundColor, () => console.WithBackgroundColor(backgroundColor, action));
|
console.WithForegroundColor(foregroundColor, () => console.WithBackgroundColor(backgroundColor, action));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,9 +14,6 @@ namespace CliFx.Services
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void RenderHelpText(IConsole console, HelpTextSource source)
|
public void RenderHelpText(IConsole console, HelpTextSource source)
|
||||||
{
|
{
|
||||||
console.GuardNotNull(nameof(console));
|
|
||||||
source.GuardNotNull(nameof(source));
|
|
||||||
|
|
||||||
// Track position
|
// Track position
|
||||||
var column = 0;
|
var column = 0;
|
||||||
var row = 0;
|
var row = 0;
|
||||||
@@ -105,7 +102,7 @@ namespace CliFx.Services
|
|||||||
RenderNewLine();
|
RenderNewLine();
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if (!source.ApplicationMetadata.Description.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(source.ApplicationMetadata.Description))
|
||||||
{
|
{
|
||||||
Render(source.ApplicationMetadata.Description);
|
Render(source.ApplicationMetadata.Description);
|
||||||
RenderNewLine();
|
RenderNewLine();
|
||||||
@@ -114,7 +111,7 @@ namespace CliFx.Services
|
|||||||
|
|
||||||
void RenderDescription()
|
void RenderDescription()
|
||||||
{
|
{
|
||||||
if (source.TargetCommandSchema.Description.IsNullOrWhiteSpace())
|
if (string.IsNullOrWhiteSpace(source.TargetCommandSchema.Description))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Margin
|
// Margin
|
||||||
@@ -142,7 +139,7 @@ namespace CliFx.Services
|
|||||||
Render(source.ApplicationMetadata.ExecutableName);
|
Render(source.ApplicationMetadata.ExecutableName);
|
||||||
|
|
||||||
// Command name
|
// Command name
|
||||||
if (!source.TargetCommandSchema.IsDefault())
|
if (!string.IsNullOrWhiteSpace(source.TargetCommandSchema.Name))
|
||||||
{
|
{
|
||||||
Render(" ");
|
Render(" ");
|
||||||
RenderWithColor(source.TargetCommandSchema.Name, ConsoleColor.Cyan);
|
RenderWithColor(source.TargetCommandSchema.Name, ConsoleColor.Cyan);
|
||||||
@@ -195,19 +192,19 @@ namespace CliFx.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delimiter
|
// Delimiter
|
||||||
if (!optionSchema.Name.IsNullOrWhiteSpace() && optionSchema.ShortName != null)
|
if (!string.IsNullOrWhiteSpace(optionSchema.Name) && optionSchema.ShortName != null)
|
||||||
{
|
{
|
||||||
Render("|");
|
Render("|");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
if (!optionSchema.Name.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(optionSchema.Name))
|
||||||
{
|
{
|
||||||
RenderWithColor($"--{optionSchema.Name}", ConsoleColor.White);
|
RenderWithColor($"--{optionSchema.Name}", ConsoleColor.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if (!optionSchema.Description.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(optionSchema.Description))
|
||||||
{
|
{
|
||||||
RenderColumnIndent();
|
RenderColumnIndent();
|
||||||
Render(optionSchema.Description);
|
Render(optionSchema.Description);
|
||||||
@@ -231,14 +228,14 @@ namespace CliFx.Services
|
|||||||
// Child commands
|
// Child commands
|
||||||
foreach (var childCommandSchema in childCommandSchemas)
|
foreach (var childCommandSchema in childCommandSchemas)
|
||||||
{
|
{
|
||||||
var relativeCommandName = GetRelativeCommandName(childCommandSchema, source.TargetCommandSchema);
|
var relativeCommandName = GetRelativeCommandName(childCommandSchema, source.TargetCommandSchema)!;
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
RenderIndent();
|
RenderIndent();
|
||||||
RenderWithColor(relativeCommandName, ConsoleColor.Cyan);
|
RenderWithColor(relativeCommandName, ConsoleColor.Cyan);
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if (!childCommandSchema.Description.IsNullOrWhiteSpace())
|
if (!string.IsNullOrWhiteSpace(childCommandSchema.Description))
|
||||||
{
|
{
|
||||||
RenderColumnIndent();
|
RenderColumnIndent();
|
||||||
Render(childCommandSchema.Description);
|
Render(childCommandSchema.Description);
|
||||||
@@ -254,7 +251,7 @@ namespace CliFx.Services
|
|||||||
Render("You can run `");
|
Render("You can run `");
|
||||||
Render(source.ApplicationMetadata.ExecutableName);
|
Render(source.ApplicationMetadata.ExecutableName);
|
||||||
|
|
||||||
if (!source.TargetCommandSchema.IsDefault())
|
if (!string.IsNullOrWhiteSpace(source.TargetCommandSchema.Name))
|
||||||
{
|
{
|
||||||
Render(" ");
|
Render(" ");
|
||||||
RenderWithColor(source.TargetCommandSchema.Name, ConsoleColor.Cyan);
|
RenderWithColor(source.TargetCommandSchema.Name, ConsoleColor.Cyan);
|
||||||
@@ -285,8 +282,8 @@ namespace CliFx.Services
|
|||||||
|
|
||||||
public partial class HelpTextRenderer
|
public partial class HelpTextRenderer
|
||||||
{
|
{
|
||||||
private static string GetRelativeCommandName(CommandSchema commandSchema, CommandSchema parentCommandSchema) =>
|
private static string? GetRelativeCommandName(CommandSchema commandSchema, CommandSchema parentCommandSchema) =>
|
||||||
parentCommandSchema.Name.IsNullOrWhiteSpace()
|
string.IsNullOrWhiteSpace(parentCommandSchema.Name) || string.IsNullOrWhiteSpace(commandSchema.Name)
|
||||||
? commandSchema.Name
|
? commandSchema.Name
|
||||||
: commandSchema.Name.Substring(parentCommandSchema.Name.Length + 1);
|
: commandSchema.Name.Substring(parentCommandSchema.Name.Length + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ namespace CliFx.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts an option to specified target type.
|
/// Converts an option to specified target type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
object ConvertOptionInput(CommandOptionInput optionInput, Type targetType);
|
object? ConvertOptionInput(CommandOptionInput optionInput, Type targetType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
{
|
{
|
||||||
@@ -52,5 +53,11 @@ namespace CliFx.Services
|
|||||||
/// Resets foreground and background color to default values.
|
/// Resets foreground and background color to default values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ResetColor();
|
void ResetColor();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides token that cancels when application cancellation is requested.
|
||||||
|
/// Subsequent calls return the same token.
|
||||||
|
/// </summary>
|
||||||
|
CancellationToken GetCancellationToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
CliFx/Services/IEnvironmentVariablesParser.cs
Normal file
15
CliFx/Services/IEnvironmentVariablesParser.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using CliFx.Models;
|
||||||
|
|
||||||
|
namespace CliFx.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parses environment variable values
|
||||||
|
/// </summary>
|
||||||
|
public interface IEnvironmentVariablesParser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parse an environment variable value and converts it to a <see cref="CommandOptionInput"/>
|
||||||
|
/// </summary>
|
||||||
|
CommandOptionInput GetCommandOptionInputFromEnvironmentVariable(string environmentVariableValue, CommandOptionSchema targetOptionSchema);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
CliFx/Services/IEnvironmentVariablesProvider.cs
Normal file
16
CliFx/Services/IEnvironmentVariablesProvider.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CliFx.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides environment variable values
|
||||||
|
/// </summary>
|
||||||
|
public interface IEnvironmentVariablesProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all the environment variables available.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If the User is not allowed to read environment variables it will return an empty dictionary.</remarks>
|
||||||
|
IReadOnlyDictionary<string, string> GetEnvironmentVariables();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
{
|
{
|
||||||
@@ -8,6 +9,8 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemConsole : IConsole
|
public class SystemConsole : IConsole
|
||||||
{
|
{
|
||||||
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TextReader Input => Console.In;
|
public TextReader Input => Console.In;
|
||||||
|
|
||||||
@@ -42,5 +45,26 @@ namespace CliFx.Services
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ResetColor() => Console.ResetColor();
|
public void ResetColor() => Console.ResetColor();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public CancellationToken GetCancellationToken()
|
||||||
|
{
|
||||||
|
if (_cancellationTokenSource is null)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
// Subscribe to CancelKeyPress event with cancellation token source
|
||||||
|
// Kills app on second cancellation (hard cancellation)
|
||||||
|
Console.CancelKeyPress += (_, args) =>
|
||||||
|
{
|
||||||
|
if (_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
args.Cancel = true;
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cancellationTokenSource.Token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using CliFx.Internal;
|
using System.Threading;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
{
|
{
|
||||||
@@ -11,6 +11,8 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class VirtualConsole : IConsole
|
public class VirtualConsole : IConsole
|
||||||
{
|
{
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TextReader Input { get; }
|
public TextReader Input { get; }
|
||||||
|
|
||||||
@@ -40,21 +42,24 @@ namespace CliFx.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public VirtualConsole(TextReader input, bool isInputRedirected,
|
public VirtualConsole(TextReader input, bool isInputRedirected,
|
||||||
TextWriter output, bool isOutputRedirected,
|
TextWriter output, bool isOutputRedirected,
|
||||||
TextWriter error, bool isErrorRedirected)
|
TextWriter error, bool isErrorRedirected,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Input = input.GuardNotNull(nameof(input));
|
Input = input;
|
||||||
IsInputRedirected = isInputRedirected;
|
IsInputRedirected = isInputRedirected;
|
||||||
Output = output.GuardNotNull(nameof(output));
|
Output = output;
|
||||||
IsOutputRedirected = isOutputRedirected;
|
IsOutputRedirected = isOutputRedirected;
|
||||||
Error = error.GuardNotNull(nameof(error));
|
Error = error;
|
||||||
IsErrorRedirected = isErrorRedirected;
|
IsErrorRedirected = isErrorRedirected;
|
||||||
|
_cancellationToken = cancellationToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="VirtualConsole"/>.
|
/// Initializes an instance of <see cref="VirtualConsole"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VirtualConsole(TextReader input, TextWriter output, TextWriter error)
|
public VirtualConsole(TextReader input, TextWriter output, TextWriter error,
|
||||||
: this(input, true, output, true, error, true)
|
CancellationToken cancellationToken = default)
|
||||||
|
: this(input, true, output, true, error, true, cancellationToken)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +67,8 @@ namespace CliFx.Services
|
|||||||
/// Initializes an instance of <see cref="VirtualConsole"/> using output stream (stdout) and error stream (stderr).
|
/// Initializes an instance of <see cref="VirtualConsole"/> using output stream (stdout) and error stream (stderr).
|
||||||
/// Input stream (stdin) is replaced with a no-op stub.
|
/// Input stream (stdin) is replaced with a no-op stub.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VirtualConsole(TextWriter output, TextWriter error)
|
public VirtualConsole(TextWriter output, TextWriter error, CancellationToken cancellationToken = default)
|
||||||
: this(TextReader.Null, output, error)
|
: this(TextReader.Null, output, error, cancellationToken)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,8 +76,8 @@ namespace CliFx.Services
|
|||||||
/// Initializes an instance of <see cref="VirtualConsole"/> using output stream (stdout).
|
/// Initializes an instance of <see cref="VirtualConsole"/> using output stream (stdout).
|
||||||
/// Input stream (stdin) and error stream (stderr) are replaced with no-op stubs.
|
/// Input stream (stdin) and error stream (stderr) are replaced with no-op stubs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VirtualConsole(TextWriter output)
|
public VirtualConsole(TextWriter output, CancellationToken cancellationToken = default)
|
||||||
: this(output, TextWriter.Null)
|
: this(output, TextWriter.Null, cancellationToken)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,5 +87,8 @@ namespace CliFx.Services
|
|||||||
ForegroundColor = ConsoleColor.Gray;
|
ForegroundColor = ConsoleColor.Gray;
|
||||||
BackgroundColor = ConsoleColor.Black;
|
BackgroundColor = ConsoleColor.Black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public CancellationToken GetCancellationToken() => _cancellationToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
92
Readme.md
92
Readme.md
@@ -1,10 +1,9 @@
|
|||||||
# CliFx
|
# CliFx
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/Tyrrrz/CliFx/branch/master)
|
[](https://github.com/Tyrrrz/CliFx/actions)
|
||||||
[](https://ci.appveyor.com/project/Tyrrrz/CliFx/branch/master/tests)
|
[](https://codecov.io/gh/Tyrrrz/CliFx)
|
||||||
[](https://codecov.io/gh/Tyrrrz/CliFx)
|
[](https://nuget.org/packages/CliFx)
|
||||||
[](https://nuget.org/packages/CliFx)
|
[](https://nuget.org/packages/CliFx)
|
||||||
[](https://nuget.org/packages/CliFx)
|
|
||||||
[](https://patreon.com/tyrrrz)
|
[](https://patreon.com/tyrrrz)
|
||||||
[](https://buymeacoffee.com/tyrrrz)
|
[](https://buymeacoffee.com/tyrrrz)
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ _CliFx is to command line interfaces what ASP.NET Core is to web applications._
|
|||||||
## Download
|
## Download
|
||||||
|
|
||||||
- [NuGet](https://nuget.org/packages/CliFx): `dotnet add package CliFx`
|
- [NuGet](https://nuget.org/packages/CliFx): `dotnet add package CliFx`
|
||||||
- [Continuous integration](https://ci.appveyor.com/project/Tyrrrz/CliFx)
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -24,12 +22,17 @@ _CliFx is to command line interfaces what ASP.NET Core is to web applications._
|
|||||||
- Resolves commands and options using attributes
|
- Resolves commands and options using attributes
|
||||||
- Handles options of various types, including custom types
|
- Handles options of various types, including custom types
|
||||||
- Supports multi-level command hierarchies
|
- Supports multi-level command hierarchies
|
||||||
|
- Allows cancellation
|
||||||
- Generates contextual help text
|
- Generates contextual help text
|
||||||
- Prints errors and routes exit codes on exceptions
|
- Prints errors and routes exit codes on exceptions
|
||||||
- Highly testable and easy to debug
|
- Highly testable and easy to debug
|
||||||
- Targets .NET Framework 4.5+ and .NET Standard 2.0+
|
- Targets .NET Framework 4.5+ and .NET Standard 2.0+
|
||||||
- No external dependencies
|
- No external dependencies
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Argument syntax
|
## Argument syntax
|
||||||
|
|
||||||
This library employs a variation of the GNU command line argument syntax. Because CliFx uses a context-unaware parser, the syntax rules are generally more consistent and intuitive.
|
This library employs a variation of the GNU command line argument syntax. Because CliFx uses a context-unaware parser, the syntax rules are generally more consistent and intuitive.
|
||||||
@@ -38,8 +41,8 @@ The following examples are valid for any application created with CliFx:
|
|||||||
|
|
||||||
- `myapp --foo bar` sets option `"foo"` to value `"bar"`
|
- `myapp --foo bar` sets option `"foo"` to value `"bar"`
|
||||||
- `myapp -f bar` sets option `'f'` to value `"bar"`
|
- `myapp -f bar` sets option `'f'` to value `"bar"`
|
||||||
- `myapp --switch` sets option `"switch"` to value `true`
|
- `myapp --switch` sets option `"switch"` to value `true`
|
||||||
- `myapp -s` sets option `'s'` to value `true`
|
- `myapp -s` sets option `'s'` to value `true`
|
||||||
- `myapp -abc` sets options `'a'`, `'b'` and `'c'` to value `true`
|
- `myapp -abc` sets options `'a'`, `'b'` and `'c'` to value `true`
|
||||||
- `myapp -xqf bar` sets options `'x'` and `'q'` to value `true`, and option `'f'` to value `"bar"`
|
- `myapp -xqf bar` sets options `'x'` and `'q'` to value `true`, and option `'f'` to value `"bar"`
|
||||||
- `myapp -i file1.txt file2.txt` sets option `'i'` to a sequence of values `"file1.txt"` and `"file2.txt"`
|
- `myapp -i file1.txt file2.txt` sets option `'i'` to a sequence of values `"file1.txt"` and `"file2.txt"`
|
||||||
@@ -95,7 +98,7 @@ public class LogCommand : ICommand
|
|||||||
|
|
||||||
By implementing `ICommand` this class also provides `ExecuteAsync` method. This is the method that gets called when the user invokes the command. Its return type is `Task` in order to facilitate asynchronous execution, but if your command runs synchronously you can simply return `Task.CompletedTask`.
|
By implementing `ICommand` this class also provides `ExecuteAsync` method. This is the method that gets called when the user invokes the command. Its return type is `Task` in order to facilitate asynchronous execution, but if your command runs synchronously you can simply return `Task.CompletedTask`.
|
||||||
|
|
||||||
The `ExecuteAsync` method also takes an instance of `IConsole` as a parameter. You should use this abstraction to interact with the console instead of calling `System.Console` so that your commands are testable.
|
The `ExecuteAsync` method also takes an instance of `IConsole` as a parameter. You should use the `console` parameter in places where you would normally use `System.Console`, in order to make your command testable.
|
||||||
|
|
||||||
Finally, the command defined above can be executed from the command line in one of the following ways:
|
Finally, the command defined above can be executed from the command line in one of the following ways:
|
||||||
|
|
||||||
@@ -123,6 +126,34 @@ When resolving options, CliFx can convert string values obtained from the comman
|
|||||||
|
|
||||||
If you want to define an option of your own type, the easiest way to do it is to make sure that your type is string-initializable, as explained above.
|
If you want to define an option of your own type, the easiest way to do it is to make sure that your type is string-initializable, as explained above.
|
||||||
|
|
||||||
|
It is also possible to configure the application to use your own converter, by calling `UseCommandOptionInputConverter` method on `CliApplicationBuilder`.
|
||||||
|
|
||||||
|
```c#
|
||||||
|
var app = new CliApplicationBuilder()
|
||||||
|
.AddCommandsFromThisAssembly()
|
||||||
|
.UseCommandOptionInputConverter(new MyConverter())
|
||||||
|
.Build();
|
||||||
|
```
|
||||||
|
|
||||||
|
The converter class must implement `ICommandOptionInputConverter` but you can also derive from `CommandOptionInputConverter` to extend the default behavior.
|
||||||
|
|
||||||
|
```c#
|
||||||
|
public class MyConverter : CommandOptionInputConverter
|
||||||
|
{
|
||||||
|
protected override object ConvertValue(string value, Type targetType)
|
||||||
|
{
|
||||||
|
// Custom conversion for MyType
|
||||||
|
if (targetType == typeof(MyType))
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default behavior for other types
|
||||||
|
return base.ConvertValue(value, targetType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Reporting errors
|
### Reporting errors
|
||||||
|
|
||||||
You may have noticed that commands in CliFx don't return exit codes. This is by design as exit codes are considered a higher-level concern and thus handled by `CliApplication`, not by individual commands.
|
You may have noticed that commands in CliFx don't return exit codes. This is by design as exit codes are considered a higher-level concern and thus handled by `CliApplication`, not by individual commands.
|
||||||
@@ -183,6 +214,30 @@ public class SecondSubCommand : ICommand
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Cancellation
|
||||||
|
|
||||||
|
It is possible to gracefully cancel execution of a command and preform any necessary cleanup. By default an app gets forcefully killed when it receives an interrupt signal (Ctrl+C or Ctrl+Break). You can call `console.GetCancellationToken()` to override the default behavior and get `CancellationToken` that represents the first interrupt signal. Second interrupt signal terminates an app immediately. Note that the code that executes before the first call to `GetCancellationToken` will not be cancellation aware.
|
||||||
|
|
||||||
|
You can pass `CancellationToken` around and check its state.
|
||||||
|
|
||||||
|
Cancelled or terminated app returns non-zero exit code.
|
||||||
|
|
||||||
|
```c#
|
||||||
|
[Command("cancel")]
|
||||||
|
public class CancellableCommand : ICommand
|
||||||
|
{
|
||||||
|
public async Task ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
console.Output.WriteLine("Printed");
|
||||||
|
|
||||||
|
// Long-running cancellable operation that throws when canceled
|
||||||
|
await Task.Delay(Timeout.InfiniteTimeSpan, console.GetCancellationToken());
|
||||||
|
|
||||||
|
console.Output.WriteLine("Never printed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Dependency injection
|
### Dependency injection
|
||||||
|
|
||||||
CliFx uses an implementation of `ICommandFactory` to initialize commands and by default it only works with types that have parameterless constructors.
|
CliFx uses an implementation of `ICommandFactory` to initialize commands and by default it only works with types that have parameterless constructors.
|
||||||
@@ -388,13 +443,12 @@ var app = new CliApplicationBuilder()
|
|||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
CliFx has the smallest performance overhead compared to other command line parsers and frameworks.
|
Here's how CliFx's execution overhead compares to that of other libraries.
|
||||||
Below you can see a table comparing execution times of a simple command across different libraries.
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.14393.0 (1607/AnniversaryUpdate/Redstone1)
|
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.14393.3144 (1607/AnniversaryUpdate/Redstone1)
|
||||||
Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
|
Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
|
||||||
Frequency=3125008 Hz, Resolution=319.9992 ns, Timer=TSC
|
Frequency=3125011 Hz, Resolution=319.9989 ns, Timer=TSC
|
||||||
.NET Core SDK=2.2.401
|
.NET Core SDK=2.2.401
|
||||||
[Host] : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
|
[Host] : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
|
||||||
Core : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
|
Core : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
|
||||||
@@ -404,10 +458,12 @@ Job=Core Runtime=Core
|
|||||||
|
|
||||||
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank |
|
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank |
|
||||||
|------------------------------------- |----------:|----------:|----------:|------:|--------:|-----:|
|
|------------------------------------- |----------:|----------:|----------:|------:|--------:|-----:|
|
||||||
| CliFx | 39.47 us | 0.7490 us | 0.9198 us | 1.00 | 0.00 | 1 |
|
| CliFx | 31.29 us | 0.6147 us | 0.7774 us | 1.00 | 0.00 | 2 |
|
||||||
| System.CommandLine | 153.98 us | 0.7112 us | 0.6652 us | 3.90 | 0.09 | 2 |
|
| System.CommandLine | 184.44 us | 3.4993 us | 4.0297 us | 5.90 | 0.21 | 4 |
|
||||||
| McMaster.Extensions.CommandLineUtils | 180.36 us | 3.5893 us | 6.7416 us | 4.59 | 0.16 | 3 |
|
| McMaster.Extensions.CommandLineUtils | 165.50 us | 1.4805 us | 1.3124 us | 5.33 | 0.13 | 3 |
|
||||||
| PowerArgs | 427.54 us | 6.9006 us | 6.4548 us | 10.82 | 0.26 | 4 |
|
| CommandLineParser | 26.65 us | 0.5530 us | 0.5679 us | 0.85 | 0.03 | 1 |
|
||||||
|
| PowerArgs | 405.44 us | 7.7133 us | 9.1821 us | 12.96 | 0.47 | 6 |
|
||||||
|
| Clipr | 220.82 us | 4.4567 us | 4.9536 us | 7.06 | 0.25 | 5 |
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
@@ -443,4 +499,4 @@ CliFx is made out of "Cli" for "Command Line Interface" and "Fx" for "Framework"
|
|||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
If you really like my projects and want to support me, consider donating to me on [Patreon](https://patreon.com/tyrrrz) or [BuyMeACoffee](https://buymeacoffee.com/tyrrrz). All donations are optional and are greatly appreciated. 🙏
|
If you really like my projects and want to support me, consider donating to me on [Patreon](https://patreon.com/tyrrrz) or [BuyMeACoffee](https://buymeacoffee.com/tyrrrz). All donations are optional and are greatly appreciated. 🙏
|
||||||
|
|||||||
26
appveyor.yml
26
appveyor.yml
@@ -1,26 +0,0 @@
|
|||||||
version: '{build}'
|
|
||||||
|
|
||||||
image: Visual Studio 2017
|
|
||||||
configuration: Release
|
|
||||||
|
|
||||||
before_build:
|
|
||||||
- dotnet restore
|
|
||||||
|
|
||||||
build:
|
|
||||||
verbosity: minimal
|
|
||||||
|
|
||||||
after_test:
|
|
||||||
- choco install codecov && codecov -f "CliFx.Tests/bin/%CONFIGURATION%/Coverage.xml" --required
|
|
||||||
|
|
||||||
artifacts:
|
|
||||||
- path: CliFx/bin/$(configuration)/CliFx*.nupkg
|
|
||||||
name: CliFx.nupkg
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: NuGet
|
|
||||||
api_key:
|
|
||||||
secure: 5VyEaGo5gRLr9HdkRFqS1enRq+K8Qarg1dzU33CE1dOmVXp43JaS2PQTNgsRHXkc
|
|
||||||
artifact: CliFx.nupkg
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
appveyor_repo_tag: true
|
|
||||||
Reference in New Issue
Block a user