]> git.ipfire.org Git - thirdparty/systemd.git/blob - hwdb/parse_hwdb.py
Merge pull request #4123 from keszybz/network-file-dropins
[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 functools
30 import glob
31 import string
32 import sys
33 import os
34
35 try:
36 from pyparsing import (Word, White, Literal, ParserElement, Regex,
37 LineStart, LineEnd,
38 ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group,
39 nums, alphanums, printables,
40 stringEnd, pythonStyleComment,
41 ParseBaseException)
42 except ImportError:
43 print('pyparsing is not available')
44 sys.exit(77)
45
46 try:
47 from evdev.ecodes import ecodes
48 except ImportError:
49 ecodes = None
50 print('WARNING: evdev is not available')
51
52 EOL = LineEnd().suppress()
53 EMPTYLINE = LineStart() + LineEnd()
54 COMMENTLINE = pythonStyleComment + EOL
55 INTEGER = Word(nums)
56 REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
57 UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
58
59 TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
60 'evdev': ('name', 'atkbd', 'input'),
61 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
62 'keyboard': ('name', ),
63 }
64
65 @functools.lru_cache()
66 def hwdb_grammar():
67 ParserElement.setDefaultWhitespaceChars('')
68
69 prefix = Or(category + ':' + Or(conn) + ':'
70 for category, conn in TYPES.items())
71 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
72 propertyline = (White(' ', exact=1).suppress() +
73 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) +
74 EOL)
75 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
76
77 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
78 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
79 (EMPTYLINE ^ stringEnd()).suppress() )
80 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
81
82 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
83
84 return grammar
85
86 @functools.lru_cache()
87 def property_grammar():
88 ParserElement.setDefaultWhitespaceChars(' ')
89
90 setting = Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ')
91 props = (('MOUSE_DPI', Group(OneOrMore(setting('SETTINGS*')))),
92 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
93 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
94 ('ID_INPUT_TRACKBALL', Literal('1')),
95 ('POINTINGSTICK_SENSITIVITY', INTEGER),
96 ('POINTINGSTICK_CONST_ACCEL', REAL),
97 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
98 )
99 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
100 for name, val in props]
101 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
102 - Suppress('=') -
103 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
104 ]
105 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
106 - Suppress('=') -
107 Word(nums + ':')('VALUE')
108 ]
109
110 grammar = Or(fixed_props + kbd_props + abs_props)
111
112 return grammar
113
114 ERROR = False
115 def error(fmt, *args, **kwargs):
116 global ERROR
117 ERROR = True
118 print(fmt.format(*args, **kwargs))
119
120 def convert_properties(group):
121 matches = [m[0] for m in group.MATCHES]
122 props = [p[0] for p in group.PROPERTIES]
123 return matches, props
124
125 def parse(fname):
126 grammar = hwdb_grammar()
127 try:
128 parsed = grammar.parseFile(fname)
129 except ParseBaseException as e:
130 error('Cannot parse {}: {}', fname, e)
131 return []
132 return [convert_properties(g) for g in parsed.GROUPS]
133
134 def check_match_uniqueness(groups):
135 matches = sum((group[0] for group in groups), [])
136 matches.sort()
137 prev = None
138 for match in matches:
139 if match == prev:
140 error('Match {!r} is duplicated', match)
141 prev = match
142
143 def check_one_default(prop, settings):
144 defaults = [s for s in settings if s.DEFAULT]
145 if len(defaults) > 1:
146 error('More than one star entry: {!r}', prop)
147
148 def check_one_keycode(prop, value):
149 if value != '!' and ecodes is not None:
150 key = 'KEY_' + value.upper()
151 if key not in ecodes:
152 error('Keycode {} unknown', key)
153
154 def check_properties(groups):
155 grammar = property_grammar()
156 for matches, props in groups:
157 prop_names = set()
158 for prop in props:
159 # print('--', prop)
160 prop = prop.partition('#')[0].rstrip()
161 try:
162 parsed = grammar.parseString(prop)
163 except ParseBaseException as e:
164 error('Failed to parse: {!r}', prop)
165 continue
166 # print('{!r}'.format(parsed))
167 if parsed.NAME in prop_names:
168 error('Property {} is duplicated', parsed.NAME)
169 prop_names.add(parsed.NAME)
170 if parsed.NAME == 'MOUSE_DPI':
171 check_one_default(prop, parsed.VALUE.SETTINGS)
172 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
173 check_one_keycode(prop, parsed.VALUE)
174
175 def print_summary(fname, groups):
176 print('{}: {} match groups, {} matches, {} properties'
177 .format(fname,
178 len(groups),
179 sum(len(matches) for matches, props in groups),
180 sum(len(props) for matches, props in groups),
181 ))
182
183 if __name__ == '__main__':
184 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
185
186 for fname in args:
187 groups = parse(fname)
188 print_summary(fname, groups)
189 check_match_uniqueness(groups)
190 check_properties(groups)
191
192 sys.exit(ERROR)