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)