micropython-stubber 1.24.1__py3-none-any.whl → 1.24.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/METADATA +9 -29
  2. micropython_stubber-1.24.4.dist-info/RECORD +107 -0
  3. {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/WHEEL +1 -1
  4. stubber/__init__.py +1 -1
  5. stubber/board/createstubs.py +44 -38
  6. stubber/board/createstubs_db.py +17 -12
  7. stubber/board/createstubs_db_min.py +63 -63
  8. stubber/board/createstubs_db_mpy.mpy +0 -0
  9. stubber/board/createstubs_mem.py +17 -12
  10. stubber/board/createstubs_mem_min.py +99 -99
  11. stubber/board/createstubs_mem_mpy.mpy +0 -0
  12. stubber/board/createstubs_min.py +111 -112
  13. stubber/board/createstubs_mpy.mpy +0 -0
  14. stubber/board/modulelist.txt +27 -27
  15. stubber/codemod/board.py +1 -1
  16. stubber/codemod/enrich.py +13 -13
  17. stubber/codemod/merge_docstub.py +83 -53
  18. stubber/codemod/visitors/type_helpers.py +143 -41
  19. stubber/commands/enrich_folder_cmd.py +17 -17
  20. stubber/commands/get_docstubs_cmd.py +27 -9
  21. stubber/commands/get_frozen_cmd.py +1 -0
  22. stubber/commands/merge_cmd.py +2 -4
  23. stubber/merge_config.py +5 -36
  24. stubber/minify.py +3 -3
  25. stubber/modcat.py +118 -0
  26. stubber/publish/merge_docstubs.py +22 -5
  27. stubber/publish/stubpackage.py +33 -28
  28. stubber/rst/lookup.py +6 -23
  29. stubber/rst/reader.py +8 -13
  30. stubber/stubs_from_docs.py +2 -1
  31. stubber/tools/manifestfile.py +2 -1
  32. stubber/{cst_transformer.py → typing_collector.py} +36 -4
  33. micropython_stubber-1.24.1.dist-info/RECORD +0 -161
  34. mpflash/README.md +0 -220
  35. mpflash/libusb_flash.ipynb +0 -203
  36. mpflash/mpflash/__init__.py +0 -0
  37. mpflash/mpflash/add_firmware.py +0 -98
  38. mpflash/mpflash/ask_input.py +0 -236
  39. mpflash/mpflash/basicgit.py +0 -324
  40. mpflash/mpflash/bootloader/__init__.py +0 -2
  41. mpflash/mpflash/bootloader/activate.py +0 -60
  42. mpflash/mpflash/bootloader/detect.py +0 -82
  43. mpflash/mpflash/bootloader/manual.py +0 -101
  44. mpflash/mpflash/bootloader/micropython.py +0 -12
  45. mpflash/mpflash/bootloader/touch1200.py +0 -36
  46. mpflash/mpflash/cli_download.py +0 -129
  47. mpflash/mpflash/cli_flash.py +0 -224
  48. mpflash/mpflash/cli_group.py +0 -111
  49. mpflash/mpflash/cli_list.py +0 -87
  50. mpflash/mpflash/cli_main.py +0 -39
  51. mpflash/mpflash/common.py +0 -217
  52. mpflash/mpflash/config.py +0 -44
  53. mpflash/mpflash/connected.py +0 -96
  54. mpflash/mpflash/download.py +0 -364
  55. mpflash/mpflash/downloaded.py +0 -138
  56. mpflash/mpflash/errors.py +0 -9
  57. mpflash/mpflash/flash/__init__.py +0 -55
  58. mpflash/mpflash/flash/esp.py +0 -59
  59. mpflash/mpflash/flash/stm32.py +0 -19
  60. mpflash/mpflash/flash/stm32_dfu.py +0 -104
  61. mpflash/mpflash/flash/uf2/__init__.py +0 -88
  62. mpflash/mpflash/flash/uf2/boardid.py +0 -15
  63. mpflash/mpflash/flash/uf2/linux.py +0 -136
  64. mpflash/mpflash/flash/uf2/macos.py +0 -42
  65. mpflash/mpflash/flash/uf2/uf2disk.py +0 -12
  66. mpflash/mpflash/flash/uf2/windows.py +0 -43
  67. mpflash/mpflash/flash/worklist.py +0 -170
  68. mpflash/mpflash/list.py +0 -106
  69. mpflash/mpflash/logger.py +0 -41
  70. mpflash/mpflash/mpboard_id/__init__.py +0 -98
  71. mpflash/mpflash/mpboard_id/add_boards.py +0 -262
  72. mpflash/mpflash/mpboard_id/board.py +0 -37
  73. mpflash/mpflash/mpboard_id/board_id.py +0 -90
  74. mpflash/mpflash/mpboard_id/board_info.zip +0 -0
  75. mpflash/mpflash/mpboard_id/store.py +0 -48
  76. mpflash/mpflash/mpremoteboard/__init__.py +0 -271
  77. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +0 -152
  78. mpflash/mpflash/mpremoteboard/runner.py +0 -140
  79. mpflash/mpflash/vendor/board_database.py +0 -185
  80. mpflash/mpflash/vendor/click_aliases.py +0 -91
  81. mpflash/mpflash/vendor/dfu.py +0 -165
  82. mpflash/mpflash/vendor/pydfu.py +0 -605
  83. mpflash/mpflash/vendor/readme.md +0 -12
  84. mpflash/mpflash/versions.py +0 -123
  85. mpflash/poetry.lock +0 -2603
  86. mpflash/pyproject.toml +0 -66
  87. mpflash/stm32_udev_rules.md +0 -63
  88. stubber/codemod/test_enrich.py +0 -87
  89. {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/LICENSE +0 -0
  90. {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/entry_points.txt +0 -0
@@ -1,271 +0,0 @@
1
- """
2
- Module to run mpremote commands, and retry on failure or timeout
3
- """
4
-
5
- import sys
6
- import time
7
- from pathlib import Path
8
- from typing import List, Optional, Union
9
-
10
- import serial.tools.list_ports
11
- from rich.progress import track
12
- from tenacity import retry, stop_after_attempt, wait_fixed
13
-
14
- from mpflash.errors import MPFlashError
15
- from mpflash.logger import log
16
- from mpflash.mpboard_id.board_id import find_board_id_by_description
17
- from mpflash.mpremoteboard.runner import run
18
-
19
- if sys.version_info >= (3, 11):
20
- import tomllib # type: ignore
21
- else:
22
- import tomli as tomllib # type: ignore
23
-
24
- ###############################################################################################
25
- HERE = Path(__file__).parent
26
-
27
- OK = 0
28
- ERROR = -1
29
- RETRIES = 3
30
- ###############################################################################################
31
-
32
-
33
- class MPRemoteBoard:
34
- """Class to run mpremote commands"""
35
-
36
- def __init__(self, serialport: str = "", update: bool = False, *, location: str = ""):
37
- """
38
- Initialize MPRemoteBoard object.
39
-
40
- Parameters:
41
- - serialport (str): The serial port to connect to. Default is an empty string.
42
- - update (bool): Whether to update the MCU information. Default is False.
43
- """
44
- self.serialport: str = serialport
45
- self.firmware = {}
46
-
47
- self.connected = False
48
- self.path: Optional[Path] = None
49
- self.family = "unknown"
50
- self.description = ""
51
- self.version = ""
52
- self.port = ""
53
- self.board = ""
54
- self.cpu = ""
55
- self.arch = ""
56
- self.mpy = ""
57
- self.build = ""
58
- self.location = location # USB location
59
- self.toml = {}
60
- if update:
61
- self.get_mcu_info()
62
-
63
- def __str__(self):
64
- """
65
- Return a string representation of the MPRemoteBoard object.
66
-
67
- Returns:
68
- - str: The string representation of the object.
69
- """
70
- return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
71
-
72
- @staticmethod
73
- def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
74
- # TODO: rename to connected_comports
75
- """
76
- Get a list of connected comports.
77
-
78
- Parameters:
79
- - bluetooth (bool): Whether to include Bluetooth ports. Default is False.
80
-
81
- Returns:
82
- - List[str]: A list of connected board ports.
83
- """
84
- comports = serial.tools.list_ports.comports()
85
-
86
- if not bluetooth:
87
- # filter out bluetooth ports
88
- comports = [p for p in comports if "bluetooth" not in p.description.lower()]
89
- comports = [p for p in comports if "BTHENUM" not in p.hwid]
90
- if description:
91
- output = [
92
- f"{p.device} {(p.manufacturer + ' ') if p.manufacturer and not p.description.startswith(p.manufacturer) else ''}{p.description}"
93
- for p in comports
94
- ]
95
- else:
96
- output = [p.device for p in comports]
97
-
98
- if sys.platform == "win32":
99
- # Windows sort of comports by number - but fallback to device name
100
- return sorted(
101
- output, key=lambda x: int(x.split()[0][3:]) if x.split()[0][3:].isdigit() else x
102
- )
103
- # sort by device name
104
- return sorted(output)
105
-
106
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
107
- def get_mcu_info(self, timeout: int = 2):
108
- """
109
- Get MCU information from the connected board.
110
-
111
- Parameters:
112
- - timeout (int): The timeout value in seconds. Default is 2.
113
-
114
- Raises:
115
- - ConnectionError: If failed to get mcu_info for the serial port.
116
- """
117
- rc, result = self.run_command(
118
- ["run", str(HERE / "mpy_fw_info.py")],
119
- no_info=True,
120
- timeout=timeout,
121
- resume=False, # Avoid restarts
122
- )
123
- if rc:
124
- log.debug(f"rc: {rc}, result: {result}")
125
- raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
126
- # Ok we have the info, now parse it
127
- raw_info = result[0].strip()
128
- if raw_info.startswith("{") and raw_info.endswith("}"):
129
- info = eval(raw_info)
130
- self.family = info["family"]
131
- self.version = info["version"]
132
- self.build = info["build"]
133
- self.port = info["port"]
134
- self.cpu = info["cpu"]
135
- self.arch = info["arch"]
136
- self.mpy = info["mpy"]
137
- self.description = descr = info["board"]
138
- pos = descr.rfind(" with")
139
- short_descr = descr[:pos].strip() if pos != -1 else ""
140
- if board_name := find_board_id_by_description(
141
- descr, short_descr, version=self.version
142
- ):
143
- self.board = board_name
144
- else:
145
- self.board = "UNKNOWN_BOARD"
146
- # get the board_info.toml
147
- self.get_board_info_toml()
148
- # now we know the board is connected
149
- self.connected = True
150
-
151
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(0.2), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
152
- def get_board_info_toml(self, timeout: int = 1):
153
- """
154
- Reads the content of the board_info.toml file from the connected board,
155
- and adds that to the board object.
156
-
157
- Parameters:
158
- - timeout (int): The timeout value in seconds.
159
-
160
- Raises:
161
- - ConnectionError: If failed to communicate with the serial port.
162
- """
163
- try:
164
- rc, result = self.run_command(
165
- ["cat", ":board_info.toml"],
166
- no_info=True,
167
- timeout=timeout,
168
- log_errors=False,
169
- )
170
- except Exception as e:
171
- raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}: {e}")
172
- # this is optional - so only parse if we got the file
173
- self.toml = {}
174
- if rc in [OK]: # sometimes we get an -9 ???
175
- try:
176
- # Ok we have the info, now parse it
177
- self.toml = tomllib.loads("".join(result))
178
- log.debug(f"board_info.toml: {self.toml}")
179
- except Exception as e:
180
- log.error(f"Failed to parse board_info.toml: {e}")
181
- else:
182
- log.trace(f"Did not find a board_info.toml: {result}")
183
-
184
- def disconnect(self) -> bool:
185
- """
186
- Disconnect from a board.
187
-
188
- Returns:
189
- - bool: True if successfully disconnected, False otherwise.
190
- """
191
- if not self.connected:
192
- return True
193
- if not self.serialport:
194
- log.error("No port connected")
195
- self.connected = False
196
- return False
197
- log.info(f"Disconnecting from {self.serialport}")
198
- result = self.run_command(["disconnect"])[0] == OK
199
- self.connected = False
200
- return result
201
-
202
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(2), reraise=True)
203
- def run_command(
204
- self,
205
- cmd: Union[str, List[str]],
206
- *,
207
- log_errors: bool = True,
208
- no_info: bool = False,
209
- timeout: int = 60,
210
- resume: Optional[bool] = None,
211
- **kwargs,
212
- ):
213
- """
214
- Run mpremote with the given command.
215
-
216
- Parameters:
217
- - cmd (Union[str, List[str]]): The command to run, either a string or a list of strings.
218
- - log_errors (bool): Whether to log errors. Default is True.
219
- - no_info (bool): Whether to skip printing info. Default is False.
220
- - timeout (int): The timeout value in seconds. Default is 60.
221
-
222
- Returns:
223
- - bool: True if the command succeeded, False otherwise.
224
- """
225
- if isinstance(cmd, str):
226
- cmd = cmd.split(" ")
227
- prefix = [sys.executable, "-m", "mpremote"]
228
- if self.serialport:
229
- prefix += ["connect", self.serialport]
230
- # if connected add resume to keep state between commands
231
- if (resume != False) and self.connected or resume:
232
- prefix += ["resume"]
233
- cmd = prefix + cmd
234
- log.debug(" ".join(cmd))
235
- result = run(cmd, timeout, log_errors, no_info, **kwargs)
236
- self.connected = result[0] == OK
237
- return result
238
-
239
- @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1))
240
- def mip_install(self, name: str) -> bool:
241
- """
242
- Install a micropython package.
243
-
244
- Parameters:
245
- - name (str): The name of the package to install.
246
-
247
- Returns:
248
- - bool: True if the installation succeeded, False otherwise.
249
- """
250
- # install createstubs to the board
251
- cmd = ["mip", "install", name]
252
- result = self.run_command(cmd)[0] == OK
253
- self.connected = True
254
- return result
255
-
256
- def wait_for_restart(self, timeout: int = 10):
257
- """wait for the board to restart"""
258
- for _ in track(
259
- range(timeout),
260
- description=f"Waiting for the board to restart ({timeout}s)",
261
- transient=True,
262
- show_speed=False,
263
- refresh_per_second=1,
264
- total=timeout,
265
- ):
266
- time.sleep(1)
267
- try:
268
- self.get_mcu_info()
269
- break
270
- except (ConnectionError, MPFlashError):
271
- pass
@@ -1,152 +0,0 @@
1
- # %%micropython
2
- import os
3
- import sys
4
-
5
-
6
- def _build(s):
7
- # extract build from sys.version or os.uname().version if available
8
- # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
9
- # sys.implementation.version: 'v1.13-103-gb137d064e'
10
- if not s:
11
- return ""
12
- s = s.split(" on ", 1)[0] if " on " in s else s
13
- if s.startswith("v"):
14
- if not "-" in s:
15
- return ""
16
- b = s.split("-")[1]
17
- return b
18
- if not "-preview" in s:
19
- return ""
20
- b = s.split("-preview")[1].split(".")[1]
21
- return b
22
-
23
-
24
- def _version_str(version: tuple): # -> str:
25
- v_str = ".".join([str(n) for n in version[:3]])
26
- if len(version) > 3 and version[3]:
27
- v_str += "-" + version[3]
28
- return v_str
29
-
30
-
31
- def _info(): # type:() -> dict[str, str]
32
- # sourcery skip: use-contextlib-suppress, use-fstring-for-formatting, use-named-expression
33
- info = dict(
34
- {
35
- "family": sys.implementation[0], # type: ignore
36
- "version": "",
37
- "build": "",
38
- "ver": "",
39
- "port": (
40
- "stm32" if sys.platform.startswith("pyb") else sys.platform
41
- ), # port: esp32 / win32 / linux / stm32
42
- "board": "GENERIC",
43
- "cpu": "",
44
- "mpy": "",
45
- "arch": "",
46
- }
47
- )
48
- try:
49
- info["version"] = _version_str(sys.implementation.version)
50
- except AttributeError:
51
- pass
52
- try:
53
- machine = (
54
- sys.implementation._machine
55
- if "_machine" in dir(sys.implementation)
56
- else os.uname().machine
57
- )
58
- info["board"] = machine.strip()
59
- info["cpu"] = machine.split("with")[-1].strip() if "with" in machine else ""
60
- info["mpy"] = (
61
- sys.implementation._mpy
62
- if "_mpy" in dir(sys.implementation)
63
- else sys.implementation.mpy if "mpy" in dir(sys.implementation) else ""
64
- )
65
- except (AttributeError, IndexError):
66
- pass
67
-
68
- try:
69
- if hasattr(sys, "version"):
70
- info["build"] = _build(sys.version)
71
- elif hasattr(os, "uname"):
72
- info["build"] = _build(os.uname()[3])
73
- if not info["build"]:
74
- # extract build from uname().release if available
75
- info["build"] = _build(os.uname()[2])
76
- except (AttributeError, IndexError):
77
- pass
78
- # avoid build hashes
79
- if info["build"] and len(info["build"]) > 5:
80
- info["build"] = ""
81
-
82
- if info["version"] == "" and sys.platform not in ("unix", "win32"):
83
- try:
84
- u = os.uname()
85
- info["version"] = u.release
86
- except (IndexError, AttributeError, TypeError):
87
- pass
88
- # detect families
89
- for fam_name, mod_name, mod_thing in [
90
- ("pycopy", "pycopy", "const"),
91
- ("pycom", "pycom", "FAT"),
92
- ("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
93
- ]:
94
- try:
95
- _t = __import__(mod_name, None, None, (mod_thing))
96
- info["family"] = fam_name
97
- del _t
98
- break
99
- except (ImportError, KeyError):
100
- pass
101
-
102
- if info["family"] == "ev3-pybricks":
103
- info["release"] = "2.0.0"
104
-
105
- if info["family"] == "micropython":
106
- if (
107
- info["version"]
108
- and info["version"].endswith(".0")
109
- and info["version"]
110
- >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
111
- and info["version"] <= "1.19.9"
112
- ):
113
- # drop the .0 for newer releases
114
- info["version"] = info["version"][:-2]
115
-
116
- # spell-checker: disable
117
- if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
118
- sys_mpy = int(info["mpy"])
119
- # .mpy architecture
120
- try:
121
- arch = [
122
- None,
123
- "x86",
124
- "x64",
125
- "armv6",
126
- "armv6m",
127
- "armv7m",
128
- "armv7em",
129
- "armv7emsp",
130
- "armv7emdp",
131
- "xtensa",
132
- "xtensawin",
133
- "hazard3riscv", # assumed
134
- ][sys_mpy >> 10]
135
- except IndexError:
136
- arch = "unknown"
137
- if arch:
138
- info["arch"] = arch
139
- # .mpy version.minor
140
- info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
141
- # simple to use version[-build] string avoiding f-strings for backward compat
142
- info["ver"] = (
143
- "v{version}-{build}".format(version=info["version"], build=info["build"])
144
- if info["build"]
145
- else "v{version}".format(version=info["version"])
146
- )
147
-
148
- return info
149
-
150
-
151
- print(_info())
152
- del _info, _build, _version_str
@@ -1,140 +0,0 @@
1
- """
2
- Run a command and return the output and return code as a tuple
3
- """
4
-
5
- import subprocess
6
- from dataclasses import dataclass
7
- from threading import Timer
8
- from typing import List, Optional, Tuple
9
-
10
- from loguru import logger as log
11
-
12
- LogTagList = List[str]
13
-
14
-
15
- @dataclass
16
- class LogTags:
17
- reset_tags: LogTagList
18
- error_tags: LogTagList
19
- warning_tags: LogTagList
20
- success_tags: LogTagList
21
- ignore_tags: LogTagList
22
-
23
-
24
- DEFAULT_RESET_TAGS = [
25
- # ESP32 reset causes
26
- "rst cause:1, boot mode:", # 1 -> hardware watch dog reset
27
- "rst cause:2, boot mode:", # 2 -> software watch dog reset (From an exception)
28
- "rst cause:3, boot mode:", # 3 -> software watch dog reset system_restart (Possibly unfed watchdog got angry)
29
- "rst cause:4, boot mode:", # 4 -> soft restart (Possibly with a restart command)
30
- "boot.esp32: PRO CPU has been reset by WDT.",
31
- "rst:0x10 (RTCWDT_RTC_RESET)",
32
- ]
33
-
34
-
35
- def run(
36
- cmd: List[str],
37
- timeout: int = 60,
38
- log_errors: bool = True,
39
- no_info: bool = False,
40
- *,
41
- log_warnings: bool = False,
42
- reset_tags: Optional[LogTagList] = None,
43
- error_tags: Optional[LogTagList] = None,
44
- warning_tags: Optional[LogTagList] = None,
45
- success_tags: Optional[LogTagList] = None,
46
- ignore_tags: Optional[LogTagList] = None,
47
- ) -> Tuple[int, List[str]]:
48
- # sourcery skip: no-long-functions
49
- """
50
- Run a command and return the output and return code as a tuple
51
- Parameters
52
- ----------
53
- cmd : List[str]
54
- The command to run
55
- timeout : int, optional
56
- The timeout in seconds, by default 60
57
- log_errors : bool, optional
58
- If False, don't log errors, Default: true
59
- no_info : bool, optional
60
- If True, don't log info, by default False
61
- error_tags : Optional[LogTagList], optional
62
- A list of strings to look for in the output to log as errors, by default None
63
- warning_tags : Optional[LogTagList], optional
64
- A list of strings to look for in the output to log as warnings, by default None
65
- Returns
66
- -------
67
- Tuple[int, List[str]]
68
- The return code and the output as a list of strings
69
- """
70
- if not reset_tags:
71
- reset_tags = DEFAULT_RESET_TAGS
72
- if not error_tags:
73
- error_tags = ["Traceback ", "Error: ", "Exception: ", "ERROR :", "CRIT :"]
74
- if not warning_tags:
75
- warning_tags = ["WARN :", "TRACE :"]
76
- if not success_tags:
77
- success_tags = []
78
- if not ignore_tags:
79
- ignore_tags = [' File "<stdin>",']
80
-
81
- replace_tags = ["\x1b[1A"]
82
-
83
- output = []
84
- try:
85
- proc = subprocess.Popen(
86
- cmd,
87
- stdout=subprocess.PIPE,
88
- stderr=subprocess.PIPE,
89
- universal_newlines=True,
90
- encoding="utf-8",
91
- )
92
- except FileNotFoundError as e:
93
- raise FileNotFoundError(f"Failed to start {cmd[0]}") from e
94
-
95
- def timed_out():
96
- proc.kill()
97
- if log_warnings:
98
- log.warning(f"Command {cmd} timed out after {timeout} seconds")
99
-
100
- timer = Timer(timeout, timed_out)
101
- try:
102
- timer.start()
103
- # stdout has most of the output, assign log categories based on text tags
104
- if proc.stdout:
105
- for line in proc.stdout:
106
- if not line or not line.strip():
107
- continue
108
- for tag in replace_tags:
109
- line = line.replace(tag, "")
110
- output.append(line) # full output, no trimming
111
- if any(tag in line for tag in reset_tags):
112
- raise RuntimeError("Board reset detected")
113
-
114
- line = line.rstrip("\n")
115
- # if any of the error tags in the line
116
- if any(tag in line for tag in error_tags):
117
- if not log_errors:
118
- continue
119
- log.error(line)
120
- elif any(tag in line for tag in warning_tags):
121
- log.warning(line)
122
- elif any(tag in line for tag in success_tags):
123
- log.success(line)
124
- elif any(tag in line for tag in ignore_tags):
125
- continue
126
- else:
127
- if not no_info:
128
- if line.startswith(("INFO : ", "WARN : ", "ERROR : ")):
129
- line = line[8:].lstrip()
130
- log.info(line)
131
- if proc.stderr and log_errors:
132
- for line in proc.stderr:
133
- log.warning(line)
134
- except UnicodeDecodeError as e:
135
- log.error(f"Failed to decode output: {e}")
136
- finally:
137
- timer.cancel()
138
-
139
- proc.wait(timeout=1)
140
- return proc.returncode or 0, output