113 lines
No EOL
3 KiB
Python
113 lines
No EOL
3 KiB
Python
import datetime
|
|
import os
|
|
from pathlib import Path
|
|
import threading
|
|
import yaml
|
|
import jinja2
|
|
import functools
|
|
import markdown
|
|
|
|
|
|
ERROR_LEADER = "[[red bold]FAIL[/bold red]] "
|
|
WARN_LEADER = "[[yellow bold]WARN[/bold yellow]] "
|
|
INFO_LEADER = "[[turquoise2 bold]info[/turquoise2 bold]] "
|
|
|
|
|
|
def load_yaml(data: str) -> any:
|
|
return yaml.load(data, Loader=yaml.FullLoader)
|
|
|
|
|
|
def is_directory_empty(path: str | Path) -> bool:
|
|
return len(os.listdir(path)) == 0
|
|
|
|
|
|
def extract_frontmatter(instr: str) -> tuple[any, str]:
|
|
first = instr.find("---")
|
|
second = instr.find("---", first + 1)
|
|
|
|
if first != 0 or second == -1:
|
|
raise ValueError("no valid frontmatter found")
|
|
|
|
end = second + 3
|
|
|
|
raw_frontmatter = instr[:end]
|
|
trailer = instr[end:]
|
|
|
|
if trailer.startswith("\n"):
|
|
trailer = trailer[1:]
|
|
|
|
frontmatter = load_yaml(raw_frontmatter.strip("-\n"))
|
|
return frontmatter, trailer
|
|
|
|
|
|
def get_absolute_url(site_config: any, path: str):
|
|
bu = site_config["baseURL"]
|
|
return bu.rstrip("/") + "/" + path.lstrip("/")
|
|
|
|
|
|
def date_to_datetime(d: datetime.date) -> datetime.datetime:
|
|
return datetime.datetime(d.year, d.month, d.day)
|
|
|
|
|
|
def build_jinja_env(site_config: any) -> jinja2.Environment:
|
|
jinja_env = jinja2.Environment(
|
|
loader=jinja2.FileSystemLoader("site/templates"), autoescape=jinja2.select_autoescape(),
|
|
)
|
|
|
|
jinja_env.filters["absurl"] = functools.partial(get_absolute_url, site_config)
|
|
jinja_env.filters["fmtdate"] = lambda x: x.strftime("%Y-%m-%d")
|
|
jinja_env.filters["render_md"] = markdown.new_simple_renderer()
|
|
|
|
return jinja_env
|
|
|
|
|
|
def debounce(wait_time):
|
|
"""
|
|
https://stackoverflow.com/a/66907107
|
|
|
|
Decorator that will debounce a function so that it is called after wait_time seconds
|
|
If it is called multiple times, will wait for the last call to be debounced and run only this one.
|
|
"""
|
|
|
|
def decorator(function):
|
|
def debounced(*args, **kwargs):
|
|
def call_function():
|
|
debounced._timer = None
|
|
return function(*args, **kwargs)
|
|
|
|
# if we already have a call to the function currently waiting to be executed, reset the timer
|
|
if debounced._timer is not None:
|
|
debounced._timer.cancel()
|
|
|
|
# after wait_time, call the function provided to the decorator with its arguments
|
|
debounced._timer = threading.Timer(wait_time, call_function)
|
|
debounced._timer.start()
|
|
|
|
debounced._timer = None
|
|
return debounced
|
|
|
|
return decorator
|
|
|
|
|
|
class DataLoader:
|
|
_base_path: Path
|
|
_store: dict[str, any]
|
|
|
|
def __init__(self, base_path: str | Path):
|
|
self._store = {}
|
|
self._base_path = Path(base_path)
|
|
|
|
def __getitem__(self, key):
|
|
if key in self._store:
|
|
return self._store[key]
|
|
|
|
p = (self._base_path/key).with_suffix(".yml")
|
|
|
|
with open(p) as f:
|
|
raw_data = f.read()
|
|
|
|
data = load_yaml(raw_data)
|
|
|
|
self._store[key] = data
|
|
|
|
return data |