nex renderer
This commit is contained in:
parent
e708dce8f7
commit
67a727ba24
2 changed files with 232 additions and 0 deletions
117
examples/markdown.nex
Normal file
117
examples/markdown.nex
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
|
||||
h1 Heading 8-)
|
||||
==============
|
||||
|
||||
|
||||
h2 Heading
|
||||
----------
|
||||
|
||||
|
||||
h3 Heading
|
||||
----------
|
||||
|
||||
|
||||
h4 Heading
|
||||
----------
|
||||
|
||||
|
||||
h5 Heading
|
||||
----------
|
||||
|
||||
|
||||
h6 Heading
|
||||
----------
|
||||
|
||||
|
||||
Horizontal Rules
|
||||
----------------
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
----------------------------------------------------------------------------
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
|
||||
Emphasis
|
||||
--------
|
||||
|
||||
This is bold text This is bold text This is italic text This is italic text
|
||||
Strikethrough
|
||||
|
||||
|
||||
Blockquotes
|
||||
-----------
|
||||
|
||||
| Blockquotes can also be nested...
|
||||
| | by using additional greater-than signs right next to each other...
|
||||
| | | or with spaces between arrows.
|
||||
|
||||
|
||||
Lists
|
||||
-----
|
||||
|
||||
|
||||
Unordered
|
||||
---------
|
||||
|
||||
+ Create a list by starting a line with +, -, or *
|
||||
- Marker character change forces new list start:
|
||||
* Ac tristique libero volutpat at
|
||||
+ Facilisis in pretium nisl aliquet
|
||||
- Nulla volutpat aliquam velit
|
||||
+ Very easy
|
||||
|
||||
|
||||
Ordered
|
||||
-------
|
||||
|
||||
1. Lorem ipsum dolor sit amet
|
||||
2. Consectetur adipiscing elit
|
||||
3. Integer molestie lorem at massa
|
||||
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
Inline code
|
||||
|
||||
|
||||
Indented code
|
||||
|
||||
|
||||
01 | // Some comments
|
||||
02 | line 1 of code
|
||||
03 | line 2 of code
|
||||
04 | line 3 of code
|
||||
|
||||
Block code "fences"
|
||||
|
||||
|
||||
01 | Sample text here...
|
||||
|
||||
Syntax highlighting
|
||||
|
||||
|
||||
01 | var foo = function (bar) {
|
||||
02 | return bar++;
|
||||
03 | };
|
||||
04 |
|
||||
05 | console.log(foo(5));
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
link text (http://dev.nodeca.com)
|
||||
|
||||
|
||||
link with title (http://nodeca.github.io/pica/demo/ "title text!")
|
||||
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
Minion (https://octodex.github.com/images/minion.png) Stormtroopocat
|
||||
(https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
|
||||
|
||||
|
||||
115
src/md2txt/renderers/nex.py
Normal file
115
src/md2txt/renderers/nex.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import Any, List
|
||||
|
||||
from ..models import BlockStyle, FrontMatter, HeadingPayload
|
||||
from ..plugins import register_renderer
|
||||
from .text import (
|
||||
BOLD_RE,
|
||||
CODE_STASH_RE,
|
||||
IMAGE_RE,
|
||||
ITALIC_RE,
|
||||
LINK_RE,
|
||||
STRIKETHROUGH_RE,
|
||||
TextRenderer,
|
||||
UNDERLINE_EM_RE,
|
||||
UNDERLINE_STRONG_RE,
|
||||
)
|
||||
|
||||
|
||||
class NexRenderer(TextRenderer):
|
||||
"""Render Markdown events as plain text for the Nex protocol.
|
||||
|
||||
Nex has no hypertext markup for hand-authored documents at all (its one
|
||||
special syntax, `=> URL`, is spec'd for server-auto-generated directory
|
||||
listings, not document bodies) -- so this renderer's whole job is to
|
||||
flatten Markdown down to plain, readable text. It reuses TextRenderer's
|
||||
wrapping/margin/hyphenation/ASCII-art/include machinery unchanged (same
|
||||
approach as AmaRenderer/MicronRenderer) and only overrides headings (no
|
||||
FIGlet -- a plain setext-style underline instead, which is a readability
|
||||
convention borrowed from Markdown itself, not a Nex spec requirement)
|
||||
and inline formatting (emphasis markers are stripped rather than
|
||||
translated, and links/images are inlined as "label (url)" plain prose
|
||||
rather than footnoted or turned into `=> ` lines).
|
||||
"""
|
||||
|
||||
def __init__(self, frontmatter: FrontMatter, *, width: int = 80, **_: Any) -> None:
|
||||
super().__init__(width=width, frontmatter=frontmatter)
|
||||
# Links are inlined by `_replace_link` below, not footnoted.
|
||||
self.links.clear()
|
||||
self.link_indices.clear()
|
||||
|
||||
def _render_heading(self, payload: HeadingPayload, style: BlockStyle) -> None:
|
||||
self._ensure_header_spacing()
|
||||
processed_text = self._process_inline(payload.text)
|
||||
underline_char = "=" if payload.level == 1 else "-"
|
||||
self._emit_render(
|
||||
partial(self._render_underlined_heading, processed_text, underline_char),
|
||||
style,
|
||||
stylable=True,
|
||||
)
|
||||
|
||||
def _render_underlined_heading(self, text: str, underline_char: str, style: BlockStyle) -> List[str]:
|
||||
wrapped = self._wrap_text(text, style=style)
|
||||
output: List[str] = []
|
||||
for line in wrapped:
|
||||
line = line.rstrip()
|
||||
if not line.strip():
|
||||
output.append("")
|
||||
continue
|
||||
leading = len(line) - len(line.lstrip(" "))
|
||||
underline = " " * leading + underline_char * len(line.lstrip(" "))
|
||||
output.append(line)
|
||||
output.append(underline)
|
||||
output.append("")
|
||||
return output
|
||||
|
||||
# Inline -------------------------------------------------------------
|
||||
|
||||
def _process_inline(self, text: str) -> str:
|
||||
code_segments: List[str] = []
|
||||
|
||||
def stash_code(match) -> str:
|
||||
code_segments.append(match.group(0)[1:-1])
|
||||
return f"\u0000CODE{len(code_segments) - 1}\u0000"
|
||||
|
||||
text = CODE_STASH_RE.sub(stash_code, text)
|
||||
text = STRIKETHROUGH_RE.sub(lambda m: m.group(1), text)
|
||||
text = BOLD_RE.sub(lambda m: m.group(1), text)
|
||||
text = UNDERLINE_STRONG_RE.sub(lambda m: m.group(1), text)
|
||||
text = ITALIC_RE.sub(lambda m: m.group(1), text)
|
||||
text = UNDERLINE_EM_RE.sub(lambda m: m.group(1), text)
|
||||
text = LINK_RE.sub(self._replace_link, text)
|
||||
text = IMAGE_RE.sub(self._replace_image, text)
|
||||
|
||||
for idx, code in enumerate(code_segments):
|
||||
text = text.replace(f"\u0000CODE{idx}\u0000", code)
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def _replace_link(match) -> str:
|
||||
label = match.group(1).strip()
|
||||
url = match.group(2).strip()
|
||||
if not label or label == url:
|
||||
return url
|
||||
return f"{label} ({url})"
|
||||
|
||||
@staticmethod
|
||||
def _replace_image(match) -> str:
|
||||
alt = match.group(1).strip()
|
||||
url = match.group(2).strip()
|
||||
if not alt:
|
||||
return url
|
||||
return f"{alt} ({url})"
|
||||
|
||||
|
||||
def _nex_renderer_factory(*, frontmatter: FrontMatter, **options: Any) -> NexRenderer:
|
||||
width = int(options.get("width", 80))
|
||||
return NexRenderer(frontmatter, width=width)
|
||||
|
||||
|
||||
try:
|
||||
register_renderer("nex", _nex_renderer_factory)
|
||||
except ValueError:
|
||||
pass
|
||||
Loading…
Add table
Add a link
Reference in a new issue