Skip to main content

carve (Python binding)

Native Python bindings for the Carve markup language. This package is a thin PyO3 binding over the Rust implementation carve-rs, so the parser is not reimplemented in Python: every conversion delegates to the same engine the Rust CLI and WASM builds use. Output is byte-identical to carve-rs for the same input.

This unlocks the Python docs / data ecosystem (MkDocs, Sphinx, Pelican, Jupyter/nbconvert) for Carve.

Install

Wheels are abi3 (abi3-py38), so a single wheel covers CPython 3.8+.

From a built wheel:

pip install carve-lang

From source (needs a Rust toolchain, 1.75+):

pip install maturin
maturin develop --release      # build + install into the active venv
# or
maturin build --release        # produce a wheel under target/wheels/

Usage

import carve

print(carve.__version__)

# Core conversion (no extensions)
html = carve.to_html("# Hello *world*")
# -> '<section id="Hello-world">\n  <h1>Hello <strong>world</strong></h1>\n</section>'

# Inline emphasis: /italic/ and *bold*
carve.to_html("/italic/ and *bold*")

# Enable opt-in extensions by name
html = carve.to_html(source, extensions=["math_block", "list_table"])

# Dedicated explicit-list variant
html = carve.to_html_with_extensions(source, ["autolink"])

# Map `:name:` symbols to their values
carve.to_html("Ship it :rocket:", symbols={"rocket": "🚀"})
# -> '<p>Ship it 🚀</p>'

# Other renderers
carve.to_markdown(source)
carve.to_plain_text(source)
carve.to_ansi(source)

# Discover supported extension names
carve.extensions()

Passing an unknown extension name raises ValueError.

The names come from the engine itself, so carve.extensions() is the list this build actually accepts rather than a list documented here that could fall behind it. They are kebab-case (math-block, table-of-contents); the snake_case spellings this binding has always taken (math_block) reach the same extensions, so nothing written against the older names has to change.

The parsed AST

carve.parse() returns the document as Python data - the PART 12 exchange shape, the same tree every Carve engine publishes, so a consumer written against one implementation reads another's output.

ast = carve.parse("# Title\n\nBody[^a].\n\n[^a]: note\n")

ast["type"]                        # "document"
[c["type"] for c in ast["children"]]   # ["heading", "paragraph", "footnote"]
ast["children"][0]["pos"]          # {"startLine": 1, "startColumn": 1, ...}

carve.parse_json(source)           # the same tree, as a JSON string

The root carries exactly type, children and srcByteLength; frontmatter and footnote definitions are block nodes inside children, not root fields. Every node except the root carries pos when the engine could place it - 1-based lines and columns, 0-based offsets, ends exclusive, counted in Unicode codepoints, not bytes. A node the engine could not place, such as reassembled table-cell text, carries no pos at all rather than an invented one.

The serialization is the engine's own, so this binding publishes byte-identical output to the carve --json CLI and to every other binding over carve-rs.

Symbols

A :name: symbol renders its literal :name: source unless the name is in the symbols map passed as the symbols= keyword (supported by to_html and to_html_with_extensions):

carve.to_html("Ship it :rocket: :shrug:", symbols={"rocket": "🚀"})
# -> '<p>Ship it 🚀 :shrug:</p>'   (an unmapped name stays literal)

The leading word-boundary guard is unaffected by an active map: a:b:c, 10:30: and me@example.com never become symbols.

Security: symbol values are TRUSTED RAW output. A mapped value is inserted into the output unescaped - the same trust class as a renderers callable. symbols={"b": "<b>x</b>"} emits a real <b> element, not escaped text. This is deliberate (processor configuration is trusted). Never build a symbols map out of untrusted / user-supplied input.

Untrusted input

Carve's normative hardening is always on and needs no argument: dangerous URL schemes are blanked, event-handler attributes like onclick are dropped, and the bidi override/isolate characters behind Trojan Source are removed from rendered text.

