Skip to content
New issue

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

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

Already on GitHub? # to your account

poc: Provide a REPL session using ReadLine package #306

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
@@ -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>
21 changes: 0 additions & 21 deletions CommandDotNet.Example/Examples.cs
Original file line number Diff line number Diff line change
@@ -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!;

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
@@ -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);
}
}
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;
}

@@ -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.");
}
}
}
6 changes: 6 additions & 0 deletions CommandDotNet.sln
Original file line number Diff line number Diff line change
@@ -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