1use std::{
4 io,
5 path::{Path, PathBuf},
6 process::Output,
7 process::Stdio,
8 sync::atomic::{AtomicBool, Ordering},
9};
10
11use color_eyre::eyre;
12use smol::{process::Command, unblock};
13
14pub(crate) async fn which(name: &'static str) -> Result<PathBuf, which::Error> {
21 unblock(move || which::which(name)).await
22}
23
24static STD_OUTPUT: AtomicBool = AtomicBool::new(false);
28
29pub fn set_std_output(enabled: bool) {
31 STD_OUTPUT.store(enabled, std::sync::atomic::Ordering::SeqCst);
32}
33
34pub(crate) fn command(command: &mut Command) -> &mut Command {
35 command
36 .kill_on_drop(true)
37 .stdout(if STD_OUTPUT.load(Ordering::SeqCst) {
38 Stdio::inherit()
39 } else {
40 Stdio::piped()
41 })
42 .stderr(if STD_OUTPUT.load(Ordering::SeqCst) {
43 Stdio::inherit()
44 } else {
45 Stdio::piped()
46 })
47}
48
49pub(crate) async fn run_command_output(
56 name: &str,
57 args: impl IntoIterator<Item = &str>,
58) -> eyre::Result<Output> {
59 let result = Command::new(name)
60 .args(args)
61 .kill_on_drop(true)
62 .stdout(Stdio::piped())
63 .stderr(Stdio::piped())
64 .output()
65 .await?;
66
67 if STD_OUTPUT.load(Ordering::SeqCst) {
69 use std::io::Write;
70 let _ = std::io::stdout().write_all(&result.stdout);
71 let _ = std::io::stderr().write_all(&result.stderr);
72 }
73
74 Ok(result)
75}
76
77pub(crate) async fn run_command(
85 name: &str,
86 args: impl IntoIterator<Item = &str>,
87) -> eyre::Result<String> {
88 let result = run_command_output(name, args).await?;
89
90 if result.status.success() {
91 Ok(String::from_utf8_lossy(&result.stdout).to_string())
92 } else {
93 Err(eyre::eyre!(
94 "Command {} failed with status {}",
95 name,
96 result.status
97 ))
98 }
99}
100
101pub(crate) fn parse_whitespace_separated_u32s(input: &str) -> Vec<u32> {
103 input
104 .split_whitespace()
105 .filter_map(|part| part.parse::<u32>().ok())
106 .collect()
107}
108
109pub async fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
116 let from = from.as_ref().to_path_buf();
117 let to = to.as_ref().to_path_buf();
118 unblock(move || reflink::reflink_or_copy(from, to).map(|_| ())).await
119}
120
121#[cfg(test)]
122mod tests {
123 use super::parse_whitespace_separated_u32s;
124
125 #[test]
126 fn parses_pidof_output_with_multiple_pids() {
127 let parsed = parse_whitespace_separated_u32s("123 456\n");
128 assert_eq!(parsed, vec![123, 456]);
129 }
130
131 #[test]
132 fn ignores_non_numeric_tokens() {
133 let parsed = parse_whitespace_separated_u32s("foo 42 bar\n");
134 assert_eq!(parsed, vec![42]);
135 }
136}