← Files OGENIC GOD TOOLKITARCHIVED FILE
scripts/netwalk_logos.py
7.83 KB · Oct 3, 2026 · 06:33 UTC
#!/usr/bin/env python3
"""Fetch vendor logos for netwalk diagrams.
Brand marks are other people's trademarks, so this repository ships none of them.
This script pulls the icons you want from Simple Icons (https://simpleicons.org),
whose icon files are CC0-1.0, into assets/logos/ where netwalk_map.py looks for them.
netwalk_logos.py fetch # the common networking vendors
netwalk_logos.py fetch cisco juniper # just these
netwalk_logos.py list # what is installed
Skipping this step is fine. A vendor with no logo file gets a lettered chip on the
diagram instead - slightly plainer, exactly as informative.
"""
from __future__ import annotations
import argparse
import os
import re
import sys
import urllib.error
import urllib.request
HERE = os.path.dirname(os.path.abspath(__file__))
LOGO_DIR = os.path.join(os.path.dirname(HERE), "assets", "logos")
CDN = "https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/{slug}.svg"
# netwalk vendor string -> Simple Icons slug
DEFAULT = {
"mikrotik": "mikrotik", "cisco": "cisco", "ubiquiti": "ubiquiti",
"juniper": "junipernetworks", "fortinet": "fortinet", "tplink": "tplink",
"synology": "synology", "netgear": "netgear",
"linux": "linux", "windows": "windows", "proxmox": "proxmox",
"raspberrypi": "raspberrypi", "openwrt": "openwrt", "pfsense": "pfsense",
}
# Vendors with no Simple Icons entry get a clean generated wordmark instead.
WORDMARK = {
"aruba": ("aruba", "#FF8300"), "hp": ("hpe", "#01A982"), "ruckus": ("ruckus", "#CC0000"),
"extreme": ("extreme", "#7F35B2"), "dell": ("dell", "#007DB8"), "unifi": ("unifi", "#0559C9"),
"zyxel": ("ZYXEL", "#E3001B"),
}
# Thai ISPs are not in Simple Icons, and an internet uplink with no mark on it is
# the one box on a network diagram people scan for first. Generated wordmarks in the
# operator's own colour are enough to tell three circuits apart at a glance.
ISP_WORDMARK = {
"3bb": ("3BB", "#0066B3"), "ais": ("AIS", "#8DC63F"),
"ais-fibre": ("AIS", "#8DC63F"), "true": ("TRUE", "#E4002B"),
"trueonline": ("TRUE", "#E4002B"), "nt": ("NT", "#005BAA"),
"tot": ("TOT", "#0057A8"), "cat": ("CAT", "#F58220"),
"symphony": ("SYMPHONY", "#00A0E9"), "uih": ("UIH", "#0072BC"),
"jastel": ("JASTEL", "#ED1C24"), "sinet": ("SINET", "#005EB8"),
"dtac": ("dtac", "#00AEEF"), "starlink": ("STARLINK", "#000000"),
"internet": ("INTERNET", "#5B6B7B"),
}
NOTICE = """# Vendor logos
This directory is populated by `scripts/netwalk_logos.py fetch` and is **not** part of
the netwalk source distribution.
Icons downloaded from [Simple Icons](https://simpleicons.org) are licensed CC0-1.0.
Wordmarks generated by this script are plain text set in a generic sans-serif.
All company names, logos and brands are the property of their respective owners and are
used here only to identify equipment in a network diagram. Their inclusion implies no
affiliation with or endorsement by those companies. Remove any file you do not have the
right to use in your context - netwalk falls back to a lettered chip.
"""
def wordmark(text: str, colour: str) -> str:
"""A name set to fit a 24x24 box, whatever its length.
Choosing a font size by character count is a guess, and a long operator name like
SYMPHONY overflowed the box and printed across the label beside it. textLength
makes the browser do the fitting instead of us.
"""
size = 10.5 if len(text) <= 5 else 9.0 if len(text) <= 7 else 7.5
return (f'<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">'
f'<title>{text}</title>'
f'<text x="12" y="15.5" text-anchor="middle" textLength="22" '
f'lengthAdjust="spacingAndGlyphs" font-family="Helvetica,Arial,sans-serif" '
f'font-weight="bold" font-size="{size}" fill="{colour}">{text}</text></svg>')
def fetch_one(vendor: str, slug: str, timeout: int = 20) -> tuple[bool, str]:
url = CDN.format(slug=slug)
try:
req = urllib.request.Request(url, headers={"User-Agent": "netwalk-logos"})
with urllib.request.urlopen(req, timeout=timeout) as r:
body = r.read().decode("utf-8")
except urllib.error.HTTPError as e:
return False, f"HTTP {e.code} for slug {slug!r}"
except Exception as e: # noqa: BLE001
return False, f"{type(e).__name__}: {e}"
if "<svg" not in body or "viewBox" not in body:
return False, "response was not an SVG"
# Drop the hard-coded fill so the diagram's theme colour applies.
body = re.sub(r'\sfill="[^"]*"', "", body, count=1)
path = os.path.join(LOGO_DIR, f"{vendor}.svg")
with open(path, "w", encoding="utf-8") as fh:
fh.write(body)
return True, path
def cmd_fetch(args: argparse.Namespace) -> int:
os.makedirs(LOGO_DIR, exist_ok=True)
wanted = args.vendor or sorted(set(DEFAULT) | set(WORDMARK))
ok = fail = 0
for v in wanted:
v = v.lower()
if v in WORDMARK:
text, colour = WORDMARK[v]
with open(os.path.join(LOGO_DIR, f"{v}.svg"), "w", encoding="utf-8") as fh:
fh.write(wordmark(text, colour))
print(f" wordmark {v}")
ok += 1
continue
slug = DEFAULT.get(v, v)
good, msg = fetch_one(v, slug)
if good:
print(f" fetched {v} (simple-icons: {slug})")
ok += 1
else:
print(f" skipped {v} - {msg}", file=sys.stderr)
fail += 1
with open(os.path.join(LOGO_DIR, "NOTICE.md"), "w", encoding="utf-8") as fh:
fh.write(NOTICE)
print(f"\n{ok} logo(s) in {LOGO_DIR}" + (f", {fail} unavailable" if fail else ""))
print("vendors without a logo file render as a lettered chip - that is fine")
return 0
def cmd_isp(args: argparse.Namespace) -> int:
"""Write wordmarks for internet providers, as isp-<slug>.svg."""
os.makedirs(LOGO_DIR, exist_ok=True)
wanted = {k.lower(): v for k, v in ISP_WORDMARK.items()}
if args.name:
for n in args.name:
slug = re.sub(r"[^a-z0-9]+", "-", n.lower()).strip("-")
if slug not in wanted:
wanted[slug] = (n.upper()[:12], args.colour or "#5B6B7B")
wanted = {k: v for k, v in wanted.items()
if k in {re.sub(r"[^a-z0-9]+", "-", n.lower()).strip("-") for n in args.name}}
for slug, (text, colour) in sorted(wanted.items()):
with open(os.path.join(LOGO_DIR, f"isp-{slug}.svg"), "w", encoding="utf-8") as fh:
fh.write(wordmark(text, colour))
print(f" wordmark isp-{slug} ({text})")
print(f"\n{len(wanted)} ISP mark(s) in {LOGO_DIR}")
print("netwalk matches these against wan_links[].isp - an uplink whose ISP has no "
"mark gets a lettered chip instead")
return 0
def cmd_list(args: argparse.Namespace) -> int:
if not os.path.isdir(LOGO_DIR):
print(f"no logo directory yet ({LOGO_DIR}) - run: netwalk_logos.py fetch")
return 1
files = sorted(f for f in os.listdir(LOGO_DIR) if f.endswith(".svg"))
for f in files:
print(f" {f[:-4]}")
print(f"\n{len(files)} logo(s) in {LOGO_DIR}")
return 0
def main() -> int:
ap = argparse.ArgumentParser(prog="netwalk_logos.py", description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
sub = ap.add_subparsers(dest="cmd", required=True)
f = sub.add_parser("fetch")
f.add_argument("vendor", nargs="*")
f.set_defaults(func=cmd_fetch)
i = sub.add_parser("isp", help="write wordmarks for internet providers")
i.add_argument("name", nargs="*", help="ISP names; omit for the built-in set")
i.add_argument("--colour", help="brand colour for a name that is not built in")
i.set_defaults(func=cmd_isp)
l = sub.add_parser("list")
l.set_defaults(func=cmd_list)
args = ap.parse_args()
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
SHA-256: ba5de101ae357b087b6e85e74e7bd3cafb47832e15003df0aae980c49b767dcc