Skip to content
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

[Question] Develop Console Prompt Screen #2422

Open
cschuchardt88 opened this issue May 7, 2024 · 3 comments
Open

[Question] Develop Console Prompt Screen #2422

cschuchardt88 opened this issue May 7, 2024 · 3 comments

Comments

@cschuchardt88
Copy link

cschuchardt88 commented May 7, 2024

I am building a custom console prompt; however I keep getting error when a command is found or not. As you will see in the image below.

How my app works:

I'm running my app as MyApp.exe connect \\.\pipe\HelloWorld then screen refreshes to appear as terminal on that computer. This would be my implementation of IPC for the program.

        public static async Task<int> RunConsolePrompt(InvocationContext context, CancellationToken cancellationToken)
        {
            context.Console.Clear();

            var rootCommand = new DefaultRemoteCommand();
            var parser = new CommandLineBuilder(rootCommand)
                .UseParseErrorReporting()
                .Build();

            while (cancellationToken.IsCancellationRequested == false)
            {
                PrintPrompt(context.Console);

                var line = context.Console.ReadLine()?.Trim() ?? string.Empty;

                if (string.IsNullOrEmpty(line) || string.IsNullOrWhiteSpace(line))
                    continue;

                if (line.Equals("exit", StringComparison.OrdinalIgnoreCase) || line.Equals("quit", StringComparison.OrdinalIgnoreCase))
                    break;

                _ = await parser.InvokeAsync(line, context.Console);
            }

            return 0;
        }
internal class ConnectCommand : Command
{
    public ConnectCommand() : base("connect", "Connect to local Neo service")
    {
        var pipeNameArgument = new Argument<string>("PIPE_NAME", "Name of the named pipe to connect to");

        AddArgument(pipeNameArgument);
    }

    public new class Handler : ICommandHandler
    {
        private static readonly string s_computerName = Environment.MachineName;
        private static readonly string s_userName = Environment.UserName;

        private NamedPipeEndPoint? _pipeEndPoint;

        public async Task<int> InvokeAsync(InvocationContext context)
        {
            var stopping = context.GetCancellationToken();

            if (EnvironmentUtility.TryGetServicePipeName(out var pipeName))
            {
                _pipeEndPoint = new NamedPipeEndPoint(pipeName);
                var pipeStream = NamedPipeTransportFactory.CreateClientStream(_pipeEndPoint);

                context.Console.SetTerminalForegroundColor(ConsoleColor.DarkMagenta);
                context.Console.WriteLine($"Connecting to {_pipeEndPoint}...");
                context.Console.ResetTerminalForegroundColor();

                try
                {
                    await pipeStream.ConnectAsync(stopping).DefaultTimeout();
                    await RunConsolePrompt(context, stopping);
                }
                catch (TimeoutException)
                {
                    context.Console.WriteLine(string.Empty);
                    context.Console.ErrorMessage($"Failed to connect! Try again Later!");

                    context.Console.SetTerminalForegroundColor(ConsoleColor.DarkCyan);
                    context.Console.WriteLine("Note: Make sure service is running.");
                    context.Console.ResetTerminalForegroundColor();
                }

                return 0;
            }

            return -1;
        }

        public int Invoke(InvocationContext context)
        {
            throw new NotImplementedException();
        }

        private static void PrintPrompt(IConsole console)
        {
            console.SetTerminalForegroundColor(ConsoleColor.DarkBlue);
            console.Write($"{s_userName}@{s_computerName}");
            console.SetTerminalForegroundColor(ConsoleColor.White);
            console.Write(":~$ ");
            console.SetTerminalForegroundColor(ConsoleColor.DarkCyan);
            console.ResetTerminalForegroundColor();
        }

        public static async Task<int> RunConsolePrompt(InvocationContext context, CancellationToken cancellationToken)
        {
            context.Console.Clear();

            var rootCommand = new DefaultRemoteCommand();
            var parser = new CommandLineBuilder(rootCommand)
                .UseParseErrorReporting()
                .Build();

            while (cancellationToken.IsCancellationRequested == false)
            {
                PrintPrompt(context.Console);

                var line = context.Console.ReadLine()?.Trim() ?? string.Empty;

                if (string.IsNullOrEmpty(line) || string.IsNullOrWhiteSpace(line))
                    continue;

                if (line.Equals("exit", StringComparison.OrdinalIgnoreCase) || line.Equals("quit", StringComparison.OrdinalIgnoreCase))
                    break;

                _ = await parser.InvokeAsync(line, context.Console);
            }

            return 0;
        }
    }
}

