testsAndMisc/python_pkg/app_icons/cli.py
Krzysztof kuhy Rudnicki 1c50cfb028
Some checks failed
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Python tests / test (push) Has been cancelled
app_icons: tutor gets a lightbulb; generate --web writes PWA icons
New `lightbulb` glyph (filled bulb over a two-bar screw base) in a new
_glyph_art_extra.py module, registered for ~/src/tutor/app. `generate
--web` overwrites a Flutter app's web/favicon.png and web/icons/Icon-*.png
from the same registry entry; the maskable variants reuse the full render
because SAFE_BOX fits inside the 40% maskable safe circle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YADHKXzZhhePzQ2rNoXFcK
2026-10-12 17:18:05 +02:00

216 lines
6.2 KiB
Python

"""Command-line entry point for the shared app-icon generator.
Usage:
PYTHONPATH=~/src/testsAndMisc python -m python_pkg.app_icons list
PYTHONPATH=~/src/testsAndMisc python -m python_pkg.app_icons preview -o sheet.png
PYTHONPATH=~/src/testsAndMisc python -m python_pkg.app_icons generate --app todo
"""
from __future__ import annotations
import argparse
from pathlib import Path
import sys
from python_pkg.app_icons import apps, generate, glyphs, preview, render, style
def _emit(message: str) -> None:
"""Write a line to stdout.
Parameters:
message (str): Line to write, without a trailing newline.
"""
sys.stdout.write(f"{message}\n")
def _add_app_argument(parser: argparse.ArgumentParser) -> None:
"""Attach the repeatable ``--app`` selector to a subparser.
Parameters:
parser (argparse.ArgumentParser): Subparser to extend.
"""
parser.add_argument(
"--app",
action="append",
dest="app_keys",
choices=sorted(apps.APPS),
help="app to act on; repeatable. Defaults to every registered app.",
)
def build_parser() -> argparse.ArgumentParser:
"""Construct the argument parser.
Returns:
argparse.ArgumentParser: Parser with the ``list``, ``preview`` and
``generate`` subcommands.
"""
parser = argparse.ArgumentParser(
prog="python -m python_pkg.app_icons",
description="Generate on-style launcher icons for kuhy's Flutter apps.",
)
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser("list", help="show registered apps and available glyphs")
preview_parser = subparsers.add_parser(
"preview", help="render a contact sheet for visual review"
)
_add_app_argument(preview_parser)
preview_parser.add_argument(
"-o",
"--output",
type=Path,
default=Path("icon_review_sheet.png"),
help="where to write the contact sheet",
)
generate_parser = subparsers.add_parser(
"generate", help="write icon assets into the app repositories"
)
_add_app_argument(generate_parser)
generate_parser.add_argument(
"--android",
action="store_true",
help="also run flutter_launcher_icons to rebuild the Android mipmaps",
)
generate_parser.add_argument(
"--web",
action="store_true",
help="also overwrite favicon + PWA icons in the app's web/ directory",
)
generate_parser.add_argument(
"--linux-out",
type=Path,
default=None,
help=(
"directory to write hicolor PNGs into for apps with a Linux target; "
"skipped when omitted"
),
)
return parser
def _selected(app_keys: list[str] | None) -> list[str]:
"""Resolve the ``--app`` selection to a concrete ordered list.
Parameters:
app_keys (list[str] | None): Keys given on the command line, if any.
Returns:
list[str]: Keys to act on, in registry order.
"""
if app_keys:
return [key for key in apps.APPS if key in set(app_keys)]
return list(apps.APPS)
def _cmd_list() -> int:
"""Print the app registry and glyph library.
Returns:
int: Process exit status.
"""
_emit("Apps:")
for app in apps.APPS.values():
linux = " +linux" if app.linux else ""
_emit(f" {app.key:16} {app.accent} {app.glyph}{linux} {app.repo}")
_emit("\nGlyphs:")
for glyph in glyphs.GLYPHS.values():
_emit(f" {glyph.name:16} {glyph.description}")
return 0
def _cmd_preview(app_keys: list[str] | None, output: Path) -> int:
"""Render the review contact sheet.
Parameters:
app_keys (list[str] | None): Selection from the command line.
output (Path): Where to write the sheet.
Returns:
int: Process exit status.
"""
sheet = preview.build_contact_sheet(_selected(app_keys), output)
_emit(f"wrote {sheet}")
return 0
def _write_web(app: apps.AppIcon) -> None:
"""Write web icons for one app, skipping apps without a web build.
Parameters:
app (apps.AppIcon): App to act on.
"""
if not app.web_dir.is_dir():
_emit(f" skipped web icons: no {app.web_dir}")
return
icons = generate.write_web_icons(app)
_emit(f" wrote {len(icons)} web icons to {app.web_dir}")
def _cmd_generate(
app_keys: list[str] | None,
*,
android: bool,
web: bool,
linux_out: Path | None,
) -> int:
"""Write icon assets, optionally driving the platform generators.
Parameters:
app_keys (list[str] | None): Selection from the command line.
android (bool): Run flutter_launcher_icons afterwards.
web (bool): Write favicon + PWA icons into apps that have a ``web/`` dir.
linux_out (Path | None): Directory for hicolor PNGs, or None to skip.
Returns:
int: Process exit status.
"""
for key in _selected(app_keys):
app = apps.get_app(key)
written = generate.write_assets(app)
_emit(f"{app.key}: wrote {len(written)} asset files to {app.asset_dir}")
overflow = render.safe_box_overflow(glyphs.get_glyph(app.glyph), app.accent)
if any(overflow):
_emit(
f" warning: glyph exceeds the {style.SAFE_BOX}px safe box "
f"by {overflow[0]}x{overflow[1]}px; a launcher mask may clip it"
)
if linux_out is not None and app.linux:
icons = generate.write_linux_icons(app, linux_out)
_emit(f" wrote {len(icons)} hicolor PNGs to {linux_out}")
if web:
_write_web(app)
if android:
generate.run_flutter_launcher_icons(app)
_emit(" regenerated Android mipmaps")
return 0
def main(argv: list[str] | None = None) -> int:
"""Run the command-line interface.
Parameters:
argv (list[str] | None): Arguments to parse; defaults to ``sys.argv[1:]``.
Returns:
int: Process exit status.
"""
args = build_parser().parse_args(argv)
if args.command == "list":
return _cmd_list()
if args.command == "preview":
return _cmd_preview(args.app_keys, args.output)
return _cmd_generate(
args.app_keys,
android=args.android,
web=args.web,
linux_out=args.linux_out,
)