defmodule BearCli.TTY do @moduledoc """ stdin 是否為終端機(TTY)的偵測(issue #19;來源 bear #52 驗收建議 2)。 不再只依賴 `:io.rows/0`/`:io.columns/0`:兩者走 io 協定詢問 group leader,escript(`erl -noshell` 的 `standard_io`)不支援行列查詢, 即使 fd 0 是真終端機也回 `{:error, :enotsup}`,互動環境會被誤判成 非 TTY(無法讀 stdin 密碼,只能靠測試注入介面繞過)。 本模組改為直接判定 fd 0 指向的裝置: - Linux:readlink `/proc/self/fd/0`,指向 `/dev/pts/*`、`/dev/tty*`、 `/dev/console`、`/dev/ptmx` 視為 TTY;pipe/socket/一般檔案(含 `/dev/null`)視為非 TTY。 - 無 `/proc` 的平台(如 macOS、Windows):退回 `:io.rows/0`(僅在 終端機裝置成功)。 """ @tty_prefixes ["/dev/pts/", "/dev/tty", "/dev/console", "/dev/ptmx"] @doc "stdin(fd 0)是否為終端機。" @spec stdin_tty?() :: boolean() def stdin_tty? do case fd0_path() do {:ok, path} -> tty_path?(path) :error -> rows_tty?() end end @doc """ 裝置路徑是否為終端機(公開供測試與診斷)。 iex> BearCli.TTY.tty_path?("/dev/pts/0") true iex> BearCli.TTY.tty_path?("pipe:[42]") false """ @spec tty_path?(String.t()) :: boolean() def tty_path?(path) when is_binary(path) do String.starts_with?(path, @tty_prefixes) end @doc """ 舊式偵測(`:io.rows/0`):終端機 io 裝置回 `{:ok, rows}`;escript 的 `standard_io` 一律回 `{:error, :enotsup}`。僅作為無 `/proc` 平台的退路。 """ @spec rows_tty?() :: boolean() def rows_tty? do match?({:ok, _}, :io.rows()) end # fd 0 實際指向的路徑(readlink /proc/self/fd/0);無 /proc 或讀取失敗 → :error defp fd0_path do case :file.read_link(~c"/proc/self/fd/0") do {:ok, path} -> {:ok, to_string(path)} {:error, _} -> :error end end end