use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
pub fn run_cmd(_pl: &(), cmd: &[String], stdin: Option<&str>, strip: bool) -> Option<String> {
let mut child = Command::new(cmd.first()?)
.args(&cmd[1..])
.stdout(Stdio::piped())
.stdin(Stdio::piped())
.spawn()
.ok()?;
if let Some(s) = stdin {
if let Some(mut child_stdin) = child.stdin.take() {
let _ = child_stdin.write_all(s.as_bytes());
}
} else {
drop(child.stdin.take());
}
let output = child.wait_with_output().ok()?;
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
if strip {
Some(stdout.trim().to_string())
} else {
Some(stdout)
}
}
pub fn asrun(pl: &(), ascript: &str) -> Option<String> {
run_cmd(
pl,
&["osascript".to_string(), "-".to_string()],
Some(ascript),
true,
)
}
pub fn readlines(cmd: &[String], cwd: &std::path::Path) -> Vec<String> {
let output = match Command::new(cmd.first().map(|s| s.as_str()).unwrap_or(""))
.args(&cmd[1..])
.current_dir(cwd)
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
{
Ok(o) => o,
Err(_) => return Vec::new(),
};
String::from_utf8_lossy(&output.stdout)
.lines()
.map(String::from)
.collect()
}
pub fn which(cmd: &str) -> Option<PathBuf> {
if cmd.contains(std::path::MAIN_SEPARATOR) {
let p = PathBuf::from(cmd);
if _access_check(&p) {
return Some(p);
}
return None;
}
let path = std::env::var_os("PATH")?;
let mut seen = std::collections::HashSet::new();
for dir in std::env::split_paths(&path) {
if !seen.insert(dir.clone()) {
continue;
}
let candidate = dir.join(cmd);
if _access_check(&candidate) {
return Some(candidate);
}
}
None
}
fn _access_check(fn_path: &std::path::Path) -> bool {
if !fn_path.exists() {
return false;
}
if fn_path.is_dir() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
match fn_path.metadata() {
Ok(m) => m.permissions().mode() & 0o111 != 0,
Err(_) => false,
}
}
#[cfg(not(unix))]
{
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_cmd_echo_returns_stdout() {
let out = run_cmd(&(), &["echo".to_string(), "hello".to_string()], None, true);
assert_eq!(out.as_deref(), Some("hello"));
}
#[test]
fn run_cmd_missing_command_returns_none() {
let out = run_cmd(
&(),
&["powerliners-nonexistent-command".to_string()],
None,
true,
);
assert!(out.is_none());
}
#[test]
fn run_cmd_no_strip_keeps_trailing_newline() {
let out = run_cmd(&(), &["echo".to_string(), "hello".to_string()], None, false);
assert_eq!(out.as_deref(), Some("hello\n"));
}
#[test]
fn which_finds_echo() {
let p = which("sh");
assert!(p.is_some(), "which('sh') should find /bin/sh on Unix");
}
#[test]
fn which_missing_returns_none() {
let p = which("powerliners-nonexistent-binary");
assert!(p.is_none());
}
#[test]
fn readlines_returns_per_line() {
let lines = readlines(
&["printf".to_string(), "a\nb\nc\n".to_string()],
&PathBuf::from("/"),
);
assert_eq!(
lines,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
}
}