From 5cadb535dcec25a739bcf75f10ecfbd6dc69fc69 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Wed, 13 Mar 2024 19:50:53 -0600 Subject: [PATCH] fixed up URL rendering relative to tree root, added config file support & url_prefix --- src/dragonglass/dragonglass.py | 51 ++++++++++++++++++++++++++-------- src/dragonglass/mparse.py | 24 ++++++++++------ src/dragonglass/tree.py | 21 +++++++------- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/dragonglass/dragonglass.py b/src/dragonglass/dragonglass.py index 4b113bc..b6735da 100644 --- a/src/dragonglass/dragonglass.py +++ b/src/dragonglass/dragonglass.py @@ -2,22 +2,45 @@ import argparse import sys +import yaml from pathlib import Path from tree import SourceNode, SourceIndex from mparse import create_markdown_parser # The command line parser parser = argparse.ArgumentParser(prog='dragonglass') -parser.add_argument('source_dir') -parser.add_argument('dest_dir') +parser.add_argument('source_dir', help='Source directory (Obsidian vault) for the conversion.') +parser.add_argument('dest_dir', help='Destination directory for the conversion.') +parser.add_argument('-C', '--config', help='Specifies an alternate name for the configuration file.') + + +class Context: + def __init__(self): + self.source_dir = None + self.config = {} + self.src_index = None + self.current_node = None + + def load_config(self, args): + config_filename = args.config if args.config else ".dragonglass" + config_path = self.source_dir / config_filename + if config_path.exists() and config_path.is_file(): + with open(config_path, "r") as f: + self.config = yaml.full_load(f) + + @property + def url_prefix(self): + rc = self.config.get("url_prefix", "/") + return rc if rc.endswith("/") else rc + '/' def main(): args = parser.parse_args() + context = Context() - source_dir = Path(args.source_dir).resolve() - if not (source_dir.exists() and source_dir.is_dir()): - print(f"{source_dir} is not a valid directory") + context.source_dir = Path(args.source_dir).resolve() + if not (context.source_dir.exists() and context.source_dir.is_dir()): + print(f"{context.source_dir} is not a valid directory") return 1 dest_dir = Path(args.dest_dir).resolve() @@ -25,15 +48,19 @@ def main(): print(f"{dest_dir} exists but is not a valid directory") return 1 - nodes = SourceNode.generate_list(source_dir) - for node in nodes: - node.load_metadata(source_dir) + context.load_config(args) - src_index = SourceIndex(nodes) - - mdparse = create_markdown_parser(src_index) + nodes = SourceNode.generate_list(context.source_dir) for node in nodes: - node.parse_markdown(source_dir, mdparse) + context.current_node = node + node.load_metadata(context.source_dir) + + context.src_index = SourceIndex(nodes) + + mdparse = create_markdown_parser(context) + for node in nodes: + context.current_node = node + node.parse_markdown(context.source_dir, mdparse) # TEMP for node in nodes: diff --git a/src/dragonglass/mparse.py b/src/dragonglass/mparse.py index 8660be9..54993a5 100644 --- a/src/dragonglass/mparse.py +++ b/src/dragonglass/mparse.py @@ -8,6 +8,10 @@ from markdown.preprocessors import Preprocessor class MetaStripper(Extension): + """ + An extension that strips the metadata off the front of Obsidian pages, as it's already been parsed in an + earlier step. + """ class MetaStripperProc(Preprocessor): def run(self, lines): @@ -23,14 +27,15 @@ class MetaStripper(Extension): class ObsidianLinks(Extension): - def __init__(self, src_index, **kwargs): + """An extension that processes Obsidian internal links in the [[page name]] format.""" + def __init__(self, context, **kwargs): super(ObsidianLinks, self).__init__(**kwargs) - self._src_index = src_index + self._context = context class ObsidianLinksProc(InlineProcessor): - def __init__(self, pattern, md, src_index): + def __init__(self, pattern, md, context): super(ObsidianLinks.ObsidianLinksProc, self).__init__(pattern, md) - self._src_index = src_index + self._context = context def parse_reference(self, contents): text = None @@ -39,11 +44,11 @@ class ObsidianLinks(Extension): text = t[1] contents = t[0] - node, linktype = self._src_index.lookup(contents) + node, linktype = self._context.src_index.lookup(contents) if not text: text = contents if node: - return node.link_target, text + return node.link_target(self._context.url_prefix), text return None, text def handleMatch(self, m, data): @@ -60,15 +65,16 @@ class ObsidianLinks(Extension): def extendMarkdown(self, md): OBSLINK_PATTERN = r'\[\[(.*?)\]\]' - md.inlinePatterns.register(ObsidianLinks.ObsidianLinksProc(OBSLINK_PATTERN, md, self._src_index), + md.inlinePatterns.register(ObsidianLinks.ObsidianLinksProc(OBSLINK_PATTERN, md, self._context), 'obsidian_links', 0) class ObsidianInlines(Extension): + """An extension that handles the special Obsidian markdown format sequences.""" def extendMarkdown(self, md): md.inlinePatterns.register(SimpleTagInlineProcessor(r'()~~(.*?)~~', 'del'), 'strikeout', 0) md.inlinePatterns.register(SimpleTagInlineProcessor(r'()\=\=(.*?)\=\=', 'ins'), 'highlight', 0) -def create_markdown_parser(src_index): - return markdown.Markdown(extensions=[MetaStripper(), ObsidianLinks(src_index), ObsidianInlines()]) +def create_markdown_parser(context): + return markdown.Markdown(extensions=[MetaStripper(), ObsidianLinks(context), ObsidianInlines()]) diff --git a/src/dragonglass/tree.py b/src/dragonglass/tree.py index a97dcf3..aae5a2c 100644 --- a/src/dragonglass/tree.py +++ b/src/dragonglass/tree.py @@ -5,14 +5,16 @@ import yaml # The paths that are always to be ignored. STATIC_IGNORE = [ - '.obsidian' + '.obsidian', + '.dragonglass' ] MARKDOWN_PAT = '*.md' class SourceNode: - def __init__(self, path, is_dir): + def __init__(self, root, path, is_dir): + self._root = root self._path = path self._is_dir = is_dir self._is_md = path.match(MARKDOWN_PAT) @@ -22,10 +24,9 @@ class SourceNode: def __str__(self): return f"SourceNode({self._path}, {self._is_dir}) [is_md={self._is_md}]" - @property - def link_target(self): + def link_target(self, prefix="/"): xpath = self._path.with_suffix('.html') if self._is_md else self._path - return urlquote(xpath.as_posix()) + return urlquote(prefix + xpath.as_posix()) @classmethod def generate_list(cls, source_root): @@ -41,13 +42,13 @@ class SourceNode: add_me = False break if add_me: - nodes.append(SourceNode(rchild, child.is_dir())) + nodes.append(SourceNode(source_root, rchild, child.is_dir())) if child.is_dir(): dirs.append(child) return nodes def load_metadata(self, source_dir): - if self._is_md: + if self._is_md and not self._is_dir: with open(source_dir / self._path, "r", encoding="utf-8") as f: l = f.readline() if l == '---\n': @@ -59,7 +60,7 @@ class SourceNode: self.metadata = yaml.full_load(''.join(metalines)) def parse_markdown(self, source_dir, markdown_parser): - if self._is_md: + if self._is_md and not self._is_dir: markdown_parser.reset() with open(source_dir / self._path, "r", encoding="utf-8") as f: self.text = markdown_parser.convert(f.read()) @@ -85,10 +86,10 @@ class SourceIndex: if alias not in self._byalias: self._byalias[alias] = node else: - key = self._path.name + key = node._path.name if key not in self._byname: self._byname[key] = node - self._byname[self._path.as_posix()] = node + self._byname[node._path.as_posix()] = node def lookup(self, reference): if reference in self._byname: