Skip to main content

repo_cli/
util.rs

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            // TODO: Have computed the larget string before calling this
113            // but format does not allow formatting with dynamic variables.
114            // This means that I cant format left based on the max_size
115            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}