mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-11-04 14:47:17 +01:00
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
6.5 KiB
Python
167 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Generates GGIconPackMapTable.generated.swift from the icon pack map data and checks it.
|
|
|
|
The data is `icon_pack_map.tsv` (Android drawable name <TAB> iOS asset name, one pair per line)
|
|
and `icon_pack_blacklist.txt` (Android names a pack may never replace, one per line). An `.icons`
|
|
pack (exteraGram / Telegram-for-Android format) names its icons by Android drawable, so the table
|
|
says which iOS asset each of them replaces.
|
|
|
|
Every iOS asset name is checked against the asset catalogs of the app: an asset upstream renamed
|
|
or removed is an error (the table must follow upstream, never silently go stale).
|
|
|
|
python3 Ghostgram/Features/Interface/IconPacks/map/generate_icon_map.py
|
|
python3 Ghostgram/Features/Interface/IconPacks/map/generate_icon_map.py --check # no writing
|
|
|
|
Bootstrap only (the TSV is committed): `--import-markdown <XTGIconPackMap.md>` rewrites the TSV
|
|
from exteraGram's map document (rows of the "Not mapped" section are skipped).
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
REPO = os.path.abspath(os.path.join(HERE, "..", "..", "..", "..", ".."))
|
|
TSV = os.path.join(HERE, "icon_pack_map.tsv")
|
|
BLACKLIST = os.path.join(HERE, "icon_pack_blacklist.txt")
|
|
OUTPUT = os.path.join(HERE, "..", "..", "Sources", "IconPacks", "GGIconPackMapTable.generated.swift")
|
|
CATALOGS = [
|
|
os.path.join(REPO, "submodules", "TelegramUI", "Images.xcassets"),
|
|
]
|
|
|
|
|
|
def import_markdown(path):
|
|
pairs = []
|
|
section = None
|
|
with open(path, "r", encoding="utf-8") as handle:
|
|
for line in handle:
|
|
line = line.rstrip("\n")
|
|
if line.startswith("## "):
|
|
section = line[3:].strip()
|
|
continue
|
|
if section is None or section.lower().startswith("not mapped"):
|
|
continue
|
|
if not line.startswith("| ") or line.startswith("| ---") or line.startswith("| Android drawable"):
|
|
continue
|
|
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
|
if len(cells) < 4:
|
|
continue
|
|
android, ios = cells[0], cells[3]
|
|
if not android or not ios or " " in android:
|
|
continue
|
|
pairs.append((android, ios))
|
|
seen = set()
|
|
with open(TSV, "w", encoding="utf-8") as handle:
|
|
handle.write("# Android drawable\tiOS asset — generated once from exteraGram's XTGIconPackMap.md, edited here since\n")
|
|
for android, ios in pairs:
|
|
if (android, ios) in seen:
|
|
continue
|
|
seen.add((android, ios))
|
|
handle.write("%s\t%s\n" % (android, ios))
|
|
print("imported %d pairs" % len(seen))
|
|
|
|
|
|
def read_pairs():
|
|
pairs = []
|
|
with open(TSV, "r", encoding="utf-8") as handle:
|
|
for number, line in enumerate(handle, 1):
|
|
line = line.rstrip("\n")
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
parts = line.split("\t")
|
|
if len(parts) != 2 or not parts[0] or not parts[1]:
|
|
raise SystemExit("icon_pack_map.tsv:%d: expected '<android>\\t<ios asset>'" % number)
|
|
pairs.append((parts[0], parts[1]))
|
|
return pairs
|
|
|
|
|
|
def read_blacklist():
|
|
names = []
|
|
with open(BLACKLIST, "r", encoding="utf-8") as handle:
|
|
for line in handle:
|
|
line = line.strip()
|
|
if line and not line.startswith("#"):
|
|
names.append(line)
|
|
return names
|
|
|
|
|
|
def asset_exists(name):
|
|
for catalog in CATALOGS:
|
|
base = os.path.join(catalog, name)
|
|
for extension in (".imageset", ".symbolset"):
|
|
if os.path.isdir(base + extension):
|
|
return True
|
|
return False
|
|
|
|
|
|
def swift_string(value):
|
|
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
|
|
|
|
def render(pairs, blacklist):
|
|
by_asset = {}
|
|
for android, ios in pairs:
|
|
by_asset.setdefault(ios, [])
|
|
if android not in by_asset[ios]:
|
|
by_asset[ios].append(android)
|
|
lines = [
|
|
"// GENERATED by Ghostgram/Features/Interface/IconPacks/map/generate_icon_map.py from",
|
|
"// icon_pack_map.tsv and icon_pack_blacklist.txt. Do not edit by hand.",
|
|
"",
|
|
"import Foundation",
|
|
"",
|
|
"enum GGIconPackMapTable {",
|
|
" /// iOS asset → the Android drawable names an `.icons` pack may use for it.",
|
|
" static let androidNamesByAsset: [String: [String]] = [",
|
|
]
|
|
for ios in sorted(by_asset):
|
|
names = ", ".join(swift_string(name) for name in by_asset[ios])
|
|
lines.append(" %s: [%s]," % (swift_string(ios), names))
|
|
lines.append(" ]")
|
|
lines.append("")
|
|
lines.append(" /// Android names a pack may never replace (structural pieces, splash art, animated vectors).")
|
|
lines.append(" static let blacklist: Set<String> = [")
|
|
for name in sorted(set(blacklist)):
|
|
lines.append(" %s," % swift_string(name))
|
|
lines.append(" ]")
|
|
lines.append("}")
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--check", action="store_true", help="only check, do not write")
|
|
parser.add_argument("--import-markdown", default=None, help="rewrite the TSV from XTGIconPackMap.md")
|
|
arguments = parser.parse_args(argv)
|
|
if arguments.import_markdown:
|
|
import_markdown(arguments.import_markdown)
|
|
pairs = read_pairs()
|
|
blacklist = read_blacklist()
|
|
missing = sorted(set(ios for _, ios in pairs if not asset_exists(ios)))
|
|
for name in missing:
|
|
print("error: asset not found in the catalogs: %s" % name)
|
|
content = render([(a, i) for a, i in pairs if i not in missing], blacklist)
|
|
output = os.path.abspath(OUTPUT)
|
|
existing = None
|
|
if os.path.exists(output):
|
|
with open(output, "r", encoding="utf-8") as handle:
|
|
existing = handle.read()
|
|
if arguments.check:
|
|
if existing != content:
|
|
print("error: %s is stale, run generate_icon_map.py" % output)
|
|
return 1
|
|
return 1 if missing else 0
|
|
if existing != content:
|
|
with open(output, "w", encoding="utf-8") as handle:
|
|
handle.write(content)
|
|
print("generate_icon_map.py: updated %s" % os.path.relpath(output, REPO))
|
|
else:
|
|
print("generate_icon_map.py: up to date")
|
|
print("%d pairs, %d assets, %d blacklisted, %d missing assets" % (len(pairs), len(set(i for _, i in pairs)), len(set(blacklist)), len(missing)))
|
|
return 1 if missing else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|