-
-
Notifications
You must be signed in to change notification settings - Fork 226
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
Use file descriptor 0 to read stdin #198
Conversation
On macOS with
|
I need to investigate that. I tried on my macOS machine, and was successful doing this:
I am on Catalina 10.15.7. Which node version and mac version are you on? |
node v14.13.1 Note: piping input works fine (as in your test). But if I just do |
Ah, thanks! The plot deepens considerably: the same is true on Linux. When piping (
When entering it with
(By input ignored, I mean that the text sent through the echo is not read; It is surprising to me that /dev/stdin does what we would expect in both cases, Given the data, I would personally favour /dev/stdin, Edit: I added Windows information. On Windows, from what I can see, only 0 is useful. |
2912519
to
1de3cfe
Compare
(Patch modified to use /dev/stdin for Linux, and 0 for macOS and Windows.) |
I'm not too happy with a solution that doesn't work for both cases on macOS. It's pretty hard to believe that there's not a solution for this in node, though I don't really write JavaScript (except for this project), so I don't know. |
This seems to work in both cases on macOS: if (process.stdin.isTTY) {
inps.push(fs.readFileSync('/dev/tty', 'utf-8'));
} else {
inps.push(fs.readFileSync('/dev/stdin', 'utf-8'));
} |
350a95a
to
5be09ef
Compare
I updated the code to be inspired by your suggestion. It now works on pipes and TTY both on Linux and macOS. I also made the code platform-independent. |
Great. Do I understand correctly that the situation is no worse off than before on Windows, since running Surely there has to be a way to make that last case work in Windows as well? |
Yes, that is correct. There might be a way to achieve it, but from my understanding, the Windows ecosystem is not built for it. I think most developers don’t make heavy use of the shell on Windows, instead using IDEs.
It feels like the way forward from Microsoft’s point of view may be WSL2, |
I develop a command line application (pandoc) which works fine in this way on Windows (either typing the content or as a pipe). This is just using the standard Haskell functions for IO with stdin. So I know it's not technically impossible. It's a serious shortcoming in node if it's not possible to do this in node; but I'm skeptical that it's not possible and I think further investigation could reveal a way. |
Could you clarify what happens with Pandoc? I installed it, opened cmd.exe (and also tried the same with Powershell), and did:
but nothing happened. |
You have to use ^Z instead of ^D on Windows command shell. |
On Linux, when Node.js (at least, v15.2.0) does a readFileSync of /dev/tty, it hangs. The addition of /dev/tty comes from 9bcf45b, which converts to ESM. It also switches from reading /dev/stdin, to: 1. on Windows, reading file descriptor 0; 2. on other platforms, reading /dev/tty (which presumably works on macOS, but not Linux). However, based on the [Node.js documentation][fd0], reading file descriptor 0 always reads stdin on all platforms, thanks to a compatibility layer in Node.js. Subtly, for Linux specifically, using /dev/stdin instead of the zero file descriptor, enables the following use-case: just enter `commonmark` on the console, causing it to wait for prompt. You can then type text followed by Control-D to have that text be the input. | File | Linux | macOS | Windows | |============|=================|=================|===========| | | Pipe | ✓ | ✓ | ✓ | | 0 |--------|-----------------|-----------------|-----------| | | Prompt | EAGAIN | EAGAIN | TypeError | |~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~|~~~~~~~~~~~| | | ✓ | ✓ | ENOENT | | /dev/stdin |-----------------|-----------------|-----------| | | ✓ | EAGAIN | ENOENT | |~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~|~~~~~~~~~~~| | | (input ignored) | (input ignored) | ENOENT | | /dev/tty |-----------------|-----------------|-----------| | | ✓ | ✓ | ENOENT | Thus we split the decision in a platform-independent way: 1. When in a TTY situation, use /dev/tty. Except on Windows, which does not have a PTY, so we manually read lines until we hit ^Z (which is EOF on Windows). 2. Otherwise, use file descriptor 0, which works across all platforms. [fd0]: https://nodejs.org/api/process.html#process_process_stdin_fd
From what I can see of the GHC source, they seem to handle ^Z manually. I pushed a commit that did the same. |
const doc = parser.parse(inp); | ||
const rendered = renderer.render(doc); | ||
if (!options.time) { process.stdout.write(rendered); } |
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.
Does this belong here? Aren't we going to fall through to line 109 and expect inps
to contain an array of strings?
I am clueless about how async stuff works in JS, but is there a way to do this synchronously to make the code more straightforward?
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.
It will fall through, but with a files
array that is empty (a requirement of the top if condition), so the rest of the code prints nothing.
There is no built-in way to do things synchronously with readline.
Node.js encourages asynchronous operation as it was its main philosophical tenet.
The fs API is an exemption to the rule.
There are external libraries that I think can do that, such as readline-sync
.
It seems to do some gnarly background-process spawning.
We can also convert the whole file to asynchronous operation, which is a more significant patch.
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.
I see. It would be nice to avoid duplicating the parsing and rendering code like this -- if it takes making the whole thing async, maybe that's the best approach.
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.
Well, maybe it's not worth the effort. I can just merge this.
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.
Actually, I think this is not good as it is. You fall through and then end up parsing another empty document and printing the result of rendering it. I'd want to avoid that.
PR #203, now merged, rolls back the esm changes that also included the changes to input handling that prompted your PR. I cannot reconstruct why that input handling change was included with the esm change, but there must have been a reason. Anyway, the current code is back to using I've verified that this works on macos and linux, both as a pipe and typing input on the terminal. It would be good to check Windows, if you can. |
OK, good to know. |
Use file descriptor 0 instead of '/dev/stdin'. Note that this allows piping but doesn't handle the case where users run `bin/commonmark` and enter input directly. See #198 for some relevant discussion.
I've added some code that uses 0 instead of |
I agree with this compromise, as I mentioned. After all, the Windows ecosystem barely has a true TTY; and writing a Markdown document at the console is a poor experience that I expect nobody prefers to a text editor. I can confirm that piping now works on Windows. |
Thanks for confirming, and for all of the above! |
On Linux, when Node.js (at least, v15.2.0) does a readFileSync of
/dev/tty, it hangs.
The addition of /dev/tty comes from 9bcf45b,
which converts to ESM.
It also switches from reading /dev/stdin, to:
(which presumably works on macOS, but not Linux).
However, based on the Node.js documentation,
reading file descriptor 0 always reads stdin on all platforms,
thanks to a compatibility layer in Node.js.
Subtly, for Linux specifically, using /dev/stdin
instead of the zero file descriptor, enables the following use-case:
just enter
commonmark
on the console, causing it to wait for prompt.You can then type text followed by Control-D
to have that text be the input.
Thus the optimal choice is to use /dev/stdin for Linux,
and file descriptor zero for the others.