Source code for logseq_doctor

"""Logseq Doctor: heal your Markdown files."""

from __future__ import annotations

import os

import mistletoe
from mistletoe import block_token, span_token, token
from mistletoe.base_renderer import BaseRenderer

from logseq_doctor.constants import CHAR_DASH

__version__ = "0.3.0"


[docs] class LogseqRenderer(BaseRenderer): """Render Markdown as an outline with bullets, like Logseq expects.""" def __init__(self, *extras: token.Token) -> None: super().__init__(*extras) self.current_level = 0 self.bullet = "-"
[docs] def outline(self, indent: int, text: str, *, nl: bool = True) -> str: """Render a line of text with the correct indentation.""" leading_spaces = " " * indent new_line_at_the_end = os.linesep if nl else "" return f"{leading_spaces}{self.bullet} {text}{new_line_at_the_end}"
[docs] def render_heading(self, token: block_token.Heading | block_token.SetextHeading) -> str: """Setext headings: https://spec.commonmark.org/0.30/#setext-headings.""" if isinstance(token, block_token.SetextHeading): # For now, only dealing with level 2 setext headers (dashes) return self.render_inner(token) + f"{os.linesep}{CHAR_DASH * 3}{os.linesep}" self.current_level = token.level hashes = "#" * token.level inner = self.render_inner(token) return self.outline(token.level - 1, f"{hashes} {inner}")
[docs] def render_line_break(self, token: span_token.LineBreak) -> str: """Render a line break.""" return token.content + os.linesep
[docs] def render_paragraph(self, token: block_token.Paragraph) -> str: """Render a paragraph with the correct indentation.""" input_lines = self.render_inner(token).strip().splitlines() output_lines: list[str] = [self.outline(self.current_level, line, nl=False) for line in input_lines] return os.linesep.join(output_lines) + os.linesep
[docs] def render_list_item(self, token: block_token.ListItem) -> str: """Render a list item with the correct indentation.""" if len(token.children) <= 1: return self.render_inner(token) self.current_level += 1 inner = self.render_inner(token) headless_parent_with_children = inner.lstrip(f"{self.bullet} ") value_before_changing_level = self.outline(self.current_level - 1, headless_parent_with_children, nl=False) self.current_level -= 1 return value_before_changing_level
[docs] def render_thematic_break(self, token: block_token.ThematicBreak) -> str: # noqa: ARG002 """Render a horizontal rule as a line of dashes.""" return f"{CHAR_DASH * 3}{os.linesep}"
# TODO: refactor: the methods below are placeholders taken from BaseRenderer.render_map. # - Uncomment them to use them during debugging. # - Remove them when there will be enough test coverage for all the different elements below # def render_strong(self, token): # return self.render_inner(token) # # def render_emphasis(self, token): # return self.render_inner(token) # # def render_inline_code(self, token): # return self.render_inner(token) # # def render_raw_text(self, token): # return self.render_inner(token) # # def render_strikethrough(self, token): # return self.render_inner(token) # # def render_image(self, token): # return self.render_inner(token) # # def render_auto_link(self, token): # return self.render_inner(token) # # def render_escape_sequence(self, token): # return self.render_inner(token) # # def render_quote(self, token): # return self.render_inner(token) # # def render_block_code(self, token): # return self.render_inner(token) # # def render_list(self, token): # return self.render_inner(token) # # def render_table(self, token): # return self.render_inner(token) # # def render_table_row(self, token): # return self.render_inner(token) # # def render_table_cell(self, token): # return self.render_inner(token) # # def render_document(self, token): # return self.render_inner(token)
[docs] def flat_markdown_to_outline(markdown_contents: str) -> str: """Convert flat Markdown to an outline.""" return mistletoe.markdown(markdown_contents, LogseqRenderer)