mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2b4e53615 | ||
|
|
2d519ab190 | ||
|
|
2d479c9cb6 | ||
|
|
2bb7e13e51 | ||
|
|
6e1dfdcdd4 | ||
|
|
5ba647e5c1 | ||
|
|
853492695f | ||
|
|
d5d72c7c50 | ||
|
|
d676b5832e | ||
|
|
28097afc1e | ||
|
|
fda96586f3 | ||
|
|
fc5af8dbbc | ||
|
|
4835e64388 | ||
|
|
0999c33f93 |
30
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Report broken functionality.
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Please check existing issues (both opened and closed) to ensure that this bug hasn't been reported before.
|
||||
- If you want to ask a question instead of reporting a bug, use [discussions](https://github.com/Tyrrrz/CliFx/discussions/new) instead.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: "Which version(s) of CliFx does this bug affect?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Details
|
||||
description: "Clear and thorough explanation of the bug. If relevant, include screenshots or screen recordings."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: "Minimum steps or code required to reproduce the bug."
|
||||
validations:
|
||||
required: true
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: "\U0001F5E8 Ask a question"
|
||||
url: https://github.com/Tyrrrz/CliFx/discussions/new
|
||||
about: Please ask and answer questions here.
|
||||
16
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
16
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: "\U00002728 Feature request"
|
||||
description: Request a new feature.
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
- Please check existing issues (both opened and closed) to ensure that this feature hasn't been requested before.
|
||||
- If you want to ask a question instead of requesting a feature, use [**discussions**](https://github.com/Tyrrrz/CliFx/discussions/new) instead.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Details
|
||||
description: "Clear and thorough explanation of the feature you have in mind."
|
||||
validations:
|
||||
required: true
|
||||
4
.github/workflows/CD.yml
vendored
4
.github/workflows/CD.yml
vendored
@@ -19,9 +19,7 @@ jobs:
|
||||
dotnet-version: 5.0.x
|
||||
|
||||
- name: Pack
|
||||
run: |
|
||||
dotnet nuget locals all --clear
|
||||
dotnet pack CliFx --configuration Release
|
||||
run: dotnet pack CliFx --configuration Release
|
||||
|
||||
- name: Deploy
|
||||
run: dotnet nuget push CliFx/bin/Release/*.nupkg -s nuget.org -k ${{ secrets.NUGET_TOKEN }}
|
||||
|
||||
4
.github/workflows/CI.yml
vendored
4
.github/workflows/CI.yml
vendored
@@ -20,9 +20,7 @@ jobs:
|
||||
dotnet-version: 5.0.x
|
||||
|
||||
- name: Build & test
|
||||
run: |
|
||||
dotnet nuget locals all --clear
|
||||
dotnet test --configuration Release --logger GitHubActions
|
||||
run: dotnet test --configuration Release --logger GitHubActions
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v1.0.5
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
### v2.0.4 (24-Apr-2021)
|
||||
|
||||
- Fixed an issue where output and error streams in `SystemConsole` defaulted to UTF8 encoding with BOM when the application was running with UTF8 codepage. `ConsoleWriter` will now discard preamble from the specified encoding. This fix brings the behavior of `SystemConsole` in line with .NET's own `System.Console` which also discards preamble for output and error streams.
|
||||
- Fixed an issue where help text tried to show default values for parameters and options whose type does not override `ToString()` method.
|
||||
- Fixed an issue where help text didn't show default values for parameters and options whose type is an enumerable of nullable enums. (Thanks [@Robert Dailey](https://github.com/rcdailey))
|
||||
- Fixed an issue where specific parts of the help text weren't legible in some terminals due to low color resolution. Removed the usage of `ConsoleColor.DarkGray` in help text.
|
||||
|
||||
### v2.0.3 (09-Apr-2021)
|
||||
|
||||
- Improved help text by showing valid values for non-scalar enum parameters and options. (Thanks [@Robert Dailey](https://github.com/rcdailey))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
<PackageReference Include="clipr" Version="1.6.1" />
|
||||
<PackageReference Include="Cocona" Version="1.5.0" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace CliFx.Demo.Domain
|
||||
|
||||
var data = File.ReadAllText(StorageFilePath);
|
||||
|
||||
return JsonConvert.DeserializeObject<Library>(data);
|
||||
return JsonConvert.DeserializeObject<Library>(data) ?? Library.Empty;
|
||||
}
|
||||
|
||||
public Book? TryGetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title);
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.3.1" />
|
||||
<PackageReference Include="CliWrap" Version="3.3.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
|
||||
<PackageReference Include="coverlet.msbuild" Version="3.0.3" PrivateAssets="all" />
|
||||
|
||||
@@ -590,10 +590,55 @@ public enum CustomEnum { One, Two, Three }
|
||||
public class Command : ICommand
|
||||
{
|
||||
[CommandParameter(0)]
|
||||
public List<CustomEnum> Foo { get; set; }
|
||||
public IReadOnlyList<CustomEnum> Foo { get; set; }
|
||||
|
||||
[CommandOption(""bar"")]
|
||||
public List<CustomEnum> Bar { get; set; }
|
||||
public IReadOnlyList<CustomEnum> Bar { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
");
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommand(commandType)
|
||||
.UseConsole(FakeConsole)
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(
|
||||
new[] {"--help"},
|
||||
new Dictionary<string, string>()
|
||||
);
|
||||
|
||||
var stdOut = FakeConsole.ReadOutputString();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(0);
|
||||
stdOut.Should().ContainAllInOrder(
|
||||
"PARAMETERS",
|
||||
"foo", "Choices:", "One", "Two", "Three",
|
||||
"OPTIONS",
|
||||
"--bar", "Choices:", "One", "Two", "Three"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Help_text_shows_all_valid_values_for_nullable_enum_parameters_and_options()
|
||||
{
|
||||
// Arrange
|
||||
var commandType = DynamicCommandBuilder.Compile(
|
||||
// language=cs
|
||||
@"
|
||||
public enum CustomEnum { One, Two, Three }
|
||||
|
||||
[Command]
|
||||
public class Command : ICommand
|
||||
{
|
||||
[CommandParameter(0)]
|
||||
public CustomEnum? Foo { get; set; }
|
||||
|
||||
[CommandOption(""bar"")]
|
||||
public IReadOnlyList<CustomEnum?> Bar { get; set; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console) => default;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CliFx.Infrastructure;
|
||||
using CliFx.Input;
|
||||
|
||||
@@ -47,9 +46,9 @@ namespace CliFx.Formatting
|
||||
foreach (var value in optionInput.Values)
|
||||
{
|
||||
Write(' ');
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
Write(value);
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write(']');
|
||||
@@ -75,9 +74,9 @@ namespace CliFx.Formatting
|
||||
Write('=');
|
||||
|
||||
// Value
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
Write(environmentVariableInput.Value);
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace CliFx.Formatting
|
||||
Write("at ");
|
||||
|
||||
// Fully qualified method name
|
||||
Write(ConsoleColor.DarkGray, stackFrame.ParentType + '.');
|
||||
Write(stackFrame.ParentType + '.');
|
||||
Write(ConsoleColor.Yellow, stackFrame.MethodName);
|
||||
|
||||
// Method parameters
|
||||
@@ -60,7 +60,7 @@ namespace CliFx.Formatting
|
||||
Write("in ");
|
||||
|
||||
// File path
|
||||
Write(ConsoleColor.DarkGray, stackFrameDirectoryPath);
|
||||
Write(stackFrameDirectoryPath);
|
||||
Write(ConsoleColor.Yellow, stackFrameFileName);
|
||||
|
||||
// Source position
|
||||
@@ -80,7 +80,7 @@ namespace CliFx.Formatting
|
||||
|
||||
// Fully qualified exception type
|
||||
var exceptionType = exception.GetType();
|
||||
Write(ConsoleColor.DarkGray, exceptionType.Namespace + '.');
|
||||
Write(exceptionType.Namespace + '.');
|
||||
Write(ConsoleColor.White, exceptionType.Name);
|
||||
Write(": ");
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace CliFx.Formatting
|
||||
|
||||
private void WriteCommandInvocation()
|
||||
{
|
||||
Write(ConsoleColor.DarkGray, _context.ApplicationMetadata.ExecutableName);
|
||||
Write(_context.ApplicationMetadata.ExecutableName);
|
||||
|
||||
// Command name
|
||||
if (!string.IsNullOrWhiteSpace(_context.CommandSchema.Name))
|
||||
@@ -190,9 +190,9 @@ namespace CliFx.Formatting
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
Write(validValue.ToString());
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write('.');
|
||||
@@ -269,9 +269,9 @@ namespace CliFx.Formatting
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
Write(validValue.ToString());
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write('.');
|
||||
@@ -317,10 +317,12 @@ namespace CliFx.Formatting
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
Write(element.ToString(CultureInfo.InvariantCulture));
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write('.');
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -329,15 +331,14 @@ namespace CliFx.Formatting
|
||||
{
|
||||
Write(ConsoleColor.White, "Default: ");
|
||||
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
Write('"');
|
||||
Write(defaultValue.ToString(CultureInfo.InvariantCulture));
|
||||
Write(ConsoleColor.DarkGray, '"');
|
||||
}
|
||||
}
|
||||
|
||||
Write('"');
|
||||
Write('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace CliFx.Infrastructure
|
||||
/// Initializes an instance of <see cref="ConsoleReader"/>.
|
||||
/// </summary>
|
||||
public ConsoleReader(IConsole console, Stream stream, Encoding encoding)
|
||||
: base(stream, encoding, false)
|
||||
: base(stream, encoding, false, 4096)
|
||||
{
|
||||
Console = console;
|
||||
}
|
||||
@@ -33,7 +33,11 @@ namespace CliFx.Infrastructure
|
||||
|
||||
public partial class ConsoleReader
|
||||
{
|
||||
internal static ConsoleReader Create(IConsole console, Stream? stream) =>
|
||||
new(console, stream is not null ? Stream.Synchronized(stream) : Stream.Null);
|
||||
internal static ConsoleReader Create(IConsole console, Stream? stream) => new(
|
||||
console,
|
||||
stream is not null
|
||||
? Stream.Synchronized(stream)
|
||||
: Stream.Null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using CliFx.Utils;
|
||||
|
||||
namespace CliFx.Infrastructure
|
||||
{
|
||||
@@ -17,7 +18,7 @@ namespace CliFx.Infrastructure
|
||||
/// Initializes an instance of <see cref="ConsoleWriter"/>.
|
||||
/// </summary>
|
||||
public ConsoleWriter(IConsole console, Stream stream, Encoding encoding)
|
||||
: base(stream, encoding)
|
||||
: base(stream, encoding, 256)
|
||||
{
|
||||
Console = console;
|
||||
}
|
||||
@@ -26,14 +27,18 @@ namespace CliFx.Infrastructure
|
||||
/// Initializes an instance of <see cref="ConsoleWriter"/>.
|
||||
/// </summary>
|
||||
public ConsoleWriter(IConsole console, Stream stream)
|
||||
: this(console, stream, System.Console.OutputEncoding)
|
||||
: this(console, stream, System.Console.OutputEncoding.WithoutPreamble())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ConsoleWriter
|
||||
{
|
||||
internal static ConsoleWriter Create(IConsole console, Stream? stream) =>
|
||||
new(console, stream is not null ? Stream.Synchronized(stream) : Stream.Null) {AutoFlush = true};
|
||||
internal static ConsoleWriter Create(IConsole console, Stream? stream) => new(
|
||||
console,
|
||||
stream is not null
|
||||
? Stream.Synchronized(stream)
|
||||
: Stream.Null
|
||||
) {AutoFlush = true};
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,20 @@ namespace CliFx.Schema
|
||||
|
||||
public IReadOnlyList<object?> GetValidValues()
|
||||
{
|
||||
var underlyingType =
|
||||
Type.TryGetNullableUnderlyingType() ??
|
||||
Type.TryGetEnumerableUnderlyingType() ??
|
||||
Type;
|
||||
static Type GetUnderlyingType(Type type)
|
||||
{
|
||||
var enumerableUnderlyingType = type.TryGetEnumerableUnderlyingType();
|
||||
if (enumerableUnderlyingType is not null)
|
||||
return GetUnderlyingType(enumerableUnderlyingType);
|
||||
|
||||
var nullableUnderlyingType = type.TryGetNullableUnderlyingType();
|
||||
if (nullableUnderlyingType is not null)
|
||||
return GetUnderlyingType(nullableUnderlyingType);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
var underlyingType = GetUnderlyingType(Type);
|
||||
|
||||
// We can only get valid values for enums
|
||||
if (underlyingType.IsEnum)
|
||||
|
||||
@@ -54,9 +54,11 @@ namespace CliFx.Utils.Extensions
|
||||
return array;
|
||||
}
|
||||
|
||||
public static bool IsToStringOverriden(this Type type) =>
|
||||
type.GetMethod(nameof(ToString), Type.EmptyTypes) !=
|
||||
typeof(object).GetMethod(nameof(ToString), Type.EmptyTypes);
|
||||
public static bool IsToStringOverriden(this Type type)
|
||||
{
|
||||
var toStringMethod = type.GetMethod(nameof(ToString), Type.EmptyTypes);
|
||||
return toStringMethod?.GetBaseDefinition()?.DeclaringType != toStringMethod?.DeclaringType;
|
||||
}
|
||||
|
||||
// Types supported by `Convert.ChangeType(...)`
|
||||
private static readonly HashSet<Type> ConvertibleTypes = new()
|
||||
|
||||
51
CliFx/Utils/NoPreambleEncoding.cs
Normal file
51
CliFx/Utils/NoPreambleEncoding.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
namespace CliFx.Utils
|
||||
{
|
||||
// Adapted from:
|
||||
// https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/Common/src/System/Text/ConsoleEncoding.cs
|
||||
internal class NoPreambleEncoding : Encoding
|
||||
{
|
||||
private readonly Encoding _underlyingEncoding;
|
||||
|
||||
public NoPreambleEncoding(Encoding underlyingEncoding) =>
|
||||
_underlyingEncoding = underlyingEncoding;
|
||||
|
||||
public override byte[] GetPreamble() =>
|
||||
Array.Empty<byte>();
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override int GetByteCount(char[] chars, int index, int count) =>
|
||||
_underlyingEncoding.GetByteCount(chars, index, count);
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) =>
|
||||
_underlyingEncoding.GetBytes(chars, charIndex, charCount, bytes, byteIndex);
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override int GetCharCount(byte[] bytes, int index, int count) =>
|
||||
_underlyingEncoding.GetCharCount(bytes, index, count);
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) =>
|
||||
_underlyingEncoding.GetChars(bytes, byteIndex, byteCount, chars, charIndex);
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override int GetMaxByteCount(int charCount) =>
|
||||
_underlyingEncoding.GetMaxByteCount(charCount);
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public override int GetMaxCharCount(int byteCount) =>
|
||||
_underlyingEncoding.GetMaxCharCount(byteCount);
|
||||
}
|
||||
|
||||
internal static class NoPreambleEncodingExtensions
|
||||
{
|
||||
public static Encoding WithoutPreamble(this Encoding encoding) =>
|
||||
encoding.GetPreamble().Length > 0
|
||||
? new NoPreambleEncoding(encoding)
|
||||
: encoding;
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@ internal static partial class PolyfillExtensions
|
||||
key = pair.Key;
|
||||
value = pair.Value;
|
||||
}
|
||||
|
||||
public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key) =>
|
||||
dic.TryGetValue(key!, out var result) ? result! : default!;
|
||||
}
|
||||
|
||||
internal static partial class PolyfillExtensions
|
||||
@@ -44,4 +41,13 @@ namespace System.Linq
|
||||
new(source, comparer);
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Collections.Generic
|
||||
{
|
||||
internal static class PolyfillExtensions
|
||||
{
|
||||
public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key) =>
|
||||
dic.TryGetValue(key!, out var result) ? result! : default!;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>2.0.3</Version>
|
||||
<Version>2.0.4</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<Copyright>Copyright (C) Alexey Golub</Copyright>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
7
NuGet.config
Normal file
7
NuGet.config
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
143
Readme.md
143
Readme.md
@@ -53,7 +53,7 @@ public static class Program
|
||||
}
|
||||
```
|
||||
|
||||
> Note: ensure that your `Main()` method returns the integer exit code provided by `CliApplication.RunAsync()`, as shown in the above example.
|
||||
> ⚠️ Ensure that your `Main()` method returns the integer exit code provided by `CliApplication.RunAsync()`, as shown in the above example.
|
||||
Exit code is used to communicate execution result to the parent process, so it's important that your program returns it.
|
||||
|
||||
The code above calls `AddCommandsFromThisAssembly()` to scan and resolve command types defined within the current assembly.
|
||||
@@ -97,10 +97,10 @@ They can be used to show help text or application version respectively:
|
||||
|
||||
MyApp v1.0
|
||||
|
||||
Usage
|
||||
USAGE
|
||||
dotnet myapp.dll [options]
|
||||
|
||||
Options
|
||||
OPTIONS
|
||||
-h|--help Shows help text.
|
||||
--version Shows version information.
|
||||
```
|
||||
@@ -176,13 +176,13 @@ Available parameters and options are also listed in the command's help text, whi
|
||||
|
||||
MyApp v1.0
|
||||
|
||||
Usage
|
||||
USAGE
|
||||
dotnet myapp.dll <value> [options]
|
||||
|
||||
Parameters
|
||||
PARAMETERS
|
||||
* value Value whose logarithm is to be found.
|
||||
|
||||
Options
|
||||
OPTIONS
|
||||
-b|--base Logarithm base. Default: "10".
|
||||
-h|--help Shows help text.
|
||||
--version Shows version information.
|
||||
@@ -245,61 +245,9 @@ Parameters and options can have the following underlying types:
|
||||
- Types that are assignable from arrays (`IReadOnlyList<T>`, `ICollection<T>`, etc.)
|
||||
- Types with a constructor accepting an array (`List<T>`, `HashSet<T>`, etc.)
|
||||
|
||||
- Example command with a custom converter:
|
||||
#### Non-scalar parameters and options
|
||||
|
||||
```csharp
|
||||
// Maps 2D vectors from AxB notation
|
||||
public class VectorConverter : BindingConverter<Vector2>
|
||||
{
|
||||
public override Vector2 Convert(string? rawValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawValue))
|
||||
return default;
|
||||
|
||||
var components = rawValue.Split('x', 'X', ';');
|
||||
var x = int.Parse(components[0], CultureInfo.InvariantCulture);
|
||||
var y = int.Parse(components[1], CultureInfo.InvariantCulture);
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Command]
|
||||
public class SurfaceCalculatorCommand : ICommand
|
||||
{
|
||||
// Custom converter is used to map raw argument values
|
||||
[CommandParameter(0, Converter = typeof(VectorConverter))]
|
||||
public Vector2 PointA { get; init; }
|
||||
|
||||
[CommandParameter(1, Converter = typeof(VectorConverter))]
|
||||
public Vector2 PointB { get; init; }
|
||||
|
||||
[CommandParameter(2, Converter = typeof(VectorConverter))]
|
||||
public Vector2 PointC { get; init; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var a = (PointB - PointA).Length();
|
||||
var b = (PointC - PointB).Length();
|
||||
var c = (PointA - PointC).Length();
|
||||
|
||||
var p = (a + b + c) / 2;
|
||||
var surface = Math.Sqrt(p * (p - a) * (p - b) * (p - c));
|
||||
|
||||
console.Output.WriteLine($"Triangle surface area: {surface}");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
> dotnet myapp.dll 0x0 0x18 24x0
|
||||
|
||||
Triangle surface area: 216
|
||||
```
|
||||
|
||||
- Example command with an array-backed parameter:
|
||||
Here's an example of a command with an array-backed parameter:
|
||||
|
||||
```csharp
|
||||
[Command]
|
||||
@@ -353,6 +301,56 @@ public class FileSizeCalculatorCommand : ICommand
|
||||
Total file size: 186368 bytes
|
||||
```
|
||||
|
||||
#### Custom conversion
|
||||
|
||||
To create a custom converter for a parameter or an option, define a class that inherits from `BindingConverter<T>` and specify it in the attribute:
|
||||
|
||||
```csharp
|
||||
// Maps 2D vectors from AxB notation
|
||||
public class VectorConverter : BindingConverter<Vector2>
|
||||
{
|
||||
public override Vector2 Convert(string? rawValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawValue))
|
||||
return default;
|
||||
|
||||
var components = rawValue.Split('x', 'X', ';');
|
||||
var x = int.Parse(components[0], CultureInfo.InvariantCulture);
|
||||
var y = int.Parse(components[1], CultureInfo.InvariantCulture);
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Command]
|
||||
public class SurfaceCalculatorCommand : ICommand
|
||||
{
|
||||
// Custom converter is used to map raw argument values
|
||||
[CommandParameter(0, Converter = typeof(VectorConverter))]
|
||||
public Vector2 PointA { get; init; }
|
||||
|
||||
[CommandParameter(1, Converter = typeof(VectorConverter))]
|
||||
public Vector2 PointB { get; init; }
|
||||
|
||||
[CommandParameter(2, Converter = typeof(VectorConverter))]
|
||||
public Vector2 PointC { get; init; }
|
||||
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
var a = (PointB - PointA).Length();
|
||||
var b = (PointC - PointB).Length();
|
||||
var c = (PointA - PointC).Length();
|
||||
|
||||
var p = (a + b + c) / 2;
|
||||
var surface = Math.Sqrt(p * (p - a) * (p - b) * (p - c));
|
||||
|
||||
console.Output.WriteLine($"Triangle surface area: {surface}");
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple commands
|
||||
|
||||
In order to facilitate a variety of different workflows, command line applications may provide the user with more than just a single command.
|
||||
@@ -401,15 +399,15 @@ Requesting help will show direct subcommands of the current command:
|
||||
|
||||
MyApp v1.0
|
||||
|
||||
Usage
|
||||
USAGE
|
||||
dotnet myapp.dll [options]
|
||||
dotnet myapp.dll [command] [...]
|
||||
|
||||
Options
|
||||
OPTIONS
|
||||
-h|--help Shows help text.
|
||||
--version Shows version information.
|
||||
|
||||
Commands
|
||||
COMMANDS
|
||||
cmd1 Subcommands: cmd1 sub.
|
||||
cmd2
|
||||
|
||||
@@ -421,21 +419,21 @@ The user can also refine their help request by querying it on a specific command
|
||||
```sh
|
||||
> dotnet myapp.dll cmd1 --help
|
||||
|
||||
Usage
|
||||
USAGE
|
||||
dotnet myapp.dll cmd1 [options]
|
||||
dotnet myapp.dll cmd1 [command] [...]
|
||||
|
||||
Options
|
||||
OPTIONS
|
||||
-h|--help Shows help text.
|
||||
|
||||
Commands
|
||||
COMMANDS
|
||||
sub
|
||||
|
||||
You can run `dotnet myapp.dll cmd1 [command] --help` to show help on a specific command.
|
||||
```
|
||||
|
||||
> Note that defining a default (unnamed) command is not required.
|
||||
In the even of its absence, running the application without specifying a command will just show root level help text.
|
||||
If it's absent, running the application without specifying a command will just show root level help text.
|
||||
|
||||
### Reporting errors
|
||||
|
||||
@@ -479,7 +477,8 @@ Division by zero is not supported.
|
||||
133
|
||||
```
|
||||
|
||||
> Note that Unix systems rely on 8-bit unsigned integers to represent exit codes, which means that you can only use values between `1` and `255` to indicate an unsuccessful execution result.
|
||||
> ⚠️ Even though exit codes are represented by 32-bit integers in .NET, using values outside of 8-bit unsigned range will cause an overflow on Unix systems.
|
||||
To avoid unexpected results, use numbers between 1 and 255 for exit codes that indicate failure.
|
||||
|
||||
### Graceful cancellation
|
||||
|
||||
@@ -512,7 +511,7 @@ public class CancellableCommand : ICommand
|
||||
```
|
||||
|
||||
> Note that a command may use this approach to delay cancellation only once.
|
||||
If the user issues a second interrupt signal, the application will be immediately terminated.
|
||||
If the user issues a second interrupt signal, the application will be terminated immediately.
|
||||
|
||||
### Type activation
|
||||
|
||||
@@ -522,7 +521,7 @@ To facilitate that, it uses an interface called `ITypeActivator` that determines
|
||||
The default implementation of `ITypeActivator` only supports types that have public parameterless constructors, which is sufficient for majority of scenarios.
|
||||
However, in some cases you may also want to define a custom initializer, for example when integrating with an external dependency container.
|
||||
|
||||
The following snippet shows how to configure your application to use [`Microsoft.Extensions.DependencyInjection`](https://nuget.org/packages/Microsoft.Extensions.DependencyInjection) as the type activator:
|
||||
The following example shows how to configure your application to use [`Microsoft.Extensions.DependencyInjection`](https://nuget.org/packages/Microsoft.Extensions.DependencyInjection) as the type activator in CliFx:
|
||||
|
||||
```csharp
|
||||
public static class Program
|
||||
@@ -633,7 +632,7 @@ public async Task ConcatCommand_executes_successfully()
|
||||
### Debug and preview mode
|
||||
|
||||
When troubleshooting issues, you may find it useful to run your app in debug or preview mode.
|
||||
To do that, you need to pass pass the corresponding directive before any other arguments.
|
||||
To do that, you need to pass the corresponding directive before any other arguments.
|
||||
|
||||
In order to run the application in debug mode, use the `[debug]` directive.
|
||||
This will cause the program to launch in a suspended state, waiting for debugger to be attached to the process:
|
||||
@@ -697,7 +696,7 @@ public class AuthCommand : ICommand
|
||||
test
|
||||
```
|
||||
|
||||
Environment variables can be configured for options of non-scalar types as well.
|
||||
Environment variables can be configured for options of non-scalar types (arrays, lists, etc.) as well.
|
||||
In such case, the values of the environment variable will be split by `Path.PathSeparator` (`;` on Windows, `:` on Linux).
|
||||
|
||||
## Etymology
|
||||
|
||||
Reference in New Issue
Block a user