mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9564cd5d30 | ||
|
|
ed458c3980 | ||
|
|
25538f99db | ||
|
|
36436e7a4b | ||
|
|
a6070332c9 | ||
|
|
25cbfdb4b8 | ||
|
|
d1b5107c2c | ||
|
|
03873d63cd | ||
|
|
89aba39964 | ||
|
|
ab57a103d1 | ||
|
|
d0b2ebc061 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -143,6 +143,7 @@ _TeamCity*
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
.ncrunchsolution
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
|
||||
BIN
.screenshots/help.png
Normal file
BIN
.screenshots/help.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -19,16 +19,17 @@ namespace CliFx.Benchmarks
|
||||
[Benchmark(Description = "McMaster.Extensions.CommandLineUtils")]
|
||||
public int ExecuteWithMcMaster() => McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute<McMasterCommand>(Arguments);
|
||||
|
||||
// Skipped because this benchmark freezes after a couple of iterations
|
||||
// Probably wasn't designed to run multiple times in single process execution
|
||||
//[Benchmark(Description = "CommandLineParser")]
|
||||
[Benchmark(Description = "CommandLineParser")]
|
||||
public void ExecuteWithCommandLineParser()
|
||||
{
|
||||
var parsed = CommandLine.Parser.Default.ParseArguments(Arguments, typeof(CommandLineParserCommand));
|
||||
var parsed = new CommandLine.Parser().ParseArguments(Arguments, typeof(CommandLineParserCommand));
|
||||
CommandLine.ParserResultExtensions.WithParsed<CommandLineParserCommand>(parsed, c => c.Execute());
|
||||
}
|
||||
|
||||
[Benchmark(Description = "PowerArgs")]
|
||||
public void ExecuteWithPowerArgs() => PowerArgs.Args.InvokeMain<PowerArgsCommand>(Arguments);
|
||||
|
||||
[Benchmark(Description = "Clipr")]
|
||||
public void ExecuteWithClipr() => clipr.CliParser.Parse<CliprCommand>(Arguments).Execute();
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
|
||||
<PackageReference Include="clipr" Version="1.6.1" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.6.0" />
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
|
||||
<PackageReference Include="PowerArgs" Version="3.6.0" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -16,6 +17,6 @@ namespace CliFx.Benchmarks.Commands
|
||||
[CommandOption("bool", 'b')]
|
||||
public bool BoolOption { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
20
CliFx.Benchmarks/Commands/CliprCommand.cs
Normal file
20
CliFx.Benchmarks/Commands/CliprCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using clipr;
|
||||
|
||||
namespace CliFx.Benchmarks.Commands
|
||||
{
|
||||
public class CliprCommand
|
||||
{
|
||||
[NamedArgument('s', "str")]
|
||||
public string StrOption { get; set; }
|
||||
|
||||
[NamedArgument('i', "int")]
|
||||
public int IntOption { get; set; }
|
||||
|
||||
[NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)]
|
||||
public bool BoolOption { get; set; }
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Demo.Internal;
|
||||
@@ -31,7 +32,7 @@ namespace CliFx.Demo.Commands
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
// To make the demo simpler, we will just generate random publish date and ISBN if they were not set
|
||||
if (Published == default)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Demo.Internal;
|
||||
using CliFx.Demo.Services;
|
||||
@@ -20,7 +21,7 @@ namespace CliFx.Demo.Commands
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
var book = _libraryService.GetBook(Title);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Demo.Internal;
|
||||
using CliFx.Demo.Services;
|
||||
@@ -16,7 +17,7 @@ namespace CliFx.Demo.Commands
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
var library = _libraryService.GetLibrary();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Demo.Services;
|
||||
using CliFx.Exceptions;
|
||||
@@ -19,7 +20,7 @@ namespace CliFx.Demo.Commands
|
||||
_libraryService = libraryService;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
var book = _libraryService.GetBook(Title);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using CliFx.Services;
|
||||
using CliFx.Tests.Stubs;
|
||||
using CliFx.Tests.TestCommands;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
@@ -31,6 +32,8 @@ namespace CliFx.Tests
|
||||
.UseDescription("test")
|
||||
.UseConsole(new VirtualConsole(TextWriter.Null))
|
||||
.UseCommandFactory(schema => (ICommand)Activator.CreateInstance(schema.Type))
|
||||
.UseCommandOptionInputConverter(new CommandOptionInputConverter())
|
||||
.UseEnvironmentVariablesProvider(new EnvironmentVariablesProviderStub())
|
||||
.Build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Services;
|
||||
using CliFx.Tests.Stubs;
|
||||
using CliFx.Tests.TestCommands;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
@@ -173,11 +174,13 @@ namespace CliFx.Tests
|
||||
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
|
||||
@@ -203,10 +206,12 @@ namespace CliFx.Tests
|
||||
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();
|
||||
|
||||
@@ -226,5 +231,31 @@ namespace CliFx.Tests
|
||||
stderr.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RunAsync_Cancellation_Test()
|
||||
{
|
||||
// Arrange
|
||||
using (var stdoutStream = new StringWriter())
|
||||
{
|
||||
var console = new VirtualConsole(stdoutStream);
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommand(typeof(CancellableCommand))
|
||||
.UseConsole(console)
|
||||
.Build();
|
||||
var args = new[] { "cancel" };
|
||||
|
||||
// Act
|
||||
var runTask = application.RunAsync(args);
|
||||
console.Cancel();
|
||||
var exitCode = await runTask.ConfigureAwait(false);
|
||||
var stdOut = stdoutStream.ToString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(-2146233029);
|
||||
stdOut.Should().Be("Printed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
<CoverletOutputFormat>opencover</CoverletOutputFormat>
|
||||
<CoverletOutput>bin/$(Configuration)/Coverage.xml</CoverletOutput>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
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 FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using CliFx.Tests.Stubs;
|
||||
|
||||
namespace CliFx.Tests.Services
|
||||
{
|
||||
@@ -71,6 +72,42 @@ namespace CliFx.Tests.Services
|
||||
}),
|
||||
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()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using CliFx.Tests.Stubs;
|
||||
|
||||
namespace CliFx.Tests.Services
|
||||
{
|
||||
@@ -11,14 +12,15 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
private static IEnumerable<TestCaseData> GetTestCases_ParseCommandInput()
|
||||
{
|
||||
yield return new TestCaseData(new string[0], CommandInput.Empty);
|
||||
yield return new TestCaseData(new string[0], CommandInput.Empty, new EmptyEnvironmentVariablesProviderStub());
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] { "--option", "value" },
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -27,7 +29,8 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("option1", "value1"),
|
||||
new CommandOptionInput("option2", "value2")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -35,7 +38,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -43,7 +47,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("option", new[] {"value1", "value2"})
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -51,7 +56,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", "value")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -60,7 +66,8 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("a", "value1"),
|
||||
new CommandOptionInput("b", "value2")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -68,7 +75,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -76,7 +84,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("a", new[] {"value1", "value2"})
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -85,7 +94,8 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("option1", "value1"),
|
||||
new CommandOptionInput("b", "value2")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -93,7 +103,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("switch")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -102,7 +113,8 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("switch1"),
|
||||
new CommandOptionInput("switch2")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -110,7 +122,8 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput(new[]
|
||||
{
|
||||
new CommandOptionInput("s")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -119,7 +132,8 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -128,7 +142,8 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -137,12 +152,14 @@ namespace CliFx.Tests.Services
|
||||
{
|
||||
new CommandOptionInput("a"),
|
||||
new CommandOptionInput("b", "value")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] { "command" },
|
||||
new CommandInput("command")
|
||||
new CommandInput("command"),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -150,12 +167,14 @@ namespace CliFx.Tests.Services
|
||||
new CommandInput("command", new[]
|
||||
{
|
||||
new CommandOptionInput("option", "value")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] { "long", "command", "name" },
|
||||
new CommandInput("long command name")
|
||||
new CommandInput("long command name"),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -163,21 +182,24 @@ namespace CliFx.Tests.Services
|
||||
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 CommandOptionInput[0]),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
new[] { "[debug]", "[preview]" },
|
||||
new CommandInput(null,
|
||||
new[] { "debug", "preview" },
|
||||
new CommandOptionInput[0])
|
||||
new CommandOptionInput[0]),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -187,7 +209,8 @@ namespace CliFx.Tests.Services
|
||||
new[]
|
||||
{
|
||||
new CommandOptionInput("o", "value")
|
||||
})
|
||||
}),
|
||||
new EmptyEnvironmentVariablesProviderStub()
|
||||
);
|
||||
|
||||
yield return new TestCaseData(
|
||||
@@ -197,17 +220,30 @@ namespace CliFx.Tests.Services
|
||||
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)
|
||||
CommandInput expectedCommandInput, IEnvironmentVariablesProvider environmentVariablesProvider)
|
||||
{
|
||||
// Arrange
|
||||
var parser = new CommandInputParser();
|
||||
var parser = new CommandInputParser(environmentVariablesProvider);
|
||||
|
||||
// Act
|
||||
var commandInput = parser.ParseCommandInput(commandLineArguments);
|
||||
|
||||
@@ -214,6 +214,12 @@ namespace CliFx.Tests.Services
|
||||
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[]),
|
||||
@@ -270,6 +276,16 @@ namespace CliFx.Tests.Services
|
||||
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)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Models;
|
||||
using CliFx.Services;
|
||||
using CliFx.Tests.TestCommands;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests.Services
|
||||
{
|
||||
@@ -15,25 +15,32 @@ namespace CliFx.Tests.Services
|
||||
private static IEnumerable<TestCaseData> GetTestCases_GetCommandSchemas()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new[] {typeof(DivideCommand), typeof(ConcatCommand)},
|
||||
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."),
|
||||
"dividend", 'D', true, "The number to divide.", null),
|
||||
new CommandOptionSchema(typeof(DivideCommand).GetProperty(nameof(DivideCommand.Divisor)),
|
||||
"divisor", 'd', true, "The number to divide by.")
|
||||
"divisor", 'd', true, "The number to divide by.", null)
|
||||
}),
|
||||
new CommandSchema(typeof(ConcatCommand), "concat", "Concatenate strings.",
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Inputs)),
|
||||
null, 'i', true, "Input strings."),
|
||||
null, 'i', true, "Input strings.", null),
|
||||
new CommandOptionSchema(typeof(ConcatCommand).GetProperty(nameof(ConcatCommand.Separator)),
|
||||
null, 's', false, "String separator.")
|
||||
})
|
||||
null, 's', false, "String separator.", null)
|
||||
}),
|
||||
new CommandSchema(typeof(EnvironmentVariableCommand), null, "Reads option values from environment variables.",
|
||||
new[]
|
||||
{
|
||||
new CommandOptionSchema(typeof(EnvironmentVariableCommand).GetProperty(nameof(EnvironmentVariableCommand.Option)),
|
||||
"opt", null, false, null, "ENV_SINGLE_VALUE")
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
10
CliFx.Tests/Stubs/EmptyEnvironmentVariablesProviderStub.cs
Normal file
10
CliFx.Tests/Stubs/EmptyEnvironmentVariablesProviderStub.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests.Stubs
|
||||
{
|
||||
public class EmptyEnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
|
||||
{
|
||||
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
18
CliFx.Tests/Stubs/EnvironmentVariablesProviderStub.cs
Normal file
18
CliFx.Tests/Stubs/EnvironmentVariablesProviderStub.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx.Tests.Stubs
|
||||
{
|
||||
public class EnvironmentVariablesProviderStub : IEnvironmentVariablesProvider
|
||||
{
|
||||
public static readonly Dictionary<string, string> EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["ENV_SINGLE_VALUE"] = "A",
|
||||
["ENV_MULTIPLE_VALUES"] = $"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}",
|
||||
["ENV_ESCAPED_MULTIPLE_VALUES"] = $"\"A{Path.PathSeparator}B{Path.PathSeparator}C{Path.PathSeparator}\""
|
||||
};
|
||||
|
||||
public IReadOnlyDictionary<string, string> GetEnvironmentVariables() => EnvironmentVariables;
|
||||
}
|
||||
}
|
||||
23
CliFx.Tests/TestCommands/CancellableCommand.cs
Normal file
23
CliFx.Tests/TestCommands/CancellableCommand.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
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, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
console.Output.WriteLine("Printed");
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
console.Output.WriteLine("Never printed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx.Services;
|
||||
@@ -14,6 +15,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption("msg", 'm')]
|
||||
public string Message { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => throw new CommandException(Message, ExitCode);
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => throw new CommandException(Message, ExitCode);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
@@ -14,7 +15,7 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption('s', Description = "String separator.")]
|
||||
public string Separator { get; set; } = "";
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
console.Output.WriteLine(string.Join(Separator, Inputs));
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -16,7 +17,7 @@ namespace CliFx.Tests.TestCommands
|
||||
// This property should be ignored by resolver
|
||||
public bool NotAnOption { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
console.Output.WriteLine(Dividend / Divisor);
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -13,6 +14,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption("fruits")]
|
||||
public string Oranges { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -13,6 +14,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption('f')]
|
||||
public string Oranges { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
16
CliFx.Tests/TestCommands/EnvironmentVariableCommand.cs
Normal file
16
CliFx.Tests/TestCommands/EnvironmentVariableCommand.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
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, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
@@ -11,6 +12,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption("msg", 'm')]
|
||||
public string Message { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => throw new Exception(Message);
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => throw new Exception(Message);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace CliFx.Tests.TestCommands
|
||||
[Command]
|
||||
public class HelloWorldDefaultCommand : ICommand
|
||||
{
|
||||
public Task ExecuteAsync(IConsole console)
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
console.Output.WriteLine("Hello world.");
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -13,6 +14,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption("option-b", 'b', Description = "OptionB description.")]
|
||||
public string OptionB { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -13,6 +14,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption("option-d", 'd', Description = "OptionD description.")]
|
||||
public string OptionD { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Services;
|
||||
|
||||
@@ -10,6 +11,6 @@ namespace CliFx.Tests.TestCommands
|
||||
[CommandOption("option-e", 'e', Description = "OptionE description.")]
|
||||
public string OptionE { get; set; }
|
||||
|
||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
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;
|
||||
public Task ExecuteAsync(IConsole console, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,11 @@ namespace CliFx.Attributes
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional environment variable name that will be used as fallback value if no option value is specified.
|
||||
/// </summary>
|
||||
public string EnvironmentVariableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandOptionAttribute"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace CliFx
|
||||
_commandInitializer.InitializeCommand(command, targetCommandSchema, commandInput);
|
||||
|
||||
// Execute command
|
||||
await command.ExecuteAsync(_console);
|
||||
await command.ExecuteAsync(_console, _console.CancellationToken);
|
||||
|
||||
// Finish the chain with exit code 0
|
||||
return 0;
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace CliFx
|
||||
private string _description;
|
||||
private IConsole _console;
|
||||
private ICommandFactory _commandFactory;
|
||||
private ICommandOptionInputConverter _commandOptionInputConverter;
|
||||
private IEnvironmentVariablesProvider _environmentVariablesProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder AddCommand(Type commandType)
|
||||
@@ -108,6 +110,20 @@ namespace CliFx
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter)
|
||||
{
|
||||
_commandOptionInputConverter = converter.GuardNotNull(nameof(converter));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider)
|
||||
{
|
||||
_environmentVariablesProvider = environmentVariablesProvider.GuardNotNull(nameof(environmentVariablesProvider));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICliApplication Build()
|
||||
{
|
||||
@@ -117,14 +133,16 @@ namespace CliFx
|
||||
_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 configuration = new ApplicationConfiguration(_commandTypes.ToArray(), _isDebugModeAllowed, _isPreviewModeAllowed);
|
||||
|
||||
return new CliApplication(metadata, configuration,
|
||||
_console, new CommandInputParser(), new CommandSchemaResolver(),
|
||||
_commandFactory, new CommandInitializer(), new HelpTextRenderer());
|
||||
_console, new CommandInputParser(_environmentVariablesProvider), new CommandSchemaResolver(),
|
||||
_commandFactory, new CommandInitializer(_commandOptionInputConverter, new EnvironmentVariablesParser()), new HelpTextRenderer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.0.4</Version>
|
||||
<Version>0.0.6</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<Authors>$(Company)</Authors>
|
||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
||||
@@ -11,7 +10,7 @@
|
||||
<PackageTags>command line executable interface framework parser arguments net core</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/Tyrrrz/CliFx</PackageProjectUrl>
|
||||
<PackageReleaseNotes>https://github.com/Tyrrrz/CliFx/blob/master/Changelog.md</PackageReleaseNotes>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/Tyrrrz/CliFx/master/favicon.png</PackageIconUrl>
|
||||
<PackageIcon>favicon.png</PackageIcon>
|
||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/Tyrrrz/CliFx</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
@@ -20,4 +19,8 @@
|
||||
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../favicon.png" Pack="True" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -59,6 +59,16 @@ namespace CliFx
|
||||
/// </summary>
|
||||
ICliApplicationBuilder UseCommandFactory(ICommandFactory factory);
|
||||
|
||||
/// <summary>
|
||||
/// Configures application to use specified implementation of <see cref="ICommandOptionInputConverter"/>.
|
||||
/// </summary>
|
||||
ICliApplicationBuilder UseCommandOptionInputConverter(ICommandOptionInputConverter converter);
|
||||
|
||||
/// <summary>
|
||||
/// Configures application to use specified implementation of <see cref="IEnvironmentVariablesProvider"/>.
|
||||
/// </summary>
|
||||
ICliApplicationBuilder UseEnvironmentVariablesProvider(IEnvironmentVariablesProvider environmentVariablesProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="ICliApplication"/> using configured parameters.
|
||||
/// Default values are used in place of parameters that were not specified.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Services;
|
||||
|
||||
namespace CliFx
|
||||
@@ -12,6 +13,6 @@ namespace CliFx
|
||||
/// Executes command using specified implementation of <see cref="IConsole"/>.
|
||||
/// This method is called when the command is invoked by a user through command line interface.
|
||||
/// </summary>
|
||||
Task ExecuteAsync(IConsole console);
|
||||
Task ExecuteAsync(IConsole console, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,13 @@ namespace CliFx.Internal
|
||||
|
||||
public static bool Implements(this Type type, Type interfaceType) => type.GetInterfaces().Contains(interfaceType);
|
||||
|
||||
public static Type GetNullableUnderlyingType(this Type type) => Nullable.GetUnderlyingType(type);
|
||||
|
||||
public static Type GetEnumerableUnderlyingType(this Type type)
|
||||
{
|
||||
if (type.IsPrimitive)
|
||||
return null;
|
||||
|
||||
if (type == typeof(IEnumerable))
|
||||
return typeof(object);
|
||||
|
||||
|
||||
@@ -25,14 +25,36 @@ namespace CliFx.Models
|
||||
/// </summary>
|
||||
public IReadOnlyList<CommandOptionInput> Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Environment variables available when the command was parsed
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string> EnvironmentVariables { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||
/// </summary>
|
||||
public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options)
|
||||
public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options, IReadOnlyDictionary<string, string> environmentVariables)
|
||||
{
|
||||
CommandName = commandName; // can be null
|
||||
Directives = directives.GuardNotNull(nameof(directives));
|
||||
Options = options.GuardNotNull(nameof(options));
|
||||
EnvironmentVariables = environmentVariables.GuardNotNull(nameof(environmentVariables));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||
/// </summary>
|
||||
public CommandInput(string commandName, IReadOnlyList<string> directives, IReadOnlyList<CommandOptionInput> options)
|
||||
: this(commandName, directives, options, EmptyEnvironmentVariables)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInput"/>.
|
||||
/// </summary>
|
||||
public CommandInput(string commandName, IReadOnlyList<CommandOptionInput> options, IReadOnlyDictionary<string, string> environmentVariables)
|
||||
: this(commandName, EmptyDirectives, options, environmentVariables)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,6 +109,7 @@ namespace CliFx.Models
|
||||
{
|
||||
private static readonly IReadOnlyList<string> EmptyDirectives = new string[0];
|
||||
private static readonly IReadOnlyList<CommandOptionInput> EmptyOptions = new CommandOptionInput[0];
|
||||
private static readonly IReadOnlyDictionary<string, string> EmptyEnvironmentVariables = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Empty input.
|
||||
|
||||
@@ -34,16 +34,22 @@ namespace CliFx.Models
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional environment variable name that will be used as fallback value if no option value is specified.
|
||||
/// </summary>
|
||||
public string EnvironmentVariableName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandOptionSchema"/>.
|
||||
/// </summary>
|
||||
public CommandOptionSchema(PropertyInfo property, string name, char? shortName, bool isRequired, string description)
|
||||
public CommandOptionSchema(PropertyInfo property, string name, char? shortName, bool isRequired, string description, string environmentVariableName)
|
||||
{
|
||||
Property = property; // can be null
|
||||
Name = name; // can be null
|
||||
ShortName = shortName; // can be null
|
||||
IsRequired = isRequired;
|
||||
Description = description; // can be null
|
||||
EnvironmentVariableName = environmentVariableName; //can be null
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -75,9 +81,9 @@ namespace CliFx.Models
|
||||
// ...in CliApplication (when reading) and HelpTextRenderer (when writing).
|
||||
|
||||
internal static CommandOptionSchema HelpOption { get; } =
|
||||
new CommandOptionSchema(null, "help", 'h', false, "Shows help text.");
|
||||
new CommandOptionSchema(null, "help", 'h', false, "Shows help text.", null);
|
||||
|
||||
internal static CommandOptionSchema VersionOption { get; } =
|
||||
new CommandOptionSchema(null, "version", null, false, "Shows version information.");
|
||||
new CommandOptionSchema(null, "version", null, false, "Shows version information.", null);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using CliFx.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.Internal;
|
||||
|
||||
namespace CliFx.Models
|
||||
{
|
||||
@@ -73,14 +73,14 @@ namespace CliFx.Models
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an option that matches specified alias, or null if not found.
|
||||
/// Finds an option input that matches the option schema specified, or null if not found.
|
||||
/// </summary>
|
||||
public static CommandOptionSchema FindByAlias(this IReadOnlyList<CommandOptionSchema> optionSchemas, string alias)
|
||||
public static CommandOptionInput FindByOptionSchema(this IReadOnlyList<CommandOptionInput> optionInputs, CommandOptionSchema optionSchema)
|
||||
{
|
||||
optionSchemas.GuardNotNull(nameof(optionSchemas));
|
||||
alias.GuardNotNull(nameof(alias));
|
||||
optionInputs.GuardNotNull(nameof(optionInputs));
|
||||
optionSchema.GuardNotNull(nameof(optionSchema));
|
||||
|
||||
return optionSchemas.FirstOrDefault(o => o.MatchesAlias(alias));
|
||||
return optionInputs.FirstOrDefault(o => optionSchema.MatchesAlias(o.Alias));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,20 +11,30 @@ namespace CliFx.Services
|
||||
public class CommandInitializer : ICommandInitializer
|
||||
{
|
||||
private readonly ICommandOptionInputConverter _commandOptionInputConverter;
|
||||
private readonly IEnvironmentVariablesParser _environmentVariablesParser;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
||||
/// </summary>
|
||||
public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter)
|
||||
public CommandInitializer(ICommandOptionInputConverter commandOptionInputConverter, IEnvironmentVariablesParser environmentVariablesParser)
|
||||
{
|
||||
_commandOptionInputConverter = commandOptionInputConverter.GuardNotNull(nameof(commandOptionInputConverter));
|
||||
_environmentVariablesParser = environmentVariablesParser.GuardNotNull(nameof(environmentVariablesParser));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
||||
/// </summary>
|
||||
public CommandInitializer(IEnvironmentVariablesParser environmentVariablesParser)
|
||||
: this(new CommandOptionInputConverter(), environmentVariablesParser)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInitializer"/>.
|
||||
/// </summary>
|
||||
public CommandInitializer()
|
||||
: this(new CommandOptionInputConverter())
|
||||
: this(new CommandOptionInputConverter(), new EnvironmentVariablesParser())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,14 +49,27 @@ namespace CliFx.Services
|
||||
var unsetRequiredOptions = commandSchema.Options.Where(o => o.IsRequired).ToList();
|
||||
|
||||
//Set command options
|
||||
foreach (var optionInput in commandInput.Options)
|
||||
foreach (var optionSchema in commandSchema.Options)
|
||||
{
|
||||
// Find matching option schema for this option input
|
||||
var optionSchema = commandSchema.Options.FindByAlias(optionInput.Alias);
|
||||
if (optionSchema == null)
|
||||
//Find matching option input
|
||||
var optionInput = commandInput.Options.FindByOptionSchema(optionSchema);
|
||||
|
||||
//If no option input is available fall back to environment variable values
|
||||
if (optionInput == null && !optionSchema.EnvironmentVariableName.IsNullOrWhiteSpace())
|
||||
{
|
||||
var fallbackEnvironmentVariableExists = commandInput.EnvironmentVariables.ContainsKey(optionSchema.EnvironmentVariableName);
|
||||
|
||||
//If no environment variable is found or there is no valid value for this option skip it
|
||||
if (!fallbackEnvironmentVariableExists || commandInput.EnvironmentVariables[optionSchema.EnvironmentVariableName].IsNullOrWhiteSpace())
|
||||
continue;
|
||||
|
||||
optionInput = _environmentVariablesParser.GetCommandOptionInputFromEnvironmentVariable(commandInput.EnvironmentVariables[optionSchema.EnvironmentVariableName], optionSchema);
|
||||
}
|
||||
|
||||
//No fallback available and no option input was specified, skip option
|
||||
if (optionInput == null)
|
||||
continue;
|
||||
|
||||
// Convert option to the type of the underlying property
|
||||
var convertedValue = _commandOptionInputConverter.ConvertOptionInput(optionInput, optionSchema.Property.PropertyType);
|
||||
|
||||
// Set value of the underlying property
|
||||
|
||||
@@ -12,6 +12,26 @@ namespace CliFx.Services
|
||||
/// </summary>
|
||||
public class CommandInputParser : ICommandInputParser
|
||||
{
|
||||
private readonly IEnvironmentVariablesProvider _environmentVariablesProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInputParser"/>
|
||||
/// </summary>
|
||||
public CommandInputParser(IEnvironmentVariablesProvider environmentVariablesProvider)
|
||||
{
|
||||
environmentVariablesProvider.GuardNotNull(nameof(environmentVariablesProvider));
|
||||
|
||||
_environmentVariablesProvider = environmentVariablesProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="CommandInputParser"/>
|
||||
/// </summary>
|
||||
public CommandInputParser()
|
||||
: this(new EnvironmentVariablesProvider())
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CommandInput ParseCommandInput(IReadOnlyList<string> commandLineArguments)
|
||||
{
|
||||
@@ -78,7 +98,9 @@ namespace CliFx.Services
|
||||
var commandName = commandNameBuilder.Length > 0 ? commandNameBuilder.ToString() : null;
|
||||
var options = optionsDic.Select(p => new CommandOptionInput(p.Key, p.Value)).ToArray();
|
||||
|
||||
return new CommandInput(commandName, directives, options);
|
||||
var environmentVariables = _environmentVariablesProvider.GetEnvironmentVariables();
|
||||
|
||||
return new CommandInput(commandName, directives, options, environmentVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,13 @@ namespace CliFx.Services
|
||||
{
|
||||
}
|
||||
|
||||
private object ConvertValue(string value, Type targetType)
|
||||
/// <summary>
|
||||
/// Converts a single string value to specified target type.
|
||||
/// </summary>
|
||||
protected virtual object ConvertValue(string value, Type targetType)
|
||||
{
|
||||
targetType.GuardNotNull(nameof(targetType));
|
||||
|
||||
try
|
||||
{
|
||||
// String or object
|
||||
@@ -108,7 +113,7 @@ namespace CliFx.Services
|
||||
return Enum.Parse(targetType, value, true);
|
||||
|
||||
// Nullable
|
||||
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
|
||||
var nullableUnderlyingType = targetType.GetNullableUnderlyingType();
|
||||
if (nullableUnderlyingType != null)
|
||||
return !value.IsNullOrWhiteSpace() ? ConvertValue(value, nullableUnderlyingType) : null;
|
||||
|
||||
@@ -126,48 +131,66 @@ namespace CliFx.Services
|
||||
var parseMethod = GetStaticParseMethod(targetType);
|
||||
if (parseMethod != null)
|
||||
return parseMethod.Invoke(null, new object[] {value});
|
||||
|
||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Wrap and rethrow exceptions that occur when trying to convert the value
|
||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].", ex);
|
||||
}
|
||||
|
||||
// Throw if we can't find a way to convert the value
|
||||
throw new CliFxException($"Can't convert value [{value}] to type [{targetType}].");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertOptionInput(CommandOptionInput optionInput, Type targetType)
|
||||
public virtual object ConvertOptionInput(CommandOptionInput optionInput, Type targetType)
|
||||
{
|
||||
optionInput.GuardNotNull(nameof(optionInput));
|
||||
targetType.GuardNotNull(nameof(targetType));
|
||||
|
||||
// Single value
|
||||
if (optionInput.Values.Count <= 1)
|
||||
// Get the underlying type of IEnumerable<T> if it's implemented by the target type.
|
||||
// Ignore string type because it's IEnumerable<T> but we don't treat it as such.
|
||||
var enumerableUnderlyingType = targetType != typeof(string) ? targetType.GetEnumerableUnderlyingType() : null;
|
||||
|
||||
// Convert to a non-enumerable type
|
||||
if (enumerableUnderlyingType == null)
|
||||
{
|
||||
// Throw if provided with more than 1 value
|
||||
if (optionInput.Values.Count > 1)
|
||||
{
|
||||
throw new CliFxException(
|
||||
$"Can't convert a sequence of values [{optionInput.Values.JoinToString(", ")}] " +
|
||||
$"to non-enumerable type [{targetType}].");
|
||||
}
|
||||
|
||||
// Retrieve a single value and convert
|
||||
var value = optionInput.Values.SingleOrDefault();
|
||||
return ConvertValue(value, targetType);
|
||||
}
|
||||
// Multiple values
|
||||
// Convert to an enumerable type
|
||||
else
|
||||
{
|
||||
// Determine underlying type of elements inside the target collection type
|
||||
var underlyingType = targetType.GetEnumerableUnderlyingType() ?? typeof(object);
|
||||
// Convert values to the underlying enumerable type and cast it to dynamic array
|
||||
var convertedValues = optionInput.Values
|
||||
.Select(v => ConvertValue(v, enumerableUnderlyingType))
|
||||
.ToNonGenericArray(enumerableUnderlyingType);
|
||||
|
||||
// Convert values to that type
|
||||
var convertedValues = optionInput.Values.Select(v => ConvertValue(v, underlyingType)).ToNonGenericArray(underlyingType);
|
||||
// Get the type of produced array
|
||||
var convertedValuesType = convertedValues.GetType();
|
||||
|
||||
// Assignable from array of values (e.g. T[], IReadOnlyList<T>, IEnumerable<T>)
|
||||
// Try to assign the array (works for T[], IReadOnlyList<T>, IEnumerable<T>, etc)
|
||||
if (targetType.IsAssignableFrom(convertedValuesType))
|
||||
return convertedValues;
|
||||
|
||||
// Has a constructor that accepts an array of values (e.g. HashSet<T>, List<T>)
|
||||
// Try to inject the array into the constructor (works for HashSet<T>, List<T>, etc)
|
||||
var arrayConstructor = targetType.GetConstructor(new[] {convertedValuesType});
|
||||
if (arrayConstructor != null)
|
||||
return arrayConstructor.Invoke(new object[] {convertedValues});
|
||||
|
||||
// Throw if we can't find a way to convert the values
|
||||
throw new CliFxException(
|
||||
$"Can't convert sequence of values [{optionInput.Values.JoinToString(", ")}] to type [{targetType}].");
|
||||
$"Can't convert a sequence of values [{optionInput.Values.JoinToString(", ")}] " +
|
||||
$"to type [{targetType}].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace CliFx.Services
|
||||
attribute.Name,
|
||||
attribute.ShortName,
|
||||
attribute.IsRequired,
|
||||
attribute.Description);
|
||||
attribute.Description,
|
||||
attribute.EnvironmentVariableName);
|
||||
|
||||
// Make sure there are no other options with the same name
|
||||
var existingOptionWithSameName = result
|
||||
|
||||
30
CliFx/Services/EnvironmentVariablesParser.cs
Normal file
30
CliFx/Services/EnvironmentVariablesParser.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CliFx.Internal;
|
||||
using CliFx.Models;
|
||||
|
||||
namespace CliFx.Services
|
||||
{
|
||||
/// <inheritdoct />
|
||||
public class EnvironmentVariablesParser : IEnvironmentVariablesParser
|
||||
{
|
||||
/// <inheritdoct />
|
||||
public CommandOptionInput GetCommandOptionInputFromEnvironmentVariable(string environmentVariableValue, CommandOptionSchema targetOptionSchema)
|
||||
{
|
||||
environmentVariableValue.GuardNotNull(nameof(environmentVariableValue));
|
||||
targetOptionSchema.GuardNotNull(nameof(targetOptionSchema));
|
||||
|
||||
//If the option is not a collection do not split environment variable values
|
||||
var optionIsCollection = targetOptionSchema.Property.PropertyType.IsCollection();
|
||||
|
||||
if (!optionIsCollection) return new CommandOptionInput(targetOptionSchema.EnvironmentVariableName, environmentVariableValue);
|
||||
|
||||
//If the option is a collection split the values using System separator, empty values are discarded
|
||||
var environmentVariableValues = environmentVariableValue.Split(Path.PathSeparator)
|
||||
.Where(v => !v.IsNullOrWhiteSpace())
|
||||
.ToList();
|
||||
|
||||
return new CommandOptionInput(targetOptionSchema.EnvironmentVariableName, environmentVariableValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
CliFx/Services/EnvironmentVariablesProvider.cs
Normal file
36
CliFx/Services/EnvironmentVariablesProvider.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Security;
|
||||
|
||||
namespace CliFx.Services
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class EnvironmentVariablesProvider : IEnvironmentVariablesProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, string> GetEnvironmentVariables()
|
||||
{
|
||||
try
|
||||
{
|
||||
var environmentVariables = Environment.GetEnvironmentVariables();
|
||||
|
||||
//Constructing the dictionary manually allows to specify a key comparer that ignores case
|
||||
//This allows to ignore casing when looking for a fallback environment variable of an option
|
||||
var environmentVariablesAsDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
//Type DictionaryEntry must be explicitly used otherwise it will enumerate as a collection of objects
|
||||
foreach (DictionaryEntry environmentVariable in environmentVariables)
|
||||
{
|
||||
environmentVariablesAsDictionary.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
||||
}
|
||||
|
||||
return environmentVariablesAsDictionary;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CliFx.Internal;
|
||||
|
||||
namespace CliFx.Services
|
||||
@@ -50,5 +51,25 @@ namespace CliFx.Services
|
||||
|
||||
console.WithForegroundColor(foregroundColor, () => console.WithBackgroundColor(backgroundColor, action));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets wether a string representing an environment variable value is escaped (i.e.: surrounded by double quotation marks)
|
||||
/// </summary>
|
||||
public static bool IsEnvironmentVariableEscaped(this string environmentVariableValue)
|
||||
{
|
||||
environmentVariableValue.GuardNotNull(nameof(environmentVariableValue));
|
||||
|
||||
return environmentVariableValue.StartsWith("\"") && environmentVariableValue.EndsWith("\"");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets wether the <see cref="Type"/> supplied is a collection implementing <see cref="IEnumerable{T}"/>
|
||||
/// </summary>
|
||||
public static bool IsCollection(this Type type)
|
||||
{
|
||||
type.GuardNotNull(nameof(type));
|
||||
|
||||
return type != typeof(string) && type.GetEnumerableUnderlyingType() != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace CliFx.Services
|
||||
{
|
||||
@@ -52,5 +53,10 @@ namespace CliFx.Services
|
||||
/// Resets foreground and background color to default values.
|
||||
/// </summary>
|
||||
void ResetColor();
|
||||
|
||||
/// <summary>
|
||||
/// Cancels when soft cancellation requested.
|
||||
/// </summary>
|
||||
CancellationToken CancellationToken { get; }
|
||||
}
|
||||
}
|
||||
15
CliFx/Services/IEnvironmentVariablesParser.cs
Normal file
15
CliFx/Services/IEnvironmentVariablesParser.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using CliFx.Models;
|
||||
|
||||
namespace CliFx.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses environment variable values
|
||||
/// </summary>
|
||||
public interface IEnvironmentVariablesParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse an environment variable value and converts it to a <see cref="CommandOptionInput"/>
|
||||
/// </summary>
|
||||
CommandOptionInput GetCommandOptionInputFromEnvironmentVariable(string environmentVariableValue, CommandOptionSchema targetOptionSchema);
|
||||
}
|
||||
}
|
||||
16
CliFx/Services/IEnvironmentVariablesProvider.cs
Normal file
16
CliFx/Services/IEnvironmentVariablesProvider.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CliFx.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides environment variable values
|
||||
/// </summary>
|
||||
public interface IEnvironmentVariablesProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all the environment variables available.
|
||||
/// </summary>
|
||||
/// <remarks>If the User is not allowed to read environment variables it will return an empty dictionary.</remarks>
|
||||
IReadOnlyDictionary<string, string> GetEnvironmentVariables();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace CliFx.Services
|
||||
{
|
||||
@@ -8,6 +9,22 @@ namespace CliFx.Services
|
||||
/// </summary>
|
||||
public class SystemConsole : IConsole
|
||||
{
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemConsole()
|
||||
{
|
||||
// Subscribe to CancelKeyPress event with cancellation token source
|
||||
// Kills app on second cancellation (hard cancellation)
|
||||
Console.CancelKeyPress += (_, args) =>
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested)
|
||||
return;
|
||||
args.Cancel = true;
|
||||
_cancellationTokenSource.Cancel();
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextReader Input => Console.In;
|
||||
|
||||
@@ -42,5 +59,8 @@ namespace CliFx.Services
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetColor() => Console.ResetColor();
|
||||
|
||||
/// <inheritdoc />
|
||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using CliFx.Internal;
|
||||
|
||||
namespace CliFx.Services
|
||||
@@ -11,6 +12,8 @@ namespace CliFx.Services
|
||||
/// </summary>
|
||||
public class VirtualConsole : IConsole
|
||||
{
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextReader Input { get; }
|
||||
|
||||
@@ -82,5 +85,16 @@ namespace CliFx.Services
|
||||
ForegroundColor = ConsoleColor.Gray;
|
||||
BackgroundColor = ConsoleColor.Black;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
|
||||
/// <summary>
|
||||
/// Simulates cancellation.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Readme.md
49
Readme.md
@@ -30,6 +30,10 @@ _CliFx is to command line interfaces what ASP.NET Core is to web applications._
|
||||
- Targets .NET Framework 4.5+ and .NET Standard 2.0+
|
||||
- No external dependencies
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||
## Argument syntax
|
||||
|
||||
This library employs a variation of the GNU command line argument syntax. Because CliFx uses a context-unaware parser, the syntax rules are generally more consistent and intuitive.
|
||||
@@ -123,6 +127,34 @@ When resolving options, CliFx can convert string values obtained from the comman
|
||||
|
||||
If you want to define an option of your own type, the easiest way to do it is to make sure that your type is string-initializable, as explained above.
|
||||
|
||||
It is also possible to configure the application to use your own converter, by calling `UseCommandOptionInputConverter` method on `CliApplicationBuilder`.
|
||||
|
||||
```c#
|
||||
var app = new CliApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.UseCommandOptionInputConverter(new MyConverter())
|
||||
.Build();
|
||||
```
|
||||
|
||||
The converter class must implement `ICommandOptionInputConverter` but you can also derive from `CommandOptionInputConverter` to extend the default behavior.
|
||||
|
||||
```c#
|
||||
public class MyConverter : CommandOptionInputConverter
|
||||
{
|
||||
protected override object ConvertValue(string value, Type targetType)
|
||||
{
|
||||
// Custom conversion for MyType
|
||||
if (targetType == typeof(MyType))
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// Default behavior for other types
|
||||
return base.ConvertValue(value, targetType);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reporting errors
|
||||
|
||||
You may have noticed that commands in CliFx don't return exit codes. This is by design as exit codes are considered a higher-level concern and thus handled by `CliApplication`, not by individual commands.
|
||||
@@ -388,13 +420,12 @@ var app = new CliApplicationBuilder()
|
||||
|
||||
## Benchmarks
|
||||
|
||||
CliFx has the smallest performance overhead compared to other command line parsers and frameworks.
|
||||
Below you can see a table comparing execution times of a simple command across different libraries.
|
||||
Here's how CliFx's execution overhead compares to that of other libraries.
|
||||
|
||||
```ini
|
||||
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.14393.0 (1607/AnniversaryUpdate/Redstone1)
|
||||
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.14393.3144 (1607/AnniversaryUpdate/Redstone1)
|
||||
Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
|
||||
Frequency=3125008 Hz, Resolution=319.9992 ns, Timer=TSC
|
||||
Frequency=3125011 Hz, Resolution=319.9989 ns, Timer=TSC
|
||||
.NET Core SDK=2.2.401
|
||||
[Host] : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
|
||||
Core : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
|
||||
@@ -404,10 +435,12 @@ Job=Core Runtime=Core
|
||||
|
||||
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank |
|
||||
|------------------------------------- |----------:|----------:|----------:|------:|--------:|-----:|
|
||||
| CliFx | 39.47 us | 0.7490 us | 0.9198 us | 1.00 | 0.00 | 1 |
|
||||
| System.CommandLine | 153.98 us | 0.7112 us | 0.6652 us | 3.90 | 0.09 | 2 |
|
||||
| McMaster.Extensions.CommandLineUtils | 180.36 us | 3.5893 us | 6.7416 us | 4.59 | 0.16 | 3 |
|
||||
| PowerArgs | 427.54 us | 6.9006 us | 6.4548 us | 10.82 | 0.26 | 4 |
|
||||
| CliFx | 31.29 us | 0.6147 us | 0.7774 us | 1.00 | 0.00 | 2 |
|
||||
| System.CommandLine | 184.44 us | 3.4993 us | 4.0297 us | 5.90 | 0.21 | 4 |
|
||||
| McMaster.Extensions.CommandLineUtils | 165.50 us | 1.4805 us | 1.3124 us | 5.33 | 0.13 | 3 |
|
||||
| CommandLineParser | 26.65 us | 0.5530 us | 0.5679 us | 0.85 | 0.03 | 1 |
|
||||
| PowerArgs | 405.44 us | 7.7133 us | 9.1821 us | 12.96 | 0.47 | 6 |
|
||||
| Clipr | 220.82 us | 4.4567 us | 4.9536 us | 7.06 | 0.25 | 5 |
|
||||
|
||||
## Philosophy
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
version: '{build}'
|
||||
|
||||
image: Visual Studio 2017
|
||||
image: Visual Studio 2019
|
||||
configuration: Release
|
||||
|
||||
before_build:
|
||||
|
||||
Reference in New Issue
Block a user