← All showcases

modinfo — a Raku distribution inspector

Source on GitHub ↗

modinfo reads META6.json files, builds the dependency graph, validates the metadata, fingerprints the source, and reports in table / JSON / YAML / XML.

It is the ecosystem showcase: almost nothing below the argument parser is hand-rolled. Seventeen distributions from the ecosystem's most-depended-on list do the work, and the program runs unchanged on Rakudo and on Raku++.

build/rakupp showcase/modinfo/modinfo.raku list
build/rakupp showcase/modinfo/modinfo.raku deps Gadget
build/rakupp showcase/modinfo/modinfo.raku path JSON::Fast --installed
build/rakupp showcase/modinfo/modinfo.raku rank --path=~/dists --top=20
build/rakupp showcase/modinfo/modinfo.raku export --format=xml --out=/tmp/report

path answers where a module (or every module of a distribution) lives on this machine: the real file under a scanned checkout, or the installation store's content-addressed source blob for --installed — resolved from the same repository chain the engine itself searches.

What it is built on

ModuleWhat it does here
JSON::Fastreads every META6.json; writes the JSON report
YAMLishreads the config file; writes the YAML report
XMLbuilds the XML report as a document, then indents the tree
Configlayered configuration with dotted-path access (report.width)
Hash::Mergemerges those layers: defaults < file < command line
IO::Globfinds */META6.json, and the installed-distribution database
File::Findwalks lib/ to catch files provides forgot
File::Whichlocates the engine modinfo about is running under
File::Directory::Treecreates the --out directory
File::Tempatomic writes: temp file in place, then rename
Digest (SHA-1)per-file content hashes
MIME::Base64turns those into npm-style sha1-<base64> integrity strings
Abbreviationsmodinfo show JSON::F resolves to JSON::Fast
Text::Utilswrap-paragraph, commify, list2text
Terminal::ANSIColornamed styles
Colorthe green→red heat gradient on the count columns
Data::Dump--debug

Config ships only a NULL parser — every real format lives in its own distribution — so modinfo.raku defines a Config::Parser::YAMLish class that satisfies Config's parser interface on top of YAMLish. Composing two ecosystem distributions that were never written for each other is the point.

Commands

CommandWhat it prints
listone row per distribution: version, auth, dependency counts, provides
show <dist>one distribution in full, with its fingerprint
deps <dist>what it depends on, as a tree, cycles cut and labelled
rdeps <dist>what depends on it, same shape
graphroots, leaves, topological build order, cycles, external references
rankdistributions ranked by number of dependents
check [<dist>]metadata validation; exits non-zero when anything is an ERROR
exportthe whole model as json, yaml, xml or all
aboutwhat modinfo is built on, and which engine is running it

<dist> may be an exact name, its shortest unique abbreviation (which is what Abbreviations computes for the whole set), or any unique prefix — modinfo show Core and modinfo show Corelib agree.

Where the distributions come from

Two sources, and both engines read them the same way:

modinfo list --path=DIR     # unpacked distributions under DIR (the default)
modinfo list --installed    # Rakudo's installation database (~/.raku/dist/*)

rank over a directory of unpacked distributions reproduces the measurement that produces an ecosystem's "most depended-on" list — the same reverse- dependency count that picked the modules in the table above.

The bundled corpus

fixtures/dists/ holds six small distributions, and they are what makes the two-engine claim checkable rather than hopeful:

FixtureWhy it exists
Corelib, Widget, Gadgeta clean three-level chain; Gadget also names an out-of-set dependency, so the graph has an external edge
LoopyKnota deliberate dependency cycle: the topological sort must fail on exactly these two, and the tree walk must cut the loop
Rustydeliberately broken metadata — version is *, no description or license, a provides entry pointing at a missing file, a duplicated and a self-referential dependency, a lib/ file nothing declares, a missing resource

modinfo check finds thirteen distinct problems in Rusty and none in the other five.

Both engines, byte for byte

RAKUPP=build/rakupp sh showcase/modinfo/compare.sh

runs all thirteen commands under raku and under rakupp and diffs STDOUT. about is left out on purpose — it reports the engine it is running under, so the two runs are supposed to differ there.

The same comparison holds on a real corpus: over the 61 unpacked distributions of the module battery, list, graph, rank, check and all three export formats are byte-identical under both engines.

If the distributions modinfo depends on are not installed, point MODLIB at a comma-separated list of lib directories that hold them — both engines accept the same RAKULIB spelling:

MODLIB=$(ls -d ~/dists/*/lib | paste -sd, -) RAKUPP=build/rakupp sh compare.sh

Determinism

A report that two engines can be diffed against has to be reproducible, and two things in Raku are not, unless you make them so:

There are no timestamps anywhere in the output, for the same reason.

Configuration

modinfo.yml (or ~/.modinfo.yml, or --config=FILE) sets the defaults:

scan:
  path: fixtures/dists
report:
  width: 92
  color: true
rank:
  top: 20

Command-line options win over the file, which wins over the built-in defaults.