]> git.ipfire.org Git - thirdparty/systemd.git/blob - hwdb/parse_hwdb.py
Merge pull request #4859 from keszybz/networkd
[thirdparty/systemd.git] / hwdb / parse_hwdb.py
1 #!/usr/bin/python3
2 # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
3 #
4 # This file is part of systemd. It is distrubuted under the MIT license, see
5 # below.
6 #
7 # Copyright 2016 Zbigniew Jędrzejewski-Szmek
8 #
9 # The MIT License (MIT)
10 #
11 # Permission is hereby granted, free of charge, to any person obtaining a copy
12 # of this software and associated documentation files (the "Software"), to deal
13 # in the Software without restriction, including without limitation the rights
14 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 # copies of the Software, and to permit persons to whom the Software is
16 # furnished to do so, subject to the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included in
19 # all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 # SOFTWARE.
28
29 import glob
30 import string
31 import sys
32 import os
33
34 try:
35 from pyparsing import (Word, White, Literal, ParserElement, Regex,
36 LineStart, LineEnd,
37 OneOrMore, Combine, Or, Optional, Suppress, Group,
38 nums, alphanums, printables,
39 stringEnd, pythonStyleComment, QuotedString,
40 ParseBaseException)
41 except ImportError:
42 print('pyparsing is not available')
43 sys.exit(77)
44
45 try:
46 from evdev.ecodes import ecodes
47 except ImportError:
48 ecodes = None
49 print('WARNING: evdev is not available')
50
51 try:
52 from functools import lru_cache
53 except ImportError:
54 # don't do caching on old python
55 lru_cache = lambda: (lambda f: f)
56
57 EOL = LineEnd().suppress()
58 EMPTYLINE = LineEnd()
59 COMMENTLINE = pythonStyleComment + EOL
60 INTEGER = Word(nums)
61 STRING = QuotedString('"')
62 REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
63 SIGNED_REAL = Combine(Optional(Word('-+')) + REAL)
64 UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
65
66 TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
67 'evdev': ('name', 'atkbd', 'input'),
68 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
69 'keyboard': ('name', ),
70 'sensor': ('modalias', ),
71 }
72
73 @lru_cache()
74 def hwdb_grammar():
75 ParserElement.setDefaultWhitespaceChars('')
76
77 prefix = Or(category + ':' + Or(conn) + ':'
78 for category, conn in TYPES.items())
79 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
80 propertyline = (White(' ', exact=1).suppress() +
81 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.!-;, "') - Optional(pythonStyleComment)) +
82 EOL)
83 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
84
85 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
86 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
87 (EMPTYLINE ^ stringEnd()).suppress())
88 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
89
90 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
91
92 return grammar
93
94 @lru_cache()
95 def property_grammar():
96 ParserElement.setDefaultWhitespaceChars(' ')
97
98 dpi_setting = (Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ'))('SETTINGS*')
99 mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL
100 mount_matrix = (mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
101
102 props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))),
103 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
104 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
105 ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
106 ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
107 ('ID_INPUT_TRACKBALL', Literal('1')),
108 ('POINTINGSTICK_SENSITIVITY', INTEGER),
109 ('POINTINGSTICK_CONST_ACCEL', REAL),
110 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
111 ('XKB_FIXED_LAYOUT', STRING),
112 ('XKB_FIXED_VARIANT', STRING),
113 ('ACCEL_MOUNT_MATRIX', mount_matrix),
114 )
115 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
116 for name, val in props]
117 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
118 - Suppress('=') -
119 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
120 ]
121 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
122 - Suppress('=') -
123 Word(nums + ':')('VALUE')
124 ]
125
126 grammar = Or(fixed_props + kbd_props + abs_props) + EOL
127
128 return grammar
129
130 ERROR = False
131 def error(fmt, *args, **kwargs):
132 global ERROR
133 ERROR = True
134 print(fmt.format(*args, **kwargs))
135
136 def convert_properties(group):
137 matches = [m[0] for m in group.MATCHES]
138 props = [p[0] for p in group.PROPERTIES]
139 return matches, props
140
141 def parse(fname):
142 grammar = hwdb_grammar()
143 try:
144 with open(fname, 'r', encoding='UTF-8') as f:
145 parsed = grammar.parseFile(f)
146 except ParseBaseException as e:
147 error('Cannot parse {}: {}', fname, e)
148 return []
149 return [convert_properties(g) for g in parsed.GROUPS]
150
151 def check_match_uniqueness(groups):
152 matches = sum((group[0] for group in groups), [])
153 matches.sort()
154 prev = None
155 for match in matches:
156 if match == prev:
157 error('Match {!r} is duplicated', match)
158 prev = match
159
160 def check_one_default(prop, settings):
161 defaults = [s for s in settings if s.DEFAULT]
162 if len(defaults) > 1:
163 error('More than one star entry: {!r}', prop)
164
165 def check_one_keycode(prop, value):
166 if value != '!' and ecodes is not None:
167 key = 'KEY_' + value.upper()
168 if key not in ecodes:
169 error('Keycode {} unknown', key)
170
171 def check_properties(groups):
172 grammar = property_grammar()
173 for matches, props in groups:
174 prop_names = set()
175 for prop in props:
176 # print('--', prop)
177 prop = prop.partition('#')[0].rstrip()
178 try:
179 parsed = grammar.parseString(prop)
180 except ParseBaseException as e:
181 error('Failed to parse: {!r}', prop)
182 continue
183 # print('{!r}'.format(parsed))
184 if parsed.NAME in prop_names:
185 error('Property {} is duplicated', parsed.NAME)
186 prop_names.add(parsed.NAME)
187 if parsed.NAME == 'MOUSE_DPI':
188 check_one_default(prop, parsed.VALUE.SETTINGS)
189 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
190 check_one_keycode(prop, parsed.VALUE)
191
192 def print_summary(fname, groups):
193 print('{}: {} match groups, {} matches, {} properties'
194 .format(fname,
195 len(groups),
196 sum(len(matches) for matches, props in groups),
197 sum(len(props) for matches, props in groups)))
198
199 if __name__ == '__main__':
200 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
201
202 for fname in args:
203 groups = parse(fname)
204 print_summary(fname, groups)
205 check_match_uniqueness(groups)
206 check_properties(groups)
207
208 sys.exit(ERROR)