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 {
36 command
37 .kill_on_drop(true)
38 .stdout(if STD_OUTPUT.load(Ordering::SeqCst) {
39 Stdio::inherit()
40 } else {
41 Stdio::piped()
42 })
43 .stderr(if STD_OUTPUT.load(Ordering::SeqCst) {
44 Stdio::inherit()
45 } else {
46 Stdio::piped()
47 })
48}
49
50pub(crate) async fn run_command_output(
57 name: &str,
58 args: impl IntoIterator<Item = &str>,
59) -> eyre::Result<Output> {
60 let result = Command::new(name)
61 .args(args)
62 .kill_on_drop(true)
63 .stdout(Stdio::piped())
64 .stderr(Stdio::piped())
65 .output()
66 .await?;
67
68 if STD_OUTPUT.load(Ordering::SeqCst) {
70 use std::io::Write;
71 let _ = std::io::stdout().write_all(&result.stdout);
72 let _ = std::io::stderr().write_all(&result.stderr);
73 }
74
75 Ok(result)
76}
77
78pub(crate) async fn run_command(
86 name: &str,
87 args: impl IntoIterator<Item = &str>,
88) -> eyre::Result<String> {
89 let result = run_command_output(name, args).await?;
90
91 if result.status.success() {
92 Ok(String::from_utf8_lossy(&result.stdout).to_string())
93 } else {
94 Err(eyre::eyre!(
95 "Command {} failed with status {}",
96 name,
97 result.status
98 ))
99 }
100}
101
102pub(crate) fn parse_whitespace_separated_u32s(input: &str) -> Vec<u32> {
104 input
105 .split_whitespace()
106 .filter_map(|part| part.parse::<u32>().ok())
107 .collect()
108}
109
110pub async fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
117 let from = from.as_ref().to_path_buf();
118 let to = to.as_ref().to_path_buf();
119 unblock(move || reflink::reflink_or_copy(from, to).map(|_| ())).await
120}
121
122#[cfg(test)]
123mod tests {
124 use super::parse_whitespace_separated_u32s;
125
126 #[test]
127 fn parses_pidof_output_with_multiple_pids() {
128 let parsed = parse_whitespace_separated_u32s("123 456\n");
129 assert_eq!(parsed, vec![123, 456]);
130 }
131
132 #[test]
133 fn ignores_non_numeric_tokens() {
134 let parsed = parse_whitespace_separated_u32s("foo 42 bar\n");
135 assert_eq!(parsed, vec![42]);
136 }
137}