Skip to main content

netsky_sh/
lib.rs

1//! Shell utilities for netsky: tmux, git, process wrapping.
2//!
3//! Forked from `dkdc-io/sh` crate `sh-core` (MIT). Extended for the
4//! netsky spawn path — primarily tmux `new-session` with per-session
5//! environment propagation, which `netsky-core::spawn` relies on.
6//!
7//! Minimal, synchronous shell abstractions. No async runtime required.
8
9use std::path::PathBuf;
10use std::process::Command;
11
12pub mod git;
13pub mod tmux;
14
15#[derive(Debug)]
16pub enum Error {
17    CommandNotFound(String),
18    CommandFailed { cmd: String, detail: String },
19    Tmux(String),
20    Io(std::io::Error),
21}
22
23impl std::fmt::Display for Error {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Self::CommandNotFound(s) => write!(f, "command not found: {s}"),
27            Self::CommandFailed { cmd, detail } => {
28                write!(f, "command failed: {cmd} — {detail}")
29            }
30            Self::Tmux(s) => write!(f, "tmux error: {s}"),
31            Self::Io(e) => write!(f, "io error: {e}"),
32        }
33    }
34}
35
36impl std::error::Error for Error {
37    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38        match self {
39            Self::Io(e) => Some(e),
40            _ => None,
41        }
42    }
43}
44
45impl From<std::io::Error> for Error {
46    fn from(e: std::io::Error) -> Self {
47        Self::Io(e)
48    }
49}
50
51/// Find a command in PATH, returning its absolute path or None.
52pub fn which(cmd: &str) -> Option<PathBuf> {
53    ::which::which(cmd).ok()
54}
55
56/// Require a command to exist in PATH.
57pub fn require(cmd: &str) -> Result<PathBuf, Error> {
58    ::which::which(cmd).map_err(|_| Error::CommandNotFound(cmd.to_string()))
59}
60
61/// Run a command and return its stdout as a String.
62pub fn run(program: &str, args: &[&str]) -> Result<String, Error> {
63    run_with_env(program, args, &[])
64}
65
66/// Run a command with extra environment variables.
67pub fn run_with_env(program: &str, args: &[&str], env: &[(&str, &str)]) -> Result<String, Error> {
68    require(program)?;
69
70    let mut command = Command::new(program);
71    command.args(args);
72    for (k, v) in env {
73        command.env(k, v);
74    }
75    let output = command.output()?;
76
77    if !output.status.success() {
78        let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
79        return Err(Error::CommandFailed {
80            cmd: format!("{program} {}", args.first().unwrap_or(&"")),
81            detail: stderr,
82        });
83    }
84
85    Ok(String::from_utf8_lossy(&output.stdout).into_owned())
86}