Coverage for linuxpy/codegen/usbids.py: 0%
78 statements
« prev ^ index » next coverage.py v7.6.8, created at 2025-05-27 13:54 +0200
« prev ^ index » next coverage.py v7.6.8, created at 2025-05-27 13:54 +0200
1# run with:
2#
4import datetime
5import logging
6import pathlib
7import platform
8import pprint
9import subprocess
11import requests
13this_dir = pathlib.Path(__file__).parent
16USB_IDS = "http://www.linux-usb.org/usb.ids"
19def get_raw(url: str = USB_IDS) -> str:
20 response = requests.get(url)
21 response.raise_for_status()
22 return response.text
25TYPES = {
26 "V": "vendor",
27 "C": "klass",
28 "AT": "audio_terminal",
29 "HID": "hid",
30 "R": "hid_item",
31 "BIAS": "bias",
32 "PHYS": "physical",
33 "HUT": "hid_usage",
34 "L": "language",
35 "HCC": "country",
36 "VT": "video_terminal",
37}
40def get(url: str = USB_IDS) -> dict[int,]:
41 logging.info(" Fetching usbids...")
42 raw = get_raw(url=url)
44 items = {}
45 l0, l1 = None, None
46 for line in raw.splitlines():
47 if line.startswith("#"):
48 continue
49 if line.startswith("\t\t"): # Interface, protocol, etc
50 l2_id, l2_name = line.strip().split(" ", 1)
51 l2_id = int(l2_id, 16)
52 l2 = {"name": l2_name}
53 l1.setdefault("children", {})[l2_id] = l2
54 elif line.startswith("\t"): # Device, subclass
55 l1_id, l1_name = line.strip().split(" ", 1)
56 l1_id = int(l1_id, 16)
57 l1 = {"name": l1_name}
58 l0.setdefault("children", {})[l1_id] = l1
59 elif line: # Vendor, class, audio terminal, etc
60 l0_id, l0_name = line.split(" ", 1)
61 if " " in l0_id:
62 itype, l0_id = l0_id.split(" ", 1)
63 else:
64 itype = "V" # Vendors don't have prefix
65 l0_id = int(l0_id, 16)
66 l0 = {"name": l0_name}
67 type_label = TYPES.get(itype, itype.lower())
68 items.setdefault(type_label, {})[l0_id] = l0
70 return items
73HEADER = """\
74# This file is part of the linuxpy project
75#
76# Copyright (c) 2023 Tiago Coutinho
77# Distributed under the GPLv3 license. See LICENSE for more info.
79# This file has been generated by {name}
80# Date: {date}
81# System: {system}
82# Release: {release}
83# Version: {version}
85"""
88def code_format(text, filename):
89 cmd = ["ruff", "check", "--fix", "--stdin-filename", str(filename)]
90 result = subprocess.run(cmd, capture_output=True, check=True, text=True, input=text)
91 fixed_text = result.stdout
93 cmd = ["ruff", "format", "--stdin-filename", str(filename)]
94 result = subprocess.run(cmd, capture_output=True, check=True, text=True, input=fixed_text)
95 return result.stdout
98def dump_item(item, name, output):
99 logging.info(" Building %s...", name)
100 output = pathlib.Path(output)
101 fields = {
102 "name": "linuxpy.codegen.usbids",
103 "date": datetime.datetime.now(),
104 "system": platform.system(),
105 "release": platform.release(),
106 "version": platform.version(),
107 }
108 text = HEADER.format(**fields)
109 items = {name: item}
110 fields = [f"{item} = {pprint.pformat(values)}\n\n" for item, values in items.items()]
111 text += "\n".join(fields)
113 logging.info(" Applying ruff to %s...", name)
114 text = code_format(text, output)
116 logging.info(" Writting %s...", name)
117 if output is None:
118 print(text, end="", flush=True)
119 else:
120 with output.open("w") as fobj:
121 print(text, end="", flush=True, file=fobj)
124def dump_items(items, output=this_dir.parent / "usb" / "ids"):
125 logging.info(" Building usbids...")
126 output = pathlib.Path(output)
127 for name, data in items.items():
128 dump_item(data, name, output / (name + ".py"))
131def main():
132 logging.basicConfig(level="INFO")
133 logging.info("Starting %s...", __name__)
134 items = get()
135 dump_items(items)
136 logging.info("Finished %s!", __name__)
139if __name__ == "__main__":
140 main()