cargo-workspace2 0.2.2

A tool to query and manage complex cargo workspaces
Documentation
use crate::cargo::{self, Dependency, Result};
use std::{fmt, path::PathBuf};
use toml_edit::{value, Document, Item, Value};

/// Initial representation of a crate, before being parsed
#[derive(Clone)]
pub struct CargoCrate {
    pub doc: Document,
    pub path: PathBuf,
    pub dependencies: Vec<Dependency>,
}

impl fmt::Debug for CargoCrate {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.path.as_path().display())
    }
}

impl CargoCrate {
    /// Get the crate name from the inner document
    pub fn name(&self) -> String {
        match &self.doc["package"]["name"] {
            Item::Value(Value::String(ref name)) => {
                name.to_string().replace("\"", "").as_str().trim().into()
            }
            _ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)),
        }
    }

    /// Get the current version
    pub fn version(&self) -> String {
        match &self.doc["package"]["version"] {
            Item::Value(Value::String(ref name)) => {
                name.to_string().replace("\"", "").as_str().trim().into()
            }
            _ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)),
        }
    }

    /// Find a cargo dependency by name
    pub fn dep_by_name(&self, name: &String) -> &Dependency {
        self.dependencies
            .iter()
            .find(|c| &c.name == name)
            .as_ref()
            .unwrap()
    }

    pub fn change_dep(&mut self, dep: &String, ver: &String) {
        let dep = self
            .dep_by_name(dep)
            .alias()
            .unwrap_or(dep.to_string())
            .clone();
        cargo::update_dependency(&mut self.doc, &dep, ver);
    }

    pub fn all_deps_mut(&mut self) -> Vec<&mut Dependency> {
        self.dependencies.iter_mut().collect()
    }

    /// Check if this crate depends on a specific version of another
    pub fn has_version(&self, name: &String) -> bool {
        self.dep_by_name(name).has_version()
    }

    /// Check if this crate depends on a specific path of another
    pub fn has_path(&self, name: &String) -> bool {
        self.dep_by_name(name).has_version()
    }

    /// Set a new version for this crate
    pub fn set_version(&mut self, version: String) {
        self.doc["package"]["version"] = value(version);
    }

    /// Sync any changes made to the document to disk
    pub fn sync(&mut self) {
        cargo::sync(&mut self.doc, self.path.join("Cargo.toml")).unwrap();
    }
}

/// Initial representation of the workspate, before getting parsed
pub struct CargoWorkspace {
    pub root: PathBuf,
    pub crates: Vec<CargoCrate>,
}

impl CargoWorkspace {
    /// Open a workspace and parse dependency graph
    ///
    /// Point this to the root of the workspace, do the root
    /// `Cargo.toml` file.
    pub fn open(p: impl Into<PathBuf>) -> Result<Self> {
        let path = p.into();

        let root_cfg = cargo::parse_root_toml(path.join("Cargo.toml"))?;
        let members = cargo::get_members(&root_cfg)?;

        let m_cfg: Vec<_> = members
            .into_iter()
            .filter_map(
                |name| match cargo::parse_toml(path.join(&name).join("Cargo.toml")) {
                    Ok(doc) => Some((
                        PathBuf::new().join(name),
                        cargo::parse_dependencies(&doc),
                        doc,
                    )),
                    Err(e) => {
                        eprintln!(
                            "Error occured while parsing member `{}`/`Cargo.toml`: {:?}",
                            name, e
                        );
                        None
                    }
                },
            )
            .collect();

        Ok(Self {
            root: path,
            crates: m_cfg
                .into_iter()
                .map(|(path, dependencies, doc)| CargoCrate {
                    path,
                    dependencies,
                    doc,
                })
                .collect(),
        })
    }
}