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

Input prompt is not captured #191

Closed
YouKnowThem opened this issue Jan 17, 2023 · 10 comments
Closed

Input prompt is not captured #191

YouKnowThem opened this issue Jan 17, 2023 · 10 comments
Labels

Comments

@YouKnowThem
Copy link

Version

3.6.0

Details

I have an external CLI software, that ouputs information and prompts the user for imput. I wanted to automate this and using the CliWrap in C# to wait for the prompts and then dynamically output the right user inputs (so a script is not intended).
The output of the CLI contains information used to decide on what to feed the CLI prompt.

Steps to reproduce

  • Step 1: Get JLinkSTM32.exe from Segger. I got the version compiled on Nov4 2021 16:25:18. It is part of the JLink software pack. Segger JLink sofware pack download selector
  • Step 2: Start the exe for reference
  • Step3: use the C# code below and see that the standard output and standard error pipe do not trigger
var jLinkSTM32 = Cli.Wrap(@"...\JLinkSTM32.exe")
            .WithStandardOutputPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line)))
            .WithStandardErrorPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line)))
            .ExecuteBufferedAsync();

I know that .ExecuteBufferedAsync() is not what I need. It is just to reproduce the issue.

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jan 17, 2023

Hey there.

In general, responding to prompts is done by piping standard input data, as that's where the underlying console application reads the input from (usually separated by new lines).

If you know all the prompts and the respective inputs ahead of time, you can just pre-compute the corresponding stdin string and pipe it to the command.

If you need to react to prompts dynamically, the best solution is to create your own PipeSource and use a synchronization primitive to write data. Something like this:

using var semaphore = new SemaphoreSlim(0, 1);
var buffer = new StringBuilder();

var stdin = PipeSource.Create(async (destination, cancellationToken) =>
{
	while (!cancellationToken.IsCancellationRequested)
    {
        await semaphore.WaitAsync(cancellationToken);
        var data = Encoding.UTF8.GetBytes(buffer.ToString());
        await destination.WriteAsync(data, 0, data.Length, cancellationToken);
    }
});

var cmd = stdin | Cli.Wrap("my cmd");

await foreach (var cmdEvent in cmd.ListenAsync())
{
    if (cmdEvent is StandardOutputEvent stdOutEvent)
    {
        // Detect if it's a prompt
        if (stdOutEvent.Text.Contains("Prompt"))
        {
            // Write the response
            buffer.Clear();
            buffer.AppendLine("Hello world");
            semaphore.Release();
        }
    } 
}

@Tyrrrz Tyrrrz added question and removed bug labels Jan 17, 2023
@YouKnowThem
Copy link
Author

Thanks for the clarification.
It's just that the await ListenAsync does not return on this JLinkSTM32.exe's output. I don't know why it doesn't. If it did, your example code would do exactly what I need.

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jan 18, 2023

What do you mean by "does not return"?

@YouKnowThem
Copy link
Author

The await ListenAsync waits indefinetly. Although when opening the CLI software JLinkSTM32.exe manually shows an immediate output with a promt waiting for user input.

That particular output is not fetched by await ListenAsync. My C# software waits for the JLinkSTM32 output forever. Although I expect it to be already there.

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jan 18, 2023

I don't know what JLinkSTM32 does, but it might be because it detects that the standard input is already redirected at the start and switches to some alternative execution mode.

@YouKnowThem
Copy link
Author

I did not think about such behavior. Also did not know such a thing would exist.
But thinking about it feels like it makes sense. I have tried piping an input in. In this case I do not get any output also the CLI does seem to never exit.
A screenshot from task manager. I guess this is the JLinkSTM32.exe's fault?
grafik

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jan 19, 2023

It's weird that it would never exit. In the worst case, you can probably fool it by wrapping cmd and using it to launch JLinkSTM32. Most likely it will behave the same as if it were launched from a terminal.

@YouKnowThem
Copy link
Author

YouKnowThem commented Jan 20, 2023

Good idea. I tried that.

var jLinkSTM32 = await (
            "JLinkSTM32.exe\r\n" |
            Cli.Wrap("cmd")
            .WithWorkingDirectory(@"C:\Program Files\SEGGER\JLink")
            .WithStandardOutputPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line)))
            .WithStandardErrorPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line)))
            )
            .ExecuteBufferedAsync();

But without success. CMD did actually output its contents. JLinkSTM32 kept quiet again. So all I saw was this:
grafik

@vpenades
Copy link

vpenades commented Jul 6, 2024

I've been having a similar problem and, adding to the discussion, I would like to suggest this feature:

In the same way that the TargetPipe has a ToDelegate(Action<string> delegate) , it could be useful to have a FromDelegate(Func<string> delegate) in the PipeSource

@Tyrrrz
Copy link
Owner

Tyrrrz commented Jul 9, 2024

@vpenades it's a good idea but not super straightforward. Please make a new issue for it because this one is old.

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

No branches or pull requests

3 participants