tomato·bazeldocs v0 · latest
Docs/Reference/Modules/rules_markdown

rules_markdown

Bazel rules to compose markdown fragments into documents, with a deep-linking aspect that resolves cross-target anchors

Latest0.0.3
Versions3
CategoryBazel rules
MaintainersMatt Marshall
Registryhttps://registry.tbzl.dev/modules/rules_markdown/
Sourcegithub.com/tomato-bazel/rules_markdown
MODULE.bazelstarlark
bazel_dep(name = "rules_markdown", version = "0.0.3")

View source & releases on GitHub ↗

Bazel rules to compose markdown fragments into documents, with a deep-linking aspect that resolves cross-target anchors at build time.

Each section of a doc is a markdown_fragment target; a markdown_document aggregates them (ordered by weight) into one rendered file — with a generated table of contents, an optional prose template, and mdref:<handle> deep links that resolve to the right in-document anchor (and fail the build if they dangle). Docs assemble from the build graph the same way code does.

rules_readme is a thin façade on top for the dominant case (a templated README materialized into the source tree).

Install

bazel_dep(name = "rules_markdown", version = "0.0.1")

Usage

load("@rules_markdown//markdown:defs.bzl", "markdown_fragment", "markdown_document")

markdown_fragment(name = "intro", src = "intro.md", title = "Introduction", weight = 0)
markdown_fragment(name = "usage", src = "usage.md", title = "Usage", weight = 10)

markdown_document(
    name = "guide",
    out = "GUIDE.md",
    template = "GUIDE.md.tmpl",   # has <!-- TOC --> and <!-- FRAGMENTS -->
    fragments = [":intro", ":usage"],
    write_to = "GUIDE.md",        # bazel run //:guide.write ; bazel test //:guide.write_test
)

A fragment deep-links to another by writing mdref:<handle> in its body, where <handle> is the target fragment’s anchor (default: its target name):

