From 24593472e3c7ea629aa4b3a53d9e692036c612b8 Mon Sep 17 00:00:00 2001 From: zach Date: Mon, 31 Aug 2026 22:33:55 +0200 Subject: [PATCH] chore: format python codeicons --- scripts/codeicons.py | 818 ++++++++++++++++++++----------------------- 1 file changed, 374 insertions(+), 444 deletions(-) diff --git a/scripts/codeicons.py b/scripts/codeicons.py index 4fdd751..e2a183f 100644 --- a/scripts/codeicons.py +++ b/scripts/codeicons.py @@ -8,452 +8,392 @@ from urllib.error import HTTPError from urllib.request import urlopen from xml.etree import ElementTree as ET -# --------------------------------------------------------------------------- -# Configuration -# --------------------------------------------------------------------------- - DEVICON_VERSION = "v2.17.0" ICONS = { - "c": "c", - "cpp": "cplusplus", - "csharp": "csharp", - "python": "python", - "rust": "rust", - "javascript": "javascript", - "typescript": "typescript", - "bash": "bash", - "zsh": "zsh", - "powershell": "powershell", - "cmake": "cmake", - "lua": "lua", - "java": "java", - "kotlin": "kotlin", - "swift": "swift", - "go": "go", - "dart": "dart", - "php": "php", - "ruby": "ruby", - "scala": "scala", - "haskell": "haskell", - "elixir": "elixir", - "erlang": "erlang", - "clojure": "clojure", - "r": "r", - "perl": "perl", - "zig": "zig", - "nim": "nim", - "ocaml": "ocaml", - "fsharp": "fsharp", - "visualbasic": "visualbasic", - "fortran": "fortran", - "crystal": "crystal", - "gleam": "gleam", - "julia": "julia", - "objectivec": "objectivec", - "vala": "vala", - "groovy": "groovy", - "racket": "racket", - "haxe": "haxe", - "purescript": "purescript", - "delphi": "delphi", - "coffeescript": "coffeescript", - "elm": "elm", - "awk": "awk", - "matlab": "matlab", - "solidity": "solidity", - "wasm": "wasm", - "vim": "vim", - "sql": "sqlite", - "json": "json", - "yaml": "yaml", - "xml": "xml", - "html": "html5", - "css": "css3", - "sass": "sass", - "markdown": "markdown", - "docker": "docker", - "latex": "latex", - "graphql": "graphql", + "c": "c", + "cpp": "cplusplus", + "csharp": "csharp", + "python": "python", + "rust": "rust", + "javascript": "javascript", + "typescript": "typescript", + "bash": "bash", + "zsh": "zsh", + "powershell": "powershell", + "cmake": "cmake", + "lua": "lua", + "java": "java", + "kotlin": "kotlin", + "swift": "swift", + "go": "go", + "dart": "dart", + "php": "php", + "ruby": "ruby", + "scala": "scala", + "haskell": "haskell", + "elixir": "elixir", + "erlang": "erlang", + "clojure": "clojure", + "r": "r", + "perl": "perl", + "zig": "zig", + "nim": "nim", + "ocaml": "ocaml", + "fsharp": "fsharp", + "visualbasic": "visualbasic", + "fortran": "fortran", + "crystal": "crystal", + "gleam": "gleam", + "julia": "julia", + "objectivec": "objectivec", + "vala": "vala", + "groovy": "groovy", + "racket": "racket", + "haxe": "haxe", + "purescript": "purescript", + "delphi": "delphi", + "coffeescript": "coffeescript", + "elm": "elm", + "awk": "awk", + "matlab": "matlab", + "solidity": "solidity", + "wasm": "wasm", + "vim": "vim", + "sql": "sqlite", + "json": "json", + "yaml": "yaml", + "xml": "xml", + "html": "html5", + "css": "css3", + "sass": "sass", + "markdown": "markdown", + "docker": "docker", + "latex": "latex", + "graphql": "graphql", } -# Markdown / syntax-highlighter aliases -> Devicon icon name. ALIASES = { - "cpp": "cpp", - "cc": "cpp", - "cxx": "cpp", - - "cs": "csharp", - - "js": "javascript", - "jsx": "javascript", - - "ts": "typescript", - "tsx": "typescript", - - "py": "python", - - "sh": "bash", - "shell": "bash", - - "ps1": "powershell", - - "vb": "visualbasic", - - "objective-c": "objectivec", - "obj-c": "objectivec", - - "groovyscript": "groovy", - - "pascal": "delphi", - - "coffee": "coffeescript", - - "mysql": "sql", - "postgres": "sql", - "postgresql": "sql", - "sqlite": "sql", - - "yml": "yaml", - - "htm": "html", - - "scss": "sass", - - "md": "markdown", - - "dockerfile": "docker", - "docker-compose": "docker", - - "tex": "latex", - - "gql": "graphql", + "cpp": "cpp", + "cc": "cpp", + "cxx": "cpp", + "cs": "csharp", + "js": "javascript", + "jsx": "javascript", + "ts": "typescript", + "tsx": "typescript", + "py": "python", + "sh": "bash", + "shell": "bash", + "ps1": "powershell", + "vb": "visualbasic", + "objective-c": "objectivec", + "obj-c": "objectivec", + "groovyscript": "groovy", + "pascal": "delphi", + "coffee": "coffeescript", + "mysql": "sql", + "postgres": "sql", + "postgresql": "sql", + "sqlite": "sql", + "yml": "yaml", + "htm": "html", + "scss": "sass", + "md": "markdown", + "dockerfile": "docker", + "docker-compose": "docker", + "tex": "latex", + "gql": "graphql", } -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - ICON_VARIANTS = ( - "plain", - "original", + "plain", + "original", ) def fetch_svg(devicon_name: str) -> str: - for variant in ICON_VARIANTS: - url = ( - f"https://raw.githubusercontent.com/devicons/devicon/" - f"{DEVICON_VERSION}/icons/{devicon_name}/" - f"{devicon_name}-{variant}.svg" - ) + for variant in ICON_VARIANTS: + url = ( + f"https://raw.githubusercontent.com/devicons/devicon/" + f"{DEVICON_VERSION}/icons/{devicon_name}/" + f"{devicon_name}-{variant}.svg" + ) - print(f"Fetching {devicon_name} ({variant}): {url}") + print(f"Fetching {devicon_name} ({variant}): {url}") - try: - with urlopen(url) as response: - return response.read().decode("utf-8") - except HTTPError as error: - if error.code != 404: - raise + try: + with urlopen(url) as response: + return response.read().decode("utf-8") + except HTTPError as error: + if error.code != 404: + raise - raise RuntimeError( - f"No usable SVG variant found for '{devicon_name}'." - ) + raise RuntimeError(f"No usable SVG variant found for '{devicon_name}'.") def element_to_path(element) -> str: - """Convert a basic SVG shape element to equivalent path data.""" - tag = element.tag.rsplit("}", 1)[-1] - attrib = element.attrib + """Convert a basic SVG shape element to equivalent path data.""" + tag = element.tag.rsplit("}", 1)[-1] + attrib = element.attrib - if tag == "path": - return attrib.get("d", "") + if tag == "path": + return attrib.get("d", "") - if tag == "circle": - cx = float(attrib["cx"]) - cy = float(attrib["cy"]) - r = float(attrib["r"]) - return ( - f"M {cx - r},{cy} " - f"a {r},{r} 0 1,0 {2 * r},0 " - f"a {r},{r} 0 1,0 {-2 * r},0 Z" - ) + if tag == "circle": + cx = float(attrib["cx"]) + cy = float(attrib["cy"]) + r = float(attrib["r"]) + return ( + f"M {cx - r},{cy} " + f"a {r},{r} 0 1,0 {2 * r},0 " + f"a {r},{r} 0 1,0 {-2 * r},0 Z" + ) - if tag == "ellipse": - cx = float(attrib["cx"]) - cy = float(attrib["cy"]) - rx = float(attrib["rx"]) - ry = float(attrib["ry"]) - return ( - f"M {cx - rx},{cy} " - f"a {rx},{ry} 0 1,0 {2 * rx},0 " - f"a {rx},{ry} 0 1,0 {-2 * rx},0 Z" - ) + if tag == "ellipse": + cx = float(attrib["cx"]) + cy = float(attrib["cy"]) + rx = float(attrib["rx"]) + ry = float(attrib["ry"]) + return ( + f"M {cx - rx},{cy} " + f"a {rx},{ry} 0 1,0 {2 * rx},0 " + f"a {rx},{ry} 0 1,0 {-2 * rx},0 Z" + ) - if tag == "rect": - x = float(attrib["x"]) - y = float(attrib["y"]) - width = float(attrib["width"]) - height = float(attrib["height"]) - return f"M {x},{y} h {width} v {height} h {-width} Z" + if tag == "rect": + x = float(attrib["x"]) + y = float(attrib["y"]) + width = float(attrib["width"]) + height = float(attrib["height"]) + return f"M {x},{y} h {width} v {height} h {-width} Z" - if tag in ("polygon", "polyline"): - points = attrib["points"].split() - commands = [ - f"{float(points[i])},{float(points[i + 1])}" - for i in range(0, len(points) - 1, 2) - ] - data = "M " + " L ".join(commands) - if tag == "polygon": - data += " Z" - return data + if tag in ("polygon", "polyline"): + points = attrib["points"].split() + commands = [ + f"{float(points[i])},{float(points[i + 1])}" + for i in range(0, len(points) - 1, 2) + ] + data = "M " + " L ".join(commands) + if tag == "polygon": + data += " Z" + return data - if tag == "line": - return ( - f"M {float(attrib['x1'])},{float(attrib['y1'])} " - f"L {float(attrib['x2'])},{float(attrib['y2'])}" - ) + if tag == "line": + return ( + f"M {float(attrib['x1'])},{float(attrib['y1'])} " + f"L {float(attrib['x2'])},{float(attrib['y2'])}" + ) - return "" + return "" -# --------------------------------------------------------------------------- -# SVG path data normalization -# -# Devicon path data crams arc flags against the following coordinate -# (e.g. "a28.78 28.78 0 00-2.65-7.58"), which Qt's PathSvg parser handles -# unreliably. Parse every path and re-emit it with canonical spacing. -# --------------------------------------------------------------------------- - PATH_COMMANDS = "MmLlHhVvCcSsQqTtAaZz" _NUMBER = re.compile(r"[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?") def _skip_separators(data: str, i: int) -> int: - while i < len(data) and data[i] in " \t\r\n,": - i += 1 - return i + while i < len(data) and data[i] in " \t\r\n,": + i += 1 + return i def _read_number(data: str, i: int) -> tuple[str, int]: - i = _skip_separators(data, i) - match = _NUMBER.match(data, i) - if not match: - raise ValueError( - f"Expected number in path data at {i}: {data[i:i + 24]!r}" - ) - return match.group(0), match.end() + i = _skip_separators(data, i) + match = _NUMBER.match(data, i) + if not match: + raise ValueError( + f"Expected number in path data at {i}: {data[i : i + 24]!r}" + ) + return match.group(0), match.end() def parse_path(data: str) -> list[tuple[str, list[str]]]: - """Parse SVG path data into (command, parameters) tuples.""" - commands: list[tuple[str, list[str]]] = [] - i, length = 0, len(data) + """Parse SVG path data into (command, parameters) tuples.""" + commands: list[tuple[str, list[str]]] = [] + i, length = 0, len(data) - while i < length: - i = _skip_separators(data, i) - if i >= length: - break + while i < length: + i = _skip_separators(data, i) + if i >= length: + break - command = data[i] - if command not in PATH_COMMANDS: - raise ValueError( - f"Unexpected character {command!r} at {i} in {data!r}" - ) - i += 1 + command = data[i] + if command not in PATH_COMMANDS: + raise ValueError( + f"Unexpected character {command!r} at {i} in {data!r}" + ) + i += 1 - if command in "Zz": - commands.append((command, [])) - continue + if command in "Zz": + commands.append((command, [])) + continue - implicit = command - while i < length: - i = _skip_separators(data, i) - if i >= length or data[i] in PATH_COMMANDS: - break + implicit = command + while i < length: + i = _skip_separators(data, i) + if i >= length or data[i] in PATH_COMMANDS: + break - if implicit in "Mm": - x, i = _read_number(data, i) - y, i = _read_number(data, i) - commands.append((implicit, [x, y])) - # Subsequent coordinate pairs after a moveto are linetos. - implicit = "L" if implicit == "M" else "l" - elif implicit in "LlTt": - x, i = _read_number(data, i) - y, i = _read_number(data, i) - commands.append((implicit, [x, y])) - elif implicit in "Hh": - x, i = _read_number(data, i) - commands.append((implicit, [x])) - elif implicit in "Vv": - y, i = _read_number(data, i) - commands.append((implicit, [y])) - elif implicit in "Cc": - # Cubic bezier: x1 y1 x2 y2 x y - p = [] - for _ in range(6): - value, i = _read_number(data, i) - p.append(value) - commands.append((implicit, p)) - elif implicit in "SsQq": - # Smooth cubic / quadratic: x1 y1 x y - p = [] - for _ in range(4): - value, i = _read_number(data, i) - p.append(value) - commands.append((implicit, p)) - elif implicit in "Aa": - rx, i = _read_number(data, i) - ry, i = _read_number(data, i) - rotation, i = _read_number(data, i) - # Arc flags are single digits, possibly crammed against - # the following coordinate, so read them positionally. - i = _skip_separators(data, i) - large_arc = data[i] - i += 1 - i = _skip_separators(data, i) - sweep = data[i] - i += 1 - if large_arc not in "01" or sweep not in "01": - raise ValueError( - f"Invalid arc flags near {i - 2} in {data!r}" - ) - x, i = _read_number(data, i) - y, i = _read_number(data, i) - commands.append( - (implicit, [rx, ry, rotation, large_arc, sweep, x, y]) - ) - else: - raise ValueError(f"Unhandled path command {implicit!r}") + if implicit in "Mm": + x, i = _read_number(data, i) + y, i = _read_number(data, i) + commands.append((implicit, [x, y])) + implicit = "L" if implicit == "M" else "l" + elif implicit in "LlTt": + x, i = _read_number(data, i) + y, i = _read_number(data, i) + commands.append((implicit, [x, y])) + elif implicit in "Hh": + x, i = _read_number(data, i) + commands.append((implicit, [x])) + elif implicit in "Vv": + y, i = _read_number(data, i) + commands.append((implicit, [y])) + elif implicit in "Cc": + p = [] + for _ in range(6): + value, i = _read_number(data, i) + p.append(value) + commands.append((implicit, p)) + elif implicit in "SsQq": + p = [] + for _ in range(4): + value, i = _read_number(data, i) + p.append(value) + commands.append((implicit, p)) + elif implicit in "Aa": + rx, i = _read_number(data, i) + ry, i = _read_number(data, i) + rotation, i = _read_number(data, i) + i = _skip_separators(data, i) + large_arc = data[i] + i += 1 + i = _skip_separators(data, i) + sweep = data[i] + i += 1 + if large_arc not in "01" or sweep not in "01": + raise ValueError( + f"Invalid arc flags near {i - 2} in {data!r}" + ) + x, i = _read_number(data, i) + y, i = _read_number(data, i) + commands.append( + (implicit, [rx, ry, rotation, large_arc, sweep, x, y]) + ) + else: + raise ValueError(f"Unhandled path command {implicit!r}") - return commands + return commands def normalize_path(data: str) -> str: - """Re-emit path data with canonical spacing (Qt PathSvg friendly).""" - return " ".join( - command if not params else command + " " + " ".join(params) - for command, params in parse_path(data) - ) + """Re-emit path data with canonical spacing (Qt PathSvg friendly).""" + return " ".join( + command if not params else command + " " + " ".join(params) + for command, params in parse_path(data) + ) def extract_paths(svg: str) -> str: - root = ET.fromstring(svg) + root = ET.fromstring(svg) - paths = [] + paths = [] - for element in root.iter(): - path = element_to_path(element) - if not path: - continue + for element in root.iter(): + path = element_to_path(element) + if not path: + continue - normalized = normalize_path(path) + normalized = normalize_path(path) - # Guard against a tokenizer that changes the path it normalizes. - if parse_path(normalized) != parse_path(path): - raise RuntimeError( - "Path normalization round-trip mismatch:\n" - f" in : {path[:80]}\n out: {normalized[:80]}" - ) + if parse_path(normalized) != parse_path(path): + raise RuntimeError( + "Path normalization round-trip mismatch:\n" + f" in : {path[:80]}\n out: {normalized[:80]}" + ) - paths.append(normalized) + paths.append(normalized) - if not paths: - raise RuntimeError("SVG contains no drawable shape elements") + if not paths: + raise RuntimeError("SVG contains no drawable shape elements") - # Multiple path elements are valid SVG path data when concatenated. - return " ".join(paths) + return " ".join(paths) def qml_string(value: str) -> str: - # JSON string escaping is valid for the string syntax we need in QML. - return json.dumps(value, ensure_ascii=False) + return json.dumps(value, ensure_ascii=False) def qml_identifier(name: str) -> str: - name = re.sub(r"[^A-Za-z0-9_]", "_", name) + name = re.sub(r"[^A-Za-z0-9_]", "_", name) - if name and name[0].isdigit(): - name = "_" + name + if name and name[0].isdigit(): + name = "_" + name - return name + return name -# --------------------------------------------------------------------------- -# Generation -# --------------------------------------------------------------------------- - def generate(output: Path) -> None: - icons = {} + icons = {} - for name, devicon_name in ICONS.items(): - svg = fetch_svg(devicon_name) - icons[name] = extract_paths(svg) + for name, devicon_name in ICONS.items(): + svg = fetch_svg(devicon_name) + icons[name] = extract_paths(svg) - output.parent.mkdir(parents=True, exist_ok=True) + output.parent.mkdir(parents=True, exist_ok=True) - with output.open("w", encoding="utf-8") as file: - file.write( - """pragma Singleton + with output.open("w", encoding="utf-8") as file: + file.write( + """pragma Singleton import Quickshell import QtQuick Singleton { """ - ) + ) - # ------------------------------------------------------------------- - # Icon path properties - # ------------------------------------------------------------------- + for icon, path in icons.items(): + identifier = qml_identifier(icon) - for icon, path in icons.items(): - identifier = qml_identifier(icon) + file.write( + f" readonly property string {identifier}: " + f"{qml_string(path)}\n" + ) - file.write( - f" readonly property string {identifier}: " - f"{qml_string(path)}\n" - ) + file.write("\n") - file.write("\n") - - # ------------------------------------------------------------------- - # Language -> icon lookup - # ------------------------------------------------------------------- - - file.write( - """ function path(language) { + file.write( + """ function path(language) { const key = language.toLowerCase() switch (key) { """ - ) + ) - for icon in icons: - identifier = qml_identifier(icon) + for icon in icons: + identifier = qml_identifier(icon) - file.write( - f' case "{icon}":\n' - f" return {identifier}\n" - ) + file.write( + f' case "{icon}":\n return {identifier}\n' + ) - for alias, icon in ALIASES.items(): - if icon not in icons: - raise RuntimeError( - f"Alias '{alias}' points to unavailable icon '{icon}'" - ) + for alias, icon in ALIASES.items(): + if icon not in icons: + raise RuntimeError( + f"Alias '{alias}' points to unavailable icon '{icon}'" + ) - identifier = qml_identifier(icon) + identifier = qml_identifier(icon) - file.write( - f' case "{alias}":\n' - f" return {identifier}\n" - ) + file.write( + f' case "{alias}":\n return {identifier}\n' + ) - file.write( - """ default: + file.write( + """ default: return "" } } @@ -461,112 +401,102 @@ Singleton { function name(language) { switch (language.toLowerCase()) { """ - ) + ) - # Human-readable names can initially be handled here. - # Add/remove these as your codeblock UI evolves. - names = { - "c": "C", - "cpp": "C++", - "csharp": "C#", - "python": "Python", - "rust": "Rust", - "javascript": "JavaScript", - "typescript": "TypeScript", - "qml": "QML", - "bash": "Bash", - "zsh": "Zsh", - "powershell": "PowerShell", - "cmake": "CMake", - "lua": "Lua", - "java": "Java", - "kotlin": "Kotlin", - "swift": "Swift", - "go": "Go", - "dart": "Dart", - "php": "PHP", - "ruby": "Ruby", - "scala": "Scala", - "haskell": "Haskell", - "elixir": "Elixir", - "erlang": "Erlang", - "clojure": "Clojure", - "r": "R", - "perl": "Perl", - "zig": "Zig", - "nim": "Nim", - "ocaml": "OCaml", - "fsharp": "F#", - "visualbasic": "Visual Basic", - "fortran": "Fortran", - "crystal": "Crystal", - "gleam": "Gleam", - "julia": "Julia", - "objectivec": "Objective-C", - "vala": "Vala", - "groovy": "Groovy", - "racket": "Racket", - "haxe": "Haxe", - "purescript": "PureScript", - "delphi": "Delphi", - "coffeescript": "CoffeeScript", - "elm": "Elm", - "awk": "AWK", - "matlab": "MATLAB", - "solidity": "Solidity", - "wasm": "Wasm", - "vim": "Vim", - "sql": "SQL", - "json": "JSON", - "yaml": "YAML", - "xml": "XML", - "html": "HTML", - "css": "CSS", - "sass": "Sass", - "markdown": "Markdown", - "docker": "Docker", - "latex": "LaTeX", - "graphql": "GraphQL", - } + names = { + "c": "C", + "cpp": "C++", + "csharp": "C#", + "python": "Python", + "rust": "Rust", + "javascript": "JavaScript", + "typescript": "TypeScript", + "qml": "QML", + "bash": "Bash", + "zsh": "Zsh", + "powershell": "PowerShell", + "cmake": "CMake", + "lua": "Lua", + "java": "Java", + "kotlin": "Kotlin", + "swift": "Swift", + "go": "Go", + "dart": "Dart", + "php": "PHP", + "ruby": "Ruby", + "scala": "Scala", + "haskell": "Haskell", + "elixir": "Elixir", + "erlang": "Erlang", + "clojure": "Clojure", + "r": "R", + "perl": "Perl", + "zig": "Zig", + "nim": "Nim", + "ocaml": "OCaml", + "fsharp": "F#", + "visualbasic": "Visual Basic", + "fortran": "Fortran", + "crystal": "Crystal", + "gleam": "Gleam", + "julia": "Julia", + "objectivec": "Objective-C", + "vala": "Vala", + "groovy": "Groovy", + "racket": "Racket", + "haxe": "Haxe", + "purescript": "PureScript", + "delphi": "Delphi", + "coffeescript": "CoffeeScript", + "elm": "Elm", + "awk": "AWK", + "matlab": "MATLAB", + "solidity": "Solidity", + "wasm": "Wasm", + "vim": "Vim", + "sql": "SQL", + "json": "JSON", + "yaml": "YAML", + "xml": "XML", + "html": "HTML", + "css": "CSS", + "sass": "Sass", + "markdown": "Markdown", + "docker": "Docker", + "latex": "LaTeX", + "graphql": "GraphQL", + } - for icon in icons: - if icon not in names: - continue + for icon in icons: + if icon not in names: + continue - name = names[icon] + name = names[icon] - file.write( - f' case "{icon}":\n' - f' return "{name}"\n' - ) + file.write(f' case "{icon}":\n return "{name}"\n') - for alias, icon in ALIASES.items(): - if icon not in names: - continue + for alias, icon in ALIASES.items(): + if icon not in names: + continue - file.write( - f' case "{alias}":\n' - f' return "{names[icon]}"\n' - ) + file.write( + f' case "{alias}":\n return "{names[icon]}"\n' + ) - file.write( - """ default: + file.write( + """ default: return language } } } """ - ) + ) - print(f"Generated {output}") + print(f"Generated {output}") -# --------------------------------------------------------------------------- -# CLI -# --------------------------------------------------------------------------- - if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} OUTPUT.qml", file=sys.stderr) - sys.exit(1) + print(f"Usage: {sys.argv[0]} OUTPUT.qml", file=sys.stderr) + sys.exit(1) generate(Path(sys.argv[1]))