diff --git a/ConsoleAppFramework.sln b/ConsoleAppFramework.sln
index 23faea1..a3eb377 100644
--- a/ConsoleAppFramework.sln
+++ b/ConsoleAppFramework.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28307.168
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29728.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1F399F98-7439-4F05-847B-CC1267B4B7F2}"
EndProject
@@ -24,9 +24,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".circleci", ".circleci", "{
.circleci\config.yml = .circleci\config.yml
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppFramework.WebHosting", "src\ConsoleAppFramework.WebHosting\ConsoleAppFramework.WebHosting.csproj", "{9AC1CAE2-E717-472A-BBFB-0FE5590E5C7A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleAppFramework.WebHosting", "src\ConsoleAppFramework.WebHosting\ConsoleAppFramework.WebHosting.csproj", "{9AC1CAE2-E717-472A-BBFB-0FE5590E5C7A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebHostingApp", "sandbox\WebHostingApp\WebHostingApp.csproj", "{2B7CDEFC-3D92-4B72-8898-2494D7B087AD}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHostingApp", "sandbox\WebHostingApp\WebHostingApp.csproj", "{2B7CDEFC-3D92-4B72-8898-2494D7B087AD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppFramework.Integration.Test", "tests\ConsoleAppFramework.Integration.Test\ConsoleAppFramework.Integration.Test.csproj", "{6A39E146-8CDF-4B04-88ED-395C56A32722}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -62,6 +64,10 @@ Global
{2B7CDEFC-3D92-4B72-8898-2494D7B087AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B7CDEFC-3D92-4B72-8898-2494D7B087AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B7CDEFC-3D92-4B72-8898-2494D7B087AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A39E146-8CDF-4B04-88ED-395C56A32722}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A39E146-8CDF-4B04-88ED-395C56A32722}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A39E146-8CDF-4B04-88ED-395C56A32722}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A39E146-8CDF-4B04-88ED-395C56A32722}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -74,6 +80,7 @@ Global
{AF15C841-5D45-4E61-BFCE-A6E6B7BA7629} = {AAD2D900-C305-4449-A9FC-6C7696FFEDFA}
{9AC1CAE2-E717-472A-BBFB-0FE5590E5C7A} = {1F399F98-7439-4F05-847B-CC1267B4B7F2}
{2B7CDEFC-3D92-4B72-8898-2494D7B087AD} = {A2CF2984-E8E2-48FC-B5A1-58D74A2467E6}
+ {6A39E146-8CDF-4B04-88ED-395C56A32722} = {AAD2D900-C305-4449-A9FC-6C7696FFEDFA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7F3E353A-C125-4020-8481-11DC6496358C}
diff --git a/ConsoleAppFramework.sln.DotSettings b/ConsoleAppFramework.sln.DotSettings
new file mode 100644
index 0000000..025f9b6
--- /dev/null
+++ b/ConsoleAppFramework.sln.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ True
\ No newline at end of file
diff --git a/ReadMe.md b/ReadMe.md
index def9921..3400b40 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -58,10 +58,10 @@ public void Hello(
{
```
-`help` command(or no argument to pass) shows there detail. This help format is same as `dotnet` command.
+`-help` option (or no argument to pass) shows there detail. This help format is same as `dotnet` command.
```
-> SampleApp.exe help
+> SampleApp.exe -help
Usage: SampleApp [options...]
Options:
@@ -69,10 +69,10 @@ Options:
-r, -repeat repeat count. (Default: 3)
```
-`version` command shows `AssemblyInformationalVersion` or `AssemblylVersion`.
+`-version` option shows `AssemblyInformationalVersion` or `AssemblylVersion`.
```
-> SampleApp.exe version
+> SampleApp.exe -version
1.0.0
```
diff --git a/src/ConsoleAppFramework/ConsoleAppEngine.cs b/src/ConsoleAppFramework/ConsoleAppEngine.cs
index 3738924..acd7c03 100644
--- a/src/ConsoleAppFramework/ConsoleAppEngine.cs
+++ b/src/ConsoleAppFramework/ConsoleAppEngine.cs
@@ -82,7 +82,7 @@ public async Task RunAsync(Type type, string[] args)
{
if (method != null)
{
- await SetFailAsync(ctx, "Found two public methods(wihtout command). Type:" + type.FullName + " Method:" + method.Name + " and " + item.Name);
+ await SetFailAsync(ctx, "Found more than one public methods(without command). Type:" + type.FullName + " Method:" + method.Name + " and " + item.Name);
return;
}
method = item; // found single public(non-command) method.
@@ -205,18 +205,36 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
{
var jsonOption = (JsonSerializerOptions)provider.GetService(typeof(JsonSerializerOptions));
- var argumentDictionary = ParseArgument(args, argsOffset);
+ // Collect option types for parsing command-line arguments.
+ var optionTypeByOptionName = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ var item = parameters[i];
+ var option = item.GetCustomAttribute();
+
+ optionTypeByOptionName[item.Name] = item.ParameterType;
+ if (!string.IsNullOrWhiteSpace(option?.ShortName))
+ {
+ optionTypeByOptionName[option!.ShortName!] = item.ParameterType;
+ }
+ }
+
+ var argumentDictionary = ParseArgument(args, argsOffset, optionTypeByOptionName);
invokeArgs = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
var item = parameters[i];
var option = item.GetCustomAttribute();
+ if (!string.IsNullOrWhiteSpace(option?.ShortName) && char.IsDigit(option!.ShortName, 0)) throw new InvalidOperationException($"Option '{item.Name}' has a short name, but the short name must start with A-Z or a-z.");
var value = default(OptionParameter);
if (option != null && option.Index != -1)
{
- value = new OptionParameter { Value = args[argsOffset + i] };
+ if (argsOffset + i < args.Length)
+ {
+ value = new OptionParameter { Value = args[argsOffset + i] };
+ }
}
if (value.Value != null || argumentDictionary.TryGetValue(item.Name, out value) || argumentDictionary.TryGetValue(option?.ShortName?.TrimStart('-') ?? "", out value))
@@ -277,7 +295,7 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
}
catch
{
- errorMessage = "Parameter \"" + item.Name + "\"" + " fail on JSON deserialize, plaease check type or JSON escape or add double-quotation.";
+ errorMessage = "Parameter \"" + item.Name + "\"" + " fail on JSON deserialize, please check type or JSON escape or add double-quotation.";
return false;
}
}
@@ -290,7 +308,7 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
}
catch
{
- errorMessage = "Parameter \"" + item.Name + "\"" + " fail on JSON deserialize, plaease check type or JSON escape or add double-quotation.";
+ errorMessage = "Parameter \"" + item.Name + "\"" + " fail on JSON deserialize, please check type or JSON escape or add double-quotation.";
return false;
}
}
@@ -335,7 +353,7 @@ bool TryGetInvokeArguments(ParameterInfo[] parameters, string?[] args, int argsO
return null;
}
- static ReadOnlyDictionary ParseArgument(string?[] args, int argsOffset)
+ static ReadOnlyDictionary ParseArgument(string?[] args, int argsOffset, IReadOnlyDictionary optionTypeByName)
{
var dict = new Dictionary(args.Length, StringComparer.OrdinalIgnoreCase);
for (int i = argsOffset; i < args.Length;)
@@ -347,21 +365,19 @@ static ReadOnlyDictionary ParseArgument(string?[] args,
}
key = key.TrimStart('-');
- if (i >= args.Length)
- {
- dict.Add(key, new OptionParameter { BooleanSwitch = true }); // Last parameter
- break;
- }
- var value = args[i];
- if (value != null && !value.StartsWith("-"))
- {
- dict.Add(key, new OptionParameter { Value = value });
- i++;
- }
- else
+ if (optionTypeByName.TryGetValue(key, out var optionType))
{
- dict.Add(key, new OptionParameter { BooleanSwitch = true });
+ if (optionType == typeof(bool))
+ {
+ dict.Add(key, new OptionParameter { BooleanSwitch = true });
+ }
+ else
+ {
+ var value = args[i];
+ dict.Add(key, new OptionParameter { Value = value });
+ i++;
+ }
}
}
diff --git a/src/ConsoleAppFramework/ConsoleAppEngineHostBuilderExtensions.cs b/src/ConsoleAppFramework/ConsoleAppEngineHostBuilderExtensions.cs
index a898818..33306ee 100644
--- a/src/ConsoleAppFramework/ConsoleAppEngineHostBuilderExtensions.cs
+++ b/src/ConsoleAppFramework/ConsoleAppEngineHostBuilderExtensions.cs
@@ -150,26 +150,26 @@ IHostBuilder ConfigureEmptyService()
else
{
// override default Help
- args = new string[] { "help" };
+ args = new string[] { "--help" };
}
}
}
- if (!hasHelp && args.Length == 1 && TrimEquals(args[0], HelpCommand))
+ if (!hasHelp && args.Length == 1 && OptionEquals(args[0], HelpCommand))
{
Console.Write(new CommandHelpBuilder().BuildHelpMessage(methods, defaultMethod));
ConfigureEmptyService();
return hostBuilder;
}
- if (args.Length == 1 && TrimEquals(args[0], VersionCommand))
+ if (args.Length == 1 && OptionEquals(args[0], VersionCommand))
{
ShowVersion();
ConfigureEmptyService();
return hostBuilder;
}
- if (args.Length == 2 && methods.Length != 1)
+ if (args.Length == 2 && methods.Length > 0 && defaultMethod == null)
{
int methodIndex = -1;
@@ -179,7 +179,7 @@ IHostBuilder ConfigureEmptyService()
methodIndex = 1;
}
// command -help
- else if (TrimEquals(args[1], HelpCommand))
+ else if (OptionEquals(args[1], HelpCommand))
{
methodIndex = 0;
}
@@ -223,6 +223,11 @@ static bool TrimEquals(string arg, string command)
return arg.Trim('-').Equals(command, StringComparison.OrdinalIgnoreCase);
}
+ static bool OptionEquals(string arg, string command)
+ {
+ return arg.StartsWith("-") && arg.Trim('-').Equals(command, StringComparison.OrdinalIgnoreCase);
+ }
+
static void ShowVersion()
{
var asm = Assembly.GetEntryAssembly();
diff --git a/tests/ConsoleAppFramework.Integration.Test/AssemblyInfo.cs b/tests/ConsoleAppFramework.Integration.Test/AssemblyInfo.cs
new file mode 100644
index 0000000..b4c10db
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using Xunit;
+
+// NOTE: This test project contains integration tests that use `Console.Out` directly. Therefore, the tests must be run sequentially.
+[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]
\ No newline at end of file
diff --git a/tests/ConsoleAppFramework.Integration.Test/CaptureConsoleOutput.cs b/tests/ConsoleAppFramework.Integration.Test/CaptureConsoleOutput.cs
new file mode 100644
index 0000000..3816e8a
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/CaptureConsoleOutput.cs
@@ -0,0 +1,25 @@
+using System;
+using System.IO;
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public class CaptureConsoleOutput : IDisposable
+ {
+ private readonly TextWriter _originalWriter;
+ private readonly StringWriter _stringWriter;
+
+ public CaptureConsoleOutput()
+ {
+ _originalWriter = Console.Out;
+ _stringWriter = new StringWriter();
+ Console.SetOut(_stringWriter);
+ }
+
+ public string Output => _stringWriter.ToString();
+
+ public void Dispose()
+ {
+ Console.SetOut(_originalWriter);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ConsoleAppFramework.Integration.Test/ConsoleAppFramework.Integration.Test.csproj b/tests/ConsoleAppFramework.Integration.Test/ConsoleAppFramework.Integration.Test.csproj
new file mode 100644
index 0000000..a067f97
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/ConsoleAppFramework.Integration.Test.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp3.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/ConsoleAppFramework.Integration.Test/MultipleCommandTest.cs b/tests/ConsoleAppFramework.Integration.Test/MultipleCommandTest.cs
new file mode 100644
index 0000000..4acf474
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/MultipleCommandTest.cs
@@ -0,0 +1,171 @@
+using System;
+using FluentAssertions;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public partial class MultipleCommandTest
+ {
+ [Fact]
+ public void NoCommandAttribute()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Found more than one public methods(without command).");
+ }
+
+ public class CommandTests_Multiple_NoCommandAttribute : ConsoleAppBase
+ {
+ public void Hello() => Console.WriteLine("Hello");
+ public void Konnichiwa() => Console.WriteLine("Konnichiwa");
+ }
+
+ [Fact]
+ public void Commands()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Commands:");
+ console.Output.Should().Contain("hello");
+ console.Output.Should().Contain("konnichiwa");
+ }
+
+ [Fact]
+ public void Commands_UnknownCommand()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "unknown-command" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Commands:");
+ console.Output.Should().Contain("hello");
+ console.Output.Should().Contain("konnichiwa");
+ }
+
+ [Fact]
+ public void Commands_UnknownCommand_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "help", "-foo", "-bar" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Commands:");
+ console.Output.Should().Contain("hello");
+ console.Output.Should().Contain("konnichiwa");
+ }
+
+ public class CommandTests_Multiple_Commands : ConsoleAppBase
+ {
+ [Command("hello")]
+ public void Hello() => Console.WriteLine("Hello");
+ [Command("konnichiwa")]
+ public void Konnichiwa() => Console.WriteLine("Konnichiwa");
+ }
+
+ [Fact]
+ public void OptionAndArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp (18)");
+ }
+
+ [Fact]
+ public void OptionAndArg_Option()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "Cysharp", "-age", "-128" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp (-128)");
+ }
+
+ [Fact]
+ public void OptionAndArg_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello help (18)");
+ }
+
+ [Fact]
+ public void OptionAndArg_HelpAndOtherArgs()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "help", "-age", "-128" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+
+ console.Output.Should().Contain("Hello help (-128)");
+ }
+
+ [Fact]
+ public void OptionAndArg_HelpOptionLike()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Arguments:");
+
+ // NOTE: Currently, ConsoleAppFramework treats the first argument as special. If the argument is '-help', it is same as '-help' option.
+ //console.Output.Should().Contain("Hello -help (-128)");
+ }
+
+ [Fact]
+ public void OptionAndArg_HelpOptionLikeAndOtherOptions()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "-help", "-age", "-128" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+
+ console.Output.Should().Contain("Hello -help (-128)");
+ }
+
+ [Fact]
+ public void CommandHelp_OptionAndArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "help", "hello" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Arguments:");
+ }
+
+ public class CommandTests_Multiple_OptionAndArg : ConsoleAppBase
+ {
+ [Command("hello")]
+ public void Hello([Option(0)]string name, int age = 18) => Console.WriteLine($"Hello {name} ({age})");
+ [Command("konnichiwa")]
+ public void Konnichiwa() => Console.WriteLine("Konnichiwa");
+ }
+
+ [Fact]
+ public void OptionHelp()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Commands:");
+ console.Output.Should().Contain("hello");
+ console.Output.Should().Contain("konnichiwa");
+ }
+
+ [Fact]
+ public void OptionVersion()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "-version" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().MatchRegex(@"\d.\d.\d"); // NOTE: When running with unit test runner, it returns a version of the runner.
+ }
+
+ }
+}
diff --git a/tests/ConsoleAppFramework.Integration.Test/NamedSingleCommandTest.cs b/tests/ConsoleAppFramework.Integration.Test/NamedSingleCommandTest.cs
new file mode 100644
index 0000000..6401f75
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/NamedSingleCommandTest.cs
@@ -0,0 +1,83 @@
+using System;
+using FluentAssertions;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public partial class NamedSingleCommandTest
+ {
+ [Fact]
+ public void NamedCommand_NoArgs_CommandIsNotSpecified()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Commands:");
+ }
+
+ [Fact]
+ public void NamedCommand_NoArgs_Invoke()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello");
+ }
+
+ [Fact]
+ public void NamedCommand_NoArgs_CommandHelp()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "help", "hello" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain(" hello");
+ }
+
+ public class CommandTests_Single_Named_NoArgs : ConsoleAppBase
+ {
+ [Command("hello")]
+ public void Hello() => Console.WriteLine("Hello");
+ }
+
+ [Fact]
+ public void NamedCommand_OneArg_CommandIsNotSpecified()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Commands:");
+ }
+
+ [Fact]
+ public void NamedCommand_OneArg_Invoke()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "hello", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp");
+ }
+
+ [Fact]
+ public void NamedCommand_OneArg_CommandHelp()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "help", "hello" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Arguments:");
+ }
+
+ public class CommandTests_Single_Named_OneArg : ConsoleAppBase
+ {
+ [Command("hello")]
+ public void Hello([Option(0)]string name) => Console.WriteLine($"Hello {name}");
+ }
+
+ }
+}
diff --git a/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.Arguments.cs b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.Arguments.cs
new file mode 100644
index 0000000..b5f99af
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.Arguments.cs
@@ -0,0 +1,127 @@
+using System;
+using FluentAssertions;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public partial class SingleCommandTest
+ {
+ [Fact]
+ public void NoOptions_OneRequiredArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp");
+ }
+
+ [Fact]
+ public void NoOptions_OneRequiredArg_ArgHelp()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello help");
+ }
+
+ [Fact]
+ public void NoOptions_OneRequiredArg_Insufficient()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Arguments:");
+ }
+
+ [Fact]
+ public void NoOptions_OneRequiredArg_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Arguments:");
+
+ // NOTE: Currently, ConsoleAppFramework treats the first argument as special. If the argument is '-help', it is same as '-help' option.
+ //console.Output.Should().Contain("Hello -version");
+ }
+
+ [Fact]
+ public void NoOptions_OneRequiredArg_Version()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-version" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().MatchRegex(@"\d.\d.\d"); // NOTE: When running with unit test runner, it returns a version of the runner.
+
+ // NOTE: Currently, ConsoleAppFramework treats the first argument as special. If the argument is '-help', it is same as '-help' option.
+ //console.Output.Should().Contain("Hello -version");
+ }
+
+ public class CommandTests_Single_NoOptions_OneRequiredArg : ConsoleAppBase
+ {
+ public void Hello([Option(0)]string name) => Console.WriteLine($"Hello {name}");
+ }
+
+ [Fact]
+ public void NoOptions_OneOptionalArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp");
+ }
+
+ [Fact]
+ public void NoOptions_OneOptionalArg_ArgHelp()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello help");
+ }
+
+ [Fact]
+ public void NoOptions_OneOptionalArg_NoInputArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Anonymous");
+ }
+
+ [Fact]
+ public void NoOptions_OneOptionalArg_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Arguments:");
+
+ // NOTE: Currently, ConsoleAppFramework treats the first argument as special. If the argument is '-help', it is same as '-help' option.
+ //console.Output.Should().Contain("Hello -help");
+ }
+
+ [Fact]
+ public void NoOptions_OneOptionalArg_Version()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-version" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().MatchRegex(@"\d.\d.\d"); // NOTE: When running with unit test runner, it returns a version of the runner.
+
+ // NOTE: Currently, ConsoleAppFramework treats the first argument as special. If the argument is '-help', it is same as '-help' option.
+ //console.Output.Should().Contain("Hello -version");
+ }
+
+ public class CommandTests_Single_NoOptions_OneOptionalArgs : ConsoleAppBase
+ {
+ public void Hello([Option(0)]string name = "Anonymous") => Console.WriteLine($"Hello {name}");
+ }
+ }
+}
diff --git a/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.Options.cs b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.Options.cs
new file mode 100644
index 0000000..a12f037
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.Options.cs
@@ -0,0 +1,207 @@
+using System;
+using FluentAssertions;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public partial class SingleCommandTest
+ {
+ [Fact]
+ public void OneRequiredOption_NoArgs()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp");
+ }
+
+ [Fact]
+ public void OneRequiredOption_NoArgs_OptionLikeValue()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello -help");
+ }
+
+ [Fact]
+ public void OneRequiredOption_NoArgs_Insufficient()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ [Fact]
+ public void OneRequiredOption_NoArgs_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ public class CommandTests_Single_OneRequiredOption_NoArgs : ConsoleAppBase
+ {
+ public void Hello(string name) => Console.WriteLine($"Hello {name}");
+ }
+
+ [Fact]
+ public void OneRequiredOneOptionalOptions_NoArgs_0()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp (17)");
+ }
+
+ [Fact]
+ public void OneRequiredOneOptionalOptions_NoArgs_1()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "Cysharp", "-age", "256" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp (256)");
+ }
+
+ [Fact]
+ public void OneRequiredOneOptionalOptions_NoArgs_OptionLikeValue()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "-help", "-age", "256" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello -help (256)");
+ }
+
+ [Fact]
+ public void OneRequiredOneOptionalOptions_NoArgs_Insufficient()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ [Fact]
+ public void OneRequiredOneOptionalOptions_NoArgs_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ public class CommandTests_Single_OneRequiredOneOptionalOptions_NoArgs : ConsoleAppBase
+ {
+ public void Hello(string name, int age = 17) => Console.WriteLine($"Hello {name} ({age})");
+ }
+
+
+ [Fact]
+ public void TwoOptionalOptions_NoArgs_0()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp (17)");
+ }
+
+ [Fact]
+ public void TwoOptionalOptions_NoArgs_1()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "Cysharp", "-age", "256" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp (256)");
+ }
+
+ [Fact]
+ public void TwoOptionalOptions_NoArgs_2()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-age", "-256" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Anonymous (-256)");
+ }
+
+ [Fact]
+ public void TwoOptionalOptions_NoArgs_Ambiguous()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-name", "-help", "-age", "256" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello -help (256)");
+ // console.GetOutputText().Should().Contain("Usage:");
+ // console.GetOutputText().Should().Contain("Options:");
+ }
+
+ [Fact]
+ public void TwoOptionalOptions_NoArgs_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ [Fact]
+ public void TwoOptionalOptions_NoArgs_AllDefaultValue()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Anonymous (17)");
+ }
+
+ public class CommandTests_Single_TwoOptionalOptions_NoArgs : ConsoleAppBase
+ {
+ public void Hello(string name = "Anonymous", int age = 17) => Console.WriteLine($"Hello {name} ({age})");
+ }
+
+ [Fact]
+ public void RequiredBoolAndOtherOption_NoArgs()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "-hello", "-name", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp");
+ }
+
+ public class CommandTests_Single_RequiredBoolAndOtherOption_NoArgs : ConsoleAppBase
+ {
+ public void Hello(bool hello, string name) => Console.WriteLine($"{(hello ? "Hello" : "Konnichiwa")} {name}");
+ }
+
+ [Fact]
+ public void OptionalBoolAndRequiredOtherOption_NoArgs()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "-name", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Konnichiwa Cysharp");
+ }
+
+ [Fact]
+ public void OptionalBoolAndRequiredOtherOption_NoArgs_1()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "-hello", "-name", "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello Cysharp");
+ }
+
+ public class CommandTests_Single_OptionalBoolAndRequiredOtherOption_NoArgs : ConsoleAppBase
+ {
+ public void Hello(string name, bool hello = false) => Console.WriteLine($"{(hello ? "Hello" : "Konnichiwa")} {name}");
+ }
+ }
+}
diff --git a/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.OptionsAndArguments.cs b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.OptionsAndArguments.cs
new file mode 100644
index 0000000..822463c
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.OptionsAndArguments.cs
@@ -0,0 +1,97 @@
+using System;
+using FluentAssertions;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public partial class SingleCommandTest
+ {
+ [Fact]
+ public void OneRequiredOption_OneRequiredArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "Cysharp", "-age", "18" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Cysharp (18)");
+ }
+
+ [Fact]
+ public void OneRequiredOption_OneRequiredArg_OptionLikeValueArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "--C--", "-age", "18" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("--C-- (18)");
+ }
+
+ [Fact]
+ public void OneRequiredOption_OneRequiredArg_Insufficient()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ [Fact]
+ public void OneRequiredOption_OneRequiredArg_Insufficient_Options()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Required parameter \"age\"");
+ }
+
+ [Fact]
+ public void OneRequiredOption_OneRequiredArg_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ public class CommandTests_OneRequiredOption_OneRequiredArg : ConsoleAppBase
+ {
+ public void Hello([Option(0)]string name, int age) => Console.WriteLine($"{name} ({age})");
+ }
+
+ [Fact]
+ public void OneOptionalOption_OneRequiredArg()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "Cysharp" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Cysharp (17)");
+ }
+
+ [Fact]
+ public void OneOptionalOption_OneRequiredArg_Option()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "Cysharp", "-age", "18" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Cysharp (18)");
+ }
+
+ [Fact]
+ public void OneOptionalOption_OneRequiredArg_Help()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new[] { "-help" };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Usage:");
+ console.Output.Should().Contain("Options:");
+ }
+
+ public class CommandTests_OneOptionalOption_OneRequiredArg : ConsoleAppBase
+ {
+ public void Hello([Option(0)]string name, int age = 17) => Console.WriteLine($"{name} ({age})");
+ }
+ }
+}
diff --git a/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.cs b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.cs
new file mode 100644
index 0000000..6399fc6
--- /dev/null
+++ b/tests/ConsoleAppFramework.Integration.Test/SingleCommandTest.cs
@@ -0,0 +1,26 @@
+using System;
+using FluentAssertions;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace ConsoleAppFramework.Integration.Test
+{
+ public partial class SingleCommandTest
+ {
+ [Fact]
+ public void NoOptions_NoArgs()
+ {
+ using var console = new CaptureConsoleOutput();
+ var args = new string[] { };
+ Host.CreateDefaultBuilder().RunConsoleAppFrameworkAsync(args);
+ console.Output.Should().Contain("Hello");
+ }
+
+ public class CommandTests_Single_NoOptions_NoArgs : ConsoleAppBase
+ {
+ public void Hello() => Console.WriteLine("Hello");
+ }
+ }
+}
diff --git a/tests/ConsoleAppFramework.Tests/SingleContainedTest.cs b/tests/ConsoleAppFramework.Tests/SingleContainedTest.cs
index 5ce1927..f87ad35 100644
--- a/tests/ConsoleAppFramework.Tests/SingleContainedTest.cs
+++ b/tests/ConsoleAppFramework.Tests/SingleContainedTest.cs
@@ -116,8 +116,8 @@ public async Task SimpleComplexArgsTest()
public class TwoArgsWithOption : ConsoleAppBase
{
public void Hello(
- [Option("-n", "name of this")]string name,
- [Option("-r", "repeat msg")]int repeat)
+ [Option("n", "name of this")]string name,
+ [Option("r", "repeat msg")]int repeat)
{
Context.Logger.LogInformation($"name:{name}");
Context.Logger.LogInformation($"repeat:{repeat}");