Metadata-Version: 2.4
Name: pyxmark
Version: 0.1.0
Summary: Clean, modern parser and HTML renderer for XMD, a markdown dialect for complex article layouts
Project-URL: Homepage, https://github.com/f1nnix/xmark
Project-URL: Source, https://github.com/f1nnix/xmark
Project-URL: Issues, https://github.com/f1nnix/xmark/issues
Author-email: Ilya Rusanen <ilya@rusanen.co.uk>
License-Expression: MIT
License-File: LICENSE
Keywords: markdown,mistune,parser,renderer,typography,xmd
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.13
Requires-Dist: mistune<3.3,>=3.2
Requires-Dist: pygments>=2.19.2
Description-Content-Type: text/markdown

## xmark

Modern parser and HTML renderer for **XMD** — a markdown dialect for complex article layouts.

Plain markdown runs out of room once an article needs pull quotes, sidebars, terminal transcripts, image galleries and typographic polish. XMD adds those as first-class syntax, and xmark renders them to HTML with stable, sequential element ids you can link to. Built on [mistune](https://github.com/lepture/mistune).

### Install

```
pip install pyxmark
```

### Usage

```python
from xmark import render_markdown

html = render_markdown(
    "First paragraph\n\nSecond line",
    target="web",          # or "pdf"
    article_id=42,
    uploads_path="https://cdn.example.com/uploads",
    default_lang="python",
    paywall_marker="",     # substituted for the ------ cut line
    wrapper=None,          # e.g. "xmd" -> wraps output in <article>
)
```

CLI reads stdin, writes HTML to stdout:

```
echo "Hello **world**" | xmark --target web --article-id 1
```

### XMD syntax

Standard markdown (headings, lists, blockquotes, fenced code, tables, inline formatting, links, images) via mistune with the `strikethrough` and `table` plugins. Bare URLs are *not* autolinked, and task lists are *not* special.

Extensions beyond standard markdown:

| Element | Syntax | Notes |
|---|---|---|
| Lead paragraph | First paragraph in document | Becomes `<div class=lead>`. Optional `$ ` line prefix stripped. Not promoted inside lists/quotes/panels; a leading list or quote disables promotion. |
| Catcher | `\| text` (consecutive lines) | Each line becomes `<div class=catcher><span>…</span></div>`. Inline markdown parsed. Lines forming a GFM table stay a table. |
| Cite | `-- author` | Single line, `<cite>`. Inline markdown parsed. |
| Panel (fenced) | `~~~type` … `~~~` | Types: `www`, `info`, `warning`, `danger`, `greeting` (h4 + type class), `term` (raw `<pre>`), empty/`panel` (default, no h4), anything else (default + h4 with original case). A typed fence closes the previous panel and opens the next; unclosed panels run to EOF. Tilde code fences are hijacked by this rule — use backticks for code. |
| Panel (bracket) | `[ type` … `]` (lone lines) | Same types as fenced. A bare `[` opens an untitled default panel. Without a closing `]` the line stays plain text. |
| Inline math | `$formula$` | `<span class="math">…</span>` in paragraphs, list items, catchers and cites — not in headings, and not inside `<code>`. Render client-side with KaTeX auto-render. |
| Underline | `_text_` | `<u>`. `__text__` is still `<strong>`. |
| Images/gallery | `![alt](url1,url2 "title")` | Separators: `,` `;` whitespace, `%20`. Multiple URLs build a `figtable` gallery table; single URL builds `<figure>`. Destinations may contain spaces and balanced parentheses. |
| Video thumbnail | `![alt](youtube_or_vimeo_url,thumb "title")` | `youtube.com`/`vimeo.com` URLs (not `youtu.be`). A missing thumb falls back to `img.youtube.com/vi/<id>/hqdefault.jpg` for YouTube. |
| Non-breaking span | `++text++` | Preprocessed to `<span class=nobr>`, skipped by typograf. |
| Paywall cut | `------` (6+ dashes) | The free/paid boundary. Replaced pre-parse with the `paywall_marker` string; dropped entirely when it is empty. |
| Inline tags | `%tag attrs` … `%tag` | Block-level: `<tag attrs>…</tag>` with inner markdown parsed. Opening line requires attrs after the tag. |

### Rendering details

**Element ids.** Every rendered element gets a sequential `id` (`id=p1`, `id=code2`, `id=h2-3`, …), assigned children-first, so a nested list gets lower ids than its parent. Ids are stable for a given input, which makes them usable as deep links.

**Code highlighting.** Pygments, emitted line-wise as `<code class='highlight <lang>'><div class=wrapper><div class="l l1">…` with per-line divs and short token classes, so blocks can wrap under `white-space: pre`.

**Typography.** The typograf stage processes text nodes only, skipping `h1..h4`, `pre`, `code`, `kbd`, `script`, `style` and `math` elements plus the `nobr` and `term` classes. It injects soft hyphens into Russian words, glues prepositions and numbers with `&nbsp;`, and converts ` -> ` arrows. The document is normalized on the way out: entities decoded, `&`/`<`/`>` re-escaped, attributes double-quoted, and void elements serialized as `<br>`, `<hr>`, `<img …>`.

**Uploads.** Relative `src=` paths are prefixed with `uploads_path`; no files are fetched.

Two behaviors worth knowing when you diff output:

- **A catcher block shares one id.** Every `| ` line of a block renders with the same `id=catcherN`; the next block gets N+1.
- **Gallery tables are preceded by an empty `<p>`** — `<p id=pN></p><table∆ class='figtable…'>` — because the table is hoisted out of its paragraph wrapper.

### Development

```
uv sync
uv run pytest
uv run ruff check
uv run ruff format
```
