File indexing completed on 2024-12-22 04:04:05
0001 # SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0002 # SPDX-License-Identifier: GPL-3.0-or-later 0003 import re 0004 import os 0005 import sys 0006 from pathlib import Path 0007 0008 import xml.etree.ElementTree as etree 0009 from xml.etree.ElementTree import parse as parse_xml 0010 0011 from markdown.inlinepatterns import InlineProcessor 0012 from markdown.blockprocessors import BlockProcessor 0013 from markdown.extensions import Extension 0014 from babel import Locale 0015 0016 project_root = Path(__file__).parent.parent.parent 0017 0018 sys.path.append(str(project_root / "deploy")) 0019 0020 from gitlab_api import GitlabApi 0021 0022 gitlab = GitlabApi() 0023 0024 def etree_fontawesome(icon, group="fas"): 0025 el = etree.Element("i") 0026 el.attrib["class"] = "%s fa-%s" % (group, icon) 0027 return el 0028 0029 0030 def clean_link(filename): 0031 if not filename.startswith("/") and not filename.startswith("."): 0032 filename = "../" + filename 0033 return filename 0034 0035 0036 def css_style(**args): 0037 string = "" 0038 for k, v in args.items(): 0039 string += "%s:%s;" % (k.replace("_", "-"), v) 0040 0041 return string 0042 0043 0044 class LottieInlineProcessor(InlineProcessor): 0045 def __init__(self, md): 0046 pattern = r'{lottie:([^:]+)(?::([0-9]+):([0-9]+))(?::([^:]*))}' 0047 super().__init__(pattern, md) 0048 self._id = 0 0049 0050 def handleMatch(self, m, data): 0051 el = etree.Element("div") 0052 el.attrib["class"] = "lottie-container" 0053 0054 animation = etree.Element("div") 0055 el.append(animation) 0056 animation.attrib["class"] = "alpha_checkered" 0057 animation.attrib["id"] = "lottie_target_%s" % self._id 0058 0059 filename = clean_link(m.group(1)) 0060 download_file = m.group(4) 0061 if download_file is None: 0062 download_file = clean_link(download_file) 0063 elif download_file == "-": 0064 download_file = filename 0065 0066 if m.group(2): 0067 animation.attrib["style"] = "width:%spx;height:%spx" % (m.group(2), m.group(3)) 0068 0069 play = etree.Element("button") 0070 el.append(play) 0071 play.attrib["id"] = "lottie_play_{id}".format(id=self._id) 0072 play.attrib["onclick"] = "anim_{id}.play(); document.getElementById('lottie_pause_{id}').style.display = 'inline-block'; this.style.display = 'none'".format(id=self._id) 0073 play.append(etree_fontawesome("play")) 0074 play.attrib["title"] = "Play" 0075 play.attrib["style"] = "display:none" 0076 0077 pause = etree.Element("button") 0078 el.append(pause) 0079 pause.attrib["id"] = "lottie_pause_{id}".format(id=self._id) 0080 pause.attrib["onclick"] = "anim_{id}.pause(); document.getElementById('lottie_play_{id}').style.display = 'inline-block'; this.style.display = 'none'".format(id=self._id) 0081 pause.append(etree_fontawesome("pause")) 0082 pause.attrib["title"] = "Pause" 0083 0084 if download_file: 0085 download = etree.Element("a") 0086 el.append(download) 0087 download.attrib["href"] = download_file 0088 if download_file.endswith("rawr"): 0089 download.attrib["download"] = "" 0090 download.attrib["title"] = "Download" 0091 download_button = etree.Element("button") 0092 download.append(download_button) 0093 download_button.append(etree_fontawesome("download")) 0094 0095 script = etree.Element("script") 0096 el.append(script) 0097 script.text = """ 0098 var anim_{id} = bodymovin.loadAnimation({{ 0099 container: document.getElementById('lottie_target_{id}'), 0100 renderer: 'svg', 0101 loop: true, 0102 autoplay: true, 0103 path: '{file}' 0104 }}); 0105 """.format(id=self._id, file=filename) 0106 0107 self._id += 1 0108 return el, m.start(0), m.end(0) 0109 0110 0111 class DownloadTable(InlineProcessor): 0112 def __init__(self, pattern, md, git): 0113 super().__init__(pattern, md) 0114 self.git = git 0115 0116 class Row: 0117 def __init__(self, name, icon_group, icon, filename, job, notes=None, parent="build/"): 0118 self.name = name 0119 self.icon_group = icon_group 0120 self.icon = icon 0121 self.filename = filename 0122 self.checksum = "SHA1" 0123 self.notes = notes if notes else "#" + name.lower().replace(" ", "-") 0124 self.job_path = job 0125 self.parent = parent 0126 0127 def element(self, parent, branch): 0128 tr = etree.SubElement(parent, "tr") 0129 0130 pack = etree.SubElement(tr, "td") 0131 icon = etree_fontawesome(self.icon, self.icon_group) 0132 icon.tail = " " 0133 pack.append(icon) 0134 0135 checksum_filename = "checksum.txt" 0136 0137 if self.job_path.startswith("-"): 0138 url = "https://github.com/mbasaglia/glaxnimate/releases/download/{0}/{{0}}".format(branch) 0139 checksum_filename = "checksum%s.txt" % self.job_path 0140 else: 0141 url = gitlab.artifact_url(branch, self.job_path, self.parent + "{0}") 0142 0143 etree.SubElement(pack, "a", {"href": url.format(self.filename)}).text = self.name 0144 0145 check = etree.SubElement(tr, "td") 0146 etree.SubElement(check, "a", {"href": url.format(checksum_filename)}).text = self.checksum 0147 0148 notes = etree.SubElement(tr, "td") 0149 etree.SubElement(notes, "a", {"href": self.notes}).text = "Notes" 0150 0151 def handleMatch(self, m, data): 0152 table = etree.Element("table") 0153 0154 thead = etree.SubElement(table, "thead") 0155 tr = etree.SubElement(thead, "tr") 0156 etree.SubElement(tr, "th").text = "Package" 0157 etree.SubElement(tr, "th").text = "Checksum" 0158 etree.SubElement(tr, "th").text = "Notes" 0159 0160 tbody = etree.SubElement(table, "tbody") 0161 branch = m.group(1) 0162 rows = [ 0163 self.Row("Linux Appimage", "fab", "linux", "glaxnimate-x86_64.AppImage", "linux:appimage"), 0164 self.Row("Deb Package", "fab", "ubuntu", "glaxnimate.deb", "linux:deb"), 0165 self.Row("Android (arm64)", "fab", "android", "glaxnimate-arm64-v8a.apk", "android:sign"), 0166 self.Row("Windows Zip", "fab", "windows", "glaxnimate-x86_64.zip", "-win"), 0167 self.Row("Mac DMG", "fab", "apple", "glaxnimate.dmg", "-mac"), 0168 self.Row("Source Tarball", "fas", "wrench", "glaxnimate-src.tar.gz", "tarball", "/contributing/read_me", ""), 0169 ] 0170 0171 if not self.git: 0172 rows.pop(2) 0173 0174 for row in rows: 0175 row.element(tbody, branch) 0176 0177 if self.git: 0178 tr = etree.SubElement(tbody, "tr") 0179 pack = etree.SubElement(tr, "td") 0180 icon = etree_fontawesome("code-branch", "fas") 0181 icon.tail = " " 0182 pack.append(icon) 0183 etree.SubElement(pack, "a", {"href": "https://gitlab.com/mattbas/glaxnimate.git"}).text = "Git Repo" 0184 etree.SubElement(tr, "td") 0185 notes = etree.SubElement(tr, "td") 0186 etree.SubElement(notes, "a", {"href": "/contributing/read_me/"}).text = "Notes" 0187 0188 return table, m.start(0), m.end(0) 0189 0190 0191 class TranslationTable(InlineProcessor): 0192 def __init__(self, pattern, md): 0193 super().__init__(pattern, md) 0194 self.data = None 0195 0196 def fetch_data(self): 0197 self.data = [] 0198 0199 source = project_root / "data" / "translations" 0200 0201 for file in source.glob("*.ts"): 0202 locale_name = file.stem.split("_", 1)[1] 0203 locale = Locale.parse(locale_name) 0204 0205 dom = parse_xml(open(file)) 0206 translated = 0 0207 untranslated = 0 0208 obsolete = 0 0209 for message in dom.findall(".//message"): 0210 type = message.find("translation").attrib.get("type") 0211 if type == "unfinished": 0212 untranslated += 1 0213 elif not type: 0214 translated += 1 0215 elif type == "vanished" or type == "obsolete": 0216 obsolete += 1 0217 0218 total = untranslated + translated 0219 self.data.append({ 0220 "total": total, 0221 "translated": translated, 0222 "language": locale.display_name, 0223 "obsolete": obsolete, 0224 "untranslated": untranslated, 0225 }) 0226 0227 self.data = sorted(self.data, key=lambda x: (-x["translated"], -x["obsolete"])) 0228 0229 def _th(self, tr, text): 0230 cell = etree.SubElement(tr, "th") 0231 cell.attrib["style"] = "text-align:right;" 0232 cell.text = text 0233 0234 def _cell(self, tr, text, style=None): 0235 cell = etree.SubElement(tr, "td") 0236 if style: 0237 cell.attrib["style"] = style 0238 cell.text = text 0239 0240 def handleMatch(self, m, data): 0241 if self.data is None: 0242 self.fetch_data() 0243 0244 table = etree.Element("table") 0245 0246 thead = etree.SubElement(table, "thead") 0247 tr = etree.SubElement(thead, "tr") 0248 etree.SubElement(tr, "th").text = "Language" 0249 self._th(tr, "Completion") 0250 self._th(tr, "Translated") 0251 self._th(tr, "Missing") 0252 self._th(tr, "Obsolete") 0253 0254 tbody = etree.SubElement(table, "tbody") 0255 0256 for row in self.data: 0257 tr = etree.SubElement(tbody, "tr") 0258 0259 percent = round(row["translated"]/row["total"]*100) 0260 if percent > 80: 0261 color = "#0a0" 0262 elif percent > 50: 0263 color = "#b80" 0264 else: 0265 color = "#c00" 0266 0267 self._cell(tr, row["language"]) 0268 data_style = "text-align:right; font-family: monospace;" 0269 self._cell(tr, "%s%%" % percent, data_style + "color: %s;" % color) 0270 self._cell(tr, str(row["translated"]), data_style) 0271 self._cell(tr, str(row["untranslated"]), data_style) 0272 self._cell(tr, str(row["obsolete"]), data_style) 0273 0274 return table, m.start(0), m.end(0) 0275 0276 0277 class LottieColor(InlineProcessor): 0278 def __init__(self, pattern, md, mult): 0279 super().__init__(pattern, md) 0280 self.mult = mult 0281 0282 def handleMatch(self, match, data): 0283 span = etree.Element("span") 0284 span.attrib["style"] = "font-family: right" 0285 0286 comp = [float(match.group(i)) / self.mult for i in range(2, 5)] 0287 0288 hex = "#" + "".join("%02x" % round(x * 255) for x in comp) 0289 color = etree.SubElement(span, "span") 0290 color.attrib["style"] = css_style( 0291 width="24px", 0292 height="24px", 0293 background_color=hex, 0294 border="1px solid black", 0295 display="inline-block", 0296 vertical_align="middle", 0297 margin_right="0.5ex" 0298 ) 0299 0300 etree.SubElement(span, "code").text = "[%s]" % ", ".join("%.3g" % x for x in comp) 0301 0302 return span, match.start(0), match.end(0) 0303 0304 0305 class Matrix(BlockProcessor): 0306 RE_FENCE_START = r'^\s*\{matrix\}\s*\n' 0307 0308 def test(self, parent, block): 0309 return re.match(self.RE_FENCE_START, block) 0310 0311 def run(self, parent, blocks): 0312 table = etree.SubElement(parent, "table") 0313 table.attrib["style"] = "font-family: monospace; text-align: center; background-color: #fcfdff; border: 1px solid #ccc;" 0314 table.attrib["class"] = "table-plain" 0315 rows = blocks.pop(0) 0316 for row in rows.split("\n")[1:]: 0317 tr = etree.SubElement(table, "tr") 0318 for cell in row.split(): 0319 td = etree.SubElement(tr, "td") 0320 td.text = cell 0321 td.attrib["style"] = "width: 25%;" 0322 return True 0323 0324 0325 class GlaxnimateExtension(Extension): 0326 def extendMarkdown(self, md): 0327 md.inlinePatterns.register(LottieInlineProcessor(md), 'lottie', 175) 0328 md.inlinePatterns.register(DownloadTable(r'{download_table:([^:]+)}', md, False), 'download_table', 175) 0329 md.inlinePatterns.register(DownloadTable(r'{download_table:([^:]+):git}', md, True), 'download_table_git', 175) 0330 md.inlinePatterns.register(TranslationTable(r'{translation_table}', md), 'translation_table', 175) 0331 md.inlinePatterns.register(LottieColor(r'{lottie_color:(([^,]+),\s*([^,]+),\s*([^,]+))}', md, 1), 'lottie_color', 175) 0332 md.inlinePatterns.register(LottieColor(r'{lottie_color_255:(([^,]+),\s*([^,]+),\s*([^,]+))}', md, 255), 'lottie_color_255', 175) 0333 md.parser.blockprocessors.register(Matrix(md.parser), 'matrix', 175) 0334 0335 0336 def makeExtension(**kwargs): 0337 return GlaxnimateExtension(**kwargs)