diff --git a/megamek/skin/gen-backgrounds.py b/megamek/skin/gen-backgrounds.py index cad7c1f..91653c5 100644 --- a/megamek/skin/gen-backgrounds.py +++ b/megamek/skin/gen-backgrounds.py @@ -44,20 +44,22 @@ from PIL import Image, ImageDraw, ImageFont HERE = Path(__file__).resolve().parent -BG = (11, 16, 23) # #0b1017 -LINE = (23, 33, 52) # a touch above #131b26, still subtle +BG = (11, 16, 23) # #0b1017 +LINE = (23, 33, 52) # a touch above #131b26, still subtle ACCENT = (76, 141, 255) # #4c8dff HOSTILE = (255, 92, 92) # #ff5c5c def blend(base, over, alpha): - return tuple(int(b * (1 - alpha) + o * alpha) for b, o in zip(base, over)) + return tuple( + int(b * (1 - alpha) + o * alpha) for b, o in zip(base, over, strict=True) + ) def battlespace(path, r=12, cols=28, rows=48, seed=51): """Dense tactical plot layer over a half-size hex lattice.""" - VS = round(math.sqrt(3) * r) # 21 - W, H = 3 * r * cols, VS * rows # 1008 x 1008 + VS = round(math.sqrt(3) * r) # 21 + W, H = 3 * r * cols, VS * rows # 1008 x 1008 VEC = blend(BG, ACCENT, 0.40) GLYPH = blend(BG, ACCENT, 0.52) @@ -86,7 +88,10 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): y0 = (VS / 2) if (col % 2) else 0.0 y = y0 - VS while y < H + VS: - pts = [(x + r * math.cos(math.pi / 3 * i), y + r * math.sin(math.pi / 3 * i)) for i in range(6)] + pts = [ + (x + r * math.cos(math.pi / 3 * i), y + r * math.sin(math.pi / 3 * i)) + for i in range(6) + ] for i in range(6): d.line([pts[i], pts[(i + 1) % 6]], fill=LINE, width=1) y += VS @@ -105,13 +110,27 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): t = 0.0 while t < length: e = min(t + dash, length) - d.line([(x0 + ux * t, y0 + uy * t), (x0 + ux * e, y0 + uy * e)], fill=color, width=1) + d.line( + [(x0 + ux * t, y0 + uy * t), (x0 + ux * e, y0 + uy * e)], + fill=color, + width=1, + ) t = e + gap if arrow: for ang in (math.pi * 5 / 6, -math.pi * 5 / 6): ca, sa = math.cos(ang), math.sin(ang) - d.line([(x1, y1), (x1 + (ux * ca - uy * sa) * 7, y1 + (ux * sa + uy * ca) * 7)], - fill=color, width=1) + d.line( + [ + (x1, y1), + ( + x1 + (ux * ca - uy * sa) * 7, + y1 + (ux * sa + uy * ca) * 7, + ), + ], + fill=color, + width=1, + ) + wrapped(seg) def dashed_circle(cx, cy, radius, color, n=28): @@ -120,16 +139,33 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): if i % 2: continue a0, a1 = 2 * math.pi * i / n, 2 * math.pi * (i + 1) / n - d.line([(cx + dx + radius * math.cos(a0), cy + dy + radius * math.sin(a0)), - (cx + dx + radius * math.cos(a1), cy + dy + radius * math.sin(a1))], fill=color, width=1) + d.line( + [ + ( + cx + dx + radius * math.cos(a0), + cy + dy + radius * math.sin(a0), + ), + ( + cx + dx + radius * math.cos(a1), + cy + dy + radius * math.sin(a1), + ), + ], + fill=color, + width=1, + ) + wrapped(seg) def warn_triangle(cx, cy, s, color): def seg(dx, dy): x, y = cx + dx, cy + dy - d.polygon([(x, y - s), (x - s * 0.87, y + s * 0.5), (x + s * 0.87, y + s * 0.5)], outline=color) + d.polygon( + [(x, y - s), (x - s * 0.87, y + s * 0.5), (x + s * 0.87, y + s * 0.5)], + outline=color, + ) d.line([(x, y - s * 0.45), (x, y + s * 0.1)], fill=color, width=1) d.point((x, y + s * 0.3), fill=color) + wrapped(seg) def reticle(cx, cy, s, color): @@ -137,9 +173,18 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): x, y = cx + dx, cy + dy for sx in (-s, s): for sy in (-s, s): - d.line([(x + sx, y + sy), (x + sx - (4 if sx > 0 else -4), y + sy)], fill=color, width=1) - d.line([(x + sx, y + sy), (x + sx, y + sy - (4 if sy > 0 else -4))], fill=color, width=1) + d.line( + [(x + sx, y + sy), (x + sx - (4 if sx > 0 else -4), y + sy)], + fill=color, + width=1, + ) + d.line( + [(x + sx, y + sy), (x + sx, y + sy - (4 if sy > 0 else -4))], + fill=color, + width=1, + ) d.point((x, y), fill=color) + wrapped(seg) def diamond(cx, cy, s, color, filled=False): @@ -147,21 +192,35 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): x, y = cx + dx, cy + dy pts = [(x, y - s), (x + s, y), (x, y + s), (x - s, y)] d.polygon(pts, outline=color, fill=color if filled else None) + wrapped(seg) def tree(cx, cy, s, color): def seg(dx, dy): x, y = cx + dx, cy + dy - d.polygon([(x, y - s), (x - s * 0.6, y + s * 0.4), (x + s * 0.6, y + s * 0.4)], outline=color) + d.polygon( + [(x, y - s), (x - s * 0.6, y + s * 0.4), (x + s * 0.6, y + s * 0.4)], + outline=color, + ) d.line([(x, y + s * 0.4), (x, y + s * 0.8)], fill=color, width=1) + wrapped(seg) def label(cx, cy, text, color, font=None, leader_to=None): f = font or FONT + def seg(dx, dy): d.text((cx + dx, cy + dy), text, fill=color, font=f) if leader_to: - d.line([(cx + dx - 3, cy + dy + 5), (leader_to[0] + dx, leader_to[1] + dy)], fill=color, width=1) + d.line( + [ + (cx + dx - 3, cy + dy + 5), + (leader_to[0] + dx, leader_to[1] + dy), + ], + fill=color, + width=1, + ) + wrapped(seg) # --- movement vectors (14, some multi-leg) ------------------------------- @@ -181,14 +240,29 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): for _ in range(10): cx, cy = rng.uniform(0, W), rng.uniform(0, H) for _ in range(rng.randint(4, 8)): - tree(cx + rng.uniform(-26, 26), cy + rng.uniform(-20, 20), rng.uniform(5, 9), TREE) + tree( + cx + rng.uniform(-26, 26), + cy + rng.uniform(-20, 20), + rng.uniform(5, 9), + TREE, + ) # --- bracketed callouts with leader lines (7) ---------------------------- - callouts = ["[DROPSHIP LANDING SITE]", "[EVAC ZONE]", "[SUPPLY CACHE]", - "[COMM RELAY]", "[LZ-2]", "[MINEFIELD?]", "[REPAIR BAY]"] + callouts = [ + "[DROPSHIP LANDING SITE]", + "[EVAC ZONE]", + "[SUPPLY CACHE]", + "[COMM RELAY]", + "[LZ-2]", + "[MINEFIELD?]", + "[REPAIR BAY]", + ] for text in callouts: cx, cy = rng.uniform(0, W), rng.uniform(0, H) - tx, ty = cx + rng.choice((-1, 1)) * rng.uniform(20, 40), cy - rng.uniform(14, 26) + tx, ty = ( + cx + rng.choice((-1, 1)) * rng.uniform(20, 40), + cy - rng.uniform(14, 26), + ) diamond(cx, cy, 3, TEXT) label(tx, ty, text, TEXT, leader_to=(cx, cy)) @@ -196,7 +270,13 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): for i in range(5): cx, cy = rng.uniform(0, W), rng.uniform(0, H) for _ in range(rng.randint(2, 4)): - diamond(cx + rng.uniform(-14, 14), cy + rng.uniform(-10, 10), 4, FOE, filled=False) + diamond( + cx + rng.uniform(-14, 14), + cy + rng.uniform(-10, 10), + 4, + FOE, + filled=False, + ) dashed_circle(cx, cy, rng.uniform(22, 34), FOE_DIM) if i < 3: label(cx + 20, cy - 30, "[POSS. ENEMY FORCE]", FOE) @@ -226,6 +306,7 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): cx, cy = rng.uniform(0, W), rng.uniform(0, H) a = rng.uniform(0, 2 * math.pi) for rr in (16, 24, 32): + def seg(dx, dy, cx=cx, cy=cy, rr=rr, a=a): steps = 14 span = 1.1 @@ -234,16 +315,27 @@ def battlespace(path, r=12, cols=28, rows=48, seed=51): continue a0 = a + span * i / steps a1 = a + span * (i + 1) / steps - d.line([(cx + dx + rr * math.cos(a0), cy + dy + rr * math.sin(a0)), - (cx + dx + rr * math.cos(a1), cy + dy + rr * math.sin(a1))], fill=DIM, width=1) + d.line( + [ + (cx + dx + rr * math.cos(a0), cy + dy + rr * math.sin(a0)), + (cx + dx + rr * math.cos(a1), cy + dy + rr * math.sin(a1)), + ], + fill=DIM, + width=1, + ) + wrapped(seg) # --- tagged unit dots (10) ----------------------------------------------- for _ in range(10): cx, cy = rng.uniform(0, W), rng.uniform(0, H) tag = f"{rng.choice('ABCD')}-{rng.randint(1, 9)}" + def seg(dx, dy, cx=cx, cy=cy): - d.rectangle([cx + dx - 2, cy + dy - 2, cx + dx + 2, cy + dy + 2], outline=VEC) + d.rectangle( + [cx + dx - 2, cy + dy - 2, cx + dx + 2, cy + dy + 2], outline=VEC + ) + wrapped(seg) label(cx + 6, cy - 5, tag, DIM, font=FONT_S) @@ -274,7 +366,10 @@ def scrollpane(path, size=8): if __name__ == "__main__": - for name, gen in (("battlespace-hex.png", battlespace), ("scrollpane.png", scrollpane)): + for name, gen in ( + ("battlespace-hex.png", battlespace), + ("scrollpane.png", scrollpane), + ): out = HERE / name gen(out) print(name, Image.open(out).size, out.stat().st_size, "bytes") diff --git a/megamek/skin/gen-icons.py b/megamek/skin/gen-icons.py index ee3eba4..ee310b8 100644 --- a/megamek/skin/gen-icons.py +++ b/megamek/skin/gen-icons.py @@ -22,11 +22,15 @@ SIZES = (16, 32, 48, 256) if __name__ == "__main__": if len(sys.argv) != 2: - sys.exit("usage: gen-icons.py (headquarters/web/public/favicon.svg)") + sys.exit( + "usage: gen-icons.py (headquarters/web/public/favicon.svg)" + ) svg = Path(sys.argv[1]).read_bytes() out_dir = HERE / "icons" out_dir.mkdir(exist_ok=True) for s in SIZES: out = out_dir / f"megamek-icon-{s}x{s}.png" - cairosvg.svg2png(bytestring=svg, write_to=str(out), output_width=s, output_height=s) + cairosvg.svg2png( + bytestring=svg, write_to=str(out), output_width=s, output_height=s + ) print(out.name, out.stat().st_size, "bytes") diff --git a/prek.toml b/prek.toml index e6adff3..324211b 100644 --- a/prek.toml +++ b/prek.toml @@ -8,6 +8,8 @@ hooks = [ { id = "check-merge-conflict" }, { id = "check-added-large-files" }, { id = "check-yaml" }, + { id = "check-toml" }, + { id = "check-xml" }, { id = "mixed-line-ending", args = ["--fix=lf"], exclude = '\.patch$' }, { id = "end-of-file-fixer", exclude = '\.patch$' }, { id = "trailing-whitespace", exclude = '\.patch$' }, @@ -19,3 +21,13 @@ rev = "v0.11.0.1" # Same flags as the repo's own gate, tests/shell/lint.sh: -x follows sourced # files, and info-level notes do not gate. hooks = [{ id = "shellcheck", args = ["-x", "--severity=warning"] }] + +[[repos]] +repo = "https://github.com/astral-sh/ruff-pre-commit" +rev = "v0.16.1" +# Covers the helper scripts under tests/ and megamek/skin/. ruff parses the +# files itself, so a syntax error fails here too - no separate check-ast. +hooks = [ + { id = "ruff-check", args = ["--fix"] }, + { id = "ruff-format" }, +] diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..3b2116a --- /dev/null +++ b/ruff.toml @@ -0,0 +1,9 @@ +# Pinned so the gate does not move when ruff's defaults do. The scripts under +# tests/ and megamek/skin/ are helpers run by hand, not a shipped package, so +# the security and blind-except rules are left off - a best-effort scanner that +# skips unreadable jars is doing the right thing. +# +# No E501: ruff format sets the line width, and it cannot split a long string. + +[lint] +select = ["E4", "E7", "E9", "F", "W", "I", "UP", "B", "SIM", "C4"] diff --git a/tests/module-scan.py b/tests/module-scan.py index 0e1d648..da72d96 100755 --- a/tests/module-scan.py +++ b/tests/module-scan.py @@ -22,6 +22,7 @@ without it, and record why in versions.env. Not part of ./test.sh - it needs the extracted third-party trees, and it reports rather than passes or fails. """ + import glob import os import re @@ -31,27 +32,48 @@ import zipfile # Kept in step with versions.env by hand. A mismatch shows up as noise in the # output rather than a wrong answer. -CURRENT = set(""" -java.base java.datatransfer java.xml java.prefs java.desktop java.logging -java.management java.security.sasl java.naming java.scripting -java.transaction.xa java.sql jdk.unsupported jdk.crypto.ec jdk.zipfs -java.instrument jdk.net jdk.accessibility -""".split()) +CURRENT = { + "java.base", + "java.datatransfer", + "java.xml", + "java.prefs", + "java.desktop", + "java.logging", + "java.management", + "java.security.sasl", + "java.naming", + "java.scripting", + "java.transaction.xa", + "java.sql", + "jdk.unsupported", + "jdk.crypto.ec", + "jdk.zipfs", + "java.instrument", + "jdk.net", + "jdk.accessibility", +} # A capturing group here would make findall return just the group, so every # match collapses to "jdk" and the scan silently finds nothing. -REF = re.compile(rb'(?:java|javax|jdk|sun|com/sun)/[a-zA-Z0-9_/$]+') +REF = re.compile(rb"(?:java|javax|jdk|sun|com/sun)/[a-zA-Z0-9_/$]+") def package_map(jdk): """package -> module, for every observable module in this JDK.""" - mods = [m.split('@')[0] for m in subprocess.run( - [f"{jdk}/bin/java", "--list-modules"], - capture_output=True, text=True, check=True).stdout.split()] + mods = [ + m.split("@")[0] + for m in subprocess.run( + [f"{jdk}/bin/java", "--list-modules"], + capture_output=True, + text=True, + check=True, + ).stdout.split() + ] pkgs = {} for m in mods: - out = subprocess.run([f"{jdk}/bin/java", "--describe-module", m], - capture_output=True, text=True).stdout + out = subprocess.run( + [f"{jdk}/bin/java", "--describe-module", m], capture_output=True, text=True + ).stdout for line in out.splitlines(): line = line.strip() if line.startswith(("exports ", "contains ")): @@ -80,10 +102,10 @@ def modules_referenced(jars, pkg2mod): except Exception: continue for ref in REF.findall(data): - parts = ref.decode().split('/') + parts = ref.decode().split("/") # Longest matching package wins: java.nio.file before java.nio. for cut in range(len(parts) - 1, 1, -1): - cand = '.'.join(parts[:cut]) + cand = ".".join(parts[:cut]) if cand in pkg2mod: found.add(pkg2mod[cand]) break diff --git a/tests/screenshot.py b/tests/screenshot.py index 0b017f1..7b5f6ba 100755 --- a/tests/screenshot.py +++ b/tests/screenshot.py @@ -8,6 +8,7 @@ is less trouble than the workarounds. Usage: cdp-screenshot.py [url-substring] """ + import base64 import json import os @@ -81,8 +82,8 @@ def ws_frames(sock, initial=b""): length = struct.unpack(">Q", bytes(buf[2:10]))[0] offset = 10 need(offset + length) - payload = bytes(buf[offset:offset + length]) - del buf[:offset + length] + payload = bytes(buf[offset : offset + length]) + del buf[: offset + length] yield payload.decode("utf-8", "replace") @@ -91,8 +92,12 @@ def main(): out = sys.argv[2] match = sys.argv[3] if len(sys.argv) > 3 else "" - targets = json.load(urllib.request.urlopen(f"http://localhost:{port}/json/list", timeout=15)) - pages = [t for t in targets if t.get("type") == "page" and match in t.get("url", "")] + targets = json.load( + urllib.request.urlopen(f"http://localhost:{port}/json/list", timeout=15) + ) + pages = [ + t for t in targets if t.get("type") == "page" and match in t.get("url", "") + ] if not pages: print(f"no page target matching {match!r}", file=sys.stderr) return 1 @@ -100,8 +105,12 @@ def main(): path = ws_url.split(f"{port}", 1)[1] sock, rest = ws_connect("localhost", port, path) - ws_send(sock, json.dumps({"id": 1, "method": "Page.captureScreenshot", - "params": {"format": "png"}})) + ws_send( + sock, + json.dumps( + {"id": 1, "method": "Page.captureScreenshot", "params": {"format": "png"}} + ), + ) for text in ws_frames(sock, rest): msg = json.loads(text) if msg.get("id") == 1: diff --git a/tests/summarize.py b/tests/summarize.py index 43f1e48..af25a0e 100644 --- a/tests/summarize.py +++ b/tests/summarize.py @@ -4,6 +4,7 @@ Reads the 'settled-game-N' checkpoints, which are taken after each newly started game has reached a real combat round, and reports the marginal delta. """ + import csv import sys from pathlib import Path @@ -17,16 +18,27 @@ def mb(kb): def main(path): - rows = list(csv.DictReader(Path(path).open())) - checkpoints = [r for r in rows if r["label"].startswith(("settled-game-", "unit-cache", "jvm-start", "final", "round-"))] + with Path(path).open() as fh: + rows = list(csv.DictReader(fh)) + checkpoints = [ + r + for r in rows + if r["label"].startswith( + ("settled-game-", "unit-cache", "jvm-start", "final", "round-") + ) + ] - print(f"{'label':<22} {'games':>5} {'rounds':>7} {'rss MB':>9} {'liveHeap MB':>12} {'meta MB':>8}") + print( + f"{'label':<22} {'games':>5} {'rounds':>7} {'rss MB':>9} {'liveHeap MB':>12} {'meta MB':>8}" + ) print("-" * 68) prev_rss = prev_live = None for r in checkpoints: rss, live = mb(r["rssKb"]), mb(r["liveHeapKb"]) - line = (f"{r['label']:<22} {r['gamesStarted']:>5} {r['totalRounds']:>7} " - f"{rss:>9.0f} {live:>12.0f} {mb(r['metaspaceKb']):>8.0f}") + line = ( + f"{r['label']:<22} {r['gamesStarted']:>5} {r['totalRounds']:>7} " + f"{rss:>9.0f} {live:>12.0f} {mb(r['metaspaceKb']):>8.0f}" + ) if r["label"].startswith("settled-game-") and prev_rss is not None: line += f" (+{rss - prev_rss:.0f} rss, +{live - prev_live:.0f} live)" print(line) @@ -41,10 +53,14 @@ def main(path): d_rss = (mb(last["rssKb"]) - mb(first["rssKb"])) / n d_live = (mb(last["liveHeapKb"]) - mb(first["liveHeapKb"])) / n print() - print(f"marginal cost per additional game, games {first['gamesStarted']}..{last['gamesStarted']}:") + print( + f"marginal cost per additional game, games {first['gamesStarted']}..{last['gamesStarted']}:" + ) print(f" RSS ~{d_rss:.0f} MB/game") print(f" live heap ~{d_live:.0f} MB/game") - print(f" (one-time shared unit cache is paid once, see unit-cache-loaded row)") + print( + " (one-time shared unit cache is paid once, see unit-cache-loaded row)" + ) if __name__ == "__main__":