ritual_common/
utils.rs

1//! Various utilities.
2
3use crate::errors::{bail, Result, ResultExt};
4use log::trace;
5use serde_derive::{Deserialize, Serialize};
6use std::collections::hash_map::{Entry, HashMap};
7use std::ffi::OsString;
8use std::fmt::{Debug, Display};
9use std::hash::{BuildHasher, Hash};
10use std::io::{stderr, stdout, Write};
11use std::path::PathBuf;
12use std::process::Command;
13use std::sync::{Arc, Mutex};
14use std::{env, iter, process};
15
16#[cfg(windows)]
17/// Returns proper executable file suffix on current platform.
18/// Returns `".exe"` on Windows and `""` on other platforms.
19pub fn exe_suffix() -> &'static str {
20    ".exe"
21}
22
23#[cfg(not(windows))]
24/// Returns proper executable file suffix on current platform.
25/// Returns `".exe"` on Windows and `""` on other platforms.
26pub fn exe_suffix() -> &'static str {
27    ""
28}
29
30/// Creates and empty collection at `hash[key]` if there isn't one already.
31/// Adds `value` to `hash[key]` collection.
32pub fn add_to_multihash<K, T, V, S>(hash: &mut HashMap<K, V, S>, key: K, value: T)
33where
34    K: Eq + Hash + Clone,
35    V: Default + Extend<T>,
36    S: BuildHasher,
37{
38    match hash.entry(key) {
39        Entry::Occupied(mut entry) => entry.get_mut().extend(iter::once(value)),
40        Entry::Vacant(entry) => {
41            let mut r = V::default();
42            r.extend(iter::once(value));
43            entry.insert(r);
44        }
45    }
46}
47
48/// Runs a command and checks that it was successful
49pub fn run_command(command: &mut Command) -> Result<()> {
50    trace!("Executing command: {:?}", command);
51    let status = command
52        .status()
53        .with_context(|_| format!("failed to run command: {:?}", command))?;
54    if status.success() {
55        Ok(())
56    } else {
57        bail!("command failed with {}: {:?}", status, command);
58    }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct CommandOutput {
63    pub status: i32,
64    pub stdout: String,
65    pub stderr: String,
66}
67
68impl CommandOutput {
69    pub fn is_success(&self) -> bool {
70        self.status == 0
71    }
72}
73
74/// Runs a command and returns its output regardless of
75/// whether it was successful
76pub fn run_command_and_capture_output(command: &mut Command) -> Result<CommandOutput> {
77    trace!("Executing command: {:?}", command);
78    command.stdout(process::Stdio::piped());
79    command.stderr(process::Stdio::piped());
80    let output = command
81        .output()
82        .with_context(|_| format!("failed to run command: {:?}", command))?;
83    Ok(CommandOutput {
84        stdout: String::from_utf8_lossy(&output.stdout).to_string(),
85        stderr: String::from_utf8_lossy(&output.stderr).to_string(),
86        status: output.status.code().unwrap_or(-1),
87    })
88}
89
90/// Runs a command and returns its stdout if it was successful
91pub fn get_command_output(command: &mut Command) -> Result<String> {
92    trace!("Executing command: {:?}", command);
93    command.stdout(process::Stdio::piped());
94    command.stderr(process::Stdio::piped());
95    let output = command
96        .output()
97        .with_context(|_| format!("failed to run command: {:?}", command))?;
98    if output.status.success() {
99        Ok(String::from_utf8(output.stdout)
100            .with_context(|_| "comand output is not valid unicode")?)
101    } else {
102        let mut stderr = stderr();
103        writeln!(stderr, "Stdout:")?;
104        stderr
105            .write_all(&output.stdout)
106            .with_context(|_| "output failed")?;
107        writeln!(stderr, "Stderr:")?;
108        stderr
109            .write_all(&output.stderr)
110            .with_context(|_| "output failed")?;
111        bail!("command failed with {}: {:?}", output.status, command);
112    }
113}
114
115/// Perform a map operation that can fail
116pub trait MapIfOk<A> {
117    /// Call closure `f` on each element of the collection and return
118    /// `Vec` of values returned by the closure. If closure returns `Err`
119    /// at some iteration, return that `Err` instead.
120    fn map_if_ok<B, E, F: FnMut(A) -> std::result::Result<B, E>>(
121        self,
122        f: F,
123    ) -> std::result::Result<Vec<B>, E>;
124}
125
126impl<A, T: IntoIterator<Item = A>> MapIfOk<A> for T {
127    fn map_if_ok<B, E, F>(self, f: F) -> std::result::Result<Vec<B>, E>
128    where
129        F: FnMut(A) -> std::result::Result<B, E>,
130    {
131        self.into_iter().map(f).collect()
132    }
133}
134
135/// Reads environment variable `env_var_name`, adds `new_paths`
136/// to acquired list of paths and returns the list formatted as path list
137/// (without applying it).
138pub fn add_env_path_item(env_var_name: &str, mut new_paths: Vec<PathBuf>) -> Result<OsString> {
139    for path in env::split_paths(&env::var(env_var_name).unwrap_or_default()) {
140        if new_paths.iter().find(|&x| x == &path).is_none() {
141            new_paths.push(path);
142        }
143    }
144    Ok(env::join_paths(new_paths).with_context(|_| "env::join_paths failed")?)
145}
146
147pub trait Inspect {
148    fn inspect(self, text: impl Display) -> Self;
149}
150
151impl<T: Debug> Inspect for T {
152    fn inspect(self, text: impl Display) -> Self {
153        println!("{} {:?}", text, self);
154        self
155    }
156}
157
158#[derive(Debug)]
159struct ProgressBarInner {
160    message: String,
161    count: u64,
162    pos: u64,
163    last_line_len: usize,
164}
165
166#[derive(Clone, Debug)]
167pub struct ProgressBar(Arc<Mutex<ProgressBarInner>>);
168
169impl ProgressBar {
170    pub fn new(count: u64, message: impl Into<String>) -> Self {
171        let mut progress_bar = ProgressBarInner {
172            count,
173            message: message.into(),
174            pos: 0,
175            last_line_len: 0,
176        };
177        progress_bar.print();
178        ProgressBar(Arc::new(Mutex::new(progress_bar)))
179    }
180
181    pub fn add(&self, n: u64) {
182        self.0.lock().unwrap().inc(n);
183    }
184}
185
186impl ProgressBarInner {
187    fn clear_line(&self) {
188        print!("\r");
189        for _ in 0..self.last_line_len {
190            print!(" ");
191        }
192        print!("\r");
193    }
194    fn print(&mut self) {
195        self.clear_line();
196        let message = format!("{}: {} / {}", self.message, self.pos, self.count);
197        self.last_line_len = message.len();
198        print!("{}\r", message);
199        stdout().flush().unwrap();
200    }
201
202    fn inc(&mut self, n: u64) {
203        self.pos += n;
204        self.print();
205    }
206}