main.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. #!/usr/bin/env python3
  2. from dataclasses import dataclass
  3. import datetime
  4. import os
  5. import markdown
  6. import pystache
  7. import shutil
  8. import yaml
  9. class Datum:
  10. @classmethod
  11. def from_yaml(cls, data):
  12. return cls(**data)
  13. @classmethod
  14. def from_file(cls, path):
  15. with open(path) as f:
  16. data = yaml.safe_load(f)
  17. return cls.from_yaml(data)
  18. @dataclass
  19. class Quote(Datum):
  20. id: str
  21. content: str
  22. author: str
  23. @dataclass
  24. class Quip(Datum):
  25. id: str
  26. content: str
  27. @dataclass
  28. class Work:
  29. slug: str
  30. category: str
  31. title: str
  32. date: str
  33. contents: str
  34. class Path:
  35. DATADIR=os.getenv('DATADIR', '/home/gdritter/projects/lib-data')
  36. OUTDIR=os.getenv('OUTDIR', '/tmp/lib')
  37. @classmethod
  38. def data(cls, *paths):
  39. return os.path.join(cls.DATADIR, *paths)
  40. @classmethod
  41. def out(cls, *paths):
  42. return os.path.join(cls.OUTDIR, *paths)
  43. @classmethod
  44. def write(cls, *paths):
  45. if len(paths) > 1:
  46. os.makedirs(cls.out(*paths[:-1]), exist_ok=True)
  47. return open(cls.out(*paths), 'w')
  48. @classmethod
  49. def read(cls, *paths):
  50. with open(cls.data(*paths)) as f:
  51. return f.read()
  52. @classmethod
  53. def list(cls, *paths):
  54. return os.listdir(cls.data(*paths))
  55. class Template:
  56. renderer = pystache.Renderer(search_dirs="templates")
  57. def load_template(name):
  58. with open(f"templates/{name}.mustache") as f:
  59. parsed = pystache.parse(f.read())
  60. return lambda stuff: Template.renderer.render(parsed, stuff)
  61. main = load_template("main")
  62. quote = load_template("quote")
  63. list = load_template("list")
  64. def main():
  65. year = datetime.datetime.now().year
  66. std_copy = f'© Getty Ritter {year}'
  67. no_copy = 'all rights reversed'
  68. # gather the quips and make their individual pages
  69. quips = []
  70. for uuid in Path.list('quips'):
  71. q = Quip.from_file(Path.data('quips', uuid))
  72. quips.append(q)
  73. with Path.write('quips', uuid, 'index.html') as f:
  74. f.write(Template.main({
  75. 'title': f"Quip {uuid}",
  76. 'contents': Template.quote({'quotelist': [q]}),
  77. 'copy': no_copy,
  78. }))
  79. # sort 'em and make the combined page
  80. quips.sort(key=lambda q: q.id)
  81. with Path.write('quips', 'index.html') as f:
  82. f.write(Template.main({
  83. 'title': "Quips",
  84. 'contents': Template.quote({'quotelist': quips}),
  85. 'copy': no_copy,
  86. }))
  87. # gather the quotes and make their individual pages
  88. quotes = []
  89. for uuid in Path.list('quotes'):
  90. q = Quote.from_file(Path.data('quotes', uuid))
  91. quotes.append(q)
  92. with Path.write('quotes', uuid, 'index.html') as f:
  93. f.write(Template.main({
  94. 'title': f"Quote {uuid}",
  95. 'contents': Template.quote({'quotelist': [q]}),
  96. 'copy': no_copy,
  97. }))
  98. # sort 'em and make their combined page
  99. quotes.sort(key=lambda q: q.id)
  100. with Path.write('quotes', 'index.html') as f:
  101. f.write(Template.main({
  102. 'title': "Quotes",
  103. 'contents': Template.quote({'quotelist': quotes}),
  104. 'copy': no_copy,
  105. }))
  106. # figure out what categories we've got
  107. with open(Path.data('works.json')) as f:
  108. categories = yaml.safe_load(f)
  109. # make an index page for each category
  110. with Path.write('category', 'index.html') as f:
  111. f.write(Template.main({
  112. 'title': 'Categories',
  113. 'contents': Template.list({
  114. 'works': [
  115. {'slug': f'category/{c["slug"]}', 'title': c['category']}
  116. for c in categories
  117. ]
  118. }),
  119. 'copy': 'whatever',
  120. }))
  121. # create each category page
  122. for slug in os.listdir(Path.data('works')):
  123. # we need to know what works exist in the category
  124. works = []
  125. for work in os.listdir(Path.data('works', slug)):
  126. # grab the metadata for this work
  127. with open(Path.data('works', slug, work, 'metadata.yaml')) as f:
  128. meta = yaml.safe_load(f)
  129. with open(Path.data('works', slug, work, 'text')) as f:
  130. text = markdown.markdown(f.read())
  131. w = Work(
  132. slug=meta.get('slug', work),
  133. category=meta.get('category', slug),
  134. title=meta['name'],
  135. date=meta['date'],
  136. contents=text,
  137. )
  138. if slug == 'pages':
  139. # always keep index/about up-to-date
  140. copy = std_copy
  141. else:
  142. # report other works in their own year
  143. copy = f'© Getty Ritter {w.date}'
  144. with Path.write(w.slug, 'index.html') as f:
  145. f.write(Template.main({
  146. 'title': w.title,
  147. 'contents': text,
  148. 'copy': copy,
  149. }))
  150. works.append(w)
  151. works.sort(key=lambda w: w.slug)
  152. # not every on-disk category should be shown: we should find
  153. # it in the categories list first
  154. category_metadata = [c for c in categories if c['slug'] == slug]
  155. if not category_metadata:
  156. print(f'skipping {slug}')
  157. continue
  158. with Path.write('category', slug, 'index.html') as f:
  159. f.write(Template.main({
  160. 'title': category_metadata[0]['category'],
  161. 'contents': Template.list({
  162. 'works': works,
  163. }),
  164. 'copy': std_copy,
  165. }))
  166. shutil.copy(Path.out('index', 'index.html'), Path.out('index.html'))
  167. os.makedirs(Path.out('static'), exist_ok=True)
  168. shutil.copy('static/main.css', Path.out('static', 'main.css'))
  169. shutil.copy('static/icon.png', Path.out('static', 'icon.png'))
  170. if __name__ == '__main__':
  171. main()