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)