website/generator/util.py

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