See [the introduction](https://github.com/tomato-bazel/rules_markdown/blob/main/mdref:intro) for the idea.

md_gen rewrites that to the Introduction section’s final anchor; with link_check = True (the default) a mdref: to an unknown handle fails the build.

See examples/basic for a complete, building example.

Status

0.0.1 — composition, ordering, TOC, template splice, deep-link resolution + dangling-link gate, write_source_files materialization. The renderer is a parse-free Python tool (host python3); a v0.2 will move to a hermetic CommonMark renderer (comrak) + Bazel-checked deep-link edges. See the CHANGELOG.

Usage#

Real usage, taken from the module’s examples/.

examples/basic/BUILD.bazel

load("//markdown:defs.bzl", "markdown_document", "markdown_fragment")

# Two composable sections. `usage` deep-links into `intro` via mdref:intro.
markdown_fragment(
    name = "intro",
    src = "intro.md",
    title = "Introduction",
    weight = 0,
)

markdown_fragment(
    name = "usage",
    src = "usage.md",
    title = "Usage",
    weight = 10,
)

# Aggregate into GUIDE.md with a TOC + a prose template; resolve + check the
# deep link; materialize into the tree (GUIDE.md) with a drift gate.
markdown_document(
    name = "guide",
    out = "GUIDE.md",
    template = "README.md.tmpl",
    fragments = [
        ":intro",
        ":usage",
    ],
    link_check = True,
    toc = True,
    write_to = "GUIDE.md",
)

examples/slots/BUILD.bazel

load("//markdown:defs.bzl", "markdown_document", "markdown_fragment")

# Two fragments targeting two named template slots — generated content placed at
# specific spots (the gitlab-profile two-tables-at-two-locations pattern).
markdown_fragment(
    name = "a",
    content = "Content routed to the **alpha** slot.",
    slot = "alpha",
)

markdown_fragment(
    name = "b",
    content = "Content routed to the **beta** slot.",
    slot = "beta",
)

markdown_document(
    name = "doc",
    out = "SLOTS.md",
    template = "tmpl.md",
    fragments = [
        ":a",
        ":b",
    ],
    link_check = False,
    toc = False,
    write_to = "SLOTS.md",
)

Rules & providers#

Generated with Stardoc from the module's .bzl sources.

from docs/defs.md

Public API for rules_markdown.

load("@rules_markdown//markdown:defs.bzl", "markdown_fragment", "markdown_document")

markdown_fragment

load("@rules_markdown//markdown:defs.bzl", "markdown_fragment")

markdown_fragment(name, deps, src, anchor, classifiers, content, level, slot, title, weight)

Declare one composable markdown fragment (a section of a document).

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsChild fragments folded in transitively.List of labelsoptional[]
srcThe fragment body (.md). Mutually exclusive with content.LabeloptionalNone
anchorExplicit deep-link handle (defaults to the target name). Reference it elsewhere as mdref:<handle>.Stringoptional""
classifiersFree-form classifiers (e.g. category/section); exposed in fragment metadata.List of stringsoptional[]
contentInline fragment body. Mutually exclusive with src.Stringoptional""
levelHeading level for title (default 2 -> ##).Integeroptional2
slotTarget a named template placeholder <!-- FRAGMENTS:<slot> --> (for generated content placed at a specific spot). Default: the unnamed <!-- FRAGMENTS -->.Stringoptional""
titleSection heading text. If set, md_gen injects a level heading; it’s also the deep-link target.Stringoptional""
weightSort key within the document (ascending).Integeroptional0

MarkdownDocInfo

load("@rules_markdown//markdown:defs.bzl", "MarkdownDocInfo")

MarkdownDocInfo(md, anchor_index, fragments)

A rendered markdown document — exposed so a doc can be embedded, link-checked, or published without re-rendering.

FIELDS

NameDescription
mdFile: the rendered document.
anchor_indexFile: JSON map of deep-link handle -> final in-doc anchor slug.
fragmentsdepset of the fragment structs that composed it.

MarkdownFragmentInfo

load("@rules_markdown//markdown:defs.bzl", "MarkdownFragmentInfo")

MarkdownFragmentInfo(fragments)

A markdown fragment + its metadata, gathered transitively by markdown_link_aspect. Each contribution is a hashable struct so it can ride a depset.

FIELDS

NameDescription
fragmentsdepset of struct(frag_id, md_file, title, level, weight, handle, tags).

markdown_document

load("@rules_markdown//markdown:defs.bzl", "markdown_document")

markdown_document(name, out, template, fragments, roots, toc, link_check, write_to, **kwargs)

Aggregate fragments into one rendered markdown document.

PARAMETERS

NameDescriptionDefault Value
nametarget name.none
outoutput filename (defaults to <name>.md).None
templateoptional prose template with <!-- FRAGMENTS --> + <!-- TOC -->.None
fragmentsfragment targets to compose.[]
rootsarbitrary targets whose graph contributes fragments (via the aspect).[]
tocemit a table of contents.True
link_checkfail on a dangling mdref: deep link.True
write_toif set (a source-relative path), also create <name>.write (bazel run materializes the doc into the tree) + <name>.write_test (drift gate), via write_source_files.None
kwargsforwarded (visibility, tags, …).none

load("@rules_markdown//markdown:defs.bzl", "markdown_link_aspect")

markdown_link_aspect()

Walk deps/fragments/roots edges collecting markdown fragment contributions.

ASPECT ATTRIBUTES

NameType
depsString
fragmentsString
rootsString

ATTRIBUTES

Conformance#

No gate findings. 15 contested atoms. See how gating works or the full report.

Contested atoms

Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.

AtomResolved hereElsewhere
apple_support 1.24.2 2.2.0 ×1
aspect_bazel_lib 2.22.5 2.8.1 ×1
bazel_lib 3.0.0 3.2.2 ×17
bazel_skylib 1.8.2 1.9.0 ×2
gawk 5.3.2.bcr.1 5.3.2.bcr.3 ×17
jq.bzl 0.1.0 0.4.0 ×17
nlohmann_json 3.6.1 3.12.0.bcr.1 ×1
package_metadata 0.0.2 0.0.5 ×3
protobuf 33.4 34.0.bcr.1 ×2
rules_jvm_external 6.7 6.8 ×4
rules_python 1.7.0 2.0.1 ×1
rules_swift 3.1.2 3.6.1 ×1
tar.bzl 0.5.1 0.10.4 ×170.6.0 ×1
upb 0.0.0-20220923-a547704 0.0.0-20230516-61a97ef ×1
yq.bzl 0.1.1 0.3.4 ×17

Dependencies#

rules_markdown in the registry graph — what it depends on (left) and what depends on it (right).

Depends on

platforms1.0.0bazel_skylib1.8.2aspect_bazel_lib2.22.5rules_shell0.6.1devstardoc0.7.2dev

Used by (1 in the registry)

Versions#

3 published versions, newest first. Each resolves to an immutable, integrity-checked archive.

VersionIntegrity (sha256)Source archive
0.0.3 latest 6e4y8u24DX4TMk85… tag archive ↗
0.0.2 /ubOv4KhuqaVfNY4… tag archive ↗
0.0.1 PmH2LokNjMSzad8u… tag archive ↗

Changelog#

All notable changes to rules_markdown.

0.0.3

Additive — no migration needed.

Added

  • Named template slots. A markdown_fragment(slot = "x") routes to a <!-- FRAGMENTS:x --> placeholder, so generated content can be placed at specific spots in a template (e.g. two tables at two locations) instead of all concatenated at the single <!-- FRAGMENTS -->. The default (unnamed) slot is unchanged; a slot with fragments but no matching placeholder is a build error.

0.0.2

Additive — no migration needed.

Added

  • Stardoc reference docs (//docs, bazel run //docs:update) + a bzl_library for the public API surface.

0.0.1

Initial release.

Added

  • markdown_fragment — declare one composable markdown section (a body via src or inline content, an optional title heading injected at level, a deep-link handle via anchor, and deps on child fragments).
  • markdown_document — aggregate fragments (ordered by weight) into one rendered document: heading injection, generated TOC, optional prose template (<!-- FRAGMENTS --> / <!-- TOC -->), and mdref:<handle> cross-fragment deep-link resolution with a dangling-link gate (link_check). write_to adds a <name>.write materialization target + <name>.write_test drift gate via write_source_files.
  • markdown_link_aspect — collect fragments across the build graph from a markdown_document’s roots.
  • Providers MarkdownFragmentInfo / MarkdownDocInfo (the latter exposes the rendered doc + an anchor_index of handle -> final slug).

Notes

  • The v0.1 renderer (markdown/private/md_gen.py) is parse-free and runs via run_shell (host python3). A v0.2 will swap in a hermetic CommonMark renderer (comrak) for full GFM parity + body-heading verification, and add Bazel-checked deep-link edges + sub-heading (export_anchors) deep links.

← All modules