FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

#!/usr/bin/env python3 """Regenerate the bundled Lucide icon data and the ``IconName`` literal. Usage: python scripts/generate-icons.py --tag 1.47.0 python scripts/generate-icons.py --source /path/to/lucide/icons --version 1.47.0 With ``--tag`` the script downloads the release archive from GitHub into a temporary directory. With ``--source`` it reads an already extracted ``icons/`` directory (the folder holding ``heart.svg``, ``heart.json``, and so on). Outputs (both checked in): - ``src/pythonnative/icons/_lucide.json``: ``{"version", "license", "icons": {name: [shape, ...]}}`` where each shape is either a path string or a small dict for the other SVG basic shapes, all in the 24 by 24 Lucide grid. - ``src/pythonnative/icons/_names.py``: the ``IconName`` ``Literal`` and ``ICON_NAMES`` tuple used for editor completion and validation. Lucide is ISC licensed; the license text is copied into ``src/pythonnative/icons/LICENSE-lucide.txt``. """ from __future__ import annotations import argparse import io import json import re import sys import tarfile import tempfile import urllib.request import xml.etree.ElementTree as ET from pathlib import Path from typing import Any, Dict, List ROOT = Path(__file__).resolve().parents[1] OUT_DIR = ROOT / "src" / "pythonnative" / "icons" SVG_NS = "{http://www.w3.org/2000/svg}" NUMERIC = ("cx", "cy", "r", "rx", "ry", "x", "y", "width", "height", "x1", "y1", "x2", "y2") def _number(text: str) -> float: value = float(text) return int(value) if value.is_integer() else value def convert(svg_text: str) -> List[Any]: """Return the shape list for one Lucide SVG document.""" root = ET.fromstring(svg_text) shapes: List[Any] = [] for element in root: tag = element.tag.replace(SVG_NS, "") if tag == "path": d = element.attrib.get("d", "").strip() if d: shapes.append(re.sub(r"\s+", " ", d)) elif tag in ("circle", "ellipse", "rect", "line"): shape: Dict[str, Any] = {"kind": tag} for key in NUMERIC: if key in element.attrib: shape[key] = _number(element.attrib[key]) shapes.append(shape) elif tag in ("polyline", "polygon"): shapes.append({"kind": tag, "points": re.sub(r"\s+", " ", element.attrib.get("points", "").strip())}) else: raise ValueError(f"unsupported element ") return shapes def load_icons(source: Path) -> Dict[str, List[Any]]: """Read every ``*.svg`` under ``source`` into a name -> shapes mapping.""" icons: Dict[str, List[Any]] = {} for path in sorted(source.glob("*.svg")): name = path.stem if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", name): print(f"skipping oddly named icon {name!r}", file=sys.stderr) continue icons[name] = convert(path.read_text(encoding="utf-8")) if not icons: raise SystemExit(f"no icons found under {source}") return icons def download(tag: str, destination: Path) -> tuple[Path, str]: """Fetch the Lucide release archive for ``tag``; return (icons dir, license).""" url = f"https://github.com/lucide-icons/lucide/archive/refs/tags/{tag}.tar.gz" print(f"downloading {url}", file=sys.stderr) with urllib.request.urlopen(url) as response: # noqa: S310 - fixed GitHub host data = response.read() with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive: members = [m for m in archive.getmembers() if "/icons/" in m.name or m.name.endswith("/LICENSE")] archive.extractall(destination, members=members) # noqa: S202 - trusted release archive top = next(destination.iterdir()) return top / "icons", (top / "LICENSE").read_text(encoding="utf-8") def write_outputs(icons: Dict[str, List[Any]], version: str, license_text: str | None) -> None: """Write the JSON data, the names module, and the license.""" OUT_DIR.mkdir(parents=True, exist_ok=True) payload = {"version": version, "license": "ISC", "source": "https://lucide.dev", "icons": icons} (OUT_DIR / "_lucide.json").write_text( json.dumps(payload, separators=(",", ":"), sort_keys=True, ensure_ascii=True) + "\n", encoding="utf-8" ) names = sorted(icons) lines = [ '"""Generated by scripts/generate-icons.py. Do not edit by hand."""', "", "# ruff: noqa: E501", "# fmt: off", "from typing import Literal, Tuple", "", f'LUCIDE_VERSION = "{version}"', "", "IconName = Literal[", *(f' "{name}",' for name in names), "]", '"""Every icon name bundled with PythonNative (Lucide).', "", "Used for editor completion and type checking of ``pn.Icon(name)``.", '"""', "", "ICON_NAMES: Tuple[str, ...] = (", *(f' "{name}",' for name in names), ")", "", ] (OUT_DIR / "_names.py").write_text("\n".join(lines), encoding="utf-8") if license_text: (OUT_DIR / "LICENSE-lucide.txt").write_text(license_text, encoding="utf-8") print(f"wrote {len(names)} icons (Lucide {version}) to {OUT_DIR}", file=sys.stderr) def main(argv: List[str]) -> int: """Download the icon set and regenerate the packaged data files.""" parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--tag", help="Lucide release tag to download (e.g. 1.47.0)") parser.add_argument("--source", type=Path, help="Extracted Lucide icons/ directory") parser.add_argument("--version", help="Version string recorded in the output (defaults to --tag)") parser.add_argument("--license", type=Path, help="Path to the Lucide LICENSE file when using --source") args = parser.parse_args(argv) if not args.tag and not args.source: parser.error("one of --tag or --source is required") if args.source: icons = load_icons(args.source) version = args.version or args.tag or "unknown" license_text = args.license.read_text(encoding="utf-8") if args.license else None write_outputs(icons, version, license_text) return 0 with tempfile.TemporaryDirectory(prefix="lucide-") as folder: icons_dir, license_text = download(args.tag, Path(folder)) write_outputs(load_icons(icons_dir), args.version or args.tag, license_text) return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))

Back | FazBrowse Home | New Git URL