Reworked icons generation

This commit is contained in:
2025-11-10 13:49:17 +01:00
parent 5cb628099a
commit 3d46e092aa
16 changed files with 28672 additions and 86923 deletions

View File

@@ -14,11 +14,12 @@ from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.BaseControl import BaseControl
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.icons.fluent import icon_panel_left_expand20_regular as left_drawer_icon
from myfasthtml.icons.fluent import icon_panel_right_expand20_regular as right_drawer_icon
from myfasthtml.icons.fluent import panel_left_expand20_regular as left_drawer_icon
from myfasthtml.icons.fluent_p2 import panel_right_expand20_regular as right_drawer_icon
logger = logging.getLogger("LayoutControl")
@dataclass
class LayoutState:
left_drawer_open: bool = True

View File

@@ -17,5 +17,15 @@ Update the root folder in `update_icons.py` to point to the root folder of the i
##
```sh
python update_icons.py
```
python manage_icons.py --help
```
To list
```sh
python manage_icons.py list
```
To generate icons
```sh
python manage_icons.py generate --no-dry-run --suppress-suffix
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,150 @@
import os
from pathlib import Path
import typer
ROOT_FOLDER = "/home/kodjo/Dev/MyDocManager/src/frontend/node_modules/@sicons"
MAX_SIZE = 2000000
import re
def pascal_to_snake(name: str) -> str:
"""Convert a PascalCase or CamelCase string to snake_case."""
# Insert underscore before capital letters (except the first one)
s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
# Handle consecutive capital letters (like 'HTTPServer' -> 'http_server')
s2 = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1)
return s2.lower()
app = typer.Typer()
def list_sources(source_folder: str):
return os.listdir(source_folder)
def list_icons_from_source(source_folder: str, source: str):
res = []
for f in os.listdir(f"{source_folder}/{source}"):
if f.endswith(".svg"):
res.append(f)
return res
def read_content(source_folder: str, source: str, file_name: str):
with open(f"{source_folder}/{source}/{file_name}", "r") as f:
return f.read().strip()
def get_dir_size(path: str | Path) -> int:
p = Path(path)
if p.is_file():
return p.stat().st_size
elif p.is_dir():
return sum(f.stat().st_size for f in p.rglob('*') if f.is_file())
else:
raise FileNotFoundError(f"Path not found: {path}")
def sizeof_fmt(num, suffix="B"):
for unit in ["", "K", "M", "G", "T"]:
if abs(num) < 1024.0:
return f"{num:3.1f}{unit}{suffix}"
num /= 1024.0
return f"{num:.1f}P{suffix}"
def init_buffer(source_folder: str, source: str):
buffer = ""
readme_file_path = f"{source_folder}/{source}/README.md"
if os.path.exists(readme_file_path):
with open(readme_file_path, "r") as f_readme:
for line in f_readme:
if line.startswith("#"):
buffer += line
else:
buffer += f"# {line}"
buffer += "\n\n"
buffer += "from fastcore.basics import NotStr\n\n"
return buffer
def flush(dry_run, suppress_suffix, source_folder: str, target_folder: str, buffer: str, size: int, part: int, source: str):
suffix = '' if suppress_suffix else f"_test"
outfile = f"{source}{suffix}.py" if part == 0 else f"{source}_p{part}{suffix}.py"
if not dry_run:
output_path = f"{target_folder}/{outfile}" if part == 0 else f"{target_folder}/{outfile}"
with open(output_path, "w") as f:
f.write(buffer)
typer.echo(f" Generated {source} as {outfile} ({sizeof_fmt(size)}, max={sizeof_fmt(MAX_SIZE)})")
return init_buffer(source_folder, source), 0, part + 1
@app.command("list")
def list_icons(
source: str = typer.Argument(None, help="The source file to list icons from"),
source_folder: str = typer.Option(ROOT_FOLDER, help="The source folder containing icons"),
count: bool = typer.Option(False, help="Counts the number of items"),
size: bool = typer.Option(False, help="Gets the size of the items"),
):
res = []
if source:
res.extend(list_icons_from_source(source_folder, source))
else:
res.extend(list_sources(source_folder))
if count:
typer.echo(len(res))
return
if size:
path = f"{source_folder}/{source}" if source else f"{source_folder}"
size = get_dir_size(path)
typer.echo(sizeof_fmt(size))
return
for r in res:
typer.echo(r)
@app.command("generate")
def generate_icons(
source: str = typer.Argument(None, help="The source file to list icons from"),
source_folder: str = typer.Option(ROOT_FOLDER, help="The source folder containing icons"),
target_folder: str = typer.Option(".", help="The folder where to create the python files."),
top: int = typer.Option(0, help="The number of top items to generate"),
dry_run: bool = typer.Option(True, help="Does not generate the icons"),
suppress_suffix: bool = typer.Option(False, help="Does not add the suffix to the icon names"),
):
sources = [source] if source else list_sources(source_folder)
for current_source in sources:
typer.echo(f"Generating icons for {current_source}")
buffer = init_buffer(source_folder, current_source)
size = 0
part = 0
for index, svg_file in enumerate(list_icons_from_source(source_folder, current_source)):
if 0 < top <= index:
break
icon_name = os.path.splitext(os.path.basename(svg_file))[0]
svg_content = read_content(source_folder, current_source, svg_file)
svg_content = svg_content.replace("<svg ", f'<svg name="{current_source}-{icon_name}" ').replace("\n", "")
content = f"{pascal_to_snake(icon_name)} = NotStr('''{svg_content}''')"
buffer += f"{content}\n"
size += len(content)
if size > MAX_SIZE:
buffer, size, part = flush(dry_run, suppress_suffix, source_folder, target_folder, buffer, size, part,
current_source)
flush(dry_run, suppress_suffix, source_folder, target_folder, buffer, size, part, current_source)
if __name__ == "__main__":
app()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff