telemetry-cli 1.0.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.
Files changed (48) hide show
  1. telemetry_cli/__init__.py +8 -0
  2. telemetry_cli/_cli.py +18 -0
  3. telemetry_cli/_getopt.py +114 -0
  4. telemetry_cli/_msg.py +39 -0
  5. telemetry_cli/_pack.py +183 -0
  6. telemetry_cli/_perl.py +183 -0
  7. telemetry_cli/pick/__init__.py +1 -0
  8. telemetry_cli/pick/__main__.py +7 -0
  9. telemetry_cli/pick/cli.py +157 -0
  10. telemetry_cli/pick/commandline.py +219 -0
  11. telemetry_cli/pick/fields.py +166 -0
  12. telemetry_cli/pick/formats.py +191 -0
  13. telemetry_cli/pick/help.py +536 -0
  14. telemetry_cli/pick/symbols/airmoc +24 -0
  15. telemetry_cli/pick/symbols/aux +28 -0
  16. telemetry_cli/pick/symbols/corr +31 -0
  17. telemetry_cli/pick/symbols/corrOld +29 -0
  18. telemetry_cli/pick/symbols/hw +16 -0
  19. telemetry_cli/pick/symbols/moc +42 -0
  20. telemetry_cli/pick/symbols/subcom +83 -0
  21. telemetry_cli/pick/symbols/subcom_2002 +111 -0
  22. telemetry_cli/recl/__init__.py +1 -0
  23. telemetry_cli/recl/__main__.py +7 -0
  24. telemetry_cli/recl/cli.py +214 -0
  25. telemetry_cli/recl/corr.py +83 -0
  26. telemetry_cli/recs/__init__.py +1 -0
  27. telemetry_cli/recs/__main__.py +7 -0
  28. telemetry_cli/recs/cli.py +182 -0
  29. telemetry_cli/recs/extract.py +399 -0
  30. telemetry_cli/tgen/__init__.py +1 -0
  31. telemetry_cli/tgen/__main__.py +7 -0
  32. telemetry_cli/tgen/cli.py +128 -0
  33. telemetry_cli/tgen/cmdfile.py +68 -0
  34. telemetry_cli/tgen/convert.py +278 -0
  35. telemetry_cli/tgen/examples/arb_data.tgen +15 -0
  36. telemetry_cli/tgen/examples/calsweep.tgen +32 -0
  37. telemetry_cli/tgen/examples/cond_fill.tgen +17 -0
  38. telemetry_cli/tgen/examples/counter.tgen +11 -0
  39. telemetry_cli/tgen/examples/null_data.tgen +11 -0
  40. telemetry_cli/tgen/examples/random_size.tgen +20 -0
  41. telemetry_cli/tgen/examples/subcom.tgen +14 -0
  42. telemetry_cli/tgen/examples/valueV_value.tgen +20 -0
  43. telemetry_cli/tgen/runtime.py +390 -0
  44. telemetry_cli-1.0.0.dist-info/METADATA +106 -0
  45. telemetry_cli-1.0.0.dist-info/RECORD +48 -0
  46. telemetry_cli-1.0.0.dist-info/WHEEL +4 -0
  47. telemetry_cli-1.0.0.dist-info/entry_points.txt +5 -0
  48. telemetry_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,390 @@
