28 Commits
1.3.0 ... 1.4.0

Author SHA1 Message Date
Matt Nadareski
be081c701d Bump version 2025-11-12 19:49:06 -05:00
Matt Nadareski
66b0e27d4e Add support for .NET 10 2025-11-11 16:43:21 -05:00
Matt Nadareski
573fe2e160 Update rolling tag 2025-10-26 20:22:42 -04:00
Matt Nadareski
6ad9fa4521 Fix split method in custom help 2025-10-07 12:37:54 -04:00
Matt Nadareski
a261f428f2 Add and allow custom help text 2025-10-07 12:36:00 -04:00
Matt Nadareski
5011331164 Clarify the case where default also has real flags 2025-10-07 12:27:51 -04:00
Matt Nadareski
e257c30892 Clarify help printing with default features 2025-10-07 12:14:18 -04:00
Matt Nadareski
029cfe6dc6 Add optional default feature to CommandSet 2025-10-07 12:13:11 -04:00
Matt Nadareski
f38b8734b5 Simplify the method name 2025-10-07 12:07:41 -04:00
Matt Nadareski
df54b92031 Add AddChildrenFrom method to CommandSet 2025-10-07 12:03:00 -04:00
Matt Nadareski
39acb90dbc Use All help by default instead of top-level only 2025-10-06 08:50:21 -04:00
Matt Nadareski
43083d35e1 Bump version 2025-10-06 07:21:36 -04:00
Matt Nadareski
fddbfa963d Directly return result of execution 2025-10-06 07:19:41 -04:00
Matt Nadareski
94ac68e5a0 Handle default help functionality 2025-10-06 07:19:23 -04:00
Matt Nadareski
0c3373e9f0 Basically all cases should output help 2025-10-05 23:26:13 -04:00
Matt Nadareski
f122bf6b6d Add tests for default parsing 2025-10-05 23:14:02 -04:00
Matt Nadareski
81b300bae6 Add default implementation of parsing in CommandSet 2025-10-05 23:00:20 -04:00
Matt Nadareski
a0576df3ee Add single-string variants to CommandSet construction 2025-10-05 22:41:18 -04:00
Matt Nadareski
e26a222755 Allow nested searches for feature values 2025-10-05 22:32:16 -04:00
Matt Nadareski
608cdae1d8 Add Get/TryGet for Feature in UserInput 2025-10-05 22:24:25 -04:00
Matt Nadareski
0bb996153a Add Get/TryGet for Feature in CommandSet 2025-10-05 22:18:48 -04:00
Matt Nadareski
1ecce85a13 Add command set Get and TryGet tests 2025-10-05 21:52:29 -04:00
Matt Nadareski
644527dbfa Add Get and TryGet to CommandSet 2025-10-05 21:48:12 -04:00
Matt Nadareski
1bd158eba0 Allow controlling detailed help 2025-10-05 20:16:47 -04:00
Matt Nadareski
46d1fb7ca6 Bump version 2025-10-05 20:05:08 -04:00
Matt Nadareski
98617b9961 Fix test for fixed issue 2025-10-05 20:04:04 -04:00
Matt Nadareski
0573e4f180 Fix midpoint printing issue 2025-10-05 20:01:30 -04:00
Matt Nadareski
9ce9d1461e Add Nuget package link 2025-10-05 18:48:56 -04:00
21 changed files with 1692 additions and 51 deletions

View File

@@ -9,17 +9,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
6.0.x
8.0.x
9.0.x
10.0.x
- name: Run tests
run: dotnet test
@@ -27,8 +27,16 @@ jobs:
- name: Run publish script
run: ./publish-nix.sh
- name: Update rolling tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -f rolling
git push origin :refs/tags/rolling || true
git push origin rolling --force
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
uses: ncipollo/release-action@v1.20.0
with:
allowUpdates: True
artifacts: "*.nupkg,*.snupkg"

View File

@@ -6,15 +6,15 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
6.0.x
8.0.x
9.0.x
10.0.x
- name: Build
run: dotnet build

View File

