mirror of
https://gitlab.com/dwt1/dotfiles.git
synced 2023-02-13 20:55:19 -05:00
263 lines
6.9 KiB
Python
263 lines
6.9 KiB
Python
|
#!/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()
|