Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

mikronova/modgunn

.nornir/manifests-guide.md

manifests — guide

Class: guide · audience: someone turning a project's manifest/lockfile into components. The what is in manifests-design.md; the emit side is sbom-guide.md. Needs --features manifests.

f0 — parse a manifest by filename (the common path)

use modgunn::manifests::parse_manifest;

// Dispatches on the basename; a path is fine — only the last component matters.
let text = std::fs::read_to_string("Cargo.lock")?;
match parse_manifest("Cargo.lock", &text) {
    Some(Ok(components)) => {
        for c in &components {
            println!("{}/{}@{}", c.ecosystem, c.name, c.version);   // cargo/serde@1.0.203
        }
    }
    Some(Err(e)) => eprintln!("malformed manifest: {e}"),
    None => eprintln!("no ecosystem parser owns this filename"),
}

Works the same for every ecosystem — swap the filename and content: requirements.txt, poetry.lock, Pipfile.lock, package-lock.json, pnpm-lock.yaml, pom.xml, go.mod, go.sum, packages.lock.json, app.csproj, Gemfile.lock.

f1 — straight into a CycloneDX SBOM

The parsed Vec<SbomComponent> is exactly what sbom::to_cyclonedx takes, so a polyglot repo becomes one SBOM with correctly-tagged purls and no ecosystem-specific glue:

use modgunn::{manifests::parse_manifest, sbom::{to_cyclonedx, SbomInput}};

let mut components = Vec::new();
for (file, text) in manifests_on_disk {                 // (String, String)
    if let Some(Ok(mut cs)) = parse_manifest(&file, &text) {
        components.append(&mut cs);
    }
}
let doc = to_cyclonedx(&SbomInput { subject: "my-repo".into(), components, vulns: vec![] });
// components[i].purl → pkg:cargo/…, pkg:pip/…, pkg:npm/…, pkg:go/…, …

f2 — call one ecosystem parser directly

When you already know the ecosystem, skip dispatch:

use modgunn::manifests::{CargoParser, EcosystemParser};
let comps = CargoParser.parse("Cargo.lock", &text)?;    // Result<Vec<SbomComponent>, ParseError>

modgunn::manifests::all() returns every parser (Box<dyn EcosystemParser>) if you want to advertise or iterate the supported ecosystems.

f3 — from the command line (no Rust)

The same seam is a CLI verb, so you can turn a checked-out repo into components (or a full SBOM) without writing any code. Build the binary with the manifests feature and run ingest:

$ cargo run --features warehouse,manifests -- ingest --path Cargo.lock
cargo   anyhow 1.0.86
cargo   serde 1.0.203
2 component(s) from 1 manifest(s) [cargo 2]

# a whole repo (walks it, skipping .git/target/node_modules/…); one ecosystem only:
$ modgunn ingest --path ./my-repo --ecosystem pip

# emit a CycloneDX 1.5 SBOM instead of the listing:
$ modgunn ingest --path Cargo.lock --sbom --subject my-repo > sbom.json

Offline and zero-shell — it never runs the project's pip/npm/go/mvn. An explicit file no parser owns exits non-zero with the supported-manifest list; a malformed manifest is named on stderr and skipped, and the run still succeeds for the rest. Without the manifests feature the verb prints a one-line rebuild hint.

Gotchas