tan-cli 0.7.0__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.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Headless serial capture: pure logic for `tan monitor --capture`
|
|
3
|
+
(tan-cli#1324).
|
|
4
|
+
|
|
5
|
+
Reads from an already-open port-like object (`read(size) -> bytes`, optional
|
|
6
|
+
`in_waiting`) for a fixed duration or until a regex matches, optionally
|
|
7
|
+
teeing the raw bytes to a binary sink. No TTY, no miniterm, no port opening;
|
|
8
|
+
the caller owns the port and the sink. The clock is injected so the timing is
|
|
9
|
+
testable without sleeping.
|
|
10
|
+
|
|
11
|
+
`--until` semantics: the regex is searched in every COMPLETE line (CR/LF
|
|
12
|
+
stripped) exactly once, and in the current UNTERMINATED line (so a prompt such
|
|
13
|
+
as `=> ` matches before any newline arrives), at most the last
|
|
14
|
+
`MAX_SEARCH_CHARS` of it per read. Long complete lines are searched on their
|
|
15
|
+
LAST `MAX_SEARCH_CHARS` too, the same window as the partial line. The deadline is checked between lines, so a
|
|
16
|
+
read that delivers thousands of lines cannot overrun `--duration`. Python's
|
|
17
|
+
`re` cannot be interrupted, so a pathological (catastrophic-backtracking)
|
|
18
|
+
pattern can still stall inside ONE search; the cap on the searched text and
|
|
19
|
+
the per-line deadline check bound the damage, they do not remove it.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
import time
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import TYPE_CHECKING, BinaryIO, Protocol
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from tan.core.serial_actions import CaptureActions
|
|
32
|
+
|
|
33
|
+
TAIL_BYTES = 256
|
|
34
|
+
#: No single `re.search` is given more than this many characters.
|
|
35
|
+
MAX_SEARCH_CHARS = 4096
|
|
36
|
+
#: A partial (unterminated) line longer than this is truncated from the left
|
|
37
|
+
#: so a binary stream with no newline cannot grow memory without bound.
|
|
38
|
+
_MAX_PENDING = 65536
|
|
39
|
+
#: Duration used when `--until` is given without `--duration`.
|
|
40
|
+
DEFAULT_UNTIL_DURATION_S = 30.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ReadPort(Protocol):
|
|
44
|
+
def read(self, size: int = 1, /) -> bytes: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SinkError(Exception):
|
|
48
|
+
"""Writing the log failed (kept apart from serial I/O errors)."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class CaptureResult:
|
|
53
|
+
matched: bool
|
|
54
|
+
matched_line: str | None
|
|
55
|
+
elapsed_s: float
|
|
56
|
+
bytes_seen: int
|
|
57
|
+
tail: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def compile_until(pattern: str) -> re.Pattern[str]:
|
|
61
|
+
"""Compile `--until`; `ValueError` (with the regex error) if malformed or
|
|
62
|
+
empty, so the caller can map it to one validation issue."""
|
|
63
|
+
if not pattern:
|
|
64
|
+
raise ValueError("--until must not be empty")
|
|
65
|
+
try:
|
|
66
|
+
return re.compile(pattern)
|
|
67
|
+
except re.error as err:
|
|
68
|
+
raise ValueError(f"--until is not a valid regex: {err}") from err
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _text(b: bytes) -> str:
|
|
72
|
+
return b.decode("utf-8", errors="replace")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _tail_text(buf: bytes) -> str:
|
|
76
|
+
if len(buf) > TAIL_BYTES:
|
|
77
|
+
buf = buf[-TAIL_BYTES:]
|
|
78
|
+
while buf and 0x80 <= buf[0] < 0xC0: # do not start mid-character
|
|
79
|
+
buf = buf[1:]
|
|
80
|
+
return _text(buf)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def capture(
|
|
84
|
+
port: ReadPort,
|
|
85
|
+
*,
|
|
86
|
+
duration_s: float,
|
|
87
|
+
until: re.Pattern[str] | None = None,
|
|
88
|
+
sink: BinaryIO | None = None,
|
|
89
|
+
initial: bytes = b"",
|
|
90
|
+
clock: Callable[[], float] = time.monotonic,
|
|
91
|
+
actions: "CaptureActions | None" = None,
|
|
92
|
+
) -> CaptureResult:
|
|
93
|
+
"""Read until `until` matches, or `duration_s` elapses.
|
|
94
|
+
|
|
95
|
+
On a match the capture stops at the end of the matched line: the rest of
|
|
96
|
+
the same read is not logged or counted. `initial` is data already received before this call (the `--break-uboot`
|
|
97
|
+
tail): it is logged and searched like any other chunk, so a prompt that
|
|
98
|
+
ended the break-in can satisfy `--until`. At least one read happens even
|
|
99
|
+
with a zero duration. A sink failure raises `SinkError`; a port failure
|
|
100
|
+
propagates as the port's own `OSError`.
|
|
101
|
+
|
|
102
|
+
`actions` (tan-cli#1451, `tan.core.serial_actions`): ticked every loop
|
|
103
|
+
iteration and offered each complete line and the current partial line
|
|
104
|
+
BEFORE `until` is tested. A reopen replaces the port, so the loop reads
|
|
105
|
+
`actions.port` rather than `port` when given. Lines are scanned when
|
|
106
|
+
either `until` or a line-triggered action is present.
|
|
107
|
+
"""
|
|
108
|
+
start = clock()
|
|
109
|
+
deadline = start + duration_s
|
|
110
|
+
pending = b""
|
|
111
|
+
tail = b""
|
|
112
|
+
seen = 0
|
|
113
|
+
first = True
|
|
114
|
+
|
|
115
|
+
def commit(part: bytes) -> None:
|
|
116
|
+
"""Count, tail and log `part`: what the capture reports as received."""
|
|
117
|
+
nonlocal seen, tail
|
|
118
|
+
seen += len(part)
|
|
119
|
+
tail = (tail + part)[-TAIL_BYTES:]
|
|
120
|
+
if sink is not None and part:
|
|
121
|
+
try:
|
|
122
|
+
sink.write(part)
|
|
123
|
+
sink.flush()
|
|
124
|
+
except OSError as err:
|
|
125
|
+
raise SinkError(str(err)) from err
|
|
126
|
+
|
|
127
|
+
def done(matched: bool, line: str | None) -> CaptureResult:
|
|
128
|
+
return CaptureResult(matched, line, clock() - start, seen, _tail_text(tail))
|
|
129
|
+
|
|
130
|
+
while True:
|
|
131
|
+
if first and initial:
|
|
132
|
+
chunk = initial
|
|
133
|
+
else:
|
|
134
|
+
if actions is not None:
|
|
135
|
+
actions.tick()
|
|
136
|
+
src = actions.port if actions is not None else port
|
|
137
|
+
n = max(1, int(getattr(src, "in_waiting", 0) or 0))
|
|
138
|
+
chunk = src.read(n) or b""
|
|
139
|
+
first = False
|
|
140
|
+
scan = until is not None or (actions is not None and actions.spec.watches_lines)
|
|
141
|
+
if chunk and not scan:
|
|
142
|
+
commit(chunk)
|
|
143
|
+
elif chunk:
|
|
144
|
+
before = len(pending)
|
|
145
|
+
*lines, pending = (pending + chunk).split(b"\n")
|
|
146
|
+
pending = pending[-_MAX_PENDING:]
|
|
147
|
+
# A read that returns at or after the deadline is still searched
|
|
148
|
+
# whole (tan-cli#1515). Its size is bounded by `in_waiting` (and
|
|
149
|
+
# each line by MAX_SEARCH_CHARS), so it needs no flood guard. Only
|
|
150
|
+
# scanning that itself overruns the deadline stops, and then only
|
|
151
|
+
# the lines already searched are committed, so the log never holds
|
|
152
|
+
# a line the result says was not searched.
|
|
153
|
+
late = clock() >= deadline
|
|
154
|
+
end = -before # offset of the current line's end within `chunk`
|
|
155
|
+
for raw in lines: # each complete line is new data: search it once
|
|
156
|
+
end += len(raw) + 1
|
|
157
|
+
line = _text(raw).rstrip("\r")[-MAX_SEARCH_CHARS:]
|
|
158
|
+
if actions is not None:
|
|
159
|
+
actions.line(line)
|
|
160
|
+
if until is not None and until.search(line):
|
|
161
|
+
# Stop at the END OF THE MATCHED LINE: bytes after it in the
|
|
162
|
+
# same read are neither logged nor reported.
|
|
163
|
+
commit(chunk[: max(0, end)])
|
|
164
|
+
return done(True, line)
|
|
165
|
+
if not late and clock() >= deadline:
|
|
166
|
+
commit(chunk[: max(0, end)])
|
|
167
|
+
return done(False, None)
|
|
168
|
+
if actions is not None and actions.take_flush():
|
|
169
|
+
# Baud changed: the rest of this read and the partial line
|
|
170
|
+
# are old-baud garbage; keep only up to the matched line.
|
|
171
|
+
commit(chunk[: max(0, end)])
|
|
172
|
+
pending, flushed = b"", True
|
|
173
|
+
break
|
|
174
|
+
else:
|
|
175
|
+
flushed = False
|
|
176
|
+
if flushed:
|
|
177
|
+
continue
|
|
178
|
+
commit(chunk)
|
|
179
|
+
partial = _text(pending).rstrip("\r")[-MAX_SEARCH_CHARS:]
|
|
180
|
+
if partial and actions is not None:
|
|
181
|
+
actions.line(partial, partial=True)
|
|
182
|
+
if partial and until is not None and until.search(partial):
|
|
183
|
+
return done(True, partial)
|
|
184
|
+
if clock() >= deadline:
|
|
185
|
+
return done(False, None)
|
tan/core/serial_url.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Up-front validation of a `--port` that is a pyserial URL (tan-cli#1448).
|
|
3
|
+
|
|
4
|
+
`rfc2217://100.64.0.1:` (an empty port number) used to reach pyserial, which
|
|
5
|
+
failed deep inside with `'<=' not supported between instances of 'int' and
|
|
6
|
+
'NoneType'`, reported as `monitor.capture-open-failed`. This names the problem
|
|
7
|
+
instead. A plain device path (no `://`) is not a URL and is never judged here.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from urllib.parse import urlsplit
|
|
12
|
+
|
|
13
|
+
#: pyserial's `serial.urlhandler` schemes (3.5). A scheme outside this set is
|
|
14
|
+
#: refused rather than handed to pyserial to fail obscurely.
|
|
15
|
+
KNOWN_SCHEMES = frozenset({"alt", "cp2110", "hwgrep", "loop", "rfc2217", "socket", "spy"})
|
|
16
|
+
#: The schemes that name a TCP endpoint and so need `host:port`.
|
|
17
|
+
NETWORK_SCHEMES = frozenset({"rfc2217", "socket"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def port_url_problem(port: str) -> str | None:
|
|
21
|
+
"""What is wrong with `port` as a serial URL, or `None` if it is fine or not a URL."""
|
|
22
|
+
if "://" not in port:
|
|
23
|
+
return None
|
|
24
|
+
scheme = port.split("://", 1)[0].lower()
|
|
25
|
+
if scheme not in KNOWN_SCHEMES:
|
|
26
|
+
return (
|
|
27
|
+
f"unknown serial URL scheme '{scheme}://' in '{port}' "
|
|
28
|
+
f"(known: {', '.join(sorted(s + '://' for s in KNOWN_SCHEMES))})"
|
|
29
|
+
)
|
|
30
|
+
if scheme not in NETWORK_SCHEMES:
|
|
31
|
+
return None
|
|
32
|
+
try:
|
|
33
|
+
parts = urlsplit(port)
|
|
34
|
+
host, number = parts.hostname, parts.port
|
|
35
|
+
except ValueError as err:
|
|
36
|
+
return f"'{port}' is not a valid {scheme}:// URL: {err}"
|
|
37
|
+
if not host:
|
|
38
|
+
return f"'{port}' has no host; use {scheme}://HOST:PORT"
|
|
39
|
+
if number is None:
|
|
40
|
+
netloc = parts.netloc
|
|
41
|
+
why = "the port number is empty" if netloc.endswith(":") else "the port number is missing"
|
|
42
|
+
return f"'{port}': {why}; use {scheme}://{host}:PORT (1-65535)"
|
|
43
|
+
if not 1 <= number <= 65535:
|
|
44
|
+
return f"'{port}': port {number} is outside 1-65535"
|
|
45
|
+
return None
|