Source code for uw.local.wcms.bin.make_font_css

"""Program to process downloaded font CSS from WCMS.

Main program for processing downloaded font CSS from WCMS.  Scans each
downloaded site for CSS @font-face directives and assembles a directory of
font files and a CSS file to refer to the font files, overriding the font
files specified by the original CSS files.
"""

import errno
import glob
import os
from os.path import join as pathjoin
import re
import sys

from operator import attrgetter

fontface_re = re.compile ('@font-face *{.*?}', re.DOTALL)

[docs]def re_capt (capture, contents): """Construct a regular expression to capture the specified contents. :param str capture: The name to use when capturing the matched value. :param str contents: The re text to match. :return: A regular expression in the form (?P[capture]<[contents]>) where [capture] is replaced by the capture name and [contents] is replaced by the contents regular expression. """ return '(?P<%s>%s)' % (capture, contents)
dst_re = re_capt ('dst', '[^/?#)]*') src_re = re_capt ('src', dst_re + '([?][^#)]*)?') rpl_re = re_capt ('rpl', src_re + '([#][^)]*)?') dir_re = re_capt ('dir', '[^?#)]*/') url_re = re.compile ('url *[(]' + dir_re + rpl_re + '[)]')
[docs]def find_fonts (src, site): """Find all font references in CSS for the specified site. :param str src: The base directory of downloaded site files. :param str site: The directory to scan within the base directory. :return: A 2-tuple consisting of a dictionary from filenames to contents; and a list of warning/error messages applicable to this site. Find all .css files within the site and scan them for @font-face sections. Collect the referenced font files together, along with a file of modified @font-face sections to refer to the collected font files. """ files = {} fontfaces = [] messages = [] for cssfile in glob.glob (pathjoin (glob.escape (src), glob.escape (site), '**', '*.css'), recursive=True): with open (cssfile) as f: contents = f.read () for fontface in fontface_re.findall (contents): for url in url_re.finditer (fontface): groups = url.groupdict () srcfilename = src + groups['dir'] + groups['src'] try: with open (srcfilename, 'rb') as f: contents = f.read () except OSError as e: if e.errno == errno.ENOENT: messages.append ("no file (%s referred to by %s)!" % (srcfilename, cssfile)) continue else: raise dstfilename = groups['dst'] if dstfilename in files: if contents != files[dstfilename]: messages.append ('mismatched file contents (%s)!' % srcfilename) else: files[dstfilename] = contents fontfaces.append (url_re.sub (r'url(\g<rpl>)', fontface)) fonts_css = ''.join (ff + '\n' for ff in fontfaces).encode () if fonts_css: files['fonts.css'] = fonts_css return files, messages
[docs]def writefiles (outputdir, files): """Write the specified file contents to the specified directory. :param str outputdir: The name of the output directory. :param dict files: A dictionary from filenames to file contents. Create the specified directory if necessary and write the file contents to that directory. """ os.makedirs (outputdir, exist_ok=True) for filename, contents in sorted (files.items ()): with open (pathjoin (outputdir, filename), 'wb') as f: f.write (contents)
[docs]def main (): (_, src, dst) = sys.argv output = None outputsite = None base = pathjoin (src, 'uwaterloo.ca') for file in sorted (os.scandir (base), key=attrgetter ('name')): site = file.name if not file.is_dir () or site in ['fonts']: continue print ('Processing site %s' % site) files, messages = find_fonts (base, site) if not files: messages.append ('no output') elif output is None: output = files outputsite = site elif output != files: messages.append ('Output files for site %s differ from output site %s' % (site, outputsite)) outputsite = None if messages: for message in messages: print (" %s" % message) writefiles (pathjoin (dst, 'sites', site), files) if outputsite: print ("Using output from site %s" % outputsite) writefiles (pathjoin (dst, 'output'), output) else: print ("Unable to generate output") exit (1)