chaud-hot 0.1.2

Implementation detail of Chaud.
Documentation
use super::Cargo;
use crate::cargo::StdioMode;
use crate::util::CommandExt as _;
use anyhow::{Context as _, Result};
use camino::{Utf8Path, Utf8PathBuf};
use core::fmt;
use core::str::Chars;
use nanoserde::{DeJson, DeJsonErr, DeJsonState, DeJsonTok};

#[derive(Debug, DeJson)]
pub struct Metadata {
    packages: Vec<Package>,
}

impl Cargo {
    pub fn load_metadata(&self) -> Result<Metadata> {
        let buf = run_cargo(self).context("Failed to run `cargo metadata`")?;
        Metadata::deserialize_json(&buf).context("Failed to parse `cargo metadata` output")
    }
}

impl Metadata {
    pub fn packages(&self) -> &[Package] {
        &self.packages
    }
}
#[derive(Debug, DeJson)]
pub struct Package {
    name: PackageName,
    manifest_path: ManifestPath,
    dependencies: Vec<Dependency>,
    targets: Vec<Target>,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ManifestPath(Utf8PathBuf);

impl DeJson for ManifestPath {
    fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<Self, DeJsonErr> {
        s.string(i)?;
        Ok(ManifestPath(s.strbuf.clone().into()))
    }
}

impl ManifestPath {
    pub fn new(path: impl Into<Utf8PathBuf>) -> Self {
        Self(path.into())
    }

    pub fn path(&self) -> &Utf8Path {
        &self.0
    }
}

impl Package {
    pub fn name(&self) -> &PackageName {
        &self.name
    }

    pub fn manifest_path(&self) -> &ManifestPath {
        &self.manifest_path
    }

    pub fn dependencies(&self) -> &[Dependency] {
        &self.dependencies
    }

    pub fn targets(&self) -> &[Target] {
        &self.targets
    }
}

#[derive(Debug, DeJson, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[nserde(transparent)]
pub struct PackageName(String);

impl fmt::Display for PackageName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "`{}`", self.0)
    }
}

#[derive(Debug, DeJson)]
pub struct Dependency {
    name: PackageName,
    kind: DependencyKind,
}

impl Dependency {
    pub fn name(&self) -> &PackageName {
        &self.name
    }

    pub fn kind(&self) -> DependencyKind {
        self.kind
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum DependencyKind {
    Normal,
    Build,
    Other,
}

impl DeJson for DependencyKind {
    fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<Self, DeJsonErr> {
        if s.tok == DeJsonTok::Null {
            s.next_tok(i)?;
            return Ok(Self::Normal);
        }
        s.string(i)?;
        match s.strbuf.as_ref() {
            "build" => Ok(Self::Build),
            _ => Ok(Self::Other),
        }
    }
}

#[derive(Debug, DeJson)]
pub struct Target {
    name: TargetName,
    kind: Vec<TargetKind>,
    #[nserde(proxy = "String")]
    src_path: Utf8PathBuf,
}

impl Target {
    pub fn name(&self) -> &TargetName {
        &self.name
    }

    pub fn kind(&self) -> &[TargetKind] {
        &self.kind
    }

    pub fn src_path(&self) -> &Utf8Path {
        &self.src_path
    }
}

#[derive(Debug, DeJson, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[nserde(transparent)]
pub struct TargetName(String);

impl PartialEq<str> for TargetName {
    fn eq(&self, other: &str) -> bool {
        self.0 == other
    }
}

impl TargetName {
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TargetKind {
    Bin,
    Lib,
    RLib,
    CustomBuild,
    ProcMacro,
    Other,
}

impl DeJson for TargetKind {
    fn de_json(s: &mut DeJsonState, i: &mut Chars) -> Result<Self, DeJsonErr> {
        s.string(i)?;
        match s.strbuf.as_ref() {
            "bin" => Ok(Self::Bin),
            "lib" => Ok(Self::Lib),
            "rlib" => Ok(Self::RLib),
            "custom-build" => Ok(Self::CustomBuild),
            "proc-macro" => Ok(Self::ProcMacro),
            _ => Ok(Self::Other),
        }
    }
}

fn run_cargo(cargo: &Cargo) -> Result<String> {
    let mut cmd = cargo.cmd("metadata", StdioMode::LoudCapture);

    cmd.args(["--format-version=1", "--no-deps"]);

    log::trace!("Running {cmd:?}");

    let output = cmd.stdout_str()?;

    Ok(output)
}