diff --git a/src/.editorconfig b/.editorconfig similarity index 100% rename from src/.editorconfig rename to .editorconfig diff --git a/src/NuGet.config b/NuGet.config similarity index 100% rename from src/NuGet.config rename to NuGet.config diff --git a/src/dotnet-repl.sln b/dotnet-repl.sln similarity index 94% rename from src/dotnet-repl.sln rename to dotnet-repl.sln index 5a90616..669a376 100644 --- a/src/dotnet-repl.sln +++ b/dotnet-repl.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31112.23 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-repl", "dotnet-repl\dotnet-repl.csproj", "{D84A3673-0973-4C16-B0A1-1E00BD9146A3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-repl", "src\dotnet-repl\dotnet-repl.csproj", "{D84A3673-0973-4C16-B0A1-1E00BD9146A3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-repl.Tests", "dotnet-repl.Tests\dotnet-repl.Tests.csproj", "{77D12413-604B-47ED-85B0-3CC253AFA9A4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-repl.Tests", "src\dotnet-repl.Tests\dotnet-repl.Tests.csproj", "{77D12413-604B-47ED-85B0-3CC253AFA9A4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7D05BFEB-F647-47E1-B50A-BACE5005B627}" EndProject diff --git a/src/dotnet-repl.sln.DotSettings b/dotnet-repl.sln.DotSettings similarity index 100% rename from src/dotnet-repl.sln.DotSettings rename to dotnet-repl.sln.DotSettings diff --git a/src/dotnet-repl.v3.ncrunchsolution b/dotnet-repl.v3.ncrunchsolution similarity index 100% rename from src/dotnet-repl.v3.ncrunchsolution rename to dotnet-repl.v3.ncrunchsolution diff --git a/src/dotnet-repl.Tests/Automation/NotebookRunnerTests.cs b/src/dotnet-repl.Tests/Automation/NotebookRunnerTests.cs index c0294e1..ebad568 100644 --- a/src/dotnet-repl.Tests/Automation/NotebookRunnerTests.cs +++ b/src/dotnet-repl.Tests/Automation/NotebookRunnerTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.CommandLine; -using System.CommandLine.Invocation; using System.Diagnostics; using System.IO; using System.Threading.Tasks; @@ -49,22 +48,23 @@ public NotebookRunnerTests() [Fact] public async Task When_an_ipynb_is_run_and_no_error_is_produced_then_the_exit_code_is_0() { - var parseResult =_rootCommand.Parse($"--run \"{_directory}/succeed.ipynb\" --exit-after-run"); - parseResult.Configuration.Error = new StringWriter(); - var result = await ((AsynchronousCommandLineAction)_rootCommand.Action).InvokeAsync(parseResult); + var error = new StringWriter(); + var result = await _rootCommand + .Parse($"--run \"{_directory}/succeed.ipynb\" --exit-after-run") + .InvokeAsync(new() { Output = new StringWriter(), Error = error }); - parseResult.Configuration.Error.ToString().Should().BeEmpty(); + error.ToString().Should().BeEmpty(); result.Should().Be(0); } [Fact] public async Task When_an_ipynb_is_run_and_an_error_is_produced_from_a_cell_then_the_exit_code_is_2() { - var parseResult = _rootCommand.Parse($"--run \"{_directory}/fail.ipynb\" --exit-after-run"); - parseResult.Configuration.Error = new StringWriter(); - var result = await ((AsynchronousCommandLineAction)_rootCommand.Action).InvokeAsync(parseResult); + var error = new StringWriter(); + var result = await _rootCommand + .Parse($"--run \"{_directory}/fail.ipynb\" --exit-after-run").InvokeAsync(new() { Output = new StringWriter(), Error = error }); - parseResult.Configuration.Error.ToString().Should().BeEmpty(); + error.ToString().Should().BeEmpty(); result.Should().Be(2); } diff --git a/src/dotnet-repl.Tests/CommandLineParserTests.cs b/src/dotnet-repl.Tests/CommandLineParserTests.cs index 65efbe4..71b612a 100644 --- a/src/dotnet-repl.Tests/CommandLineParserTests.cs +++ b/src/dotnet-repl.Tests/CommandLineParserTests.cs @@ -1,15 +1,14 @@ +using dotnet_repl.Tests.Utility; +using FluentAssertions; using System; using System.CommandLine; -using System.CommandLine.Invocation; using System.IO; using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; using Xunit; namespace dotnet_repl.Tests; -public class CommandLineParserTests +public partial class CommandLineParserTests { private readonly RootCommand _rootCommand; @@ -22,10 +21,9 @@ public CommandLineParserTests() public void Help_is_snazzy() { var parseResult = _rootCommand.Parse("-h"); - parseResult.Configuration.Output = new StringWriter(); - ((SynchronousCommandLineAction)parseResult.Action).Invoke(parseResult); + parseResult.Invoke(new() { Output = new StringWriter() }); - var outputLines = parseResult.Configuration + var outputLines = parseResult.InvocationConfiguration .Output .ToString() .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries) @@ -34,19 +32,19 @@ public void Help_is_snazzy() string.Join('\n', outputLines) .Should().Contain( """ -  _ _ _____ _____ ____ _____ ____ _  -  | \ | | | ____| |_ _| | _ \ | ____| | _ \ | |  -  | \| | | _| | | | |_) | | _| | |_) | | |  -  _ | |\ | | |___ | | | _ < | |___ | __/ | |___  -  (_) |_| \_| |_____| |_| |_| \_\ |_____| |_| |_____| -   - """.Replace("\r", "")); +  _ _ _____ _____ ____ _____ ____ _  +  | \ | | | ____| |_ _| | _ \ | ____| | _ \ | |  +  | \| | | _| | | | |_) | | _| | |_) | | |  +  _ | |\ | | |___ | | | _ < | |___ | __/ | |___  +  (_) |_| \_| |_____| |_| |_| \_\ |_____| |_| |_____| +   + """.Replace("\r", "")); } [Fact] public void Parser_configuration_is_valid() { - _rootCommand.Parse("").Configuration.ThrowIfInvalid(); + _rootCommand.ThrowIfInvalid(); } [Fact] diff --git a/src/dotnet-repl.Tests/Utility/CommandExtensions.cs b/src/dotnet-repl.Tests/Utility/CommandExtensions.cs new file mode 100644 index 0000000..44e28cf --- /dev/null +++ b/src/dotnet-repl.Tests/Utility/CommandExtensions.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; + +namespace dotnet_repl.Tests.Utility; + +public static class CommandExtensions +{ + /// + /// Throws an exception if the parser configuration is ambiguous or otherwise not valid. + /// + /// Due to the performance cost of this method, it is recommended to be used in unit testing or in scenarios where the parser is configured dynamically at runtime. + /// Thrown if the configuration is found to be invalid. + public static void ThrowIfInvalid(this Command command) + { + if (command.Parents.FlattenBreadthFirst(c => c.Parents).Any(ancestor => ancestor == command)) + { + throw new InvalidOperationException($"Cycle detected in command tree. Command '{command.Name}' is its own ancestor."); + } + + int count = command.Subcommands.Count + command.Options.Count; + for (var i = 0; i < count; i++) + { + Symbol symbol1 = GetChild(i, command, out HashSet aliases1); + + for (var j = i + 1; j < count; j++) + { + Symbol symbol2 = GetChild(j, command, out HashSet aliases2); + + if (symbol1.Name.Equals(symbol2.Name, StringComparison.Ordinal) + || aliases1 is not null && aliases1.Contains(symbol2.Name)) + { + throw new InvalidOperationException($"Duplicate alias '{symbol2.Name}' found on command '{command.Name}'."); + } + else if (aliases2 is not null && aliases2.Contains(symbol1.Name)) + { + throw new InvalidOperationException($"Duplicate alias '{symbol1.Name}' found on command '{command.Name}'."); + } + + if (aliases1 is not null && aliases2 is not null) + { + // take advantage of the fact that we are dealing with two hash sets + if (aliases1.Overlaps(aliases2)) + { + foreach (string symbol2Alias in aliases2) + { + if (aliases1.Contains(symbol2Alias)) + { + throw new InvalidOperationException($"Duplicate alias '{symbol2Alias}' found on command '{command.Name}'."); + } + } + } + } + } + + if (symbol1 is Command childCommand) + { + childCommand.ThrowIfInvalid(); + } + } + + static Symbol GetChild(int index, Command command, out HashSet aliases) + { + if (index < command.Subcommands.Count) + { + aliases = command.Subcommands[index].Aliases.ToHashSet(); + return command.Subcommands[index]; + } + + aliases = command.Options[index - command.Subcommands.Count].Aliases.ToHashSet(); + return command.Options[index - command.Subcommands.Count]; + } + } +} \ No newline at end of file diff --git a/src/dotnet-repl.Tests/Utility/EnumerableExtensions.cs b/src/dotnet-repl.Tests/Utility/EnumerableExtensions.cs new file mode 100644 index 0000000..6661827 --- /dev/null +++ b/src/dotnet-repl.Tests/Utility/EnumerableExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace dotnet_repl.Tests.Utility; + +internal static class EnumerableExtensions +{ + internal static IEnumerable FlattenBreadthFirst( + this IEnumerable source, + Func> children) + { + var queue = new Queue(); + + foreach (var item in source) + { + queue.Enqueue(item); + } + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + + foreach (var option in children(current)) + { + queue.Enqueue(option); + } + + yield return current; + } + } +} \ No newline at end of file diff --git a/src/dotnet-repl.Tests/Utility/ParserConfigurationValidationTests.cs b/src/dotnet-repl.Tests/Utility/ParserConfigurationValidationTests.cs new file mode 100644 index 0000000..ad9ad90 --- /dev/null +++ b/src/dotnet-repl.Tests/Utility/ParserConfigurationValidationTests.cs @@ -0,0 +1,243 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.CommandLine; +using FluentAssertions; +using Xunit; + +namespace dotnet_repl.Tests.Utility; + +public class CommandLineConfigurationValidationTests +{ + [Fact] + public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_option_aliases_on_the_root_command() + { + var option1 = new Option("--dupe"); + var option2 = new Option("-y"); + option2.Aliases.Add("--dupe"); + + var command = new RootCommand + { + option1, + option2 + }; + + var validate = () => command.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be($"Duplicate alias '--dupe' found on command '{command.Name}'."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_option_aliases_on_a_subcommand() + { + var option1 = new Option("--dupe"); + var option2 = new Option("--ok"); + option2.Aliases.Add("--dupe"); + + var command = new RootCommand + { + new Command("subcommand") + { + option1, + option2 + } + }; + + var validate = () => command.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be("Duplicate alias '--dupe' found on command 'subcommand'."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_subcommand_aliases_on_the_root_command() + { + var command1 = new Command("dupe"); + var command2 = new Command("not-a-dupe"); + command2.Aliases.Add("dupe"); + + var rootCommand = new RootCommand + { + command1, + command2 + }; + + var validate = () => rootCommand.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be($"Duplicate alias 'dupe' found on command '{rootCommand.Name}'."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_subcommand_aliases_on_a_subcommand() + { + var command = new RootCommand + { + new Command("subcommand") + { + new Command("dupe"), + new Command("not-a-dupe") { Aliases = { "dupe" } } + } + }; + + var validate = () => command.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be("Duplicate alias 'dupe' found on command 'subcommand'."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_sibling_command_and_option_aliases_collide_on_the_root_command() + { + var option = new Option("dupe"); + var command = new Command("not-a-dupe"); + command.Aliases.Add("dupe"); + + var rootCommand = new RootCommand + { + option, + command + }; + + var validate = () => rootCommand.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be($"Duplicate alias 'dupe' found on command '{rootCommand.Name}'."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_sibling_command_and_option_aliases_collide_on_a_subcommand() + { + var option = new Option("dupe"); + var command = new Command("not-a-dupe"); + command.Aliases.Add("dupe"); + + var rootCommand = new RootCommand + { + new Command("subcommand") + { + option, + command + } + }; + + var validate = () => rootCommand.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be("Duplicate alias 'dupe' found on command 'subcommand'."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_global_option_aliases_on_the_root_command() + { + var option1 = new Option("--dupe") { Recursive = true }; + var option2 = new Option("-y") { Recursive = true }; + option2.Aliases.Add("--dupe"); + + var command = new RootCommand(); + command.Options.Add(option1); + command.Options.Add(option2); + + var validate = () => command.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be($"Duplicate alias '--dupe' found on command '{command.Name}'."); + } + + [Fact] + public void ThrowIfInvalid_does_not_throw_if_global_option_alias_is_the_same_as_local_option_alias() + { + var rootCommand = new RootCommand + { + new Command("subcommand") + { + new Option("--dupe") + } + }; + rootCommand.Options.Add(new Option("--dupe") { Recursive = true }); + + var validate = () => rootCommand.ThrowIfInvalid(); + + validate.Should().NotThrow(); + } + + [Fact] + public void ThrowIfInvalid_does_not_throw_if_global_option_alias_is_the_same_as_subcommand_alias() + { + var rootCommand = new RootCommand + { + new Command("subcommand") + { + new Command("--dupe") + } + }; + rootCommand.Options.Add(new Option("--dupe") { Recursive = true }); + + var validate = () => rootCommand.ThrowIfInvalid(); + + validate.Should().NotThrow(); + } + + [Fact] + public void ThrowIfInvalid_throws_if_a_command_is_its_own_parent() + { + var command = new RootCommand(); + command.Add(command); + + var validate = () => command.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be($"Cycle detected in command tree. Command '{command.Name}' is its own ancestor."); + } + + [Fact] + public void ThrowIfInvalid_throws_if_a_parentage_cycle_is_detected() + { + var command = new Command("command"); + var rootCommand = new RootCommand { command }; + command.Add(rootCommand); + + var validate = () => command.ThrowIfInvalid(); + + validate.Should() + .Throw() + .Which + .Message + .Should() + .Be($"Cycle detected in command tree. Command '{command.Name}' is its own ancestor."); + } +} \ No newline at end of file diff --git a/src/dotnet-repl.Tests/dotnet-repl.Tests.csproj b/src/dotnet-repl.Tests/dotnet-repl.Tests.csproj index 8f1c6ae..28e1102 100644 --- a/src/dotnet-repl.Tests/dotnet-repl.Tests.csproj +++ b/src/dotnet-repl.Tests/dotnet-repl.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/dotnet-repl/CommandLineParser.cs b/src/dotnet-repl/CommandLineParser.cs index de0ee3c..437b4ec 100644 --- a/src/dotnet-repl/CommandLineParser.cs +++ b/src/dotnet-repl/CommandLineParser.cs @@ -1,16 +1,18 @@ -using System; + +using System; using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Help; +using System.CommandLine.Invocation; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Automation; +using Spectre.Console; +using Pocket; using Microsoft.DotNet.Interactive.Documents; using Microsoft.DotNet.Interactive.Documents.Jupyter; -using Pocket; -using Spectre.Console; +using Automation; namespace dotnet_repl; @@ -109,8 +111,10 @@ public static RootCommand Create( DescribeCommand(), }; - var helpOption = rootCommand.Options.OfType().Single(); - ((HelpAction)helpOption.Action).Builder = new SpectreHelpBuilder(); + rootCommand.Options + .OfType() + .Single() + .Action = new SpectreHelpAction(); startRepl ??= StartAsync; @@ -271,6 +275,6 @@ await repl.RunAsync( : 0; } - return 0; + } -} \ No newline at end of file +} diff --git a/src/dotnet-repl/Program.cs b/src/dotnet-repl/Program.cs index 8b3631a..a2ae506 100644 --- a/src/dotnet-repl/Program.cs +++ b/src/dotnet-repl/Program.cs @@ -1,5 +1,4 @@ using System; -using System.CommandLine.Parsing; using System.IO; using System.Reflection; using System.Text; diff --git a/src/dotnet-repl/SpectreHelpAction.cs b/src/dotnet-repl/SpectreHelpAction.cs new file mode 100644 index 0000000..7acdc8f --- /dev/null +++ b/src/dotnet-repl/SpectreHelpAction.cs @@ -0,0 +1,15 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using dotnet_repl; + +public class SpectreHelpAction : SynchronousCommandLineAction +{ + public override int Invoke(ParseResult parseResult) + { + var output = parseResult.InvocationConfiguration.Output; + + new SpectreHelpBuilder().ShowHelp(parseResult.CommandResult.Command, output); + + return 0; + } +} \ No newline at end of file diff --git a/src/dotnet-repl/SpectreHelpBuilder.cs b/src/dotnet-repl/SpectreHelpBuilder.cs index 03f04ae..e2ddb64 100644 --- a/src/dotnet-repl/SpectreHelpBuilder.cs +++ b/src/dotnet-repl/SpectreHelpBuilder.cs @@ -1,149 +1,126 @@ -using Spectre.Console; -using System; -using System.Collections.Generic; -using System.CommandLine; +using System.CommandLine; using System.CommandLine.Completions; -using System.CommandLine.Help; +using System.IO; using System.Linq; -using HelpSectionDelegate = System.Func; +using Spectre.Console; namespace dotnet_repl; -internal class SpectreHelpBuilder : HelpBuilder +internal class SpectreHelpBuilder { - public SpectreHelpBuilder(int maxWidth = 2147483647) : base(maxWidth) + private readonly int _maxWidth; + + public SpectreHelpBuilder(int maxWidth = int.MaxValue) { - Func> getLayout = GetLayout; - CustomizeLayout(getLayout); + _maxWidth = maxWidth; } - private IEnumerable GetLayout(HelpContext context) + public void ShowHelp(Command command, TextWriter output) { - if (context.ParseResult.Errors.Any()) - { - // don't show help on error - yield break; - } - - var console = AnsiConsole.Create(new AnsiConsoleSettings + var spectreConsole = AnsiConsole.Create(new AnsiConsoleSettings { Ansi = AnsiSupport.Yes, - Out = new AnsiConsoleOutput(context.Output) + Out = new AnsiConsoleOutput(output) }); - yield return TitleSection(console); + TitleSection(spectreConsole); + CommandUsageSection(spectreConsole, command); + OptionsSection(spectreConsole, command); + ReplHelpSection(spectreConsole, command); + } + + private void TitleSection(IAnsiConsole console) + { + var panel = new Grid(); + panel.AddColumn(new GridColumn()); + var figletText = new FigletText(".NET REPL").Color(Color.SandyBrown); + figletText.Justification = Justify.Center; + panel.AddRow(figletText); + console.Write(panel); + } - yield return CommandUsageSection(console); + private void CommandUsageSection(IAnsiConsole console, Command command) + { + if (command is RootCommand) + { + console.Write(new Markup("🔵[sandybrown italic] Start the REPL like this:[/]\n\n")); + } - yield return OptionsSection(console); + var panel = new Panel($"{command.Name} [[[Magenta1]options[/]]]") + .NoBorder() + .Expand(); - yield return ReplHelpSection(console); + console.Write(panel); } - private HelpSectionDelegate TitleSection(IAnsiConsole console) => - _ => - { - var panel = new Grid(); - panel.AddColumn(new GridColumn()); - var figletText = new FigletText(".NET REPL").Color(Color.SandyBrown); - figletText.Justification = Justify.Center; - panel.AddRow(figletText); - console.Write(panel); - return true; - }; - - private HelpSectionDelegate CommandUsageSection(IAnsiConsole console) => - ctx => + private void OptionsSection(IAnsiConsole console, Command command) + { + if (!command.Options.Any()) { - if (ctx.Command is RootCommand) - { - console.Write(new Markup("🔵[sandybrown italic] Start the REPL like this:[/]\n\n")); - } + return; + } - var panel = new Panel($"{ctx.Command.Name} [[[Magenta1]options[/]]]") - .NoBorder() - .Expand(); + var table = new Table() + .AddColumn("[magenta1 italic]Option[/]") + .AddColumn("[magenta1 italic]Description[/]") + .BorderColor(Color.Magenta1); - console.Write(panel); + foreach (var option in command.Options) + { + var aliases = string.Join(", ", option.Aliases + .Concat(new[] { option.Name }) + .Where(a => !a.StartsWith("/")) + .OrderBy(a => a.Length)); + + table.AddRow( + $"{aliases} {OptionArgumentHelpName(option)}", + option.Description ?? ""); + } - return true; - }; + console.Write(table); - private HelpSectionDelegate OptionsSection(IAnsiConsole console) => - ctx => + string OptionArgumentHelpName(Option option) { - if (!ctx.Command.Options.Any()) + if (option.HelpName is not null) { - return false; + return InAngleBrackets($"{option.HelpName}"); } - var table = new Table() - .AddColumn("[magenta1 italic]Option[/]") - .AddColumn("[magenta1 italic]Description[/]") - .BorderColor(Color.Magenta1); - - foreach (var option in ctx.Command.Options) + if (option.ValueType == typeof(bool)) { - var aliases = string.Join(", ", option.Aliases - .Concat([option.Name]) - .Where(a => !a.StartsWith("/")) - .OrderBy(a => a.Length)); - - table.AddRow( - $"{aliases} {OptionArgumentHelpName(option)}", - option.Description ?? ""); + return ""; } - console.Write(table); - - return true; - - string OptionArgumentHelpName(Option option) + var completions = option.GetCompletions(CompletionContext.Empty).ToArray(); + if (completions.Length > 0) { - if (option.HelpName is not null) - { - return InAngleBrackets($"{option.HelpName}"); - } - - if (option.ValueType == typeof(bool)) - { - return ""; - } - - var completions = option.GetCompletions(CompletionContext.Empty).ToArray(); - if (completions.Length > 0) - { - return InAngleBrackets($"{string.Join("[gray]|[/]", completions.Select(c => c.Label)).Replace("csharp", "[bold aqua]csharp[/]")}"); - } - - return InAngleBrackets(option.Name.ToUpper()); + return InAngleBrackets($"{string.Join("[gray]|[/]", completions.Select(c => c.Label)).Replace("csharp", "[bold aqua]csharp[/]")}"); } - string InAngleBrackets(string value) - { - return $"[gray]<[/]{value}[gray]>[/]"; - } - }; + return InAngleBrackets(option.Name.ToUpper()); + } - private static HelpSectionDelegate ReplHelpSection(IAnsiConsole console) - { - return ctx => + string InAngleBrackets(string value) { - if (ctx.Command is not RootCommand) - { - return false; - } + return $"[gray]<[/]{value}[gray]>[/]"; + } + } - console.Write(new Markup("🟢[sandybrown italic] Once it's running, here are some things you can do:[/]\n\n")); + private void ReplHelpSection(IAnsiConsole console, Command command) + { + if (command is not RootCommand) + { + return; + } - var grid = new Grid(); - grid.AddColumn(); + console.Write(new Markup("🟢[sandybrown italic] Once it's running, here are some things you can do:[/]\n\n")); - grid.ShowShortcutKeys(); + var grid = new Grid(); + grid.AddColumn(); - console.Announce(grid); + // Add your REPL shortcut keys or help rows here + // grid.AddRow(...); - return true; - }; + console.Write(grid); } } \ No newline at end of file diff --git a/src/dotnet-repl/dotnet-repl.csproj b/src/dotnet-repl/dotnet-repl.csproj index a5fe1ae..3dd8d35 100644 --- a/src/dotnet-repl/dotnet-repl.csproj +++ b/src/dotnet-repl/dotnet-repl.csproj @@ -38,7 +38,7 @@ - + all