Now this is the prompt (image) its not my powershell prompt; even though it looks like it. But still in my terminal.

image

@elgonzo
Copy link
Contributor

elgonzo commented May 7, 2024

Your attempt with --help:

You are using a CommandLineBuilder. To use the built-in help options, call either UseHelp() or UseDefaults() (which itself calls UseHelp) on the CommandLineBuilder before building the parser from it.

Your attempt with ?:

Note that ? is different from -?. -? is one of the built-in help option aliases, but ? is not.
Depending on what you want, you can declare ? either as a help option alias, or as a custom command with the name ?. If you want ? just as another help option alias, pass it together with all other desired help option aliases as parameter to the UseHelp() method. (Attention: The aliases passed to UseHelp() replace the existing help option aliases, they are not added to them.)

@cschuchardt88
Copy link
Author

Problem that I am having when I don't use RootCommand It can't not even parse --help or -?; with UseHelp or UseDefaults. I believe the reason is because of RootCommand.ExecutableName and RootCommand.ExecutablePath is empty or if used isn't on the line that I am parsing. What I would like to know is how to display Help info with the Command class; I just want to add a new class HelpCommand : ICommandHandler to able to type help and display all the commands; also only show red text in terminal image above (not the rest of the text that is displayed). I have tried looking through the source and couldn't find an easy way to do so.

@elgonzo
Copy link
Contributor

elgonzo commented May 8, 2024

Unfortunately, i am unable to follow your explanations :-( You say that "Problem that I am having when I don't use RootCommand It can't not even parse --help or -?". But System.CommandLine doesn't need a RootCommand to successfully parse the help options (demo: https://dotnetfiddle.net/XNX628)

I believe the reason is because of RootCommand.ExecutableName and RootCommand.ExecutablePath is empty or if used isn't on the line that I am parsing.

While i am not 100% certain, i doubt RootCommand.ExecutableName and RootCommand.ExecutablePath would be related to SCL being (un)able to parse the input string "--help" or "-?". The parser built from the CommandLineBuilder should successfully parse either input string, assuming the built-in help options have been actived via UseHelp() or UseDefaults(). But without being able to easily reproduce this on my end, i can't say for sure either way...

What I would like to know is how to display Help info with the Command class

I see two ways to do it. One way is to let the handler for the help command simply parse and invoke on a bespoke string featuring a help option. If your existing parser instance has these built-in help options activated, you could reuse it, i believe. Otherwise, let the help command create a new parser based off a new CommandLineBuilder that has the built-in help options activated.

The other way i see is letting the handler for the Help command do something similar to to what the SCL middleware for the built-in help options is doing:

public void Apply(InvocationContext context)
{
var output = context.Console.Out.CreateTextWriter();
var helpContext = new HelpContext(context.BindingContext.HelpBuilder,
context.ParseResult.CommandResult.Command,
output,
context.ParseResult);
context.BindingContext
.HelpBuilder
.Write(helpContext);

also only show red text in terminal image above (not the rest of the text that is displayed).

Don't use UseParseErrorReporting() then, as it printing the help text is unfortunately hard-coded. Instead, inspect the parse result before calling Invoke/Async on it. Specifically look at its ParseResult.Errors list. Each of the parse errors in this list provides the error message for that error in its ParseError.Message property, which you can print out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants