cargo-treemap 0.1.0

Interactive treemap analyzer for Rust binary size and dependency API usage — like webpack-bundle-analyzer, for cargo.
//! Output backends: terminal table, machine JSON, and the self-contained HTML treemap.

mod diffhtml;
mod diffout;
mod html;
mod json;
mod table;

use crate::apiusage::DepValue;
use crate::cli::{Format, TreemapArgs};
use crate::diff::DiffNode;
use crate::model::{Meta, Node};
use crate::parity::CrateRow;
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};

pub fn emit_diff(diff: &DiffNode, meta: &Meta, base_path: &Path, args: &TreemapArgs) -> Result<()> {
    diffout::emit(diff, meta, base_path, args)
}

/// Serialize to JSON and neutralize any `</script>` breakout, for inlining into a `<script>`.
pub(crate) fn safe_json<T: serde::Serialize + ?Sized>(v: &T) -> String {
    serde_json::to_string(v)
        .unwrap_or_else(|_| "null".to_string())
        .replace("</", "<\\/")
}

pub fn emit(
    root: &Node,
    meta: &Meta,
    deps: &[DepValue],
    crates: &[CrateRow],
    args: &TreemapArgs,
) -> Result<()> {
    match args.format {
        Format::Table => {
            table::render(root, meta, deps, crates, args.metric);
            Ok(())
        }
        Format::Json => {
            let text = json::render(root, meta, deps, crates)?;
            match &args.output {
                Some(path) => {
                    std::fs::write(path, text)
                        .with_context(|| format!("writing {}", path.display()))?;
                    eprintln!("cargo-treemap: wrote {}", path.display());
                }
                None => println!("{text}"),
            }
            Ok(())
        }
        Format::Html => {
            let html = html::render(root, meta, deps, crates, args.metric);
            let path = args
                .output
                .clone()
                .unwrap_or_else(|| PathBuf::from("cargo-treemap.html"));
            std::fs::write(&path, html).with_context(|| format!("writing {}", path.display()))?;
            let abs = std::fs::canonicalize(&path).unwrap_or(path);
            eprintln!("cargo-treemap: wrote {}", abs.display());
            if args.open {
                open_in_browser(&abs);
            } else {
                eprintln!("cargo-treemap: open it with `--open`, or load the file in a browser");
            }
            Ok(())
        }
    }
}

fn open_in_browser(path: &std::path::Path) {
    #[cfg(target_os = "macos")]
    let cmd = "open";
    #[cfg(target_os = "windows")]
    let cmd = "explorer";
    #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
    let cmd = "xdg-open";
    if let Err(e) = std::process::Command::new(cmd).arg(path).spawn() {
        eprintln!("cargo-treemap: could not open browser ({e}); open {} manually", path.display());
    }
}