Files
housecarl/scripts/logo.py
T
abrown38andClaude Opus 5.5 54beb0bba3 Start Housecarl: the logo, the wordmark and the skeleton
Housecarl will be a firewall manager for nftables, built the way Jarl and
Steward are: housecarld (a daemon with a REST API and web UI), hc (the
CLI) and SQLite.

- The logo: a carved Nordic doorway in Jarl's blue (artwork/logo.svg),
  with a small version without the carvings, lighter on dark
  backgrounds. scripts/logo.py builds the web UI's copies and
  scripts/banner.py the README banner.
- The HOUSECARL wordmark in Jarl's and Steward's angular letters, with
  new H, O, U and C (assets/brand.js), and the UI's color tokens.
- housecarld and hc print their version; the Makefile, version package,
  AGENTS.md, CHANGELOG and the design sketch (docs/design.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:20:05 +01:00

54 lines
2.1 KiB
Python

#!/usr/bin/env python3
# Builds the web UI's logo from the masters: artwork/logo.svg (a carved
# Nordic doorway) and artwork/logo-small.svg (the same doorway without its
# carvings, for small sizes). Each master is one evenodd path.
#
# internal/web/static/assets/logo.svg the favicon (a copy of the small one)
# internal/web/static/assets/logo.js both paths for the web UI, which
# draws them inline (brand.js), so the
# logo appears together with the page
#
# It then runs scripts/banner.py to rebuild the README's banner. Run from
# the repository root after changing a master:
#
# python3 scripts/logo.py
import json
import os
import shutil
import subprocess
import sys
import xml.etree.ElementTree as ET
MASTERS = {"full": "artwork/logo.svg", "small": "artwork/logo-small.svg"}
OUT = "internal/web/static/assets"
SVG = "{http://www.w3.org/2000/svg}"
def read(path):
root = ET.parse(path).getroot()
paths = root.findall(f"{SVG}path")
if len(paths) != 1:
sys.exit(f"logo.py: {path} should hold exactly one <path>")
return root.attrib["viewBox"], paths[0].attrib["d"]
def main():
found = {name: read(path) for name, path in MASTERS.items()}
views = {vb for vb, _ in found.values()}
if len(views) != 1:
sys.exit("logo.py: the full and small logos must share one viewBox")
with open(os.path.join(OUT, "logo.js"), "w") as f:
f.write("// The Housecarl logo, from artwork/logo.svg and artwork/logo-small.svg by\n"
"// scripts/logo.py; don't edit. brand.js draws it; logo.svg is the small one.\n"
f"export const viewBox = {json.dumps(views.pop())};\n"
f"export const full = {json.dumps(found['full'][1])};\n"
f"export const small = {json.dumps(found['small'][1])};\n")
shutil.copyfile(MASTERS["small"], os.path.join(OUT, "logo.svg"))
for name in ("logo.svg", "logo.js"):
print(f"{OUT}/{name}: {os.path.getsize(os.path.join(OUT, name))} bytes")
subprocess.run([sys.executable, "scripts/banner.py"], check=True)
if __name__ == "__main__":
sys.exit(main())