mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
671532efce | ||
|
|
5b124345b0 | ||
|
|
b812bd1423 | ||
|
|
c854f5fb8d | ||
|
|
f38bd32510 | ||
|
|
765fa5503e | ||
|
|
57f168723b | ||
|
|
79e1a2e3d7 | ||
|
|
f4f6d04857 | ||
|
|
015ede0d15 | ||
|
|
4fd7f7c3ca | ||
|
|
896dd49eb4 | ||
|
|
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 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,4 +1,3 @@
|
|||||||
github: Tyrrrz
|
github: Tyrrrz
|
||||||
patreon: Tyrrrz
|
patreon: Tyrrrz
|
||||||
open_collective: Tyrrrz
|
custom: ['buymeacoffee.com/Tyrrrz', 'tyrrrz.me/donate']
|
||||||
custom: ['buymeacoffee.com/Tyrrrz']
|
|
||||||
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@v2
|
||||||
|
|
||||||
|
- name: Install .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1.4.0
|
||||||
|
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}}
|
||||||
29
.github/workflows/CI.yml
vendored
Normal file
29
.github/workflows/CI.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1.4.0
|
||||||
|
with:
|
||||||
|
dotnet-version: 3.1.100
|
||||||
|
|
||||||
|
- name: Build & test
|
||||||
|
run: dotnet test --configuration Release
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
uses: codecov/codecov-action@v1.0.5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
file: CliFx.Tests/bin/Release/Coverage.xml
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
### v1.1 (16-Mar-2020)
|
||||||
|
|
||||||
|
- Changed `IConsole` interface (and as a result, `SystemConsole` and `VirtualConsole`) to support writing binary data. Instead of `TextReader`/`TextWriter` instances, the streams are now exposed as `StreamReader`/`StreamWriter` which provide the `BaseStream` property that allows raw access. Existing usages inside commands should remain the same because `StreamReader`/`StreamWriter` are compatible with their base classes `TextReader`/`TextWriter`, but if you were using `VirtualConsole` in tests, you may have to update it to the new API. Refer to the readme for more info.
|
||||||
|
- Changed argument binding behavior so that an error is produced if the user provides an argument that doesn't match with any parameter or option. This is done in order to improve user experience, as otherwise the user may make a typo without knowing that their input wasn't taken into account.
|
||||||
|
- Changed argument binding behavior so that options can be set to multiple argument values while specifying them with mixed naming. For example, `--option value1 -o value2 --option value3` would result in the option being set to corresponding three values, assuming `--option` and `-o` match with the same option.
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using CliFx.Benchmarks.Commands;
|
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
|
||||||
{
|
|
||||||
[CoreJob]
|
|
||||||
[RankColumn]
|
|
||||||
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);
|
|
||||||
|
|
||||||
[Benchmark(Description = "System.CommandLine")]
|
|
||||||
public Task<int> ExecuteWithSystemCommandLine() => new SystemCommandLineCommand().ExecuteAsync(Arguments);
|
|
||||||
|
|
||||||
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
|
|
||||||
public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments);
|
|
||||||
|
|
||||||
[Benchmark(Description = "CommandLineParser")]
|
|
||||||
public void ExecuteWithCommandLineParser()
|
|
||||||
{
|
|
||||||
var parsed = new CommandLine.Parser().ParseArguments(Arguments, typeof(CommandLineParserCommand));
|
|
||||||
CommandLine.ParserResultExtensions.WithParsed<CommandLineParserCommand>(parsed, c => c.Execute());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Description = "PowerArgs")]
|
|
||||||
public void ExecuteWithPowerArgs() => PowerArgs.Args.InvokeMain<PowerArgsCommand>(Arguments);
|
|
||||||
|
|
||||||
[Benchmark(Description = "Clipr")]
|
|
||||||
public void ExecuteWithClipr() => clipr.CliParser.Parse<CliprCommand>(Arguments).Execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
CliFx.Benchmarks/Benchmarks.cs
Normal file
52
CliFx.Benchmarks/Benchmarks.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Configs;
|
||||||
|
using BenchmarkDotNet.Order;
|
||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
using CliFx.Benchmarks.Commands;
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace CliFx.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob]
|
||||||
|
[RankColumn]
|
||||||
|
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
|
||||||
|
public class Benchmarks
|
||||||
|
{
|
||||||
|
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"};
|
||||||
|
|
||||||
|
[Benchmark(Description = "CliFx", Baseline = true)]
|
||||||
|
public async ValueTask<int> ExecuteWithCliFx() =>
|
||||||
|
await new CliApplicationBuilder().AddCommand(typeof(CliFxCommand)).Build().RunAsync(Arguments, new Dictionary<string, string>());
|
||||||
|
|
||||||
|
[Benchmark(Description = "System.CommandLine")]
|
||||||
|
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);
|
||||||
|
|
||||||
|
[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);
|
||||||
|
|
||||||
|
[Benchmark(Description = "Clipr")]
|
||||||
|
public void ExecuteWithClipr() =>
|
||||||
|
clipr.CliParser.Parse<CliprCommand>(Arguments).Execute();
|
||||||
|
|
||||||
|
[Benchmark(Description = "Cocona")]
|
||||||
|
public void ExecuteWithCocona() =>
|
||||||
|
Cocona.CoconaApp.Run<CoconaCommand>(Arguments);
|
||||||
|
|
||||||
|
public static void Main() =>
|
||||||
|
BenchmarkRunner.Run<Benchmarks>(DefaultConfig.Instance.With(ConfigOptions.DisableOptimizationsValidator));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../CliFx.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
|
||||||
<PackageReference Include="clipr" Version="1.6.1" />
|
<PackageReference Include="clipr" Version="1.6.1" />
|
||||||
<PackageReference Include="CommandLineParser" Version="2.6.0" />
|
<PackageReference Include="Cocona" Version="1.3.0" />
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
|
<PackageReference Include="CommandLineParser" Version="2.7.82" />
|
||||||
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.6.0" />
|
||||||
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
||||||
<PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" />
|
<PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19317.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Benchmarks.Commands
|
namespace CliFx.Benchmarks.Commands
|
||||||
{
|
{
|
||||||
@@ -9,7 +7,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class CliFxCommand : ICommand
|
public class CliFxCommand : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("str", 's')]
|
[CommandOption("str", 's')]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[CommandOption("int", 'i')]
|
[CommandOption("int", 'i')]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
@@ -17,6 +15,6 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
[CommandOption("bool", 'b')]
|
[CommandOption("bool", 'b')]
|
||||||
public bool BoolOption { get; set; }
|
public bool BoolOption { get; set; }
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class CliprCommand
|
public class CliprCommand
|
||||||
{
|
{
|
||||||
[NamedArgument('s', "str")]
|
[NamedArgument('s', "str")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[NamedArgument('i', "int")]
|
[NamedArgument('i', "int")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
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
|
public class CommandLineParserCommand
|
||||||
{
|
{
|
||||||
[Option('s', "str")]
|
[Option('s', "str")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[Option('i', "int")]
|
[Option('i', "int")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class McMasterCommand
|
public class McMasterCommand
|
||||||
{
|
{
|
||||||
[Option("--str|-s")]
|
[Option("--str|-s")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[Option("--int|-i")]
|
[Option("--int|-i")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
public class PowerArgsCommand
|
public class PowerArgsCommand
|
||||||
{
|
{
|
||||||
[ArgShortcut("--str"), ArgShortcut("-s")]
|
[ArgShortcut("--str"), ArgShortcut("-s")]
|
||||||
public string StrOption { get; set; }
|
public string? StrOption { get; set; }
|
||||||
|
|
||||||
[ArgShortcut("--int"), ArgShortcut("-i")]
|
[ArgShortcut("--int"), ArgShortcut("-i")]
|
||||||
public int IntOption { get; set; }
|
public int IntOption { get; set; }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace CliFx.Benchmarks.Commands
|
|||||||
{
|
{
|
||||||
new Option(new[] {"--str", "-s"})
|
new Option(new[] {"--str", "-s"})
|
||||||
{
|
{
|
||||||
Argument = new Argument<string>()
|
Argument = new Argument<string?>()
|
||||||
},
|
},
|
||||||
new Option(new[] {"--int", "-i"})
|
new Option(new[] {"--int", "-i"})
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
using BenchmarkDotNet.Configs;
|
|
||||||
using BenchmarkDotNet.Running;
|
|
||||||
|
|
||||||
namespace CliFx.Benchmarks
|
|
||||||
{
|
|
||||||
public static class Program
|
|
||||||
{
|
|
||||||
public static void Main() =>
|
|
||||||
BenchmarkRunner.Run(typeof(Program).Assembly, DefaultConfig.Instance
|
|
||||||
.With(ConfigOptions.DisableOptimizationsValidator));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../CliFx.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Demo.Internal;
|
using CliFx.Demo.Internal;
|
||||||
using CliFx.Demo.Models;
|
using CliFx.Demo.Models;
|
||||||
using CliFx.Demo.Services;
|
using CliFx.Demo.Services;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands
|
||||||
{
|
{
|
||||||
@@ -15,31 +13,25 @@ namespace CliFx.Demo.Commands
|
|||||||
{
|
{
|
||||||
private readonly LibraryService _libraryService;
|
private readonly LibraryService _libraryService;
|
||||||
|
|
||||||
[CommandOption("title", 't', IsRequired = true, Description = "Book title.")]
|
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; } = "";
|
||||||
|
|
||||||
[CommandOption("author", 'a', IsRequired = true, Description = "Book author.")]
|
[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.")]
|
[CommandOption("published", 'p', Description = "Book publish date.")]
|
||||||
public DateTimeOffset Published { get; set; }
|
public DateTimeOffset Published { get; set; } = CreateRandomDate();
|
||||||
|
|
||||||
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
|
||||||
public Isbn Isbn { get; set; }
|
public Isbn Isbn { get; set; } = CreateRandomIsbn();
|
||||||
|
|
||||||
public BookAddCommand(LibraryService libraryService)
|
public BookAddCommand(LibraryService libraryService)
|
||||||
{
|
{
|
||||||
_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)
|
if (_libraryService.GetBook(Title) != null)
|
||||||
throw new CommandException("Book already exists.", 1);
|
throw new CommandException("Book already exists.", 1);
|
||||||
|
|
||||||
@@ -49,7 +41,7 @@ namespace CliFx.Demo.Commands
|
|||||||
console.Output.WriteLine("Book added.");
|
console.Output.WriteLine("Book added.");
|
||||||
console.RenderBook(book);
|
console.RenderBook(book);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +58,7 @@ namespace CliFx.Demo.Commands
|
|||||||
Random.Next(1, 59),
|
Random.Next(1, 59),
|
||||||
TimeSpan.Zero);
|
TimeSpan.Zero);
|
||||||
|
|
||||||
public static Isbn CreateRandomIsbn() => new Isbn(
|
private static Isbn CreateRandomIsbn() => new Isbn(
|
||||||
Random.Next(0, 999),
|
Random.Next(0, 999),
|
||||||
Random.Next(0, 99),
|
Random.Next(0, 99),
|
||||||
Random.Next(0, 99999),
|
Random.Next(0, 99999),
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Demo.Internal;
|
using CliFx.Demo.Internal;
|
||||||
using CliFx.Demo.Services;
|
using CliFx.Demo.Services;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands
|
||||||
{
|
{
|
||||||
@@ -13,15 +11,15 @@ namespace CliFx.Demo.Commands
|
|||||||
{
|
{
|
||||||
private readonly LibraryService _libraryService;
|
private readonly LibraryService _libraryService;
|
||||||
|
|
||||||
[CommandOption("title", 't', IsRequired = true, Description = "Book title.")]
|
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; } = "";
|
||||||
|
|
||||||
public BookCommand(LibraryService libraryService)
|
public BookCommand(LibraryService libraryService)
|
||||||
{
|
{
|
||||||
_libraryService = libraryService;
|
_libraryService = libraryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
var book = _libraryService.GetBook(Title);
|
var book = _libraryService.GetBook(Title);
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ namespace CliFx.Demo.Commands
|
|||||||
|
|
||||||
console.RenderBook(book);
|
console.RenderBook(book);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Demo.Internal;
|
using CliFx.Demo.Internal;
|
||||||
using CliFx.Demo.Services;
|
using CliFx.Demo.Services;
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands
|
||||||
{
|
{
|
||||||
@@ -17,7 +15,7 @@ namespace CliFx.Demo.Commands
|
|||||||
_libraryService = libraryService;
|
_libraryService = libraryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
var library = _libraryService.GetLibrary();
|
var library = _libraryService.GetLibrary();
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ namespace CliFx.Demo.Commands
|
|||||||
console.RenderBook(book);
|
console.RenderBook(book);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
using System.Threading;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Demo.Services;
|
using CliFx.Demo.Services;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Demo.Commands
|
namespace CliFx.Demo.Commands
|
||||||
{
|
{
|
||||||
@@ -12,15 +10,15 @@ namespace CliFx.Demo.Commands
|
|||||||
{
|
{
|
||||||
private readonly LibraryService _libraryService;
|
private readonly LibraryService _libraryService;
|
||||||
|
|
||||||
[CommandOption("title", 't', IsRequired = true, Description = "Book title.")]
|
[CommandParameter(0, Name = "title", Description = "Book title.")]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; } = "";
|
||||||
|
|
||||||
public BookRemoveCommand(LibraryService libraryService)
|
public BookRemoveCommand(LibraryService libraryService)
|
||||||
{
|
{
|
||||||
_libraryService = libraryService;
|
_libraryService = libraryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
var book = _libraryService.GetBook(Title);
|
var book = _libraryService.GetBook(Title);
|
||||||
|
|
||||||
@@ -31,7 +29,7 @@ namespace CliFx.Demo.Commands
|
|||||||
|
|
||||||
console.Output.WriteLine($"Book {Title} removed.");
|
console.Output.WriteLine($"Book {Title} removed.");
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.Demo.Models;
|
using CliFx.Demo.Models;
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Demo.Internal
|
namespace CliFx.Demo.Internal
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace CliFx.Demo.Models
|
namespace CliFx.Demo.Models
|
||||||
{
|
{
|
||||||
@@ -24,21 +23,23 @@ namespace CliFx.Demo.Models
|
|||||||
CheckDigit = checkDigit;
|
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 partial class Isbn
|
||||||
{
|
{
|
||||||
public static Isbn Parse(string value)
|
public static Isbn Parse(string value, IFormatProvider formatProvider)
|
||||||
{
|
{
|
||||||
var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries);
|
var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
return new Isbn(
|
return new Isbn(
|
||||||
int.Parse(components[0], CultureInfo.InvariantCulture),
|
int.Parse(components[0], formatProvider),
|
||||||
int.Parse(components[1], CultureInfo.InvariantCulture),
|
int.Parse(components[1], formatProvider),
|
||||||
int.Parse(components[2], CultureInfo.InvariantCulture),
|
int.Parse(components[2], formatProvider),
|
||||||
int.Parse(components[3], CultureInfo.InvariantCulture),
|
int.Parse(components[3], formatProvider),
|
||||||
int.Parse(components[4], CultureInfo.InvariantCulture));
|
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.Commands;
|
||||||
using CliFx.Demo.Services;
|
using CliFx.Demo.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -7,7 +8,7 @@ namespace CliFx.Demo
|
|||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static Task<int> Main(string[] args)
|
private static IServiceProvider GetServiceProvider()
|
||||||
{
|
{
|
||||||
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
// We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
@@ -21,13 +22,14 @@ namespace CliFx.Demo
|
|||||||
services.AddTransient<BookRemoveCommand>();
|
services.AddTransient<BookRemoveCommand>();
|
||||||
services.AddTransient<BookListCommand>();
|
services.AddTransient<BookListCommand>();
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
return new CliApplicationBuilder()
|
public static async Task<int> Main() =>
|
||||||
|
await new CliApplicationBuilder()
|
||||||
.AddCommandsFromThisAssembly()
|
.AddCommandsFromThisAssembly()
|
||||||
.UseCommandFactory(schema => (ICommand) serviceProvider.GetRequiredService(schema.Type))
|
.UseTypeActivator(GetServiceProvider().GetService)
|
||||||
.Build()
|
.Build()
|
||||||
.RunAsync(args);
|
.RunAsync();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
Sample command line interface for managing a library of books.
|
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`.
|
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);
|
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)
|
public void AddBook(Book book)
|
||||||
{
|
{
|
||||||
|
|||||||
13
CliFx.Tests.Dummy/CliFx.Tests.Dummy.csproj
Normal file
13
CliFx.Tests.Dummy/CliFx.Tests.Dummy.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../CliFx.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CliFx\CliFx.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
23
CliFx.Tests.Dummy/Commands/ConsoleTestCommand.cs
Normal file
23
CliFx.Tests.Dummy/Commands/ConsoleTestCommand.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.Dummy.Commands
|
||||||
|
{
|
||||||
|
[Command("console-test")]
|
||||||
|
public class ConsoleTestCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
var input = console.Input.ReadToEnd();
|
||||||
|
|
||||||
|
console.WithColors(ConsoleColor.Black, ConsoleColor.White, () =>
|
||||||
|
{
|
||||||
|
console.Output.WriteLine(input);
|
||||||
|
console.Error.WriteLine(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
CliFx.Tests.Dummy/Program.cs
Normal file
21
CliFx.Tests.Dummy/Program.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CliFx.Tests.Dummy
|
||||||
|
{
|
||||||
|
public static partial class Program
|
||||||
|
{
|
||||||
|
public static Assembly Assembly { get; } = typeof(Program).Assembly;
|
||||||
|
|
||||||
|
public static string Location { get; } = Assembly.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static partial class Program
|
||||||
|
{
|
||||||
|
public static async Task Main() =>
|
||||||
|
await new CliApplicationBuilder()
|
||||||
|
.AddCommandsFromThisAssembly()
|
||||||
|
.Build()
|
||||||
|
.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
137
CliFx.Tests/ApplicationSpecs.Commands.cs
Normal file
137
CliFx.Tests/ApplicationSpecs.Commands.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class ApplicationSpecs
|
||||||
|
{
|
||||||
|
[Command]
|
||||||
|
private class NonImplementedCommand
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonAnnotatedCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("dup")]
|
||||||
|
private class DuplicateNameCommandA : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("dup")]
|
||||||
|
private class DuplicateNameCommandB : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class DuplicateParameterOrderCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(13)]
|
||||||
|
public string? ParameterA { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(13)]
|
||||||
|
public string? ParameterB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class NonLastNonScalarParameterCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0)]
|
||||||
|
public IReadOnlyList<string>? ParameterA { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1)]
|
||||||
|
public string? ParameterB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class DuplicateOptionNamesCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("fruits")]
|
||||||
|
public string? Apples { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("fruits")]
|
||||||
|
public string? Oranges { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class DuplicateOptionShortNamesCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption('x')]
|
||||||
|
public string? OptionA { get; set; }
|
||||||
|
|
||||||
|
[CommandOption('x')]
|
||||||
|
public string? OptionB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class ValidCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("hidden", Description = "Description")]
|
||||||
|
private class HiddenPropertiesCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(13, Name = "param", Description = "Param description")]
|
||||||
|
public string? Parameter { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("option", 'o', Description = "Option description", EnvironmentVariableName = "ENV")]
|
||||||
|
public string? Option { get; set; }
|
||||||
|
|
||||||
|
public string? HiddenA { get; set; }
|
||||||
|
|
||||||
|
public bool? HiddenB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
197
CliFx.Tests/ApplicationSpecs.cs
Normal file
197
CliFx.Tests/ApplicationSpecs.cs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using CliFx.Domain;
|
||||||
|
using CliFx.Exceptions;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class ApplicationSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Application_can_be_created_with_a_default_configuration()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var app = new CliApplicationBuilder()
|
||||||
|
.AddCommandsFromThisAssembly()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
app.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Application_can_be_created_with_a_custom_configuration()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var app = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(ValidCommand))
|
||||||
|
.AddCommandsFrom(typeof(ValidCommand).Assembly)
|
||||||
|
.AddCommands(new[] {typeof(ValidCommand)})
|
||||||
|
.AddCommandsFrom(new[] {typeof(ValidCommand).Assembly})
|
||||||
|
.AddCommandsFromThisAssembly()
|
||||||
|
.AllowDebugMode()
|
||||||
|
.AllowPreviewMode()
|
||||||
|
.UseTitle("test")
|
||||||
|
.UseExecutableName("test")
|
||||||
|
.UseVersionText("test")
|
||||||
|
.UseDescription("test")
|
||||||
|
.UseConsole(new VirtualConsole(Stream.Null))
|
||||||
|
.UseTypeActivator(Activator.CreateInstance)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
app.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void At_least_one_command_must_be_defined_in_an_application()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = Array.Empty<Type>();
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Commands_must_implement_the_corresponding_interface()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(NonImplementedCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Commands_must_be_annotated_by_an_attribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(NonAnnotatedCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Commands_must_have_unique_names()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(DuplicateNameCommandA), typeof(DuplicateNameCommandB)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_parameters_must_have_unique_order()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(DuplicateParameterOrderCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_parameters_must_have_unique_names()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(DuplicateParameterNameCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_parameter_can_be_non_scalar_only_if_no_other_such_parameter_is_present()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(MultipleNonScalarParametersCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_parameter_can_be_non_scalar_only_if_it_is_the_last_in_order()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(NonLastNonScalarParameterCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_options_must_have_unique_names()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(DuplicateOptionNamesCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_options_must_have_unique_short_names()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(DuplicateOptionShortNamesCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_options_must_have_unique_environment_variable_names()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(DuplicateOptionEnvironmentVariableNamesCommand)};
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() => ApplicationSchema.Resolve(commandTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Command_options_and_parameters_must_be_annotated_by_corresponding_attributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandTypes = new[] {typeof(HiddenPropertiesCommand)};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var schema = ApplicationSchema.Resolve(commandTypes);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
schema.Should().BeEquivalentTo(new ApplicationSchema(new[]
|
||||||
|
{
|
||||||
|
new CommandSchema(
|
||||||
|
typeof(HiddenPropertiesCommand),
|
||||||
|
"hidden",
|
||||||
|
"Description",
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new CommandParameterSchema(
|
||||||
|
typeof(HiddenPropertiesCommand).GetProperty(nameof(HiddenPropertiesCommand.Parameter)),
|
||||||
|
13,
|
||||||
|
"param",
|
||||||
|
"Param description")
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new CommandOptionSchema(
|
||||||
|
typeof(HiddenPropertiesCommand).GetProperty(nameof(HiddenPropertiesCommand.Option)),
|
||||||
|
"option",
|
||||||
|
'o',
|
||||||
|
"ENV",
|
||||||
|
false,
|
||||||
|
"Option description")
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
schema.ToString().Should().NotBeNullOrWhiteSpace(); // this is only for coverage, I'm sorry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
191
CliFx.Tests/ArgumentBindingSpecs.Commands.cs
Normal file
191
CliFx.Tests/ArgumentBindingSpecs.Commands.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class ArgumentBindingSpecs
|
||||||
|
{
|
||||||
|
[Command]
|
||||||
|
private 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(CustomEnum))]
|
||||||
|
public CustomEnum CustomEnum { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(IntNullable))]
|
||||||
|
public int? IntNullable { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(CustomEnumNullable))]
|
||||||
|
public CustomEnum? CustomEnumNullable { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(TimeSpanNullable))]
|
||||||
|
public TimeSpan? TimeSpanNullable { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(TestStringConstructable))]
|
||||||
|
public StringConstructable? TestStringConstructable { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(TestStringParseable))]
|
||||||
|
public StringParseable? TestStringParseable { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(TestStringParseableWithFormatProvider))]
|
||||||
|
public StringParseableWithFormatProvider? 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(CustomEnumArray))]
|
||||||
|
public CustomEnum[]? CustomEnumArray { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(IntNullableArray))]
|
||||||
|
public int?[]? IntNullableArray { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(TestStringConstructableArray))]
|
||||||
|
public StringConstructable[]? 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; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class ArrayOptionCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("option", 'o')]
|
||||||
|
public IReadOnlyList<string>? Option { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class RequiredOptionCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption(nameof(OptionA))]
|
||||||
|
public string? OptionA { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(OptionB), IsRequired = true)]
|
||||||
|
public string? OptionB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class ParametersCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0)]
|
||||||
|
public string? ParameterA { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1)]
|
||||||
|
public string? ParameterB { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(2)]
|
||||||
|
public IReadOnlyList<string>? ParameterC { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class UnsupportedPropertyTypeCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption(nameof(Option))]
|
||||||
|
public DummyType? Option { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class UnsupportedEnumerablePropertyTypeCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption(nameof(Option))]
|
||||||
|
public CustomEnumerable<string>? Option { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class NoParameterCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption(nameof(OptionA))]
|
||||||
|
public string? OptionA { get; set; }
|
||||||
|
|
||||||
|
[CommandOption(nameof(OptionB))]
|
||||||
|
public string? OptionB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
CliFx.Tests/ArgumentBindingSpecs.Types.cs
Normal file
64
CliFx.Tests/ArgumentBindingSpecs.Types.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class ArgumentBindingSpecs
|
||||||
|
{
|
||||||
|
private enum CustomEnum
|
||||||
|
{
|
||||||
|
Value1 = 1,
|
||||||
|
Value2 = 2,
|
||||||
|
Value3 = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StringConstructable
|
||||||
|
{
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
public StringConstructable(string value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StringParseable
|
||||||
|
{
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
private StringParseable(string value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringParseable Parse(string value) => new StringParseable(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StringParseableWithFormatProvider
|
||||||
|
{
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
private StringParseableWithFormatProvider(string value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringParseableWithFormatProvider Parse(string value, IFormatProvider formatProvider) =>
|
||||||
|
new StringParseableWithFormatProvider(value + " " + formatProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DummyType
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomEnumerable<T> : IEnumerable<T>
|
||||||
|
{
|
||||||
|
private readonly T[] _arr = new T[0];
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _arr).GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1076
CliFx.Tests/ArgumentBindingSpecs.cs
Normal file
1076
CliFx.Tests/ArgumentBindingSpecs.cs
Normal file
File diff suppressed because it is too large
Load Diff
315
CliFx.Tests/ArgumentSyntaxSpecs.cs
Normal file
315
CliFx.Tests/ArgumentSyntaxSpecs.cs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
using System;
|
||||||
|
using CliFx.Domain;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public class ArgumentSyntaxSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Input_is_empty_if_no_arguments_are_provided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var args = Array.Empty<string>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var input = CommandLineInput.Parse(args);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
input.Should().BeEquivalentTo(CommandLineInput.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[][] DirectivesTestData => new[]
|
||||||
|
{
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"[preview]"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddDirective("preview")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"[preview]", "[debug]"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddDirective("preview")
|
||||||
|
.AddDirective("debug")
|
||||||
|
.Build()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(DirectivesTestData))]
|
||||||
|
internal void Directive_can_be_enabled_by_specifying_its_name_in_square_brackets(string[] arguments, CommandLineInput expectedInput)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var input = CommandLineInput.Parse(arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
input.Should().BeEquivalentTo(expectedInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[][] OptionsTestData => new[]
|
||||||
|
{
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option", "value"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option", "value")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option", "value1", "value2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option", "value1", "value2")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option", "same value"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option", "same value")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option1", "--option2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option1")
|
||||||
|
.AddOption("option2")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option1", "value1", "--option2", "value2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option1", "value1")
|
||||||
|
.AddOption("option2", "value2")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option1", "value1", "value2", "--option2", "value3", "value4"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option1", "value1", "value2")
|
||||||
|
.AddOption("option2", "value3", "value4")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"--option1", "value1", "value2", "--option2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("option1", "value1", "value2")
|
||||||
|
.AddOption("option2")
|
||||||
|
.Build()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(OptionsTestData))]
|
||||||
|
internal void Option_can_be_set_by_specifying_its_name_after_two_dashes(string[] arguments, CommandLineInput expectedInput)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var input = CommandLineInput.Parse(arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
input.Should().BeEquivalentTo(expectedInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[][] ShortOptionsTestData => new[]
|
||||||
|
{
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-o"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("o")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-o", "value"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("o", "value")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-o", "value1", "value2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("o", "value1", "value2")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-o", "same value"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("o", "same value")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-a", "-b"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a")
|
||||||
|
.AddOption("b")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-a", "value1", "-b", "value2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a", "value1")
|
||||||
|
.AddOption("b", "value2")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-a", "value1", "value2", "-b", "value3", "value4"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a", "value1", "value2")
|
||||||
|
.AddOption("b", "value3", "value4")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-a", "value1", "value2", "-b"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a", "value1", "value2")
|
||||||
|
.AddOption("b")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-abc"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a")
|
||||||
|
.AddOption("b")
|
||||||
|
.AddOption("c")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-abc", "value"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a")
|
||||||
|
.AddOption("b")
|
||||||
|
.AddOption("c", "value")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"-abc", "value1", "value2"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddOption("a")
|
||||||
|
.AddOption("b")
|
||||||
|
.AddOption("c", "value1", "value2")
|
||||||
|
.Build()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ShortOptionsTestData))]
|
||||||
|
internal void Option_can_be_set_by_specifying_its_short_name_after_a_single_dash(string[] arguments, CommandLineInput expectedInput)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var input = CommandLineInput.Parse(arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
input.Should().BeEquivalentTo(expectedInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object[][] UnboundArgumentsTestData => new[]
|
||||||
|
{
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"foo"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddUnboundArgument("foo")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"foo", "bar"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddUnboundArgument("foo")
|
||||||
|
.AddUnboundArgument("bar")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"[preview]", "foo"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddDirective("preview")
|
||||||
|
.AddUnboundArgument("foo")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"foo", "--option", "value", "-abc"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddUnboundArgument("foo")
|
||||||
|
.AddOption("option", "value")
|
||||||
|
.AddOption("a")
|
||||||
|
.AddOption("b")
|
||||||
|
.AddOption("c")
|
||||||
|
.Build()
|
||||||
|
},
|
||||||
|
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
new[] {"[preview]", "[debug]", "foo", "bar", "--option", "value", "-abc"},
|
||||||
|
new CommandLineInputBuilder()
|
||||||
|
.AddDirective("preview")
|
||||||
|
.AddDirective("debug")
|
||||||
|
.AddUnboundArgument("foo")
|
||||||
|
.AddUnboundArgument("bar")
|
||||||
|
.AddOption("option", "value")
|
||||||
|
.AddOption("a")
|
||||||
|
.AddOption("b")
|
||||||
|
.AddOption("c")
|
||||||
|
.Build()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(UnboundArgumentsTestData))]
|
||||||
|
internal void Any_remaining_arguments_are_treated_as_unbound_arguments(string[] arguments, CommandLineInput expectedInput)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var input = CommandLineInput.Parse(arguments);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
input.Should().BeEquivalentTo(expectedInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
CliFx.Tests/CancellationSpecs.Commands.cs
Normal file
27
CliFx.Tests/CancellationSpecs.Commands.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class CancellationSpecs
|
||||||
|
{
|
||||||
|
[Command("cancel")]
|
||||||
|
private class CancellableCommand : ICommand
|
||||||
|
{
|
||||||
|
public async ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(3), console.GetCancellationToken());
|
||||||
|
console.Output.WriteLine("Never printed");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
console.Output.WriteLine("Cancellation requested");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
CliFx.Tests/CancellationSpecs.cs
Normal file
41
CliFx.Tests/CancellationSpecs.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class CancellationSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Command_can_perform_additional_cleanup_if_cancellation_is_requested()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut, cancellationToken: cts.Token);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(CancellableCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
cts.CancelAfter(TimeSpan.FromSeconds(0.2));
|
||||||
|
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"cancel"},
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().NotBe(0);
|
||||||
|
stdOutData.Should().Be("Cancellation requested");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using NUnit.Framework;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.Stubs;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
|
|
||||||
namespace CliFx.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class CliApplicationBuilderTests
|
|
||||||
{
|
|
||||||
// Make sure all builder methods work
|
|
||||||
[Test]
|
|
||||||
public void All_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))
|
|
||||||
.UseCommandFactory(schema => (ICommand)Activator.CreateInstance(schema.Type))
|
|
||||||
.UseCommandOptionInputConverter(new CommandOptionInputConverter())
|
|
||||||
.UseEnvironmentVariablesProvider(new EnvironmentVariablesProviderStub())
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure builder can produce an application with no parameters specified
|
|
||||||
[Test]
|
|
||||||
public void Build_Test()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var builder = new CliApplicationBuilder();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
builder.Build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
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 CliFx.Tests.Stubs;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
|
|
||||||
namespace CliFx.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class CliApplicationTests
|
|
||||||
{
|
|
||||||
private const string TestVersionText = "v1.0";
|
|
||||||
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_RunAsync()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(HelloWorldDefaultCommand) },
|
|
||||||
new string[0],
|
|
||||||
"Hello world."
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "concat", "-i", "foo", "-i", "bar", "-s", " " },
|
|
||||||
"foo bar"
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "concat", "-i", "one", "two", "three", "-s", ", " },
|
|
||||||
"one, two, three"
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(DivideCommand) },
|
|
||||||
new[] { "div", "-D", "24", "-d", "8" },
|
|
||||||
"3"
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(HelloWorldDefaultCommand) },
|
|
||||||
new[] { "--version" },
|
|
||||||
TestVersionText
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "--version" },
|
|
||||||
TestVersionText
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(HelloWorldDefaultCommand) },
|
|
||||||
new[] { "-h" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(HelloWorldDefaultCommand) },
|
|
||||||
new[] { "--help" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new string[0],
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "-h" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "--help" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "concat", "-h" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ExceptionCommand) },
|
|
||||||
new[] { "exc", "-h" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(CommandExceptionCommand) },
|
|
||||||
new[] { "exc", "-h" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "[preview]" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ExceptionCommand) },
|
|
||||||
new[] { "exc", "[preview]" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "concat", "[preview]", "-o", "value" },
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_RunAsync_Negative()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new Type[0],
|
|
||||||
new string[0],
|
|
||||||
null, null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ConcatCommand) },
|
|
||||||
new[] { "non-existing" },
|
|
||||||
null, null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(ExceptionCommand) },
|
|
||||||
new[] { "exc" },
|
|
||||||
null, null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(CommandExceptionCommand) },
|
|
||||||
new[] { "exc" },
|
|
||||||
null, null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(CommandExceptionCommand) },
|
|
||||||
new[] { "exc" },
|
|
||||||
null, null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(CommandExceptionCommand) },
|
|
||||||
new[] { "exc", "-m", "foo bar" },
|
|
||||||
"foo bar", null
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(CommandExceptionCommand) },
|
|
||||||
new[] { "exc", "-m", "foo bar", "-c", "666" },
|
|
||||||
"foo bar", 666
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCaseSource(nameof(GetTestCases_RunAsync))]
|
|
||||||
public async Task RunAsync_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments,
|
|
||||||
string expectedStdOut = null)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var stdoutStream = new StringWriter())
|
|
||||||
{
|
|
||||||
var console = new VirtualConsole(stdoutStream);
|
|
||||||
var environmentVariablesProvider = new EnvironmentVariablesProviderStub();
|
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
|
||||||
.AddCommands(commandTypes)
|
|
||||||
.UseVersionText(TestVersionText)
|
|
||||||
.UseConsole(console)
|
|
||||||
.UseEnvironmentVariablesProvider(environmentVariablesProvider)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var exitCode = await application.RunAsync(commandLineArguments);
|
|
||||||
var stdOut = stdoutStream.ToString().Trim();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
exitCode.Should().Be(0);
|
|
||||||
|
|
||||||
if (expectedStdOut != null)
|
|
||||||
stdOut.Should().Be(expectedStdOut);
|
|
||||||
else
|
|
||||||
stdOut.Should().NotBeNullOrWhiteSpace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCaseSource(nameof(GetTestCases_RunAsync_Negative))]
|
|
||||||
public async Task RunAsync_Negative_Test(IReadOnlyList<Type> commandTypes, IReadOnlyList<string> commandLineArguments,
|
|
||||||
string expectedStdErr = null, int? expectedExitCode = null)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var stderrStream = new StringWriter())
|
|
||||||
{
|
|
||||||
var console = new VirtualConsole(TextWriter.Null, stderrStream);
|
|
||||||
var environmentVariablesProvider = new EnvironmentVariablesProviderStub();
|
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
|
||||||
.AddCommands(commandTypes)
|
|
||||||
.UseVersionText(TestVersionText)
|
|
||||||
.UseEnvironmentVariablesProvider(environmentVariablesProvider)
|
|
||||||
.UseConsole(console)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var exitCode = await application.RunAsync(commandLineArguments);
|
|
||||||
var stderr = stderrStream.ToString().Trim();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if (expectedExitCode != null)
|
|
||||||
exitCode.Should().Be(expectedExitCode);
|
|
||||||
else
|
|
||||||
exitCode.Should().NotBe(0);
|
|
||||||
|
|
||||||
if (expectedStdErr != null)
|
|
||||||
stderr.Should().Be(expectedStdErr);
|
|
||||||
else
|
|
||||||
stderr.Should().NotBeNullOrWhiteSpace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task RunAsync_Cancellation_Test()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var stdoutStream = new StringWriter())
|
|
||||||
using (var cancellationTokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
var console = new VirtualConsole(stdoutStream, cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
var application = new CliApplicationBuilder()
|
|
||||||
.AddCommand(typeof(CancellableCommand))
|
|
||||||
.UseConsole(console)
|
|
||||||
.Build();
|
|
||||||
var args = new[] { "cancel" };
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var runTask = application.RunAsync(args);
|
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
var exitCode = await runTask.ConfigureAwait(false);
|
|
||||||
var stdOut = stdoutStream.ToString().Trim();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
exitCode.Should().Be(-2146233029);
|
|
||||||
stdOut.Should().Be("Printed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../CliFx.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net46</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
<CollectCoverage>true</CollectCoverage>
|
<CollectCoverage>true</CollectCoverage>
|
||||||
@@ -10,18 +11,29 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="5.8.0" />
|
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
|
|
||||||
<PackageReference Include="coverlet.msbuild" Version="2.6.3">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CliWrap" Version="3.0.0" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="5.10.2" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||||
|
<PackageReference Include="coverlet.msbuild" Version="2.8.0" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CliFx.Tests.Dummy\CliFx.Tests.Dummy.csproj" />
|
||||||
<ProjectReference Include="..\CliFx\CliFx.csproj" />
|
<ProjectReference Include="..\CliFx\CliFx.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../CliFx.Tests.Dummy/bin/$(Configuration)/$(TargetFramework)/CliFx.Tests.Dummy.runtimeconfig.json">
|
||||||
|
<Link>CliFx.Tests.Dummy.runtimeconfig.json</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<Visible>False</Visible>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
72
CliFx.Tests/ConsoleSpecs.cs
Normal file
72
CliFx.Tests/ConsoleSpecs.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliWrap;
|
||||||
|
using CliWrap.Buffered;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public class ConsoleSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Real_implementation_of_console_maps_directly_to_system_console()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var command = "Hello world" | Cli.Wrap("dotnet")
|
||||||
|
.WithArguments(a => a
|
||||||
|
.Add(Dummy.Program.Location)
|
||||||
|
.Add("console-test"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await command.ExecuteBufferedAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.StandardOutput.TrimEnd().Should().Be("Hello world");
|
||||||
|
result.StandardError.TrimEnd().Should().Be("Hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Fake_implementation_of_console_can_be_used_to_execute_commands_in_isolation()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var stdIn = new MemoryStream(Console.InputEncoding.GetBytes("input"));
|
||||||
|
using var stdOut = new MemoryStream();
|
||||||
|
using var stdErr = new MemoryStream();
|
||||||
|
|
||||||
|
var console = new VirtualConsole(
|
||||||
|
input: stdIn,
|
||||||
|
output: stdOut,
|
||||||
|
error: stdErr);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
console.Output.Write("output");
|
||||||
|
console.Error.Write("error");
|
||||||
|
|
||||||
|
var stdInData = console.Input.ReadToEnd();
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray());
|
||||||
|
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray());
|
||||||
|
|
||||||
|
console.ResetColor();
|
||||||
|
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||||
|
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdInData.Should().Be("input");
|
||||||
|
stdOutData.Should().Be("output");
|
||||||
|
stdErrData.Should().Be("error");
|
||||||
|
|
||||||
|
console.Input.Should().NotBeSameAs(Console.In);
|
||||||
|
console.Output.Should().NotBeSameAs(Console.Out);
|
||||||
|
console.Error.Should().NotBeSameAs(Console.Error);
|
||||||
|
|
||||||
|
console.IsInputRedirected.Should().BeTrue();
|
||||||
|
console.IsOutputRedirected.Should().BeTrue();
|
||||||
|
console.IsErrorRedirected.Should().BeTrue();
|
||||||
|
|
||||||
|
console.ForegroundColor.Should().NotBe(Console.ForegroundColor);
|
||||||
|
console.BackgroundColor.Should().NotBe(Console.BackgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
CliFx.Tests/DependencyInjectionSpecs.Commands.cs
Normal file
37
CliFx.Tests/DependencyInjectionSpecs.Commands.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class DependencyInjectionSpecs
|
||||||
|
{
|
||||||
|
[Command]
|
||||||
|
private class WithoutDependenciesCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DependencyA
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DependencyB
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class WithDependenciesCommand : ICommand
|
||||||
|
{
|
||||||
|
private readonly DependencyA _dependencyA;
|
||||||
|
private readonly DependencyB _dependencyB;
|
||||||
|
|
||||||
|
public WithDependenciesCommand(DependencyA dependencyA, DependencyB dependencyB)
|
||||||
|
{
|
||||||
|
_dependencyA = dependencyA;
|
||||||
|
_dependencyB = dependencyB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
CliFx.Tests/DependencyInjectionSpecs.cs
Normal file
58
CliFx.Tests/DependencyInjectionSpecs.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using CliFx.Exceptions;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class DependencyInjectionSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Default_type_activator_can_initialize_a_command_if_it_has_a_parameterless_constructor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var activator = new DefaultTypeActivator();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var obj = activator.CreateInstance(typeof(WithoutDependenciesCommand));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
obj.Should().BeOfType<WithoutDependenciesCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Default_type_activator_cannot_initialize_a_command_if_it_does_not_have_a_parameterless_constructor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var activator = new DefaultTypeActivator();
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() =>
|
||||||
|
activator.CreateInstance(typeof(WithDependenciesCommand)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Delegate_type_activator_can_initialize_a_command_using_a_custom_function()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var activator = new DelegateTypeActivator(_ =>
|
||||||
|
new WithDependenciesCommand(new DependencyA(), new DependencyB()));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var obj = activator.CreateInstance(typeof(WithDependenciesCommand));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
obj.Should().BeOfType<WithDependenciesCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Delegate_type_activator_throws_if_the_underlying_function_returns_null()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var activator = new DelegateTypeActivator(_ => null);
|
||||||
|
|
||||||
|
// Act & assert
|
||||||
|
Assert.Throws<CliFxException>(() =>
|
||||||
|
activator.CreateInstance(typeof(WithDependenciesCommand)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
CliFx.Tests/DirectivesSpecs.Commands.cs
Normal file
14
CliFx.Tests/DirectivesSpecs.Commands.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class DirectivesSpecs
|
||||||
|
{
|
||||||
|
[Command("cmd")]
|
||||||
|
private class NamedCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
CliFx.Tests/DirectivesSpecs.cs
Normal file
36
CliFx.Tests/DirectivesSpecs.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class DirectivesSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Preview_directive_can_be_enabled_to_print_provided_arguments_as_they_were_parsed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(NamedCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.AllowPreviewMode()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"[preview]", "cmd", "param", "-abc", "--option", "foo"},
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOutData.Should().ContainAll("cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option foo]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
CliFx.Tests/EnvironmentVariablesSpecs.Commands.cs
Normal file
27
CliFx.Tests/EnvironmentVariablesSpecs.Commands.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class EnvironmentVariablesSpecs
|
||||||
|
{
|
||||||
|
[Command]
|
||||||
|
private class EnvironmentVariableCollectionCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("opt", EnvironmentVariableName = "ENV_OPT")]
|
||||||
|
public IReadOnlyList<string>? Option { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
private class EnvironmentVariableCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("opt", EnvironmentVariableName = "ENV_OPT")]
|
||||||
|
public string? Option { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
CliFx.Tests/EnvironmentVariablesSpecs.cs
Normal file
96
CliFx.Tests/EnvironmentVariablesSpecs.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Domain;
|
||||||
|
using CliWrap;
|
||||||
|
using CliWrap.Buffered;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class EnvironmentVariablesSpecs
|
||||||
|
{
|
||||||
|
// This test uses a real application to make sure environment variables are actually read correctly
|
||||||
|
[Fact]
|
||||||
|
public async Task Option_can_use_a_specific_environment_variable_as_fallback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var command = Cli.Wrap("dotnet")
|
||||||
|
.WithArguments(a => a
|
||||||
|
.Add(Dummy.Program.Location))
|
||||||
|
.WithEnvironmentVariables(e => e
|
||||||
|
.Set("ENV_TARGET", "Mars"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var stdOut = await command.ExecuteBufferedAsync().Select(r => r.StandardOutput);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOut.TrimEnd().Should().Be("Hello Mars!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test uses a real application to make sure environment variables are actually read correctly
|
||||||
|
[Fact]
|
||||||
|
public async Task Option_only_uses_environment_variable_as_fallback_if_the_value_was_not_directly_provided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var command = Cli.Wrap("dotnet")
|
||||||
|
.WithArguments(a => a
|
||||||
|
.Add(Dummy.Program.Location)
|
||||||
|
.Add("--target")
|
||||||
|
.Add("Jupiter"))
|
||||||
|
.WithEnvironmentVariables(e => e
|
||||||
|
.Set("ENV_TARGET", "Mars"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var stdOut = await command.ExecuteBufferedAsync().Select(r => r.StandardOutput);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOut.TrimEnd().Should().Be("Hello Jupiter!");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Option_of_non_scalar_type_can_take_multiple_separated_values_from_an_environment_variable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var schema = ApplicationSchema.Resolve(new[] {typeof(EnvironmentVariableCollectionCommand)});
|
||||||
|
|
||||||
|
var input = CommandLineInput.Empty;
|
||||||
|
var envVars = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["ENV_OPT"] = $"foo{Path.PathSeparator}bar"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var command = schema.InitializeEntryPoint(input, envVars);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
command.Should().BeEquivalentTo(new EnvironmentVariableCollectionCommand
|
||||||
|
{
|
||||||
|
Option = new[] {"foo", "bar"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Option_of_scalar_type_can_only_take_a_single_value_from_an_environment_variable_even_if_it_contains_separators()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var schema = ApplicationSchema.Resolve(new[] {typeof(EnvironmentVariableCommand)});
|
||||||
|
|
||||||
|
var input = CommandLineInput.Empty;
|
||||||
|
var envVars = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["ENV_OPT"] = $"foo{Path.PathSeparator}bar"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var command = schema.InitializeEntryPoint(input, envVars);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
command.Should().BeEquivalentTo(new EnvironmentVariableCommand
|
||||||
|
{
|
||||||
|
Option = $"foo{Path.PathSeparator}bar"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
CliFx.Tests/ErrorReportingSpecs.Commands.cs
Normal file
31
CliFx.Tests/ErrorReportingSpecs.Commands.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Exceptions;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class ErrorReportingSpecs
|
||||||
|
{
|
||||||
|
[Command("exc")]
|
||||||
|
private class GenericExceptionCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("msg", 'm')]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => throw new Exception(Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("exc")]
|
||||||
|
private 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
CliFx.Tests/ErrorReportingSpecs.cs
Normal file
84
CliFx.Tests/ErrorReportingSpecs.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class ErrorReportingSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Command_may_throw_a_generic_exception_which_exits_and_prints_full_error_details()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdErr = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(error: stdErr);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(GenericExceptionCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"exc", "-m", "Kaput"},
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().NotBe(0);
|
||||||
|
stdErrData.Should().Contain("Kaput");
|
||||||
|
stdErrData.Length.Should().BeGreaterThan("Kaput".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Command_may_throw_a_specialized_exception_which_exits_with_custom_code_and_prints_minimal_error_details()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdErr = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(error: stdErr);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(CommandExceptionCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"exc", "-m", "Kaput", "-c", "69"},
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(69);
|
||||||
|
stdErrData.Should().Be("Kaput");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Command_may_throw_a_specialized_exception_without_error_message_which_exits_and_prints_full_error_details()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdErr = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(error: stdErr);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(CommandExceptionCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"exc", "-m", "Kaput"},
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().NotBe(0);
|
||||||
|
stdErrData.Should().NotBeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
CliFx.Tests/HelpTextSpecs.Commands.cs
Normal file
96
CliFx.Tests/HelpTextSpecs.Commands.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class HelpTextSpecs
|
||||||
|
{
|
||||||
|
[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 ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("cmd", Description = "NamedCommand description.")]
|
||||||
|
private class NamedCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, Name = "param-a", Description = "ParameterA description.")]
|
||||||
|
public string? ParameterA { get; set; }
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("cmd sub", Description = "NamedSubCommand description.")]
|
||||||
|
private class NamedSubCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, Name = "param-b", Description = "ParameterB description.")]
|
||||||
|
public string? ParameterB { get; set; }
|
||||||
|
|
||||||
|
[CommandParameter(1, Name = "param-c", Description = "ParameterC description.")]
|
||||||
|
public string? ParameterC { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("option-e", 'e', Description = "OptionE description.")]
|
||||||
|
public string? OptionE { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("cmd-with-params")]
|
||||||
|
private class ParametersCommand : 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? Option { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("cmd-with-req-opts")]
|
||||||
|
private class RequiredOptionsCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("option-f", 'f', IsRequired = true)]
|
||||||
|
public string? OptionF { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("option-g", 'g', IsRequired = true)]
|
||||||
|
public IEnumerable<int>? OptionG { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("option-h", 'h')]
|
||||||
|
public string? OptionH { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("cmd-with-env-vars")]
|
||||||
|
private class EnvironmentVariableCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandOption("option-a", 'a', IsRequired = true, EnvironmentVariableName = "ENV_OPT_A")]
|
||||||
|
public string? OptionA { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("option-b", 'b', EnvironmentVariableName = "ENV_OPT_B")]
|
||||||
|
public string? OptionB { get; set; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
246
CliFx.Tests/HelpTextSpecs.cs
Normal file
246
CliFx.Tests/HelpTextSpecs.cs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class HelpTextSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Version_information_can_be_requested_by_providing_the_version_option_without_other_arguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(DefaultCommand))
|
||||||
|
.AddCommand(typeof(NamedCommand))
|
||||||
|
.AddCommand(typeof(NamedSubCommand))
|
||||||
|
.UseVersionText("v6.9")
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(new[] {"--version"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOutData.Should().Be("v6.9");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_can_be_requested_by_providing_the_help_option()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(DefaultCommand))
|
||||||
|
.AddCommand(typeof(NamedCommand))
|
||||||
|
.AddCommand(typeof(NamedSubCommand))
|
||||||
|
.UseTitle("AppTitle")
|
||||||
|
.UseVersionText("AppVer")
|
||||||
|
.UseDescription("AppDesc")
|
||||||
|
.UseExecutableName("AppExe")
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"AppTitle", "AppVer",
|
||||||
|
"AppDesc",
|
||||||
|
"Usage",
|
||||||
|
"AppExe", "[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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_can_be_requested_on_a_specific_named_command()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(DefaultCommand))
|
||||||
|
.AddCommand(typeof(NamedCommand))
|
||||||
|
.AddCommand(typeof(NamedSubCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"cmd", "--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"Description",
|
||||||
|
"NamedCommand description.",
|
||||||
|
"Usage",
|
||||||
|
"cmd", "[command]", "<param-a>", "[options]",
|
||||||
|
"Parameters",
|
||||||
|
"* param-a", "ParameterA description.",
|
||||||
|
"Options",
|
||||||
|
"-c|--option-c", "OptionC description.",
|
||||||
|
"-d|--option-d", "OptionD description.",
|
||||||
|
"-h|--help", "Shows help text.",
|
||||||
|
"Commands",
|
||||||
|
"sub", "SubCommand description.",
|
||||||
|
"You can run", "to show help on a specific command."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_can_be_requested_on_a_specific_named_sub_command()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(DefaultCommand))
|
||||||
|
.AddCommand(typeof(NamedCommand))
|
||||||
|
.AddCommand(typeof(NamedSubCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"cmd", "sub", "--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"Description",
|
||||||
|
"SubCommand description.",
|
||||||
|
"Usage",
|
||||||
|
"cmd sub", "<param-b>", "<param-c>", "[options]",
|
||||||
|
"Parameters",
|
||||||
|
"* param-b", "ParameterB description.",
|
||||||
|
"* param-c", "ParameterC description.",
|
||||||
|
"Options",
|
||||||
|
"-e|--option-e", "OptionE description.",
|
||||||
|
"-h|--help", "Shows help text."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_can_be_requested_without_specifying_command_even_if_default_command_is_not_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(NamedCommand))
|
||||||
|
.AddCommand(typeof(NamedSubCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"Usage",
|
||||||
|
"[command]",
|
||||||
|
"Options",
|
||||||
|
"-h|--help", "Shows help text.",
|
||||||
|
"--version", "Shows version information.",
|
||||||
|
"Commands",
|
||||||
|
"cmd", "NamedCommand description.",
|
||||||
|
"You can run", "to show help on a specific command."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_shows_usage_format_which_lists_all_parameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(ParametersCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"cmd-with-params", "--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"Usage",
|
||||||
|
"cmd-with-params", "<first>", "<parameterb>", "<third list...>", "[options]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_shows_usage_format_which_lists_all_required_options()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(RequiredOptionsCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"cmd-with-req-opts", "--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"Usage",
|
||||||
|
"cmd-with-req-opts", "--option-f <value>", "--option-g <values...>", "[options]",
|
||||||
|
"Options",
|
||||||
|
"* -f|--option-f",
|
||||||
|
"* -g|--option-g",
|
||||||
|
"-h|--option-h"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_lists_environment_variable_names_for_options_that_have_them_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(EnvironmentVariableCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await application.RunAsync(new[] {"cmd-with-env-vars", "--help"});
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(
|
||||||
|
"Options",
|
||||||
|
"* -a|--option-a", "Environment variable:", "ENV_OPT_A",
|
||||||
|
"-b|--option-b", "Environment variable:", "ENV_OPT_B"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
CliFx.Tests/RoutingSpecs.Commands.cs
Normal file
51
CliFx.Tests/RoutingSpecs.Commands.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class RoutingSpecs
|
||||||
|
{
|
||||||
|
[Command]
|
||||||
|
private class DefaultCommand : ICommand
|
||||||
|
{
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
console.Output.WriteLine("Hello world!");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("concat", Description = "Concatenate strings.")]
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("div", Description = "Divide one number by another.")]
|
||||||
|
private 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; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
console.Output.WriteLine(Dividend / Divisor);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
CliFx.Tests/RoutingSpecs.cs
Normal file
90
CliFx.Tests/RoutingSpecs.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public partial class RoutingSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Default_command_is_executed_if_provided_arguments_do_not_match_any_named_command()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(DefaultCommand))
|
||||||
|
.AddCommand(typeof(ConcatCommand))
|
||||||
|
.AddCommand(typeof(DivideCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
Array.Empty<string>(),
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOutData.Should().Be("Hello world!");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Help_text_is_printed_if_no_arguments_were_provided_and_default_command_is_not_defined()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(ConcatCommand))
|
||||||
|
.AddCommand(typeof(DivideCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.UseDescription("This will be visible in help")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
Array.Empty<string>(),
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOutData.Should().Contain("This will be visible in help");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Specific_named_command_is_executed_if_provided_arguments_match_its_name()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut);
|
||||||
|
|
||||||
|
var application = new CliApplicationBuilder()
|
||||||
|
.AddCommand(typeof(DefaultCommand))
|
||||||
|
.AddCommand(typeof(ConcatCommand))
|
||||||
|
.AddCommand(typeof(DivideCommand))
|
||||||
|
.UseConsole(console)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exitCode = await application.RunAsync(
|
||||||
|
new[] {"concat", "-i", "foo", "bar", "-s", ", "},
|
||||||
|
new Dictionary<string, string>());
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
exitCode.Should().Be(0);
|
||||||
|
stdOutData.Should().Be("foo, bar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public 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(HelloWorldDefaultCommand)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[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,173 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using CliFx.Exceptions;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
using CliFx.Tests.Stubs;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public 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 DivideCommand(),
|
|
||||||
GetCommandSchema(typeof(DivideCommand)),
|
|
||||||
new CommandInput("div", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("dividend", "13"),
|
|
||||||
new CommandOptionInput("divisor", "8")
|
|
||||||
}),
|
|
||||||
new DivideCommand { Dividend = 13, Divisor = 8 }
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new DivideCommand(),
|
|
||||||
GetCommandSchema(typeof(DivideCommand)),
|
|
||||||
new CommandInput("div", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("dividend", "13"),
|
|
||||||
new CommandOptionInput("d", "8")
|
|
||||||
}),
|
|
||||||
new DivideCommand { Dividend = 13, Divisor = 8 }
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new DivideCommand(),
|
|
||||||
GetCommandSchema(typeof(DivideCommand)),
|
|
||||||
new CommandInput("div", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("D", "13"),
|
|
||||||
new CommandOptionInput("d", "8")
|
|
||||||
}),
|
|
||||||
new DivideCommand { Dividend = 13, Divisor = 8 }
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new ConcatCommand(),
|
|
||||||
GetCommandSchema(typeof(ConcatCommand)),
|
|
||||||
new CommandInput("concat", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("i", new[] {"foo", " ", "bar"})
|
|
||||||
}),
|
|
||||||
new ConcatCommand { Inputs = new[] { "foo", " ", "bar" } }
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new ConcatCommand(),
|
|
||||||
GetCommandSchema(typeof(ConcatCommand)),
|
|
||||||
new CommandInput("concat", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("i", new[] {"foo", "bar"}),
|
|
||||||
new CommandOptionInput("s", " ")
|
|
||||||
}),
|
|
||||||
new ConcatCommand { Inputs = new[] { "foo", "bar" }, Separator = " " }
|
|
||||||
);
|
|
||||||
|
|
||||||
//Will read a value from environment variables because none is supplied via CommandInput
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new EnvironmentVariableCommand(),
|
|
||||||
GetCommandSchema(typeof(EnvironmentVariableCommand)),
|
|
||||||
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
|
|
||||||
new EnvironmentVariableCommand { Option = "A" }
|
|
||||||
);
|
|
||||||
|
|
||||||
//Will read multiple values from environment variables because none is supplied via CommandInput
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new EnvironmentVariableWithMultipleValuesCommand(),
|
|
||||||
GetCommandSchema(typeof(EnvironmentVariableWithMultipleValuesCommand)),
|
|
||||||
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
|
|
||||||
new EnvironmentVariableWithMultipleValuesCommand { Option = new[] { "A", "B", "C" } }
|
|
||||||
);
|
|
||||||
|
|
||||||
//Will not read a value from environment variables because one is supplied via CommandInput
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new EnvironmentVariableCommand(),
|
|
||||||
GetCommandSchema(typeof(EnvironmentVariableCommand)),
|
|
||||||
new CommandInput(null, new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("opt", new[] { "X" })
|
|
||||||
},
|
|
||||||
EnvironmentVariablesProviderStub.EnvironmentVariables),
|
|
||||||
new EnvironmentVariableCommand { Option = "X" }
|
|
||||||
);
|
|
||||||
|
|
||||||
//Will not split environment variable values because underlying property is not a collection
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new EnvironmentVariableWithoutCollectionPropertyCommand(),
|
|
||||||
GetCommandSchema(typeof(EnvironmentVariableWithoutCollectionPropertyCommand)),
|
|
||||||
new CommandInput(null, new CommandOptionInput[0], EnvironmentVariablesProviderStub.EnvironmentVariables),
|
|
||||||
new EnvironmentVariableWithoutCollectionPropertyCommand { Option = "A;B;C;" }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_InitializeCommand_Negative()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new DivideCommand(),
|
|
||||||
GetCommandSchema(typeof(DivideCommand)),
|
|
||||||
new CommandInput("div")
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new DivideCommand(),
|
|
||||||
GetCommandSchema(typeof(DivideCommand)),
|
|
||||||
new CommandInput("div", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("D", "13")
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new ConcatCommand(),
|
|
||||||
GetCommandSchema(typeof(ConcatCommand)),
|
|
||||||
new CommandInput("concat")
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new ConcatCommand(),
|
|
||||||
GetCommandSchema(typeof(ConcatCommand)),
|
|
||||||
new CommandInput("concat", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("s", "_")
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[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<CliFxException>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.Stubs;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class CommandInputParserTests
|
|
||||||
{
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(new string[0], CommandInput.Empty, new EmptyEnvironmentVariablesProviderStub());
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--option", "value" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--option1", "value1", "--option2", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option1", "value1"),
|
|
||||||
new CommandOptionInput("option2", "value2")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--option", "value1", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--option", "value1", "--option", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-a", "value" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-a", "value1", "-b", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a", "value1"),
|
|
||||||
new CommandOptionInput("b", "value2")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-a", "value1", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-a", "value1", "-a", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--option1", "value1", "-b", "value2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option1", "value1"),
|
|
||||||
new CommandOptionInput("b", "value2")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--switch" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("switch")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "--switch1", "--switch2" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("switch1"),
|
|
||||||
new CommandOptionInput("switch2")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-s" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("s")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-a", "-b" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a"),
|
|
||||||
new CommandOptionInput("b")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-ab" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a"),
|
|
||||||
new CommandOptionInput("b")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "-ab", "value" },
|
|
||||||
new CommandInput(new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("a"),
|
|
||||||
new CommandOptionInput("b", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "command" },
|
|
||||||
new CommandInput("command"),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "command", "--option", "value" },
|
|
||||||
new CommandInput("command", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "long", "command", "name" },
|
|
||||||
new CommandInput("long command name"),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "long", "command", "name", "--option", "value" },
|
|
||||||
new CommandInput("long command name", new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("option", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "[debug]" },
|
|
||||||
new CommandInput(null,
|
|
||||||
new[] { "debug" },
|
|
||||||
new CommandOptionInput[0]),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "[debug]", "[preview]" },
|
|
||||||
new CommandInput(null,
|
|
||||||
new[] { "debug", "preview" },
|
|
||||||
new CommandOptionInput[0]),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "[debug]", "[preview]", "-o", "value" },
|
|
||||||
new CommandInput(null,
|
|
||||||
new[] { "debug", "preview" },
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("o", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "command", "[debug]", "[preview]", "-o", "value" },
|
|
||||||
new CommandInput("command",
|
|
||||||
new[] { "debug", "preview" },
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("o", "value")
|
|
||||||
}),
|
|
||||||
new EmptyEnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { "command", "[debug]", "[preview]", "-o", "value" },
|
|
||||||
new CommandInput("command",
|
|
||||||
new[] { "debug", "preview" },
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandOptionInput("o", "value")
|
|
||||||
},
|
|
||||||
EnvironmentVariablesProviderStub.EnvironmentVariables),
|
|
||||||
new EnvironmentVariablesProviderStub()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCaseSource(nameof(GetTestCases_ParseCommandInput))]
|
|
||||||
public void ParseCommandInput_Test(IReadOnlyList<string> commandLineArguments,
|
|
||||||
CommandInput expectedCommandInput, IEnvironmentVariablesProvider environmentVariablesProvider)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var parser = new CommandInputParser(environmentVariablesProvider);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var commandInput = parser.ParseCommandInput(commandLineArguments);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
commandInput.Should().BeEquivalentTo(expectedCommandInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using CliFx.Exceptions;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.TestCustomTypes;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public 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[] {"47"}),
|
|
||||||
typeof(int[]),
|
|
||||||
new[] {47}
|
|
||||||
);
|
|
||||||
|
|
||||||
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", new[] {"123", "456"}),
|
|
||||||
typeof(int)
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new CommandOptionInput("option"),
|
|
||||||
typeof(int)
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new CommandOptionInput("option", "123"),
|
|
||||||
typeof(TestNonStringParseable)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[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<CliFxException>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using CliFx.Exceptions;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class CommandSchemaResolverTests
|
|
||||||
{
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(DivideCommand), typeof(ConcatCommand), typeof(EnvironmentVariableCommand) },
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandSchema(typeof(DivideCommand), "div", "Divide one number by another.",
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Dividend)),
|
|
||||||
"dividend", 'D', true, "The number to divide.", null),
|
|
||||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)),
|
|
||||||
"divisor", 'd', true, "The number to divide by.", null)
|
|
||||||
}),
|
|
||||||
new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.",
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)),
|
|
||||||
null, 'i', true, "Input strings.", null),
|
|
||||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)),
|
|
||||||
null, 's', false, "String separator.", null)
|
|
||||||
}),
|
|
||||||
new CommandSchema(typeof(EnvironmentVariableCommand), null, "Reads option values from environment variables.",
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandOptionSchema(typeof(EnvironmentVariableCommand).GetProperty(nameof(EnvironmentVariableCommand.Option)),
|
|
||||||
"opt", null, false, null, "ENV_SINGLE_VALUE")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new[] { typeof(HelloWorldDefaultCommand) },
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new CommandSchema(typeof(HelloWorldDefaultCommand), null, null, new CommandOptionSchema[0])
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas_Negative()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(new object[]
|
|
||||||
{
|
|
||||||
new Type[0]
|
|
||||||
});
|
|
||||||
|
|
||||||
yield return new TestCaseData(new object[]
|
|
||||||
{
|
|
||||||
new[] {typeof(NonImplementedCommand)}
|
|
||||||
});
|
|
||||||
|
|
||||||
yield return new TestCaseData(new object[]
|
|
||||||
{
|
|
||||||
new[] {typeof(NonAnnotatedCommand)}
|
|
||||||
});
|
|
||||||
|
|
||||||
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(ExceptionCommand), typeof(CommandExceptionCommand)}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[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<CliFxException>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class DelegateCommandFactoryTests
|
|
||||||
{
|
|
||||||
private static CommandSchema GetCommandSchema(Type commandType) =>
|
|
||||||
new CommandSchemaResolver().GetCommandSchemas(new[] {commandType}).Single();
|
|
||||||
|
|
||||||
private static IEnumerable<TestCaseData> GetTestCases_CreateCommand()
|
|
||||||
{
|
|
||||||
yield return new TestCaseData(
|
|
||||||
new Func<CommandSchema, ICommand>(schema => (ICommand) Activator.CreateInstance(schema.Type)),
|
|
||||||
GetCommandSchema(typeof(HelloWorldDefaultCommand))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
[TestCaseSource(nameof(GetTestCases_CreateCommand))]
|
|
||||||
public void CreateCommand_Test(Func<CommandSchema, ICommand> factoryMethod, CommandSchema commandSchema)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var factory = new DelegateCommandFactory(factoryMethod);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var command = factory.CreateCommand(commandSchema);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
command.Should().BeOfType(commandSchema.Type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
using CliFx.Tests.TestCommands;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public 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(HelpDefaultCommand), typeof(HelpNamedCommand), typeof(HelpSubCommand)},
|
|
||||||
typeof(HelpDefaultCommand)),
|
|
||||||
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"Description",
|
|
||||||
"HelpDefaultCommand description.",
|
|
||||||
"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", "HelpNamedCommand description.",
|
|
||||||
"You can run", "to show help on a specific command."
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
yield return new TestCaseData(
|
|
||||||
CreateHelpTextSource(
|
|
||||||
new[] {typeof(HelpDefaultCommand), typeof(HelpNamedCommand), typeof(HelpSubCommand)},
|
|
||||||
typeof(HelpNamedCommand)),
|
|
||||||
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"Description",
|
|
||||||
"HelpNamedCommand description.",
|
|
||||||
"Usage",
|
|
||||||
"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(
|
|
||||||
CreateHelpTextSource(
|
|
||||||
new[] {typeof(HelpDefaultCommand), typeof(HelpNamedCommand), typeof(HelpSubCommand)},
|
|
||||||
typeof(HelpSubCommand)),
|
|
||||||
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"Description",
|
|
||||||
"HelpSubCommand 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 console = new VirtualConsole(stdout);
|
|
||||||
var renderer = new HelpTextRenderer();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
renderer.RenderHelpText(console, source);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
stdout.ToString().Should().ContainAll(expectedSubstrings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CliFx.Services;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class SystemConsoleTests
|
|
||||||
{
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
// Reset console color so it doesn't carry on into next tests
|
|
||||||
Console.ResetColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure console correctly wraps around System.Console
|
|
||||||
[Test]
|
|
||||||
public void All_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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using CliFx.Services;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Services
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class VirtualConsoleTests
|
|
||||||
{
|
|
||||||
// Make sure console uses specified streams and doesn't leak to System.Console
|
|
||||||
[Test]
|
|
||||||
public void All_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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Stubs
|
|
||||||
{
|
|
||||||
public class EmptyEnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
|
|
||||||
{
|
|
||||||
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.Stubs
|
|
||||||
{
|
|
||||||
public class EnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
|
|
||||||
{
|
|
||||||
public static readonly Dictionary<string, string> EnvironmentVariables = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["ENV_SINGLE_VALUE"] = "A",
|
|
||||||
["ENV_MULTIPLE_VALUES"] = $"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}",
|
|
||||||
["ENV_ESCAPED_MULTIPLE_VALUES"] = $"\"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}\""
|
|
||||||
};
|
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => EnvironmentVariables;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command("cancel")]
|
|
||||||
public class CancellableCommand : ICommand
|
|
||||||
{
|
|
||||||
public async Task ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
|
|
||||||
console.Output.WriteLine("Printed");
|
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), console.GetCancellationToken()).ConfigureAwait(false);
|
|
||||||
|
|
||||||
console.Output.WriteLine("Never printed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Exceptions;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
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 Task ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
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 Task ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
console.Output.WriteLine(string.Join(Separator, Inputs));
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
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 Task ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
console.Output.WriteLine(Dividend / Divisor);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command]
|
|
||||||
public class DuplicateOptionNamesCommand : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption("fruits")]
|
|
||||||
public string Apples { get; set; }
|
|
||||||
|
|
||||||
[CommandOption("fruits")]
|
|
||||||
public string Oranges { get; set; }
|
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command]
|
|
||||||
public class DuplicateOptionShortNamesCommand : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption('f')]
|
|
||||||
public string Apples { get; set; }
|
|
||||||
|
|
||||||
[CommandOption('f')]
|
|
||||||
public string Oranges { get; set; }
|
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command(Description = "Reads option values from environment variables.")]
|
|
||||||
public class EnvironmentVariableCommand : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption("opt", EnvironmentVariableName = "ENV_SINGLE_VALUE")]
|
|
||||||
public string Option { get; set; }
|
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command(Description = "Reads multiple option values from environment variables.")]
|
|
||||||
public class EnvironmentVariableWithMultipleValuesCommand : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
|
|
||||||
public IEnumerable<string> Option { get; set; }
|
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command(Description = "Reads one option value from environment variables because target property is not a collection.")]
|
|
||||||
public class EnvironmentVariableWithoutCollectionPropertyCommand : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption("opt", EnvironmentVariableName = "ENV_MULTIPLE_VALUES")]
|
|
||||||
public string Option { get; set; }
|
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command("exc")]
|
|
||||||
public class ExceptionCommand : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption("msg", 'm')]
|
|
||||||
public string Message { get; set; }
|
|
||||||
|
|
||||||
public Task ExecuteAsync(IConsole console) => throw new Exception(Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command]
|
|
||||||
public class HelloWorldDefaultCommand : ICommand
|
|
||||||
{
|
|
||||||
public Task ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
console.Output.WriteLine("Hello world.");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
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 Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
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 Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
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 Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
public class NonAnnotatedCommand : ICommand
|
|
||||||
{
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using CliFx.Attributes;
|
|
||||||
|
|
||||||
namespace CliFx.Tests.TestCommands
|
|
||||||
{
|
|
||||||
[Command]
|
|
||||||
public class NonImplementedCommand
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace CliFx.Tests.TestCustomTypes
|
|
||||||
{
|
|
||||||
public enum TestEnum
|
|
||||||
{
|
|
||||||
Value1,
|
|
||||||
Value2,
|
|
||||||
Value3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace CliFx.Tests.TestCustomTypes
|
|
||||||
{
|
|
||||||
public class TestNonStringParseable
|
|
||||||
{
|
|
||||||
public int Value { get; }
|
|
||||||
|
|
||||||
public TestNonStringParseable(int value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace CliFx.Tests.TestCustomTypes
|
|
||||||
{
|
|
||||||
public class TestStringConstructable
|
|
||||||
{
|
|
||||||
public string Value { get; }
|
|
||||||
|
|
||||||
public TestStringConstructable(string value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using CliFx.Services;
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
CliFx.Tests/UtilitiesSpecs.cs
Normal file
54
CliFx.Tests/UtilitiesSpecs.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using CliFx.Utilities;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CliFx.Tests
|
||||||
|
{
|
||||||
|
public class UtilitiesSpecs
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Progress_ticker_can_be_used_to_report_progress_to_console()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: stdOut, isOutputRedirected: 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")).ToArray();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
foreach (var progress in progressValues)
|
||||||
|
ticker.Report(progress);
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().ContainAll(progressStringValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Progress_ticker_does_not_write_to_console_if_output_is_redirected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var stdOut = new MemoryStream();
|
||||||
|
var console = new VirtualConsole(output: 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);
|
||||||
|
|
||||||
|
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
stdOutData.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
CliFx.Tests/xunit.runner.json
Normal file
5
CliFx.Tests/xunit.runner.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
|
||||||
|
"methodDisplayOptions": "all",
|
||||||
|
"methodDisplay": "method"
|
||||||
|
}
|
||||||
11
CliFx.props
Normal file
11
CliFx.props
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<Project>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<Version>1.1</Version>
|
||||||
|
<Company>Tyrrrz</Company>
|
||||||
|
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
15
CliFx.sln
15
CliFx.sln
@@ -12,12 +12,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
Changelog.md = Changelog.md
|
Changelog.md = Changelog.md
|
||||||
License.txt = License.txt
|
License.txt = License.txt
|
||||||
Readme.md = Readme.md
|
Readme.md = Readme.md
|
||||||
|
CliFx.props = CliFx.props
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx.Benchmarks", "CliFx.Benchmarks\CliFx.Benchmarks.csproj", "{8ACD6DC2-D768-4850-9223-5B7C83A78513}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliFx.Benchmarks", "CliFx.Benchmarks\CliFx.Benchmarks.csproj", "{8ACD6DC2-D768-4850-9223-5B7C83A78513}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CliFx.Tests.Dummy", "CliFx.Tests.Dummy\CliFx.Tests.Dummy.csproj", "{F717347D-8656-44DA-A4A2-BE515E8C4655}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -76,6 +79,18 @@ Global
|
|||||||
{AAB6844C-BF71-448F-A11B-89AEE459AB15}.Release|x64.Build.0 = Release|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{AAB6844C-BF71-448F-A11B-89AEE459AB15}.Release|x86.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CliFx.Internal;
|
|
||||||
|
|
||||||
namespace CliFx.Models
|
namespace CliFx
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration of an application.
|
/// Configuration of an application.
|
||||||
@@ -27,10 +26,11 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="ApplicationConfiguration"/>.
|
/// Initializes an instance of <see cref="ApplicationConfiguration"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApplicationConfiguration(IReadOnlyList<Type> commandTypes,
|
public ApplicationConfiguration(
|
||||||
|
IReadOnlyList<Type> commandTypes,
|
||||||
bool isDebugModeAllowed, bool isPreviewModeAllowed)
|
bool isDebugModeAllowed, bool isPreviewModeAllowed)
|
||||||
{
|
{
|
||||||
CommandTypes = commandTypes.GuardNotNull(nameof(commandTypes));
|
CommandTypes = commandTypes;
|
||||||
IsDebugModeAllowed = isDebugModeAllowed;
|
IsDebugModeAllowed = isDebugModeAllowed;
|
||||||
IsPreviewModeAllowed = isPreviewModeAllowed;
|
IsPreviewModeAllowed = isPreviewModeAllowed;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using CliFx.Internal;
|
namespace CliFx
|
||||||
|
|
||||||
namespace CliFx.Models
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Metadata associated with an application.
|
/// Metadata associated with an application.
|
||||||
@@ -25,17 +23,17 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application description.
|
/// Application description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; }
|
public string? Description { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
/// Initializes an instance of <see cref="ApplicationMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApplicationMetadata(string title, string executableName, string versionText, string description)
|
public ApplicationMetadata(string title, string executableName, string versionText, string? description)
|
||||||
{
|
{
|
||||||
Title = title.GuardNotNull(nameof(title));
|
Title = title;
|
||||||
ExecutableName = executableName.GuardNotNull(nameof(executableName));
|
ExecutableName = executableName;
|
||||||
VersionText = versionText.GuardNotNull(nameof(versionText));
|
VersionText = versionText;
|
||||||
Description = description; // can be null
|
Description = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,27 +10,29 @@ namespace CliFx.Attributes
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command name.
|
/// 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>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command description, which is used in help text.
|
/// Command description, which is used in help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandAttribute(string name)
|
public CommandAttribute(string name)
|
||||||
{
|
{
|
||||||
Name = name; // can be null
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
/// Initializes an instance of <see cref="CommandAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandAttribute()
|
public CommandAttribute()
|
||||||
: this(null)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ namespace CliFx.Attributes
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option name.
|
/// 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>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option short name.
|
/// Option short name.
|
||||||
|
/// Either <see cref="Name"/> or <see cref="ShortName"/> must be set.
|
||||||
|
/// All options in a command must have different short names (comparison is case-sensitive).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public char? ShortName { get; }
|
public char? ShortName { get; }
|
||||||
|
|
||||||
@@ -26,20 +30,20 @@ namespace CliFx.Attributes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Option description, which is used in help text.
|
/// Option description, which is used in help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optional environment variable name that will be used as fallback value if no option value is specified.
|
/// Environment variable that will be used as fallback if no option value is specified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string EnvironmentVariableName { get; set; }
|
public string? EnvironmentVariableName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionAttribute(string name, char? shortName)
|
private CommandOptionAttribute(string? name, char? shortName)
|
||||||
{
|
{
|
||||||
Name = name; // can be null
|
Name = name;
|
||||||
ShortName = shortName; // can be null
|
ShortName = shortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -62,7 +66,7 @@ namespace CliFx.Attributes
|
|||||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommandOptionAttribute(char shortName)
|
public CommandOptionAttribute(char shortName)
|
||||||
: this(null, shortName)
|
: this(null, (char?) shortName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,205 +1,166 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CliFx.Domain;
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Internal;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx
|
namespace CliFx
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default implementation of <see cref="ICliApplication"/>.
|
/// Command line application facade.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CliApplication : ICliApplication
|
public class CliApplication
|
||||||
{
|
{
|
||||||
private readonly ApplicationMetadata _metadata;
|
private readonly ApplicationMetadata _metadata;
|
||||||
private readonly ApplicationConfiguration _configuration;
|
private readonly ApplicationConfiguration _configuration;
|
||||||
|
|
||||||
private readonly IConsole _console;
|
private readonly IConsole _console;
|
||||||
private readonly ICommandInputParser _commandInputParser;
|
private readonly ITypeActivator _typeActivator;
|
||||||
private readonly ICommandSchemaResolver _commandSchemaResolver;
|
|
||||||
private readonly ICommandFactory _commandFactory;
|
private readonly HelpTextWriter _helpTextWriter;
|
||||||
private readonly ICommandInitializer _commandInitializer;
|
|
||||||
private readonly IHelpTextRenderer _helpTextRenderer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes an instance of <see cref="CliApplication"/>.
|
/// Initializes an instance of <see cref="CliApplication"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CliApplication(ApplicationMetadata metadata, ApplicationConfiguration configuration,
|
public CliApplication(
|
||||||
IConsole console, ICommandInputParser commandInputParser, ICommandSchemaResolver commandSchemaResolver,
|
ApplicationMetadata metadata, ApplicationConfiguration configuration,
|
||||||
ICommandFactory commandFactory, ICommandInitializer commandInitializer, IHelpTextRenderer helpTextRenderer)
|
IConsole console, ITypeActivator typeActivator)
|
||||||
{
|
{
|
||||||
_metadata = metadata.GuardNotNull(nameof(metadata));
|
_metadata = metadata;
|
||||||
_configuration = configuration.GuardNotNull(nameof(configuration));
|
_configuration = configuration;
|
||||||
|
_console = console;
|
||||||
|
_typeActivator = typeActivator;
|
||||||
|
|
||||||
_console = console.GuardNotNull(nameof(console));
|
_helpTextWriter = new HelpTextWriter(metadata, 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int?> HandleDebugDirectiveAsync(CommandInput commandInput)
|
private async ValueTask<int?> HandleDebugDirectiveAsync(CommandLineInput commandLineInput)
|
||||||
{
|
{
|
||||||
// Debug mode is enabled if it's allowed in the application and it was requested via corresponding directive
|
var isDebugMode = _configuration.IsDebugModeAllowed && commandLineInput.IsDebugDirectiveSpecified;
|
||||||
var isDebugMode = _configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified();
|
|
||||||
|
|
||||||
// If not in debug mode, pass execution to the next handler
|
|
||||||
if (!isDebugMode)
|
if (!isDebugMode)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Inform user which process they need to attach debugger to
|
_console.WithForegroundColor(ConsoleColor.Green, () =>
|
||||||
_console.WithForegroundColor(ConsoleColor.Green,
|
_console.Output.WriteLine($"Attach debugger to PID {Process.GetCurrentProcess().Id} to continue."));
|
||||||
() => _console.Output.WriteLine($"Attach debugger to PID {Process.GetCurrentProcess().Id} to continue."));
|
|
||||||
|
|
||||||
// Wait until debugger is attached
|
|
||||||
while (!Debugger.IsAttached)
|
while (!Debugger.IsAttached)
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
// Debug directive never short-circuits
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? HandlePreviewDirective(CommandInput commandInput)
|
private int? HandlePreviewDirective(ApplicationSchema applicationSchema, CommandLineInput commandLineInput)
|
||||||
{
|
{
|
||||||
// Preview mode is enabled if it's allowed in the application and it was requested via corresponding directive
|
var isPreviewMode = _configuration.IsPreviewModeAllowed && commandLineInput.IsPreviewDirectiveSpecified;
|
||||||
var isPreviewMode = _configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified();
|
|
||||||
|
|
||||||
// If not in preview mode, pass execution to the next handler
|
|
||||||
if (!isPreviewMode)
|
if (!isPreviewMode)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Render command name
|
var commandSchema = applicationSchema.TryFindCommand(commandLineInput, out var argumentOffset);
|
||||||
_console.Output.WriteLine($"Command name: {commandInput.CommandName}");
|
|
||||||
_console.Output.WriteLine();
|
|
||||||
|
|
||||||
// Render directives
|
_console.Output.WriteLine("Parser preview:");
|
||||||
_console.Output.WriteLine("Directives:");
|
|
||||||
foreach (var directive in commandInput.Directives)
|
// Command name
|
||||||
|
if (commandSchema != null && argumentOffset > 0)
|
||||||
{
|
{
|
||||||
_console.Output.Write(" ");
|
_console.WithForegroundColor(ConsoleColor.Cyan, () =>
|
||||||
_console.Output.WriteLine(directive);
|
_console.Output.Write(commandSchema.Name));
|
||||||
|
|
||||||
|
_console.Output.Write(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Margin
|
// Parameters
|
||||||
_console.Output.WriteLine();
|
foreach (var parameter in commandLineInput.UnboundArguments.Skip(argumentOffset))
|
||||||
|
|
||||||
// Render options
|
|
||||||
_console.Output.WriteLine("Options:");
|
|
||||||
foreach (var option in commandInput.Options)
|
|
||||||
{
|
{
|
||||||
_console.Output.Write(" ");
|
_console.Output.Write('<');
|
||||||
_console.Output.WriteLine(option);
|
|
||||||
|
_console.WithForegroundColor(ConsoleColor.White, () =>
|
||||||
|
_console.Output.Write(parameter));
|
||||||
|
|
||||||
|
_console.Output.Write('>');
|
||||||
|
_console.Output.Write(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short-circuit with exit code 0
|
// 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? HandleVersionOption(CommandInput commandInput)
|
private int? HandleVersionOption(CommandLineInput commandLineInput)
|
||||||
{
|
{
|
||||||
// Version should be rendered if it was requested on a default command
|
// Version option is available only on the default command (i.e. when arguments are not specified)
|
||||||
var shouldRenderVersion = !commandInput.IsCommandSpecified() && commandInput.IsVersionOptionSpecified();
|
var shouldRenderVersion = !commandLineInput.UnboundArguments.Any() && commandLineInput.IsVersionOptionSpecified;
|
||||||
|
|
||||||
// If shouldn't render version, pass execution to the next handler
|
|
||||||
if (!shouldRenderVersion)
|
if (!shouldRenderVersion)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Render version text
|
|
||||||
_console.Output.WriteLine(_metadata.VersionText);
|
_console.Output.WriteLine(_metadata.VersionText);
|
||||||
|
|
||||||
// Short-circuit with exit code 0
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? HandleHelpOption(CommandInput commandInput,
|
private int? HandleHelpOption(ApplicationSchema applicationSchema, CommandLineInput commandLineInput)
|
||||||
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema targetCommandSchema)
|
|
||||||
{
|
{
|
||||||
// Help should be rendered if it was requested, or when executing a command which isn't defined
|
// Help is rendered either when it's requested or when the user provides no arguments and there is no default command
|
||||||
var shouldRenderHelp = commandInput.IsHelpOptionSpecified() || targetCommandSchema == null;
|
var shouldRenderHelp =
|
||||||
|
commandLineInput.IsHelpOptionSpecified ||
|
||||||
|
!applicationSchema.Commands.Any(c => c.IsDefault) && !commandLineInput.UnboundArguments.Any() && !commandLineInput.Options.Any();
|
||||||
|
|
||||||
// If shouldn't render help, pass execution to the next handler
|
|
||||||
if (!shouldRenderHelp)
|
if (!shouldRenderHelp)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Keep track whether there was an error in the input
|
// Get the command schema that matches the input or use a dummy default command as a fallback
|
||||||
var isError = false;
|
var commandSchema =
|
||||||
|
applicationSchema.TryFindCommand(commandLineInput) ??
|
||||||
|
CommandSchema.StubDefaultCommand;
|
||||||
|
|
||||||
// If target command isn't defined, find its contextual replacement
|
_helpTextWriter.Write(applicationSchema, commandSchema);
|
||||||
if (targetCommandSchema == null)
|
|
||||||
{
|
|
||||||
// If command was specified, inform the user that it's not defined
|
|
||||||
if (commandInput.IsCommandSpecified())
|
|
||||||
{
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Red,
|
|
||||||
() => _console.Error.WriteLine($"Specified command [{commandInput.CommandName}] is not defined."));
|
|
||||||
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace target command with closest parent of specified command
|
|
||||||
targetCommandSchema = availableCommandSchemas.FindParent(commandInput.CommandName);
|
|
||||||
|
|
||||||
// If there's no parent, replace with stub default command
|
|
||||||
if (targetCommandSchema == null)
|
|
||||||
{
|
|
||||||
targetCommandSchema = CommandSchema.StubDefaultCommand;
|
|
||||||
availableCommandSchemas = availableCommandSchemas.Concat(CommandSchema.StubDefaultCommand).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build help text source
|
|
||||||
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, targetCommandSchema);
|
|
||||||
|
|
||||||
// Render help text
|
|
||||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
|
||||||
|
|
||||||
// Short-circuit with appropriate exit code
|
|
||||||
return isError ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> HandleCommandExecutionAsync(CommandInput commandInput, CommandSchema targetCommandSchema)
|
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Finish the chain with exit code 0
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
private async ValueTask<int> HandleCommandExecutionAsync(
|
||||||
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
ApplicationSchema applicationSchema,
|
||||||
|
CommandLineInput commandLineInput,
|
||||||
|
IReadOnlyDictionary<string, string> environmentVariables)
|
||||||
{
|
{
|
||||||
commandLineArguments.GuardNotNull(nameof(commandLineArguments));
|
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
|
try
|
||||||
{
|
{
|
||||||
// Parse command input from arguments
|
var applicationSchema = ApplicationSchema.Resolve(_configuration.CommandTypes);
|
||||||
var commandInput = _commandInputParser.ParseCommandInput(commandLineArguments);
|
var commandLineInput = CommandLineInput.Parse(commandLineArguments);
|
||||||
|
|
||||||
// Get schemas for all available command types
|
|
||||||
var availableCommandSchemas = _commandSchemaResolver.GetCommandSchemas(_configuration.CommandTypes);
|
|
||||||
|
|
||||||
// Find command schema matching the name specified in the input
|
|
||||||
var targetCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
|
|
||||||
|
|
||||||
// Chain handlers until the first one that produces an exit code
|
|
||||||
return
|
return
|
||||||
await HandleDebugDirectiveAsync(commandInput) ??
|
await HandleDebugDirectiveAsync(commandLineInput) ??
|
||||||
HandlePreviewDirective(commandInput) ??
|
HandlePreviewDirective(applicationSchema, commandLineInput) ??
|
||||||
HandleVersionOption(commandInput) ??
|
HandleVersionOption(commandLineInput) ??
|
||||||
HandleHelpOption(commandInput, availableCommandSchemas, targetCommandSchema) ??
|
HandleHelpOption(applicationSchema, commandLineInput) ??
|
||||||
await HandleCommandExecutionAsync(commandInput, targetCommandSchema);
|
await HandleCommandExecutionAsync(applicationSchema, commandLineInput, environmentVariables);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -207,25 +168,42 @@ namespace CliFx
|
|||||||
// Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions.
|
// Doing this also gets rid of the annoying Windows troubleshooting dialog that shows up on unhandled exceptions.
|
||||||
|
|
||||||
// Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException
|
// Prefer showing message without stack trace on exceptions coming from CliFx or on CommandException
|
||||||
if (!ex.Message.IsNullOrWhiteSpace() && (ex is CliFxException || ex is CommandException))
|
var errorMessage = !string.IsNullOrWhiteSpace(ex.Message) && (ex is CliFxException || ex is CommandException)
|
||||||
{
|
? ex.Message
|
||||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex.Message));
|
: ex.ToString();
|
||||||
|
|
||||||
|
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(errorMessage));
|
||||||
|
|
||||||
|
return ex is CommandException commandException
|
||||||
|
? commandException.ExitCode
|
||||||
|
: ex.HResult;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Error.WriteLine(ex));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return exit code if it was specified via CommandException
|
/// <summary>
|
||||||
if (ex is CommandException commandException)
|
/// 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)
|
||||||
{
|
{
|
||||||
return commandException.ExitCode;
|
var environmentVariables = Environment.GetEnvironmentVariables()
|
||||||
|
.Cast<DictionaryEntry>()
|
||||||
|
.ToDictionary(e => (string) e.Key, e => (string) e.Value, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return await RunAsync(commandLineArguments, environmentVariables);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the application and returns the exit code.
|
||||||
|
/// Command line arguments and environment variables are retrieved automatically.
|
||||||
|
/// </summary>
|
||||||
|
public async ValueTask<int> RunAsync()
|
||||||
{
|
{
|
||||||
return ex.HResult;
|
var commandLineArguments = Environment.GetCommandLineArgs()
|
||||||
}
|
.Skip(1)
|
||||||
}
|
.ToArray();
|
||||||
|
|
||||||
|
return await RunAsync(commandLineArguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,146 +3,171 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using CliFx.Attributes;
|
using CliFx.Domain;
|
||||||
using CliFx.Internal;
|
|
||||||
using CliFx.Models;
|
|
||||||
using CliFx.Services;
|
|
||||||
|
|
||||||
namespace CliFx
|
namespace CliFx
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default implementation of <see cref="ICliApplicationBuilder"/>.
|
/// Builds an instance of <see cref="CliApplication"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CliApplicationBuilder : ICliApplicationBuilder
|
public partial class CliApplicationBuilder
|
||||||
{
|
{
|
||||||
private readonly HashSet<Type> _commandTypes = new HashSet<Type>();
|
private readonly HashSet<Type> _commandTypes = new HashSet<Type>();
|
||||||
|
|
||||||
private bool _isDebugModeAllowed = true;
|
private bool _isDebugModeAllowed = true;
|
||||||
private bool _isPreviewModeAllowed = true;
|
private bool _isPreviewModeAllowed = true;
|
||||||
private string _title;
|
private string? _title;
|
||||||
private string _executableName;
|
private string? _executableName;
|
||||||
private string _versionText;
|
private string? _versionText;
|
||||||
private string _description;
|
private string? _description;
|
||||||
private IConsole _console;
|
private IConsole? _console;
|
||||||
private ICommandFactory _commandFactory;
|
private ITypeActivator? _typeActivator;
|
||||||
private ICommandOptionInputConverter _commandOptionInputConverter;
|
|
||||||
private IEnvironmentVariablesProvider _environmentVariablesProvider;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder AddCommand(Type commandType)
|
/// Adds a command of specified type to the application.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder AddCommand(Type commandType)
|
||||||
{
|
{
|
||||||
commandType.GuardNotNull(nameof(commandType));
|
|
||||||
|
|
||||||
_commandTypes.Add(commandType);
|
_commandTypes.Add(commandType);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder AddCommandsFrom(Assembly commandAssembly)
|
/// 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)))
|
|
||||||
.Where(t => t.IsDefined(typeof(CommandAttribute)))
|
|
||||||
.Where(t => !t.IsAbstract && !t.IsInterface);
|
|
||||||
|
|
||||||
foreach (var commandType in commandTypes)
|
foreach (var commandType in commandTypes)
|
||||||
AddCommand(commandType);
|
AddCommand(commandType);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder AllowDebugMode(bool isAllowed = true)
|
/// Adds commands from the specified assembly to the application.
|
||||||
|
/// Only adds public valid command types.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder AddCommandsFrom(Assembly commandAssembly)
|
||||||
|
{
|
||||||
|
foreach (var commandType in commandAssembly.ExportedTypes.Where(CommandSchema.IsCommandType))
|
||||||
|
AddCommand(commandType);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds commands from the specified assemblies to the application.
|
||||||
|
/// Only adds public valid command types.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder AddCommandsFrom(IEnumerable<Assembly> commandAssemblies)
|
||||||
|
{
|
||||||
|
foreach (var commandAssembly in commandAssemblies)
|
||||||
|
AddCommandsFrom(commandAssembly);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds commands from the calling assembly to the application.
|
||||||
|
/// Only adds public valid command types.
|
||||||
|
/// </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)
|
||||||
{
|
{
|
||||||
_isDebugModeAllowed = isAllowed;
|
_isDebugModeAllowed = isAllowed;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder AllowPreviewMode(bool isAllowed = true)
|
/// Specifies whether preview mode (enabled with [preview] directive) is allowed in the application.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder AllowPreviewMode(bool isAllowed = true)
|
||||||
{
|
{
|
||||||
_isPreviewModeAllowed = isAllowed;
|
_isPreviewModeAllowed = isAllowed;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseTitle(string title)
|
/// Sets application title, which appears in the help text.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder UseTitle(string title)
|
||||||
{
|
{
|
||||||
_title = title.GuardNotNull(nameof(title));
|
_title = title;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseExecutableName(string executableName)
|
/// Sets application executable name, which appears in the help text.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder UseExecutableName(string executableName)
|
||||||
{
|
{
|
||||||
_executableName = executableName.GuardNotNull(nameof(executableName));
|
_executableName = executableName;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseVersionText(string versionText)
|
/// Sets application version text, which appears in the help text and when the user requests version information.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder UseVersionText(string versionText)
|
||||||
{
|
{
|
||||||
_versionText = versionText.GuardNotNull(nameof(versionText));
|
_versionText = versionText;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseDescription(string description)
|
/// Sets application description, which appears in the help text.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder UseDescription(string? description)
|
||||||
{
|
{
|
||||||
_description = description; // can be null
|
_description = description;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseConsole(IConsole console)
|
/// Configures the application to use the specified implementation of <see cref="IConsole"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder UseConsole(IConsole console)
|
||||||
{
|
{
|
||||||
_console = console.GuardNotNull(nameof(console));
|
_console = console;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseCommandFactory(ICommandFactory factory)
|
/// Configures the application to use the specified implementation of <see cref="ITypeActivator"/>.
|
||||||
|
/// </summary>
|
||||||
|
public CliApplicationBuilder UseTypeActivator(ITypeActivator typeActivator)
|
||||||
{
|
{
|
||||||
_commandFactory = factory.GuardNotNull(nameof(factory));
|
_typeActivator = typeActivator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter)
|
/// Configures the application to use the specified function for activating types.
|
||||||
{
|
/// </summary>
|
||||||
_commandOptionInputConverter = converter.GuardNotNull(nameof(converter));
|
public CliApplicationBuilder UseTypeActivator(Func<Type, object> typeActivator) =>
|
||||||
return this;
|
UseTypeActivator(new DelegateTypeActivator(typeActivator));
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider)
|
/// 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()
|
||||||
{
|
{
|
||||||
_environmentVariablesProvider = environmentVariablesProvider.GuardNotNull(nameof(environmentVariablesProvider));
|
_title ??= GetDefaultTitle() ?? "App";
|
||||||
return this;
|
_executableName ??= GetDefaultExecutableName() ?? "app";
|
||||||
}
|
_versionText ??= GetDefaultVersionText() ?? "v1.0";
|
||||||
|
_console ??= new SystemConsole();
|
||||||
|
_typeActivator ??= new DefaultTypeActivator();
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ICliApplication Build()
|
|
||||||
{
|
|
||||||
// Use defaults for required parameters that were not configured
|
|
||||||
_title = _title ?? GetDefaultTitle() ?? "App";
|
|
||||||
_executableName = _executableName ?? GetDefaultExecutableName() ?? "app";
|
|
||||||
_versionText = _versionText ?? GetDefaultVersionText() ?? "v1.0";
|
|
||||||
_console = _console ?? new SystemConsole();
|
|
||||||
_commandFactory = _commandFactory ?? new CommandFactory();
|
|
||||||
_commandOptionInputConverter = _commandOptionInputConverter ?? new CommandOptionInputConverter();
|
|
||||||
_environmentVariablesProvider = _environmentVariablesProvider ?? new EnvironmentVariablesProvider();
|
|
||||||
|
|
||||||
// Project parameters to expected types
|
|
||||||
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
|
var metadata = new ApplicationMetadata(_title, _executableName, _versionText, _description);
|
||||||
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
|
var configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
|
||||||
|
|
||||||
return new CliApplication(metadata, configuration,
|
return new CliApplication(metadata, configuration, _console, _typeActivator);
|
||||||
_console, new CommandInputParser(_environmentVariablesProvider), new CommandSchemaResolver(),
|
|
||||||
_commandFactory, new CommandInitializer(_commandOptionInputConverter, new EnvironmentVariablesParser()), new HelpTextRenderer());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,9 +178,9 @@ namespace CliFx
|
|||||||
// Entry assembly is null in tests
|
// Entry assembly is null in tests
|
||||||
private static Assembly EntryAssembly => LazyEntryAssembly.Value;
|
private static Assembly EntryAssembly => LazyEntryAssembly.Value;
|
||||||
|
|
||||||
private static string GetDefaultTitle() => EntryAssembly?.GetName().Name;
|
private static string? GetDefaultTitle() => EntryAssembly?.GetName().Name;
|
||||||
|
|
||||||
private static string GetDefaultExecutableName()
|
private static string? GetDefaultExecutableName()
|
||||||
{
|
{
|
||||||
var entryAssemblyLocation = EntryAssembly?.Location;
|
var entryAssemblyLocation = EntryAssembly?.Location;
|
||||||
|
|
||||||
@@ -169,6 +194,9 @@ namespace CliFx
|
|||||||
return Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
return Path.GetFileNameWithoutExtension(entryAssemblyLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDefaultVersionText() => EntryAssembly != null ? $"v{EntryAssembly.GetName().Version}" : null;
|
private static string? GetDefaultVersionText() =>
|
||||||
|
EntryAssembly != null
|
||||||
|
? $"v{EntryAssembly.GetName().Version}"
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,44 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../CliFx.props" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.1;netstandard2.0;net45</TargetFrameworks>
|
||||||
<Version>0.0.7</Version>
|
|
||||||
<Company>Tyrrrz</Company>
|
|
||||||
<Authors>$(Company)</Authors>
|
<Authors>$(Company)</Authors>
|
||||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
|
||||||
<Description>Declarative framework for CLI applications</Description>
|
<Description>Declarative framework for CLI applications</Description>
|
||||||
<PackageTags>command line executable interface framework parser arguments net core</PackageTags>
|
<PackageTags>command line executable interface framework parser arguments net core</PackageTags>
|
||||||
<PackageProjectUrl>https://github.com/Tyrrrz/CliFx</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Tyrrrz/CliFx</PackageProjectUrl>
|
||||||
<PackageReleaseNotes>https://github.com/Tyrrrz/CliFx/blob/master/Changelog.md</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/Tyrrrz/CliFx/blob/master/Changelog.md</PackageReleaseNotes>
|
||||||
<PackageIcon>favicon.png</PackageIcon>
|
<PackageIcon>favicon.png</PackageIcon>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<RepositoryUrl>https://github.com/Tyrrrz/CliFx</RepositoryUrl>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
|
<PublishRepositoryUrl>True</PublishRepositoryUrl>
|
||||||
|
<EmbedUntrackedSources>True</EmbedUntrackedSources>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||||
|
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||||
|
<_Parameter1>$(AssemblyName).Analyzers</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="../favicon.png" Pack="True" PackagePath="" />
|
<None Include="../favicon.png" Pack="True" PackagePath="" />
|
||||||
</ItemGroup>
|
</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.1" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net45'">
|
||||||
|
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user