summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2021-08-20 11:58:42 +0200
committerDaniel Schadt <kingdread@gmx.de>2021-08-20 11:58:42 +0200
commita05368c8c8f9b97d727dc8d2efcf847743b29f66 (patch)
tree3f501f19f6315ce09325873df5ad3a907e56fa67
parent9b5b2bda1e43e659f142bb88a6b8138962e754e9 (diff)
downloadwikimini-a05368c8c8f9b97d727dc8d2efcf847743b29f66.tar.gz
wikimini-a05368c8c8f9b97d727dc8d2efcf847743b29f66.tar.bz2
wikimini-a05368c8c8f9b97d727dc8d2efcf847743b29f66.zip
implement Gemtext format
-rw-r--r--wikimini/document.py16
-rw-r--r--wikimini/formats/__init__.py47
-rw-r--r--wikimini/formats/gemtext.py58
3 files changed, 109 insertions, 12 deletions
diff --git a/wikimini/document.py b/wikimini/document.py
index 7c459b6..1e18a8b 100644
--- a/wikimini/document.py
+++ b/wikimini/document.py
@@ -325,22 +325,22 @@ class BlockQuote(Block):
"""A quote.
Attributes:
- nodes: The content of the blockquote.
+ content: The content of the blockquote.
"""
- __slots__ = ("nodes",)
- nodes: Paragraph
+ __slots__ = ("content",)
+ content: Paragraph
def __bool__(self):
- return bool(self.nodes)
+ return bool(self.content)
def append(self, node):
- self.nodes.append(node)
+ self.content.append(node)
def plain(self):
- return self.nodes.plain()
+ return self.content.plain()
- def to_nodes(self):
- return self.nodes.to_nodes()
+ def to_content(self):
+ return self.content.to_nodes()
@dataclass
diff --git a/wikimini/formats/__init__.py b/wikimini/formats/__init__.py
index 8d6296c..b48486a 100644
--- a/wikimini/formats/__init__.py
+++ b/wikimini/formats/__init__.py
@@ -4,7 +4,8 @@
Formats work by being given a file-like buffer as argument, into which the
output should be written.
"""
-from typing import TextIO
+import io
+from typing import TextIO, Union
from ..document import (
Document, Block, BlockLink, BlockQuote, Heading, ItemList, LineBreak,
@@ -18,9 +19,9 @@ class Format:
Any output format should inherit from this class and override the specific
output methods. Note that by default, no output is generated.
- The methods :meth:`render_document`, :meth:`render_block` and
- :meth:`render_node` have sensible default implementations that dispatch to
- the more specific rendering methods.
+ The methods :meth:`render`, :meth:`render_document`, :meth:`render_block`
+ and :meth:`render_node` have sensible default implementations that dispatch
+ to the more specific rendering methods.
Attributes:
writer: The file-like object that output should be written to.
@@ -30,6 +31,21 @@ class Format:
def __init__(self, writer: TextIO):
self.writer = writer
+ def render(self, obj: Union[Document, Block, Node]):
+ """Renders the given object.
+
+ Args:
+ obj: The object to render.
+ """
+ if isinstance(obj, Document):
+ self.render_document(obj)
+ elif isinstance(obj, Block):
+ self.render_block(obj)
+ elif isinstance(obj, Node):
+ self.render_node(obj)
+ else:
+ raise TypeError(f"Cannot render {obj}, unknown type")
+
def render_document(self, document: Document):
"""Renders the given document.
@@ -146,3 +162,26 @@ class Format:
Args:
style: The styled text to render.
"""
+
+
+def as_string(formatter: Format, obj: Union[Document, Node, Block]) -> str:
+ """Runs the given format function and returns the result as a string.
+
+ This temporarily replaces the output writer by an in-memory string object,
+ runs the render function and then restores the writer.
+
+ Args:
+ formatter: The formatter to run.
+ obj: The object to render.
+
+ Returns:
+ The content, as string.
+ """
+ old_writer = formatter.writer
+ buffer = io.StringIO()
+ formatter.writer = buffer
+ try:
+ formatter.render(obj)
+ finally:
+ formatter.writer = old_writer
+ return buffer.getvalue()
diff --git a/wikimini/formats/gemtext.py b/wikimini/formats/gemtext.py
new file mode 100644
index 0000000..935565c
--- /dev/null
+++ b/wikimini/formats/gemtext.py
@@ -0,0 +1,58 @@
+"""This module contains a Gemtext formatter for
+:class:`~wikimini.document.Document`.
+"""
+from itertools import zip_longest
+from . import Format, as_string
+from ..document import LineBreak, BlockLink, InlineLink
+
+
+class Gemtext(Format):
+ """The Gemtext formatter."""
+
+ def render_document(self, document):
+ for block, next_block in zip_longest(
+ document.blocks, document.blocks[1:]):
+ self.render_block(block)
+ if not isinstance(next_block, (LineBreak, BlockLink)):
+ self.writer.write("\n")
+
+ def render_block_link(self, block_link):
+ self.writer.write(f"=> {block_link.href} {block_link.title}\n")
+
+ def render_block_quote(self, block_quote):
+ content = as_string(self, block_quote.content)
+ for line in content.split("\n"):
+ self.writer.write(f"> {line}\n")
+
+ def render_heading(self, heading):
+ level = min(3, heading.level)
+ self.writer.write("#" * level + f" {heading.text}\n")
+
+ def render_inline_link(self, inline_link):
+ self.render(inline_link.title)
+
+ def render_item_list(self, item_list):
+ for item in item_list.items:
+ if len(item.nodes) == 1 and isinstance(item.nodes[0], InlineLink):
+ link = item.nodes[0]
+ self.render(BlockLink(link.href, link.title.plain()))
+ else:
+ self.writer.write("* ")
+ self.render(item)
+
+ def render_line_break(self, _):
+ self.writer.write("\n")
+
+ def render_paragraph(self, paragraph):
+ for node in paragraph.nodes:
+ self.render(node)
+ self.writer.write("\n")
+
+ def render_plain(self, plain):
+ self.writer.write(plain.text)
+
+ def render_style(self, style):
+ self.render(style.inner)
+
+ def render_verbatim(self, verbatim):
+ self.writer.write(f"```\n{verbatim.text}\n```\n")