Some checks failed
CI / skinny-install (aco) (push) Successful in 1m12s
CI / skinny-install (api) (push) Successful in 30s
CI / skinny-install (bcda) (push) Successful in 36s
CI / skinny-install (bib) (push) Successful in 35s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 41s
CI / skinny-install (cms) (push) Successful in 37s
CI / skinny-install (conf) (push) Successful in 38s
CI / skinny-install (opps) (push) Successful in 33s
CI / skinny-install (perf) (push) Successful in 38s
CI / skinny-install (pfs) (push) Successful in 38s
CI / skinny-install (rex) (push) Successful in 34s
Deploy / build-scan-report (push) Failing after 46s
Infra CI / notebooks (push) Failing after 25s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Failing after 16s
CI / lint-test (push) Failing after 11m2s
Infra CI / mc (push) Successful in 21s
Infra CI / api (push) Successful in 29s
Package Supply Chain / pkg-supply-chain (push) Failing after 41s
Mail: Maddy on DO (corwins.media+Resend, fhirworx.io+Postmark), touchless/stateless/idempotent. Gitea SMTP via env_file. CMS inbox at cmsupdates@mail.fhirworx.io with IMAP→bib poller. Bib: regulations.gov v4 client, Federal Register discovery, 164K comment backfill (running), IMAP email ingest, Zotero sync routing. PRISMA: altcha PoW solver, CrossRef DOI resolution, 83/129 PDFs. Zotero: schema parity, ops module, CLI, fail-fast guard. CI: docs.Dockerfile COPY glob fix (tracks #341). Infra: Gitea+marimo fhirworx themes, IOM/OIG modules.
332 lines
14 KiB
Python
332 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate `theme/frontend/src/lucide-shim.tsx`.
|
|
|
|
Scans a marimo source tree for every `import {…} from 'lucide-react'`
|
|
and emits a TSX shim that re-exports Tabler icons under each Lucide
|
|
name. Runs outside the Docker build (the shim is committed), so you
|
|
only rerun this when bumping the pinned marimo version or when an
|
|
upstream change introduces new lucide icons.
|
|
|
|
Usage:
|
|
python3 gen-lucide-shim.py [MARIMO_SRC_ROOT]
|
|
|
|
Default source root is `infra/marimo/src` relative to the stack repo root.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Lucide-name → Tabler export name. Populated with the divergent cases;
|
|
# straight-rename names are handled by `transform()` below.
|
|
SPECIAL: dict[str, str] = {
|
|
"Play": "IconPlayerPlay",
|
|
"PlayIcon": "IconPlayerPlay",
|
|
"PlayCircleIcon": "IconPlayerPlay",
|
|
"PlaySquareIcon": "IconPlayerPlay",
|
|
"CirclePlayIcon": "IconPlayerPlay",
|
|
"Pause": "IconPlayerPause",
|
|
"StopCircleIcon": "IconPlayerStop",
|
|
"SkipForwardIcon": "IconPlayerSkipForward",
|
|
"FastForwardIcon": "IconPlayerTrackNext",
|
|
"Loader2": "IconLoader2",
|
|
"Loader2Icon": "IconLoader2",
|
|
"LoaderCircle": "IconLoader",
|
|
"TriangleAlert": "IconAlertTriangle",
|
|
"ChartColumn": "IconChartBar",
|
|
"ChartColumnIcon": "IconChartBar",
|
|
"ChartColumnStacked": "IconChartBarOff",
|
|
"ChartNoAxesColumn": "IconChartBar",
|
|
"HatGlasses": "IconMoodHappy",
|
|
"RulerDimensionLine": "IconRuler",
|
|
"FunnelPlusIcon": "IconFilterPlus",
|
|
"FilterX": "IconFilterOff",
|
|
"AreaChartIcon": "IconChartArea",
|
|
"BarChartIcon": "IconChartBar",
|
|
"BarChart2Icon": "IconChartBar",
|
|
"BarChartBigIcon": "IconChartBar",
|
|
"LineChartIcon": "IconChartLine",
|
|
"PieChartIcon": "IconChartPie",
|
|
"ChartPieIcon": "IconChartPie",
|
|
"ChartScatterIcon": "IconChartDots",
|
|
"ChartSplineIcon": "IconChartLine",
|
|
"Grid3x3Icon": "IconGrid3x3",
|
|
"Columns2Icon": "IconColumns2",
|
|
"Edit3Icon": "IconEdit",
|
|
"Code2Icon": "IconCode",
|
|
"FileAudio2Icon": "IconFileMusic",
|
|
"FileAudioIcon": "IconFileMusic",
|
|
"FileVideoCameraIcon": "IconFileTypography",
|
|
"SquareMIcon": "IconSquareLetterM",
|
|
"SquareCodeIcon": "IconCode",
|
|
"SquareDashedBottomCodeIcon": "IconSquareDashed",
|
|
"CircleHelpIcon": "IconHelpCircle",
|
|
"HelpCircleIcon": "IconHelpCircle",
|
|
"MessageCircleQuestionIcon": "IconMessageCircleQuestion",
|
|
"AlertOctagonIcon": "IconAlertOctagon",
|
|
"CheckCircle2Icon": "IconCircleCheck",
|
|
"CheckCircleIcon": "IconCircleCheck",
|
|
"CheckSquareIcon": "IconSquareCheck",
|
|
"XCircle": "IconCircleX",
|
|
"XCircleIcon": "IconCircleX",
|
|
"DatabaseZap": "IconDatabase",
|
|
"DatabaseZapIcon": "IconDatabase",
|
|
"ArrowDownWideNarrowIcon": "IconArrowsSort",
|
|
"ArrowUpWideNarrowIcon": "IconArrowsSort",
|
|
"ArrowUpNarrowWideIcon": "IconArrowsSort",
|
|
"ChevronsUpDown": "IconChevronUpDown",
|
|
"ChevronsUpDownIcon": "IconChevronUpDown",
|
|
"ChevronsDownUpIcon": "IconChevronDownUp",
|
|
"CircleEllipsis": "IconDotsCircleHorizontal",
|
|
"MousePointerSquareDashedIcon": "IconPointerShare",
|
|
"SquareMousePointerIcon": "IconPointer",
|
|
"SquareArrowOutUpRightIcon": "IconExternalLink",
|
|
"SquareFunction": "IconMathFunction",
|
|
"SquareFunctionIcon": "IconMathFunction",
|
|
"FunctionSquareIcon": "IconMathFunction",
|
|
"SquareEqualIcon": "IconEqual",
|
|
"Share2Icon": "IconShare",
|
|
"Undo2Icon": "IconArrowBackUp",
|
|
"Trash2Icon": "IconTrash",
|
|
"BetweenHorizontalStartIcon": "IconLayoutRows",
|
|
"BugPlayIcon": "IconBug",
|
|
"FolderArchiveIcon": "IconFolder",
|
|
"FolderCog2": "IconFolderCog",
|
|
"TerminalSquareIcon": "IconTerminal2",
|
|
"HardDriveDownloadIcon":"IconDeviceSdCard",
|
|
"DownloadCloudIcon": "IconCloudDownload",
|
|
"HardDrive": "IconDeviceSdCard",
|
|
"HardDriveIcon": "IconDeviceSdCard",
|
|
"MemoryStickIcon": "IconDeviceSdCard",
|
|
"MicrochipIcon": "IconCpu",
|
|
"BrickWallIcon": "IconWall",
|
|
"ViewIcon": "IconEye",
|
|
"BotMessageSquareIcon": "IconRobot",
|
|
"BotIcon": "IconRobot",
|
|
"TextCursorInputIcon": "IconCursorText",
|
|
"TextSelectionIcon": "IconTextCaption",
|
|
"TextSearchIcon": "IconZoomCode",
|
|
"TextIcon": "IconTextCaption",
|
|
"TypeIcon": "IconTypography",
|
|
"CaseSensitiveIcon": "IconLetterCase",
|
|
"WholeWordIcon": "IconAlphabetLatin",
|
|
"WrapTextIcon": "IconTextWrap",
|
|
"SendHorizontalIcon": "IconSend",
|
|
"NotebookText": "IconNotebook",
|
|
"NotebookPenIcon": "IconNotebook",
|
|
"BookMarkedIcon": "IconBookmark",
|
|
"BookTextIcon": "IconBook",
|
|
"KeyRoundIcon": "IconKey",
|
|
"PartyPopperIcon": "IconConfetti",
|
|
"PaintRollerIcon": "IconBrush",
|
|
"PackageCheckIcon": "IconPackage",
|
|
"PackageXIcon": "IconPackageOff",
|
|
"YoutubeIcon": "IconBrandYoutube",
|
|
"GithubIcon": "IconBrandGithub",
|
|
"Info": "IconInfoCircle",
|
|
"InfoIcon": "IconInfoCircle",
|
|
"CopyMinusIcon": "IconCopy",
|
|
"CopySlashIcon": "IconCopyOff",
|
|
"SearchCheck": "IconZoomCheck",
|
|
"ChevronDownCircleIcon": "IconCircleChevronDown",
|
|
"ChevronRightCircleIcon": "IconCircleChevronRight",
|
|
"MoreHorizontal": "IconDots",
|
|
"MoreHorizontalIcon": "IconDots",
|
|
"MoreVerticalIcon": "IconDotsVertical",
|
|
"RefreshCcw": "IconRefresh",
|
|
"RefreshCcwIcon": "IconRefresh",
|
|
"RefreshCwIcon": "IconRefresh",
|
|
"RotateCcwIcon": "IconRotate",
|
|
"RotateCwIcon": "IconRotateClockwise",
|
|
"AtSignIcon": "IconAt",
|
|
"SigmaIcon": "IconSum",
|
|
"BaselineIcon": "IconBaselineDensitySmall",
|
|
"AlignCenterVerticalIcon": "IconAlignCenter",
|
|
"AlignStartVerticalIcon": "IconAlignLeft",
|
|
"AlignEndVerticalIcon": "IconAlignRight",
|
|
"AlignHorizontalSpaceAroundIcon": "IconAlignJustified",
|
|
"AlignJustifyIcon": "IconAlignJustified",
|
|
"ZapIcon": "IconBolt",
|
|
"ZapOffIcon": "IconBoltOff",
|
|
"ListFilterIcon": "IconFilter",
|
|
"ListFilterPlusIcon": "IconFilterPlus",
|
|
"CircleHelp": "IconHelpCircle",
|
|
"CalendarClockIcon": "IconCalendarTime",
|
|
"ClipboardPasteIcon": "IconClipboardText",
|
|
"GraduationCapIcon": "IconSchool",
|
|
"ExpandIcon": "IconArrowsMaximize",
|
|
"LayersIcon": "IconStack",
|
|
"LayoutTemplateIcon": "IconLayoutBoard",
|
|
"ListOrderedIcon": "IconListNumbers",
|
|
"GroupIcon": "IconUsersGroup",
|
|
"GlobeIcon": "IconWorld",
|
|
"WorkflowIcon": "IconSitemap",
|
|
"Table2Icon": "IconTable",
|
|
"MessagesSquareIcon": "IconMessages",
|
|
"MenuIcon": "IconMenu2",
|
|
"MonitorIcon": "IconDeviceDesktop",
|
|
"PlusCircleIcon": "IconCirclePlus",
|
|
"PlusSquareIcon": "IconSquarePlus",
|
|
"FilePenIcon": "IconFilePencil",
|
|
"FilePlus2Icon": "IconFilePlus",
|
|
"FileImageIcon": "IconPhoto",
|
|
"FileJsonIcon": "IconJson",
|
|
"FileVideoIcon": "IconVideo",
|
|
"ImageIcon": "IconPhoto",
|
|
"PanelLeftIcon": "IconLayoutSidebar",
|
|
"PanelRightIcon": "IconLayoutSidebarRight",
|
|
"PowerOffIcon": "IconPower",
|
|
"PowerSquareIcon": "IconPower",
|
|
"SaveIcon": "IconDeviceFloppy",
|
|
"ShuffleIcon": "IconArrowsShuffle",
|
|
"GridIcon": "IconGrid3x3",
|
|
"CombineIcon": "IconStackPush",
|
|
"ArrowDownToLineIcon": "IconArrowBarToDown",
|
|
"ArrowRightFromLineIcon": "IconArrowBarRight",
|
|
"ArrowRightToLineIcon": "IconArrowBarToRight",
|
|
"ArrowRightSquareIcon": "IconSquareArrowRight",
|
|
"ArrowUpDownIcon": "IconArrowsUpDown",
|
|
"ArrowUpToLineIcon": "IconArrowBarToUp",
|
|
"SquareStack": "IconStack",
|
|
"WrenchIcon": "IconTool",
|
|
"SquareIcon": "IconSquare",
|
|
"CornerLeftUp": "IconCornerLeftUp",
|
|
"GripHorizontal": "IconGripHorizontal",
|
|
"GripHorizontalIcon": "IconGripHorizontal",
|
|
"GripVerticalIcon": "IconGripVertical",
|
|
"CrosshairIcon": "IconCrosshair",
|
|
"CommandIcon": "IconCommand",
|
|
"PinIcon": "IconPin",
|
|
"PinOffIcon": "IconPinnedOff",
|
|
"DiamondPlusIcon": "IconDiamondPlus",
|
|
"BinaryIcon": "IconBinary",
|
|
"BoxIcon": "IconBox",
|
|
"BracesIcon": "IconBraces",
|
|
"BracketsIcon": "IconBrackets",
|
|
"BrainIcon": "IconBrain",
|
|
"ActivityIcon": "IconActivity",
|
|
"Cog": "IconSettings",
|
|
"CogIcon": "IconSettings",
|
|
# Names that don't exist as-is in @tabler/icons-react v3.41.
|
|
"BookOpenIcon": "IconBook",
|
|
"BookPlusIcon": "IconBook",
|
|
"ChevronsUpDown": "IconSelector",
|
|
"ChevronsUpDownIcon": "IconSelector",
|
|
"ChevronsDownUpIcon": "IconSelector",
|
|
"CurlyBracesIcon": "IconBraces",
|
|
"DiamondPlusIcon": "IconDiamond",
|
|
"EllipsisIcon": "IconDots",
|
|
"FlaskConicalIcon": "IconFlask",
|
|
"FolderTreeIcon": "IconFolders",
|
|
"OrbitIcon": "IconCircleDashed",
|
|
"ScrollIcon": "IconArticle",
|
|
"ScrollTextIcon": "IconArticle",
|
|
}
|
|
|
|
|
|
_FALLBACK = "IconQuestionMark"
|
|
|
|
|
|
def load_tabler_exports() -> set[str] | None:
|
|
"""Return the set of Tabler icon names if the authoritative list is on
|
|
disk (generated by probing @tabler/icons-react in a container). If it's
|
|
missing, return None — generator emits without validation and build-time
|
|
errors surface the unknowns."""
|
|
cand = Path(__file__).resolve().parent / "tabler-icons.txt"
|
|
if not cand.is_file():
|
|
cand = Path("/tmp/tabler-icons.txt")
|
|
if not cand.is_file():
|
|
return None
|
|
return {line.strip() for line in cand.read_text().splitlines() if line.strip()}
|
|
|
|
|
|
_TABLER: set[str] | None = load_tabler_exports()
|
|
|
|
|
|
def transform(name: str) -> str:
|
|
"""Map a Lucide export name to its Tabler equivalent, falling back to
|
|
IconQuestionMark when the intended target doesn't actually exist in the
|
|
installed @tabler/icons-react version."""
|
|
if name in SPECIAL:
|
|
target = SPECIAL[name]
|
|
else:
|
|
base = name[:-4] if name.endswith("Icon") else name
|
|
target = "Icon" + base
|
|
if _TABLER is not None and target not in _TABLER:
|
|
return _FALLBACK
|
|
return target
|
|
|
|
|
|
def extract(src_root: Path) -> set[str]:
|
|
"""Collect every name marimo imports or re-exports from lucide-react."""
|
|
names: set[str] = set()
|
|
# Covers both `import { X, Y as Z } from "lucide-react"` and
|
|
# `export { X as A } from "lucide-react"` (0.23.1 added re-exports).
|
|
pattern = re.compile(
|
|
r'(?:import|export)\s*(?:type\s*)?\{([^}]+)\}\s*from\s*["\']lucide-react["\']'
|
|
)
|
|
for root, _, files in os.walk(src_root):
|
|
for fn in files:
|
|
if not fn.endswith((".ts", ".tsx")):
|
|
continue
|
|
text = (Path(root) / fn).read_text()
|
|
for match in pattern.finditer(text):
|
|
for token in match.group(1).split(","):
|
|
token = token.strip().removeprefix("type ").strip()
|
|
token = token.split(" as ")[0].strip()
|
|
if token and re.match(r"^[A-Z]", token):
|
|
names.add(token)
|
|
return names
|
|
|
|
|
|
def render(names: set[str]) -> str:
|
|
TYPE_ONLY = {"LucideIcon", "LucideProps"}
|
|
sorted_names = sorted(n for n in names if n not in TYPE_ONLY)
|
|
# De-dup tabler names (many lucide aliases resolve to the same icon).
|
|
tabler_set = sorted({transform(n) for n in sorted_names})
|
|
|
|
header = [
|
|
"// Auto-generated: Tabler Icons as a drop-in for lucide-react.",
|
|
"// Build-time replacement; do not edit by hand — see gen-lucide-shim.py",
|
|
"import type { ComponentType, SVGProps } from 'react';",
|
|
"import {",
|
|
]
|
|
header.extend(f" {name}," for name in tabler_set)
|
|
header.extend([
|
|
" type IconProps,",
|
|
"} from '@tabler/icons-react';",
|
|
"",
|
|
"// Prop shapes mirror lucide's public types so Omit<LucideProps, 'ref'>",
|
|
"// and similar patterns in upstream marimo code keep typechecking.",
|
|
"export type LucideProps = SVGProps<SVGSVGElement> & {",
|
|
" size?: number | string;",
|
|
" absoluteStrokeWidth?: boolean;",
|
|
"};",
|
|
"export type LucideIcon = ComponentType<LucideProps & IconProps>;",
|
|
"",
|
|
])
|
|
body = [
|
|
f"export const {n}: LucideIcon = {transform(n)} as LucideIcon;"
|
|
for n in sorted_names
|
|
]
|
|
return "\n".join(header + body) + "\n"
|
|
|
|
|
|
def main() -> None:
|
|
default = Path(__file__).resolve().parents[2] / "src"
|
|
src = Path(sys.argv[1]) if len(sys.argv) > 1 else default
|
|
if not src.is_dir():
|
|
raise SystemExit(f"marimo source not found: {src}")
|
|
names = extract(src / "frontend" / "src")
|
|
out = Path(__file__).resolve().parents[1] / "frontend" / "src" / "lucide-shim.tsx"
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text(render(names))
|
|
print(f"wrote {out} ({len(names)} names)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|