#!/usr/bin/env python3 import json import os import re import xml.etree.ElementTree as ET from datetime import date import yaml from history_store import latest_date, load_history from yaml_2_js import fields def require(condition, message): if not condition: raise RuntimeError(message) def parse_data_js(text): match = re.fullmatch( r'var osc_data = (\[.*\]);\nvar cols = (\[.*\]);\nvar col_keys = (\[.*\]);\n', text, flags=re.S, ) require(match is not None, 'data.js has an unexpected structure') return ( json.loads(match.group(1)), json.loads(match.group(2)), json.loads(match.group(3)), ) def parse_star_history_js(text): match = re.fullmatch( r'var osc_history = (\{.*\});\n' r'var osc_history_range = (\{.*\});\n' r'var osc_history_default = (\[.*\]);\n', text, flags=re.S, ) require(match is not None, 'star-history.js has an unexpected structure') return ( json.loads(match.group(1)), json.loads(match.group(2)), json.loads(match.group(3)), ) FONT_SUBSET_PATH = 'css/fonts/archivo-subset.txt' def report_font_coverage(rows): """Warn about table text the subset font cannot draw. The web font is cut down to the characters the page actually renders, so a newly added description can introduce one the font lacks. That degrades to a fallback glyph rather than breaking anything, so this reports instead of failing the update. """ if not os.path.exists(FONT_SUBSET_PATH): return with open(FONT_SUBSET_PATH, 'r', encoding='utf-8') as source: covered = set(source.read().rstrip('\n')) uncovered = sorted({ character for row in rows for cell in row for character in cell if character.isprintable() and character not in covered }) if uncovered: print( 'Note: {} character(s) in data.js are outside the font subset and ' 'will use a fallback glyph: {}'.format( len(uncovered), ' '.join('{} (U+{:04X})'.format(c, ord(c)) for c in uncovered), ) ) def main(): with open('data.yaml', 'r', encoding='utf-8') as source: data = yaml.load(source, Loader=yaml.SafeLoader) require(isinstance(data, dict) and data, 'data.yaml must contain a non-empty mapping') require( all(isinstance(name, str) and isinstance(item, dict) for name, item in data.items()), 'data.yaml contains an invalid entry', ) with open('data.js', 'r', encoding='utf-8') as source: rows, columns, keys = parse_data_js(source.read()) require(keys == fields, 'data.js column keys do not match yaml_2_js.fields') require(len(rows) == len(data), 'data.js row count does not match data.yaml') require( all(isinstance(row, list) and len(row) == len(fields) for row in rows), 'data.js contains a row with the wrong number of columns', ) require( len(columns) == len(fields) and all(isinstance(column, dict) and set(column) == {'title'} for column in columns), 'data.js column metadata is invalid', ) for index, item in enumerate(data.values()): if item.get('update_warning'): star_cell = rows[index][0] require('stars-with-extra' in star_cell, 'warning row has no star marker') require('Update warning on' in star_cell, 'warning row has no tooltip') history = load_history() expected_date = os.environ.get('OSC_EXPECT_SNAPSHOT_DATE', str(date.today())) require( latest_date(history) == expected_date, 'history latest date is {}, expected {}'.format(latest_date(history), expected_date), ) with open('index.html', 'r', encoding='utf-8') as source: html = source.read() require(html.count('