@@ -6,6 +6,8 @@ This library contains logic to both parse commandlines using typed inputs as wel
All inputs allow for non-flag inputs in both `a=b` and `a b` formats. This allows for greater flexibility for cross-platform support. Flags for each input are fully defined by the implementer, so inputs of the form `-a`, `--a`, `/a`, and `a` are all accepted and can even be mixed. The only restriction is that flags should not end with the `=` character as it may interfere with default parsing.
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.CommandLine).
For an example of a program implementing the library as well as the original source of this library code, see [SabreTools](https://github.com/SabreTools/SabreTools).
## Special Types

View File

@@ -1,4 +1,6 @@
using SabreTools.CommandLine.Inputs;
using System;
using System.Collections.Generic;
using SabreTools.CommandLine.Inputs;
using Xunit;
namespace SabreTools.CommandLine.Test
@@ -11,9 +13,16 @@ namespace SabreTools.CommandLine.Test
var input1 = new FlagInput("input1", "--input1", "input1");
var input2 = new FlagInput("input2", "--input2", "input2");
var feature1 = new MockFeature("feature1", "feature1", "feature1");
var inputA = new FlagInput("inputA", "--inputA", "inputA");
var inputB = new FlagInput("inputB", "--inputB", "inputB");
feature1.Add(inputA);
feature1.Add(inputB);
var featureSet = new CommandSet();
featureSet.Add(input1);
featureSet.Add(input2);
featureSet.AddFrom(feature1);
var actualInput1 = featureSet["input1"];
Assert.NotNull(actualInput1);
@@ -25,6 +34,14 @@ namespace SabreTools.CommandLine.Test
var actualInput3 = featureSet["input3"];
Assert.Null(actualInput3);
var actualInputA = featureSet["inputA"];
Assert.NotNull(actualInputA);
Assert.Equal("inputA", actualInputA.Name);
var actualinputB = featureSet["inputB"];
Assert.NotNull(actualinputB);
Assert.Equal("inputB", actualinputB.Name);
}
[Fact]
@@ -90,5 +107,780 @@ namespace SabreTools.CommandLine.Test
bool actualTop3 = featureSet.IsTopLevel("input3");
Assert.False(actualTop3);
}
#region GetBoolean
[Fact]
public void GetBoolean_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new BooleanInput("b", "b", "b");
commandSet.Add(child);
bool actual = commandSet.GetBoolean("c");
Assert.False(actual);
}
[Fact]
public void GetBoolean_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetBoolean("b"));
}
[Fact]
public void GetBoolean_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new BooleanInput("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "true"], ref index);
bool actual = commandSet.GetBoolean("b");
Assert.True(actual);
}
[Fact]
public void GetBoolean_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new BooleanInput("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "true"], ref index);
bool actual = commandSet.GetBoolean("c");
Assert.True(actual);
}
#endregion
#region GetFeature
[Fact]
public void GetFeature_InvalidKey_Null()
{
var commandSet = new CommandSet();
var child = new MockFeature("b", "b", "b");
commandSet.Add(child);
Feature? actual = commandSet.GetFeature("c");
Assert.Null(actual);
}
[Fact]
public void GetFeature_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetFeature("b"));
}
[Fact]
public void GetFeature_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new MockFeature("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b"], ref index);
Feature? actual = commandSet.GetFeature("b");
Assert.NotNull(actual);
Assert.Equal("b", actual.Name);
Assert.True(actual.Value);
}
[Fact]
public void GetFeature_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new MockFeature("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c"], ref index);
Feature? actual = commandSet.GetFeature("c");
Assert.NotNull(actual);
Assert.Equal("c", actual.Name);
Assert.True(actual.Value);
}
#endregion
#region GetInt8
[Fact]
public void GetInt8_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new Int8Input("b", "b", "b");
commandSet.Add(child);
sbyte actual = commandSet.GetInt8("c");
Assert.Equal(sbyte.MinValue, actual);
}
[Fact]
public void GetInt8_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetInt8("b"));
}
[Fact]
public void GetInt8_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new Int8Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
sbyte actual = commandSet.GetInt8("b");
Assert.Equal(5, actual);
}
[Fact]
public void GetInt8_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new Int8Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
sbyte actual = commandSet.GetInt8("c");
Assert.Equal(5, actual);
}
#endregion
#region GetInt16
[Fact]
public void GetInt16_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new Int16Input("b", "b", "b");
commandSet.Add(child);
short actual = commandSet.GetInt16("c");
Assert.Equal(short.MinValue, actual);
}
[Fact]
public void GetInt16_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetInt16("b"));
}
[Fact]
public void GetInt16_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new Int16Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
short actual = commandSet.GetInt16("b");
Assert.Equal(5, actual);
}
[Fact]
public void GetInt16_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new Int16Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
short actual = commandSet.GetInt16("c");
Assert.Equal(5, actual);
}
#endregion
#region GetInt32
[Fact]
public void GetInt32_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new Int32Input("b", "b", "b");
commandSet.Add(child);
int actual = commandSet.GetInt32("c");
Assert.Equal(int.MinValue, actual);
}
[Fact]
public void GetInt32_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetInt32("b"));
}
[Fact]
public void GetInt32_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new Int32Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
int actual = commandSet.GetInt32("b");
Assert.Equal(5, actual);
}
[Fact]
public void GetInt32_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new Int32Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
int actual = commandSet.GetInt32("c");
Assert.Equal(5, actual);
}
#endregion
#region GetInt64
[Fact]
public void GetInt64_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new Int64Input("b", "b", "b");
commandSet.Add(child);
long actual = commandSet.GetInt64("c");
Assert.Equal(long.MinValue, actual);
}
[Fact]
public void GetInt64_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetInt64("b"));
}
[Fact]
public void GetInt64_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new Int64Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
long actual = commandSet.GetInt64("b");
Assert.Equal(5, actual);
}
[Fact]
public void GetInt64_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new Int64Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
long actual = commandSet.GetInt64("c");
Assert.Equal(5, actual);
}
#endregion
#region GetString
[Fact]
public void GetString_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new StringInput("b", "b", "b");
commandSet.Add(child);
string? actual = commandSet.GetString("c");
Assert.Null(actual);
}
[Fact]
public void GetString_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetString("b"));
}
[Fact]
public void GetString_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new StringInput("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "value"], ref index);
string? actual = commandSet.GetString("b");
Assert.Equal("value", actual);
}
[Fact]
public void GetString_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new StringInput("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "value"], ref index);
string? actual = commandSet.GetString("c");
Assert.Equal("value", actual);
}
#endregion
#region GetStringList
[Fact]
public void GetStringList_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new StringListInput("b", "b", "b");
commandSet.Add(child);
List<string> actual = commandSet.GetStringList("c");
Assert.Empty(actual);
}
[Fact]
public void GetStringList_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetStringList("b"));
}
[Fact]
public void GetStringList_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new StringListInput("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "value"], ref index);
List<string> actual = commandSet.GetStringList("b");
string value = Assert.Single(actual);
Assert.Equal("value", value);
}
[Fact]
public void GetStringList_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new StringListInput("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "value"], ref index);
List<string> actual = commandSet.GetStringList("c");
string value = Assert.Single(actual);
Assert.Equal("value", value);
}
#endregion
#region GetUInt8
[Fact]
public void GetUInt8_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new UInt8Input("b", "b", "b");
commandSet.Add(child);
byte actual = commandSet.GetUInt8("c");
Assert.Equal(byte.MinValue, actual);
}
[Fact]
public void GetUInt8_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetUInt8("b"));
}
[Fact]
public void GetUInt8_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new UInt8Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
byte actual = commandSet.GetUInt8("b");
Assert.Equal(5, actual);
}
[Fact]
public void GetUInt8_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new UInt8Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
byte actual = commandSet.GetUInt8("c");
Assert.Equal(5, actual);
}
#endregion
#region GetUInt16
[Fact]
public void GetUInt16_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new UInt16Input("b", "b", "b");
commandSet.Add(child);
ushort actual = commandSet.GetUInt16("c");
Assert.Equal(ushort.MinValue, actual);
}
[Fact]
public void GetUInt16_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetUInt16("b"));
}
[Fact]
public void GetUInt16_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new UInt16Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
ushort actual = commandSet.GetUInt16("b");
Assert.Equal(5, actual);
}
[Fact]
public void GetUInt16_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new UInt16Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
ushort actual = commandSet.GetUInt16("c");
Assert.Equal(5, actual);
}
#endregion
#region GetUInt32
[Fact]
public void GetUInt32_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new UInt32Input("b", "b", "b");
commandSet.Add(child);
uint actual = commandSet.GetUInt32("c");
Assert.Equal(uint.MinValue, actual);
}
[Fact]
public void GetUInt32_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetUInt32("b"));
}
[Fact]
public void GetUInt32_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new UInt32Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
uint actual = commandSet.GetUInt32("b");
Assert.Equal(5u, actual);
}
[Fact]
public void GetUInt32_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new UInt32Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
uint actual = commandSet.GetUInt32("c");
Assert.Equal(5u, actual);
}
#endregion
#region GetUInt64
[Fact]
public void GetUInt64_InvalidKey_DefaultValue()
{
var commandSet = new CommandSet();
var child = new UInt64Input("b", "b", "b");
commandSet.Add(child);
ulong actual = commandSet.GetUInt64("c");
Assert.Equal(ulong.MinValue, actual);
}
[Fact]
public void GetUInt64_Exists_WrongType_Throws()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
Assert.Throws<ArgumentException>(() => _ = commandSet.GetUInt64("b"));
}
[Fact]
public void GetUInt64_Exists_Returns()
{
var commandSet = new CommandSet();
var child = new UInt64Input("b", "b", "b");
commandSet.Add(child);
int index = 0;
child.ProcessInput(["b", "5"], ref index);
ulong actual = commandSet.GetUInt64("b");
Assert.Equal(5u, actual);
}
[Fact]
public void GetUInt64_NestedExists_Returns()
{
var commandSet = new CommandSet();
var child = new MockUserInput("b", "b", "b");
commandSet.Add(child);
var subChild = new UInt64Input("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c", "5"], ref index);
ulong actual = commandSet.GetUInt64("c");
Assert.Equal(5u, actual);
}
#endregion
#region ProcessArgs
[Fact]
public void ProcessArgs_EmptyArgs_Success()
{
var commandSet = new CommandSet();
string[] args = [];
bool actual = commandSet.ProcessArgs(args);
Assert.True(actual);
}
[Fact]
public void ProcessArgs_ValidArgs_Success()
{
var commandSet = new CommandSet();
Feature feature = new MockFeature("a", "a", "a");
feature.Add(new FlagInput("b", "b", "b"));
feature.Add(new FlagInput("c", "c", "c"));
commandSet.Add(feature);
string[] args = ["a", "b", "c"];
bool actual = commandSet.ProcessArgs(args);
Assert.True(actual);
Assert.Empty(feature.Inputs);
}
[Fact]
public void ProcessArgs_InvalidArg_AddedAsGeneric()
{
var commandSet = new CommandSet();
Feature feature = new MockFeature("a", "a", "a");
feature.Add(new FlagInput("b", "b", "b"));
feature.Add(new FlagInput("d", "d", "d"));
commandSet.Add(feature);
string[] args = ["a", "b", "c"];
bool actual = commandSet.ProcessArgs(args);
Assert.True(actual);
string input = Assert.Single(feature.Inputs);
Assert.Equal("c", input);
}
[Fact]
public void ProcessArgs_NestedArgs_Success()
{
var commandSet = new CommandSet();
Feature feature = new MockFeature("a", "a", "a");
var sub = new FlagInput("b", "b", "b");
sub.Add(new FlagInput("c", "c", "c"));
feature.Add(sub);
commandSet.Add(feature);
string[] args = ["a", "b", "c"];
bool actual = commandSet.ProcessArgs(args);
Assert.True(actual);
Assert.Empty(feature.Inputs);
}
#endregion
/// <summary>
/// Mock Feature implementation for testing
/// </summary>
private class MockFeature : Feature
{
public MockFeature(string name, string flag, string description, string? detailed = null)
: base(name, flag, description, detailed)
{
}
public MockFeature(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
}
/// <inheritdoc/>
public override bool Execute() => true;
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
/// <summary>
/// Mock UserInput implementation for testing
/// </summary>
private class MockUserInput : UserInput<object?>
{
public MockUserInput(string name, string flag, string description, string? detailed = null)
: base(name, flag, description, detailed)
{
}
public MockUserInput(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
}
/// <inheritdoc/>
public override bool ProcessInput(string[] args, ref int index) => true;
/// <inheritdoc/>
protected override string FormatFlags() => string.Empty;
}
}
}

