fixed up URL rendering relative to tree root, added config file support & url_prefix
This commit is contained in:
parent
f34f446bf9
commit
5cadb535dc
|
@ -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:
|
||||||
|
|
|
@ -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()])
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user