diff --git a/CommandDotNet.Example/CommandDotNet.Example.csproj b/CommandDotNet.Example/CommandDotNet.Example.csproj
index 009a3beb8..f174f5402 100644
--- a/CommandDotNet.Example/CommandDotNet.Example.csproj
+++ b/CommandDotNet.Example/CommandDotNet.Example.csproj
@@ -8,6 +8,7 @@
+
diff --git a/CommandDotNet.Example/Examples.cs b/CommandDotNet.Example/Examples.cs
index e3c7a4a30..17bc1e3d3 100644
--- a/CommandDotNet.Example/Examples.cs
+++ b/CommandDotNet.Example/Examples.cs
@@ -14,27 +14,6 @@ namespace CommandDotNet.Example
"Example: %AppName% [debug] [parse] [log:info] cancel-me")]
internal class Examples
{
- private static bool _inSession;
-
- [DefaultMethod]
- public void StartSession(
- CommandContext context,
- InteractiveSession interactiveSession,
- [Option(ShortName = "i")] bool interactive)
- {
- if (interactive && !_inSession)
- {
- context.Console.WriteLine("start session");
- _inSession = true;
- interactiveSession.Start();
- }
- else
- {
- context.Console.WriteLine($"no session {interactive} {_inSession}");
- context.ShowHelpOnExit = true;
- }
- }
-
[SubCommand]
public Git Git { get; set; } = null!;
diff --git a/CommandDotNet.Example/InteractiveMiddleware.cs b/CommandDotNet.Example/InteractiveMiddleware.cs
deleted file mode 100644
index 57c21f627..000000000
--- a/CommandDotNet.Example/InteractiveMiddleware.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace CommandDotNet.Example
-{
- public static class InteractiveMiddleware
- {
- public static AppRunner UseInteractiveMode(this AppRunner appRunner, string appName)
- {
- return appRunner.Configure(c =>
- {
- // use the existing appRunner to reuse the configuration.
- c.UseParameterResolver(ctx => new InteractiveSession(appRunner, appName, ctx));
- });
- }
- }
-}
\ No newline at end of file
diff --git a/CommandDotNet.Example/Program.cs b/CommandDotNet.Example/Program.cs
index eb615d2b7..507e6ae96 100644
--- a/CommandDotNet.Example/Program.cs
+++ b/CommandDotNet.Example/Program.cs
@@ -2,6 +2,7 @@
using CommandDotNet.Diagnostics;
using CommandDotNet.FluentValidation;
using CommandDotNet.NameCasing;
+using CommandDotNet.ReadLineRepl;
namespace CommandDotNet.Example
{
@@ -24,7 +25,7 @@ public static AppRunner GetAppRunner(NameValueCollection? appSettings = null)
.UseLog2ConsoleDirective()
.UseNameCasing(Case.KebabCase)
.UseFluentValidation()
- .UseInteractiveMode("Example")
+ .UseRepl(replConfig: new ReplConfig {AppName = "Example", ReplOption = {LongName = "interactive", ShortName = 'i'}})
.UseDefaultsFromAppSetting(appSettings, includeNamingConventions: true);
}
}
diff --git a/CommandDotNet.ReadLineRepl/CommandDotNet.ReadLineRepl.csproj b/CommandDotNet.ReadLineRepl/CommandDotNet.ReadLineRepl.csproj
new file mode 100644
index 000000000..a4bbd50c6
--- /dev/null
+++ b/CommandDotNet.ReadLineRepl/CommandDotNet.ReadLineRepl.csproj
@@ -0,0 +1,27 @@
+
+
+
+ CommandDotNet.ReadLineRepl
+ Provides an interactive session using the GNU style readline package
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/CommandDotNet.ReadLineRepl/MiddlewareSteps.cs b/CommandDotNet.ReadLineRepl/MiddlewareSteps.cs
new file mode 100644
index 000000000..8b582a393
--- /dev/null
+++ b/CommandDotNet.ReadLineRepl/MiddlewareSteps.cs
@@ -0,0 +1,9 @@
+using CommandDotNet.Execution;
+
+namespace CommandDotNet.ReadLineRepl
+{
+ public static class MiddlewareSteps
+ {
+ public static MiddlewareStep ReplSession { get; } = CommandDotNet.Execution.MiddlewareSteps.Help.CheckIfShouldShowHelp - 1000;
+ }
+}
\ No newline at end of file
diff --git a/CommandDotNet.ReadLineRepl/ReplConfig.cs b/CommandDotNet.ReadLineRepl/ReplConfig.cs
new file mode 100644
index 000000000..bb703c573
--- /dev/null
+++ b/CommandDotNet.ReadLineRepl/ReplConfig.cs
@@ -0,0 +1,51 @@
+using System;
+using CommandDotNet.Builders;
+
+namespace CommandDotNet.ReadLineRepl
+{
+ public class ReplConfig
+ {
+ private Func? _sessionInitMessage;
+ private Func? _sessionHelpMessage;
+
+ public string? AppName { get; set; }
+
+ public ReplOption ReplOption { get; set; } = new ReplOption();
+
+ public Func GetSessionInitMessage
+ {
+ get => _sessionInitMessage ?? BuildSessionInit ;
+ set => _sessionInitMessage = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+
+ public Func GetSessionHelpMessage
+ {
+ get => _sessionHelpMessage ?? BuildSessionHelp;
+ set => _sessionHelpMessage = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ private string BuildSessionInit(CommandContext context)
+ {
+ var appInfo = AppInfo.GetAppInfo();
+ return @$"{AppName ?? appInfo.FileName} {appInfo.Version}
+Type 'help' to see interactive options
+{BuildSessionHelp(context)}";
+ }
+
+ private string BuildSessionHelp(CommandContext context)
+ {
+ return @"Type '-h' or '--help' for the list of commands
+Type 'exit', 'quit' or 'Ctrl+C + Enter' to exit.";
+ }
+ }
+
+ public class ReplOption
+ {
+ public string? LongName { get; set; }
+ public char? ShortName { get; set; }
+ public string? Description { get; set; }
+
+ internal bool IsRequested => LongName is { } || ShortName is { };
+ }
+}
\ No newline at end of file
diff --git a/CommandDotNet.ReadLineRepl/ReplMiddleware.cs b/CommandDotNet.ReadLineRepl/ReplMiddleware.cs
new file mode 100644
index 000000000..f803bc1f9
--- /dev/null
+++ b/CommandDotNet.ReadLineRepl/ReplMiddleware.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Threading.Tasks;
+using CommandDotNet.Execution;
+
+namespace CommandDotNet.ReadLineRepl
+{
+ public static class ReplMiddleware
+ {
+ public static AppRunner UseRepl(this AppRunner appRunner, ReplConfig? replConfig = null)
+ {
+ ReadLine.HistoryEnabled = true;
+
+ replConfig ??= new ReplConfig();
+ return appRunner.Configure(c =>
+ {
+ c.UseMiddleware(ReplSession, MiddlewareSteps.ReplSession);
+ // use the existing appRunner to reuse the configuration.
+ c.UseParameterResolver(ctx => new ReplSession(appRunner, replConfig, ctx));
+
+ var config = new Config(appRunner, replConfig);
+ c.Services.Add(config);
+
+ var replOption = replConfig.ReplOption;
+ if (replOption?.IsRequested ?? false)
+ {
+ var option = new Option(replOption!.LongName, replOption.ShortName, TypeInfo.Flag, ArgumentArity.Zero)
+ {
+ Description = replOption.Description
+ };
+ config.Option = option;
+
+ c.BuildEvents.OnCommandCreated += args =>
+ {
+ var builder = args.CommandBuilder;
+
+ // do not include option if already in a session
+ if (!config.InSession && builder.Command.IsRootCommand())
+ {
+ builder.AddArgument(option);
+ }
+ };
+ }
+ });
+ }
+
+ private class Config
+ {
+ public AppRunner AppRunner { get; }
+ public ReplConfig ReplConfig { get; }
+ public bool InSession { get; set; }
+ public Option? Option { get; set; }
+
+ public Config(AppRunner appRunner, ReplConfig replConfig)
+ {
+ AppRunner = appRunner ?? throw new ArgumentNullException(nameof(appRunner));
+ ReplConfig = replConfig ?? throw new ArgumentNullException(nameof(replConfig));
+ }
+ }
+
+ private static Task ReplSession(CommandContext ctx, ExecutionDelegate next)
+ {
+ var parseResult = ctx.ParseResult!;
+ var cmd = parseResult.TargetCommand;
+ if (cmd.IsRootCommand()
+ && !cmd.IsExecutable
+ && parseResult.ParseError is null
+ && !parseResult.HelpWasRequested())
+ {
+ var config = ctx.AppConfig.Services.GetOrThrow();
+ var option = config.Option;
+ if (!config.InSession && (option is null || cmd.HasInputValues(option.Name)))
+ {
+ config.InSession = true;
+ new ReplSession(config.AppRunner, config.ReplConfig, ctx).Start();
+ return ExitCodes.Success;
+ }
+ }
+
+ return next(ctx);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CommandDotNet.Example/InteractiveSession.cs b/CommandDotNet.ReadLineRepl/ReplSession.cs
similarity index 67%
rename from CommandDotNet.Example/InteractiveSession.cs
rename to CommandDotNet.ReadLineRepl/ReplSession.cs
index b7c54fd03..10647c11f 100644
--- a/CommandDotNet.Example/InteractiveSession.cs
+++ b/CommandDotNet.ReadLineRepl/ReplSession.cs
@@ -1,20 +1,19 @@
using System;
using System.Linq;
-using CommandDotNet.Builders;
using CommandDotNet.Tokens;
-namespace CommandDotNet.Example
+namespace CommandDotNet.ReadLineRepl
{
- public class InteractiveSession
+ public class ReplSession
{
private readonly AppRunner _appRunner;
- private readonly string _appName;
+ private readonly ReplConfig _replConfig;
private readonly CommandContext _context;
- public InteractiveSession(AppRunner appRunner, string appName, CommandContext context)
+ public ReplSession(AppRunner appRunner, ReplConfig replConfig, CommandContext context)
{
_appRunner = appRunner;
- _appName = appName;
+ _replConfig = replConfig;
_context = context;
}
@@ -29,7 +28,10 @@ public void Start()
pressedCtrlC = true;
};
- PrintSessionInit();
+ var sessionInitMessage = _replConfig.GetSessionInitMessage(_context);
+ var sessionHelpMessage = _replConfig.GetSessionHelpMessage(_context);
+
+ console.WriteLine(sessionInitMessage);
bool pendingNewLine = false;
void Write(string? value = null)
@@ -56,7 +58,7 @@ void EnsureNewLine()
{
EnsureNewLine();
Write(">>>");
- var input = console.In.ReadLine();
+ var input = ReadLine.Read();
if (input is null || pressedCtrlC)
{
pressedCtrlC = false;
@@ -79,7 +81,7 @@ void EnsureNewLine()
case "quit":
return;
case "help":
- PrintSessionHelp();
+ console.WriteLine(sessionHelpMessage);
continue;
}
if (singleArg == Environment.NewLine)
@@ -93,22 +95,5 @@ void EnsureNewLine()
}
EnsureNewLine();
}
-
- private void PrintSessionInit()
- {
- var appInfo = AppInfo.GetAppInfo(_context);
- var console = _context.Console;
- console.WriteLine($"{_appName} {appInfo.Version}");
- console.WriteLine("Type 'help' to see interactive options");
- console.WriteLine("Type '-h' or '--help' to options for commands");
- console.WriteLine("Type 'exit', 'quit' or 'Ctrl+C' to exit.");
- }
-
- private void PrintSessionHelp()
- {
- var console = _context.Console;
- console.WriteLine("Type '-h' or '--help' to options for commands");
- console.WriteLine("Type 'exit', 'quit' or 'Ctrl+C' to exit.");
- }
}
}
\ No newline at end of file
diff --git a/CommandDotNet.sln b/CommandDotNet.sln
index 92b23f185..6aebd6b34 100644
--- a/CommandDotNet.sln
+++ b/CommandDotNet.sln
@@ -40,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandDotNet.Example.Tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandDotNet.DataAnnotations", "CommandDotNet.DataAnnotations\CommandDotNet.DataAnnotations.csproj", "{558AA426-06D4-4FC9-B2E5-B6742F0A5D77}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandDotNet.ReadLineRepl", "CommandDotNet.ReadLineRepl\CommandDotNet.ReadLineRepl.csproj", "{1149E03F-FF07-448A-BB42-82876459B138}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -94,6 +96,10 @@ Global
{558AA426-06D4-4FC9-B2E5-B6742F0A5D77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{558AA426-06D4-4FC9-B2E5-B6742F0A5D77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{558AA426-06D4-4FC9-B2E5-B6742F0A5D77}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1149E03F-FF07-448A-BB42-82876459B138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1149E03F-FF07-448A-BB42-82876459B138}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1149E03F-FF07-448A-BB42-82876459B138}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1149E03F-FF07-448A-BB42-82876459B138}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE