mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4365ad457a | ||
|
|
fb3617980e | ||
|
|
7690aae456 | ||
|
|
076678a08c | ||
|
|
104279d6e9 | ||
|
|
515d51a91d | ||
|
|
4fdf543190 | ||
|
|
4e1ab096c9 | ||
|
|
8aa6911cca | ||
|
|
f0362019ed | ||
|
|
82895f2e42 | ||
|
|
4cf622abe5 | ||
|
|
d4e22a78d6 | ||
|
|
3883c831e9 | ||
|
|
63441688fe | ||
|
|
e48839b938 | ||
|
|
ed87373dc3 | ||
|
|
6ce52c70f7 | ||
|
|
d2b0b16121 | ||
|
|
d67a9fe762 | ||
|
|
ce2a3153e6 | ||
|
|
d4b54231fb | ||
|
|
70bfe0bf91 | ||
|
|
9690c380d3 | ||
|
|
85caa275ae | ||
|
|
32026e59c0 | ||
|
|
486ccb9685 | ||
|
|
7b766f70f3 | ||
|
|
f73e96488f | ||
|
|
af63fa5a1f | ||
|
|
e8f53c9463 | ||
|
|
9564cd5d30 | ||
|
|
ed458c3980 | ||
|
|
25538f99db | ||
|
|
36436e7a4b | ||
|
|
a6070332c9 | ||
|
|
25cbfdb4b8 | ||
|
|
d1b5107c2c | ||
|
|
03873d63cd | ||
|
|
89aba39964 | ||
|
|
ab57a103d1 | ||
|
|
d0b2ebc061 | ||
|
|
857257ca73 | ||
|
|
3587155c7e | ||
|
|
ae05e0db96 | ||
|
|
41c0493e66 | ||
|
|
43a304bb26 | ||
|
|
cd3892bf83 | ||
|
|
3f7c02342d | ||
|
|
c65cdf465e | ||
|
|
b5d67ecf24 | ||
|
|
a94b2296e1 | ||
|
|
fa05e4df3f | ||
|
|
b70b25076e | ||
|
|
0662f341e6 | ||
|
|
80bf477f3b | ||
|
|
e4a502d9d6 | ||
|
|
13b15b98ed | ||
|
|
80465e0e51 | ||
|
|
9a1ce7e7e5 | ||
|
|
b45da64664 | ||
|
|
df01dc055e | ||
|
|
31dd24d189 | ||
|
|
2a76dfe1c8 | ||
|
|
59ee2e34d8 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,4 +1,3 @@
|
||||
github: Tyrrrz
|
||||
patreon: Tyrrrz
|
||||
open_collective: Tyrrrz
|
||||
custom: ['buymeacoffee.com/Tyrrrz']
|
||||
custom: ['buymeacoffee.com/Tyrrrz', 'tyrrrz.me/donate']
|
||||
25
.github/workflows/CD.yml
vendored
Normal file
25
.github/workflows/CD.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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.1.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}}
|
||||
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.1.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_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
.ncrunchsolution
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
|
||||
BIN
.screenshots/help.png
Normal file
BIN
.screenshots/help.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -1,34 +1,46 @@
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Order;
|
||||
using CliFx.Benchmarks.Commands;
|
||||
using CommandLine;
|
||||
|
||||
namespace CliFx.Benchmarks
|
||||
{
|
||||
[CoreJob]
|
||||
[SimpleJob]
|
||||
[RankColumn]
|
||||
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
|
||||
public class Benchmark
|
||||
{
|
||||
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
|
||||
|
||||
[Benchmark(Description = "CliFx", Baseline = true)]
|
||||
public Task<int> ExecuteWithCliFx() => new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments);
|
||||
public async ValueTask<int> ExecuteWithCliFx() =>
|
||||
await new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments);
|
||||
|
||||
[Benchmark(Description = "System.CommandLine")]
|
||||
public Task<int> ExecuteWithSystemCommandLine() => new SystemCommandLineCommand().ExecuteAsync(Arguments);
|
||||
public async Task<int> ExecuteWithSystemCommandLine() =>
|
||||
await new SystemCommandLineCommand().ExecuteAsync(Arguments);
|
||||
|
||||
[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
|
||||
// Probably wasn't designed to run multiple times in single process execution
|
||||
//[Benchmark(Description = "CommandLineParser")]
|
||||
public void ExecuteWithCommandLineParser()
|
||||
{
|
||||
var parsed = CommandLine.Parser.Default.ParseArguments(Arguments, typeof(CommandLineParserCommand));
|
||||
CommandLine.ParserResultExtensions.WithParsed<CommandLineParserCommand>(parsed, c => c.Execute());
|
||||
}
|
||||
[Benchmark(Description = "CommandLineParser")]
|
||||
public void ExecuteWithCommandLineParser() =>
|
||||
new Parser()
|
||||
.ParseArguments(Arguments, typeof(CommandLineParserCommand))
|
||||
.WithParsed<CommandLineParserCommand>(c => c.Execute());
|
||||
|
||||
[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();
|
||||
|
||||
[Benchmark(Description = "Cocona")]
|
||||
public void ExecuteWithCocona() =>
|
||||
Cocona.CoconaApp.Run<CoconaCommand>(Arguments);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../CliFx.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.6.0" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
|
||||
<PackageReference Include="clipr" Version="1.6.1" />
|
||||
<PackageReference Include="Cocona" Version="1.0.0" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.7.82" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.5.0" />
|
||||
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
||||
<PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Benchmarks.Commands
|
||||
{
|
||||
@@ -8,7 +7,7 @@ namespace CliFx.Benchmarks.Commands
|
||||
public class CliFxCommand : ICommand
|
||||
{
|
||||
[CommandOption("str", 's')]
|
||||
public string StrOption { get; set; }
|
||||
public string? StrOption { get; set; }
|
||||
|
||||
[CommandOption("int", 'i')]
|
||||
public int IntOption { get; set; }
|
||||
@@ -16,6 +15,6 @@ namespace CliFx.Benchmarks.Commands
|
||||
[CommandOption("bool", 'b')]
|
||||
public bool BoolOption { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
17
CliFx.Benchmarks/Commands/CoconaCommand.cs
Normal file
17
CliFx.Benchmarks/Commands/CoconaCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Cocona;
|
||||
|
||||
namespace CliFx.Benchmarks.Commands
|
||||
{
|
||||
public class CoconaCommand
|
||||
{
|
||||
public void Execute(
|
||||
[Option("str", new []{'s'})]
|
||||
string? strOption,
|
||||
[Option("int", new []{'i'})]
|
||||
int intOption,
|
||||
[Option("bool", new []{'b'})]
|
||||
bool boolOption)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
||||
public class CommandLineParserCommand
|
||||
{
|
||||
[Option('s', "str")]
|
||||
public string StrOption { get; set; }
|
||||
public string? StrOption { get; set; }
|
||||
|
||||
[Option('i', "int")]
|
||||
public int IntOption { get; set; }
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
||||
public class McMasterCommand
|
||||
{
|
||||
[Option("--str|-s")]
|
||||
public string StrOption { get; set; }
|
||||
public string? StrOption { get; set; }
|
||||
|
||||
[Option("--int|-i")]
|
||||
public int IntOption { get; set; }
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
||||
public class PowerArgsCommand
|
||||
{
|
||||
[ArgShortcut("--str"), ArgShortcut("-s")]
|
||||
public string StrOption { get; set; }
|
||||
public string? StrOption { get; set; }
|
||||
|
||||
[ArgShortcut("--int"), ArgShortcut("-i")]
|
||||
public int IntOption { get; set; }
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace CliFx.Benchmarks.Commands
|
||||
{
|
||||
new Option(new[] {"--str", "-s"})
|
||||
{
|
||||
Argument = new Argument<string>()
|
||||
Argument = new Argument<string?>()
|
||||
},
|
||||
new Option(new[] {"--int", "-i"})
|
||||
{
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../CliFx.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,7 +5,6 @@ using CliFx.Demo.Internal;
|
||||
using CliFx.Demo.Models;
|
||||
using CliFx.Demo.Services;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Demo.Commands
|
||||
{
|
||||
@@ -14,31 +13,25 @@ namespace CliFx.Demo.Commands
|
||||
{
|
||||
private readonly LibraryService _libraryService;
|
||||
|
||||
[CommandOption("title", 't', IsRequired = true, Description = "Book title.")]
|
||||
public string Title { get; set; }
|
||||
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[CommandOption("author", 'a', IsRequired = true, Description = "Book author.")]
|
||||
public string Author { get; set; }
|
||||
public string Author { get; set; } = "";
|
||||
|
||||
[CommandOption("published", 'p', Description = "Book publish date.")]
|
||||
public DateTimeOffset Published { get; set; }
|
||||
public DateTimeOffset Published { get; set; } = CreateRandomDate();
|
||||
|
||||
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
||||
public Isbn Isbn { get; set; }
|
||||
public Isbn Isbn { get; set; } = CreateRandomIsbn();
|
||||
|
||||
public BookAddCommand(LibraryService libraryService)
|
||||
{
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
// To make the demo simpler, we will just generate random publish date and ISBN if they were not set
|
||||
if (Published == default)
|
||||
Published = CreateRandomDate();
|
||||
if (Isbn == default)
|
||||
Isbn = CreateRandomIsbn();
|
||||
|
||||
if (_libraryService.GetBook(Title) != null)
|
||||
throw new CommandException("Book already exists.", 1);
|
||||
|
||||
@@ -48,7 +41,7 @@ namespace CliFx.Demo.Commands
|
||||
console.Output.WriteLine("Book added.");
|
||||
console.RenderBook(book);
|
||||
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +58,7 @@ namespace CliFx.Demo.Commands
|
||||
Random.Next(1, 59),
|
||||
TimeSpan.Zero);
|
||||
|
||||
public static Isbn CreateRandomIsbn() => new Isbn(
|
||||
private static Isbn CreateRandomIsbn() => new Isbn(
|
||||
Random.Next(0, 999),
|
||||
Random.Next(0, 99),
|
||||
Random.Next(0, 99999),
|
||||
|
||||
@@ -3,7 +3,6 @@ using CliFx.Attributes;
|
||||
using CliFx.Demo.Internal;
|
||||
using CliFx.Demo.Services;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Demo.Commands
|
||||
{
|
||||
@@ -12,15 +11,15 @@ namespace CliFx.Demo.Commands
|
||||
{
|
||||
private readonly LibraryService _libraryService;
|
||||
|
||||
[CommandOption("title", 't', IsRequired = true, Description = "Book title.")]
|
||||
public string Title { get; set; }
|
||||
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
public BookCommand(LibraryService libraryService)
|
||||
{
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var book = _libraryService.GetBook(Title);
|
||||
|
||||
@@ -29,7 +28,7 @@ namespace CliFx.Demo.Commands
|
||||
|
||||
console.RenderBook(book);
|
||||
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Demo.Internal;
|
||||
using CliFx.Demo.Services;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Demo.Commands
|
||||
{
|
||||
@@ -16,7 +15,7 @@ namespace CliFx.Demo.Commands
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var library = _libraryService.GetLibrary();
|
||||
|
||||
@@ -32,7 +31,7 @@ namespace CliFx.Demo.Commands
|
||||
console.RenderBook(book);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Demo.Services;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Demo.Commands
|
||||
{
|
||||
@@ -11,15 +10,15 @@ namespace CliFx.Demo.Commands
|
||||
{
|
||||
private readonly LibraryService _libraryService;
|
||||
|
||||
[CommandOption("title", 't', IsRequired = true, Description = "Book title.")]
|
||||
public string Title { get; set; }
|
||||
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
public BookRemoveCommand(LibraryService libraryService)
|
||||
{
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var book = _libraryService.GetBook(Title);
|
||||
|
||||
@@ -30,7 +29,7 @@ namespace CliFx.Demo.Commands
|
||||
|
||||
console.Output.WriteLine($"Book {Title} removed.");
|
||||
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using CliFx.Demo.Models;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Demo.Internal
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CliFx.Demo.Models
|
||||
{
|
||||
@@ -24,21 +23,23 @@ namespace CliFx.Demo.Models
|
||||
CheckDigit = checkDigit;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}";
|
||||
public override string ToString() =>
|
||||
$"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}";
|
||||
}
|
||||
|
||||
public partial class Isbn
|
||||
{
|
||||
public static Isbn Parse(string value)
|
||||
public static Isbn Parse(string value, IFormatProvider formatProvider)
|
||||
{
|
||||
var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return new Isbn(
|
||||
int.Parse(components[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(components[1], CultureInfo.InvariantCulture),
|
||||
int.Parse(components[2], CultureInfo.InvariantCulture),
|
||||
int.Parse(components[3], CultureInfo.InvariantCulture),
|
||||
int.Parse(components[4], CultureInfo.InvariantCulture));
|
||||
int.Parse(components[0], formatProvider),
|
||||
int.Parse(components[1], formatProvider),
|
||||
int.Parse(components[2], formatProvider),
|
||||
int.Parse(components[3], formatProvider),
|
||||
int.Parse(components[4], formatProvider)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Demo.Commands;
|
||||
using CliFx.Demo.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -7,7 +8,7 @@ namespace CliFx.Demo
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static Task<int> Main(string[] args)
|
||||
private static IServiceProvider GetServiceProvider()
|
||||
{
|
||||
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
||||
var services = new ServiceCollection();
|
||||
@@ -21,13 +22,14 @@ namespace CliFx.Demo
|
||||
services.AddTransient<BookRemoveCommand>();
|
||||
services.AddTransient<BookListCommand>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
return new CliApplicationBuilder()
|
||||
public static async Task<int> Main() =>
|
||||
await new CliApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type))
|
||||
.UseTypeActivator(GetServiceProvider().GetService)
|
||||
.Build()
|
||||
.RunAsync(args);
|
||||
}
|
||||
.RunAsync();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
Sample command line interface for managing a library of books.
|
||||
|
||||
This demo project shows basic CliFx functionality such as command routing, option parsing, autogenerated help text, and some other things.
|
||||
This demo project shows basic CliFx functionality such as command routing, argument parsing, autogenerated help text, and some other things.
|
||||
|
||||
You can get a list of available commands by running `CliFx.Demo --help`.
|
||||
@@ -25,7 +25,7 @@ namespace CliFx.Demo.Services
|
||||
return JsonConvert.DeserializeObject<Library>(data);
|
||||
}
|
||||
|
||||
public Book GetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title);
|
||||
public Book? GetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title);
|
||||
|
||||
public void AddBook(Book book)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../CliFx.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net46</TargetFramework>
|
||||
<Version>1.2.3.4</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests.Dummy.Commands
|
||||
{
|
||||
[Command]
|
||||
public class GreeterCommand : ICommand
|
||||
{
|
||||
[CommandOption("target", 't', Description = "Greeting target.")]
|
||||
public string Target { get; set; } = "world";
|
||||
|
||||
[CommandOption('e', Description = "Whether the greeting should be exclaimed.")]
|
||||
public bool IsExclaimed { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
{
|
||||
var buffer = new StringBuilder();
|
||||
|
||||
buffer.Append("Hello").Append(' ').Append(Target);
|
||||
|
||||
if (IsExclaimed)
|
||||
buffer.Append('!');
|
||||
|
||||
console.Output.WriteLine(buffer.ToString());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
CliFx.Tests.Dummy/Commands/HelloWorldCommand.cs
Normal file
19
CliFx.Tests.Dummy/Commands/HelloWorldCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.Dummy.Commands
|
||||
{
|
||||
[Command]
|
||||
public class HelloWorldCommand : ICommand
|
||||
{
|
||||
[CommandOption("target", EnvironmentVariableName = "ENV_TARGET")]
|
||||
public string Target { get; set; } = "World";
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
console.Output.WriteLine($"Hello {Target}!");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests.Dummy.Commands
|
||||
{
|
||||
[Command("log", Description = "Calculate the logarithm of a value.")]
|
||||
public class LogCommand : ICommand
|
||||
{
|
||||
[CommandOption("value", 'v', IsRequired = true, Description = "Value whose logarithm is to be found.")]
|
||||
public double Value { get; set; }
|
||||
|
||||
[CommandOption("base", 'b', Description = "Logarithm base.")]
|
||||
public double Base { get; set; } = 10;
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
{
|
||||
var result = Math.Log(Value, Base);
|
||||
console.Output.WriteLine(result);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests.Dummy.Commands
|
||||
{
|
||||
[Command("sum", Description = "Calculate the sum of all input values.")]
|
||||
public class SumCommand : ICommand
|
||||
{
|
||||
[CommandOption("values", 'v', IsRequired = true, Description = "Input values.")]
|
||||
public IReadOnlyList<double> Values { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
{
|
||||
var result = Values.Sum();
|
||||
console.Output.WriteLine(result);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,13 @@
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CliFx.Tests.Dummy
|
||||
{
|
||||
public static class Program
|
||||
public class Program
|
||||
{
|
||||
public static Task<int> Main(string[] args)
|
||||
{
|
||||
// Set culture to invariant to maintain consistent format because we rely on it in tests
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
||||
|
||||
return new CliApplicationBuilder()
|
||||
public static async Task Main() =>
|
||||
await new CliApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.UseDescription("Dummy program used for E2E tests.")
|
||||
.Build()
|
||||
.RunAsync(args);
|
||||
}
|
||||
.RunAsync();
|
||||
}
|
||||
}
|
||||
45
CliFx.Tests/CliApplicationBuilderTests.cs
Normal file
45
CliFx.Tests/CliApplicationBuilderTests.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using CliFx.Tests.TestCommands;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CliApplicationBuilderTests
|
||||
{
|
||||
[Test(Description = "All builder methods must return without exceptions")]
|
||||
public void Smoke_Test()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CliApplicationBuilder();
|
||||
|
||||
// Act
|
||||
builder
|
||||
.AddCommand(typeof(HelloWorldDefaultCommand))
|
||||
.AddCommandsFrom(typeof(HelloWorldDefaultCommand).Assembly)
|
||||
.AddCommands(new[] {typeof(HelloWorldDefaultCommand)})
|
||||
.AddCommandsFrom(new[] {typeof(HelloWorldDefaultCommand).Assembly})
|
||||
.AddCommandsFromThisAssembly()
|
||||
.AllowDebugMode()
|
||||
.AllowPreviewMode()
|
||||
.UseTitle("test")
|
||||
.UseExecutableName("test")
|
||||
.UseVersionText("test")
|
||||
.UseDescription("test")
|
||||
.UseConsole(new VirtualConsole(TextWriter.Null))
|
||||
.UseTypeActivator(Activator.CreateInstance)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Test(Description = "Builder must be able to produce an application when no parameters are specified")]
|
||||
public void Build_Test()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new CliApplicationBuilder();
|
||||
|
||||
// Act
|
||||
builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class CliApplicationTests
|
||||
{
|
||||
[Command]
|
||||
private class DefaultCommand : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
{
|
||||
console.Output.WriteLine("DefaultCommand executed.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[Command("cmd")]
|
||||
private class NamedCommand : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
{
|
||||
console.Output.WriteLine("NamedCommand executed.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Negative
|
||||
public partial class CliApplicationTests
|
||||
{
|
||||
[Command("faulty1")]
|
||||
private class FaultyCommand1 : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => throw new CommandException(150);
|
||||
}
|
||||
|
||||
[Command("faulty2")]
|
||||
private class FaultyCommand2 : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => throw new CommandException("FaultyCommand2 error message.", 150);
|
||||
}
|
||||
|
||||
[Command("faulty3")]
|
||||
private class FaultyCommand3 : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => throw new Exception("FaultyCommand3 error message.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,125 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using CliFx.Tests.TestCommands;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class CliApplicationTests
|
||||
public class CliApplicationTests
|
||||
{
|
||||
private const string TestAppName = "TestApp";
|
||||
private const string TestVersionText = "v1.0";
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_RunAsync()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DefaultCommand)},
|
||||
new[] {typeof(HelloWorldDefaultCommand)},
|
||||
new string[0],
|
||||
"DefaultCommand executed."
|
||||
new Dictionary<string, string>(),
|
||||
"Hello world."
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(NamedCommand)},
|
||||
new[] {"cmd"},
|
||||
"NamedCommand executed."
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_HelpAndVersion_RunAsync()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DefaultCommand)},
|
||||
new string[0]
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"concat", "-i", "foo", "-i", "bar", "-s", " "},
|
||||
new Dictionary<string, string>(),
|
||||
"foo bar"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DefaultCommand)},
|
||||
new[] {"-h"}
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"concat", "-i", "one", "two", "three", "-s", ", "},
|
||||
new Dictionary<string, string>(),
|
||||
"one, two, three"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DefaultCommand)},
|
||||
new[] {"--help"}
|
||||
new[] {typeof(DivideCommand)},
|
||||
new[] {"div", "-D", "24", "-d", "8"},
|
||||
new Dictionary<string, string>(),
|
||||
"3"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DefaultCommand)},
|
||||
new[] {"--version"}
|
||||
new[] {typeof(HelloWorldDefaultCommand)},
|
||||
new[] {"--version"},
|
||||
new Dictionary<string, string>(),
|
||||
TestVersionText
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(NamedCommand)},
|
||||
new string[0]
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"--version"},
|
||||
new Dictionary<string, string>(),
|
||||
TestVersionText
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(NamedCommand)},
|
||||
new[] {"cmd", "-h"}
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new string[0],
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(FaultyCommand1)},
|
||||
new[] {"faulty1", "-h"}
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"-h"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(FaultyCommand2)},
|
||||
new[] {"faulty2", "-h"}
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"--help"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(FaultyCommand3)},
|
||||
new[] {"faulty3", "-h"}
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"concat", "-h"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ExceptionCommand)},
|
||||
new[] {"exc", "-h"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(CommandExceptionCommand)},
|
||||
new[] {"exc", "-h"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"[preview]"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ExceptionCommand)},
|
||||
new[] {"[preview]", "exc"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"[preview]", "concat", "-o", "value"},
|
||||
new Dictionary<string, string>(),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,97 +127,325 @@ namespace CliFx.Tests
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new Type[0],
|
||||
new string[0]
|
||||
new string[0],
|
||||
new Dictionary<string, string>(),
|
||||
null, null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DefaultCommand)},
|
||||
new[] {"non-existing"}
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"non-existing"},
|
||||
new Dictionary<string, string>(),
|
||||
null, null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(FaultyCommand1)},
|
||||
new[] {"faulty1"}
|
||||
new[] {typeof(ExceptionCommand)},
|
||||
new[] {"exc"},
|
||||
new Dictionary<string, string>(),
|
||||
null, null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(FaultyCommand2)},
|
||||
new[] {"faulty2"}
|
||||
new[] {typeof(CommandExceptionCommand)},
|
||||
new[] {"exc"},
|
||||
new Dictionary<string, string>(),
|
||||
null, null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(FaultyCommand3)},
|
||||
new[] {"faulty3"}
|
||||
new[] {typeof(CommandExceptionCommand)},
|
||||
new[] {"exc"},
|
||||
new Dictionary<string, string>(),
|
||||
null, null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(CommandExceptionCommand)},
|
||||
new[] {"exc", "-m", "foo bar"},
|
||||
new Dictionary<string, string>(),
|
||||
"foo bar", null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(CommandExceptionCommand)},
|
||||
new[] {"exc", "-m", "foo bar", "-c", "666"},
|
||||
new Dictionary<string, string>(),
|
||||
"foo bar", 666
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_RunAsync_Help()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(HelpDefaultCommand), typeof(HelpNamedCommand), typeof(HelpSubCommand)},
|
||||
new[] {"--help"},
|
||||
new[]
|
||||
{
|
||||
TestVersionText,
|
||||
"Description",
|
||||
"HelpDefaultCommand description.",
|
||||
"Usage",
|
||||
TestAppName, "[command]", "[options]",
|
||||
"Options",
|
||||
"-a|--option-a", "OptionA description.",
|
||||
"-b|--option-b", "OptionB description.",
|
||||
"-h|--help", "Shows help text.",
|
||||
"--version", "Shows version information.",
|
||||
"Commands",
|
||||
"cmd", "HelpNamedCommand description.",
|
||||
"You can run", "to show help on a specific command."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(HelpSubCommand)},
|
||||
new[] {"--help"},
|
||||
new[]
|
||||
{
|
||||
TestVersionText,
|
||||
"Usage",
|
||||
TestAppName, "[command]",
|
||||
"Options",
|
||||
"-h|--help", "Shows help text.",
|
||||
"--version", "Shows version information.",
|
||||
"Commands",
|
||||
"cmd sub", "HelpSubCommand description.",
|
||||
"You can run", "to show help on a specific command."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(HelpDefaultCommand), typeof(HelpNamedCommand), typeof(HelpSubCommand)},
|
||||
new[] {"cmd", "--help"},
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"HelpNamedCommand description.",
|
||||
"Usage",
|
||||
TestAppName, "cmd", "[command]", "[options]",
|
||||
"Options",
|
||||
"-c|--option-c", "OptionC description.",
|
||||
"-d|--option-d", "OptionD description.",
|
||||
"-h|--help", "Shows help text.",
|
||||
"Commands",
|
||||
"sub", "HelpSubCommand description.",
|
||||
"You can run", "to show help on a specific command."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(HelpDefaultCommand), typeof(HelpNamedCommand), typeof(HelpSubCommand)},
|
||||
new[] {"cmd", "sub", "--help"},
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"HelpSubCommand description.",
|
||||
"Usage",
|
||||
TestAppName, "cmd sub", "[options]",
|
||||
"Options",
|
||||
"-e|--option-e", "OptionE description.",
|
||||
"-h|--help", "Shows help text."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ParameterCommand)},
|
||||
new[] {"param", "cmd", "--help"},
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"Command using positional parameters",
|
||||
"Usage",
|
||||
TestAppName, "param cmd", "<first>", "<parameterb>", "<third list...>", "[options]",
|
||||
"Parameters",
|
||||
"* first",
|
||||
"* parameterb",
|
||||
"* third list", "A list of numbers",
|
||||
"Options",
|
||||
"-o|--option",
|
||||
"-h|--help", "Shows help text."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllRequiredOptionsCommand)},
|
||||
new[] {"allrequired", "--help"},
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"AllRequiredOptionsCommand description.",
|
||||
"Usage",
|
||||
TestAppName, "allrequired --option-f <value> --option-g <value>"
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(SomeRequiredOptionsCommand)},
|
||||
new[] {"somerequired", "--help"},
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"SomeRequiredOptionsCommand description.",
|
||||
"Usage",
|
||||
TestAppName, "somerequired --option-f <value> [options]"
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(EnvironmentVariableCommand)},
|
||||
new[] {"--help"},
|
||||
new[]
|
||||
{
|
||||
"Environment variable:", "ENV_SINGLE_VALUE"
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new[] {"concat", "--help"},
|
||||
new[]
|
||||
{
|
||||
"Usage",
|
||||
TestAppName, "concat", "-i", "<values...>", "[options]",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_RunAsync))]
|
||||
public async Task RunAsync_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments, string expectedStdOut)
|
||||
public async Task RunAsync_Test(
|
||||
IReadOnlyList<Type> commandTypes,
|
||||
IReadOnlyList<string> commandLineArguments,
|
||||
IReadOnlyDictionary<string, string> environmentVariables,
|
||||
string? expectedStdOut = null)
|
||||
{
|
||||
// Arrange
|
||||
using (var stdout = new StringWriter())
|
||||
{
|
||||
var console = new VirtualConsole(stdout);
|
||||
await using var stdOutStream = new StringWriter();
|
||||
var console = new VirtualConsole(stdOutStream);
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
.UseTitle(TestAppName)
|
||||
.UseExecutableName(TestAppName)
|
||||
.UseVersionText(TestVersionText)
|
||||
.UseConsole(console)
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments);
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stdOut = stdOutStream.ToString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(0);
|
||||
stdout.ToString().Trim().Should().Be(expectedStdOut);
|
||||
}
|
||||
stdOut.Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
if (expectedStdOut != null)
|
||||
stdOut.Should().Be(expectedStdOut);
|
||||
|
||||
Console.WriteLine(stdOut);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_HelpAndVersion_RunAsync))]
|
||||
public async Task RunAsync_HelpAndVersion_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments)
|
||||
{
|
||||
// Arrange
|
||||
using (var stdout = new StringWriter())
|
||||
{
|
||||
var console = new VirtualConsole(stdout);
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
.UseConsole(console)
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments);
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(0);
|
||||
stdout.ToString().Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[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,
|
||||
IReadOnlyDictionary<string, string> environmentVariables,
|
||||
string? expectedStdErr = null,
|
||||
int? expectedExitCode = null)
|
||||
{
|
||||
// Arrange
|
||||
using (var stderr = new StringWriter())
|
||||
{
|
||||
var console = new VirtualConsole(TextWriter.Null, stderr);
|
||||
await using var stdErrStream = new StringWriter();
|
||||
var console = new VirtualConsole(TextWriter.Null, stdErrStream);
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
.UseTitle(TestAppName)
|
||||
.UseExecutableName(TestAppName)
|
||||
.UseVersionText(TestVersionText)
|
||||
.UseConsole(console)
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments);
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stderr = stdErrStream.ToString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().NotBe(0);
|
||||
stderr.ToString().Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
stderr.Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
if (expectedExitCode != null)
|
||||
exitCode.Should().Be(expectedExitCode);
|
||||
|
||||
if (expectedStdErr != null)
|
||||
stderr.Should().Be(expectedStdErr);
|
||||
|
||||
Console.WriteLine(stderr);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_RunAsync_Help))]
|
||||
public async Task RunAsync_Help_Test(
|
||||
IReadOnlyList<Type> commandTypes,
|
||||
IReadOnlyList<string> commandLineArguments,
|
||||
IReadOnlyList<string>? expectedSubstrings = null)
|
||||
{
|
||||
// Arrange
|
||||
await using var stdOutStream = new StringWriter();
|
||||
var console = new VirtualConsole(stdOutStream);
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
.UseTitle(TestAppName)
|
||||
.UseExecutableName(TestAppName)
|
||||
.UseVersionText(TestVersionText)
|
||||
.UseConsole(console)
|
||||
.Build();
|
||||
|
||||
var environmentVariables = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stdOut = stdOutStream.ToString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(0);
|
||||
stdOut.Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
if (expectedSubstrings != null)
|
||||
stdOut.Should().ContainAll(expectedSubstrings);
|
||||
|
||||
Console.WriteLine(stdOut);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RunAsync_Cancellation_Test()
|
||||
{
|
||||
// Arrange
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
await using var stdOutStream = new StringWriter();
|
||||
await using var stdErrStream = new StringWriter();
|
||||
var console = new VirtualConsole(stdOutStream, stdErrStream, cancellationTokenSource.Token);
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommand(typeof(CancellableCommand))
|
||||
.UseConsole(console)
|
||||
.Build();
|
||||
|
||||
var commandLineArguments = new[] {"cancel"};
|
||||
var environmentVariables = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(0.2));
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stdOut = stdOutStream.ToString().Trim();
|
||||
var stdErr = stdErrStream.ToString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().NotBe(0);
|
||||
stdOut.Should().BeNullOrWhiteSpace();
|
||||
stdErr.Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
Console.WriteLine(stdErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../CliFx.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net46</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
||||
<CoverletOutput>bin/$(Configuration)/Coverage.xml</CoverletOutput>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="5.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="CliWrap" Version="2.5.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
||||
<PackageReference Include="CliWrap" Version="2.3.1" />
|
||||
<PackageReference Include="coverlet.msbuild" Version="2.6.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
|
||||
<PackageReference Include="coverlet.msbuild" Version="2.8.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,4 +24,8 @@
|
||||
<ProjectReference Include="..\CliFx\CliFx.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Copy dummy's runtime config" AfterTargets="AfterBuild">
|
||||
<Copy SourceFiles="../CliFx.Tests.Dummy/bin/$(Configuration)/$(TargetFramework)/CliFx.Tests.Dummy.runtimeconfig.json" DestinationFiles="$(OutputPath)CliFx.Tests.Dummy.runtimeconfig.json" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class CommandFactoryTests
|
||||
{
|
||||
[Command]
|
||||
private class TestCommand : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class CommandFactoryTests
|
||||
{
|
||||
private static CommandSchema GetCommandSchema(Type commandType) =>
|
||||
new CommandSchemaResolver().GetCommandSchemas(new[] {commandType}).Single();
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_CreateCommand()
|
||||
{
|
||||
yield return new TestCaseData(GetCommandSchema(typeof(TestCommand)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_CreateCommand))]
|
||||
public void CreateCommand_Test(CommandSchema commandSchema)
|
||||
{
|
||||
// Arrange
|
||||
var factory = new CommandFactory();
|
||||
|
||||
// Act
|
||||
var command = factory.CreateCommand(commandSchema);
|
||||
|
||||
// Assert
|
||||
command.Should().BeOfType(commandSchema.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class CommandInitializerTests
|
||||
{
|
||||
[Command]
|
||||
private class TestCommand : ICommand
|
||||
{
|
||||
[CommandOption("int", 'i', IsRequired = true)]
|
||||
public int IntOption { get; set; } = 24;
|
||||
|
||||
[CommandOption("str", 's')]
|
||||
public string StringOption { get; set; } = "foo bar";
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class CommandInitializerTests
|
||||
{
|
||||
private static CommandSchema GetCommandSchema(Type commandType) =>
|
||||
new CommandSchemaResolver().GetCommandSchemas(new[] {commandType}).Single();
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new TestCommand(),
|
||||
GetCommandSchema(typeof(TestCommand)),
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("int", "13")
|
||||
}),
|
||||
new TestCommand {IntOption = 13}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new TestCommand(),
|
||||
GetCommandSchema(typeof(TestCommand)),
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("int", "13"),
|
||||
new CommandOptionInput("str", "hello world")
|
||||
}),
|
||||
new TestCommand {IntOption = 13, StringOption = "hello world"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new TestCommand(),
|
||||
GetCommandSchema(typeof(TestCommand)),
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("i", "13")
|
||||
}),
|
||||
new TestCommand {IntOption = 13}
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand_Negative()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new TestCommand(),
|
||||
GetCommandSchema(typeof(TestCommand)),
|
||||
CommandInput.Empty
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new TestCommand(),
|
||||
GetCommandSchema(typeof(TestCommand)),
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("str", "hello world")
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_InitializeCommand))]
|
||||
public void InitializeCommand_Test(ICommand command, CommandSchema commandSchema, CommandInput commandInput, ICommand expectedCommand)
|
||||
{
|
||||
// Arrange
|
||||
var initializer = new CommandInitializer();
|
||||
|
||||
// Act
|
||||
initializer.InitializeCommand(command, commandSchema, commandInput);
|
||||
|
||||
// Assert
|
||||
command.Should().BeEquivalentTo(expectedCommand, o => o.RespectingRuntimeTypes());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_InitializeCommand_Negative))]
|
||||
public void InitializeCommand_Negative_Test(ICommand command, CommandSchema commandSchema, CommandInput commandInput)
|
||||
{
|
||||
// Arrange
|
||||
var initializer = new CommandInitializer();
|
||||
|
||||
// Act & Assert
|
||||
initializer.Invoking(i => i.InitializeCommand(command, commandSchema, commandInput))
|
||||
.Should().ThrowExactly<MissingCommandOptionInputException>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CommandInputParserTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput()
|
||||
{
|
||||
yield return new TestCaseData(new string[0], CommandInput.Empty);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option", "value"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option1", "value1", "--option2", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option1", "value1"),
|
||||
new CommandOptionInput("option2", "value2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option", "value1", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option", "value1", "--option", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value1", "-b", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", "value1"),
|
||||
new CommandOptionInput("b", "value2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value1", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value1", "-a", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option1", "value1", "-b", "value2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option1", "value1"),
|
||||
new CommandOptionInput("b", "value2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--switch"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("switch")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--switch1", "--switch2"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("switch1"),
|
||||
new CommandOptionInput("switch2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-s"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("s")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "-b"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-ab"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-ab", "value"},
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"command"},
|
||||
new CommandInput("command")
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"command", "--option", "value"},
|
||||
new CommandInput("command", new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"long", "command", "name"},
|
||||
new CommandInput("long command name")
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"long", "command", "name", "--option", "value"},
|
||||
new CommandInput("long command name", new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_ParseCommandInput))]
|
||||
public void ParseCommandInput_Test(IReadOnlyList<string> commandLineArguments, CommandInput expectedCommandInput)
|
||||
{
|
||||
// Arrange
|
||||
var parser = new CommandInputParser();
|
||||
|
||||
// Act
|
||||
var commandInput = parser.ParseCommandInput(commandLineArguments);
|
||||
|
||||
// Assert
|
||||
commandInput.Should().BeEquivalentTo(expectedCommandInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class CommandOptionInputConverterTests
|
||||
{
|
||||
private enum TestEnum
|
||||
{
|
||||
Value1,
|
||||
Value2,
|
||||
Value3
|
||||
}
|
||||
|
||||
private class TestStringConstructable
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
public TestStringConstructable(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestStringParseable
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private TestStringParseable(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static TestStringParseable Parse(string value) => new TestStringParseable(value);
|
||||
}
|
||||
|
||||
private class TestStringParseableWithFormatProvider
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private TestStringParseableWithFormatProvider(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static TestStringParseableWithFormatProvider Parse(string value, IFormatProvider formatProvider) =>
|
||||
new TestStringParseableWithFormatProvider(value + " " + formatProvider);
|
||||
}
|
||||
}
|
||||
|
||||
// Negative
|
||||
public partial class CommandOptionInputConverterTests
|
||||
{
|
||||
private class NonStringParseable
|
||||
{
|
||||
public int Value { get; }
|
||||
|
||||
public NonStringParseable(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class CommandOptionInputConverterTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_ConvertOptionInput()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value"),
|
||||
typeof(string),
|
||||
"value"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value"),
|
||||
typeof(object),
|
||||
"value"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "true"),
|
||||
typeof(bool),
|
||||
true
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "false"),
|
||||
typeof(bool),
|
||||
false
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option"),
|
||||
typeof(bool),
|
||||
true
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "a"),
|
||||
typeof(char),
|
||||
'a'
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "15"),
|
||||
typeof(sbyte),
|
||||
(sbyte) 15
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "15"),
|
||||
typeof(byte),
|
||||
(byte) 15
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "15"),
|
||||
typeof(short),
|
||||
(short) 15
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "15"),
|
||||
typeof(ushort),
|
||||
(ushort) 15
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123"),
|
||||
typeof(int),
|
||||
123
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123"),
|
||||
typeof(uint),
|
||||
123u
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123"),
|
||||
typeof(long),
|
||||
123L
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123"),
|
||||
typeof(ulong),
|
||||
123UL
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123.45"),
|
||||
typeof(float),
|
||||
123.45f
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123.45"),
|
||||
typeof(double),
|
||||
123.45
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123.45"),
|
||||
typeof(decimal),
|
||||
123.45m
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "28 Apr 1995"),
|
||||
typeof(DateTime),
|
||||
new DateTime(1995, 04, 28)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "28 Apr 1995"),
|
||||
typeof(DateTimeOffset),
|
||||
new DateTimeOffset(new DateTime(1995, 04, 28))
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "00:14:59"),
|
||||
typeof(TimeSpan),
|
||||
new TimeSpan(00, 14, 59)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value2"),
|
||||
typeof(TestEnum),
|
||||
TestEnum.Value2
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "666"),
|
||||
typeof(int?),
|
||||
666
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option"),
|
||||
typeof(int?),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value3"),
|
||||
typeof(TestEnum?),
|
||||
TestEnum.Value3
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option"),
|
||||
typeof(TestEnum?),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "01:00:00"),
|
||||
typeof(TimeSpan?),
|
||||
new TimeSpan(01, 00, 00)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option"),
|
||||
typeof(TimeSpan?),
|
||||
null
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value"),
|
||||
typeof(TestStringConstructable),
|
||||
new TestStringConstructable("value")
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value"),
|
||||
typeof(TestStringParseable),
|
||||
TestStringParseable.Parse("value")
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "value"),
|
||||
typeof(TestStringParseableWithFormatProvider),
|
||||
TestStringParseableWithFormatProvider.Parse("value", CultureInfo.InvariantCulture)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(string[]),
|
||||
new[] {"value1", "value2"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(object[]),
|
||||
new[] {"value1", "value2"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"47", "69"}),
|
||||
typeof(int[]),
|
||||
new[] {47, 69}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value3"}),
|
||||
typeof(TestEnum[]),
|
||||
new[] {TestEnum.Value1, TestEnum.Value3}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"1337", "2441"}),
|
||||
typeof(int?[]),
|
||||
new int?[] {1337, 2441}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(TestStringConstructable[]),
|
||||
new[] {new TestStringConstructable("value1"), new TestStringConstructable("value2")}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(IEnumerable),
|
||||
new[] {"value1", "value2"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(IEnumerable<string>),
|
||||
new[] {"value1", "value2"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(IReadOnlyList<string>),
|
||||
new[] {"value1", "value2"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(List<string>),
|
||||
new List<string> {"value1", "value2"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"}),
|
||||
typeof(HashSet<string>),
|
||||
new HashSet<string> {"value1", "value2"}
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_ConvertOptionInput_Negative()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "1234.5"),
|
||||
typeof(int)
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new CommandOptionInput("option", "123"),
|
||||
typeof(NonStringParseable)
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_ConvertOptionInput))]
|
||||
public void ConvertOptionInput_Test(CommandOptionInput optionInput, Type targetType, object expectedConvertedValue)
|
||||
{
|
||||
// Arrange
|
||||
var converter = new CommandOptionInputConverter();
|
||||
|
||||
// Act
|
||||
var convertedValue = converter.ConvertOptionInput(optionInput, targetType);
|
||||
|
||||
// Assert
|
||||
convertedValue.Should().BeEquivalentTo(expectedConvertedValue);
|
||||
convertedValue?.Should().BeAssignableTo(targetType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_ConvertOptionInput_Negative))]
|
||||
public void ConvertOptionInput_Negative_Test(CommandOptionInput optionInput, Type targetType)
|
||||
{
|
||||
// Arrange
|
||||
var converter = new CommandOptionInputConverter();
|
||||
|
||||
// Act & Assert
|
||||
converter.Invoking(c => c.ConvertOptionInput(optionInput, targetType))
|
||||
.Should().ThrowExactly<InvalidCommandOptionInputException>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class CommandSchemaResolverTests
|
||||
{
|
||||
[Command("cmd", Description = "NormalCommand1 description.")]
|
||||
private class NormalCommand1 : ICommand
|
||||
{
|
||||
[CommandOption("option-a", 'a')]
|
||||
public int OptionA { get; set; }
|
||||
|
||||
[CommandOption("option-b", IsRequired = true)]
|
||||
public string OptionB { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command(Description = "NormalCommand2 description.")]
|
||||
private class NormalCommand2 : ICommand
|
||||
{
|
||||
[CommandOption("option-c", Description = "OptionC description.")]
|
||||
public bool OptionC { get; set; }
|
||||
|
||||
[CommandOption("option-d", 'd')]
|
||||
public DateTimeOffset OptionD { get; set; }
|
||||
|
||||
public string NotAnOption { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Negative
|
||||
public partial class CommandSchemaResolverTests
|
||||
{
|
||||
[Command("conflict")]
|
||||
private class ConflictingCommand1 : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command("conflict")]
|
||||
private class ConflictingCommand2 : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command]
|
||||
private class InvalidCommand1
|
||||
{
|
||||
}
|
||||
|
||||
[Command]
|
||||
private class InvalidCommand2 : ICommand
|
||||
{
|
||||
[CommandOption("conflict")]
|
||||
public string ConflictingOption1 { get; set; }
|
||||
|
||||
[CommandOption("conflict")]
|
||||
public string ConflictingOption2 { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command]
|
||||
private class InvalidCommand3 : ICommand
|
||||
{
|
||||
[CommandOption('c')]
|
||||
public string ConflictingOption1 { get; set; }
|
||||
|
||||
[CommandOption('c')]
|
||||
public string ConflictingOption2 { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class CommandSchemaResolverTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(NormalCommand1), typeof(NormalCommand2)},
|
||||
new[]
|
||||
{
|
||||
new CommandSchema(typeof(NormalCommand1), "cmd", "NormalCommand1 description.",
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(NormalCommand1).GetProperty(nameof(NormalCommand1.OptionA)),
|
||||
"option-a", 'a', false, null),
|
||||
new CommandOptionSchema(typeof(NormalCommand1).GetProperty(nameof(NormalCommand1.OptionB)),
|
||||
"option-b", null, true, null)
|
||||
}),
|
||||
new CommandSchema(typeof(NormalCommand2), null, "NormalCommand2 description.",
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(NormalCommand2).GetProperty(nameof(NormalCommand2.OptionC)),
|
||||
"option-c", null, false, "OptionC description."),
|
||||
new CommandOptionSchema(typeof(NormalCommand2).GetProperty(nameof(NormalCommand2.OptionD)),
|
||||
"option-d", 'd', false, null)
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas_Negative()
|
||||
{
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new Type[0]
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(ConflictingCommand1), typeof(ConflictingCommand2)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(InvalidCommand1)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(InvalidCommand2)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(InvalidCommand3)}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_GetCommandSchemas))]
|
||||
public void GetCommandSchemas_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<CommandSchema> expectedCommandSchemas)
|
||||
{
|
||||
// Arrange
|
||||
var commandSchemaResolver = new CommandSchemaResolver();
|
||||
|
||||
// Act
|
||||
var commandSchemas = commandSchemaResolver.GetCommandSchemas(commandTypes);
|
||||
|
||||
// Assert
|
||||
commandSchemas.Should().BeEquivalentTo(expectedCommandSchemas);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_GetCommandSchemas_Negative))]
|
||||
public void GetCommandSchemas_Negative_Test(IReadOnlyList<Type> commandTypes)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new CommandSchemaResolver();
|
||||
|
||||
// Act & Assert
|
||||
resolver.Invoking(r => r.GetCommandSchemas(commandTypes))
|
||||
.Should().ThrowExactly<InvalidCommandSchemaException>();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
CliFx.Tests/DefaultCommandFactoryTests.cs
Normal file
48
CliFx.Tests/DefaultCommandFactoryTests.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Tests.TestCommands;
|
||||
using CliFx.Tests.TestCustomTypes;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DefaultCommandFactoryTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_CreateInstance()
|
||||
{
|
||||
yield return new TestCaseData(typeof(HelloWorldDefaultCommand));
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_CreateInstance_Negative()
|
||||
{
|
||||
yield return new TestCaseData(typeof(TestNonStringParseable));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_CreateInstance))]
|
||||
public void CreateInstance_Test(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var activator = new DefaultTypeActivator();
|
||||
|
||||
// Act
|
||||
var obj = activator.CreateInstance(type);
|
||||
|
||||
// Assert
|
||||
obj.Should().BeOfType(type);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_CreateInstance_Negative))]
|
||||
public void CreateInstance_Negative_Test(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var activator = new DefaultTypeActivator();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<CliFxException>(() => activator.CreateInstance(type));
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class DelegateCommandFactoryTests
|
||||
{
|
||||
[Command]
|
||||
private class TestCommand : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Tests.TestCommands;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class DelegateCommandFactoryTests
|
||||
public class DelegateCommandFactoryTests
|
||||
{
|
||||
private static CommandSchema GetCommandSchema(Type commandType) =>
|
||||
new CommandSchemaResolver().GetCommandSchemas(new[] {commandType}).Single();
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_CreateCommand()
|
||||
private static IEnumerable<TestCaseData> GetTestCases_CreateInstance()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new Func<CommandSchema, ICommand>(schema => (ICommand) Activator.CreateInstance(schema.Type)),
|
||||
GetCommandSchema(typeof(TestCommand))
|
||||
new Func<Type, object>(Activator.CreateInstance),
|
||||
typeof(HelloWorldDefaultCommand)
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_CreateCommand))]
|
||||
public void CreateCommand_Test(Func<CommandSchema, ICommand> factoryMethod, CommandSchema commandSchema)
|
||||
private static IEnumerable<TestCaseData> GetTestCases_CreateInstance_Negative()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new Func<Type, object>(_ => null),
|
||||
typeof(HelloWorldDefaultCommand)
|
||||
);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_CreateInstance))]
|
||||
public void CreateInstance_Test(Func<Type, object> activatorFunc, Type type)
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DelegateCommandFactory(factoryMethod);
|
||||
var activator = new DelegateTypeActivator(activatorFunc);
|
||||
|
||||
// Act
|
||||
var command = factory.CreateCommand(commandSchema);
|
||||
var obj = activator.CreateInstance(type);
|
||||
|
||||
// Assert
|
||||
command.Should().BeOfType(commandSchema.Type);
|
||||
obj.Should().BeOfType(type);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_CreateInstance_Negative))]
|
||||
public void CreateInstance_Negative_Test(Func<Type, object> activatorFunc, Type type)
|
||||
{
|
||||
// Arrange
|
||||
var activator = new DelegateTypeActivator(activatorFunc);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<CliFxException>(() => activator.CreateInstance(type));
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
888
CliFx.Tests/Domain/ApplicationSchemaTests.cs
Normal file
888
CliFx.Tests/Domain/ApplicationSchemaTests.cs
Normal file
@@ -0,0 +1,888 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using CliFx.Domain;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Tests.TestCommands;
|
||||
using CliFx.Tests.TestCustomTypes;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests.Domain
|
||||
{
|
||||
[TestFixture]
|
||||
internal partial class ApplicationSchemaTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_Resolve()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[]
|
||||
{
|
||||
typeof(DivideCommand),
|
||||
typeof(ConcatCommand),
|
||||
typeof(EnvironmentVariableCommand)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new CommandSchema(typeof(DivideCommand), "div", "Divide one number by another.",
|
||||
new CommandParameterSchema[0], new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Dividend)),
|
||||
"dividend", 'D', null, true, "The number to divide."),
|
||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)),
|
||||
"divisor", 'd', null, true, "The number to divide by.")
|
||||
}),
|
||||
new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.",
|
||||
new CommandParameterSchema[0],
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)),
|
||||
null, 'i', null, true, "Input strings."),
|
||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)),
|
||||
null, 's', null, false, "String separator.")
|
||||
}),
|
||||
new CommandSchema(typeof(EnvironmentVariableCommand), null, "Reads option values from environment variables.",
|
||||
new CommandParameterSchema[0],
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(EnvironmentVariableCommand).GetProperty(nameof(EnvironmentVariableCommand.Option)),
|
||||
"opt", null, "ENV_SINGLE_VALUE", false, null)
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(SimpleParameterCommand)},
|
||||
new[]
|
||||
{
|
||||
new CommandSchema(typeof(SimpleParameterCommand), "param cmd2", "Command using positional parameters",
|
||||
new[]
|
||||
{
|
||||
new CommandParameterSchema(typeof(SimpleParameterCommand).GetProperty(nameof(SimpleParameterCommand.ParameterA)),
|
||||
0, "first", null),
|
||||
new CommandParameterSchema(typeof(SimpleParameterCommand).GetProperty(nameof(SimpleParameterCommand.ParameterB)),
|
||||
10, null, null)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(SimpleParameterCommand).GetProperty(nameof(SimpleParameterCommand.OptionA)),
|
||||
"option", 'o', null, false, null)
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(HelloWorldDefaultCommand)},
|
||||
new[]
|
||||
{
|
||||
new CommandSchema(typeof(HelloWorldDefaultCommand), null, null,
|
||||
new CommandParameterSchema[0],
|
||||
new CommandOptionSchema[0])
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_Resolve_Negative()
|
||||
{
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new Type[0]
|
||||
});
|
||||
|
||||
// Command validation failure
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(NonImplementedCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
// Same name
|
||||
new[] {typeof(ExceptionCommand), typeof(CommandExceptionCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(NonAnnotatedCommand)}
|
||||
});
|
||||
|
||||
// Parameter validation failure
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(DuplicateParameterOrderCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(DuplicateParameterNameCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(MultipleNonScalarParametersCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(NonLastNonScalarParameterCommand)}
|
||||
});
|
||||
|
||||
// Option validation failure
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(DuplicateOptionNamesCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(DuplicateOptionShortNamesCommand)}
|
||||
});
|
||||
|
||||
yield return new TestCaseData(new object[]
|
||||
{
|
||||
new[] {typeof(DuplicateOptionEnvironmentVariableNamesCommand)}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_Resolve))]
|
||||
public void Resolve_Test(
|
||||
IReadOnlyList<Type> commandTypes,
|
||||
IReadOnlyList<CommandSchema> expectedCommandSchemas)
|
||||
{
|
||||
// Act
|
||||
var applicationSchema = ApplicationSchema.Resolve(commandTypes);
|
||||
|
||||
// Assert
|
||||
applicationSchema.Commands.Should().BeEquivalentTo(expectedCommandSchemas);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_Resolve_Negative))]
|
||||
public void Resolve_Negative_Test(IReadOnlyList<Type> commandTypes)
|
||||
{
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class ApplicationSchemaTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_InitializeEntryPoint()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Object), "value")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Object = "value"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.String), "value")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {String = "value"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Bool), "true")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Bool = true}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Bool), "false")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Bool = false}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Bool))
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Bool = true}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Char), "a")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Char = 'a'}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Sbyte), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Sbyte = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Byte), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Byte = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Short), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Short = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Ushort), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Ushort = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Int), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Int = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Uint), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Uint = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Long), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Long = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Ulong), "15")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Ulong = 15}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Float), "123.45")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Float = 123.45f}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Double), "123.45")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Double = 123.45}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Decimal), "123.45")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Decimal = 123.45m}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.DateTime), "28 Apr 1995")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {DateTime = new DateTime(1995, 04, 28)}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.DateTimeOffset), "28 Apr 1995")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {DateTimeOffset = new DateTime(1995, 04, 28)}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TimeSpan), "00:14:59")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TimeSpan = new TimeSpan(00, 14, 59)}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestEnum), "value2")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TestEnum = TestEnum.Value2}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.IntNullable), "666")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {IntNullable = 666}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.IntNullable))
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {IntNullable = null}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestEnumNullable), "value3")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TestEnumNullable = TestEnum.Value3}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestEnumNullable))
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TestEnumNullable = null}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TimeSpanNullable), "01:00:00")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TimeSpanNullable = new TimeSpan(01, 00, 00)}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TimeSpanNullable))
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TimeSpanNullable = null}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestStringConstructable), "value")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TestStringConstructable = new TestStringConstructable("value")}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestStringParseable), "value")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TestStringParseable = TestStringParseable.Parse("value")}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestStringParseableWithFormatProvider), "value")
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand
|
||||
{
|
||||
TestStringParseableWithFormatProvider =
|
||||
TestStringParseableWithFormatProvider.Parse("value", CultureInfo.InvariantCulture)
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.ObjectArray), new[] {"value1", "value2"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {ObjectArray = new object[] {"value1", "value2"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.StringArray), new[] {"value1", "value2"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {StringArray = new[] {"value1", "value2"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.StringArray))
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {StringArray = new string[0]}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.IntArray), new[] {"47", "69"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {IntArray = new[] {47, 69}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestEnumArray), new[] {"value1", "value3"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {TestEnumArray = new[] {TestEnum.Value1, TestEnum.Value3}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.IntNullableArray), new[] {"1337", "2441"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {IntNullableArray = new int?[] {1337, 2441}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.TestStringConstructableArray), new[] {"value1", "value2"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand
|
||||
{
|
||||
TestStringConstructableArray = new[]
|
||||
{
|
||||
new TestStringConstructable("value1"),
|
||||
new TestStringConstructable("value2")
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.Enumerable), new[] {"value1", "value3"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {Enumerable = new[] {"value1", "value3"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.StringEnumerable), new[] {"value1", "value3"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {StringEnumerable = new[] {"value1", "value3"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.StringReadOnlyList), new[] {"value1", "value3"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {StringReadOnlyList = new[] {"value1", "value3"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.StringList), new[] {"value1", "value3"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {StringList = new List<string> {"value1", "value3"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput(nameof(AllSupportedTypesCommand.StringHashSet), new[] {"value1", "value3"})
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new AllSupportedTypesCommand {StringHashSet = new HashSet<string> {"value1", "value3"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"div"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("dividend", "13"),
|
||||
new CommandOptionInput("divisor", "8"),
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new DivideCommand {Dividend = 13, Divisor = 8}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"div"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("D", "13"),
|
||||
new CommandOptionInput("d", "8"),
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new DivideCommand {Dividend = 13, Divisor = 8}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"div"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("dividend", "13"),
|
||||
new CommandOptionInput("d", "8"),
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new DivideCommand {Dividend = 13, Divisor = 8}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"concat"},
|
||||
new[] {new CommandOptionInput("i", new[] {"foo", " ", "bar"}),}),
|
||||
new Dictionary<string, string>(),
|
||||
new ConcatCommand {Inputs = new[] {"foo", " ", "bar"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"concat"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("i", new[] {"foo", "bar"}),
|
||||
new CommandOptionInput("s", " "),
|
||||
}),
|
||||
new Dictionary<string, string>(),
|
||||
new ConcatCommand {Inputs = new[] {"foo", "bar"}, Separator = " "}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(EnvironmentVariableCommand)},
|
||||
CommandLineInput.Empty,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["ENV_SINGLE_VALUE"] = "A"
|
||||
},
|
||||
new EnvironmentVariableCommand {Option = "A"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(EnvironmentVariableWithMultipleValuesCommand)},
|
||||
CommandLineInput.Empty,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["ENV_MULTIPLE_VALUES"] = string.Join(Path.PathSeparator, "A", "B", "C")
|
||||
},
|
||||
new EnvironmentVariableWithMultipleValuesCommand {Option = new[] {"A", "B", "C"}}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(EnvironmentVariableCommand)},
|
||||
new CommandLineInput(new[] {new CommandOptionInput("opt", "X")}),
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["ENV_SINGLE_VALUE"] = "A"
|
||||
},
|
||||
new EnvironmentVariableCommand {Option = "X"}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(EnvironmentVariableWithoutCollectionPropertyCommand)},
|
||||
CommandLineInput.Empty,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["ENV_MULTIPLE_VALUES"] = string.Join(Path.PathSeparator, "A", "B", "C")
|
||||
},
|
||||
new EnvironmentVariableWithoutCollectionPropertyCommand {Option = string.Join(Path.PathSeparator, "A", "B", "C")}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ParameterCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"param", "cmd", "abc", "123", "1", "2"},
|
||||
new[] {new CommandOptionInput("o", "option value")}),
|
||||
new Dictionary<string, string>(),
|
||||
new ParameterCommand
|
||||
{
|
||||
ParameterA = "abc",
|
||||
ParameterB = 123,
|
||||
ParameterC = new[] {1, 2},
|
||||
OptionA = "option value"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_InitializeEntryPoint_Negative()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {new CommandOptionInput(nameof(AllSupportedTypesCommand.Int), "1234.5")}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {new CommandOptionInput(nameof(AllSupportedTypesCommand.Int), new[] {"123", "456"})}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {new CommandOptionInput(nameof(AllSupportedTypesCommand.Int))}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(AllSupportedTypesCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {new CommandOptionInput(nameof(AllSupportedTypesCommand.NonConvertible), "123")}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand)},
|
||||
new CommandLineInput(new[] {"div"}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand)},
|
||||
new CommandLineInput(new[] {"div", "-D", "13"}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new CommandLineInput(new[] {"concat"}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ConcatCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"concat"},
|
||||
new[] {new CommandOptionInput("s", "_")}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ParameterCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"param", "cmd"},
|
||||
new[] {new CommandOptionInput("o", "option value")}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(ParameterCommand)},
|
||||
new CommandLineInput(
|
||||
new[] {"param", "cmd", "abc", "123", "invalid"},
|
||||
new[] {new CommandOptionInput("o", "option value")}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand)},
|
||||
new CommandLineInput(new[] {"non-existing"}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(BrokenEnumerableCommand)},
|
||||
new CommandLineInput(new[] {"value1", "value2"}),
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_InitializeEntryPoint))]
|
||||
public void InitializeEntryPoint_Test(
|
||||
IReadOnlyList<Type> commandTypes,
|
||||
CommandLineInput commandLineInput,
|
||||
IReadOnlyDictionary<string, string> environmentVariables,
|
||||
ICommand expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var applicationSchema = ApplicationSchema.Resolve(commandTypes);
|
||||
var typeActivator = new DefaultTypeActivator();
|
||||
|
||||
// Act
|
||||
var command = applicationSchema.InitializeEntryPoint(commandLineInput, environmentVariables, typeActivator);
|
||||
|
||||
// Assert
|
||||
command.Should().BeEquivalentTo(expectedResult, o => o.RespectingRuntimeTypes());
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_InitializeEntryPoint_Negative))]
|
||||
public void InitializeEntryPoint_Negative_Test(
|
||||
IReadOnlyList<Type> commandTypes,
|
||||
CommandLineInput commandLineInput,
|
||||
IReadOnlyDictionary<string, string> environmentVariables)
|
||||
{
|
||||
// Arrange
|
||||
var applicationSchema = ApplicationSchema.Resolve(commandTypes);
|
||||
var typeActivator = new DefaultTypeActivator();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<CliFxException>(() =>
|
||||
applicationSchema.InitializeEntryPoint(commandLineInput, environmentVariables, typeActivator));
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
264
CliFx.Tests/Domain/CommandLineInputTests.cs
Normal file
264
CliFx.Tests/Domain/CommandLineInputTests.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Domain;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests.Domain
|
||||
{
|
||||
[TestFixture]
|
||||
internal class CommandLineInputTests
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_Parse()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new string[0],
|
||||
CommandLineInput.Empty
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"param"},
|
||||
new CommandLineInput(
|
||||
new[] {"param"})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"cmd", "param"},
|
||||
new CommandLineInput(
|
||||
new[] {"cmd", "param"})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option", "value"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option1", "value1", "--option2", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option1", "value1"),
|
||||
new CommandOptionInput("option2", "value2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option", "value1", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option", "value1", "--option", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value1", "-b", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a", "value1"),
|
||||
new CommandOptionInput("b", "value2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value1", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "value1", "-a", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--option1", "value1", "-b", "value2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option1", "value1"),
|
||||
new CommandOptionInput("b", "value2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--switch"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("switch")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--switch1", "--switch2"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("switch1"),
|
||||
new CommandOptionInput("switch2")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-s"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("s")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-a", "-b"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-ab"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"-ab", "value"},
|
||||
new CommandLineInput(
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"cmd", "--option", "value"},
|
||||
new CommandLineInput(
|
||||
new[] {"cmd"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"[debug]"},
|
||||
new CommandLineInput(
|
||||
new[] {"debug"},
|
||||
new string[0],
|
||||
new CommandOptionInput[0])
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"[debug]", "[preview]"},
|
||||
new CommandLineInput(
|
||||
new[] {"debug", "preview"},
|
||||
new string[0],
|
||||
new CommandOptionInput[0])
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"cmd", "param1", "param2", "--option", "value"},
|
||||
new CommandLineInput(
|
||||
new[] {"cmd", "param1", "param2"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"[debug]", "[preview]", "-o", "value"},
|
||||
new CommandLineInput(
|
||||
new[] {"debug", "preview"},
|
||||
new string[0],
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("o", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"cmd", "[debug]", "[preview]", "-o", "value"},
|
||||
new CommandLineInput(
|
||||
new[] {"debug", "preview"},
|
||||
new[] {"cmd"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("o", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"cmd", "[debug]", "[preview]", "-o", "value"},
|
||||
new CommandLineInput(
|
||||
new[] {"debug", "preview"},
|
||||
new[] {"cmd"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("o", "value")
|
||||
})
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"cmd", "param", "[debug]", "[preview]", "-o", "value"},
|
||||
new CommandLineInput(
|
||||
new[] {"debug", "preview"},
|
||||
new[] {"cmd", "param"},
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("o", "value")
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_Parse))]
|
||||
public void Parse_Test(IReadOnlyList<string> commandLineArguments, CommandLineInput expectedResult)
|
||||
{
|
||||
// Act
|
||||
var result = CommandLineInput.Parse(commandLineArguments);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEquivalentTo(expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using CliWrap;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
@@ -8,66 +12,71 @@ namespace CliFx.Tests
|
||||
[TestFixture]
|
||||
public class DummyTests
|
||||
{
|
||||
private static string DummyFilePath => typeof(Dummy.Program).Assembly.Location;
|
||||
private static Assembly DummyAssembly { get; } = typeof(Dummy.Program).Assembly;
|
||||
|
||||
private static string DummyVersionText => typeof(Dummy.Program).Assembly.GetName().Version.ToString();
|
||||
|
||||
[Test]
|
||||
[TestCase("", "Hello world")]
|
||||
[TestCase("-t .NET", "Hello .NET")]
|
||||
[TestCase("-e", "Hello world!")]
|
||||
[TestCase("sum -v 1 2", "3")]
|
||||
[TestCase("sum -v 2.75 3.6 4.18", "10.53")]
|
||||
[TestCase("sum -v 4 -v 16", "20")]
|
||||
[TestCase("sum --values 2 5 --values 3", "10")]
|
||||
[TestCase("log -v 100", "2")]
|
||||
[TestCase("log --value 256 --base 2", "8")]
|
||||
public async Task CliApplication_RunAsync_Test(string arguments, string expectedOutput)
|
||||
private static IEnumerable<TestCaseData> GetTestCases_RunAsync()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = await Cli.Wrap(DummyFilePath)
|
||||
.SetArguments(arguments)
|
||||
yield return new TestCaseData(
|
||||
new[] {"--version"},
|
||||
new Dictionary<string, string>(),
|
||||
$"v{DummyAssembly.GetName().Version}"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new string[0],
|
||||
new Dictionary<string, string>(),
|
||||
"Hello World!"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--target", "Earth"},
|
||||
new Dictionary<string, string>(),
|
||||
"Hello Earth!"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new string[0],
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["ENV_TARGET"] = "Mars"
|
||||
},
|
||||
"Hello Mars!"
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] {"--target", "Earth"},
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["ENV_TARGET"] = "Mars"
|
||||
},
|
||||
"Hello Earth!"
|
||||
);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_RunAsync))]
|
||||
public async Task RunAsync_Test(
|
||||
IReadOnlyList<string> arguments,
|
||||
IReadOnlyDictionary<string, string> environmentVariables,
|
||||
string expectedStdOut)
|
||||
{
|
||||
// Arrange
|
||||
var cli = Cli.Wrap("dotnet")
|
||||
.SetArguments(arguments.Prepend(DummyAssembly.Location).ToArray())
|
||||
.EnableExitCodeValidation()
|
||||
.EnableStandardErrorValidation()
|
||||
.ExecuteAsync();
|
||||
.SetStandardOutputCallback(Console.WriteLine)
|
||||
.SetStandardErrorCallback(Console.WriteLine);
|
||||
|
||||
foreach (var (key, value) in environmentVariables)
|
||||
cli.SetEnvironmentVariable(key, value);
|
||||
|
||||
// Act
|
||||
var result = await cli.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
result.StandardOutput.Trim().Should().Be(expectedOutput);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("--version")]
|
||||
public async Task CliApplication_RunAsync_ShowVersion_Test(string arguments)
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = await Cli.Wrap(DummyFilePath)
|
||||
.SetArguments(arguments)
|
||||
.EnableExitCodeValidation()
|
||||
.EnableStandardErrorValidation()
|
||||
.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
result.StandardOutput.Trim().Should().Be(DummyVersionText);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("--help")]
|
||||
[TestCase("-h")]
|
||||
[TestCase("sum -h")]
|
||||
[TestCase("sum --help")]
|
||||
[TestCase("log -h")]
|
||||
[TestCase("log --help")]
|
||||
public async Task CliApplication_RunAsync_ShowHelp_Test(string arguments)
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = await Cli.Wrap(DummyFilePath)
|
||||
.SetArguments(arguments)
|
||||
.EnableExitCodeValidation()
|
||||
.EnableStandardErrorValidation()
|
||||
.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
result.StandardOutput.Trim().Should().NotBeNullOrWhiteSpace();
|
||||
result.ExitCode.Should().Be(0);
|
||||
result.StandardError.Should().BeNullOrWhiteSpace();
|
||||
result.StandardOutput.TrimEnd().Should().Be(expectedStdOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
public partial class HelpTextRendererTests
|
||||
{
|
||||
[Command(Description = "DefaultCommand description.")]
|
||||
private class DefaultCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-a", 'a', Description = "OptionA description.")]
|
||||
public string OptionA { get; set; }
|
||||
|
||||
[CommandOption("option-b", 'b', Description = "OptionB description.")]
|
||||
public string OptionB { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command("cmd", Description = "NamedCommand description.")]
|
||||
private class NamedCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-c", 'c', Description = "OptionC description.")]
|
||||
public string OptionC { get; set; }
|
||||
|
||||
[CommandOption("option-d", 'd', Description = "OptionD description.")]
|
||||
public string OptionD { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command("cmd sub", Description = "NamedSubCommand description.")]
|
||||
private class NamedSubCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-e", 'e', Description = "OptionE description.")]
|
||||
public string OptionE { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class HelpTextRendererTests
|
||||
{
|
||||
private static HelpTextSource CreateHelpTextSource(IReadOnlyList<Type> availableCommandTypes, Type targetCommandType)
|
||||
{
|
||||
var commandSchemaResolver = new CommandSchemaResolver();
|
||||
|
||||
var applicationMetadata = new ApplicationMetadata("TestApp", "testapp", "1.0", null);
|
||||
var availableCommandSchemas = commandSchemaResolver.GetCommandSchemas(availableCommandTypes);
|
||||
var targetCommandSchema = availableCommandSchemas.Single(s => s.Type == targetCommandType);
|
||||
|
||||
return new HelpTextSource(applicationMetadata, availableCommandSchemas, targetCommandSchema);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetTestCases_RenderHelpText()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
CreateHelpTextSource(
|
||||
new[] {typeof(DefaultCommand), typeof(NamedCommand), typeof(NamedSubCommand)},
|
||||
typeof(DefaultCommand)),
|
||||
|
||||
new[]
|
||||
{
|
||||
"Usage",
|
||||
"[command]", "[options]",
|
||||
"Options",
|
||||
"-a|--option-a", "OptionA description.",
|
||||
"-b|--option-b", "OptionB description.",
|
||||
"-h|--help", "Shows help text.",
|
||||
"--version", "Shows version information.",
|
||||
"Commands",
|
||||
"cmd", "NamedCommand description.",
|
||||
"You can run", "to show help on a specific command."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
CreateHelpTextSource(
|
||||
new[] {typeof(DefaultCommand), typeof(NamedCommand), typeof(NamedSubCommand)},
|
||||
typeof(NamedCommand)),
|
||||
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"NamedCommand description.",
|
||||
"Usage",
|
||||
"cmd", "[command]", "[options]",
|
||||
"Options",
|
||||
"-c|--option-c", "OptionC description.",
|
||||
"-d|--option-d", "OptionD description.",
|
||||
"-h|--help", "Shows help text.",
|
||||
"Commands",
|
||||
"sub", "NamedSubCommand description.",
|
||||
"You can run", "to show help on a specific command."
|
||||
}
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
CreateHelpTextSource(
|
||||
new[] {typeof(DefaultCommand), typeof(NamedCommand), typeof(NamedSubCommand)},
|
||||
typeof(NamedSubCommand)),
|
||||
|
||||
new[]
|
||||
{
|
||||
"Description",
|
||||
"NamedSubCommand description.",
|
||||
"Usage",
|
||||
"cmd sub", "[options]",
|
||||
"Options",
|
||||
"-e|--option-e", "OptionE description.",
|
||||
"-h|--help", "Shows help text."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(GetTestCases_RenderHelpText))]
|
||||
public void RenderHelpText_Test(HelpTextSource source, IReadOnlyList<string> expectedSubstrings)
|
||||
{
|
||||
// Arrange
|
||||
using (var stdout = new StringWriter())
|
||||
{
|
||||
var renderer = new HelpTextRenderer();
|
||||
var console = new VirtualConsole(stdout);
|
||||
|
||||
// Act
|
||||
renderer.RenderHelpText(console, source);
|
||||
|
||||
// Assert
|
||||
stdout.ToString().Should().ContainAll(expectedSubstrings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
CliFx.Tests/SystemConsoleTests.cs
Normal file
39
CliFx.Tests/SystemConsoleTests.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SystemConsoleTests
|
||||
{
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Reset console color so it doesn't carry on into the next tests
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
[Test(Description = "Must be in sync with system console")]
|
||||
public void Smoke_Test()
|
||||
{
|
||||
// Arrange
|
||||
var console = new SystemConsole();
|
||||
|
||||
// Act
|
||||
console.ResetColor();
|
||||
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||
|
||||
// Assert
|
||||
console.Input.Should().BeSameAs(Console.In);
|
||||
console.IsInputRedirected.Should().Be(Console.IsInputRedirected);
|
||||
console.Output.Should().BeSameAs(Console.Out);
|
||||
console.IsOutputRedirected.Should().Be(Console.IsOutputRedirected);
|
||||
console.Error.Should().BeSameAs(Console.Error);
|
||||
console.IsErrorRedirected.Should().Be(Console.IsErrorRedirected);
|
||||
console.ForegroundColor.Should().Be(Console.ForegroundColor);
|
||||
console.BackgroundColor.Should().Be(Console.BackgroundColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/AllRequiredOptionsCommand.cs
Normal file
17
CliFx.Tests/TestCommands/AllRequiredOptionsCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("allrequired", Description = "AllRequiredOptionsCommand description.")]
|
||||
public class AllRequiredOptionsCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-f", 'f', IsRequired = true, Description = "OptionF description.")]
|
||||
public string? OptionF { get; set; }
|
||||
|
||||
[CommandOption("option-g", 'g', IsRequired = true, Description = "OptionG description.")]
|
||||
public string? OptionFG { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
126
CliFx.Tests/TestCommands/AllSupportedTypesCommand.cs
Normal file
126
CliFx.Tests/TestCommands/AllSupportedTypesCommand.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Tests.TestCustomTypes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class AllSupportedTypesCommand : ICommand
|
||||
{
|
||||
[CommandOption(nameof(Object))]
|
||||
public object? Object { get; set; } = 42;
|
||||
|
||||
[CommandOption(nameof(String))]
|
||||
public string? String { get; set; } = "foo bar";
|
||||
|
||||
[CommandOption(nameof(Bool))]
|
||||
public bool Bool { get; set; }
|
||||
|
||||
[CommandOption(nameof(Char))]
|
||||
public char Char { get; set; }
|
||||
|
||||
[CommandOption(nameof(Sbyte))]
|
||||
public sbyte Sbyte { get; set; }
|
||||
|
||||
[CommandOption(nameof(Byte))]
|
||||
public byte Byte { get; set; }
|
||||
|
||||
[CommandOption(nameof(Short))]
|
||||
public short Short { get; set; }
|
||||
|
||||
[CommandOption(nameof(Ushort))]
|
||||
public ushort Ushort { get; set; }
|
||||
|
||||
[CommandOption(nameof(Int))]
|
||||
public int Int { get; set; }
|
||||
|
||||
[CommandOption(nameof(Uint))]
|
||||
public uint Uint { get; set; }
|
||||
|
||||
[CommandOption(nameof(Long))]
|
||||
public long Long { get; set; }
|
||||
|
||||
[CommandOption(nameof(Ulong))]
|
||||
public ulong Ulong { get; set; }
|
||||
|
||||
[CommandOption(nameof(Float))]
|
||||
public float Float { get; set; }
|
||||
|
||||
[CommandOption(nameof(Double))]
|
||||
public double Double { get; set; }
|
||||
|
||||
[CommandOption(nameof(Decimal))]
|
||||
public decimal Decimal { get; set; }
|
||||
|
||||
[CommandOption(nameof(DateTime))]
|
||||
public DateTime DateTime { get; set; }
|
||||
|
||||
[CommandOption(nameof(DateTimeOffset))]
|
||||
public DateTimeOffset DateTimeOffset { get; set; }
|
||||
|
||||
[CommandOption(nameof(TimeSpan))]
|
||||
public TimeSpan TimeSpan { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestEnum))]
|
||||
public TestEnum TestEnum { get; set; }
|
||||
|
||||
[CommandOption(nameof(IntNullable))]
|
||||
public int? IntNullable { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestEnumNullable))]
|
||||
public TestEnum? TestEnumNullable { get; set; }
|
||||
|
||||
[CommandOption(nameof(TimeSpanNullable))]
|
||||
public TimeSpan? TimeSpanNullable { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestStringConstructable))]
|
||||
public TestStringConstructable? TestStringConstructable { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestStringParseable))]
|
||||
public TestStringParseable? TestStringParseable { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestStringParseableWithFormatProvider))]
|
||||
public TestStringParseableWithFormatProvider? TestStringParseableWithFormatProvider { get; set; }
|
||||
|
||||
[CommandOption(nameof(ObjectArray))]
|
||||
public object[]? ObjectArray { get; set; }
|
||||
|
||||
[CommandOption(nameof(StringArray))]
|
||||
public string[]? StringArray { get; set; }
|
||||
|
||||
[CommandOption(nameof(IntArray))]
|
||||
public int[]? IntArray { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestEnumArray))]
|
||||
public TestEnum[]? TestEnumArray { get; set; }
|
||||
|
||||
[CommandOption(nameof(IntNullableArray))]
|
||||
public int?[]? IntNullableArray { get; set; }
|
||||
|
||||
[CommandOption(nameof(TestStringConstructableArray))]
|
||||
public TestStringConstructable[]? TestStringConstructableArray { get; set; }
|
||||
|
||||
[CommandOption(nameof(Enumerable))]
|
||||
public IEnumerable? Enumerable { get; set; }
|
||||
|
||||
[CommandOption(nameof(StringEnumerable))]
|
||||
public IEnumerable<string>? StringEnumerable { get; set; }
|
||||
|
||||
[CommandOption(nameof(StringReadOnlyList))]
|
||||
public IReadOnlyList<string>? StringReadOnlyList { get; set; }
|
||||
|
||||
[CommandOption(nameof(StringList))]
|
||||
public List<string>? StringList { get; set; }
|
||||
|
||||
[CommandOption(nameof(StringHashSet))]
|
||||
public HashSet<string>? StringHashSet { get; set; }
|
||||
|
||||
[CommandOption(nameof(NonConvertible))]
|
||||
public TestNonStringParseable? NonConvertible { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
15
CliFx.Tests/TestCommands/BrokenEnumerableCommand.cs
Normal file
15
CliFx.Tests/TestCommands/BrokenEnumerableCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Tests.TestCustomTypes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class BrokenEnumerableCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0)]
|
||||
public TestCustomEnumerable<string>? Test { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
16
CliFx.Tests/TestCommands/CancellableCommand.cs
Normal file
16
CliFx.Tests/TestCommands/CancellableCommand.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("cancel")]
|
||||
public class CancellableCommand : ICommand
|
||||
{
|
||||
public async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(3), console.GetCancellationToken());
|
||||
console.Output.WriteLine("Never printed");
|
||||
}
|
||||
}
|
||||
}
|
||||
18
CliFx.Tests/TestCommands/CommandExceptionCommand.cs
Normal file
18
CliFx.Tests/TestCommands/CommandExceptionCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("exc")]
|
||||
public class CommandExceptionCommand : ICommand
|
||||
{
|
||||
[CommandOption("code", 'c')]
|
||||
public int ExitCode { get; set; } = 1337;
|
||||
|
||||
[CommandOption("msg", 'm')]
|
||||
public string? Message { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode);
|
||||
}
|
||||
}
|
||||
22
CliFx.Tests/TestCommands/ConcatCommand.cs
Normal file
22
CliFx.Tests/TestCommands/ConcatCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("concat", Description = "Concatenate strings.")]
|
||||
public class ConcatCommand : ICommand
|
||||
{
|
||||
[CommandOption('i', IsRequired = true, Description = "Input strings.")]
|
||||
public IReadOnlyList<string> Inputs { get; set; }
|
||||
|
||||
[CommandOption('s', Description = "String separator.")]
|
||||
public string Separator { get; set; } = "";
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
console.Output.WriteLine(string.Join(Separator, Inputs));
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
CliFx.Tests/TestCommands/DivideCommand.cs
Normal file
24
CliFx.Tests/TestCommands/DivideCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("div", Description = "Divide one number by another.")]
|
||||
public class DivideCommand : ICommand
|
||||
{
|
||||
[CommandOption("dividend", 'D', IsRequired = true, Description = "The number to divide.")]
|
||||
public double Dividend { get; set; }
|
||||
|
||||
[CommandOption("divisor", 'd', IsRequired = true, Description = "The number to divide by.")]
|
||||
public double Divisor { get; set; }
|
||||
|
||||
// This property should be ignored by resolver
|
||||
public bool NotAnOption { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
console.Output.WriteLine(Dividend / Divisor);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class DuplicateOptionEnvironmentVariableNamesCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-a", EnvironmentVariableName = "ENV_VAR")]
|
||||
public string? OptionA { get; set; }
|
||||
|
||||
[CommandOption("option-b", EnvironmentVariableName = "ENV_VAR")]
|
||||
public string? OptionB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/DuplicateOptionNamesCommand.cs
Normal file
17
CliFx.Tests/TestCommands/DuplicateOptionNamesCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class DuplicateOptionNamesCommand : ICommand
|
||||
{
|
||||
[CommandOption("fruits")]
|
||||
public string? Apples { get; set; }
|
||||
|
||||
[CommandOption("fruits")]
|
||||
public string? Oranges { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/DuplicateOptionShortNamesCommand.cs
Normal file
17
CliFx.Tests/TestCommands/DuplicateOptionShortNamesCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class DuplicateOptionShortNamesCommand : ICommand
|
||||
{
|
||||
[CommandOption('x')]
|
||||
public string? OptionA { get; set; }
|
||||
|
||||
[CommandOption('x')]
|
||||
public string? OptionB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/DuplicateParameterNameCommand.cs
Normal file
17
CliFx.Tests/TestCommands/DuplicateParameterNameCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class DuplicateParameterNameCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0, Name = "param")]
|
||||
public string? ParameterA { get; set; }
|
||||
|
||||
[CommandParameter(1, Name = "param")]
|
||||
public string? ParameterB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/DuplicateParameterOrderCommand.cs
Normal file
17
CliFx.Tests/TestCommands/DuplicateParameterOrderCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class DuplicateParameterOrderCommand : ICommand
|
||||
{
|
||||
[CommandParameter(13)]
|
||||
public string? ParameterA { get; set; }
|
||||
|
||||
[CommandParameter(13)]
|
||||
public string? ParameterB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
14
CliFx.Tests/TestCommands/EnvironmentVariableCommand.cs
Normal file
14
CliFx.Tests/TestCommands/EnvironmentVariableCommand.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
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 ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
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 ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
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 ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
15
CliFx.Tests/TestCommands/ExceptionCommand.cs
Normal file
15
CliFx.Tests/TestCommands/ExceptionCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("exc")]
|
||||
public class ExceptionCommand : ICommand
|
||||
{
|
||||
[CommandOption("msg", 'm')]
|
||||
public string? Message { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => throw new Exception(Message);
|
||||
}
|
||||
}
|
||||
15
CliFx.Tests/TestCommands/HelloWorldDefaultCommand.cs
Normal file
15
CliFx.Tests/TestCommands/HelloWorldDefaultCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class HelloWorldDefaultCommand : ICommand
|
||||
{
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
console.Output.WriteLine("Hello world.");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/HelpDefaultCommand.cs
Normal file
17
CliFx.Tests/TestCommands/HelpDefaultCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command(Description = "HelpDefaultCommand description.")]
|
||||
public class HelpDefaultCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-a", 'a', Description = "OptionA description.")]
|
||||
public string? OptionA { get; set; }
|
||||
|
||||
[CommandOption("option-b", 'b', Description = "OptionB description.")]
|
||||
public string? OptionB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/HelpNamedCommand.cs
Normal file
17
CliFx.Tests/TestCommands/HelpNamedCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("cmd", Description = "HelpNamedCommand description.")]
|
||||
public class HelpNamedCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-c", 'c', Description = "OptionC description.")]
|
||||
public string? OptionC { get; set; }
|
||||
|
||||
[CommandOption("option-d", 'd', Description = "OptionD description.")]
|
||||
public string? OptionD { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
14
CliFx.Tests/TestCommands/HelpSubCommand.cs
Normal file
14
CliFx.Tests/TestCommands/HelpSubCommand.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("cmd sub", Description = "HelpSubCommand description.")]
|
||||
public class HelpSubCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-e", 'e', Description = "OptionE description.")]
|
||||
public string? OptionE { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class MultipleNonScalarParametersCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0)]
|
||||
public IReadOnlyList<string>? ParameterA { get; set; }
|
||||
|
||||
[CommandParameter(1)]
|
||||
public IReadOnlyList<string>? ParameterB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
9
CliFx.Tests/TestCommands/NonAnnotatedCommand.cs
Normal file
9
CliFx.Tests/TestCommands/NonAnnotatedCommand.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
public class NonAnnotatedCommand : ICommand
|
||||
{
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
9
CliFx.Tests/TestCommands/NonImplementedCommand.cs
Normal file
9
CliFx.Tests/TestCommands/NonImplementedCommand.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class NonImplementedCommand
|
||||
{
|
||||
}
|
||||
}
|
||||
18
CliFx.Tests/TestCommands/NonLastNonScalarParameterCommand.cs
Normal file
18
CliFx.Tests/TestCommands/NonLastNonScalarParameterCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command]
|
||||
public class NonLastNonScalarParameterCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0)]
|
||||
public IReadOnlyList<string>? ParameterA { get; set; }
|
||||
|
||||
[CommandParameter(1)]
|
||||
public string? ParameterB { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
24
CliFx.Tests/TestCommands/ParameterCommand.cs
Normal file
24
CliFx.Tests/TestCommands/ParameterCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("param cmd", Description = "Command using positional parameters")]
|
||||
public class ParameterCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0, Name = "first")]
|
||||
public string? ParameterA { get; set; }
|
||||
|
||||
[CommandParameter(10)]
|
||||
public int? ParameterB { get; set; }
|
||||
|
||||
[CommandParameter(20, Description = "A list of numbers", Name = "third list")]
|
||||
public IEnumerable<int>? ParameterC { get; set; }
|
||||
|
||||
[CommandOption("option", 'o')]
|
||||
public string? OptionA { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
20
CliFx.Tests/TestCommands/SimpleParameterCommand.cs
Normal file
20
CliFx.Tests/TestCommands/SimpleParameterCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("param cmd2", Description = "Command using positional parameters")]
|
||||
public class SimpleParameterCommand : ICommand
|
||||
{
|
||||
[CommandParameter(0, Name = "first")]
|
||||
public string? ParameterA { get; set; }
|
||||
|
||||
[CommandParameter(10)]
|
||||
public int? ParameterB { get; set; }
|
||||
|
||||
[CommandOption("option", 'o')]
|
||||
public string? OptionA { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
17
CliFx.Tests/TestCommands/SomeRequiredOptionsCommand.cs
Normal file
17
CliFx.Tests/TestCommands/SomeRequiredOptionsCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
|
||||
namespace CliFx.Tests.TestCommands
|
||||
{
|
||||
[Command("somerequired", Description = "SomeRequiredOptionsCommand description.")]
|
||||
public class SomeRequiredOptionsCommand : ICommand
|
||||
{
|
||||
[CommandOption("option-f", 'f', IsRequired = true, Description = "OptionF description.")]
|
||||
public string? OptionF { get; set; }
|
||||
|
||||
[CommandOption("option-g", 'g', Description = "OptionG description.")]
|
||||
public string? OptionFG { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
}
|
||||
14
CliFx.Tests/TestCustomTypes/TestCustomEnumerable.cs
Normal file
14
CliFx.Tests/TestCustomTypes/TestCustomEnumerable.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CliFx.Tests.TestCustomTypes
|
||||
{
|
||||
public class TestCustomEnumerable<T> : IEnumerable<T>
|
||||
{
|
||||
private readonly T[] _arr = new T[0];
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _arr).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
9
CliFx.Tests/TestCustomTypes/TestEnum.cs
Normal file
9
CliFx.Tests/TestCustomTypes/TestEnum.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CliFx.Tests.TestCustomTypes
|
||||
{
|
||||
public enum TestEnum
|
||||
{
|
||||
Value1,
|
||||
Value2,
|
||||
Value3
|
||||
}
|
||||
}
|
||||
12
CliFx.Tests/TestCustomTypes/TestNonStringParseable.cs
Normal file
12
CliFx.Tests/TestCustomTypes/TestNonStringParseable.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace CliFx.Tests.TestCustomTypes
|
||||
{
|
||||
public class TestNonStringParseable
|
||||
{
|
||||
public int Value { get; }
|
||||
|
||||
public TestNonStringParseable(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
CliFx.Tests/TestCustomTypes/TestStringConstructable.cs
Normal file
12
CliFx.Tests/TestCustomTypes/TestStringConstructable.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace CliFx.Tests.TestCustomTypes
|
||||
{
|
||||
public class TestStringConstructable
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
public TestStringConstructable(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
CliFx.Tests/TestCustomTypes/TestStringParseable.cs
Normal file
14
CliFx.Tests/TestCustomTypes/TestStringParseable.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace CliFx.Tests.TestCustomTypes
|
||||
{
|
||||
public class TestStringParseable
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private TestStringParseable(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static TestStringParseable Parse(string value) => new TestStringParseable(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace CliFx.Tests.TestCustomTypes
|
||||
{
|
||||
public class TestStringParseableWithFormatProvider
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private TestStringParseableWithFormatProvider(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static TestStringParseableWithFormatProvider Parse(string value, IFormatProvider formatProvider) =>
|
||||
new TestStringParseableWithFormatProvider(value + " " + formatProvider);
|
||||
}
|
||||
}
|
||||
54
CliFx.Tests/Utilities/ProgressTickerTests.cs
Normal file
54
CliFx.Tests/Utilities/ProgressTickerTests.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CliFx.Utilities;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests.Utilities
|
||||
{
|
||||
[TestFixture]
|
||||
public class ProgressTickerTests
|
||||
{
|
||||
[Test]
|
||||
public void Report_Test()
|
||||
{
|
||||
// Arrange
|
||||
var formatProvider = CultureInfo.InvariantCulture;
|
||||
|
||||
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 progressStringValues = progressValues.Select(p => p.ToString("P2", formatProvider)).ToArray();
|
||||
|
||||
// Act
|
||||
foreach (var progress in progressValues)
|
||||
ticker.Report(progress);
|
||||
|
||||
// Assert
|
||||
stdout.ToString().Should().ContainAll(progressStringValues);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Report_Redirected_Test()
|
||||
{
|
||||
// Arrange
|
||||
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();
|
||||
|
||||
// Act
|
||||
foreach (var progress in progressValues)
|
||||
ticker.Report(progress);
|
||||
|
||||
// Assert
|
||||
stdout.ToString().Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
40
CliFx.Tests/VirtualConsoleTests.cs
Normal file
40
CliFx.Tests/VirtualConsoleTests.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class VirtualConsoleTests
|
||||
{
|
||||
[Test(Description = "Must not leak to system console")]
|
||||
public void Smoke_Test()
|
||||
{
|
||||
// Arrange
|
||||
using var stdin = new StringReader("hello world");
|
||||
using var stdout = new StringWriter();
|
||||
using var stderr = new StringWriter();
|
||||
|
||||
var console = new VirtualConsole(stdin, stdout, stderr);
|
||||
|
||||
// Act
|
||||
console.ResetColor();
|
||||
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||
|
||||
// Assert
|
||||
console.Input.Should().BeSameAs(stdin);
|
||||
console.Input.Should().NotBeSameAs(Console.In);
|
||||
console.IsInputRedirected.Should().BeTrue();
|
||||
console.Output.Should().BeSameAs(stdout);
|
||||
console.Output.Should().NotBeSameAs(Console.Out);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
CliFx.props
Normal file
11
CliFx.props
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
31
CliFx.sln
31
CliFx.sln
@@ -7,18 +7,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx", "CliFx\CliFx.csproj
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx.Tests", "CliFx.Tests\CliFx.Tests.csproj", "{268CF863-65A5-49BB-93CF-08972B7756DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx.Tests.Dummy", "CliFx.Tests.Dummy\CliFx.Tests.Dummy.csproj", "{4904B3EB-3286-4F1B-8B74-6FF051C8E787}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3AAE8166-BB8E-49DA-844C-3A0EE6BD40A0}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Changelog.md = Changelog.md
|
||||
License.txt = License.txt
|
||||
Readme.md = Readme.md
|
||||
CliFx.props = CliFx.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx.Benchmarks", "CliFx.Benchmarks\CliFx.Benchmarks.csproj", "{8ACD6DC2-D768-4850-9223-5B7C83A78513}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CliFx.Demo", "CliFx.Demo\CliFx.Demo.csproj", "{AAB6844C-BF71-448F-A11B-89AEE459AB15}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx.Demo", "CliFx.Demo\CliFx.Demo.csproj", "{AAB6844C-BF71-448F-A11B-89AEE459AB15}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CliFx.Tests.Dummy", "CliFx.Tests.Dummy\CliFx.Tests.Dummy.csproj", "{F717347D-8656-44DA-A4A2-BE515E8C4655}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -54,18 +55,6 @@ Global
|
||||
{268CF863-65A5-49BB-93CF-08972B7756DC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{268CF863-65A5-49BB-93CF-08972B7756DC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{268CF863-65A5-49BB-93CF-08972B7756DC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Release|x64.Build.0 = Release|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{4904B3EB-3286-4F1B-8B74-6FF051C8E787}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8ACD6DC2-D768-4850-9223-5B7C83A78513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8ACD6DC2-D768-4850-9223-5B7C83A78513}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8ACD6DC2-D768-4850-9223-5B7C83A78513}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
@@ -90,6 +79,18 @@ Global
|
||||
{AAB6844C-BF71-448F-A11B-89AEE459AB15}.Release|x64.Build.0 = Release|Any CPU
|
||||
{AAB6844C-BF71-448F-A11B-89AEE459AB15}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{AAB6844C-BF71-448F-A11B-89AEE459AB15}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F717347D-8656-44DA-A4A2-BE515E8C4655}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
38
CliFx/ApplicationConfiguration.cs
Normal file
38
CliFx/ApplicationConfiguration.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration of an application.
|
||||
/// </summary>
|
||||
public class ApplicationConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Command types defined in this application.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Type> CommandTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether debug mode is allowed in this application.
|
||||
/// </summary>
|
||||
public bool IsDebugModeAllowed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether preview mode is allowed in this application.
|
||||
/// </summary>
|
||||
public bool IsPreviewModeAllowed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="ApplicationConfiguration"/>.
|
||||
/// </summary>
|
||||
public ApplicationConfiguration(
|
||||
IReadOnlyList<Type> commandTypes,
|
||||
bool isDebugModeAllowed, bool isPreviewModeAllowed)
|
||||
{
|
||||
CommandTypes = commandTypes;
|
||||
IsDebugModeAllowed = isDebugModeAllowed;
|
||||
IsPreviewModeAllowed = isPreviewModeAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using CliFx.Internal;
|
||||
|
||||
namespace CliFx.Models
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata associated with an application.
|
||||
@@ -25,17 +23,17 @@ namespace CliFx.Models
|
||||
/// <summary>
|
||||
/// Application description.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
public string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
||||
/// </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));
|
||||
ExecutableName = executableName.GuardNotNull(nameof(executableName));
|
||||
VersionText = versionText.GuardNotNull(nameof(versionText));
|
||||
Description = description; // can be null
|
||||
Title = title;
|
||||
ExecutableName = executableName;
|
||||
VersionText = versionText;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,27 +10,29 @@ namespace CliFx.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Command name.
|
||||
/// If the name is not set, the command is treated as a default command, i.e. the one that gets executed when the user
|
||||
/// does not specify a command name in the arguments.
|
||||
/// All commands in an application must have different names. Likewise, only one command without a name is allowed.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
public string? Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command description, which is used in help text.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||
/// </summary>
|
||||
public CommandAttribute(string name)
|
||||
{
|
||||
Name = name; // can be null
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||
/// </summary>
|
||||
public CommandAttribute()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@ namespace CliFx.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Option name.
|
||||
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||
/// All options in a command must have different names (comparison is not case-sensitive).
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
public string? Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Option short name.
|
||||
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||
/// All options in a command must have different short names (comparison is case-sensitive).
|
||||
/// </summary>
|
||||
public char? ShortName { get; }
|
||||
|
||||
@@ -26,15 +30,20 @@ namespace CliFx.Attributes
|
||||
/// <summary>
|
||||
/// Option description, which is used in help text.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Environment variable that will be used as fallback if no option value is specified.
|
||||
/// </summary>
|
||||
public string? EnvironmentVariableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||
/// </summary>
|
||||
public CommandOptionAttribute(string name, char? shortName)
|
||||
private CommandOptionAttribute(string? name, char? shortName)
|
||||
{
|
||||
Name = name; // can be null
|
||||
ShortName = shortName; // can be null
|
||||
Name = name;
|
||||
ShortName = shortName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,7 +66,7 @@ namespace CliFx.Attributes
|
||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||
/// </summary>
|
||||
public CommandOptionAttribute(char shortName)
|
||||
: this(null, shortName)
|
||||
: this(null, (char?) shortName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
37
CliFx/Attributes/CommandParameterAttribute.cs
Normal file
37
CliFx/Attributes/CommandParameterAttribute.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace CliFx.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Annotates a property that defines a command parameter.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class CommandParameterAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Order of this parameter compared to other parameters.
|
||||
/// All parameters in a command must have different order.
|
||||
/// Parameter whose type is a non-scalar (e.g. array), must be the last in order and only one such parameter is allowed.
|
||||
/// </summary>
|
||||
public int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameter name, which is only used in help text.
|
||||
/// If this isn't specified, property name is used instead.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameter description, which is used in help text.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandParameterAttribute"/>.
|
||||
/// </summary>
|
||||
public CommandParameterAttribute(int order)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
}
|
||||
}
|
||||
324
CliFx/CliApplication.Help.cs
Normal file
324
CliFx/CliApplication.Help.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CliFx.Domain;
|
||||
using CliFx.Internal;
|
||||
|
||||
namespace CliFx
|
||||
{
|
||||
public partial class CliApplication
|
||||
{
|
||||
private void RenderHelp(ApplicationSchema applicationSchema, CommandSchema command)
|
||||
{
|
||||
var column = 0;
|
||||
var row = 0;
|
||||
|
||||
var childCommands = applicationSchema.GetChildCommands(command.Name);
|
||||
|
||||
bool IsEmpty() => column == 0 && row == 0;
|
||||
|
||||
void Render(string text)
|
||||
{
|
||||
_console.Output.Write(text);
|
||||
|
||||
column += text.Length;
|
||||
}
|
||||
|
||||
void RenderNewLine()
|
||||
{
|
||||
_console.Output.WriteLine();
|
||||
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
|
||||
void RenderMargin(int lines = 1)
|
||||
{
|
||||
if (!IsEmpty())
|
||||
{
|
||||
for (var i = 0; i < lines; i++)
|
||||
RenderNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderIndent(int spaces = 2)
|
||||
{
|
||||
Render(' '.Repeat(spaces));
|
||||
}
|
||||
|
||||
void RenderColumnIndent(int spaces = 20, int margin = 2)
|
||||
{
|
||||
if (column + margin < spaces)
|
||||
{
|
||||
RenderIndent(spaces - column);
|
||||
}
|
||||
else
|
||||
{
|
||||
Render(" ");
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWithColor(string text, ConsoleColor foregroundColor)
|
||||
{
|
||||
_console.WithForegroundColor(foregroundColor, () => Render(text));
|
||||
}
|
||||
|
||||
void RenderHeader(string text)
|
||||
{
|
||||
RenderWithColor(text, ConsoleColor.Magenta);
|
||||
RenderNewLine();
|
||||
}
|
||||
|
||||
void RenderApplicationInfo()
|
||||
{
|
||||
if (!command.IsDefault)
|
||||
return;
|
||||
|
||||
// Title and version
|
||||
RenderWithColor(_metadata.Title, ConsoleColor.Yellow);
|
||||
Render(" ");
|
||||
RenderWithColor(_metadata.VersionText, ConsoleColor.Yellow);
|
||||
RenderNewLine();
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(_metadata.Description))
|
||||
{
|
||||
Render(_metadata.Description);
|
||||
RenderNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDescription()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command.Description))
|
||||
return;
|
||||
|
||||
RenderMargin();
|
||||
RenderHeader("Description");
|
||||
|
||||
RenderIndent();
|
||||
Render(command.Description);
|
||||
RenderNewLine();
|
||||
}
|
||||
|
||||
void RenderUsage()
|
||||
{
|
||||
RenderMargin();
|
||||
RenderHeader("Usage");
|
||||
|
||||
// Exe name
|
||||
RenderIndent();
|
||||
Render(_metadata.ExecutableName);
|
||||
|
||||
// Command name
|
||||
if (!string.IsNullOrWhiteSpace(command.Name))
|
||||
{
|
||||
Render(" ");
|
||||
RenderWithColor(command.Name, ConsoleColor.Cyan);
|
||||
}
|
||||
|
||||
// Child command placeholder
|
||||
if (childCommands.Any())
|
||||
{
|
||||
Render(" ");
|
||||
RenderWithColor("[command]", ConsoleColor.Cyan);
|
||||
}
|
||||
|
||||
// Parameters
|
||||
foreach (var parameter in command.Parameters)
|
||||
{
|
||||
Render(" ");
|
||||
Render(parameter.IsScalar
|
||||
? $"<{parameter.DisplayName}>"
|
||||
: $"<{parameter.DisplayName}...>");
|
||||
}
|
||||
|
||||
// Required options
|
||||
var requiredOptionSchemas = command.Options
|
||||
.Where(o => o.IsRequired)
|
||||
.ToArray();
|
||||
|
||||
foreach (var option in requiredOptionSchemas)
|
||||
{
|
||||
Render(" ");
|
||||
if (!string.IsNullOrWhiteSpace(option.Name))
|
||||
{
|
||||
RenderWithColor($"--{option.Name}", ConsoleColor.White);
|
||||
Render(" ");
|
||||
Render(option.IsScalar
|
||||
? "<value>"
|
||||
: "<values...>");
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderWithColor($"-{option.ShortName}", ConsoleColor.White);
|
||||
Render(" ");
|
||||
Render(option.IsScalar
|
||||
? "<value>"
|
||||
: "<values...>");
|
||||
}
|
||||
}
|
||||
|
||||
// Options placeholder
|
||||
if (command.Options.Count != requiredOptionSchemas.Length)
|
||||
{
|
||||
Render(" ");
|
||||
RenderWithColor("[options]", ConsoleColor.White);
|
||||
}
|
||||
|
||||
RenderNewLine();
|
||||
}
|
||||
|
||||
void RenderParameters()
|
||||
{
|
||||
if (!command.Parameters.Any())
|
||||
return;
|
||||
|
||||
RenderMargin();
|
||||
RenderHeader("Parameters");
|
||||
|
||||
var parameters = command.Parameters
|
||||
.OrderBy(p => p.Order)
|
||||
.ToArray();
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
RenderWithColor("* ", ConsoleColor.Red);
|
||||
RenderWithColor($"{parameter.DisplayName}", ConsoleColor.White);
|
||||
|
||||
RenderColumnIndent();
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(parameter.Description))
|
||||
{
|
||||
Render(parameter.Description);
|
||||
}
|
||||
|
||||
RenderNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderOptions()
|
||||
{
|
||||
RenderMargin();
|
||||
RenderHeader("Options");
|
||||
|
||||
var options = command.Options
|
||||
.OrderByDescending(o => o.IsRequired)
|
||||
.ToList();
|
||||
|
||||
// Add built-in options
|
||||
options.Add(CommandOptionSchema.HelpOption);
|
||||
if (command.IsDefault)
|
||||
options.Add(CommandOptionSchema.VersionOption);
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
if (option.IsRequired)
|
||||
{
|
||||
RenderWithColor("* ", ConsoleColor.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderIndent();
|
||||
}
|
||||
|
||||
// Short name
|
||||
if (option.ShortName != null)
|
||||
{
|
||||
RenderWithColor($"-{option.ShortName}", ConsoleColor.White);
|
||||
}
|
||||
|
||||
// Delimiter
|
||||
if (!string.IsNullOrWhiteSpace(option.Name) && option.ShortName != null)
|
||||
{
|
||||
Render("|");
|
||||
}
|
||||
|
||||
// Name
|
||||
if (!string.IsNullOrWhiteSpace(option.Name))
|
||||
{
|
||||
RenderWithColor($"--{option.Name}", ConsoleColor.White);
|
||||
}
|
||||
|
||||
RenderColumnIndent();
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(option.Description))
|
||||
{
|
||||
Render(option.Description);
|
||||
Render(" ");
|
||||
}
|
||||
|
||||
// Environment variable
|
||||
if (!string.IsNullOrWhiteSpace(option.EnvironmentVariableName))
|
||||
{
|
||||
Render($"(Environment variable: {option.EnvironmentVariableName}).");
|
||||
Render(" ");
|
||||
}
|
||||
|
||||
RenderNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderChildCommands()
|
||||
{
|
||||
if (!childCommands.Any())
|
||||
return;
|
||||
|
||||
RenderMargin();
|
||||
RenderHeader("Commands");
|
||||
|
||||
foreach (var childCommand in childCommands)
|
||||
{
|
||||
var relativeCommandName =
|
||||
string.IsNullOrWhiteSpace(childCommand.Name) || string.IsNullOrWhiteSpace(command.Name)
|
||||
? childCommand.Name
|
||||
: childCommand.Name.Substring(command.Name.Length + 1);
|
||||
|
||||
// Name
|
||||
RenderIndent();
|
||||
RenderWithColor(relativeCommandName, ConsoleColor.Cyan);
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(childCommand.Description))
|
||||
{
|
||||
RenderColumnIndent();
|
||||
Render(childCommand.Description);
|
||||
}
|
||||
|
||||
RenderNewLine();
|
||||
}
|
||||
|
||||
RenderMargin();
|
||||
|
||||
// Child command help tip
|
||||
Render("You can run `");
|
||||
Render(_metadata.ExecutableName);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(command.Name))
|
||||
{
|
||||
Render(" ");
|
||||
RenderWithColor(command.Name, ConsoleColor.Cyan);
|
||||
}
|
||||
|
||||
Render(" ");
|
||||
RenderWithColor("[command]", ConsoleColor.Cyan);
|
||||
|
||||
Render(" ");
|
||||
RenderWithColor("--help", ConsoleColor.White);
|
||||
|
||||
Render("` to show help on a specific command.");
|
||||
|
||||
RenderNewLine();
|
||||
}
|
||||
|
||||
_console.ResetColor();
|
||||
RenderApplicationInfo();
|
||||
RenderDescription();
|
||||
RenderUsage();
|
||||
RenderParameters();
|
||||
RenderOptions();
|
||||
RenderChildCommands();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +1,205 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Domain;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Internal;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICliApplication"/>.
|
||||
/// Command line application facade.
|
||||
/// </summary>
|
||||
public class CliApplication : ICliApplication
|
||||
public partial class CliApplication
|
||||
{
|
||||
private readonly ApplicationMetadata _metadata;
|
||||
private readonly ApplicationConfiguration _configuration;
|
||||
|
||||
private readonly IConsole _console;
|
||||
private readonly ICommandInputParser _commandInputParser;
|
||||
private readonly ICommandSchemaResolver _commandSchemaResolver;
|
||||
private readonly ICommandFactory _commandFactory;
|
||||
private readonly ICommandInitializer _commandInitializer;
|
||||
private readonly IHelpTextRenderer _helpTextRenderer;
|
||||
private readonly ITypeActivator _typeActivator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CliApplication"/>.
|
||||
/// </summary>
|
||||
public CliApplication(ApplicationMetadata metadata, ApplicationConfiguration configuration,
|
||||
IConsole console, ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
|
||||
ICommandFactory commandFactory, ICommandInitializer commandInitializer, IHelpTextRenderer helpTextRenderer)
|
||||
public CliApplication(
|
||||
ApplicationMetadata metadata, ApplicationConfiguration configuration,
|
||||
IConsole console, ITypeActivator typeActivator)
|
||||
{
|
||||
_metadata = metadata.GuardNotNull(nameof(metadata));
|
||||
_configuration = configuration.GuardNotNull(nameof(configuration));
|
||||
|
||||
_console = console.GuardNotNull(nameof(console));
|
||||
_commandInputParser = commandInputParser.GuardNotNull(nameof(commandInputParser));
|
||||
_commandSchemaResolver = commandSchemaResolver.GuardNotNull(nameof(commandSchemaResolver));
|
||||
_commandFactory = commandFactory.GuardNotNull(nameof(commandFactory));
|
||||
_commandInitializer = commandInitializer.GuardNotNull(nameof(commandInitializer));
|
||||
_helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer));
|
||||
_metadata = metadata;
|
||||
_configuration = configuration;
|
||||
_console = console;
|
||||
_typeActivator = typeActivator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
||||
private async ValueTask<int?> HandleDebugDirectiveAsync(CommandLineInput commandLineInput)
|
||||
{
|
||||
commandLineArguments.GuardNotNull(nameof(commandLineArguments));
|
||||
var isDebugMode = _configuration.IsDebugModeAllowed && commandLineInput.IsDebugDirectiveSpecified;
|
||||
if (!isDebugMode)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Get schemas for all available command types
|
||||
var availableCommandSchemas = _commandSchemaResolver.GetCommandSchemas(_configuration.CommandTypes);
|
||||
_console.WithForegroundColor(ConsoleColor.Green, () =>
|
||||
_console.Output.WriteLine($"Attach debugger to PID {Process.GetCurrentProcess().Id} to continue."));
|
||||
|
||||
// Parse command input from arguments
|
||||
var commandInput = _commandInputParser.ParseCommandInput(commandLineArguments);
|
||||
while (!Debugger.IsAttached)
|
||||
await Task.Delay(100);
|
||||
|
||||
// Find command schema matching the name specified in the input
|
||||
var targetCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
|
||||
|
||||
// Handle cases where requested command is not defined
|
||||
if (targetCommandSchema == null)
|
||||
{
|
||||
var isError = false;
|
||||
|
||||
// If specified a command - show error
|
||||
if (commandInput.IsCommandSpecified())
|
||||
{
|
||||
isError = true;
|
||||
|
||||
_console.WithForegroundColor(ConsoleColor.Red,
|
||||
() => _console.Error.WriteLine($"Specified command [{commandInput.CommandName}] is not defined."));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get parent command schema
|
||||
var parentCommandSchema = availableCommandSchemas.FindParent(commandInput.CommandName);
|
||||
|
||||
// Show help for parent command if it's defined
|
||||
if (parentCommandSchema != null)
|
||||
private int? HandlePreviewDirective(ApplicationSchema applicationSchema, CommandLineInput commandLineInput)
|
||||
{
|
||||
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, parentCommandSchema);
|
||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
||||
}
|
||||
// Otherwise show help for a stub default command
|
||||
else
|
||||
{
|
||||
var helpTextSource = new HelpTextSource(_metadata,
|
||||
availableCommandSchemas.Concat(CommandSchema.StubDefaultCommand).ToArray(),
|
||||
CommandSchema.StubDefaultCommand);
|
||||
var isPreviewMode = _configuration.IsPreviewModeAllowed && commandLineInput.IsPreviewDirectiveSpecified;
|
||||
if (!isPreviewMode)
|
||||
return null;
|
||||
|
||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
||||
var commandSchema = applicationSchema.TryFindCommand(commandLineInput, out var argumentOffset);
|
||||
|
||||
_console.Output.WriteLine("Parser preview:");
|
||||
|
||||
// Command name
|
||||
if (commandSchema != null && argumentOffset > 0)
|
||||
{
|
||||
_console.WithForegroundColor(ConsoleColor.Cyan, () =>
|
||||
_console.Output.Write(commandSchema.Name));
|
||||
|
||||
_console.Output.Write(' ');
|
||||
}
|
||||
|
||||
return isError ? -1 : 0;
|
||||
// Parameters
|
||||
foreach (var parameter in commandLineInput.Arguments.Skip(argumentOffset))
|
||||
{
|
||||
_console.Output.Write('<');
|
||||
|
||||
_console.WithForegroundColor(ConsoleColor.White, () =>
|
||||
_console.Output.Write(parameter));
|
||||
|
||||
_console.Output.Write('>');
|
||||
_console.Output.Write(' ');
|
||||
}
|
||||
|
||||
// Show version if it was requested without specifying a command
|
||||
if (commandInput.IsVersionRequested() && !commandInput.IsCommandSpecified())
|
||||
// Options
|
||||
foreach (var option in commandLineInput.Options)
|
||||
{
|
||||
_console.Output.Write('[');
|
||||
|
||||
_console.WithForegroundColor(ConsoleColor.White, () =>
|
||||
_console.Output.Write(option));
|
||||
|
||||
_console.Output.Write(']');
|
||||
_console.Output.Write(' ');
|
||||
}
|
||||
|
||||
_console.Output.WriteLine();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int? HandleVersionOption(CommandLineInput commandLineInput)
|
||||
{
|
||||
// Version option is available only on the default command (i.e. when arguments are not specified)
|
||||
var shouldRenderVersion = !commandLineInput.Arguments.Any() && commandLineInput.IsVersionOptionSpecified;
|
||||
if (!shouldRenderVersion)
|
||||
return null;
|
||||
|
||||
_console.Output.WriteLine(_metadata.VersionText);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Show help if it was requested
|
||||
if (commandInput.IsHelpRequested())
|
||||
private int? HandleHelpOption(ApplicationSchema applicationSchema, CommandLineInput commandLineInput)
|
||||
{
|
||||
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, targetCommandSchema);
|
||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
||||
// Help is rendered either when it's requested or when the user provides no arguments and there is no default command
|
||||
var shouldRenderHelp =
|
||||
commandLineInput.IsHelpOptionSpecified ||
|
||||
!applicationSchema.Commands.Any(c => c.IsDefault) && !commandLineInput.Arguments.Any() && !commandLineInput.Options.Any();
|
||||
|
||||
if (!shouldRenderHelp)
|
||||
return null;
|
||||
|
||||
// Get the command schema that matches the input or use a dummy default command as a fallback
|
||||
var commandSchema =
|
||||
applicationSchema.TryFindCommand(commandLineInput) ??
|
||||
CommandSchema.StubDefaultCommand;
|
||||
|
||||
RenderHelp(applicationSchema, commandSchema);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create an instance of the command
|
||||
var command = _commandFactory.CreateCommand(targetCommandSchema);
|
||||
|
||||
// Populate command with options according to its schema
|
||||
_commandInitializer.InitializeCommand(command, targetCommandSchema, commandInput);
|
||||
|
||||
// Execute command
|
||||
await command.ExecuteAsync(_console);
|
||||
private async ValueTask<int> HandleCommandExecutionAsync(
|
||||
ApplicationSchema applicationSchema,
|
||||
CommandLineInput commandLineInput,
|
||||
IReadOnlyDictionary<string, string> environmentVariables)
|
||||
{
|
||||
await applicationSchema
|
||||
.InitializeEntryPoint(commandLineInput, environmentVariables, _typeActivator)
|
||||
.ExecuteAsync(_console);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the application with specified command line arguments and environment variables, and returns the exit code.
|
||||
/// </summary>
|
||||
public async ValueTask<int> RunAsync(
|
||||
IReadOnlyList<string> commandLineArguments,
|
||||
IReadOnlyDictionary<string, string> environmentVariables)
|
||||
{
|
||||
try
|
||||
{
|
||||
var applicationSchema = ApplicationSchema.Resolve(_configuration.CommandTypes);
|
||||
var commandLineInput = CommandLineInput.Parse(commandLineArguments);
|
||||
|
||||
return
|
||||
await HandleDebugDirectiveAsync(commandLineInput) ??
|
||||
HandlePreviewDirective(applicationSchema, commandLineInput) ??
|
||||
HandleVersionOption(commandLineInput) ??
|
||||
HandleHelpOption(applicationSchema, commandLineInput) ??
|
||||
await HandleCommandExecutionAsync(applicationSchema, commandLineInput, environmentVariables);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// We want to catch exceptions in order to print errors and return correct exit codes.
|
||||
// Also, by doing this we get 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.
|
||||
|
||||
// In case we catch a CliFx-specific exception, we want to just show the error message, not the stack trace.
|
||||
// Stack trace isn't very useful to the user if the exception is not really coming from their code.
|
||||
// Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException
|
||||
var errorMessage = !string.IsNullOrWhiteSpace(ex.Message) && (ex is CliFxException || ex is CommandException)
|
||||
? ex.Message
|
||||
: ex.ToString();
|
||||
|
||||
// CommandException is the same, but it also lets users specify exit code so we want to return that instead of default.
|
||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(errorMessage));
|
||||
|
||||
var message = ex is CliFxException && !ex.Message.IsNullOrWhiteSpace() ? ex.Message : ex.ToString();
|
||||
var exitCode = ex is CommandException commandEx ? commandEx.ExitCode : ex.HResult;
|
||||
return ex is CommandException commandException
|
||||
? commandException.ExitCode
|
||||
: ex.HResult;
|
||||
}
|
||||
}
|
||||
|
||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(message));
|
||||
/// <summary>
|
||||
/// Runs the application with specified command line arguments and returns the exit code.
|
||||
/// Environment variables are retrieved automatically.
|
||||
/// </summary>
|
||||
public async ValueTask<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
||||
{
|
||||
var environmentVariables = Environment.GetEnvironmentVariables()
|
||||
.Cast<DictionaryEntry>()
|
||||
.ToDictionary(e => (string) e.Key, e => (string) e.Value, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
return await RunAsync(commandLineArguments, environmentVariables);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the application and returns the exit code.
|
||||
/// Command line arguments and environment variables are retrieved automatically.
|
||||
/// </summary>
|
||||
public async ValueTask<int> RunAsync()
|
||||
{
|
||||
var commandLineArguments = Environment.GetCommandLineArgs()
|
||||
.Skip(1)
|
||||
.ToArray();
|
||||
|
||||
return await RunAsync(commandLineArguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,155 +3,171 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CliFx.Internal;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using CliFx.Domain;
|
||||
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICliApplicationBuilder"/>.
|
||||
/// Builds an instance of <see cref="CliApplication"/>.
|
||||
/// </summary>
|
||||
public partial class CliApplicationBuilder : ICliApplicationBuilder
|
||||
public partial class CliApplicationBuilder
|
||||
{
|
||||
private readonly HashSet<Type> _commandTypes = new HashSet<Type>();
|
||||
|
||||
private string _title;
|
||||
private string _executableName;
|
||||
private string _versionText;
|
||||
private string _description;
|
||||
private IConsole _console;
|
||||
private ICommandFactory _commandFactory;
|
||||
private bool _isDebugModeAllowed = true;
|
||||
private bool _isPreviewModeAllowed = true;
|
||||
private string? _title;
|
||||
private string? _executableName;
|
||||
private string? _versionText;
|
||||
private string? _description;
|
||||
private IConsole? _console;
|
||||
private ITypeActivator? _typeActivator;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder AddCommand(Type commandType)
|
||||
/// <summary>
|
||||
/// Adds a command of specified type to the application.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AddCommand(Type commandType)
|
||||
{
|
||||
commandType.GuardNotNull(nameof(commandType));
|
||||
|
||||
_commandTypes.Add(commandType);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder AddCommandsFrom(Assembly commandAssembly)
|
||||
/// <summary>
|
||||
/// Adds multiple commands to the application.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AddCommands(IEnumerable<Type> commandTypes)
|
||||
{
|
||||
commandAssembly.GuardNotNull(nameof(commandAssembly));
|
||||
|
||||
var commandTypes = commandAssembly.ExportedTypes.Where(t => t.Implements(typeof(ICommand)));
|
||||
|
||||
foreach (var commandType in commandTypes)
|
||||
AddCommand(commandType);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseTitle(string title)
|
||||
/// <summary>
|
||||
/// Adds commands from the specified assembly to the application.
|
||||
/// Only the public types are added.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AddCommandsFrom(Assembly commandAssembly)
|
||||
{
|
||||
_title = title.GuardNotNull(nameof(title));
|
||||
foreach (var commandType in commandAssembly.ExportedTypes.Where(CommandSchema.IsCommandType))
|
||||
AddCommand(commandType);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseExecutableName(string executableName)
|
||||
/// <summary>
|
||||
/// Adds commands from the specified assemblies to the application.
|
||||
/// Only the public types are added.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AddCommandsFrom(IEnumerable<Assembly> commandAssemblies)
|
||||
{
|
||||
_executableName = executableName.GuardNotNull(nameof(executableName));
|
||||
foreach (var commandAssembly in commandAssemblies)
|
||||
AddCommandsFrom(commandAssembly);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseVersionText(string versionText)
|
||||
/// <summary>
|
||||
/// Adds commands from the calling assembly to the application.
|
||||
/// Only the public types are added.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AddCommandsFromThisAssembly() => AddCommandsFrom(Assembly.GetCallingAssembly());
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether debug mode (enabled with [debug] directive) is allowed in the application.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AllowDebugMode(bool isAllowed = true)
|
||||
{
|
||||
_versionText = versionText.GuardNotNull(nameof(versionText));
|
||||
_isDebugModeAllowed = isAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseDescription(string description)
|
||||
/// <summary>
|
||||
/// Specifies whether preview mode (enabled with [preview] directive) is allowed in the application.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder AllowPreviewMode(bool isAllowed = true)
|
||||
{
|
||||
_description = description; // can be null
|
||||
_isPreviewModeAllowed = isAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseConsole(IConsole console)
|
||||
/// <summary>
|
||||
/// Sets application title, which appears in the help text.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseTitle(string title)
|
||||
{
|
||||
_console = console.GuardNotNull(nameof(console));
|
||||
_title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseCommandFactory(ICommandFactory factory)
|
||||
/// <summary>
|
||||
/// Sets application executable name, which appears in the help text.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseExecutableName(string executableName)
|
||||
{
|
||||
_commandFactory = factory.GuardNotNull(nameof(factory));
|
||||
_executableName = executableName;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void SetFallbackValues()
|
||||
/// <summary>
|
||||
/// Sets application version text, which appears in the help text and when the user requests version information.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseVersionText(string versionText)
|
||||
{
|
||||
if (_title.IsNullOrWhiteSpace())
|
||||
{
|
||||
// Entry assembly is null in tests
|
||||
UseTitle(EntryAssembly?.GetName().Name ?? "App");
|
||||
_versionText = versionText;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_executableName.IsNullOrWhiteSpace())
|
||||
/// <summary>
|
||||
/// Sets application description, which appears in the help text.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseDescription(string? description)
|
||||
{
|
||||
// Entry assembly is null in tests
|
||||
var entryAssemblyLocation = EntryAssembly?.Location;
|
||||
|
||||
// Set different executable name depending on location
|
||||
if (!entryAssemblyLocation.IsNullOrWhiteSpace())
|
||||
{
|
||||
// Prepend 'dotnet' to assembly file name if the entry assembly is a dll file (extension needs to be kept)
|
||||
if (string.Equals(Path.GetExtension(entryAssemblyLocation), ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
UseExecutableName("dotnet " + Path.GetFileName(entryAssemblyLocation));
|
||||
}
|
||||
// Otherwise just use assembly file name without extension
|
||||
else
|
||||
{
|
||||
UseExecutableName(Path.GetFileNameWithoutExtension(entryAssemblyLocation));
|
||||
}
|
||||
}
|
||||
// If location is null then just use a stub
|
||||
else
|
||||
{
|
||||
UseExecutableName("app");
|
||||
}
|
||||
_description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_versionText.IsNullOrWhiteSpace())
|
||||
/// <summary>
|
||||
/// Configures the application to use the specified implementation of <see cref="IConsole"/>.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseConsole(IConsole console)
|
||||
{
|
||||
// Entry assembly is null in tests
|
||||
UseVersionText(EntryAssembly?.GetName().Version.ToString() ?? "1.0");
|
||||
_console = console;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_console == null)
|
||||
/// <summary>
|
||||
/// Configures the application to use the specified implementation of <see cref="ITypeActivator"/>.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseTypeActivator(ITypeActivator typeActivator)
|
||||
{
|
||||
UseConsole(new SystemConsole());
|
||||
_typeActivator = typeActivator;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (_commandFactory == null)
|
||||
{
|
||||
UseCommandFactory(new CommandFactory());
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Configures the application to use the specified function for activating types.
|
||||
/// </summary>
|
||||
public CliApplicationBuilder UseTypeActivator(Func<Type, object> typeActivator) =>
|
||||
UseTypeActivator(new DelegateTypeActivator(typeActivator));
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplication Build()
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="CliApplication"/> using configured parameters.
|
||||
/// Default values are used in place of parameters that were not specified.
|
||||
/// </summary>
|
||||
public CliApplication Build()
|
||||
{
|
||||
// Use defaults for required parameters that were not configured
|
||||
SetFallbackValues();
|
||||
_title ??= GetDefaultTitle() ?? "App";
|
||||
_executableName ??= GetDefaultExecutableName() ?? "app";
|
||||
_versionText ??= GetDefaultVersionText() ?? "v1.0";
|
||||
_console ??= new SystemConsole();
|
||||
_typeActivator ??= new DefaultTypeActivator();
|
||||
|
||||
// Project parameters to expected types
|
||||
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
|
||||
var configuration = new ApplicationConfiguration(_commandTypes.ToArray());
|
||||
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
|
||||
|
||||
return new CliApplication(metadata, configuration,
|
||||
_console, new CommandInputParser(), new CommandSchemaResolver(),
|
||||
_commandFactory, new CommandInitializer(), new HelpTextRenderer());
|
||||
return new CliApplication(metadata, configuration, _console, _typeActivator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +175,28 @@ namespace CliFx
|
||||
{
|
||||
private static readonly Lazy<Assembly> LazyEntryAssembly = new Lazy<Assembly>(Assembly.GetEntryAssembly);
|
||||
|
||||
// Entry assembly is null in tests
|
||||
private static Assembly EntryAssembly => LazyEntryAssembly.Value;
|
||||
|
||||
private static string? GetDefaultTitle() => EntryAssembly?.GetName().Name;
|
||||
|
||||
private static string? GetDefaultExecutableName()
|
||||
{
|
||||
var entryAssemblyLocation = EntryAssembly?.Location;
|
||||
|
||||
// If it's a .dll assembly, prepend 'dotnet' and keep the file extension
|
||||
if (string.Equals(Path.GetExtension(entryAssemblyLocation), ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "dotnet " + Path.GetFileName(entryAssemblyLocation);
|
||||
}
|
||||
|
||||
// Otherwise just use assembly file name without extension
|
||||
return Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
||||
}
|
||||
|
||||
private static string? GetDefaultVersionText() =>
|
||||
EntryAssembly != null
|
||||
? $"v{EntryAssembly.GetName().Version}"
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../CliFx.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.0.2</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<TargetFrameworks>netstandard2.1;netstandard2.0;net45</TargetFrameworks>
|
||||
<Authors>$(Company)</Authors>
|
||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
||||
<Description>Declarative framework for CLI applications</Description>
|
||||
<PackageTags>command line executable interface framework parser arguments net core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/Tyrrrz/CliFx</PackageProjectUrl>
|
||||
<PackageReleaseNotes>https://github.com/Tyrrrz/CliFx/blob/master/Changelog.md</PackageReleaseNotes>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/Tyrrrz/CliFx/master/favicon.png</PackageIconUrl>
|
||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/Tyrrrz/CliFx</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageIcon>favicon.png</PackageIcon>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<PublishRepositoryUrl>True</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>True</EmbedUntrackedSources>
|
||||
<IncludeSymbols>True</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../favicon.png" Pack="True" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Nullable" Version="1.2.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net45'">
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
29
CliFx/DefaultTypeActivator.cs
Normal file
29
CliFx/DefaultTypeActivator.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using CliFx.Exceptions;
|
||||
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// Type activator that uses the <see cref="Activator"/> class to instantiate objects.
|
||||
/// </summary>
|
||||
public class DefaultTypeActivator : ITypeActivator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Activator.CreateInstance(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new CliFxException(new StringBuilder()
|
||||
.Append($"Failed to create an instance of {type.FullName}.").Append(" ")
|
||||
.AppendLine("The type must have a public parameter-less constructor in order to be instantiated by the default activator.")
|
||||
.Append($"To supply a custom activator (for example when using dependency injection), call {nameof(CliApplicationBuilder)}.{nameof(CliApplicationBuilder.UseTypeActivator)}(...).")
|
||||
.ToString(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
CliFx/DelegateTypeActivator.cs
Normal file
27
CliFx/DelegateTypeActivator.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using CliFx.Exceptions;
|
||||
|
||||
namespace CliFx
|
||||
{
|
||||
/// <summary>
|
||||
/// Type activator that uses the specified delegate to instantiate objects.
|
||||
/// </summary>
|
||||
public class DelegateTypeActivator : ITypeActivator
|
||||
{
|
||||
private readonly Func<Type, object> _func;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="DelegateTypeActivator"/>.
|
||||
/// </summary>
|
||||
public DelegateTypeActivator(Func<Type, object> func) => _func = func;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object CreateInstance(Type type) =>
|
||||
_func(type) ?? throw new CliFxException(new StringBuilder()
|
||||
.Append($"Failed to create an instance of type {type.FullName}, received <null> instead.").Append(" ")
|
||||
.Append("Make sure that the provided type activator was configured correctly.").Append(" ")
|
||||
.Append("If you are using a dependency container, make sure that this type is registered.")
|
||||
.ToString());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user