Skip to main content

mkdocs-carve

A MkDocs plugin that renders Carve (.crv) source files as documentation pages, converting them to HTML with the carve-py engine. Carve pages are wrapped by your active MkDocs theme exactly like Markdown pages, and they coexist with .md pages in the same docs/ tree.

Installation

pip install mkdocs-carve

The Carve engine (carve-lang, the PyO3 binding over carve-rs) is installed from PyPI as a normal dependency. It ships prebuilt abi3 wheels for Linux, macOS and Windows, so no Rust toolchain is needed.

Usage

Enable the plugin in mkdocs.yml:

site_name: My Site
plugins:
  - carve

Then write pages with a .crv extension under docs/ and reference them in nav by their source path:

nav:
  - Home: index.crv
  - About: about.crv
  - Guide:
      - Introduction: guide/intro.crv
  - Changelog: changelog.md   # plain Markdown still works

A Carve page, docs/index.crv:

# Carve in MkDocs

This homepage is written in *Carve* (note: `*bold*` is strong,
`/italic/` is emphasis in Carve).

- lists
- `inline code`
- [links](https://example.com)

| feature | works |
|---------|-------|
| tables  | yes   |

Configuration

Option Type Default Description
extensions list of names or {name: options} ["heading_permalinks"] Carve extensions enabled for every Carve page, each optionally with its options. Set to [] to use the core renderer only. See Extension options.
lint bool false Report lint findings on Carve pages as build warnings, so mkdocs build --strict fails on them. See Lint.
emoji none | unicode | twemoji none Resolve :smile: and friends through the emoji database your Markdown pages already use. See Symbols and emoji.
symbols mapping, or a path (none) Your own :name: symbols, written inline or kept in a JSON file whose path is given here. Values are emitted raw - see the warning below.
includes bool false Expand {{ path }} includes for file-backed pages. Off leaves a directive literal. See Includes.
include_root absolute path docs_dir The containment root includes resolve within. Must be absolute; a relative value is refused, not resolved.

Example enabling additional Carve extensions:

plugins:
  - carve:
      extensions:
        - heading_permalinks
        - math_block
        - list_table

Available extension names come from the installed Carve engine, so ask it rather than trusting a list in a README that cannot know which engine build you have:

python -c "import carve; print(carve.extensions())"

Extension options

An entry can also be a mapping from an extension name to its options, the same shape markdown_extensions uses:

plugins:
  - carve:
      extensions:
        - heading_permalinks:
            symbol: "#"
        - wikilinks:
            css_class: wiki
            new_window: true
        - math_block

The options go to the engine as written. An unknown extension or option is a configuration error when the build starts, not a failure on the first page that uses it. The error names the options the extension accepts.

Lint

Carve's linter reports constructs that parse and render, but not the way the author meant: a block attribute that attaches to nothing, a reference link with no definition, a duplicate footnote. Turn it on to see those as build warnings:

plugins:
  - carve:
      lint: true
WARNING -  mkdocs_carve: guide/intro.crv:3:1: unattached-block-attribute: This block attribute reaches no block: ...

Each warning names the page, line, column and rule id. mkdocs build --strict treats them like any other warning and fails the build. The linter reads the same extensions list the pages render with, and it checks the page source before includes are expanded.

lint and extension options need carve-lang 0.1.2 or newer. On an older engine either one is a configuration error.

Highlighting Carve code blocks

To highlight carve fences in your Markdown pages, install pygments-carve. Pygments picks the lexer up on its own, so no configuration is needed:

pip install pygments-carve

Symbols and emoji

Carve parses :name: as a symbol in its core - no extension needed - but what a name renders as is a render option. Without a map, :smile: renders as the text :smile:.

emoji turns on a name table. The names are not bundled with this plugin: they come from pymdownx.emoji, which is the same database your site's Markdown pages resolve :smile: through (Material for MkDocs enables it by default). One source means a :smile: in page.md and a :smile: in page.crv cannot drift apart. If your site configures its own emoji_index under markdown_extensions

  • Material for MkDocs does - that index is used instead, together with the options it is configured with, so a custom icon set resolves on both page types.
plugins:
  - carve:
      emoji: unicode    # the character itself
      # emoji: twemoji  # the <img class="twemoji"> a Markdown page gets

unicode needs no network and inherits the page font, which on some platforms has no color emoji face. twemoji emits the same element and the same class a Markdown page gets, so the theme's sizing applies and the two page types look identical. Install pymdown-extensions if your theme does not already pull it in; a mode other than none without it is a configuration error, not a silent fallback.

symbols adds your own entries, layered on top of the emoji table so you can override one or add something that is not an emoji at all:

plugins:
  - carve:
      symbols:
        crv: '<abbr title="Carve">CRV</abbr>'
        rarr: "&rarr;"

A long map buries the rest of mkdocs.yml, so it can live in a JSON file instead. The path is resolved relative to mkdocs.yml:

plugins:
  - carve:
      symbols: symbols.json
{
  "crv": "<abbr title=\"Carve\">CRV</abbr>",
  "rarr": "&rarr;"
}

The :name: match carries a word-boundary guard, so ordinary punctuation is left alone. With smile mapped:

A :smile: here     ->  substituted
(:smile:)          ->  substituted
a:smile:b          ->  left literal
3:smile:4          ->  left literal
`:smile:`          ->  left literal (a code span)
ratio 3:4, 12:30   ->  left literal

The map is trusted input, and it is emitted raw

A mapped value is inserted into the output without escaping - the same trust class as a build-time renderer callback. symbols: {logo: "<img src='/l.svg'>"} emits a real <img> element. That is deliberate: processor configuration is trusted, and it is what makes the twemoji mode possible at all.

It is safe only because of where the map can come from. This plugin resolves it once, in on_config, from exactly two places: mkdocs.yml itself, and a JSON file whose path is written in mkdocs.yml. No page has been read at that point, so no page can contribute a value.

Never build a symbols map out of untrusted or user-supplied input. Do not generate the JSON file from page content, from front matter, from a form submission, or from anything a contributor can influence without review. If a value can reach the map from outside your own repository, it can put arbitrary HTML on every page of your site.

Includes

A Carve page can pull another file in with {{ path }}. The plugin leaves that literal until a site asks for it:

plugins:
  - carve:
      includes: true

Paths resolve relative to the file that wrote them, and nothing resolves outside the containment root. The root is docs_dir unless include_root names another one, and an include that would leave it is not expanded.

include_root must be an absolute path. A relative one is refused rather than resolved, because every way of resolving it ends at the directory the build happened to run from, which is not a root anyone chose. Absolutize it yourself if you need to - in mkdocs.yml that means writing the path out, or setting the value from your own build script.

plugins:
  - carve:
      includes: true
      include_root: /srv/shared/fragments

A target that cannot be read is reported as a build warning naming the page, and the directive is left as written. The warning does not say whether the file was missing or refused by containment: both report include-unresolved, so a page cannot be used to probe the filesystem. Which one it was is in the build log at INFO.

Under mkdocs serve, a change anywhere under the containment root triggers a rebuild, including a fragment that did not exist yet.

Fragments living under docs_dir are still pages in their own right, since this plugin claims every .crv file. Keep them out of the built site with MkDocs' own exclude_docs, or put them under an include_root outside docs_dir.

Include expansion needs carve-lang 0.1.4 or newer, above the floor this plugin declares. includes: true on an engine without it is a configuration error rather than a silent no-op.

How it works

  • on_files promotes each .crv file to a documentation page and recomputes its destination path and URL using the same rules MkDocs applies to Markdown (driven by the file stem and use_directory_urls). This handles index pages, README files, nested folders, and both use_directory_urls: true and false modes without hand-rolled path logic.
  • The Carve file keeps its original .crv src_uri, so nav entries that reference .crv paths resolve without translation.
  • on_page_markdown converts the Carve source to an HTML fragment. MkDocs' Markdown step passes raw HTML through untouched, so the theme template wraps the converted output like any normal page.

Development

Contributor setup, testing, and maintenance notes are in the development guide.

Metadata

Release files for mkdocs-carve 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mkdocs-carve 0.1.3
File Size Uploaded Provenance
mkdocs_carve-0.1.3.tar.gz 34.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mkdocs-carve 0.1.3
File Interpreter ABI Platform
mkdocs_carve-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 52.9 kB

Release files / mkdocs_carve-0.1.3.tar.gz

Download URL mkdocs_carve-0.1.3.tar.gz
Size 34.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6d50ea66899417305b93455991d25feeb96e0f3886a8780fd456182cf652afc1
BLAKE2b-256 checksum
How to use checksums
e234dcd9455cd0994ed71981935e8ba4e071952203c8ab50fd4f2dbaab7c343a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / mkdocs_carve-0.1.3-py3-none-any.whl

Download URL mkdocs_carve-0.1.3-py3-none-any.whl
Size 18.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a6a5299c1169cd6c5c3eb38ac6a1f27c79b1186927a8392cb002c74d318729d3
BLAKE2b-256 checksum
How to use checksums
2c84e31c67847b000a24518f377b7d61160f08193962837549b55e227def99c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for mkdocs-carve 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page