chore: format python codeicons
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 30s
C++ / fmt (pull_request) Successful in 4s
C++ / clang-tidy (pull_request) Failing after 27s
C++ / build (pull_request) Failing after 40s
Rust / fmt (pull_request) Successful in 1m24s
Rust / build (pull_request) Successful in 2m12s
Rust / clippy (pull_request) Successful in 1m52s
Python / verify (pull_request) Successful in 2m24s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 30s
C++ / fmt (pull_request) Successful in 4s
C++ / clang-tidy (pull_request) Failing after 27s
C++ / build (pull_request) Failing after 40s
Rust / fmt (pull_request) Successful in 1m24s
Rust / build (pull_request) Successful in 2m12s
Rust / clippy (pull_request) Successful in 1m52s
Python / verify (pull_request) Successful in 2m24s
This commit is contained in:
+374
-444
@@ -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]))
|
||||
|
||||
Reference in New Issue
Block a user