at 8d154b4
use std::io::IsTerminal; use std::path::{Path, PathBuf}; use flate2::Compression; use flate2::write::GzEncoder; use miette::{IntoDiagnostic, Result}; /// Expands shell-style variables in a path string. /// /// This function takes a path containing variables in `$VAR` or `${VAR}` syntax /// and substitutes them with values from the provided iterator of key-value pairs. /// Any variables not found in the provided mappings are left unchanged. /// /// # Generic Parameters /// /// * `P` - A type that can be converted to a `Path` /// * `I` - An iterator-like type that produces `(S, S)` tuples /// * `S` - A type that can be read as `&str` /// /// # Arguments /// /// * `path` - The path string or `Path` object containing variables /// * `vars` - An iterator of `(key, value)` pairs for variable substitution /// /// # Returns /// /// A `PathBuf` with all variables substituted with their corresponding values. /// /// # Examples /// /// ``` /// use std::collections::HashMap; /// /// let mut vars = HashMap::new(); /// vars.insert("USER", "john"); /// vars.insert("PROJECT", "rust"); /// /// let expanded = expand_variables("/home/$USER/$PROJECT", vars); /// assert_eq!(expanded.to_string_lossy(), "/home/john/rust"); /// ``` /// /// With a vector: /// /// ``` /// let vars = vec![("USER", "john"), ("PROJECT", "rust")]; /// let expanded = expand_variables("/home/$USER/${PROJECT}", vars); /// assert_eq!(expanded.to_string_lossy(), "/home/john/rust"); /// ``` pub fn expand_variables<P, I, S>(path: P, vars: I) -> PathBuf where P: AsRef<Path>, I: IntoIterator<Item = (S, S)>, S: AsRef<str>, { let mut path_str = path.as_ref().to_string_lossy().to_string(); for (key, value) in vars { let key = key.as_ref(); let value = value.as_ref(); path_str = path_str.replace(&format!("${{{key}}}"), value); path_str = path_str.replace(&format!("${key}"), value); } PathBuf::from(path_str) } /// Pack `src` directory into a `.tar.gz` archive at `dest`. /// /// The top-level entry inside the archive is named after the source directory. pub(crate) fn archive_dir(src: &Path, dest: &Path) -> Result<()> { let dir_name = src .file_name() .map(|n| n.to_string_lossy().into_owned()) .unwrap_or_else(|| "docs".to_owned()); let file = std::fs::File::create(dest).into_diagnostic()?; let enc = GzEncoder::new(file, Compression::default()); let mut archive = tar::Builder::new(enc); archive.append_dir_all(&dir_name, src).into_diagnostic()?; archive .into_inner() .into_diagnostic()? .finish() .into_diagnostic()?; Ok(()) } /// Check whether the program is running in an interactive terminal suitable /// for progress bars and spinners. /// /// Returns `false` when stdout is not a terminal (piped to a file or another /// process) or when `TERM` is set to `dumb`. This lets callers gracefully /// fall back to plain log output instead of using ANSI-spinner-based UI. pub fn is_interactive() -> bool { std::io::stdout().is_terminal() && std::env::var("TERM").ok().as_deref() != Some("dumb") }