Coverage for src / tracekit / cli / shell.py: 99%
69 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Interactive REPL shell for TraceKit exploration.
3This module provides an interactive Python shell with TraceKit auto-imports,
4tab completion, and persistent history for exploratory data analysis.
6 - Auto-imports TraceKit modules
7 - Tab completion for methods and attributes
8 - Persistent command history
9 - Customized prompt with context info
11Example:
12 $ tracekit shell
13 TraceKit Shell v0.1.0
14 Type 'help()' for TraceKit help, 'exit()' to quit.
16 In [1]: trace = load("capture.wfm")
17 In [2]: rise_time(trace)
18 Out[2]: 2.5e-9
19 In [3]: freq, mag = fft(trace)
21References:
22 - Python readline module
23 - IPython-style interaction patterns
24"""
26from __future__ import annotations
28import atexit
29import code
30import contextlib
31import readline
32import rlcompleter
33import sys
34from pathlib import Path
35from typing import Any
37# History file location
38HISTORY_FILE = Path.home() / ".tracekit_history"
39HISTORY_LENGTH = 1000
42def get_tracekit_namespace() -> dict[str, Any]:
43 """Build namespace with TraceKit auto-imports.
45 Returns:
46 Dictionary with all commonly-used TraceKit functions and classes.
47 """
48 namespace: dict[str, Any] = {}
50 # Core imports
51 try:
52 import tracekit as tk
54 namespace["tk"] = tk
56 # Auto-import commonly used functions at top level
57 from tracekit import (
58 DigitalTrace,
59 ProtocolPacket,
60 TraceMetadata,
61 # Core types
62 WaveformTrace,
63 # Math
64 add,
65 amplitude,
66 band_pass,
67 band_stop,
68 # Statistics
69 basic_stats,
70 detect_edges,
71 differentiate,
72 divide,
73 duty_cycle,
74 enob,
75 fall_time,
76 # Spectral
77 fft,
78 frequency,
79 get_supported_formats,
80 high_pass,
81 histogram,
82 integrate,
83 # Loaders
84 load,
85 # Filtering
86 low_pass,
87 mean,
88 measure,
89 multiply,
90 overshoot,
91 percentiles,
92 period,
93 psd,
94 pulse_width,
95 # Measurements
96 rise_time,
97 rms,
98 sfdr,
99 sinad,
100 snr,
101 spectrogram,
102 subtract,
103 thd,
104 # Digital
105 to_digital,
106 undershoot,
107 )
109 namespace.update(
110 {
111 "WaveformTrace": WaveformTrace,
112 "DigitalTrace": DigitalTrace,
113 "TraceMetadata": TraceMetadata,
114 "ProtocolPacket": ProtocolPacket,
115 "load": load,
116 "get_supported_formats": get_supported_formats,
117 "rise_time": rise_time,
118 "fall_time": fall_time,
119 "frequency": frequency,
120 "period": period,
121 "amplitude": amplitude,
122 "rms": rms,
123 "mean": mean,
124 "overshoot": overshoot,
125 "undershoot": undershoot,
126 "duty_cycle": duty_cycle,
127 "pulse_width": pulse_width,
128 "measure": measure,
129 "fft": fft,
130 "psd": psd,
131 "thd": thd,
132 "snr": snr,
133 "sinad": sinad,
134 "enob": enob,
135 "sfdr": sfdr,
136 "spectrogram": spectrogram,
137 "to_digital": to_digital,
138 "detect_edges": detect_edges,
139 "low_pass": low_pass,
140 "high_pass": high_pass,
141 "band_pass": band_pass,
142 "band_stop": band_stop,
143 "add": add,
144 "subtract": subtract,
145 "multiply": multiply,
146 "divide": divide,
147 "differentiate": differentiate,
148 "integrate": integrate,
149 "basic_stats": basic_stats,
150 "histogram": histogram,
151 "percentiles": percentiles,
152 }
153 )
155 # Protocol decoders
156 try:
157 from tracekit.analyzers.protocols import (
158 decode_can,
159 decode_i2c,
160 decode_spi,
161 decode_uart,
162 )
164 namespace.update(
165 {
166 "decode_uart": decode_uart,
167 "decode_spi": decode_spi,
168 "decode_i2c": decode_i2c,
169 "decode_can": decode_can,
170 }
171 )
172 except ImportError:
173 pass
175 # Discovery
176 try:
177 from tracekit.discovery import (
178 characterize_signal,
179 decode_protocol,
180 find_anomalies,
181 )
183 namespace.update(
184 {
185 "characterize_signal": characterize_signal,
186 "find_anomalies": find_anomalies,
187 "decode_protocol": decode_protocol,
188 }
189 )
190 except ImportError:
191 pass
193 except ImportError as e:
194 print(f"Warning: Could not import TraceKit: {e}")
196 # Common utilities
197 try:
198 import matplotlib.pyplot as plt
200 namespace["plt"] = plt
201 except ImportError:
202 pass
204 try:
205 import numpy as np
207 namespace["np"] = np
208 except ImportError:
209 pass
211 return namespace
214def setup_history() -> None:
215 """Set up readline history with persistence."""
216 # Enable tab completion
217 readline.parse_and_bind("tab: complete")
219 # Load history if exists
220 if HISTORY_FILE.exists():
221 with contextlib.suppress(Exception):
222 readline.read_history_file(HISTORY_FILE)
224 # Set history length
225 readline.set_history_length(HISTORY_LENGTH)
227 # Save history on exit
228 atexit.register(lambda: readline.write_history_file(HISTORY_FILE))
231def tracekit_help() -> None:
232 """Display TraceKit help in the REPL."""
233 help_text = """
234TraceKit Interactive Shell - Quick Reference
235=============================================
237Loading Data:
238 trace = load("file.wfm") # Auto-detect format
239 trace = load("file.csv") # CSV file
240 formats = get_supported_formats() # List supported formats
242Waveform Measurements:
243 rise_time(trace) # 10-90% rise time
244 fall_time(trace) # 90-10% fall time
245 frequency(trace) # Fundamental frequency
246 amplitude(trace) # Peak-to-peak amplitude
247 measure(trace) # All measurements
249Spectral Analysis:
250 freq, mag = fft(trace) # FFT
251 freq, pwr = psd(trace) # Power Spectral Density
252 thd(trace) # Total Harmonic Distortion
253 snr(trace) # Signal-to-Noise Ratio
255Digital Analysis:
256 digital = to_digital(trace) # Extract digital signal
257 edges = detect_edges(trace) # Find edges
259Filtering:
260 filtered = low_pass(trace, 1e6) # Low-pass filter
261 filtered = high_pass(trace, 1e3) # High-pass filter
263Protocol Decoding:
264 packets = decode_uart(trace) # UART decode
265 packets = decode_spi(clk, mosi) # SPI decode
266 packets = decode_i2c(scl, sda) # I2C decode
268Discovery (Auto-Analysis):
269 result = characterize_signal(trace) # Auto-characterize
270 anomalies = find_anomalies(trace) # Find anomalies
272For detailed help on any function:
273 help(function_name)
275Full documentation: https://github.com/lair-click-bats/tracekit
276"""
277 print(help_text)
280class TraceKitConsole(code.InteractiveConsole):
281 """Custom interactive console for TraceKit.
283 Provides IPython-style prompts and enhanced error handling.
284 """
286 def __init__(self, locals: dict[str, Any] | None = None) -> None:
287 """Initialize the console with TraceKit namespace."""
288 super().__init__(locals=locals, filename="<tracekit>")
289 self.prompt_counter = 1
291 def interact(self, banner: str | None = None, exitmsg: str | None = None) -> None:
292 """Start the interactive session."""
293 if banner is None: 293 ↛ 304line 293 didn't jump to line 304 because the condition on line 293 was always true
294 import tracekit
296 banner = f"""
297TraceKit Shell v{tracekit.__version__}
298Python {sys.version.split()[0]} on {sys.platform}
299Type 'tracekit_help()' for quick reference, 'exit()' to quit.
301Auto-imported: tk (tracekit), np (numpy), plt (matplotlib.pyplot)
302Common functions: load, measure, fft, psd, thd, low_pass, high_pass
303"""
304 if exitmsg is None:
305 exitmsg = "Goodbye!"
307 super().interact(banner=banner, exitmsg=exitmsg)
309 def raw_input(self, prompt: str = "") -> str:
310 """Override prompt with counter."""
311 custom_prompt = f"In [{self.prompt_counter}]: "
312 result = super().raw_input(custom_prompt)
313 self.prompt_counter += 1
314 return result
316 def showtraceback(self) -> None:
317 """Show traceback with helpful hints."""
318 super().showtraceback()
319 # Could add context-sensitive hints here
322def start_shell() -> None:
323 """Start the TraceKit interactive shell.
325 This is the main entry point for the REPL, providing:
326 - Auto-imported TraceKit functions and modules
327 - Tab completion
328 - Persistent command history
329 - Customized prompts
330 """
331 # Set up history
332 setup_history()
334 # Build namespace
335 namespace = get_tracekit_namespace()
337 # Add help function
338 namespace["tracekit_help"] = tracekit_help
340 # Set up completer
341 completer = rlcompleter.Completer(namespace)
342 readline.set_completer(completer.complete)
344 # Start console
345 console = TraceKitConsole(locals=namespace)
346 console.interact()
349if __name__ == "__main__":
350 start_shell()