main.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 = []
  60. tgt = f'{os.path.join(*paths)}/'
  61. for path in sys.argv[2:]:
  62. if tgt in path:
  63. print(f'{tgt} in {path}')
  64. chunks = path.split('/')
  65. idx = chunks.index(paths[-1])
  66. stuff.append(chunks[idx +1])
  67. return stuff
  68. class Template:
  69. renderer = pystache.Renderer(search_dirs="templates")
  70. def load_template(name):
  71. with open(f"templates/{name}.mustache") as f:
  72. parsed = pystache.parse(f.read())
  73. return lambda stuff: Template.renderer.render(parsed, stuff)
  74. main = load_template("main")
  75. quote = load_template("quote")
  76. list = load_template("list")
  77. def main():
  78. out_file = sys.argv[1]
  79. year = datetime.datetime.now().year
  80. std_copy = f'© Getty Ritter {year}'
  81. no_copy = 'all rights reversed'
  82. # gather the quips and make their individual pages
  83. quips = []
  84. for uuid in Path.list('quips'):
  85. q = Quip.from_file(Path.data('quips', uuid))
  86. quips.append(q)
  87. with Path.write('quips', uuid, 'index.html') as f:
  88. f.write(Template.main({
  89. 'title': f"Quip {uuid}",
  90. 'contents': Template.quote({'quotelist': [q]}),
  91. 'copy': no_copy,
  92. }))
  93. # sort 'em and make the combined page
  94. quips.sort(key=lambda q: q.id)
  95. with Path.write('quips', 'index.html') as f:
  96. f.write(Template.main({
  97. 'title': "Quips",
  98. 'contents': Template.quote({'quotelist': quips}),
  99. 'copy': no_copy,
  100. }))
  101. # gather the quotes and make their individual pages
  102. quotes = []
  103. for uuid in Path.list('quotes'):
  104. q = Quote.from_file(Path.data('quotes', uuid))
  105. quotes.append(q)
  106. with Path.write('quotes', uuid, 'index.html') as f:
  107. f.write(Template.main({
  108. 'title': f"Quote {uuid}",
  109. 'contents': Template.quote({'quotelist': [q]}),
  110. 'copy': no_copy,
  111. }))
  112. # sort 'em and make their combined page
  113. quotes.sort(key=lambda q: q.id)
  114. with Path.write('quotes', 'index.html') as f:
  115. f.write(Template.main({
  116. 'title': "Quotes",
  117. 'contents': Template.quote({'quotelist': quotes}),
  118. 'copy': no_copy,
  119. }))
  120. # figure out what categories we've got
  121. with open(Path.data('works.json')) as f:
  122. categories = yaml.safe_load(f)
  123. # make an index page for each category
  124. with Path.write('category', 'index.html') as f:
  125. f.write(Template.main({
  126. 'title': 'Categories',
  127. 'contents': Template.list({
  128. 'works': [
  129. {'slug': f'category/{c["slug"]}', 'title': c['category']}
  130. for c in categories
  131. ]
  132. }),
  133. 'copy': 'whatever',
  134. }))
  135. # create each category page
  136. for slug in Path.list('works'):
  137. # we need to know what works exist in the category
  138. works = []
  139. for work in Path.list('works', slug):
  140. # grab the metadata for this work
  141. with open(Path.data('works', slug, work, 'metadata.yaml')) as f:
  142. meta = yaml.safe_load(f)
  143. with open(Path.data('works', slug, work, 'text')) as f:
  144. text = markdown.markdown(f.read())
  145. w = Work(
  146. slug=meta.get('slug', work),
  147. category=meta.get('category', slug),
  148. title=meta['name'],
  149. date=meta['date'],
  150. contents=text,
  151. )
  152. if slug == 'pages':
  153. # always keep index/about up-to-date
  154. copy = std_copy
  155. else:
  156. # report other works in their own year
  157. copy = f'© Getty Ritter {w.date}'
  158. with Path.write(w.slug, 'index.html') as f:
  159. f.write(Template.main({
  160. 'title': w.title,
  161. 'contents': text,
  162. 'copy': copy,
  163. }))
  164. works.append(w)
  165. works.sort(key=lambda w: w.slug)
  166. # not every on-disk category should be shown: we should find
  167. # it in the categories list first
  168. category_metadata = [c for c in categories if c['slug'] == slug]
  169. if not category_metadata:
  170. print(f'skipping {slug}')
  171. continue
  172. with Path.write('category', slug, 'index.html') as f:
  173. f.write(Template.main({
  174. 'title': category_metadata[0]['category'],
  175. 'contents': Template.list({
  176. 'works': works,
  177. }),
  178. 'copy': std_copy,
  179. }))
  180. shutil.copy(Path.out('index', 'index.html'), Path.out('index.html'))
  181. os.makedirs(Path.out('static'), exist_ok=True)
  182. shutil.copy('static/main.css', Path.out('static', 'main.css'))
  183. shutil.copy('static/icon.png', Path.out('static', 'icon.png'))
  184. print(f'writing to {out_file}')
  185. shutil.make_archive('output', 'zip', Path.OUTDIR.name)
  186. shutil.move('output.zip', out_file)
  187. Path.OUTDIR.cleanup()
  188. if __name__ == '__main__':
  189. main()