View File

@@ -95,7 +95,7 @@ namespace SabreTools.CommandLine.Test
[InlineData(0, -1, "a a")]
[InlineData(-1, 0, "a a")]
[InlineData(0, 0, "a a")]
[InlineData(2, 30, " a a")]
[InlineData(2, 30, " a a")]
[InlineData(4, 0, " a a")]
public void FormatStandardTest(int pre, int midpoint, string expected)
{

View File

@@ -114,6 +114,65 @@ namespace SabreTools.CommandLine.Test.Inputs
#endregion
#region GetFeature
[Fact]
public void GetFeature_InvalidKey_Null()
{
UserInput userInput = new MockUserInput("a", "a", "a");
var child = new MockFeature("b", "b", "b");
userInput.Add(child);
Feature? actual = userInput.GetFeature("c");
Assert.Null(actual);
}
[Fact]
public void GetFeature_Exists_WrongType_Throws()
{
UserInput userInput = new MockUserInput("a", "a", "a");
var child = new MockUserInput("b", "b", "b");
userInput.Add(child);
Assert.Throws<ArgumentException>(() => _ = userInput.GetFeature("b"));
}
[Fact]
public void GetFeature_Exists_Returns()
{
UserInput userInput = new MockUserInput("a", "a", "a");
var child = new MockFeature("b", "b", "b");
userInput.Add(child);
int index = 0;
child.ProcessInput(["b"], ref index);
Feature? actual = userInput.GetFeature("b");
Assert.NotNull(actual);
Assert.Equal("b", actual.Name);
Assert.True(actual.Value);
}
[Fact]
public void GetFeature_NestedExists_Returns()
{
UserInput userInput = new MockUserInput("a", "a", "a");
var child = new MockUserInput("b", "b", "b");
userInput.Add(child);
var subChild = new MockFeature("c", "c", "c");
child.Add(subChild);
int index = 0;
subChild.ProcessInput(["c"], ref index);
Feature? actual = userInput.GetFeature("c");
Assert.NotNull(actual);
Assert.Equal("c", actual.Name);
Assert.True(actual.Value);
}
#endregion
#region GetInt8
[Fact]
@@ -666,6 +725,28 @@ namespace SabreTools.CommandLine.Test.Inputs
#endregion
/// <summary>
/// Mock Feature implementation for testing
/// </summary>
private class MockFeature : Feature
{
public MockFeature(string name, string flag, string description, string? detailed = null)
: base(name, flag, description, detailed)
{
}
public MockFeature(string name, string[] flags, string description, string? detailed = null)
: base(name, flags, description, detailed)
{
}
/// <inheritdoc/>
public override bool Execute() => true;
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
/// <summary>
/// Mock UserInput implementation for testing
/// </summary>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
@@ -16,7 +16,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using SabreTools.CommandLine.Features;
using SabreTools.CommandLine.Inputs;
namespace SabreTools.CommandLine
@@ -39,6 +40,41 @@ namespace SabreTools.CommandLine
#endregion
#region Properties
/// <summary>
/// Custom help text to print instead of the automatically
/// generated generic help
/// </summary>
/// <remarks>
/// This only replaces the automatically generated help
/// for <see cref="OutputGenericHelp"/>. It does not impact
/// either <see cref="OutputAllHelp"/> or <see cref="OutputFeatureHelp"/>.
/// </remarks>
public string? CustomHelp { get; set; }
/// <summary>
/// Feature that represents the default functionality
/// for a command set
/// </summary>
/// <remarks>
/// It is recommended to use this in applications that
/// do not need multiple distinct functional modes.
///
/// When printing help text, the flags and description
/// of this feature will be omitted, instead printing
/// the children of the feature directly at the
/// top level.
///
/// If the default feature is included as a normal
/// top-level input for the command set, then the flags
/// will be printed twice - once under the feature
/// itself and once at the top level.
/// </remarks>
public Feature? DefaultFeature { get; set; }
#endregion
#region Constructors
/// <summary>
@@ -46,6 +82,15 @@ namespace SabreTools.CommandLine
/// </summary>
public CommandSet() { }
/// <summary>
/// Create a new CommandSet with a printable header
/// </summary>
/// <param name="header">Custom commandline header to be printed when outputting help</param>
public CommandSet(string header)
{
_header.Add(header);
}
/// <summary>
/// Create a new CommandSet with a printable header
/// </summary>
@@ -55,6 +100,17 @@ namespace SabreTools.CommandLine
_header.AddRange(header);
}
/// <summary>
/// Create a new CommandSet with a printable header
/// </summary>
/// <param name="header">Custom commandline header to be printed when outputting help</param>
/// <param name="footer">Custom commandline footer to be printed when outputting help</param>
public CommandSet(string header, string footer)
{
_header.Add(header);
_footer.Add(footer);
}
/// <summary>
/// Create a new CommandSet with a printable header
/// </summary>
@@ -99,6 +155,565 @@ namespace SabreTools.CommandLine
public void Add(UserInput input)
=> _inputs.Add(input.Name, input);
/// <summary>
/// Add all children from an input to the set
/// </summary>
/// <param name="input">UserInput object to retrieve children from</param>
/// <remarks>
/// This should only be used in situations where an input is defined
/// but not used within the context of the command set directly.
///
/// This is helpful for when there are applications with default functionality
/// that need to be able to expose both defined features as well as
/// the default functionality in help text.
///
/// If there is any overlap between existing names and the names from
/// any of the children, this operation will overwrite them.
/// </reamrks>
public void AddFrom(UserInput input)
{
foreach (var kvp in input.Children)
{
_inputs.Add(kvp.Key, kvp.Value);
}
}
#endregion
#region Children
/// <summary>
/// Get a boolean value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public bool GetBoolean(string key, bool defaultValue = false)
{
if (TryGetBoolean(key, out bool value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a Feature value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <returns>The value if found, null otherwise</returns>
public Feature? GetFeature(string key)
{
if (TryGetFeature(key, out Feature? value))
return value;
return null;
}
/// <summary>
/// Get an Int8 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public sbyte GetInt8(string key, sbyte defaultValue = sbyte.MinValue)
{
if (TryGetInt8(key, out sbyte value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get an Int16 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public short GetInt16(string key, short defaultValue = short.MinValue)
{
if (TryGetInt16(key, out short value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get an Int32 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public int GetInt32(string key, int defaultValue = int.MinValue)
{
if (TryGetInt32(key, out int value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get an Int64 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public long GetInt64(string key, long defaultValue = long.MinValue)
{
if (TryGetInt64(key, out long value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a string value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public string? GetString(string key, string? defaultValue = null)
{
if (TryGetString(key, out string? value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a string list value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public List<string> GetStringList(string key)
{
if (TryGetStringList(key, out var value))
return value;
return [];
}
/// <summary>
/// Get a UInt8 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public byte GetUInt8(string key, byte defaultValue = byte.MinValue)
{
if (TryGetUInt8(key, out byte value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a UInt16 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public ushort GetUInt16(string key, ushort defaultValue = ushort.MinValue)
{
if (TryGetUInt16(key, out ushort value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a UInt32 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public uint GetUInt32(string key, uint defaultValue = uint.MinValue)
{
if (TryGetUInt32(key, out uint value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a UInt64 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>The value if found, the default value otherwise</returns>
public ulong GetUInt64(string key, ulong defaultValue = ulong.MinValue)
{
if (TryGetUInt64(key, out ulong value, defaultValue))
return value;
return defaultValue;
}
/// <summary>
/// Get a boolean value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetBoolean(string key, out bool value, bool defaultValue = false)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is BooleanInput b)
{
value = b.Value ?? defaultValue;
return true;
}
else if (input is FlagInput f)
{
value = f.Value;
return true;
}
throw new ArgumentException("Input is not a bool");
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetBoolean(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get a Feature value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetFeature(string key, out Feature? value)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not Feature i)
throw new ArgumentException("Input is not a Feature");
value = i;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetFeature(key, out value))
return true;
}
value = null;
return false;
}
/// <summary>
/// Get an Int8 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetInt8(string key, out sbyte value, sbyte defaultValue = sbyte.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not Int8Input i)
throw new ArgumentException("Input is not an sbyte");
value = i.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetInt8(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get an Int16 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetInt16(string key, out short value, short defaultValue = short.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not Int16Input i)
throw new ArgumentException("Input is not a short");
value = i.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetInt16(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get an Int32 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetInt32(string key, out int value, int defaultValue = int.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not Int32Input i)
throw new ArgumentException("Input is not an int");
value = i.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetInt32(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get an Int64 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetInt64(string key, out long value, long defaultValue = long.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not Int64Input l)
throw new ArgumentException("Input is not a long");
value = l.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetInt64(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get a string value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetString(string key, out string? value, string? defaultValue = null)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not StringInput s)
throw new ArgumentException("Input is not a string");
value = s.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetString(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get a string list value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetStringList(string key, out List<string> value)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not StringListInput l)
throw new ArgumentException("Input is not a list");
value = l.Value ?? [];
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetStringList(key, out value))
return true;
}
value = [];
return false;
}
/// <summary>
/// Get a UInt8 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetUInt8(string key, out byte value, byte defaultValue = byte.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not UInt8Input i)
throw new ArgumentException("Input is not an byte");
value = i.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetUInt8(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get a UInt16 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetUInt16(string key, out ushort value, ushort defaultValue = ushort.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not UInt16Input i)
throw new ArgumentException("Input is not a ushort");
value = i.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetUInt16(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get a UInt32 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetUInt32(string key, out uint value, uint defaultValue = uint.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not UInt32Input i)
throw new ArgumentException("Input is not an uint");
value = i.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetUInt32(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
/// <summary>
/// Get a UInt64 value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <param name="defaultValue">Optional default value if not found</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetUInt64(string key, out ulong value, ulong defaultValue = ulong.MinValue)
{
// Try to check immediate children
if (_inputs.TryGetValue(key, out var input))
{
if (input is not UInt64Input l)
throw new ArgumentException("Input is not a ulong");
value = l.Value ?? defaultValue;
return true;
}
// Check all children recursively
foreach (var child in _inputs.Values)
{
if (child.TryGetUInt64(key, out value, defaultValue))
return true;
}
value = defaultValue;
return false;
}
#endregion
#region Inputs
@@ -163,7 +778,8 @@ namespace SabreTools.CommandLine
/// <summary>
/// Output top-level features only
/// </summary>
public void OutputGenericHelp()
/// <param name="detailed">True if the detailed descriptions should be formatted and output, false otherwise</param>
public void OutputGenericHelp(bool detailed = false)
{
// Start building the output list
List<string> output = [];
@@ -172,13 +788,34 @@ namespace SabreTools.CommandLine
if (_header.Count > 0)
output.AddRange(_header);
// Now append all available top-level flags
output.Add("Available options:");
foreach (var input in _inputs.Values)
// If custom help text is defined
if (CustomHelp != null)
{
var outputs = input.Format(pre: 2, midpoint: 30);
if (outputs != null)
output.AddRange(outputs);
string customHelp = CustomHelp.Replace("\r\n", "\n");
string[] customLines = customHelp.Split('\n');
output.AddRange(customLines);
}
else
{
// Append all available top-level flags
output.Add("Available options:");
foreach (var input in _inputs.Values)
{
var outputs = input.Format(pre: 2, midpoint: 30, detailed);
if (outputs != null)
output.AddRange(outputs);
}
// If there is a default feature
if (DefaultFeature != null)
{
foreach (var input in DefaultFeature.Children)
{
var outputs = input.Value.Format(pre: 2, midpoint: 30, detailed);
if (outputs != null)
output.AddRange(outputs);
}
}
}
// Append the footer, if needed
@@ -192,7 +829,8 @@ namespace SabreTools.CommandLine
/// <summary>
/// Output all features recursively
/// </summary>
public void OutputAllHelp()
/// <param name="detailed">True if the detailed descriptions should be formatted and output, false otherwise</param>
public void OutputAllHelp(bool detailed = false)
{
// Start building the output list
List<string> output = [];
@@ -201,15 +839,26 @@ namespace SabreTools.CommandLine
if (_header.Count > 0)
output.AddRange(_header);
// Now append all available flags recursively
// Append all available flags recursively
output.Add("Available options:");
foreach (var input in _inputs.Values)
{
var outputs = input.FormatRecursive(pre: 2, midpoint: 30, detailed: true);
var outputs = input.FormatRecursive(pre: 2, midpoint: 30, detailed);
if (outputs != null)
output.AddRange(outputs);
}
// If there is a default feature
if (DefaultFeature != null)
{
foreach (var input in DefaultFeature.Children)
{
var outputs = input.Value.Format(pre: 2, midpoint: 30, detailed);
if (outputs != null)
output.AddRange(outputs);
}
}
// Append the footer, if needed
if (_footer.Count > 0)
output.AddRange(_footer);
@@ -313,5 +962,71 @@ namespace SabreTools.CommandLine
}
#endregion
#region Processing
/// <summary>
/// Process args list with default handling
/// </summary>
/// <param name="args">Set of arguments to process</param>
/// <returns>True if all arguments were processed correctly, false otherwise</returns>
/// <remarks>
/// This default processing implementation assumes a few key points:
/// - Top-level items are all <see cref="Feature"/>
/// - There is only top-level item allowed at a time
/// - The first argument is always the <see cref="Feature"/> flag
/// </remarks>
public bool ProcessArgs(string[] args)
{
// If there's no arguments, show help
if (args.Length == 0)
{
OutputAllHelp();
return true;
}
// Get the first argument as a feature flag
string featureName = args[0];
// Get the associated feature
var topLevel = GetTopLevel(featureName);
if (topLevel == null || topLevel is not Feature feature)
{
Console.WriteLine($"'{featureName}' is not valid feature flag");
OutputFeatureHelp(featureName);
return false;
}
// Handle default help functionality
if (topLevel is Help helpFeature)
{
helpFeature.ProcessArgs(args, 0, this);
return true;
}
else if (topLevel is HelpExtended helpExtFeature)
{
helpExtFeature.ProcessArgs(args, 0, this);
return true;
}
// Now verify that all other flags are valid
if (!feature.ProcessArgs(args, 1))
{
OutputFeatureHelp(topLevel.Name);
return false;
}
// If inputs are required
if (feature.RequiresInputs && !feature.VerifyInputs())
{
OutputFeatureHelp(topLevel.Name);
return false;
}
// Now execute the current feature
return feature.Execute();
}
#endregion
}
}

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="bool"/>
/// Represents a user input bounded to the range of <see cref="bool"/>
/// </summary>
public class BooleanInput : UserInput<bool?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="short"/>
/// Represents a user input bounded to the range of <see cref="short"/>
/// </summary>
public class Int16Input : UserInput<short?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="int"/>
/// Represents a user input bounded to the range of <see cref="int"/>
/// </summary>
public class Int32Input : UserInput<int?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="long"/>
/// Represents a user input bounded to the range of <see cref="long"/>
/// </summary>
public class Int64Input : UserInput<long?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="sbyte"/>
/// Represents a user input bounded to the range of <see cref="sbyte"/>
/// </summary>
public class Int8Input : UserInput<sbyte?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="ushort"/>
/// Represents a user input bounded to the range of <see cref="ushort"/>
/// </summary>
public class UInt16Input : UserInput<ushort?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="uint"/>
/// Represents a user input bounded to the range of <see cref="uint"/>
/// </summary>
public class UInt32Input : UserInput<uint?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="ulong"/>
/// Represents a user input bounded to the range of <see cref="ulong"/>
/// </summary>
public class UInt64Input : UserInput<ulong?>
{

View File

@@ -3,7 +3,7 @@ using System.Text;
namespace SabreTools.CommandLine.Inputs
{
/// <summary>
/// Represents a user input bounded to the range of <see cref="byte"/>
/// Represents a user input bounded to the range of <see cref="byte"/>
/// </summary>
public class UInt8Input : UserInput<byte?>
{

View File

@@ -41,7 +41,7 @@ namespace SabreTools.CommandLine.Inputs
/// <summary>
/// Set of children associated with this input
/// </summary>
protected readonly Dictionary<string, UserInput> Children = [];
internal protected readonly Dictionary<string, UserInput> Children = [];
#endregion
@@ -86,7 +86,7 @@ namespace SabreTools.CommandLine.Inputs
/// </summary>
public UserInput? this[UserInput subfeature]
{
get
get
{
if (!Children.TryGetValue(subfeature.Name, out var input))
return null;
@@ -135,6 +135,19 @@ namespace SabreTools.CommandLine.Inputs
return defaultValue;
}
/// <summary>
/// Get a Feature value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <returns>The value if found, null otherwise</returns>
public Feature? GetFeature(string key)
{
if (TryGetFeature(key, out Feature? value))
return value;
return null;
}
/// <summary>
/// Get an Int8 value from a named input
/// </summary>
@@ -298,7 +311,7 @@ namespace SabreTools.CommandLine.Inputs
return true;
}
throw new ArgumentException("Feature is not a bool");
throw new ArgumentException("Input is not a bool");
}
// Check all children recursively
@@ -312,6 +325,35 @@ namespace SabreTools.CommandLine.Inputs
return false;
}
/// <summary>
/// Get a Feature value from a named input
/// </summary>
/// <param name="key">Input name to retrieve, if possible</param>
/// <param name="value">Value that was found, default value otherwise</param>
/// <returns>True if the value was found, false otherwise</returns>
public bool TryGetFeature(string key, out Feature? value)
{
// Try to check immediate children
if (Children.TryGetValue(key, out var input))
{
if (input is not Feature i)
throw new ArgumentException("Input is not a Feature");
value = i;
return true;
}
// Check all children recursively
foreach (var child in Children.Values)
{
if (child.TryGetFeature(key, out value))
return true;
}
value = null;
return false;
}
/// <summary>
/// Get an Int8 value from a named input
/// </summary>
@@ -325,7 +367,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not Int8Input i)
throw new ArgumentException("Feature is not an sbyte");
throw new ArgumentException("Input is not an sbyte");
value = i.Value ?? defaultValue;
return true;
@@ -355,7 +397,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not Int16Input i)
throw new ArgumentException("Feature is not a short");
throw new ArgumentException("Input is not a short");
value = i.Value ?? defaultValue;
return true;
@@ -385,7 +427,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not Int32Input i)
throw new ArgumentException("Feature is not an int");
throw new ArgumentException("Input is not an int");
value = i.Value ?? defaultValue;
return true;
@@ -415,7 +457,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not Int64Input l)
throw new ArgumentException("Feature is not a long");
throw new ArgumentException("Input is not a long");
value = l.Value ?? defaultValue;
return true;
@@ -445,7 +487,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not StringInput s)
throw new ArgumentException("Feature is not a string");
throw new ArgumentException("Input is not a string");
value = s.Value ?? defaultValue;
return true;
@@ -474,7 +516,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not StringListInput l)
throw new ArgumentException("Feature is not a list");
throw new ArgumentException("Input is not a list");
value = l.Value ?? [];
return true;
@@ -504,7 +546,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not UInt8Input i)
throw new ArgumentException("Feature is not an byte");
throw new ArgumentException("Input is not an byte");
value = i.Value ?? defaultValue;
return true;
@@ -534,7 +576,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not UInt16Input i)
throw new ArgumentException("Feature is not a ushort");
throw new ArgumentException("Input is not a ushort");
value = i.Value ?? defaultValue;
return true;
@@ -564,7 +606,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not UInt32Input i)
throw new ArgumentException("Feature is not an uint");
throw new ArgumentException("Input is not an uint");
value = i.Value ?? defaultValue;
return true;
@@ -594,7 +636,7 @@ namespace SabreTools.CommandLine.Inputs
if (Children.TryGetValue(key, out var input))
{
if (input is not UInt64Input l)
throw new ArgumentException("Feature is not a ulong");
throw new ArgumentException("Input is not a ulong");
value = l.Value ?? defaultValue;
return true;
@@ -699,13 +741,14 @@ namespace SabreTools.CommandLine.Inputs
{
var output = new StringBuilder();
output.Append(CreatePadding(pre));
output.Append(FormatFlags());
// Determine the midpoint padding size
int midpointPadding = midpoint > 0 && output.Length < midpoint
? midpoint - output.Length
: 1;
output.Append(CreatePadding(pre));
output.Append(FormatFlags());
output.Append(CreatePadding(midpointPadding));
output.Append(Description);

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<IncludeSymbols>true</IncludeSymbols>
@@ -11,7 +11,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.3.0</Version>
<Version>1.4.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>

View File

@@ -1,7 +1,7 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
# - .NET 10.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.

View File

@@ -1,5 +1,5 @@
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
# - .NET 10.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.