Raw passthrough is the deliberate exception. A ```=html block or a `…`{=html} span renders verbatim by design, so it is the one thing input you did not author has to switch off:

html = carve.to_html(user_input, safe=True, profile="comment")

safe=True escapes those raw blocks and spans instead of emitting them. It is HTML-only, because HTML is the only target that can emit live markup: to_markdown escapes raw HTML, to_plain_text drops it, to_ansi keeps it as terminal text.

profile restricts which constructs are allowed at all and caps input length - "full", "article", "comment", "minimal", or None. It applies to every target, including to_markdown / to_plain_text / to_ansi.

An unknown profile name raises ValueError, and so does a rejection - input past the profile's max length, or a denied construct when the action is error:

carve.to_html("x" * 20_000, profile="minimal")
# ValueError: Profile violations: 'document' is not allowed: max_length_exceeded (...)

It raises rather than returning something that looks like output: the engine's infallible entry point answers a rejection with an empty string, which a caller cannot tell from a document that legitimately rendered to nothing.

Full recipe, defaults and threat model: Security.

Stored documents and spec versions

carve fmt --stamp (in any Carve engine) records the spec version a document was last processed under. This binding reads that marker back, so a repository of stored .crv files can be checked for documents predating a breaking spec change:

carve.read_stamp(source)
# {'version': '0.1', 'generated_by': 'carve-php 0.1.0'}

carve.needs_review(source)   # True when the document predates this engine

An unstamped document answers True: its provenance is unknown, and assuming it is current is the unsafe direction. Both marker forms are read, and a marker written by any engine reads the same - the format is the contract, not any one API.

What a version difference means is the versioning contract: only [behavior] changelog entries between the stamped version and yours can require a document change.

Supported extensions

The string passed in extensions=[...] maps to a carve-rs extension:

name effect
autolink turn bare URLs into links
details collapsible <details> blocks
external_links mark external links (rel/target)
fenced_render render fenced blocks of a target language (mermaid)
fenced_render_chart render fenced chart blocks (Chart.js)
fenced_render_plantuml render plantuml/puml blocks (Kroki client)
fenced_render_graphviz render dot/graphviz blocks
fenced_render_d2 render d2 blocks
fenced_render_wavedrom render wavedrom blocks
fenced_render_vega_lite render vega-lite blocks (Vega-Lite)
fenced_render_abc render abc music-notation blocks
heading_permalinks add permalink anchors to headings
list_table build tables from nested lists
math_block fenced math blocks
spoiler spoiler / hidden-content inline
tab_normalize normalize tab indentation
wikilinks [[wiki style]] links
citations citation references
code-callouts numbered callouts in fenced code blocks

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

carve_lang-0.1.1.tar.gz (24.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

carve_lang-0.1.1-cp38-abi3-win_amd64.whl (1.1 MB view details)

Uploaded CPython 3.8+Windows x86-64

carve_lang-0.1.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

carve_lang-0.1.1-cp38-abi3-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

File details

Details for the file carve_lang-0.1.1.tar.gz.

File metadata

  • Download URL: carve_lang-0.1.1.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for carve_lang-0.1.1.tar.gz
Algorithm Hash digest
SHA256 25ade7578ab1b6e2e63325e1da74f45366e4f9f22759daf5dc6dc82f753c13d6
MD5 0f253aaf1043e643d6210095cca08e9c
BLAKE2b-256 339e45e7e194c0f78ee3908540c03c76d316a67271ca930fae2ef485d5bac364

See more details on using hashes here.

File details

Details for the file carve_lang-0.1.1-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: carve_lang-0.1.1-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for carve_lang-0.1.1-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 c09cd3525bc5ad1b7b95584ad0c12d564c82d68105a8de130d88753ed94a5c62
MD5 b048b989b314584f7b4942b80f29474a
BLAKE2b-256 b9aa3951844cf1f2e447e712105791943bfe12da87b9ddd6389bc6a6e979b988

See more details on using hashes here.

File details

Details for the file carve_lang-0.1.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for carve_lang-0.1.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9c51f8bb263bd4b624ee1a59a6684764f6cc07d6ca72705ffc6e30983a0ac606
MD5 2c22f755bf9ca1a3e358cb134b5e5958
BLAKE2b-256 01b94c732a669e4e3c1081e32a4c000a27e3861183ee2d25b2461ef51478fd9b

See more details on using hashes here.

File details

Details for the file carve_lang-0.1.1-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for carve_lang-0.1.1-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d4407d8b4f90a072b8fd1206c92e591850e46a520a702c0f544af1c1b3eab404
MD5 e8991646b0b664eac445aa803bb92628
BLAKE2b-256 f2dc243339b0d651e815e656557a3e855da0cb2d65df7a94817211e9b8be1c9b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.1 This release

4 files

0.1.0

4 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page