xbp 10.7.0

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! utility functions for xbp cli
//!
//! provides utility modules for common operations
//! includes version api integration and other helper functions

pub mod version;

pub use version::{fetch_version, increment_version};

use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub struct FoundXbpConfig {
    pub project_root: PathBuf,
    pub config_path: PathBuf,
    pub kind: &'static str,
    pub location: String,
}

pub fn find_xbp_config_upwards(start_dir: &Path) -> Option<FoundXbpConfig> {
    for dir in start_dir.ancestors() {
        let candidates: [(PathBuf, &'static str); 6] = [
            (dir.join(".xbp").join("xbp.yaml"), "yaml"),
            (dir.join(".xbp").join("xbp.yml"), "yaml"),
            (dir.join(".xbp").join("xbp.json"), "json"),
            (dir.join("xbp.yaml"), "yaml"),
            (dir.join("xbp.yml"), "yaml"),
            (dir.join("xbp.json"), "json"),
        ];

        for (path, kind) in candidates {
            if !path.exists() {
                continue;
            }

            let project_root = path
                .parent()
                .and_then(|p| {
                    if p.file_name() == Some(std::ffi::OsStr::new(".xbp")) {
                        p.parent().map(|pp| pp.to_path_buf())
                    } else {
                        Some(p.to_path_buf())
                    }
                })
                .unwrap_or_else(|| dir.to_path_buf());

            let location = path
                .strip_prefix(&project_root)
                .ok()
                .map(|p| p.to_string_lossy().replace('\\', "/"))
                .unwrap_or_else(|| path.to_string_lossy().replace('\\', "/"));

            return Some(FoundXbpConfig {
                project_root,
                config_path: path,
                kind,
                location,
            });
        }
    }

    None
}

pub fn expand_home_in_string(input: &str) -> String {
    let home = dirs::home_dir()
        .unwrap_or_else(|| std::path::PathBuf::from("."))
        .to_string_lossy()
        .to_string();

    if input == "~" {
        return home;
    }

    if let Some(rest) = input
        .strip_prefix("~/")
        .or_else(|| input.strip_prefix("~\\"))
    {
        return format!("{}{}{}", home, std::path::MAIN_SEPARATOR, rest);
    }

    if let Some(rest) = input
        .strip_prefix("$HOME/")
        .or_else(|| input.strip_prefix("$HOME\\"))
    {
        return format!("{}{}{}", home, std::path::MAIN_SEPARATOR, rest);
    }

    if let Some(rest) = input
        .strip_prefix("${HOME}/")
        .or_else(|| input.strip_prefix("${HOME}\\"))
    {
        return format!("{}{}{}", home, std::path::MAIN_SEPARATOR, rest);
    }

    input.to_string()
}

pub fn collapse_home_to_env(input: &str) -> String {
    let home = dirs::home_dir()
        .unwrap_or_else(|| std::path::PathBuf::from("."))
        .to_string_lossy()
        .to_string();

    if input == home {
        return "$HOME".to_string();
    }

    if let Some(rest) = input.strip_prefix(&(home.clone() + "/")) {
        return format!("$HOME/{}", rest);
    }

    if let Some(rest) = input.strip_prefix(&(home.clone() + "\\")) {
        return format!("$HOME\\{}", rest);
    }

    input.to_string()
}