fixed up URL rendering relative to tree root, added config file support & url_prefix

This commit is contained in:
Amy G. Bowersox 2024-03-13 19:50:53 -06:00
parent f34f446bf9
commit 5cadb535dc
3 changed files with 65 additions and 31 deletions

View File

@ -2,22 +2,45 @@
import argparse import argparse
import sys import sys
import yaml
from pathlib import Path from pathlib import Path
from tree import SourceNode, SourceIndex from tree import SourceNode, SourceIndex
from mparse import create_markdown_parser from mparse import create_markdown_parser
# The command line parser # The command line parser
parser = argparse.ArgumentParser(prog='dragonglass') parser = argparse.ArgumentParser(prog='dragonglass')
parser.add_argument('source_dir') parser.add_argument('source_dir', help='Source directory (Obsidian vault) for the conversion.')
parser.add_argument('dest_dir') 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(): def main():
args = parser.parse_args() args = parser.parse_args()
context = Context()
source_dir = Path(args.source_dir).resolve() context.source_dir = Path(args.source_dir).resolve()
if not (source_dir.exists() and source_dir.is_dir()): if not (context.source_dir.exists() and context.source_dir.is_dir()):
print(f"{source_dir} is not a valid directory") print(f"{context.source_dir} is not a valid directory")
return 1 return 1
dest_dir = Path(args.dest_dir).resolve() dest_dir = Path(args.dest_dir).resolve()
@ -25,15 +48,19 @@ def main():
print(f"{dest_dir} exists but is not a valid directory") print(f"{dest_dir} exists but is not a valid directory")
return 1 return 1
nodes = SourceNode.generate_list(source_dir) context.load_config(args)
for node in nodes:
node.load_metadata(source_dir)
src_index = SourceIndex(nodes) nodes = SourceNode.generate_list(context.source_dir)
mdparse = create_markdown_parser(src_index)
for node in nodes: 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 # TEMP
for node in nodes: for node in nodes:

View File

@ -8,6 +8,10 @@ from markdown.preprocessors import Preprocessor
class MetaStripper(Extension): 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): class MetaStripperProc(Preprocessor):
def run(self, lines): def run(self, lines):
@ -23,14 +27,15 @@ class MetaStripper(Extension):
class ObsidianLinks(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) super(ObsidianLinks, self).__init__(**kwargs)
self._src_index = src_index self._context = context
class ObsidianLinksProc(InlineProcessor): class ObsidianLinksProc(InlineProcessor):
def __init__(self, pattern, md, src_index): def __init__(self, pattern, md, context):
super(ObsidianLinks.ObsidianLinksProc, self).__init__(pattern, md) super(ObsidianLinks.ObsidianLinksProc, self).__init__(pattern, md)
self._src_index = src_index self._context = context
def parse_reference(self, contents): def parse_reference(self, contents):
text = None text = None
@ -39,11 +44,11 @@ class ObsidianLinks(Extension):
text = t[1] text = t[1]
contents = t[0] contents = t[0]
node, linktype = self._src_index.lookup(contents) node, linktype = self._context.src_index.lookup(contents)
if not text: if not text:
text = contents text = contents
if node: if node:
return node.link_target, text return node.link_target(self._context.url_prefix), text
return None, text return None, text
def handleMatch(self, m, data): def handleMatch(self, m, data):
@ -60,15 +65,16 @@ class ObsidianLinks(Extension):
def extendMarkdown(self, md): def extendMarkdown(self, md):
OBSLINK_PATTERN = r'\[\[(.*?)\]\]' 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) 'obsidian_links', 0)
class ObsidianInlines(Extension): class ObsidianInlines(Extension):
"""An extension that handles the special Obsidian markdown format sequences."""
def extendMarkdown(self, md): def extendMarkdown(self, md):
md.inlinePatterns.register(SimpleTagInlineProcessor(r'()~~(.*?)~~', 'del'), 'strikeout', 0) md.inlinePatterns.register(SimpleTagInlineProcessor(r'()~~(.*?)~~', 'del'), 'strikeout', 0)
md.inlinePatterns.register(SimpleTagInlineProcessor(r'()\=\=(.*?)\=\=', 'ins'), 'highlight', 0) md.inlinePatterns.register(SimpleTagInlineProcessor(r'()\=\=(.*?)\=\=', 'ins'), 'highlight', 0)
def create_markdown_parser(src_index): def create_markdown_parser(context):
return markdown.Markdown(extensions=[MetaStripper(), ObsidianLinks(src_index), ObsidianInlines()]) return markdown.Markdown(extensions=[MetaStripper(), ObsidianLinks(context), ObsidianInlines()])

View File

@ -5,14 +5,16 @@ import yaml
# The paths that are always to be ignored. # The paths that are always to be ignored.
STATIC_IGNORE = [ STATIC_IGNORE = [
'.obsidian' '.obsidian',
'.dragonglass'
] ]
MARKDOWN_PAT = '*.md' MARKDOWN_PAT = '*.md'
class SourceNode: class SourceNode:
def __init__(self, path, is_dir): def __init__(self, root, path, is_dir):
self._root = root
self._path = path self._path = path
self._is_dir = is_dir self._is_dir = is_dir
self._is_md = path.match(MARKDOWN_PAT) self._is_md = path.match(MARKDOWN_PAT)
@ -22,10 +24,9 @@ class SourceNode:
def __str__(self): def __str__(self):
return f"SourceNode({self._path}, {self._is_dir}) [is_md={self._is_md}]" return f"SourceNode({self._path}, {self._is_dir}) [is_md={self._is_md}]"
@property def link_target(self, prefix="/"):
def link_target(self):
xpath = self._path.with_suffix('.html') if self._is_md else self._path 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 @classmethod
def generate_list(cls, source_root): def generate_list(cls, source_root):
@ -41,13 +42,13 @@ class SourceNode:
add_me = False add_me = False
break break
if add_me: if add_me:
nodes.append(SourceNode(rchild, child.is_dir())) nodes.append(SourceNode(source_root, rchild, child.is_dir()))
if child.is_dir(): if child.is_dir():
dirs.append(child) dirs.append(child)
return nodes return nodes
def load_metadata(self, source_dir): 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: with open(source_dir / self._path, "r", encoding="utf-8") as f:
l = f.readline() l = f.readline()
if l == '---\n': if l == '---\n':
@ -59,7 +60,7 @@ class SourceNode:
self.metadata = yaml.full_load(''.join(metalines)) self.metadata = yaml.full_load(''.join(metalines))
def parse_markdown(self, source_dir, markdown_parser): def parse_markdown(self, source_dir, markdown_parser):
if self._is_md: if self._is_md and not self._is_dir:
markdown_parser.reset() markdown_parser.reset()
with open(source_dir / self._path, "r", encoding="utf-8") as f: with open(source_dir / self._path, "r", encoding="utf-8") as f:
self.text = markdown_parser.convert(f.read()) self.text = markdown_parser.convert(f.read())
@ -85,10 +86,10 @@ class SourceIndex:
if alias not in self._byalias: if alias not in self._byalias:
self._byalias[alias] = node self._byalias[alias] = node
else: else:
key = self._path.name key = node._path.name
if key not in self._byname: if key not in self._byname:
self._byname[key] = node self._byname[key] = node
self._byname[self._path.as_posix()] = node self._byname[node._path.as_posix()] = node
def lookup(self, reference): def lookup(self, reference):
if reference in self._byname: if reference in self._byname: