started using pyright and added type annotations to most of the code

(still some pyright errors)
This commit is contained in:
Amy G. Bowersox 2024-07-27 00:52:18 -06:00
parent ab2b775e3a
commit ba28ea0abf
5 changed files with 104 additions and 69 deletions

View File

@ -23,10 +23,33 @@ build-backend = "hatchling.build"
[tool.rye]
managed = true
dev-dependencies = []
dev-dependencies = [
"pyright~=1.1.373",
]
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["src/dragonglass"]
[tool.ruff]
line-length = 120
[tool.ruff.lint]
select = [
"A", # shadowing built-ins
"E", # style and whitespace
"F", # important pyflakes lints
"I", # import sorting
"N", # naming
"T100" # breakpoints
]
[tool.ruff.lint.isort]
known-first-party = ["dragonglass"]
[tool.pyright]
venvPath = "."
venv = ".venv"
strict = ["**/*.py"]

View File

@ -17,5 +17,8 @@ markdown==3.6
markupsafe==2.1.5
# via dragonglass
# via jinja2
nodeenv==1.9.1
# via pyright
pyright==1.1.373
pyyaml==6.0.1
# via dragonglass

View File

@ -3,8 +3,9 @@
import argparse
import yaml
from pathlib import Path
from tree import SourceNode, SourceIndex
from mparse import create_markdown_parser
from typing import Any
from .tree import generate_list, SourceNode, SourceIndex
from .mparse import create_markdown_parser
# The command line parser
parser = argparse.ArgumentParser(prog='dragonglass')
@ -14,26 +15,27 @@ parser.add_argument('-C', '--config', help='Specifies an alternate name for the
class Context:
def __init__(self):
self.source_dir = None
self.config = {}
self.src_index = None
self.current_node = None
def __init__(self) -> None:
self.source_dir: Path | None = None
self.config: dict[str, Any] = {}
self.src_index: SourceIndex | None = None
self.current_node: SourceNode | None = None
def load_config(self, args):
config_filename = args.config if args.config else ".dragonglass"
def load_config(self, args: argparse.Namespace) -> None:
config_filename: str = str(args.config) if args.config else ".dragonglass"
assert self.source_dir is not None
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):
def url_prefix(self) -> str:
rc = self.config.get("url_prefix", "/")
return rc if rc.endswith("/") else rc + '/'
def main():
def main() -> int:
args = parser.parse_args()
context = Context()
@ -49,7 +51,7 @@ def main():
context.load_config(args)
nodes = SourceNode.generate_list(context.source_dir)
nodes = generate_list(context.source_dir)
for node in nodes:
context.current_node = node
node.load_metadata(context.source_dir)

View File

