Add parameter to control whether to unescape HTML entities
[qBittorrent.git] / .github / workflows / helper / pre-commit / check_grid_items_order.py
blob0ab3d6715d30116ee81a76f9d8afbca4025a2b2e
1 #!/usr/bin/env python3
3 # A pre-commit hook for checking items order in grid layouts
4 # Copyright (C) 2024 Mike Tzou (Chocobo1)
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 # In addition, as a special exception, the copyright holders give permission to
21 # link this program with the OpenSSL project's "OpenSSL" library (or with
22 # modified versions of it that use the same license as the "OpenSSL" library),
23 # and distribute the linked executables. You must obey the GNU General Public
24 # License in all respects for all of the code used other than "OpenSSL". If you
25 # modify file(s), you may extend this exception to your version of the file(s),
26 # but you are not obligated to do so. If you do not wish to do so, delete this
27 # exception statement from your version.
29 from collections.abc import Callable, Sequence
30 from typing import Optional
31 import argparse
32 import re
33 import xml.etree.ElementTree as ElementTree
34 import sys
37 def traversePostOrder(root: ElementTree.Element, visitFunc: Callable[[ElementTree.Element], None]) -> None:
38 stack = [(root, False)]
40 while len(stack) > 0:
41 (element, visit) = stack.pop()
42 if visit:
43 visitFunc(element)
44 else:
45 stack.append((element, True))
46 stack.extend((child, False) for child in reversed(element))
49 def modifyElement(element: ElementTree.Element) -> None:
50 def getSortKey(e: ElementTree.Element) -> tuple[int, int]:
51 if e.tag == 'item':
52 return (int(e.attrib['row']), int(e.attrib['column']))
53 return (-1, -1) # don't care
55 if element.tag == 'layout' and element.attrib['class'] == 'QGridLayout' and len(element) > 0:
56 element[:] = sorted(element, key=getSortKey)
58 # workaround_2a: ElementTree will unescape `"` and we need to escape it back
59 if element.tag == 'string' and element.text is not None:
60 element.text = element.text.replace('"', '"')
63 def main(argv: Optional[Sequence[str]] = None) -> int:
64 parser = argparse.ArgumentParser()
65 parser.add_argument('filenames', nargs='*', help='Filenames to check')
66 args = parser.parse_args(argv)
68 for filename in args.filenames:
69 with open(filename, 'r+') as f:
70 orig = f.read()
71 root = ElementTree.fromstring(orig)
72 traversePostOrder(root, modifyElement)
73 ElementTree.indent(root, ' ')
75 # workaround_1: cannot use `xml_declaration=True` since it uses single quotes instead of Qt preferred double quotes
76 ret = f'<?xml version="1.0" encoding="UTF-8"?>\n{ElementTree.tostring(root, 'unicode')}\n'
78 # workaround_2b: ElementTree will turn `&quot;` into `&amp;quot;`, so revert it back
79 ret = ret.replace('&amp;quot;', '&quot;')
81 # workaround_3: Qt prefers no whitespaces in self-closing tags
82 ret = re.sub('<(.+) +/>', r'<\1/>', ret)
84 if ret != orig:
85 print(f'Tip: run this script to apply the fix: `python {__file__} {filename}`', file=sys.stderr)
87 f.seek(0)
88 f.write(ret)
89 f.truncate()
91 return 0
94 if __name__ == '__main__':
95 sys.exit(main())