1use anyhow::{Context, Result};
2use std::{
3 borrow::Borrow,
4 fs::{File, OpenOptions},
5 io::Read,
6 path::{Path, PathBuf},
7};
8
9pub fn make_path_buf<S: AsRef<str>>(s: S) -> Result<PathBuf> {
10 shellexpand::full(s.as_ref())
11 .map(|s| PathBuf::from(s.borrow() as &str))
12 .map_err(Into::into)
13}
14
15pub fn read_content<P>(path: P) -> Result<String>
16where
17 P: AsRef<Path> + std::fmt::Debug,
18{
19 let mut content = String::new();
20 File::open(&path)
21 .context(format!("failed to open file: {:#?}", path))?
22 .read_to_string(&mut content)
23 .context(format!("failed to read file: '{:#?}'", path))?;
24
25 Ok(content)
26}
27
28pub fn write_content<P, F>(path: P, write_fn: F) -> Result<()>
29where
30 P: AsRef<Path>,
31 F: FnOnce(&mut File) -> Result<()>,
32{
33 std::fs::create_dir_all(path.as_ref().parent().unwrap())?;
34 let mut file = OpenOptions::new()
35 .write(true)
36 .create(true)
37 .truncate(true)
38 .open(&path)?;
39 write_fn(&mut file)
40}
41
42pub mod process {
43 use anyhow::{Context, Result};
44 use std::{
45 io::{BufRead, BufReader},
46 process::{Command, ExitStatus, Stdio},
47 };
48
49 pub fn inherit(name: &str) -> Command {
50 let mut command = Command::new(name);
51 command.stdin(Stdio::inherit());
52 command.stdout(Stdio::inherit());
53 command.stderr(Stdio::inherit());
54 command
55 }
56
57 pub fn piped(name: &str) -> Command {
58 let mut command = Command::new(name);
59 command.stdin(Stdio::piped());
60 command.stdout(Stdio::piped());
61 command.stderr(Stdio::piped());
62 command
63 }
64
65 pub fn null(name: &str) -> Command {
66 let mut command = Command::new(name);
67 command.stdin(Stdio::null());
68 command.stdout(Stdio::null());
69 command.stderr(Stdio::null());
70 command
71 }
72
73 pub fn execute_command(command: &mut Command, prefix: String) -> Result<ExitStatus> {
74 let mut child = command
75 .spawn()
76 .context("failed executing command as a child process")?;
77
78 let stdout_child = if let Some(stdout) = child.stdout.take() {
79 let pre = prefix.clone();
80 Some(std::thread::spawn(move || forward_stdout(stdout, &pre)))
81 } else {
82 None
83 };
84
85 if let Some(stderr) = child.stderr.take() {
86 forward_stdout(stderr, &prefix).context("could not forward stderr to stdout")?;
87 }
88
89 if let Some(child_thread) = stdout_child {
90 child_thread
91 .join()
92 .expect("failed to join stdout child thread with main thread")?;
93 }
94
95 child.wait().map_err(Into::into)
96 }
97
98 fn forward_stdout<T>(read: T, prefix: &str) -> Result<()>
99 where
100 T: std::io::Read,
101 {
102 let mut buffer = BufReader::new(read);
103 loop {
104 let mut line = String::new();
105 let result = buffer
106 .read_line(&mut line)
107 .context("could not read buffered line")?;
108 if result == 0 {
109 break;
110 }
111
112 let prefix = format!("{:>20.20} |", prefix);
116 print!("{} {}", prefix, line);
117 }
118
119 Ok(())
120 }
121}
122
123#[cfg(not(windows))]
124pub fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
125 path.as_ref().canonicalize().map_err(Into::into)
126}
127
128#[cfg(windows)]
129pub fn canonicalize<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
130 path.as_ref()
131 .canonicalize()
132 .map_err(Into::into)
133 .map(|path| {
134 path.to_string_lossy()
135 .trim_start_matches(r"\\?\")
136 .replace("\\", "/")
137 })
138 .map(PathBuf::from)
139}