123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- #!/usr/bin/env python3
- # * Imports
- import os
- import shutil
- import subprocess
- import sys
- import multiprocessing
- import functools
- from collections import namedtuple
- from tempfile import mkstemp
- # * Variables
- sites_dir="sites"
- themes_dir = "themes"
- css_dir = "css"
- screenshots_dir="screenshots"
- phantomjs_command = "phantomjs --ssl-protocol=any --ignore-ssl-errors=true screenshot.js".split()
- common_deps = ["styl/index.styl", "styl/mixins.styl"]
- CSS = namedtuple("CSS", ['path', 'deps', 'theme', 'site'])
- Theme = namedtuple("Theme", ['name', 'styl_path', 'support_files'])
- # * Functions
- def main():
- "Update CSS files by default, or update screenshots."
- if len(sys.argv) > 1 and sys.argv[1] == "screenshots":
- update_screenshots()
- else:
- update_css_files()
- # ** CSS
- def update_css_files():
- "Build CSS files that need to be built."
- css_files = list_css(themes(), sites())
- # Make directories first to avoid race condition
- for css in css_files:
- dir = os.path.join(css_dir, css.theme.name)
- if not os.path.isdir(dir):
- os.makedirs(dir)
- pool = multiprocessing.Pool(multiprocessing.cpu_count())
- pool.map(build, css_files)
- def build(css):
- "Build CSS file if necessary."
- css_mtime = mtime(css.path)
- make = False
- for dep in css.deps:
- if mtime(dep) > css_mtime:
- make = True
- break
- if make:
- stylus(css)
- def stylus(css):
- "Run Stylus to build CSS file."
- output_file = css.path
- command = ["stylus", "--include", "styl",
- "--import", css.theme.styl_path,
- "--import", "styl",
- "-p", "sites/%s.styl" % css.site]
- result = subprocess.check_output(command)
- with open(output_file, "wb") as f:
- f.write(result)
- print(output_file)
- # ** Screenshots
- def update_screenshots():
- "Update screenshots."
- css_files = list_css(themes(), sites())
- if not os.path.isdir(screenshots_dir):
- # If the directory does not exist, create a new worktree for it.
- # Assumes the screenshots branch exists.
- subprocess.call(["git", "worktree", "prune"])
- subprocess.call(["git", "worktree", "add",
- screenshots_dir, "screenshots"])
- # Make directories first to avoid race condition
- for css in css_files:
- output_dir = os.path.join(screenshots_dir, css.theme.name)
- if not os.path.isdir(output_dir):
- os.makedirs(output_dir)
- pool = multiprocessing.Pool(multiprocessing.cpu_count())
- pool.map(update_screenshot, css_files)
- commit_screenshots()
- def commit_screenshots():
- if os.path.exists(os.path.join(screenshots_dir, ".git")):
- subprocess.call(["git", "-C", screenshots_dir,
- "add", "-A"])
- # amend changes instead of keeping them to save space
- subprocess.call(["git", "-C", screenshots_dir,
- "commit", "--amend", "-m", "Update screenshots"])
- else:
- print("screenshot dir was not a worktree, aborting commit")
- def update_screenshot(css):
- "Update screenshot for CSS if necessary."
- screenshot_path = screenshot_path_for_css(css)
- if mtime(css.path) > mtime(screenshot_path):
- save_screenshot(css)
- def screenshot_path_for_css(css):
- "Return path of screenshot for CSS."
- return os.path.join(screenshots_dir, css.theme.name, "%s.png" % css.site)
- def save_screenshot(css):
- "Save screenshot for CSS."
- # Prepare filename
- screenshot_path = screenshot_path_for_css(css)
- # Get URL
- url = css_screenshot_url(css)
- if not url:
- # Screenshot disabled
- return False
- # Prepare command
- command = list(phantomjs_command)
- command.extend([url, screenshot_path, css.path])
- # Run PhantomJS
- subprocess.check_output(command)
- # Compress with pngcrush
- _, tempfile_path = mkstemp(suffix=".png")
- subprocess.check_output(["pngcrush", screenshot_path, tempfile_path], stderr=subprocess.DEVNULL)
- shutil.move(tempfile_path, screenshot_path)
- print(screenshot_path)
- def css_screenshot_url(css):
- "Return URL for taking screenshots of CSS."
- # Get site URL
- site_url_filename = os.path.join(sites_dir, css.site + ".url")
- if os.path.exists(site_url_filename):
- with open(site_url_filename, "r") as f:
- url = f.readlines()
- if url:
- # Use URL given in .url file
- url = url[0].strip()
- else:
- # Use name of site file (without .styl extension)
- url = "http://" + css.site
- return url
- # ** Support
- def list_css(themes, sites):
- "Return list of CSS files for THEMES and SITES."
- return [CSS("%s/%s/%s-%s.css" % (css_dir, theme.name, theme.name,
- site.strip('_')),
- dependencies(theme, site), theme, site)
- for theme in themes
- for site in sites]
- def themes():
- "Return list of themes."
- theme_names = []
- themes = []
- # Make list of theme directories
- for d in os.listdir(themes_dir):
- theme_names.append(d)
- # Iterate over theme directories
- for theme in theme_names:
- support_files = []
- variant_files = []
- directory = os.path.join(themes_dir, theme)
- # Iterate over files in theme directory
- for f in os.listdir(directory):
- path = os.path.join(themes_dir, theme, f)
- if f == "colors.styl":
- # Support file
- support_files.append(path)
- elif f.endswith(".styl"):
- # Theme file
- variant_files.append({'variant': without_styl(f), 'path': path})
- # Otherwise, not a relevant file
- # Add theme object to list
- if len(variant_files) == 1:
- # Only one variant: omit variant name from theme name
- themes.append(Theme(theme, variant_files[0]['path'], support_files))
- else:
- # Multiple variants: include variant name in theme name
- for f in variant_files:
- themes.append(Theme("%s-%s" % (theme, f['variant']), f['path'], support_files))
- return themes
- def sites():
- "Return list of sites."
- for path, dirs, files in os.walk(sites_dir):
- return [site.replace(".styl", "")
- for site in files
- if site.endswith(".styl")]
- def dependencies(theme, site):
- "Return list of dependency .styl files for THEME and SITE."
- deps = list(common_deps)
- deps.append(theme.styl_path)
- deps.extend(theme.support_files)
- deps.append("sites/%s.styl" % site)
- if site == "all-sites":
- deps += ["sites/%s.styl" % s for s in sites()]
- return deps
- @functools.lru_cache()
- def mtime(path):
- "Return mtime for PATH."
- if os.path.isfile(path):
- return os.path.getmtime(path)
- else:
- return 0
- def without_styl(s):
- """Return string S without ".styl" extension."""
- return s.replace(".styl", "")
- # * Footer
- if __name__ == "__main__":
- main()
|