Abbaye

at e1dfdc3

use std::collections::HashMap;
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();

    // Convert iterator into a HashMap for efficient lookup
    let var_map: HashMap<String, String> = vars
        .into_iter()
        .map(|(k, v)| (k.as_ref().to_string(), v.as_ref().to_string()))
        .collect();

    // Replace each variable
    for (key, value) in var_map {
        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(())
}