kubernetes / community

Kubernetes community content
Apache License 2.0
11.98k stars 5.17k forks source link

Iconify Icons #8118

Open acederberg opened 3 days ago

acederberg commented 3 days ago

Describe the issue

Hi everybody!

I wanted to make an iconify icon pack to distribute svg icons for use in mermaid architecture diagrams. I was wondering if it would be worth while to contribute the snippet I wrote to make iconify.json so that distribution may be automated here. I will like to the script once I have pushed it somewhere.

Further, I would like to note that the script is a python script, which should run in the container specify in the icons folder (it is ubuntu:22 I think) so that should be fine so long as I remove any dependencies that do not ship with python by default.

Thanks you everybody!

acederberg commented 3 days ago

/sig docs /sig contributor-experience

acederberg commented 2 days ago

If anybody is reading this, I wrote up a little script to do this:

"""This script should generate ``icons.json`` (e.g. ``iconify``) from existing svgs 
so that icons are available for use with tools [mermaid](https://mermaid.js.org) 
that load the svg from json.
"""

import json
import os
import pathlib
from typing import Any, Iterable, Optional
from xml.dom import minidom

import rich.console
import typer

path_here = pathlib.Path(__file__).resolve().parent.parent
path_icons_json = path_here / "icons.json"
path_svg = path_here / "svg"
DELIM = "_"

def walk(directory: pathlib.Path):
    """Iterate through ``directory`` content."""
    contents = os.listdir(directory)

    for path in map(lambda path: directory / path, contents):

        if os.path.isdir(path):
            yield from walk(directory / path)
            continue

        yield directory / path

def load(path: pathlib.Path) -> str:
    """Load and process the ``svg`` file at ``path``."""

    loaded = minidom.parse(str(path))
    elements = loaded.getElementsByTagName("g")
    element = next((ee for ee in elements if ee.getAttribute("id") == "layer1"), None)

    if element is None:
        console.print(f"Could not find ``layer1`` of ``{path}``.")
        raise typer.Exit(1)

    return element.toxml("utf-8").decode()

def create_name(path: pathlib.Path):
    """Create name from ``path``."""
    head, _ = os.path.splitext(os.path.relpath(path, path_svg))
    pieces = head.split("/")

    # NOTE: Labeled icons are the default. When an icon is unlabeled, just
    #       attach ``unlabeled`` to the end.
    if "labeled" in pieces:
        pieces.remove("labeled")
    elif "unlabeled" in pieces:
        loc = pieces.index("unlabeled")
        pieces[loc], pieces[-1] = pieces[-1], "u"

    # NOTE: These prefixes are not necessary in icon names.
    if "infrastructure_components" in pieces:
        pieces.remove("infrastructure_components")
    elif "control_plane_components" in pieces:
        pieces.remove("control_plane_components")
    elif "resources" in pieces:
        pieces.remove("resources")

    return DELIM.join(pieces).replace("-", DELIM)

def create_alias(name: str) -> str | None:
    """For a short name, create its long alias."""

    split = name.split("-")
    for pos, item in enumerate(split):
        if item in abbr:
            split[pos] = abbr[item]
        elif item == "u":
            split[pos] = "unlabeled"

    alias = DELIM.join(split).replace("-", DELIM)
    if alias == name:
        return None

    return alias

def create_aliases(names: Iterable[str]):
    """Create aliases ``$.aliases``."""

    aliases = {
        alias: {"parent": name}
        for name in names
        if (alias := create_alias(name)) is not None
    }
    return aliases

def create_iconify_icon(path: pathlib.Path) -> dict[str, Any]:
    """Create ``$.icons`` values."""

    # NOTE: Height and width must be 17 to prevent cropping.
    return {"height": 17, "width": 17, "body": load(path)}

def create_icons():
    """Create ``$.icons``."""
    return {create_name(item): create_iconify_icon(item) for item in walk(path_svg)}

def create_iconify_json(include: set[str] = set()):
    """Create ``kubernetes.json``, the iconify icon set."""

    icons = create_icons()
    if include:
        icons = {name: icon for name, icon in create_icons().items() if name in include}

    aliases = create_aliases(icons)
    return {"icons": icons, "prefix": "k8s", "aliases": aliases}

cli = typer.Typer()
console = rich.console.Console()

abbr = {
    "pvc": "persistent_volume-claim",
    "svc": "service",
    "vol": "volume",
    "rb": "role-binding",
    "rs": "replica-set",
    "ing": "ingress",
    "secret": "secret",
    "pv": "persistent-volume",
    "cronjob": "cron-job",
    "sts": "stateful-set",
    "pod": "pod",
    "cm": "config-map",
    "deploy": "deployment",
    "sc": "storage-class",
    "hpa": "horizontal-pod-autoscaler",
    "crd": "custom-resource-definition",
    "quota": "resource-quota",
    "psp": "pod-security-policy",
    "sa": "service-account",
    "role": "role",
    "c-role": "cluster-role",
    "ns": "namespace",
    "node": "node",
    "job": "job",
    "ds": "daemon-set",
    "ep": "endpoint",
    "crb": "cluster-role-binding",
    "limits": "limit-range",
    "control-plane": "control-plane",
    "k-proxy": "kube-proxy",
    "sched": "scheduler",
    "api": "api-server",
    "c-m": "controller-manager",
    "c-c-m": "cloud-controller-manager",
    "kubelet": "kubelet",
    "group": "group",
    "user": "user",
    "netpol": "network-policy",
}

@cli.command("make")
def main(include: list[str] = list(), out: Optional[pathlib.Path] = None):

    iconify = create_iconify_json(set(include))

    if out is None:
        print(json.dumps(iconify, indent=2))
        return

    with open(out, "w") as file:
        json.dump(iconify, file, indent=2)

@cli.command("aliases")
def aliases(include: list[str] = list()):
    names = map(create_name, walk(path_svg))
    if include:
        _include = set(include)
        names = filter(lambda item: item in _include, names)

    aliases = create_aliases(names)
    print(json.dumps(aliases))

@cli.command("names")
def names():
    print(json.dumps(list(map(create_name, walk(path_svg)))))

if __name__ == "__main__":
    cli()

I will write up a version with no extra dependencies for python and make a pull request unless anyone objects.