Skip to content

poc: Provide a REPL session using ReadLine package #306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CommandDotNet.Example/CommandDotNet.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<ProjectReference Include="..\CommandDotNet.FluentValidation\CommandDotNet.FluentValidation.csproj" />
<ProjectReference Include="..\CommandDotNet.NewerReleasesAlerts\CommandDotNet.NewerReleasesAlerts.csproj" />
<ProjectReference Include="..\CommandDotNet.NameCasing\CommandDotNet.NameCasing.csproj" />
<ProjectReference Include="..\CommandDotNet.ReadLineRepl\CommandDotNet.ReadLineRepl.csproj" />
<ProjectReference Include="..\CommandDotNet\CommandDotNet.csproj" />
</ItemGroup>
<ItemGroup>
Expand Down
21 changes: 0 additions & 21 deletions CommandDotNet.Example/Examples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;

Expand Down
14 changes: 0 additions & 14 deletions CommandDotNet.Example/InteractiveMiddleware.cs

This file was deleted.

3 changes: 2 additions & 1 deletion CommandDotNet.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CommandDotNet.Diagnostics;
using CommandDotNet.FluentValidation;
using CommandDotNet.NameCasing;
using CommandDotNet.ReadLineRepl;

namespace CommandDotNet.Example
{
Expand All @@ -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);
}
}
Expand Down
27 changes: 27 additions & 0 deletions CommandDotNet.ReadLineRepl/CommandDotNet.ReadLineRepl.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyTitle>CommandDotNet.ReadLineRepl</AssemblyTitle>
<Description>Provides an interactive session using the GNU style readline package</Description>
</PropertyGroup>
<ItemGroup>
<Compile Remove="output\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="output\**" />
</ItemGroup>
<ItemGroup>
<None Remove="output\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReadLine" Version="2.0.1" />
<PackageReference Include="Nullable" Version="1.2.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CommandDotNet\CommandDotNet.csproj" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions CommandDotNet.ReadLineRepl/MiddlewareSteps.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
51 changes: 51 additions & 0 deletions CommandDotNet.ReadLineRepl/ReplConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using CommandDotNet.Builders;

namespace CommandDotNet.ReadLineRepl
{
public class ReplConfig
{
private Func<CommandContext, string>? _sessionInitMessage;
private Func<CommandContext, string>? _sessionHelpMessage;

public string? AppName { get; set; }

public ReplOption ReplOption { get; set; } = new ReplOption();

public Func<CommandContext, string> GetSessionInitMessage
{
get => _sessionInitMessage ?? BuildSessionInit ;
set => _sessionInitMessage = value ?? throw new ArgumentNullException(nameof(value));
}


public Func<CommandContext, string> 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 { };
}
}
82 changes: 82 additions & 0 deletions CommandDotNet.ReadLineRepl/ReplMiddleware.cs
Original file line number Diff line number Diff line change
@@ -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<int> 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<Config>();
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}

Expand All @@ -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)
Expand All @@ -56,7 +58,7 @@ void EnsureNewLine()
{
EnsureNewLine();
Write(">>>");
var input = console.In.ReadLine();
var input = ReadLine.Read();
if (input is null || pressedCtrlC)
{
pressedCtrlC = false;
Expand All @@ -79,7 +81,7 @@ void EnsureNewLine()
case "quit":
return;
case "help":
PrintSessionHelp();
console.WriteLine(sessionHelpMessage);
continue;
}
if (singleArg == Environment.NewLine)
Expand All @@ -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.");
}
}
}
6 changes: 6 additions & 0 deletions CommandDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down