generate-readmes 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/env python3
  2. import json
  3. import os
  4. import shutil
  5. import subprocess
  6. import tempfile
  7. from multiprocessing.pool import Pool
  8. from PIL import Image as PillowImage
  9. class Image:
  10. @classmethod
  11. def thumbnail(cls, args):
  12. source, target, size = args
  13. if os.path.exists(target):
  14. return
  15. print(f"Generating thumbnail for {source}")
  16. scratch = tempfile.mkdtemp(prefix="lnxpcs-")
  17. resized = os.path.join(scratch, "resized.png")
  18. cls._resize(source, resized, size)
  19. cls._optimise(resized, target)
  20. shutil.rmtree(scratch)
  21. @classmethod
  22. def _resize(cls, source, target, size):
  23. try:
  24. im = PillowImage.open(source)
  25. im.thumbnail(size)
  26. im.save(target, "PNG")
  27. except IOError:
  28. print(f"Thumbnail creation failed for {source}")
  29. @classmethod
  30. def _optimise(cls, source, target):
  31. subprocess.Popen(
  32. ["optipng", "-o7", source, "-out", target],
  33. stdout=subprocess.DEVNULL,
  34. stderr=subprocess.DEVNULL
  35. ).wait()
  36. class Prettifier:
  37. ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
  38. THUMB_SIZE = (256, 256)
  39. def __init__(self, columns, threads=None):
  40. self.columns = columns
  41. self.threads = threads or int(
  42. subprocess.check_output(["nproc"]).strip())
  43. def _generate_headers(self):
  44. return "| {} |\n| {} |\n".format(
  45. " | ".join(["" for _ in range(self.columns)]),
  46. " | ".join([":---:" for _ in range(self.columns)])
  47. )
  48. def _generate_image_cell(self, path, base):
  49. return "![{name}]({path})".format(
  50. name=path.replace(".png", ""),
  51. path=os.path.join(".meta", "thumbnails", path)
  52. )
  53. def _generate_text_cell(self, path, base):
  54. """
  55. Look for a file called ``support.json`` in the ``.meta`` subdirectory
  56. if it's there, it should have the following format:
  57. {
  58. image-name: {
  59. "shirt": "https://url.to/shirt",
  60. "mug": "https://hurl.to/mug",
  61. ...
  62. },
  63. }
  64. Using this file as a guide, we generate the relevant text bits.
  65. """
  66. name = path.replace(".png", "")
  67. r = "[{name}]({path})".format(
  68. name=name.replace("-", " "),
  69. path=path
  70. )
  71. support = {}
  72. try:
  73. support_path = os.path.join(
  74. self.ROOT,
  75. base,
  76. ".meta",
  77. "support.json"
  78. )
  79. with open(support_path) as f:
  80. support = json.load(f)[name]
  81. except (FileNotFoundError, KeyError):
  82. pass
  83. for key, value in support.items():
  84. r += f" ([{key}]({value}))"
  85. return r
  86. def _generate_thumbnails(self, sources):
  87. args = []
  88. for source in sources:
  89. args.append((
  90. source,
  91. os.path.join(
  92. os.path.dirname(source),
  93. ".meta",
  94. "thumbnails",
  95. os.path.basename(source)),
  96. self.THUMB_SIZE
  97. ))
  98. with Pool(processes=self.threads) as pool:
  99. pool.map(Image.thumbnail, args)
  100. def _generate_rows(self, paths, base):
  101. this, remainder = paths[:self.columns], paths[self.columns:]
  102. r = "| {} |\n| {} |\n".format(
  103. " | ".join([self._generate_image_cell(p, base) for p in this]),
  104. " | ".join([self._generate_text_cell(p, base) for p in this])
  105. )
  106. if remainder:
  107. r += self._generate_rows(remainder, base)
  108. return r
  109. def generate(self, path):
  110. return self._generate_headers() + self._generate_rows(
  111. sorted(
  112. [_ for _ in os.listdir(path) if ".png" in _]
  113. ),
  114. path.replace(self.ROOT + "/", "")
  115. )
  116. @classmethod
  117. def _get_image_dirs(cls, path):
  118. r = []
  119. for path, _, files in os.walk(path):
  120. if "." in path:
  121. continue
  122. for f in files:
  123. if ".png" in f:
  124. r.append(path)
  125. break
  126. return r
  127. @classmethod
  128. def main(cls, columns):
  129. instance = cls(columns)
  130. originals = []
  131. for d in cls._get_image_dirs(cls.ROOT):
  132. thumb_dir = os.path.join(d, ".meta", "thumbnails")
  133. os.makedirs(thumb_dir, exist_ok=True)
  134. with open(os.path.join(d, "README.md"), "w") as f:
  135. f.write(instance.generate(d))
  136. originals += [os.path.join(d, _) for _ in os.listdir(d) if ".png" in _] # NOQA: E501
  137. instance._generate_thumbnails(originals)
  138. if __name__ == "__main__":
  139. Prettifier.main(4)