from pathlib import Path from typing import Set from docutils import nodes, writers from docutils.io import StringOutput from sphinx.builders import Builder from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator from sphinx.util.osutil import ensuredir, os_path logger = logging.getLogger(__name__) class GemtextWriter(writers.Writer): def __init__(self, builder): super().__init__() self.builder = builder def translate(self): visitor = self.builder.create_translator(self.document, self.builder) self.document.walkabout(visitor) self.output = '\n'.join(visitor.body) + '\n' class GemtextTranslator(SphinxTranslator): def __init__(self, document: nodes.document, builder): super().__init__(document, builder) self.heading_level = 0 self.current_line = '' self.body = [] self.literal = False def _finish_line(self): if self.current_line != '': self.body.append(self.current_line) self.current_line = '' def visit_document(self, node: nodes.document): self.heading_level = 1 self.current_line = '' self.body = [] self.literal = False def depart_document(self, node: nodes.document): self._finish_line() def visit_section(self, node: nodes.section): self.heading_level += 1 if len(self.body) > 0 and self.body[-1] != '': self.body.append('') def depart_section(self, node: nodes.section): self.heading_level -= 1 def visit_title(self, node: nodes.title): pass def depart_title(self, node: nodes.title): self.current_line = '#' * (self.heading_level - 1) + ' ' + self.current_line self._finish_line() self.body.append('') def visit_Text(self, node: nodes.Text): text = node.astext() if not self.literal: text = text.replace('\n', ' ') self.current_line += text def depart_Text(self, node: nodes.Text): pass def visit_paragraph(self, node: nodes.paragraph): pass def depart_paragraph(self, node: nodes.paragraph): self._finish_line() def visit_reference(self, node: nodes.reference): pass def depart_reference(self, node: nodes.reference): if self.current_line.startswith('=>'): self._finish_line() self.body.append('=> {} {}'.format(node.attributes['refuri'], node.astext())) else: self.current_line = '=> {} {}'.format(node.attributes['refuri'], self.current_line) def visit_image(self, node: nodes.image): if self.current_line == '': self.body.append('=> {} {}'.format(node.attributes['uri'], node.attributes['alt'])) else: raise NotImplementedError('inline images') def depart_image(self, node: nodes.image): pass def visit_comment(self, node: nodes.comment): raise nodes.SkipNode def visit_strong(self, node: nodes.strong): self.current_line += '**' depart_strong = visit_strong def visit_emphasis(self, node: nodes.emphasis): self.current_line += '_' depart_emphasis = visit_emphasis def visit_target(self, node: nodes.target): raise nodes.SkipNode def visit_bullet_list(self, node: nodes.bullet_list): pass def depart_bullet_list(self, node: nodes.bullet_list): pass def visit_list_item(self, node: nodes.list_item): self.current_line += '* ' def depart_list_item(self, node: nodes.list_item): pass def visit_compound(self, node: nodes.compound): pass def depart_compound(self, node: nodes.compound): pass def visit_inline(self, node: nodes.inline): pass def depart_inline(self, node: nodes.inline): pass def visit_title_reference(self, nodes: nodes.title_reference): pass def depart_title_reference(self, nodes: nodes.title_reference): pass def visit_literal(self, nodes: nodes.literal): self.current_line += '`' depart_literal = visit_literal def visit_literal_block(self, nodes: nodes.literal_block): self.body.append('') self.body.append('```') self.literal = True def depart_literal_block(self, nodes: nodes.literal_block): self._finish_line() self.body.append('```') self.body.append('') self.literal = False class GemtextBuilder(Builder): name = 'gmi' format = 'gmi' out_suffix = '.gmi' default_translator_class = GemtextTranslator def get_outdated_docs(self): return self.env.found_docs # can't be fucked to implement this right def get_target_uri(self, docname: str, typ: str = None): return docname def prepare_writing(self, docnames: Set[str]): self.writer = GemtextWriter(self) def write_doc(self, docname: str, doctree: nodes.document): destination = StringOutput(encoding='utf-8') self.writer.write(doctree, destination) out_name = Path(self.outdir, os_path(docname) + '.gmi') out_name.parent.mkdir(parents=True, exist_ok=True) with open(out_name, 'w', encoding='utf-8') as out_file: out_file.write(self.writer.output) def setup(app): app.add_builder(GemtextBuilder) return { 'version': '0.1', 'parallel_read_safe': True, 'parallel_write_safe': True, }