|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# A pre-commit hook for checking items order in grid layouts |
| 4 | +# Copyright (C) 2024 Mike Tzou (Chocobo1) |
| 5 | +# |
| 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. |
| 10 | +# |
| 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. |
| 15 | +# |
| 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. |
| 19 | +# |
| 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. |
| 28 | + |
| 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 |
| 35 | + |
| 36 | + |
| 37 | +def traversePostOrder(root: ElementTree.Element, visitFunc: Callable[[ElementTree.Element], None]) -> None: |
| 38 | + stack = [(root, False)] |
| 39 | + |
| 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)) |
| 47 | + |
| 48 | + |
| 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 |
| 54 | + |
| 55 | + if element.tag == 'layout' and element.attrib['class'] == 'QGridLayout' and len(element) > 0: |
| 56 | + element[:] = sorted(element, key=getSortKey) |
| 57 | + |
| 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('"', '"') |
| 61 | + |
| 62 | + |
| 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) |
| 67 | + |
| 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, ' ') |
| 74 | + |
| 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' |
| 77 | + |
| 78 | + # workaround_2b: ElementTree will turn `"` into `&quot;`, so revert it back |
| 79 | + ret = ret.replace('&quot;', '"') |
| 80 | + |
| 81 | + # workaround_3: Qt prefers no whitespaces in self-closing tags |
| 82 | + ret = re.sub('<(.+) +/>', r'<\1/>', ret) |
| 83 | + |
| 84 | + if ret != orig: |
| 85 | + f.seek(0) |
| 86 | + f.write(ret) |
| 87 | + f.truncate() |
| 88 | + |
| 89 | + return 0 |
| 90 | + |
| 91 | + |
| 92 | +if __name__ == '__main__': |
| 93 | + sys.exit(main()) |
0 commit comments