Skip to content

Spike: experimental transclusion/fragment extension (parsed args via host resolver)#201

Draft
dereuromark wants to merge 2 commits into
masterfrom
spike/transclusion-fragments
Draft

Spike: experimental transclusion/fragment extension (parsed args via host resolver)#201
dereuromark wants to merge 2 commits into
masterfrom
spike/transclusion-fragments

Conversation

@dereuromark
Copy link
Copy Markdown
Contributor

What

A spike (opt-in, experimental) that maps template/fragment invocations with parsed arguments onto Djot, using only the existing extension/AST infrastructure.

This is the djot-php-side experiment for the upstream discussion on documenting Djot extensions and mapping wikitext-style templates onto Djot: jgm/djot#366

Why

Djot attributes are flat strings, so a markup-bearing template argument (a title containing emphasis, a link, or even another template) cannot live in an attribute. The discussion converges on a core question: there is good syntax for unparsed args (attributes), but no good mechanism for parsed args. This spike demonstrates one answer: a dedicated node whose arguments carry parsed child nodes, expanded by a host resolver before render, leaving the Djot core ignorant of template semantics.

What it adds

  • Djot\Node\Inline\Transclusion - node holding the invocation name.
  • Djot\Node\Inline\TransclusionArgument - one argument; named or positional, value held as parsed inline children.
  • Djot\Extension\TransclusionExtension - parses {{name|positional|key=value}} via the existing custom inline-pattern API, builds the node with parsed argument children, and on beforeRender calls a host-supplied resolver:
    • resolver returns a replacement node, spliced into the AST, or
    • returns null (or no resolver) and the call falls back to a visible transclusion-unresolved span containing the literal {{name}}.
  • Docs entry in docs/extensions/index.md (table row + section with a resolver contract), marked experimental.

So {{cite|title=_Important_ book}} yields an emphasis node in the title slot, not a flat string - that is the whole point.

Design notes

  • Reuses existing infrastructure only: custom inline pattern API, mutable AST, beforeRender hook. No parser-core changes, no renderer changes (the resolver expands the node before the renderer ever sees it).
  • The pipe-based surface syntax is deliberately throwaway. The contribution is the AST model: multiple named/positional parsed argument slots, with expansion delegated to the host. Whether the eventual surface form is this, or "named captions", or something else, the node shape is what matters.
  • Nested invocation as an argument falls out for free: an argument's parsed children can themselves contain a Transclusion node.

Spike limitations (intentional)

  • Pattern is {{...}} with [^{}] inner, so nested braces / literal braces inside arguments are not supported, and a literal | inside an argument is not supported (pipe always splits).
  • Resolver output is not re-scanned for nested transclusions.
  • Surface syntax is exploratory, not proposed as spec.

Status

Draft / spike for discussion. Not intended to merge as-is; opening it to make the AST-model argument concrete with running code.

Proof of concept for mapping template/fragment invocations with parsed
arguments onto Djot, exploring the parsed-vs-unparsed argument problem
discussed upstream in jgm/djot discussion 366.

Adds an opt-in TransclusionExtension that parses {{name|positional|key=value}}
into a Transclusion node whose arguments are TransclusionArgument nodes holding
PARSED inline children (so a markup-bearing argument becomes real AST, not a
flat attribute string). A host-supplied resolver expands the node into normal
AST before render; unresolved calls fall back to a visible placeholder span.

This reuses only existing infrastructure (custom inline pattern API, mutable
AST, beforeRender hook) and adds nothing to the parser core. The pipe-based
surface syntax is deliberately throwaway; the point is the AST model: multiple
named/positional parsed argument slots, expansion delegated to the host.

Marked experimental and not part of the official Djot spec.
Copilot AI review requested due to automatic review settings June 1, 2026 18:45
@dereuromark dereuromark added the enhancement New feature or request label Jun 1, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 93.65079% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.79%. Comparing base (eceebcd) to head (b5f7764).

Files with missing lines Patch % Lines
src/Node/Inline/Transclusion.php 60.00% 2 Missing ⚠️
src/Node/Inline/TransclusionArgument.php 77.77% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #201      +/-   ##
============================================
+ Coverage     91.78%   91.79%   +0.01%     
- Complexity     3441     3467      +26     
============================================
  Files           104      107       +3     
  Lines          9753     9816      +63     
============================================
+ Hits           8952     9011      +59     
- Misses          801      805       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an opt-in, experimental “transclusion” extension that parses {{name|args}} inline invocations into dedicated AST nodes, then optionally expands them via a host-provided resolver in a beforeRender hook, with documentation and tests to demonstrate parsed (markup-bearing) arguments.

Changes:

  • Added Transclusion and TransclusionArgument inline AST nodes to represent invocations and their parsed argument values.
  • Added TransclusionExtension to parse {{...}} calls into those nodes and replace them pre-render via a resolver (or an unresolved fallback span).
  • Added PHPUnit coverage and documentation for the experimental extension and resolver contract.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/TestCase/Extension/TransclusionExtensionTest.php Adds tests for positional/named args, parsed inline argument content, and unresolved fallbacks.
src/Node/Inline/TransclusionArgument.php Introduces an inline node type representing a positional or named transclusion argument with parsed children.
src/Node/Inline/Transclusion.php Introduces an inline node type representing a transclusion invocation name plus argument children.
src/Extension/TransclusionExtension.php Implements parsing for `{{name
docs/extensions/index.md Documents the experimental extension, syntax limits, resolver contract, and fallback behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Extension/TransclusionExtension.php
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants