From 90386f1ec9abf000ad65d85418e03f29726166a0 Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Sat, 19 Apr 2025 08:44:41 +0100 Subject: [PATCH] feat: guess whether command was invoked non-interactively Previously, it was made possible to control the interactivity status of the progress bar by setting `_G.interactive`. However, this was not automatically inferred from the context the process was started from, and was expected to be manually set by the library invoker. Non-interactivity status is now inferred automatically based on the context the command was called from. --- toolchainlib/src/init.luau | 7 ++++- toolchainlib/src/utils/sys.luau | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 toolchainlib/src/utils/sys.luau diff --git a/toolchainlib/src/init.luau b/toolchainlib/src/init.luau index 515dfc3..b0a41d0 100644 --- a/toolchainlib/src/init.luau +++ b/toolchainlib/src/init.luau @@ -30,6 +30,7 @@ local ProgressBar = require("./utils/progress") local compression = require("./compression") local eq = require("./utils/eq") local manifest = require("./manifest") +local sys = require("./utils/sys") export type ToolId = { alias: Option, @@ -165,8 +166,12 @@ local function getGithubToken(): Option end) end +local function isInteractive(): boolean + return sys.isTTY("stdout") and process.env.TERM ~= "dumb" and process.env.CI == nil +end + -- Initialize the shared global progress bar state -_G.interactive = nil +_G.interactive = isInteractive() or nil local barFns function installTool(tool: ToolId, installPath: pathfs.Path): number diff --git a/toolchainlib/src/utils/sys.luau b/toolchainlib/src/utils/sys.luau new file mode 100644 index 0000000..9fbc839 --- /dev/null +++ b/toolchainlib/src/utils/sys.luau @@ -0,0 +1,52 @@ +--> Non-exhaustive set of util functions for Linux systems + +local process = require("@lune/process") + +local ResultExt = require("./ext/result") + +local fs = require("../../lune_packages/pathfs").fs +local Result = require("../../lune_packages/result") +local Option = require("../../lune_packages/option") + +local sys = {} +sys.consts = { + S_IFMT = 0xF000, + S_IFCHR = 0x2000, +} + +export type StdioFd = "stdin" | "stdout" | "stderr" +local STDIO_FD_LOOKUP: { [StdioFd]: number } = { + stdin = 0, + stdout = 1, + stderr = 2, +} + +function sys.isCharacterDevice(mode: number) + return bit32.band(mode, sys.consts.S_IFMT) == sys.consts.S_IFCHR +end + +function sys.isTTY(fd: number | StdioFd): boolean + if process.os ~= "linux" then + -- idk about windows and macOS :p + return true + end + + local inputFd: number = if typeof(fd) == "string" then STDIO_FD_LOOKUP[fd] else fd + local statFd = fs.readFile("/proc/self/stat") + local pid = assert(string.match(statFd, "^%S+%s+%S+%s+%S+%s+(%S+)"), "Could not get current process PID") + + local stdoutFdMode = ResultExt.ok( + Result.try(process.spawn, "stat", { "-L", "-c", "%f", `/proc/{pid}/fd/{inputFd}` }) + ) + :andThen(function(child: process.SpawnResult) + if not child.ok then + return Option.None + end + + return Option.from(tonumber(child.stdout, 16)) + end) + + return stdoutFdMode:isSomeAnd(sys.isCharacterDevice) +end + +return sys