pugneum-linker
v1.2.0
Published
Link together multiple pugneum abstract syntax trees
Readme
pugneum-linker
Link multiple pugneum ASTs together using include/extends
Installation
npm install pugneum-linkerNode.js 22 or newer is required.
Usage
var link = require('pugneum-linker');link(ast, options)
Assemble inclusion and inheritance, then resolve document-level references, footnotes, table-of-contents nodes, and structured-AST lints. The returned tree is a new owned copy; the input AST and dependency ASTs are not consumed and may be reused after success or failure.
The linker doesn't read the file system to resolve
and parse included and extended files.
Thus, the main AST must already have the ASTs
of the included and extended files embedded
in the FileReference nodes.
pugneum-loader is designed to do that.
If the tree contains filters, use the staged API so nodes generated by a filter participate in document-global resolution:
var filter = require('pugneum-filterer');
var assembled = link.assemble(loaded, options);
var filtered = filter(assembled, options);
var resolved = link.resolve(filtered, options);link(ast, options) is the convenient equivalent for already-filtered trees or
trees with no filters. link.assemble(ast, options) performs only inheritance
and inclusion. link.resolve(ast, options) performs only document-global
resolution and linting. Before resolving those document facts, link() and
link.resolve() lower mixin declarations and calls into independent call-site
AST instances. The resolved result therefore contains the nodes that will
actually render rather than reusable declarations or ignored caller blocks.
All three return owned copies without mutating their input tree.
options can contain the following properties:
sources(object): a map from filename to that file's Pugneum source string, used to attach source context (±3lines and a caret) to diagnostics. This is normally populated bypugneum-loader. For the entry file, scalarsourceandfilenameoptions are also used as a fallback.warnings(array): an extensible array that receives non-fatal diagnostics. If omitted, the linker establishesoptions.warnings; therefore a frozen options object must supply its own warnings array.maxLinkDepth(number): a safe integer from0through256, defaulting to256. It counts followed include and extends edges in one combined chain:0allows a plain root but no dependency edge, and a failure is reported at the edge that would exceed the limit. Caller properties that resemble private recursion state are ignored.compilationLimits(object) orcompilationContext(created bypugneum-error): a local budget or a shared build-wide budget. Linking charges AST validation, owned/inherited/yielded materialization, cloned binary bytes, and mixin invocations. Every yield receives a separately charged owned copy, so wide acyclic fan-out cannot bypassmaxLinkDepth.
Inheritance blocks
A replace-mode block name in document structure declares an inheritance slot.
block append name and block prepend name modify an existing declared slot;
an append/prepend occurrence alone does not create a slot that descendants can
override. Validation and composition use this same effective-slot model.
Named blocks inside mixin declarations or calls belong to that mixin and are
never template-inheritance targets. Put the inheritance block outside the mixin
call, then invoke or fill the mixin inside the overriding block when both
features are needed. An extending root may contain inheritance blocks, mixin
declarations, and document-global references declarations.
Diagnostics
The linker throws PUGNEUM:-coded errors (e.g. UNDEFINED_REFERENCE,
DUPLICATE_REFERENCE, UNDEFINED_FOOTNOTE, DUPLICATE_FOOTNOTE,
UNEXPECTED_BLOCK, LINK_DEPTH_EXCEEDED, MISSING_YIELD) for fatal problems,
and pushes the following non-fatal warnings into options.warnings:
DUPLICATE_ID— two elements share the sameid.IMG_WITHOUT_ALT— animghas noaltattribute.UNUSED_REFERENCE— areferencesentry is defined but never used.UNUSED_FOOTNOTE— afootnotesentry is defined but never referenced.EMPTY_TOC— atocproduced nothing (no headings with an explicitid).
HTML tag and attribute names used by these lints and by table-of-contents discovery have ASCII-case-insensitive identity. Their authored spelling in the AST is preserved.
These are structured-AST lints, not an HTML parser or validator. Markup hidden inside raw includes, raw HTML, verbatim text, or filter output represented as opaque text is not inspected for duplicate IDs or missing image alternatives.
The linker also resolves:
- Reference links/images —
@[name]and![name]nodes are resolved against areferencesblock. Definitions can include optional default display text:name url Default Text. - Footnotes —
^[name]nodes are resolved against afootnotesblock. Names may contain only ASCII letters, digits,-, and_. Reachable nested references are numbered in first-discovery order, then the linker generates a<section role="doc-endnotes">with DPUB-ARIA roles. Generated IDs are currently derived directly from names and can collide with adversarial names or author IDs;DUPLICATE_IDreports the collision but cannot repair it. - Table of contents —
tocnodes are replaced with a<nav role="doc-toc">containing nested<ol>lists. Any structured heading tag with an explicit non-empty stringidcontaining no ASCII whitespace can participate; the ID need not use shorthand syntax.
Scope of references and footnotes
Reference (@[name], ![name]), footnote (^[name]), and toc resolution
runs as a single pass over the fully assembled document — after extends
and include are spliced in and filters have run. Resolution is therefore
document-global: a @[name]/^[name] use matches any
references/footnotes block in the assembled tree, regardless of which
physical file the use or the definition came from (a use in an included partial
resolves against a block in the including file, and vice versa). A use with no
matching definition anywhere in the document raises UNDEFINED_REFERENCE /
UNDEFINED_FOOTNOTE; because the namespace is document-wide, duplicate names
collide across files (DUPLICATE_REFERENCE / DUPLICATE_FOOTNOTE), and only
one footnotes block is allowed per document (DUPLICATE_FOOTNOTES_BLOCK).
Running after the filterer is also what lets references, footnotes, and toc
emitted by a pugneum-type filter (e.g. a :table cell) resolve against the
document's blocks.
BlockComment descendants are outside that document-global scope because the
renderer serializes a buffered body into one HTML comment string (and discards
an unbuffered body). Headings, IDs, images, references, footnotes, and toc
inside either form therefore do not create live navigation, endnotes, usage, or
lint facts. A buffered reference keeps only its local label/alt text, a footnote
reference remains readable literal ^[name] text, and declarations/toc emit
nothing inside the comment.
An unused footnote definition is discarded content. Its reference links and reference declarations do not participate in resolution or mark global definitions as used. When a footnote is reached from the document—or transitively from another reached footnote—its definition body joins the same document-global reference namespace and is resolved normally.
Mixin calls are instantiated before this document-global pass. An unused declaration contributes no IDs, headings, references, footnotes, images, or lints; each rendered call contributes an independent instance; and only caller content consumed by a named or unnamed slot participates. Parameter values are resolved while lowering, including values used by heading IDs, visible TOC text, attributes, reachable footnote bodies, and deferred reference URLs.
License
MIT
