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

1# run with: 

2# 

3 

4import datetime 

5import logging 

6import pathlib 

7import platform 

8import pprint 

9import subprocess 

10 

11import requests 

12 

13this_dir = pathlib.Path(__file__).parent 

14 

15 

16USB_IDS = "http://www.linux-usb.org/usb.ids" 

17 

18 

19def get_raw(url: str = USB_IDS) -> str: 

20 response = requests.get(url) 

21 response.raise_for_status() 

22 return response.text 

23 

24 

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} 

38 

39 

40def get(url: str = USB_IDS) -> dict[int,]: 

41 logging.info(" Fetching usbids...") 

42 raw = get_raw(url=url) 

43 

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 

69 

70 return items 

71 

72 

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. 

78 

79# This file has been generated by {name} 

80# Date: {date} 

81# System: {system} 

82# Release: {release} 

83# Version: {version} 

84 

85""" 

86 

87 

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 

92 

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 

96 

97 

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) 

112 

113 logging.info(" Applying ruff to %s...", name) 

114 text = code_format(text, output) 

115 

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) 

122 

123 

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")) 

129 

130 

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__) 

137 

138 

139if __name__ == "__main__": 

140 main()