GgenIgniter.Pack (ggen_igniter v26.9.8)

Copy Markdown View Source

Resolves the priv/ggen/<pack-name>/ convention (or an explicit --pack-dir) into sane defaults for --ontology/--query/--template, per the pack convention design:

priv/ggen/<pack-name>/
 pack.toml            # optional, not read here
 ontology.ttl          # default --ontology
 gates/*.rq            # default --query source, one query per file
 templates/*.{eex,tmpl} # default --template source (single-file case)

Pure helper, no Igniter dependency, so both ggen_igniter.sync and ggen_igniter.doctor (and tests) can call it directly.

Marketplace fetch (fetch_pack!/2)

Modeled on the real Rust ggen's ggen pack add <registry>:<id> (crates/ggen-marketplace/src/marketplace/install.rs): resolve a package spec, download a real archive over HTTP, verify it, extract it into a local cache directory so resolve_dir!/1-style discovery works against it.

Two real, simple registry sources are implemented -- their verification strength is genuinely different, and this moduledoc says so honestly:

  • "github:owner/repo" (optionally "@ref", default "main") -- fetches https://github.com/<owner>/<repo>/archive/refs/heads/<ref>.tar.gz. GitHub's archive endpoint publishes no checksum to verify against, so this path is print-only: the real SHA-256 of the downloaded archive is printed so the caller can pin/verify it manually (e.g. against a value they've recorded from a prior trusted fetch). This is not fail-closed verification -- there is nothing to fail closed against.
  • "hex:name@version" (or "hex:name" to resolve the latest stable version via the Hex API) -- fetches the real Hex package tarball from https://repo.hex.pm/tarballs/<name>-<version>.tar and compares its real SHA-256 against the checksum field hex.pm's own API (https://hex.pm/api/packages/<name>/releases/<version>) publishes for that release. This path is fail-closed: a mismatch raises before anything is extracted.

Neither source is fabricated -- both are real public HTTP endpoints exercised by the test suite (tagged :requires_network, see test/ggen_igniter_pack_fetch_test.exs).

Summary

Functions

Default --ontology path for pack_dir: <pack_dir>/ontology.ttl.

Discovers every <pack_dir>/gates/*.rq file, sorted lexically (so the NNN_ numeric-prefix convention controls ordering), mapped to {name, path} where name is the filename stem with any leading ^\d+_ digit-prefix stripped: 010_spec.rq -> "spec", entities.rq -> "entities" (no prefix, no change).

Discovers the --template under <pack_dir>/templates/.

Fetches a real marketplace pack over HTTP and extracts it into a local cache directory, returning the extracted pack directory (usable directly with resolve_dir!/1 via pack_dir:).

Resolves the pack directory from opts[:pack_dir] (explicit override) or opts[:pack] (looked up under priv/ggen/<name>/). Raises ArgumentError if neither is given.

Functions

default_ontology(pack_dir)

@spec default_ontology(String.t()) :: String.t()

Default --ontology path for pack_dir: <pack_dir>/ontology.ttl.

discover_queries(pack_dir)

@spec discover_queries(String.t()) :: [{String.t(), String.t()}]

Discovers every <pack_dir>/gates/*.rq file, sorted lexically (so the NNN_ numeric-prefix convention controls ordering), mapped to {name, path} where name is the filename stem with any leading ^\d+_ digit-prefix stripped: 010_spec.rq -> "spec", entities.rq -> "entities" (no prefix, no change).

discover_template(pack_dir, stem \\ nil)

@spec discover_template(String.t(), String.t() | nil) ::
  {:ok, String.t()}
  | {:error, :none}
  | {:error, {:ambiguous, [String.t()]}}
  | {:error, {:stem_not_found, String.t(), [String.t()]}}

Discovers the --template under <pack_dir>/templates/.

With stem omitted (or nil) -- the plain --pack NAME case -- returns {:ok, path} when exactly one *.eex or *.tmpl file exists, {:error, :none} when there are zero, or {:error, {:ambiguous, paths}} when there is more than one (no guessing which of N templates is "the" one).

With stem given -- the --pack NAME:TEMPLATE_STEM case (see Mix.Tasks.GgenIgniter.Sync's moduledoc) -- selects the one template file whose basename up to its first . equals stem ("resource.ex.eex" -> stem "resource", "domain.ex.eex" -> stem "domain"), bypassing the ambiguity error entirely even when the pack has multiple templates: {:ok, path} on a unique match, {:error, {:stem_not_found, stem, paths}} when no template's stem matches (paths lists every template actually found, for a helpful error), or {:error, {:ambiguous, paths}} in the degenerate case of two templates sharing the same stem with different extensions (e.g. both resource.eex and resource.tmpl present).

fetch_pack!(spec, opts \\ [])

@spec fetch_pack!(
  String.t(),
  keyword()
) :: String.t()

Fetches a real marketplace pack over HTTP and extracts it into a local cache directory, returning the extracted pack directory (usable directly with resolve_dir!/1 via pack_dir:).

spec is one of:

  • "github:owner/repo" or "github:owner/repo@ref" (ref defaults to "main") -- see the moduledoc for what verification this gives you (print-only SHA-256, no source-supplied checksum to compare against).
  • "hex:name" or "hex:name@version" (version defaults to the latest stable release per the Hex API) -- fail-closed SHA-256 verification against hex.pm's own published release checksum.

Options:

  • :cache_dir -- override the cache root (default ~/.cache/ggen_igniter/packs). Each pack is extracted to a spec-derived subdirectory under this root and is safe to re-fetch (previous contents at that path are replaced).

Raises ArgumentError for an unrecognized spec, and RuntimeError for any HTTP failure or (hex only) checksum mismatch.

resolve_dir!(opts)

@spec resolve_dir!(keyword() | map()) :: String.t()

Resolves the pack directory from opts[:pack_dir] (explicit override) or opts[:pack] (looked up under priv/ggen/<name>/). Raises ArgumentError if neither is given.