epsh 0.0.5

embeddable posix shell
Documentation
use std::io::Read;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let mut shell = epsh::eval::Shell::new();

    if args.len() > 1 {
        // Process options
        let mut i = 1;
        while i < args.len() {
            if args[i] == "-c" {
                // Execute command string
                if i + 1 >= args.len() {
                    eprintln!("epsh: -c requires an argument");
                    std::process::exit(2);
                }
                let script = &args[i + 1];
                // Remaining args become $0, $1, ...
                if i + 2 < args.len() {
                    shell.set_args(
                        &args[i + 2..]
                            .iter()
                            .map(|s| s.as_str())
                            .collect::<Vec<&str>>(),
                    );
                }
                let status = shell.run_script(script);
                std::process::exit(status);
            } else if args[i] == "-s" {
                // Read from stdin
                break;
            } else if (args[i] == "-o" || args[i] == "+o") && i + 1 < args.len() {
                // Long options: -o pipefail, +o pipefail, etc.
                let enable = args[i] == "-o";
                i += 1;
                match args[i].as_str() {
                    "pipefail" => shell.opts_mut().pipefail = enable,
                    "errexit" => shell.opts_mut().errexit = enable,
                    "nounset" => shell.opts_mut().nounset = enable,
                    "xtrace" => shell.opts_mut().xtrace = enable,
                    "noglob" => shell.opts_mut().noglob = enable,
                    "noexec" => shell.opts_mut().noexec = enable,
                    opt => {
                        eprintln!("epsh: unknown option: {opt}");
                        std::process::exit(2);
                    }
                }
                i += 1;
            } else if args[i].starts_with('-') && args[i].len() > 1 {
                // Shell options like -e, -x, etc.
                for ch in args[i][1..].chars() {
                    match ch {
                        'e' => shell.opts_mut().errexit = true,
                        'u' => shell.opts_mut().nounset = true,
                        'x' => shell.opts_mut().xtrace = true,
                        'f' => shell.opts_mut().noglob = true,
                        'n' => shell.opts_mut().noexec = true,
                        _ => {
                            eprintln!("epsh: unknown option: -{ch}");
                            std::process::exit(2);
                        }
                    }
                }
                i += 1;
            } else {
                // Script file
                let filename = &args[i];
                shell.set_args(&args[i..].iter().map(|s| s.as_str()).collect::<Vec<&str>>());
                let content = match std::fs::read(filename) {
                    Ok(bytes) => epsh::encoding::bytes_to_str(&bytes),
                    Err(e) => {
                        eprintln!("epsh: {filename}: {e}");
                        std::process::exit(127);
                    }
                };
                let status = shell.run_script(&content);
                std::process::exit(status);
            }
        }
    }

    // If stdin is a terminal and no -s flag, print usage and exit
    if unsafe { libc::isatty(0) } != 0 {
        eprintln!("epsh — embeddable POSIX shell");
        eprintln!();
        eprintln!(
            "usage: epsh [-e] [-u] [-x] [-f] [-n] [-o option] [-c command] [script [args...]]"
        );
        eprintln!();
        eprintln!("  -c command       execute command string");
        eprintln!("  -e               exit on error (set -e)");
        eprintln!("  -u               error on unset variables (set -u)");
        eprintln!("  -x               print commands before execution (set -x)");
        eprintln!("  -f               disable pathname expansion / globbing (set -f)");
        eprintln!("  -n               parse but do not execute (syntax check) (set -n)");
        eprintln!("  -o pipefail      exit status is highest nonzero pipeline stage");
        eprintln!("  -o errexit       same as -e");
        eprintln!("  -o nounset       same as -u");
        eprintln!("  -o xtrace        same as -x");
        eprintln!("  -o noglob        same as -f");
        eprintln!("  -o noexec        same as -n");
        eprintln!("  +o <option>      disable option");
        eprintln!("  script           execute script file");
        eprintln!("  (no args)        read script from stdin (pipe)");
        std::process::exit(0);
    }

    // Read from stdin (pipe) — preserve non-UTF-8 bytes via PUA encoding
    let mut bytes = Vec::new();
    if let Err(e) = std::io::stdin().read_to_end(&mut bytes) {
        eprintln!("epsh: {e}");
        std::process::exit(1);
    }
    let input = epsh::encoding::bytes_to_str(&bytes);
    let status = shell.run_script(&input);
    std::process::exit(status);
}