]> git.ipfire.org Git - thirdparty/systemd.git/blob - hwdb/parse_hwdb.py
Merge pull request #3990 from AnchorCat/networkd-fixes
[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.
5 #
6 # Copyright 2016 Zbigniew Jędrzejewski-Szmek
7 #
8 # systemd is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU Lesser General Public License as published by
10 # the Free Software Foundation; either version 2.1 of the License, or
11 # (at your option) any later version.
12 #
13 # systemd is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with systemd; If not, see <http://www.gnu.org/licenses/>.
20
21 import functools
22 import glob
23 import string
24 import sys
25 import os
26
27 try:
28 from pyparsing import (Word, White, Literal, ParserElement, Regex,
29 LineStart, LineEnd,
30 ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group,
31 nums, alphanums, printables,
32 stringEnd, pythonStyleComment,
33 ParseBaseException)
34 except ImportError:
35 print('pyparsing is not available')
36 sys.exit(77)
37
38 try:
39 from evdev.ecodes import ecodes
40 except ImportError:
41 ecodes = None
42 print('WARNING: evdev is not available')
43
44 EOL = LineEnd().suppress()
45 EMPTYLINE = LineStart() + LineEnd()
46 COMMENTLINE = pythonStyleComment + EOL
47 INTEGER = Word(nums)
48 REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
49 UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
50
51 TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
52 'evdev': ('name', 'atkbd', 'input'),
53 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
54 'keyboard': ('name', ),
55 }
56
57 @functools.lru_cache()
58 def hwdb_grammar():
59 ParserElement.setDefaultWhitespaceChars('')
60
61 prefix = Or(category + ':' + Or(conn) + ':'
62 for category, conn in TYPES.items())
63 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
64 propertyline = (White(' ', exact=1).suppress() +
65 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) +
66 EOL)
67 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
68
69 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
70 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
71 (EMPTYLINE ^ stringEnd()).suppress() )
72 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
73
74 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
75
76 return grammar
77
78 @functools.lru_cache()
79 def property_grammar():
80 ParserElement.setDefaultWhitespaceChars(' ')
81
82 setting = Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ')
83 props = (('MOUSE_DPI', Group(OneOrMore(setting('SETTINGS*')))),
84 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
85 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZ', INTEGER),
86 ('ID_INPUT_TRACKBALL', Literal('1')),
87 ('POINTINGSTICK_SENSITIVITY', INTEGER),
88 ('POINTINGSTICK_CONST_ACCEL', REAL),
89 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
90 )
91 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
92 for name, val in props]
93 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
94 - Suppress('=') -
95 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
96 ]
97 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
98 - Suppress('=') -
99 Word(nums + ':')('VALUE')
100 ]
101
102 grammar = Or(fixed_props + kbd_props + abs_props)
103
104 return grammar
105
106 ERROR = False
107 def error(fmt, *args, **kwargs):
108 global ERROR
109 ERROR = True
110 print(fmt.format(*args, **kwargs))
111
112 def convert_properties(group):
113 matches = [m[0] for m in group.MATCHES]
114 props = [p[0] for p in group.PROPERTIES]
115 return matches, props
116
117 def parse(fname):
118 grammar = hwdb_grammar()
119 try:
120 parsed = grammar.parseFile(fname)
121 except ParseBaseException as e:
122 error('Cannot parse {}: {}', fname, e)
123 return []
124 return [convert_properties(g) for g in parsed.GROUPS]
125
126 def check_match_uniqueness(groups):
127 matches = sum((group[0] for group in groups), [])
128 matches.sort()
129 prev = None
130 for match in matches:
131 if match == prev:
132 error('Match {!r} is duplicated', match)
133 prev = match
134
135 def check_one_default(prop, settings):
136 defaults = [s for s in settings if s.DEFAULT]
137 if len(defaults) > 1:
138 error('More than one star entry: {!r}', prop)
139
140 def check_one_keycode(prop, value):
141 if value != '!' and ecodes is not None:
142 key = 'KEY_' + value.upper()
143 if key not in ecodes:
144 error('Keycode {} unknown', key)
145
146 def check_properties(groups):
147 grammar = property_grammar()
148 for matches, props in groups:
149 prop_names = set()
150 for prop in props:
151 # print('--', prop)
152 prop = prop.partition('#')[0].rstrip()
153 try:
154 parsed = grammar.parseString(prop)
155 except ParseBaseException as e:
156 error('Failed to parse: {!r}', prop)
157 continue
158 # print('{!r}'.format(parsed))
159 if parsed.NAME in prop_names:
160 error('Property {} is duplicated', parsed.NAME)
161 prop_names.add(parsed.NAME)
162 if parsed.NAME == 'MOUSE_DPI':
163 check_one_default(prop, parsed.VALUE.SETTINGS)
164 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
165 check_one_keycode(prop, parsed.VALUE)
166
167 def print_summary(fname, groups):
168 print('{}: {} match groups, {} matches, {} properties'
169 .format(fname,
170 len(groups),
171 sum(len(matches) for matches, props in groups),
172 sum(len(props) for matches, props in groups),
173 ))
174
175 if __name__ == '__main__':
176 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
177
178 for fname in args:
179 groups = parse(fname)
180 print_summary(fname, groups)
181 check_match_uniqueness(groups)
182 check_properties(groups)
183
184 sys.exit(ERROR)