dcr 0.6.8

DCR is a utility for managing C/C++ projects in a Cargo-like style.
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#[allow(dead_code)]
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum DepSource {
    Path(String),
    Git {
        url: String,
        branch: Option<String>,
        tag: Option<String>,
        rev: Option<String>,
    },
    Registry {
        version: String,
        features: Option<Vec<String>>,
    },
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DepSpec {
    pub name: String,
    pub source: DepSource,
    pub include_raw: Option<Vec<String>>,
    pub lib_raw: Option<Vec<String>>,
    pub libs_raw: Option<Vec<String>>,
}

#[derive(Debug, Clone, Default)]
pub struct ResolvedDeps {
    pub include_dirs: Vec<String>,
    pub lib_dirs: Vec<String>,
    pub libs: Vec<String>,
}

#[allow(dead_code)]
pub fn resolve_path(project_root: &Path, raw: &str) -> Result<PathBuf, String> {
    let p = Path::new(raw);
    let full = if p.is_absolute() {
        p.to_path_buf()
    } else {
        project_root.join(p)
    };
    Ok(full)
}

#[allow(dead_code)]
pub fn resolve_paths(
    base: &Path,
    raw: Option<&[String]>,
    defaults: &[&str],
    profile: &str,
) -> Result<Vec<PathBuf>, String> {
    let mut out = Vec::new();
    if let Some(raw) = raw {
        for r in raw {
            let expanded = expand_profile(r, profile);
            let p = Path::new(&expanded);
            let full = if p.is_absolute() {
                p.to_path_buf()
            } else {
                base.join(p)
            };
            if !full.exists() {
                return Err(format!("Path does not exist: {}", full.display()));
            }
            out.push(full);
        }
        return Ok(out);
    }

    for d in defaults {
        let candidate = base.join(d);
        if candidate.exists() {
            out.push(candidate);
        }
    }
    Ok(out)
}

#[allow(dead_code)]
pub fn expand_profile(raw: &str, profile: &str) -> String {
    raw.replace("{profile}", profile)
}

#[allow(dead_code)]
pub fn sync_dep_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
    if dst.exists() {
        fs::remove_dir_all(dst)?;
    }
    copy_dir_all(src, dst)
}

#[allow(dead_code)]
pub fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> {
    fs::create_dir_all(dst)?;
    for entry in fs::read_dir(src)? {
        let entry = entry?;
        let ty = entry.file_type()?;
        let from = entry.path();
        let to = dst.join(entry.file_name());
        if ty.is_dir() {
            copy_dir_all(&from, &to)?;
        } else {
            fs::copy(&from, &to)?;
        }
    }
    Ok(())
}