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 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.0.tar.gz (20.4 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.0-cp38-abi3-win_amd64.whl (918.6 kB view details)

Uploaded CPython 3.8+Windows x86-64

carve_lang-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

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

carve_lang-0.1.0-cp38-abi3-macosx_11_0_arm64.whl (937.7 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: carve_lang-0.1.0.tar.gz
  • Upload date:
  • Size: 20.4 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.0.tar.gz
Algorithm Hash digest
SHA256 af98aa2e7feca48ea6e16cc3e2bd77bc6cd4c7443987b4de782132faa9c6ca7d
MD5 919c128b464238c492be1daf50f3607a
BLAKE2b-256 a255e150c99e1a46f7f392d53b9fab580fbdd2230b792cbfdec16a264b9a0b64

See more details on using hashes here.

File details

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

File metadata

  • Download URL: carve_lang-0.1.0-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 918.6 kB
  • 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.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 09038bbcfa38612e638aff0a39727633219a61b1c18a2d70763175776b0da797
MD5 66bf7b56be994b89b40c13a09658f676
BLAKE2b-256 c160fd7a8091485fe2a6c9d3ac354fa5b3cea09d3eddd715fb49df66cbc45583

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for carve_lang-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 f949bccfb75e72d5bc0e2316cb09564c4cc96d51a9cc5c7be2a47a75bf5e237e
MD5 291342e9098429e271a67a1adcc56d09
BLAKE2b-256 368496f23618e152f5c638675e410eef8bfbe5cbd40ac98996f82bbaa1c07ad2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for carve_lang-0.1.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 01bafa24243b165373820bee00666559842ccfc4057e8cf1ea25da4bbcb69e2e
MD5 693c91cb03b61588d174ce51ff9352cc
BLAKE2b-256 380994b3a16978fcc3f930e30aae0fc55482c83ee2af9d2ec1f400f3887d6fa7

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.1

4 files

This release

0.1.0 This release

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