main.py 6.7 KB

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