Files
MyFastHtml/src/myfasthtml/icons/manage_icons.py

151 lines
4.8 KiB
Python

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()