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
33 import xml
.etree
.ElementTree
as ElementTree
37 def traversePostOrder(root
: ElementTree
.Element
, visitFunc
: Callable
[[ElementTree
.Element
], None]) -> None:
38 stack
= [(root
, False)]
41 (element
, visit
) = stack
.pop()
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]:
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
:
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 `"` into `&quot;`, so revert it back
79 ret
= ret
.replace('&quot;', '"')
81 # workaround_3: Qt prefers no whitespaces in self-closing tags
82 ret
= re
.sub('<(.+) +/>', r
'<\1/>', ret
)
85 print(f
'Tip: run this script to apply the fix: `python {__file__} {filename}`', file=sys
.stderr
)
94 if __name__
== '__main__':