Context for the "About" label
[inkscape.git] / buildtools / media-check-keys.py
blob4ebcd2f926fb1ee865cd592091297a3c30003cfb
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Keys checker:
5 # * Are there bad xml elements / parsing
6 # * Does it contain non-key xml elements
7 # * Does the keys file reference something unknown
8 # * Does it repeat empty actions
9 # * Are there keys formatted as verbs
10 # * Are some of the keys missing
11 # * Does it include the default xml
13 # Author: Martin Owens <doctormo@geek-2.com>
14 # Licensed under GPL version 2 or any later version, read the file "COPYING" for more information.
16 import fnmatch
17 import os
18 import sys
20 from collections import defaultdict
22 from lxml import etree
24 KEYS_PATH = os.path.join('.', 'share', 'keys')
25 DEFAULT = 'inkscape.xml'
27 IGNORE_MISSING_KEYS = ['org.']
29 class Keys:
30 """Open and parse a keys xml file"""
31 def __init__(self, filename):
32 self.filename = filename
33 self.mods = set()
34 self.keys = set()
35 self.olds = set()
36 self.ticks = defaultdict(list)
37 self.errors = False
38 self.parse(etree.parse(filename))
40 def parse(self, doc):
41 """Parse the document into checkable concerns"""
42 for child in doc.getroot().getchildren():
43 try:
44 if child.tag == "modifier":
45 name = child.attrib['action']
46 self.mods.add(name)
47 self.ticks[name].append(child.attrib.get('modifiers'))
48 elif child.tag == "bind":
49 name = child.attrib['gaction']
50 self.keys.add(name)
51 self.ticks[name].append(child.attrib.get('keys'))
52 if 'key' in child.attrib or 'modifiers' in child.attrib:
53 self.olds.add(name)
54 elif child.tag == "{http://www.w3.org/2001/XInclude}include":
55 self.parse_include(child.attrib['href'])
56 elif isinstance(child.tag, str):
57 sys.stderr.write(f"Unrecognised tag in keys file {child.tag}\n")
58 self.errors = True
59 except KeyError as err:
60 sys.stderr.write(f"Missing attribute g/action in {self.filename}\n")
61 self.errors = True
63 def parse_include(self, file):
64 """Parse in the linked file"""
65 other = Keys(os.path.join(os.path.dirname(self.filename), file))
66 self.mods = self.mods.union(other.mods)
67 self.keys = self.keys.union(other.keys)
69 @classmethod
70 def others(cls):
71 """Load all non default keys"""
72 for name in os.listdir(KEYS_PATH):
73 filename = os.path.join(KEYS_PATH, name)
74 if name == DEFAULT:
75 continue
76 if not os.path.isfile(filename) or not filename.endswith('.xml'):
77 continue
78 yield name, cls(filename)
80 @classmethod
81 def default(cls):
82 """Load default keys"""
83 return cls(os.path.join(KEYS_PATH, DEFAULT))
86 if __name__ == '__main__':
87 sys.stderr.write("\n\n==== CHECKING KEYBOARD FILES ====\n\n")
88 data = defaultdict(set)
89 names = set()
91 errors = False
92 that = Keys.default()
95 for name, this in Keys.others():
96 sys.stderr.write(f"Checking '{name}'\n")
98 for old in this.olds:
99 sys.stderr.write(f" ! Old formatted key binding {old}\n")
101 add = []
102 if '-' not in sys.argv:
103 for key in this.keys ^ (that.keys & this.keys):
104 sys.stderr.write(f" + Unknown extra key {key}\n")
105 errors = True
107 for mod in this.mods ^ (that.mods & this.mods):
108 sys.stderr.write(f" + Unknown extra modifier {mod}\n")
109 errors = True
111 for tick, lst in this.ticks.items():
112 if len(lst) > 1 and None in lst:
113 sys.stderr.write(f" * Multiple empty references to {tick}\n")
115 if '+' not in sys.argv:
116 for key in that.keys ^ (that.keys & this.keys):
117 if [ig for ig in IGNORE_MISSING_KEYS if key.startswith(ig)]:
118 continue
119 if '-' in sys.argv:
120 add.append(f"<bind gaction=\"{key}\" />")
121 else:
122 sys.stderr.write(f" - Missing key {key}\n")
123 errors = True
125 for mod in that.mods ^ (that.mods & this.mods):
126 if '-' in sys.argv:
127 add.append(f"<modifier action=\"{mod}\" />")
128 else:
129 sys.stderr.write(f" - Missing modifier {mod}\n")
130 errors = True
132 for item in sorted(add):
133 sys.stderr.write(f" {item}\n")
134 sys.stderr.write("\n")
136 if errors:
137 sys.exit(5)
139 # vi:sw=4:expandtab: