diff --git a/crates/termlens/examples/inspect.rs b/crates/termlens/examples/inspect.rs index 976d0f9..157b026 100644 --- a/crates/termlens/examples/inspect.rs +++ b/crates/termlens/examples/inspect.rs @@ -5,6 +5,7 @@ //! cargo run --example inspect -- ls -la //! cargo run --example inspect -- --size 120x40 htop //! cargo run --example inspect -- --timeout 30 ./target/debug/slow-app +//! cargo run --example inspect -- --env NO_COLOR=1 my-app //! ``` //! //! Waits for the program to exit (up to the deadline, `--timeout`, five @@ -33,12 +34,15 @@ use termlens::Terminal; /// The one copy of the usage text: `--help` prints it to stdout and exits /// 0, a missing program prints it to stderr and exits 1 (#229). const USAGE: &str = "\ -usage: inspect [--size COLSxROWS] [--timeout SECONDS] [--idle MILLIS] [args…] +usage: inspect [--size COLSxROWS] [--timeout SECONDS] [--idle MILLIS] + [--inherit-env] [--env KEY=VALUE]... [args…] Runs in an 80x24 pseudo-terminal (or --size), waits for it to exit or for the deadline (--timeout, default 5 seconds), and prints the rendered screen. A program still running at the deadline is snapshotted after --idle milliseconds (default 300) of output silence, then killed. +The child environment is cleared by default except for PATH; --inherit-env +keeps the caller's environment, and repeatable --env sets selected values. Exit code 0: a screen was printed; the trailer under it says what the program did. Exit code 1: inspect itself could not run — bad arguments, @@ -71,6 +75,8 @@ fn main() -> ExitCode { let mut size = (80u16, 24u16); let mut timeout = Duration::from_secs(5); let mut idle = Duration::from_millis(300); + let mut inherit_env = false; + let mut env = Vec::new(); // Options come before the program; everything after it is the // program's own, however flag-like it looks. @@ -95,6 +101,15 @@ fn main() -> ExitCode { .map(|secs| timeout = Duration::from_secs(secs)), "--idle" => take(&mut args, "--idle", "MILLIS", "1000", |s| s.parse().ok()) .map(|millis| idle = Duration::from_millis(millis)), + "--inherit-env" => { + inherit_env = true; + Ok(()) + } + "--env" => take(&mut args, "--env", "KEY=VALUE", "NO_COLOR=1", |s| { + let (key, value) = s.split_once('=')?; + (!key.is_empty()).then(|| (key.to_owned(), value.to_owned())) + }) + .map(|pair| env.push(pair)), other => Err(format!("unknown option {other:?} (try --help)")), }; if let Err(message) = parsed { @@ -108,12 +123,21 @@ fn main() -> ExitCode { return ExitCode::FAILURE; }; - let mut t = match Terminal::builder() + let mut builder = Terminal::builder() .size(size.0, size.1) .timeout(timeout) - .args(args) - .spawn(&program) - { + .args(args); + if !inherit_env { + builder = builder.env_clear(); + if let Some(path) = std::env::var_os("PATH") { + builder = builder.env("PATH", path); + } + } + for (key, value) in env { + builder = builder.env(key, value); + } + + let mut t = match builder.spawn(&program) { Ok(t) => t, Err(e) => { eprintln!("inspect: {e}"); diff --git a/crates/termlens/tests/inspect.rs b/crates/termlens/tests/inspect.rs index 34f93ed..61a523f 100644 --- a/crates/termlens/tests/inspect.rs +++ b/crates/termlens/tests/inspect.rs @@ -65,6 +65,34 @@ fn inspect_runs_and_reports_cli_failures() { ); } +#[test] +fn inspect_clears_and_selectively_sets_the_child_environment() { + let bin = inspect_bin(); + let output = Command::new(&bin) + .env("TERMLENS_INSPECT_LEAK", "secret") + .args(["--env", "KEPT=yes"]) + .args(["sh", "-c", "printf ${TERMLENS_INSPECT_LEAK-unset}:$KEPT"]) + .output() + .expect("inspect runs"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("unset:yes"), + "cleared/selected environment missing from:\n{stdout}" + ); + + let inherited = Command::new(&bin) + .env("TERMLENS_INSPECT_INHERITED", "yes") + .args(["--inherit-env"]) + .args(["sh", "-c", "printf inherited:$TERMLENS_INSPECT_INHERITED"]) + .output() + .expect("inspect runs"); + let stdout = String::from_utf8_lossy(&inherited.stdout); + assert!( + stdout.contains("inherited:yes"), + "inherited environment missing from:\n{stdout}" + ); +} + #[test] fn inspect_survives_a_reader_that_closes_early() { use std::io::Read;