1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
#!/usr/bin/env python3
import json
import os
import shutil
from argparse import ArgumentParser
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
import jinja2
from loguru import logger
from typst import Compiler, TypstError
from watchfiles import watch
@dataclass
class CompiledPage:
url: str
title: str
date: datetime
class Builder:
def __init__(self, site_dir: Path, static_dir: Path, public_dir: Path) -> None:
self.site_dir = site_dir
self.content_dir = site_dir / 'content'
self.static_dir = static_dir
self.public_dir = public_dir
self.compiler = Compiler(root=site_dir)
def _compile_typst_page(self, path: Path) -> CompiledPage:
relative_path = path.relative_to(self.content_dir)
logger.info(f'Typst: {relative_path}')
output_path = self.public_dir / relative_path.with_suffix('.html')
output_path.parent.mkdir(parents=True, exist_ok=True)
self.compiler.compile(
input=path,
output=output_path,
sys_inputs={
'path': str(relative_path.with_suffix('')),
},
)
metadata = json.loads(
self.compiler.query(
selector='<post-meta>',
field='value',
one=True,
)
)
return CompiledPage(
url=str(relative_path.with_suffix('.html')),
title=metadata['title'],
date=datetime(
year=metadata['date_year'],
month=metadata['date_month'],
day=metadata['date_day'],
),
)
def _compile_typst_pages(self) -> list[CompiledPage]:
return [self._compile_typst_page(path) for path in self.content_dir.rglob('*.typ')]
def _compile_index_page(self, pages: list[CompiledPage]) -> None:
pages.sort(key=lambda page: page.date)
template = jinja2.Template((self.site_dir / 'index.html').read_text())
(self.public_dir / 'index.html').write_text(template.render(pages=pages))
def build(self) -> None:
logger.info(f'Removing {str(self.public_dir)!r}...')
shutil.rmtree(self.public_dir, ignore_errors=True)
self.public_dir.mkdir(parents=True, exist_ok=True)
logger.info('Linking static directory...')
(self.public_dir / 'static').symlink_to(self.static_dir)
pages = self._compile_typst_pages()
logger.info('Building index page...')
self._compile_index_page(pages)
def main() -> None:
parser = ArgumentParser()
parser.add_argument('-w', '--watch', action='store_true', help='watch for changes and rebuild')
args = parser.parse_args()
repo_dir = Path(__file__).parent
site_dir = repo_dir / 'site'
public_dir = repo_dir / 'public'
static_dir = repo_dir / 'static'
os.environ['TYPST_FEATURES'] = 'html'
builder = Builder(site_dir=site_dir, static_dir=static_dir, public_dir=public_dir)
builder.build()
if args.watch:
logger.info('Watching for changes...')
for changes in watch(site_dir):
print(changes)
try:
builder.build()
except TypstError as e:
logger.exception(e)
if __name__ == '__main__':
main()
|