use crate::errors::{bail, Result, ResultExt};
use log::trace;
use serde_derive::{Deserialize, Serialize};
use std::collections::hash_map::{Entry, HashMap};
use std::ffi::OsString;
use std::fmt::{Debug, Display};
use std::hash::{BuildHasher, Hash};
use std::io::{stderr, stdout, Write};
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::{env, iter, process};
#[cfg(windows)]
pub fn exe_suffix() -> &'static str {
".exe"
}
#[cfg(not(windows))]
pub fn exe_suffix() -> &'static str {
""
}
pub fn add_to_multihash<K, T, V, S>(hash: &mut HashMap<K, V, S>, key: K, value: T)
where
K: Eq + Hash + Clone,
V: Default + Extend<T>,
S: BuildHasher,
{
match hash.entry(key) {
Entry::Occupied(mut entry) => entry.get_mut().extend(iter::once(value)),
Entry::Vacant(entry) => {
let mut r = V::default();
r.extend(iter::once(value));
entry.insert(r);
}
}
}
pub fn run_command(command: &mut Command) -> Result<()> {
trace!("Executing command: {:?}", command);
let status = command
.status()
.with_context(|_| format!("failed to run command: {:?}", command))?;
if status.success() {
Ok(())
} else {
bail!("command failed with {}: {:?}", status, command);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommandOutput {
pub status: i32,
pub stdout: String,
pub stderr: String,
}
impl CommandOutput {
pub fn is_success(&self) -> bool {
self.status == 0
}
}
pub fn run_command_and_capture_output(command: &mut Command) -> Result<CommandOutput> {
trace!("Executing command: {:?}", command);
command.stdout(process::Stdio::piped());
command.stderr(process::Stdio::piped());
let output = command
.output()
.with_context(|_| format!("failed to run command: {:?}", command))?;
Ok(CommandOutput {
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
status: output.status.code().unwrap_or(-1),
})
}
pub fn get_command_output(command: &mut Command) -> Result<String> {
trace!("Executing command: {:?}", command);
command.stdout(process::Stdio::piped());
command.stderr(process::Stdio::piped());
let output = command
.output()
.with_context(|_| format!("failed to run command: {:?}", command))?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)
.with_context(|_| "comand output is not valid unicode")?)
} else {
let mut stderr = stderr();
writeln!(stderr, "Stdout:")?;
stderr
.write_all(&output.stdout)
.with_context(|_| "output failed")?;
writeln!(stderr, "Stderr:")?;
stderr
.write_all(&output.stderr)
.with_context(|_| "output failed")?;
bail!("command failed with {}: {:?}", output.status, command);
}
}
pub trait MapIfOk<A> {
fn map_if_ok<B, E, F: FnMut(A) -> std::result::Result<B, E>>(
self,
f: F,
) -> std::result::Result<Vec<B>, E>;
}
impl<A, T: IntoIterator<Item = A>> MapIfOk<A> for T {
fn map_if_ok<B, E, F>(self, f: F) -> std::result::Result<Vec<B>, E>
where
F: FnMut(A) -> std::result::Result<B, E>,
{
self.into_iter().map(f).collect()
}
}
pub fn add_env_path_item(env_var_name: &str, mut new_paths: Vec<PathBuf>) -> Result<OsString> {
for path in env::split_paths(&env::var(env_var_name).unwrap_or_default()) {
if new_paths.iter().find(|&x| x == &path).is_none() {
new_paths.push(path);
}
}
Ok(env::join_paths(new_paths).with_context(|_| "env::join_paths failed")?)
}
pub trait Inspect {
fn inspect(self, text: impl Display) -> Self;
}
impl<T: Debug> Inspect for T {
fn inspect(self, text: impl Display) -> Self {
println!("{} {:?}", text, self);
self
}
}
#[derive(Debug)]
struct ProgressBarInner {
message: String,
count: u64,
pos: u64,
last_line_len: usize,
}
#[derive(Clone, Debug)]
pub struct ProgressBar(Arc<Mutex<ProgressBarInner>>);
impl ProgressBar {
pub fn new(count: u64, message: impl Into<String>) -> Self {
let mut progress_bar = ProgressBarInner {
count,
message: message.into(),
pos: 0,
last_line_len: 0,
};
progress_bar.print();
ProgressBar(Arc::new(Mutex::new(progress_bar)))
}
pub fn add(&self, n: u64) {
self.0.lock().unwrap().inc(n);
}
}
impl ProgressBarInner {
fn clear_line(&self) {
print!("\r");
for _ in 0..self.last_line_len {
print!(" ");
}
print!("\r");
}
fn print(&mut self) {
self.clear_line();
let message = format!("{}: {} / {}", self.message, self.pos, self.count);
self.last_line_len = message.len();
print!("{}\r", message);
stdout().flush().unwrap();
}
fn inc(&mut self, n: u64) {
self.pos += n;
self.print();
}
}