-
Notifications
You must be signed in to change notification settings - Fork 94
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
Allow using stderr for prompt output #109
Conversation
I don't think we should have a method for this. The best practice should be the default. |
Waiting for review from @jessarcher |
Agreed. Based on the linked post, I think I can agree that using Any changes we make would need to consider the newline behaviour. We output a single blank line between prompts and other informational messages so we need to track the number of trailing newlines previously output so we know how many (if any) need to be output before the next prompt. If output is mixed between |
Symfony commands don't have convenience methods like protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>This goes to STDOUT</info>');
// If you want to output to STDERR, you have to do it manually
$err = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
$err->writeln('<error>This goes to STDERR</error>');
return self::SUCCESS;
} Good point about
We should be able to detect if STDOUT is being redirected by using this technique. |
Makes sense, although it could be a pain if you want the command to output well-formed JSON that can be redirected to STDOUT, but still want to show some informational messages that don't get redirected.
Currently, we only check whether STDIN is a TTY (or whether the What do you think about the following additional checks?
That should make things "just work" as expected for most scenarios. |
Excellent solution 👍 I think I'll have time to implement it this week. |
Looking forward to hearing your feedback. php playground/index.php > out.txt The prompt should work as always, and out.txt should only contain the final var_dump() with the selected values. |
Hey @mortenscheel, that seems to work really well! The only problem is that when Prompts is used with Laravel, we configure it to use Laravel's This is so we can keep track of newlines output from both Prompts and Laravel to ensure the correct spacing between output from each source. Rather than duplicate the logic in Laravel, can we move it within the |
Sure. @jessarcher just to make sure I understand correctly, you're suggesting that the different prompts should call |
@jessarcher please take a look at the implementation. I was a bit confused by the fact that |
Hi @mortenscheel, I've just tested this out, and there's an issue with the newlines between Prompt output and Laravel output. On the left is the current behaviour, and on the right is with this PR. Note the extra newlines between the Prompts output and the other output from Laravel. This is without redirecting any streams. I'll need to do more testing to figure out what's going on, but I suspect it's to do with the two places where the Additionally, STDERR doesn't seem to be used when STDOUT is redirected when Prompts are used in Laravel. Only when used standalone. |
if ($output instanceof ConsoleOutputInterface && stream_isatty(STDERR) && ! stream_isatty(STDOUT)) { | ||
$output = $output->getErrorOutput(); | ||
} | ||
self::$writer = new PromptWriter($output); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Laravel sends an instance of \Illuminate\Console\OutputStyle
, which doesn't implement ConsoleOutputInterface
, meaning we won't use STDERR with Laravel even when STDOUT is redirected elsewhere.
Laravel's OutputStyle
class is an extension of Symfony's SymfonyStyle
and OutputStyle
classes, which are a wrapper around an underlying OutputInterface
. It doesn't expose the getErrorOutput
method but instead exposes a getErrorStyle
method. Unfortunately, this returns a new instance of SymfonyStyle
rather than Laravel's OutputStyle
class (due to their use of self
).
Even if we could get it to return a new instance of Laravel's OutputStyle
, the newline tracking would break because Laravel will still be using its original OutputStyle
instance rather than the new instance we'd be using in Prompts.
It's a complicated problem because we need to keep track of the newlines between Prompts's and Laravel's output. Both Laravel and Prompts need to know how many trailing newlines were in the previous output, regardless of where that previous output came from, so that they each know how many newlines to emit before any new output. If Laravel isn't aware of the trailing newlines that Prompts has written, it won't be able to output the correct amount of leading newlines before any new output, and vice versa. It gets more complicated with Symfony because they have a separate instance for STDERR. If Laravel and Prompts use different instances, they won't be aware of each other's trailing newlines.
The only solutions I can think of are:
- Add logic to Laravel to make it use an STDERR output instance when appropriate (and share that instance with Prompts). This could have unintended consequences in user commands that expect to output on STDOUT regardless of any redirection, so it's probably a breaking change. Alternatively,
- Add a method to Laravel's
OutputStyle
class (or create a new wrapper class) that allows us to write to STDERR, while still tracking trailing newlines regardless of the stream. It would potentially need to be smart enough to track newlines separately when streams are redirected to different places because, in that scenario, the trailing newlines from the previous output of another stream shouldn't be considered. Prompts could then choose to output to STDERR and both Laravel and Prompts would still know how many leading newlines they needed to output.
Either way it's a big change covering two code bases. I still see the value in doing this, but I'm concerned about the effort and risk involved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed explanation. I can't think of any other solutions, so you're welcome to close this if you want.
I learned a lot about console output, and it was nice to meet you guys 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @mortenscheel! Nice to meet you as well 🙂
Happy to reconsider this at some point if we can figure out a good solution. Having one blank line between output looks great, but it certainly adds a lot of challenges 😅
Sending the prompt output to
stdout
(which is the default behavior) can be problematic in situations where the output of the command needs to be redirected to a file, or piped to another command.> data.json
, the prompt is sent directly to the file and won't be displayed in the console. The command will keep running, waiting for input, but the user won't know what's going on.| grep "foo"
, the prompt might also be invisible, or if the receiving command expects structured data like csv or json, it might cause an error.The fix is easy. Just send the prompt output to
stderr
in stead. That's what Symfony's QuestionHelper does by default. And according to this excellent post by Chris Fidao, it's actually a well established unix convention.Like I mentioned in #107, I think the best solution would be to use stderr by default, but I realize that this could be considered a breaking change. So in stead I've just added a simple
Prompt::useStderr()
method that makes it easy to send all prompts to stderr.I really wanted to add some tests that assert that actual output is sent to stdout, and prompts are send to stderr, but I honestly have no idea how to do that.