@ -3,16 +3,18 @@
import re
import markdown
import xml.etree.ElementTree as etree
from typing import Any
from urllib.parse import urlparse
from urllib.parse import unquote as urlunquote
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor, SimpleTagInlineProcessor
from markdown.preprocessors import Preprocessor
from .dragonglass import Context
PRIO_BASE = 10000 # priority base for our extensions
def is_proper_url(s):
def is_proper_url(s: str) -> bool:
"""
Checks to see if a string is a "proper" URL.
@ -26,7 +28,7 @@ def is_proper_url(s):
return True if parseout.scheme else False
def sanitize_reference(s):
def sanitize_reference(s: str) -> str:
"""
Sanitizes an internal reference to a file by removing URL-quoted characters and any Markdown suffix.
@ -49,7 +51,7 @@ class MetaStripper(Extension):
"""
class MetaStripperProc(Preprocessor):
def run(self, lines):
def run(self, lines: list[str]) -> list[str]:
if lines[0] == '---':
lines.pop(0)
while lines[0] != '---':
@ -57,7 +59,7 @@ class MetaStripper(Extension):
lines.pop(0)
return lines
def extendMarkdown(self, md):
def extendMarkdown(self, md: markdown.Markdown) -> None:
md.preprocessors.register(MetaStripper.MetaStripperProc(md), 'metastripper', PRIO_BASE)
@ -65,15 +67,15 @@ class ObsidianImages(Extension):
"""An extension that supports image tags the way Obsidian handles them."""
DIMS = re.compile(r'(.*)\|(\d+)(?:x(\d+))?')
def __init__(self, context, **kwargs):
def __init__(self, context: Context, **kwargs: dict[str, Any]) -> None:
super(ObsidianImages, self).__init__(**kwargs)
self._context = context
@property
def invalid_reference_classname(self):
def invalid_reference_classname(self) -> str:
return 'invalid-reference'
def _parse_dimensions(self, s):
def _parse_dimensions(self, s: str) -> tuple[str, int, int]:
m = self.DIMS.match(s)
if m:
width = int(m.group(2))
@ -82,18 +84,19 @@ class ObsidianImages(Extension):
else:
return s, -1, -1
def _lookup_image_reference(self, name):
def _lookup_image_reference(self, name: str) -> str | None:
assert self._context.src_index is not None
node, _ = self._context.src_index.lookup(name)
if node:
return node.link_target(self._context.url_prefix)
return None
class ObsidianImageProc(InlineProcessor):
def __init__(self, pattern, md, extref):
def __init__(self, pattern: str, md: markdown.Markdown, extref: Any) -> None:
super(ObsidianImages.ObsidianImageProc, self).__init__(pattern, md)
self._extref = extref
def handleMatch(self, m, data):
def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element, int, int]:
name, width, height = self._extref._parse_dimensions(m.group(1))
link = self._extref._lookup_image_reference(name)
if link is None:
@ -111,11 +114,11 @@ class ObsidianImages(Extension):
return el, m.start(0), m.end(0)
class GenericImageProc(InlineProcessor):
def __init__(self, pattern, md, extref):
def __init__(self, pattern: str, md: markdown.Markdown, extref: Any) -> None:
super(ObsidianImages.GenericImageProc, self).__init__(pattern, md)
self._extref = extref
def handleMatch(self, m, data):
def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element, int, int]:
name, width, height = self._extref._parse_dimensions(m.group(1))
link = m.group(2)
if is_proper_url(link):
@ -144,7 +147,7 @@ class ObsidianImages(Extension):
el.text = link
return el, m.start(0), m.end(0)
def extendMarkdown(self, md):
def extendMarkdown(self, md: markdown.Markdown) -> None:
OBSIMAGE_PATTERN = r'!\[\[(.*?)\]\]'
GENERICIMAGE_PATTERN = r'!\[(.*?)\]\((.*?)\)'
md.inlinePatterns.register(ObsidianImages.ObsidianImageProc(OBSIMAGE_PATTERN, md, self),
@ -158,26 +161,27 @@ class ObsidianLinks(Extension):
An extension that processes Obsidian internal links in the [[page name]] format, as well as overrides the standard
Markdown link processing to handle Obsidian internal links as well as external links.
"""
def __init__(self, context, **kwargs):
def __init__(self, context: Context, **kwargs: dict[str, Any]) -> None:
super(ObsidianLinks, self).__init__(**kwargs)
self._context = context
@property
def obsidian_link_classname(self):
def obsidian_link_classname(self) -> str:
return 'obsidian-link'
@property
def invalid_reference_classname(self):
def invalid_reference_classname(self) -> str:
return 'invalid-reference'
def _parse_reference(self, contents):
def _parse_reference(self, contents: str) -> tuple[str | None, str]:
text = None
t = contents.split('|')
if len(t) > 1:
text = t[1]
contents = t[0]
node, linktype = self._context.src_index.lookup(contents)
assert self._context.src_index is not None
node, _ = self._context.src_index.lookup(contents)
if not text:
text = contents
if node:
@ -185,11 +189,11 @@ class ObsidianLinks(Extension):
return None, text
class ObsidianLinksProc(InlineProcessor):
def __init__(self, pattern, md, extref):
def __init__(self, pattern: str, md: markdown.Markdown, extref: Any) -> None:
super(ObsidianLinks.ObsidianLinksProc, self).__init__(pattern, md)
self._extref = extref
def handleMatch(self, m, data):
def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element, int, int]:
link, text = self._extref._parse_reference(m.group(1))
if link is None:
el = etree.Element('span')
@ -203,11 +207,11 @@ class ObsidianLinks(Extension):
return el, m.start(0), m.end(0)
class GenericLinksProc(InlineProcessor):
def __init__(self, pattern, md, extref):
def __init__(self, pattern: str, md: markdown.Markdown, extref: Any) -> None:
super(ObsidianLinks.GenericLinksProc, self).__init__(pattern, md)
self._extref = extref
def handleMatch(self, m, data):
def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element, int, int]:
text = m.group(1)
link = m.group(2)
if is_proper_url(link):
@ -227,7 +231,7 @@ class ObsidianLinks(Extension):
el.text = text
return el, m.start(0), m.end(0)
def extendMarkdown(self, md):
def extendMarkdown(self, md: markdown.Markdown) -> None:
OBSLINK_PATTERN = r'\[\[(.*?)\]\]'
GENERICLINK_PATTERN = r'\[(.*?)\]\((.*?)\)'
md.inlinePatterns.register(ObsidianLinks.ObsidianLinksProc(OBSLINK_PATTERN, md, self),
@ -238,10 +242,10 @@ class ObsidianLinks(Extension):
class ObsidianInlines(Extension):
"""An extension that handles the special Obsidian markdown format sequences."""
def extendMarkdown(self, md):
def extendMarkdown(self, md: markdown.Markdown) -> None:
md.inlinePatterns.register(SimpleTagInlineProcessor(r'()~~(.*?)~~', 'del'), 'strikeout', PRIO_BASE)
md.inlinePatterns.register(SimpleTagInlineProcessor(r'()\=\=(.*?)\=\=', 'ins'), 'highlight', PRIO_BASE + 1)
def create_markdown_parser(context):
def create_markdown_parser(context: Context) -> markdown.Markdown:
return markdown.Markdown(extensions=[MetaStripper(), ObsidianImages(context), ObsidianLinks(context), ObsidianInlines()])

View File

@ -1,6 +1,9 @@
#!/usr/bin/env python
from pathlib import Path
from typing import Any
from urllib.parse import quote as urlquote
import markdown
import yaml
# The paths that are always to be ignored.
@ -13,63 +16,63 @@ MARKDOWN_PAT = '*.md'
class SourceNode:
def __init__(self, root, path, is_dir):
def __init__(self, root: Path, path: Path, is_dir: bool) -> None:
self._root = root
self._path = path
self._is_dir = is_dir
self._is_md = path.match(MARKDOWN_PAT)
self.metadata = None
self.text = None
self.metadata: dict[str, Any] | None = None
self.text: str | None = None
def __str__(self):
def __str__(self) -> str:
return f"SourceNode({self._path}, {self._is_dir}) [is_md={self._is_md}]"
def link_target(self, prefix="/"):
def link_target(self, prefix: str = "/") -> str:
xpath = self._path.with_suffix('.html') if self._is_md else self._path
return urlquote(prefix + xpath.as_posix())
@classmethod
def generate_list(cls, source_root):
nodes = []
dirs = [source_root]
while len(dirs) > 0:
current_dir = dirs.pop(0)
for child in current_dir.iterdir():
rchild = child.relative_to(source_root)
add_me = True
for pat in STATIC_IGNORE:
if rchild.match(pat):
add_me = False
break
if add_me:
nodes.append(SourceNode(source_root, rchild, child.is_dir()))
if child.is_dir():
dirs.append(child)
return nodes
def load_metadata(self, source_dir):
def load_metadata(self, source_dir: Path) -> None:
if self._is_md and not self._is_dir:
with open(source_dir / self._path, "r", encoding="utf-8") as f:
cur_line = f.readline()
if cur_line == '---\n':
metalines = []
metalines: list[str] = []
cur_line = f.readline()
while cur_line != '---\n':
metalines.append(cur_line)
cur_line = f.readline()
self.metadata = yaml.full_load(''.join(metalines))
def parse_markdown(self, source_dir, markdown_parser):
def parse_markdown(self, source_dir: Path, markdown_parser: markdown.Markdown) -> None:
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())
def generate_list(source_root: Path) -> list[SourceNode]:
nodes: list[SourceNode] = []
dirs = [source_root]
while len(dirs) > 0:
current_dir = dirs.pop(0)
for child in current_dir.iterdir():
rchild = child.relative_to(source_root)
add_me = True
for pat in STATIC_IGNORE:
if rchild.match(pat):
add_me = False
break
if add_me:
nodes.append(SourceNode(source_root, rchild, child.is_dir()))
if child.is_dir():
dirs.append(child)
return nodes
class SourceIndex:
def __init__(self, nodelist):
self._byname = {}
self._byalias = {}
def __init__(self, nodelist: list[SourceNode]) -> None:
self._byname: dict[str, SourceNode] = {}
self._byalias: dict[str, SourceNode] = {}
for node in nodelist:
if node._is_dir:
continue
@ -91,7 +94,7 @@ class SourceIndex:
self._byname[key] = node
self._byname[node._path.as_posix()] = node
def lookup(self, reference):
def lookup(self, reference: str) -> tuple[SourceNode | None, str | None]:
if reference in self._byname:
return self._byname[reference], 'NAME'
elif reference in self._byalias: