Skip to main content

ferrijs_std/tty/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use crate::utils::module::{export_default, ModuleInfo};
4use rquickjs::{
5    module::{Declarations, Exports, ModuleDef},
6    prelude::Func,
7    Ctx, Result,
8};
9
10// Windows' CRT `_isatty` answers for any character device, so a pipe to NUL
11// counts and the result disagrees with what Node reports through
12// `uv_guess_handle`. `IsTerminal` asks the console directly, which is the same
13// question, so the three standard descriptors go through it. Anything else
14// still has to be asked of the C library, and Windows has no descriptor table
15// to ask about.
16fn isatty(fd: i32) -> bool {
17    use std::io::IsTerminal;
18
19    match fd {
20        0 => std::io::stdin().is_terminal(),
21        1 => std::io::stdout().is_terminal(),
22        2 => std::io::stderr().is_terminal(),
23        #[cfg(unix)]
24        other => unsafe { libc::isatty(other) != 0 },
25        #[cfg(not(unix))]
26        _ => false,
27    }
28}
29
30pub struct TtyModule;
31
32impl ModuleDef for TtyModule {
33    fn declare(declare: &Declarations<'_>) -> Result<()> {
34        declare.declare("isatty")?;
35        declare.declare("default")?;
36        Ok(())
37    }
38
39    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
40        export_default(ctx, exports, |default| {
41            default.set("isatty", Func::from(isatty))?;
42            Ok(())
43        })
44    }
45}
46
47impl From<TtyModule> for ModuleInfo<TtyModule> {
48    fn from(val: TtyModule) -> Self {
49        ModuleInfo {
50            name: "tty",
51            module: val,
52        }
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use crate::tty::TtyModule;
59    use crate::test::{call_test, test_async_with, ModuleEvaluator};
60    use std::io::{stderr, stdin, stdout, IsTerminal};
61
62    #[tokio::test]
63    async fn test_isatty() {
64        test_async_with(|ctx| {
65            Box::pin(async move {
66                ModuleEvaluator::eval_rust::<TtyModule>(ctx.clone(), "tty")
67                    .await
68                    .unwrap();
69
70                let module = ModuleEvaluator::eval_js(
71                    ctx.clone(),
72                    "test",
73                    r#"
74                        import { isatty } from 'tty';
75
76                        export async function test() {
77                            return new Array(3).fill(0).map((_, i) => +isatty(i)).join('')
78                        }
79                    "#,
80                )
81                .await
82                .unwrap();
83                let expect = [
84                    stdin().is_terminal(),
85                    stdout().is_terminal(),
86                    stderr().is_terminal(),
87                ]
88                .map(|i| (i as u8).to_string())
89                .join("");
90                let result = call_test::<String, _>(&ctx, &module, ()).await;
91                assert_eq!(result, expect);
92            })
93        })
94        .await;
95    }
96}