#!/usr/bin/env python3
"""
Build a single self-contained HTML file from the source files.
Usage:
python3 build.py [output.html]
python3 build.py --check build to a temp file and only verify
What gets inlined:
- config.js, i18n.js (plain ' % tag
if old not in html:
raise SystemExit('build.py: expected tag not found: ' + tag)
# a literal inside an inline block would end it early
return html.replace(old, '' % body.replace('', '<\\/'), 1)
def parse_themes(config_src):
"""Return {themeId: cssFileName} from window.THEMES.
Accepts both shapes:
'ash': 'ash.css'
'ash': { css: 'ash.css', lighting: { ... } }
"""
m = re.search(r'window\.THEMES\s*=\s*\{', config_src)
if not m:
raise SystemExit('build.py: window.THEMES not found in config.js')
i = m.end()
depth = 1
block_start = i
while i < len(config_src) and depth:
c = config_src[i]
if c == '{':
depth += 1
elif c == '}':
depth -= 1
i += 1
block = config_src[block_start:i - 1]
themes = {}
# top level entries only: split on commas that sit at brace depth 0
parts, buf, depth = [], '', 0
for c in block:
if c == '{':
depth += 1
elif c == '}':
depth -= 1
if c == ',' and depth == 0:
parts.append(buf)
buf = ''
else:
buf += c
parts.append(buf)
for part in parts:
entry = re.match(r"\s*['\"]?([A-Za-z0-9_-]+)['\"]?\s*:\s*(.*)", part, re.S)
if not entry:
continue
name, value = entry.group(1), entry.group(2)
css = re.search(r"['\"]([^'\"]+\.css)['\"]", value)
if not css:
raise SystemExit('build.py: theme %r has no .css reference' % name)
themes[name] = css.group(1)
if not themes:
raise SystemExit('build.py: window.THEMES is empty')
return themes
def verify(html):
"""Fail if anything still points at a sibling file."""
leftovers = re.findall(r'\n'
% (json.dumps(stars, separators=(',', ':')), json.dumps(theme_css))
)
# must come before config.js so the early theme loader sees the CSS
anchor = ''
if anchor not in html:
raise SystemExit('build.py: anchor script tag not found in background.html')
html = html.replace(anchor, embed + anchor, 1)
for tag in INLINED_SCRIPTS:
html = inline_script(html, tag, read(tag))
# themes are embedded in window.__inlineThemeCss, so the stylesheet link
# is dropped completely (the theme loader guards on getElementById)
rep_link = re.search(r'\n[ \t]*]*>', html)
if not rep_link:
raise SystemExit('build.py: theme-css link not found in background.html')
html = html[:rep_link.start()] + '\n ' + \
html[rep_link.end():]
verify(html)
if check:
with tempfile.NamedTemporaryFile('w', suffix='.html', delete=False) as f:
f.write(html)
tmp = f.name
print('OK: self-contained (%d bytes, %d themes, %d stars)'
% (len(html), len(themes), len(stars)))
os.unlink(tmp)
return
with open(os.path.join(ROOT, out), 'w', encoding='utf-8') as f:
f.write(html)
print('Wrote %s (%d bytes)' % (out, len(html)))
print('Inlined: %s, stars.json (%d entries), themes: %s'
% (', '.join(INLINED_SCRIPTS), len(stars), ', '.join(sorted(themes))))
if __name__ == '__main__':
main()