1
+ """Generating records from a parsed command file.
2
+
3
+ Each field of an item specification is Python code, evaluated for every
4
+ record in one shared namespace. A field may hold several statements
5
+ separated by semicolons; its value is that of the last one (an expression,
6
+ or the value assigned by an assignment).
7
+
8
+ The namespace provides:
9
+
10
+ I the record counter
11
+ F the fill byte: each read gives the fill value, or a new
12
+ random byte when F is set to a string containing "ran"
13
+ (as with the -r option)
14
+ X, O set by seek_file
15
+ DEBUG set true to trace evaluation on stderr
16
+ P the items: P.name.value is the item's value for this record
17
+ (or the last record, if it hasn't been evaluated yet), and
18
+ P.name._value evaluates its value field again. Likewise for
19
+ name, offset, trigger and type.
20
+ rand, srand Perl-compatible random numbers (drand48)
21
+ subcom_file, file_lookup, seek_file
22
+ read data from files
23
+ pmod Perl's % (on the integer parts of its operands)
24
+ pand, por, pxor, pshl, pshr
25
+ Perl's &, |, ^, << and >> (on unsigned 64-bit integers)
26
+ cat Perl's . (join values as text; numbers as Perl prints them)
27
+ sprintf Perl's sprintf
28
+ pi, sin, cos, tan, atan2, sqrt, exp, log, floor, ceil
29
+ stderr for diagnostics (stdout is the data)
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import ast
35
+ import math
36
+ import sys
37
+ from collections.abc import Callable
38
+ from typing import BinaryIO, TextIO
39
+
40
+ from .. import _pack
41
+ from .._perl import UV_MAX, numify, perl_str, sprintf, to_uv
42
+ from .cmdfile import Item, Statement
43
+
44
+
45
+ class TgenError(ValueError):
46
+ pass
47
+
48
+
49
+ class Drand48:
50
+ """Perl's random number generator (Perl_drand48), so seeded runs match Perl's."""
51
+
52
+ A, C, M = 0x5DEECE66D, 0xB, 1 << 48
53
+
54
+ def __init__(self, seed: int | None = None):
55
+ if seed is None:
56
+ import os
57
+
58
+ seed = int.from_bytes(os.urandom(4), "little")
59
+ self.seed(seed)
60
+
61
+ def seed(self, seed: int) -> None:
62
+ self.x = ((int(seed) & 0xFFFFFFFF) << 16) + 0x330E
63
+
64
+ def random(self) -> float:
65
+ self.x = (self.A * self.x + self.C) % self.M
66
+ return self.x / self.M
67
+
68
+
69
+ def perl_true(v) -> bool:
70
+ """Perl's truth of a field's value: undef, 0, "" and "0" are false."""
71
+ if v is None:
72
+ return False
73
+ if isinstance(v, str):
74
+ return v not in ("", "0")
75
+ if isinstance(v, (list, tuple, bytes)):
76
+ return True # a reference, or a non-empty string of bytes
77
+ return bool(v)
78
+
79
+
80
+ def pmod(a, b):
81
+ """Perl's a % b: both operands are truncated to integers first."""
82
+ a, b = int(numify(a)), int(numify(b))
83
+ if b == 0:
84
+ raise ZeroDivisionError("Illegal modulus zero")
85
+ return a % b
86
+
87
+
88
+ def cat(*values) -> str:
89
+ """Perl's string concatenation."""
90
+ return "".join(perl_str(v) for v in values)
91
+
92
+
93
+ class _FieldCode:
94
+ """A compiled field: statements, then the expression whose value is the field's."""
95
+
96
+ def __init__(self, src: str, where: str):
97
+ self.src, self.where = src, where
98
+ try:
99
+ tree = ast.parse(src.strip() or "None", mode="exec")
100
+ except SyntaxError as e:
101
+ raise TgenError(f"{where}: can't parse {src!r}: {e.msg}") from None
102
+ tree = _ReadFill().visit(tree)
103
+ body = tree.body
104
+ last = body[-1] if body else ast.Expr(ast.Constant(None))
105
+ if isinstance(last, ast.Expr):
106
+ result = last.value
107
+ body = body[:-1]
108
+ elif isinstance(last, (ast.Assign, ast.AugAssign, ast.AnnAssign)):
109
+ target = last.targets[-1] if isinstance(last, ast.Assign) else last.target
110
+ result = _loaded(target)
111
+ else:
112
+ result = ast.Constant(None)
113
+ self.stmts = compile(ast.fix_missing_locations(ast.Module(body, [])), where, "exec")
114
+ self.expr = compile(ast.fix_missing_locations(ast.Expression(result)), where, "eval")
115
+
116
+ def run(self, ns: dict):
117
+ try:
118
+ exec(self.stmts, ns)
119
+ return eval(self.expr, ns)
120
+ except TgenError:
121
+ raise
122
+ except Exception as e:
123
+ raise TgenError(f"{self.where}: {type(e).__name__}: {e} (in {self.src!r})") from None
124
+
125
+
126
+ def _loaded(target: ast.expr) -> ast.expr:
127
+ """An expression reading back an assignment target (with reads of F as reads)."""
128
+ node = ast.parse(ast.unparse(target), mode="eval").body
129
+ return _ReadFill().visit(node)
130
+
131
+
132
+ class _ReadFill(ast.NodeTransformer):
133
+ """Turn each read of F into a call, so it can give a new random byte every time."""
134
+
135
+ def visit_Name(self, node: ast.Name):
136
+ if node.id == "F" and isinstance(node.ctx, ast.Load):
137
+ return ast.copy_location(ast.Call(ast.Name("_read_fill", ast.Load()), [], []), node)
138
+ return node
139
+
140
+
141
+ class ItemValues:
142
+ """P.name: one item's current values, and its fields for re-evaluation."""
143
+
144
+ def __init__(self, gen: Generator, item: Item):
145
+ self._gen, self._item = gen, item
146
+ self.name = item.name
147
+ self._current: dict = {}
148
+
149
+ def _get(self, field: str):
150
+ # The Perl pasted $P{name}{field} into the code as text, so a float
151
+ # came back with 15 significant digits.
152
+ v = self._current.get(field)
153
+ return float(perl_str(v)) if isinstance(v, float) and math.isfinite(v) else v
154
+
155
+ offset = property(lambda self: self._get("offset"))
156
+ trigger = property(lambda self: self._get("trigger"))
157
+ type = property(lambda self: self._get("type"))
158
+ value = property(lambda self: self._get("value"))
159
+
160
+ @property
161
+ def _name(self):
162
+ return self._item.name
163
+
164
+ @property
165
+ def _offset(self):
166
+ return self._gen._code(self._item, "offset").run(self._gen.ns)
167
+
168
+ @property
169
+ def _trigger(self):
170
+ return self._gen._code(self._item, "trigger").run(self._gen.ns)
171
+
172
+ @property
173
+ def _type(self):
174
+ return self._gen._code(self._item, "type").run(self._gen.ns)
175
+
176
+ @property
177
+ def _value(self):
178
+ return self._gen._code(self._item, "value").run(self._gen.ns)
179
+
180
+ def __getitem__(self, key: str):
181
+ return getattr(self, key)
182
+
183
+
184
+ class Items:
185
+ def __init__(self):
186
+ self._items: dict[str, ItemValues] = {}
187
+
188
+ def __getattr__(self, name: str) -> ItemValues:
189
+ try:
190
+ return self.__dict__["_items"][name]
191
+ except KeyError:
192
+ raise AttributeError(f"no item named {name!r}") from None
193
+
194
+ def __getitem__(self, name: str) -> ItemValues:
195
+ return self._items[name]
196
+
197
+
198
+ class Generator:
199
+ def __init__(
200
+ self,
201
+ items: list[Item],
202
+ statements: list[Statement],
203
+ *,
204
+ fill="0",
205
+ seed: int | None = None,
206
+ stderr: TextIO | None = None,
207
+ source: str = "command file",
208
+ ):
209
+ self.items = items
210
+ self.source = source
211
+ self.stderr = stderr if stderr is not None else sys.stderr
212
+ self.rng = Drand48(seed)
213
+ self.files: dict[str, BinaryIO] = {}
214
+ self._compiled: dict[tuple[int, str], _FieldCode] = {}
215
+ self.P = Items()
216
+ for it in items:
217
+ self.P._items[it.name] = ItemValues(self, it)
218
+ self.ns: dict = self._namespace(fill)
219
+ for st in statements:
220
+ _FieldCode(st.code, f"{source}:{st.line}").run(self.ns)
221
+
222
+ # --- the namespace --------------------------------------------------------
223
+
224
+ def _namespace(self, fill) -> dict:
225
+ ns = {
226
+ "I": 0,
227
+ "F": fill,
228
+ "X": None,
229
+ "O": 0,
230
+ "DEBUG": 0,
231
+ "P": self.P,
232
+ "rand": self.rand,
233
+ "srand": self.srand,
234
+ "subcom_file": self.subcom_file,
235
+ "file_lookup": self.file_lookup,
236
+ "seek_file": self.seek_file,
237
+ "pmod": pmod,
238
+ "pand": lambda a, b: to_uv(a) & to_uv(b),
239
+ "por": lambda a, b: to_uv(a) | to_uv(b),
240
+ "pxor": lambda a, b: to_uv(a) ^ to_uv(b),
241
+ "pshl": lambda a, b: (to_uv(a) << to_uv(b)) & UV_MAX if to_uv(b) < 64 else 0,
242
+ "pshr": lambda a, b: to_uv(a) >> to_uv(b) if to_uv(b) < 64 else 0,
243
+ "cat": cat,
244
+ "sprintf": sprintf,
245
+ "stderr": self.stderr,
246
+ "_read_fill": self.read_fill,
247
+ }
248
+ for name in ("pi", "sin", "cos", "tan", "atan2", "sqrt", "exp", "log", "floor", "ceil"):
249
+ ns[name] = getattr(math, name)
250
+ return ns
251
+
252
+ def rand(self, n=1):
253
+ """Perl's rand: a float in [0, n); rand(0) is rand(1)."""
254
+ n = numify(n) or 1
255
+ return self.rng.random() * n
256
+
257
+ def srand(self, seed=0):
258
+ self.rng.seed(int(numify(seed)))
259
+ return seed
260
+
261
+ def read_fill(self):
262
+ """The value of F: the fill, or a random byte if the fill is "random"."""
263
+ mode = self.ns["F"]
264
+ if isinstance(mode, str) and "ran" in mode:
265
+ return int(self.rng.random() * 256)
266
+ return mode
267
+
268
+ # --- file helpers (each file is opened once) ------------------------------
269
+
270
+ def _file(self, name) -> BinaryIO:
271
+ name = str(name)
272
+ if name not in self.files:
273
+ try:
274
+ self.files[name] = open(name, "rb") # noqa: SIM115
275
+ except OSError as e:
276
+ raise TgenError(f"Can't read from {name}: {e.strerror}") from None
277
+ return self.files[name]
278
+
279
+ def _read(self, name, offset, nbytes) -> bytes | None:
280
+ f = self._file(name)
281
+ offset = int(numify(offset))
282
+ if offset < 0:
283
+ return None
284
+ f.seek(offset)
285
+ return f.read(max(int(numify(nbytes)), 0))
286
+
287
+ def subcom_file(self, file, offset, nbytes):
288
+ """nbytes of file starting at offset, as a byte string."""
289
+ data = self._read(file, offset, nbytes)
290
+ return b"" if data is None else data
291
+
292
+ def file_lookup(self, file, offset, nbytes, unpack_type):
293
+ """The first value unpacked from nbytes of file at offset (0 if they can't be read)."""
294
+ nbytes = int(numify(nbytes))
295
+ data = self._read(file, offset, nbytes)
296
+ if data is None or len(data) != nbytes:
297
+ return 0
298
+ values = _pack.unpack(str(unpack_type), data)
299
+ return values[0] if values else None
300
+
301
+ def seek_file(self, file, offset, nbytes, unpack_type, cond):
302
+ """Read nbytes at offset (evaluated with O = 0, 1, 2, ...) until cond is true.
303
+
304
+ offset and cond are code, as strings; X holds the value just read.
305
+ Returns the offset that satisfied cond, or -1 at the end of the file.
306
+ """
307
+ offset_code = _FieldCode(str(offset), "seek_file offset")
308
+ cond_code = _FieldCode(str(cond), "seek_file condition")
309
+ nbytes = int(numify(nbytes))
310
+ self.ns["O"] = 0
311
+ while True:
312
+ at = offset_code.run(self.ns)
313
+ data = self._read(file, at, nbytes)
314
+ if data is None or len(data) != nbytes:
315
+ return -1
316
+ values = _pack.unpack(str(unpack_type), data)
317
+ self.ns["X"] = values[0] if values else None
318
+ if self.ns.get("DEBUG"):
319
+ print(f"o={self.ns['O']}; seek_value={self.ns['X']}", file=self.stderr)
320
+ self.ns["O"] += 1
321
+ if perl_true(cond_code.run(self.ns)):
322
+ return at
323
+
324
+ # --- generating -------------------------------------------------------------
325
+
326
+ def _code(self, item: Item, field: str) -> _FieldCode:
327
+ key = (id(item), field)
328
+ if key not in self._compiled:
329
+ self._compiled[key] = _FieldCode(
330
+ getattr(item, field), f"{self.source}:{item.line} ({item.name}, {field})"
331
+ )
332
+ return self._compiled[key]
333
+
334
+ def _eval(self, item: Item, field: str):
335
+ value = self._code(item, field).run(self.ns)
336
+ if self.ns.get("DEBUG"):
337
+ print(f"{item.name}.{field}: {getattr(item, field)!r} -> {value!r}", file=self.stderr)
338
+ return value
339
+
340
+ def frame(self) -> bytes:
341
+ frame: list[int | None] = []
342
+ for item in self.items:
343
+ cur = self.P._items[item.name]._current
344
+ cur["offset"] = offset = self._eval(item, "offset")
345
+ cur["trigger"] = trigger = self._eval(item, "trigger")
346
+ if not perl_true(trigger):
347
+ continue
348
+ if numify(offset) < 0: # a calculation only: evaluate the value, don't pack
349
+ cur["value"] = self._eval(item, "value")
350
+ continue
351
+ cur["type"] = type_ = self._eval(item, "type")
352
+ cur["value"] = value = self._eval(item, "value")
353
+ values = list(value) if isinstance(value, (list, tuple)) else [value]
354
+ try:
355
+ packed = _pack.pack(str(type_), *values)
356
+ except _pack.PackError as e:
357
+ raise TgenError(f"{self.source}:{item.line} ({item.name}): {e}") from None
358
+ start = int(numify(offset))
359
+ end = start + len(packed)
360
+ if end > len(frame):
361
+ frame.extend([None] * (end - len(frame)))
362
+ frame[start:end] = packed
363
+ out = bytearray()
364
+ for b in frame:
365
+ if b is None:
366
+ out.append(int(numify(self.read_fill())) & 0xFF)
367
+ else:
368
+ out.append(b)
369
+ return bytes(out)
370
+
371
+ def run(
372
+ self,
373
+ out: BinaryIO,
374
+ nframes: int,
375
+ first: int,
376
+ *,
377
+ notify: int = 100,
378
+ quiet: bool = False,
379
+ progress: Callable[[str], None] | None = None,
380
+ ) -> None:
381
+ last = first + nframes - 1
382
+ self.ns["I"] = first
383
+ while numify(self.ns["I"]) <= last:
384
+ out.write(self.frame())
385
+ i = int(numify(self.ns["I"]))
386
+ if not quiet and not (i + 1) % notify and progress:
387
+ progress(f"{i}/{last}")
388
+ self.ns["I"] = self.ns["I"] + 1
389
+ for f in self.files.values():
390
+ f.close()
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.5
2
+ Name: telemetry-cli
3
+ Version: 1.0.0
4
+ Summary: Command-line tools for binary telemetry: decode, extract, measure and generate records
5
+ Project-URL: Repository, https://github.com/donalgrant/telemetry-cli
6
+ Project-URL: Documentation, https://github.com/donalgrant/telemetry-cli/tree/main/docs
7
+ Project-URL: Changelog, https://github.com/donalgrant/telemetry-cli/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/donalgrant/telemetry-cli/issues
9
+ Author: David A. Imel
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: binary,cli,decoding,pcm,records,telemetry
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: numpy>=1.23
26
+ Provides-Extra: dev
27
+ Requires-Dist: hypothesis>=6; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=4; extra == 'dev'
29
+ Requires-Dist: pytest>=7; extra == 'dev'
30
+ Requires-Dist: ruff>=0.5; extra == 'dev'
31
+ Requires-Dist: tomli>=2; (python_version < '3.11') and extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # telemetry-cli
35
+
36
+ Command-line tools for binary telemetry, built for Unix pipelines:
37
+
38
+ - **pick** decodes fields from fixed-length binary records: integers, floats,
39
+ complex numbers, characters and bit fields, in any byte order
40
+ ([manual](https://github.com/donalgrant/telemetry-cli/blob/main/docs/pick.md)).
41
+ - **recs** extracts records from a byte stream by their start (and end)
42
+ markers ([manual](https://github.com/donalgrant/telemetry-cli/blob/main/docs/recs.md)).
43
+ - **recl** estimates the record length of a binary file
44
+ ([manual](https://github.com/donalgrant/telemetry-cli/blob/main/docs/recl.md)).
45
+ - **tgen** generates synthetic telemetry from a command file describing each
46
+ record ([manual](https://github.com/donalgrant/telemetry-cli/blob/main/docs/tgen.md)).
47
+
48
+ ```console
49
+ $ printf '\x00\x00\x00\x01\xff\xfeHi\x00\x00\x00\x02\x00\x07ok' > demo.bin
50
+ $ pick demo.bin 8 -q order=big u s 2S
51
+ 1 -2 Hi
52
+ 2 7 ok
53
+ $ pick demo.bin 8 -q order=big -n u0x w2b
54
+ 0 00 00 00 01 1111 1111 1111 1110
55
+ 1 00 00 00 02 0000 0000 0000 0111
56
+ ```
57
+
58
+ Together: tgen makes a stream, recs pulls the frames out of it, recl finds
59
+ their length, and pick decodes them. See
60
+ [Using the tools together](https://github.com/donalgrant/telemetry-cli/blob/main/docs/pipelines.md).
61
+
62
+ ## Install
63
+
64
+ Requires Python 3.10 or later. The package is `telemetry-cli` on PyPI, and is
65
+ best installed as an isolated command-line tool:
66
+
67
+ ```text
68
+ pipx install telemetry-cli
69
+ # or
70
+ uv tool install telemetry-cli
71
+ ```
72
+
73
+ Either installs the commands `pick`, `recs`, `recl` and `tgen`. You can also
74
+ `pip install telemetry-cli` in a virtual environment.
75
+
76
+ ## History
77
+
78
+ These are Python ports of tools written at JPL. pick and recl began together
79
+ as C++ tools, for a project whose telemetry arrived with no documentation of
80
+ its structure: recl to find the length of the records, and pick to take them
81
+ apart and work out what was in them. (A bit reminiscent of the film
82
+ *Contact*.) Both were later rewritten in Perl (pick by 2000), and tgen (2003)
83
+ and recs joined them.
84
+
85
+ The command lines are the same, and so is the output, apart from fixes listed
86
+ in the [CHANGELOG](https://github.com/donalgrant/telemetry-cli/blob/main/CHANGELOG.md):
87
+ chiefly pick's byte-order handling on little-endian machines, and recl's
88
+ measurement. tgen's command files are now Python; `tgen --convert` translates
89
+ the old Perl ones. The Perl originals, with their history, are in
90
+ [legacy/](https://github.com/donalgrant/telemetry-cli/tree/main/legacy).
91
+
92
+ ## Development
93
+
94
+ ```text
95
+ python -m venv .venv && .venv/bin/pip install -e '.[dev]'
96
+ .venv/bin/pytest
97
+ ```
98
+
99
+ The tests compare each tool with its Perl original. `tests/golden/` holds the
100
+ Perl's output for large tables of command lines (about 2,200 for pick, 700
101
+ for recs), and the tests check that the Python version reproduces it byte for
102
+ byte. Where it deliberately differs, the case names the CHANGELOG entry that
103
+ explains why. With perl installed, further tests run random command lines
104
+ through both versions and compare them (`HYPOTHESIS_PROFILE=deep pytest` for
105
+ a longer search), and `python tools/make_golden.py --check` confirms the saved
106
+ output is current. The examples in the documentation run as tests too.
@@ -0,0 +1,48 @@
1
+ telemetry_cli/__init__.py,sha256=eb6NP89vHLTizc4DECIfkXYph1MHAVG3lP6xRh9PZDw,281
2
+ telemetry_cli/_cli.py,sha256=Toez71kX-dZeAlgN1WxqujmG2htv_FXHrWeGjVJLH-U,508
3
+ telemetry_cli/_getopt.py,sha256=v8DxS0bz0pbGcYqZTebQEle0Xkji-Ue8CE0KM9ixKUs,3800
4
+ telemetry_cli/_msg.py,sha256=HEpnPAtRBIpA41DULrwMcMlJGVzNV9LM9AoJ7vqRZvM,1239
5
+ telemetry_cli/_pack.py,sha256=DkoGgN_15gntl3GMZkiVl2GkiHyVGTljQOnaT6diLSY,6205
6
+ telemetry_cli/_perl.py,sha256=VV7g_48MCtnAvmmpgGYJjsvXUhDsvmPjQ7tdt1JsmGI,5841
7
+ telemetry_cli/pick/__init__.py,sha256=tf-fMfkUVpW4nhWQu6D8ctDkl3o8-9AIc-yM2UvKs60,71
8
+ telemetry_cli/pick/__main__.py,sha256=R3wrnWRPRBDHCRIznlmRsnZ-nnDdJN7Lgo-Gnj8PgAE,99
9
+ telemetry_cli/pick/cli.py,sha256=sGWx6I-e-GZa7tNrzsNtR2HK_XKfk21PEh0GXMP1suQ,5178
10
+ telemetry_cli/pick/commandline.py,sha256=4rE0ADiJ0yeSN-WFQozy-PegkQPAtMPVOTXwLjDEy78,7725
11
+ telemetry_cli/pick/fields.py,sha256=UtZoAotrkYSWP37SRjslysesuVelu8pXVH9iUT1clHM,5842
12
+ telemetry_cli/pick/formats.py,sha256=rveagoksyngGNJgTvMm3lDM6A-3X3fH20K0tjzfeCTk,6908
13
+ telemetry_cli/pick/help.py,sha256=Iie7L72JDWnNedb9dogOi02VcZd6AJqzPgsfk2tqeYI,18548
14
+ telemetry_cli/pick/symbols/airmoc,sha256=vHkLQrkCCGhYDaPsKiAzoz5IfUXXiNSZ7GRW-y5MbGI,366
15
+ telemetry_cli/pick/symbols/aux,sha256=wZ4rdl5XJiPL3ojuHiA3rmYaYkfgoBuNT1Bd-JxjBRc,409
16
+ telemetry_cli/pick/symbols/corr,sha256=MCAFjE-hbWVwgj1LwFnOSLRDZWoeSNPFXiwDc8RkXCg,623
17
+ telemetry_cli/pick/symbols/corrOld,sha256=E3fF8GL8XNJsoHfE1Z6KfXyyXuuc40du5YbCKC-YhbU,599
18
+ telemetry_cli/pick/symbols/hw,sha256=statq5EeYbWwOXy4EC8BzveTyhGaxxVydJyMTbF4ltE,190
19
+ telemetry_cli/pick/symbols/moc,sha256=MFj0nYy-o-YDa8C8iaztFASj4rqCterlVKAIJtJhEVs,664
20
+ telemetry_cli/pick/symbols/subcom,sha256=GxSFJjxwZSeg5LlXyNVRS1vW0Q2ZD5uPkhKMp_1ERq8,2446
21
+ telemetry_cli/pick/symbols/subcom_2002,sha256=sAjEgc6qwwLXwXzd9aq1wXQnMEGKXI1juBYO4A8WH98,2823
22
+ telemetry_cli/recl/__init__.py,sha256=z4QFTEsk7RwgQHQCwjODqE5YSO0AnE30i0REjhAAX2A,57
23
+ telemetry_cli/recl/__main__.py,sha256=30MLg92M0QF-x99qm1OapckDppgFkSgurwxg_Ne8GTI,99
24
+ telemetry_cli/recl/cli.py,sha256=y2xdUzbHojbrT_AEacmPjB3S0hFs4wUnyd7cdBalMoc,7257
25
+ telemetry_cli/recl/corr.py,sha256=z_fxBFrBw0gILJ8-7ZWQgYDSJWcFKvti4RsX28jxtFw,3536
26
+ telemetry_cli/recs/__init__.py,sha256=E_nQYAX3sjrMia2Wk-x_HOZIgIWcWarJE3km7XGGLXw,82
27
+ telemetry_cli/recs/__main__.py,sha256=KwNEOVw9Po2oqwSLDAqwyq7S2XSfMTGOoWNLs3vO4zE,99
28
+ telemetry_cli/recs/cli.py,sha256=flmY1LRg-JRnagMAVyRCLLuvvWmUpUtjfYGXeJQIbIo,5886
29
+ telemetry_cli/recs/extract.py,sha256=ggGsB5c6wfHaC6sjuC0Npv1csr9dFZIMoLs1NSOnptg,13854
30
+ telemetry_cli/tgen/__init__.py,sha256=1mvMJA14vBGpCXT07p07xuY8nQAS-ulJZCJXdyXYrJY,60
31
+ telemetry_cli/tgen/__main__.py,sha256=os93C6HnHnoAE5JGHRQHog0Z0Ai76-LpU9O8Zu5_w-s,99
32
+ telemetry_cli/tgen/cli.py,sha256=csAJMRESnff07BpXGnXo9PIe9zSCquovo-5cs8132CU,4198
33
+ telemetry_cli/tgen/cmdfile.py,sha256=07iLI_Ldm9MNLerAF4mPMr-1h2cr6-SgXBcu3314vMY,1725
34
+ telemetry_cli/tgen/convert.py,sha256=x_BhnLNZLih1PX5QxKPWht1wDiLhmkEtQZdF5vV-mCE,9479
35
+ telemetry_cli/tgen/runtime.py,sha256=Q0bHzqDeTP7-IypT85AOimvPnDW7WvWJs78QWZwxk94,13959
36
+ telemetry_cli/tgen/examples/arb_data.tgen,sha256=ZV_EHPJWEzzwd5U6l4_3JZxtZvedBy8XyY4x2_hAjJ0,554
37
+ telemetry_cli/tgen/examples/calsweep.tgen,sha256=zG29oFJ3ieTT-h4yi1HA4uMAElhpmi6b8P8JWtnYGuU,955
38
+ telemetry_cli/tgen/examples/cond_fill.tgen,sha256=ddShQz4dn40xwfpCra6h3iag6aBmrJAOakN7N2tqjAw,487
39
+ telemetry_cli/tgen/examples/counter.tgen,sha256=5LoP-S2hbGUfNdcoHsktCSTZNiro8vvT8QTGIp1LnoI,262
40
+ telemetry_cli/tgen/examples/null_data.tgen,sha256=GKomAS3eyyBf_AXm5wezKdV-98QN-1k80lpFL4fWIhk,319
41
+ telemetry_cli/tgen/examples/random_size.tgen,sha256=9t_6ir51jVbjKM8TR6cw6qWUWyyY6gQhEKicnM1ENUs,655
42
+ telemetry_cli/tgen/examples/subcom.tgen,sha256=Hw3OACp9ueA4hsQimYT4R3w6U8DvAhd3zMlq0O_YvYM,436
43
+ telemetry_cli/tgen/examples/valueV_value.tgen,sha256=ZnFGtF-trqeEHowmTghYW8JNR0SXMnOovXVXyL-Vdcg,674
44
+ telemetry_cli-1.0.0.dist-info/METADATA,sha256=PT8DdJr04W51n4jQErEuBh51tclxgjr53AnuEfc-pas,4640
45
+ telemetry_cli-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
46
+ telemetry_cli-1.0.0.dist-info/entry_points.txt,sha256=sOtbf_d1OIyGvm9DPBSUddoTjWZmMXePZNl69TzqZKA,158
47
+ telemetry_cli-1.0.0.dist-info/licenses/LICENSE,sha256=PiTqw42Wp1U8DisYmUyCMgJny4s3Hd-0_hMWUciTX-k,1075
48
+ telemetry_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ pick = telemetry_cli.pick.cli:main
3
+ recl = telemetry_cli.recl.cli:main
4
+ recs = telemetry_cli.recs.cli:main
5
+ tgen = telemetry_cli.tgen.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2000-2026 David A. Imel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.