lvtypedesc 0.2.0__tar.gz

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.
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1 @@
1
+ buy_me_a_coffee: jlianesglrs
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0]
10
+
11
+ - Command-line interface: a `lvtypedesc` console script with `decode` (optional
12
+ `--tree`), `codes`, `--version` and runnable `--help` examples. Errors go to
13
+ stderr with exit code 1.
14
+ - `to_bytes` re-emits the canonical short-form descriptor for every modelled
15
+ node, so parsing and emission are inverses for the supported subset.
16
+ - New public API: `TypeCode` enum, `known_codes()`, `pretty()`, `to_bytes()`,
17
+ and the `FixedPoint` and `Refnum` node types.
18
+ - Fixed-point (`FXP`) and refnum descriptors are now recognised. Their
19
+ type-specific bytes are preserved verbatim rather than interpreted, because
20
+ their internal layout is version-specific and not fully documented.
21
+ - `LVString` now carries the descriptor's 32-bit `size` word (`VARIABLE` when not
22
+ fixed), so fixed-size strings round-trip exactly.
23
+ - README now states the provenance plainly: the code table is community
24
+ reverse-engineering from the LabVIEW wiki, not an official NI byte spec.
25
+
26
+ ## [0.1.0]
27
+
28
+ - First release.
29
+ - `parse_type_descriptor` reads a LabVIEW type-descriptor byte string into a
30
+ typed tree of frozen dataclasses, and `describe` renders it as a readable line.
31
+ - Supported subset: numeric scalars (I8 to U64, SGL, DBL, EXT, complex),
32
+ booleans, strings, paths, pictures, n-dimensional arrays and nested clusters.
33
+ - Out-of-scope codes (enums, variants, waveforms, refnums) and truncated input
34
+ raise `TypeDescError`. Fills the gap left by VI-file parsers, which do not
35
+ decode the runtime type-descriptor byte string.
@@ -0,0 +1,19 @@
1
+ # Contributing
2
+
3
+ Thanks for taking the time. This project keeps a small, predictable shape.
4
+
5
+ ## Setup
6
+
7
+ ```
8
+ pip install -r requirements-dev.txt
9
+ ```
10
+
11
+ ## Before you open a pull request
12
+
13
+ - Keep public API in `src/lvtypedesc/__init__.py`; internals live in `_`-prefixed modules.
14
+ - Add a test for the behaviour you change. Tests inject time, disk and network; they
15
+ never touch the real thing.
16
+ - Run `scripts/check.ps1` (format, lint, types, tests) and make sure it passes.
17
+ - One logical change per commit, imperative subject line.
18
+
19
+ Open an issue first if the change is large or shifts the public API.
@@ -0,0 +1,63 @@
1
+ # Improvements round
2
+
3
+ Ranked, substantial improvements for this round. Each is additive: the existing
4
+ `parse_type_descriptor` / `describe` API keeps working unchanged.
5
+
6
+ ## Provenance note (read first)
7
+
8
+ The type-descriptor code table this library uses is the community
9
+ reverse-engineering published at the LabVIEW wiki
10
+ (https://labviewwiki.org/wiki/Type_descriptor). It is consistent with the
11
+ scattered fragments NI has published, but it is **not** an official, byte-exact
12
+ NI specification. Codes can also differ between LabVIEW versions and between the
13
+ in-memory 16-bit-word form and the flattened byte form. So every code and layout
14
+ below is treated as the common documented set, not a guarantee, and the parser
15
+ reports honest errors rather than guessing. Where a payload cannot be verified
16
+ (fixed-point and refnum internals), the raw bytes are preserved instead of being
17
+ invented into fields.
18
+
19
+ ## Ranked improvements
20
+
21
+ 1. **Command-line interface.** A real `lvtypedesc` console script (argparse,
22
+ `[project.scripts]`): `decode` a hex descriptor into a readable type, a
23
+ `codes` listing of every code the library knows, `--version`, and a `--help`
24
+ with runnable examples. Errors go to stderr with exit code 1. This is the
25
+ single biggest reach improvement: it makes the library usable without writing
26
+ Python.
27
+
28
+ 2. **Expose the type-code enum.** A public `TypeCode` `IntEnum` so callers can
29
+ refer to `TypeCode.DBL` instead of a bare `0x0A`, plus `known_codes()` that
30
+ returns the full `(code, name, category)` table that backs the CLI listing.
31
+
32
+ 3. **Round-trip emission.** `to_bytes(node)` re-emits the canonical descriptor
33
+ for every node type the library models, so `parse` and `to_bytes` are
34
+ inverses for the supported subset. This is round-trip "where defined": it
35
+ emits the short-form descriptor and refuses (honest error) when the length
36
+ would overflow the 16-bit short-form length word.
37
+
38
+ 4. **Recognise fixed-point and refnum.** FXP (`0x5F`) and Refnum (`0x70`) move
39
+ from "unsupported, raises" to recognised nodes. Because their internal
40
+ payload layout is version-specific and not fully verifiable, the parser keeps
41
+ the raw payload bytes rather than inventing fields. The refnum subtype word is
42
+ surfaced as a best-effort `subtype` and documented as provisional.
43
+
44
+ 5. **Faithful string sizing.** `LVString` now carries the descriptor's 32-bit
45
+ size word (`VARIABLE` for `0xFFFFFFFF`), so fixed-size strings, paths and
46
+ pictures round-trip exactly and the size is inspectable.
47
+
48
+ 6. **Multi-line pretty-print.** `pretty(node)` renders a parsed descriptor as an
49
+ indented tree, complementing the existing one-line `describe`. The CLI uses it
50
+ for `decode --tree`.
51
+
52
+ 7. **Stronger tests.** A contract test that parses representative descriptors for
53
+ every structural family (scalar, string, array of DBL, cluster) and asserts
54
+ the whole typed tree; round-trip property tests; a CLI unit test; and a real
55
+ end-to-end test that pip-installs the package editable and drives the actual
56
+ installed `lvtypedesc` console script through a subprocess.
57
+
58
+ ## Deliberately out of scope
59
+
60
+ Enums, variants and waveforms stay unsupported with a clear error: their payloads
61
+ are not documented well enough to parse honestly. No codes are invented. The
62
+ long-form (32-bit length) descriptor variant is not emitted; `to_bytes` reports
63
+ when a tree is too large for the short form instead of guessing the long form.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Javier Lianes
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.
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.5
2
+ Name: lvtypedesc
3
+ Version: 0.2.0
4
+ Summary: Parse LabVIEW type-descriptor byte strings into a readable, typed schema
5
+ Project-URL: Homepage, https://lianes8studio.com
6
+ Project-URL: Repository, https://github.com/JaviLianes8/lvtypedesc
7
+ Project-URL: Issues, https://github.com/JaviLianes8/lvtypedesc/issues
8
+ Project-URL: Changelog, https://github.com/JaviLianes8/lvtypedesc/blob/main/CHANGELOG.md
9
+ Project-URL: LinkedIn, https://www.linkedin.com/in/jlianes/
10
+ Project-URL: Funding, https://buymeacoffee.com/jlianesglrs
11
+ Author-email: Javier Lianes <javier@lianes8studio.com>
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Provides-Extra: dev
20
+ Requires-Dist: build; extra == 'dev'
21
+ Requires-Dist: mypy; extra == 'dev'
22
+ Requires-Dist: pytest; extra == 'dev'
23
+ Requires-Dist: ruff; extra == 'dev'
24
+ Requires-Dist: twine; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # lvtypedesc
28
+
29
+ Parse LabVIEW type-descriptor byte strings into a readable, typed schema
30
+
31
+ ## Install
32
+
33
+ ```
34
+ pip install lvtypedesc
35
+ ```
36
+
37
+ A LabVIEW type descriptor is the small, length-prefixed block of type codes that
38
+ precedes flattened LabVIEW data. This library turns that block into a typed tree
39
+ you can inspect, which is the schema a generic unflatten needs.
40
+
41
+ ## Usage
42
+
43
+ ```python
44
+ from lvtypedesc import parse_type_descriptor, describe, pretty, to_bytes
45
+
46
+ # A 1D array of clusters, each holding an I32 and a string.
47
+ data = bytes.fromhex(
48
+ "001C 0040 0001 FFFFFFFF 0012 0050 0002 0004 0003 0008 0030 FFFFFFFF".replace(" ", "")
49
+ )
50
+
51
+ node = parse_type_descriptor(data)
52
+ print(describe(node)) # 1D Array of Cluster { I32, String }
53
+ print(node.rank) # 1
54
+ print(node.element.fields[0].name) # I32
55
+ print(to_bytes(node) == data) # True, round-trips exactly
56
+ print(pretty(node))
57
+ # Array (rank 1, dims [variable])
58
+ # Cluster (2 fields)
59
+ # [0] I32
60
+ # [1] String (size variable)
61
+ ```
62
+
63
+ The tree is built from frozen dataclasses: `Scalar`, `Boolean`, `LVString` (with
64
+ `size`), `Array` (with `element` and `dimensions`), `Cluster` (with `fields`),
65
+ `FixedPoint` and `Refnum`. A dimension or string whose size is not fixed in the
66
+ descriptor uses the sentinel `VARIABLE`. Trailing bytes, such as the flattened
67
+ data after the descriptor, are ignored. Truncated, unknown or out-of-scope input
68
+ raises `TypeDescError`.
69
+
70
+ `to_bytes` re-emits the canonical short-form descriptor for any node the library
71
+ models, so `parse_type_descriptor` and `to_bytes` are inverses for the supported
72
+ subset. `TypeCode` exposes the recognised codes by name and `known_codes()`
73
+ returns the full table.
74
+
75
+ ## Command line
76
+
77
+ Installing the package also installs a `lvtypedesc` console script:
78
+
79
+ ```
80
+ $ lvtypedesc decode 00040003
81
+ I32
82
+ $ lvtypedesc decode 000E00400001FFFFFFFF0004000A
83
+ 1D Array of DBL
84
+ $ lvtypedesc decode --tree 000E00500002000400030004000A
85
+ Cluster (2 fields)
86
+ [0] I32
87
+ [1] DBL
88
+ $ lvtypedesc codes # list every recognised type code
89
+ $ lvtypedesc --version
90
+ ```
91
+
92
+ Whitespace in the hex is ignored. Errors go to stderr with exit code 1. Run
93
+ `lvtypedesc --help` for the full list of examples.
94
+
95
+ ## Supported subset
96
+
97
+ Fully parsed: the numeric scalars (`I8` to `U64`, `SGL`, `DBL`, `EXT` and the
98
+ complex variants), booleans, strings, paths, pictures, n-dimensional arrays and
99
+ clusters, nested to any depth. Fixed-point (`FXP`) and refnum descriptors are
100
+ recognised and their type-specific bytes preserved, but their internal layout is
101
+ not interpreted (see provenance below).
102
+
103
+ Not covered, and reported with a clear error: enums, variants and waveforms,
104
+ whose payloads are not documented well enough to parse honestly.
105
+
106
+ ## Provenance (please read)
107
+
108
+ The type-descriptor code table used here is **community reverse-engineering**,
109
+ documented at the [LabVIEW wiki](https://labviewwiki.org/wiki/Type_descriptor).
110
+ It is consistent with the fragments NI has published but it is **not** an
111
+ official, byte-exact NI specification. Two further caveats:
112
+
113
+ - Codes can change between LabVIEW versions, so treat them as the common
114
+ documented set rather than a guarantee for every build.
115
+ - The in-memory 16-bit-word form and the flattened byte form can differ. This
116
+ library parses the flattened byte form.
117
+
118
+ Because of this, anything that cannot be verified is treated as provisional:
119
+ unknown codes raise an honest error instead of a guess, and the fixed-point and
120
+ refnum payloads are kept as raw bytes rather than invented into fields. The
121
+ refnum `subtype` word is surfaced best-effort and is itself provisional.
122
+
123
+ ## Why
124
+
125
+ LabVIEW ships flattened data with a leading type descriptor, and decoding it in
126
+ Python needs that descriptor parsed into a schema first. OpenG's Variant Data
127
+ Library parses descriptors inside LabVIEW, but it is LabVIEW VIs, not Python. The
128
+ existing PyPI packages read LabVIEW source files instead: `pylabview` and `lvkit`
129
+ parse the `.vi` binary, `pylavi` validates VI files. None of them exposes a parser
130
+ for the raw runtime type-descriptor byte string that precedes flattened data or
131
+ sits inside a variant. This library fills that gap with a zero-dependency, fully
132
+ typed reader for the common documented types.
133
+
134
+ ## License
135
+
136
+ MIT. See [LICENSE](LICENSE).
137
+
138
+ ## Links
139
+
140
+ - Website: https://lianes8studio.com
141
+ - GitHub: https://github.com/JaviLianes8
142
+ - LinkedIn: https://www.linkedin.com/in/jlianes/
@@ -0,0 +1,116 @@
1
+ # lvtypedesc
2
+
3
+ Parse LabVIEW type-descriptor byte strings into a readable, typed schema
4
+
5
+ ## Install
6
+
7
+ ```
8
+ pip install lvtypedesc
9
+ ```
10
+
11
+ A LabVIEW type descriptor is the small, length-prefixed block of type codes that
12
+ precedes flattened LabVIEW data. This library turns that block into a typed tree
13
+ you can inspect, which is the schema a generic unflatten needs.
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ from lvtypedesc import parse_type_descriptor, describe, pretty, to_bytes
19
+
20
+ # A 1D array of clusters, each holding an I32 and a string.
21
+ data = bytes.fromhex(
22
+ "001C 0040 0001 FFFFFFFF 0012 0050 0002 0004 0003 0008 0030 FFFFFFFF".replace(" ", "")
23
+ )
24
+
25
+ node = parse_type_descriptor(data)
26
+ print(describe(node)) # 1D Array of Cluster { I32, String }
27
+ print(node.rank) # 1
28
+ print(node.element.fields[0].name) # I32
29
+ print(to_bytes(node) == data) # True, round-trips exactly
30
+ print(pretty(node))
31
+ # Array (rank 1, dims [variable])
32
+ # Cluster (2 fields)
33
+ # [0] I32
34
+ # [1] String (size variable)
35
+ ```
36
+
37
+ The tree is built from frozen dataclasses: `Scalar`, `Boolean`, `LVString` (with
38
+ `size`), `Array` (with `element` and `dimensions`), `Cluster` (with `fields`),
39
+ `FixedPoint` and `Refnum`. A dimension or string whose size is not fixed in the
40
+ descriptor uses the sentinel `VARIABLE`. Trailing bytes, such as the flattened
41
+ data after the descriptor, are ignored. Truncated, unknown or out-of-scope input
42
+ raises `TypeDescError`.
43
+
44
+ `to_bytes` re-emits the canonical short-form descriptor for any node the library
45
+ models, so `parse_type_descriptor` and `to_bytes` are inverses for the supported
46
+ subset. `TypeCode` exposes the recognised codes by name and `known_codes()`
47
+ returns the full table.
48
+
49
+ ## Command line
50
+
51
+ Installing the package also installs a `lvtypedesc` console script:
52
+
53
+ ```
54
+ $ lvtypedesc decode 00040003
55
+ I32
56
+ $ lvtypedesc decode 000E00400001FFFFFFFF0004000A
57
+ 1D Array of DBL
58
+ $ lvtypedesc decode --tree 000E00500002000400030004000A
59
+ Cluster (2 fields)
60
+ [0] I32
61
+ [1] DBL
62
+ $ lvtypedesc codes # list every recognised type code
63
+ $ lvtypedesc --version
64
+ ```
65
+
66
+ Whitespace in the hex is ignored. Errors go to stderr with exit code 1. Run
67
+ `lvtypedesc --help` for the full list of examples.
68
+
69
+ ## Supported subset
70
+
71
+ Fully parsed: the numeric scalars (`I8` to `U64`, `SGL`, `DBL`, `EXT` and the
72
+ complex variants), booleans, strings, paths, pictures, n-dimensional arrays and
73
+ clusters, nested to any depth. Fixed-point (`FXP`) and refnum descriptors are
74
+ recognised and their type-specific bytes preserved, but their internal layout is
75
+ not interpreted (see provenance below).
76
+
77
+ Not covered, and reported with a clear error: enums, variants and waveforms,
78
+ whose payloads are not documented well enough to parse honestly.
79
+
80
+ ## Provenance (please read)
81
+
82
+ The type-descriptor code table used here is **community reverse-engineering**,
83
+ documented at the [LabVIEW wiki](https://labviewwiki.org/wiki/Type_descriptor).
84
+ It is consistent with the fragments NI has published but it is **not** an
85
+ official, byte-exact NI specification. Two further caveats:
86
+
87
+ - Codes can change between LabVIEW versions, so treat them as the common
88
+ documented set rather than a guarantee for every build.
89
+ - The in-memory 16-bit-word form and the flattened byte form can differ. This
90
+ library parses the flattened byte form.
91
+
92
+ Because of this, anything that cannot be verified is treated as provisional:
93
+ unknown codes raise an honest error instead of a guess, and the fixed-point and
94
+ refnum payloads are kept as raw bytes rather than invented into fields. The
95
+ refnum `subtype` word is surfaced best-effort and is itself provisional.
96
+
97
+ ## Why
98
+
99
+ LabVIEW ships flattened data with a leading type descriptor, and decoding it in
100
+ Python needs that descriptor parsed into a schema first. OpenG's Variant Data
101
+ Library parses descriptors inside LabVIEW, but it is LabVIEW VIs, not Python. The
102
+ existing PyPI packages read LabVIEW source files instead: `pylabview` and `lvkit`
103
+ parse the `.vi` binary, `pylavi` validates VI files. None of them exposes a parser
104
+ for the raw runtime type-descriptor byte string that precedes flattened data or
105
+ sits inside a variant. This library fills that gap with a zero-dependency, fully
106
+ typed reader for the common documented types.
107
+
108
+ ## License
109
+
110
+ MIT. See [LICENSE](LICENSE).
111
+
112
+ ## Links
113
+
114
+ - Website: https://lianes8studio.com
115
+ - GitHub: https://github.com/JaviLianes8
116
+ - LinkedIn: https://www.linkedin.com/in/jlianes/
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lvtypedesc"
7
+ version = "0.2.0"
8
+ description = "Parse LabVIEW type-descriptor byte strings into a readable, typed schema"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Javier Lianes", email = "javier@lianes8studio.com" }]
14
+ keywords = []
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ dev = ["ruff", "mypy", "pytest", "build", "twine"]
25
+
26
+ [project.scripts]
27
+ lvtypedesc = "lvtypedesc._cli:main"
28
+
29
+ [project.urls]
30
+ Homepage = "https://lianes8studio.com"
31
+ Repository = "https://github.com/JaviLianes8/lvtypedesc"
32
+ Issues = "https://github.com/JaviLianes8/lvtypedesc/issues"
33
+ Changelog = "https://github.com/JaviLianes8/lvtypedesc/blob/main/CHANGELOG.md"
34
+ LinkedIn = "https://www.linkedin.com/in/jlianes/"
35
+ Funding = "https://buymeacoffee.com/jlianesglrs"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/lvtypedesc"]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py310"
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
46
+
47
+ [tool.mypy]
48
+ python_version = "3.10"
49
+ strict = true
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ pythonpath = ["src"]
54
+ addopts = "-q"
@@ -0,0 +1,3 @@
1
+ # Development dependencies: the package in editable mode plus its dev extras
2
+ # (ruff, mypy, pytest, build, twine). Install with: pip install -r requirements-dev.txt
3
+ -e .[dev]
@@ -0,0 +1,16 @@
1
+ # Local quality gate. Run before every commit. No CI runs in the cloud.
2
+ # $ErrorActionPreference does NOT stop on a native command's non-zero exit, so each tool's
3
+ # exit code is checked explicitly: a red gate must fail, never pass silently.
4
+ $ErrorActionPreference = "Stop"
5
+
6
+ function Invoke-Step($label, [scriptblock]$cmd) {
7
+ Write-Host "== $label =="
8
+ & $cmd
9
+ if ($LASTEXITCODE -ne 0) { Write-Host "FAILED: $label (exit $LASTEXITCODE)"; exit $LASTEXITCODE }
10
+ }
11
+
12
+ Invoke-Step "ruff format" { ruff format --check src tests }
13
+ Invoke-Step "ruff lint" { ruff check src tests }
14
+ Invoke-Step "mypy" { mypy src tests }
15
+ Invoke-Step "pytest" { pytest }
16
+ Write-Host "gate green"
@@ -0,0 +1,28 @@
1
+ # Build and upload to PyPI. Run scripts/check.ps1 first.
2
+ # The name must not already exist on PyPI; the token is read from a secret store, never git.
3
+ $ErrorActionPreference = "Stop"
4
+
5
+ # Works on both Windows PowerShell 5.1 and pwsh 7 (generic catch, no PS7-only types).
6
+ $taken = $true
7
+ try {
8
+ Invoke-WebRequest -UseBasicParsing "https://pypi.org/pypi/lvtypedesc/json" | Out-Null
9
+ } catch {
10
+ if ($_.Exception.Response.StatusCode.value__ -eq 404) { $taken = $false } else { throw }
11
+ }
12
+ if ($taken) { throw "The name 'lvtypedesc' already exists on PyPI. Choose another before publishing." }
13
+
14
+ $token = $env:PYPI_TOKEN
15
+ if (-not $token -and (Test-Path "C:\secrets\pypi-token.txt")) {
16
+ $token = (Get-Content "C:\secrets\pypi-token.txt" -Raw).Trim()
17
+ }
18
+ if (-not $token) {
19
+ throw "No PyPI token. Set `$env:PYPI_TOKEN or save it to C:\secrets\pypi-token.txt (never commit it)."
20
+ }
21
+
22
+ Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue
23
+ python -m build
24
+ python -m twine check dist/*
25
+
26
+ $env:TWINE_USERNAME = "__token__"
27
+ $env:TWINE_PASSWORD = $token
28
+ try { python -m twine upload dist/* } finally { Remove-Item Env:\TWINE_PASSWORD -ErrorAction SilentlyContinue }
@@ -0,0 +1,43 @@
1
+ """Parse LabVIEW type-descriptor byte strings into a readable, typed schema."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._codes import Category, TypeCode, known_codes
6
+ from ._emit import to_bytes
7
+ from ._errors import TypeDescError
8
+ from ._model import (
9
+ VARIABLE,
10
+ Array,
11
+ Boolean,
12
+ Cluster,
13
+ FixedPoint,
14
+ LVString,
15
+ Refnum,
16
+ Scalar,
17
+ TypeDesc,
18
+ describe,
19
+ )
20
+ from ._parser import parse_type_descriptor
21
+ from ._pretty import pretty
22
+
23
+ __version__ = "0.2.0"
24
+
25
+ __all__ = [
26
+ "VARIABLE",
27
+ "Array",
28
+ "Boolean",
29
+ "Category",
30
+ "Cluster",
31
+ "FixedPoint",
32
+ "LVString",
33
+ "Refnum",
34
+ "Scalar",
35
+ "TypeCode",
36
+ "TypeDesc",
37
+ "TypeDescError",
38
+ "describe",
39
+ "known_codes",
40
+ "parse_type_descriptor",
41
+ "pretty",
42
+ "to_bytes",
43
+ ]
@@ -0,0 +1,91 @@
1
+ """Command-line interface for lvtypedesc.
2
+
3
+ Decode a hex type descriptor into a readable type, or list the codes the library
4
+ knows. Errors are written to stderr and the process exits with status 1.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import sys
11
+ from collections.abc import Sequence
12
+
13
+ from . import __version__
14
+ from ._codes import known_codes
15
+ from ._errors import TypeDescError
16
+ from ._model import describe
17
+ from ._parser import parse_type_descriptor
18
+ from ._pretty import pretty
19
+
20
+ _EXAMPLES = """\
21
+ examples:
22
+ lvtypedesc decode 00040003 -> I32
23
+ lvtypedesc decode "0004 000A" -> DBL (spaces are ignored)
24
+ lvtypedesc decode 000E00400001FFFFFFFF0004000A -> 1D Array of DBL
25
+ lvtypedesc decode --tree 000E00500002000400030004000A
26
+ lvtypedesc codes -> list every known type code
27
+ """
28
+
29
+
30
+ def _build_parser() -> argparse.ArgumentParser:
31
+ parser = argparse.ArgumentParser(
32
+ prog="lvtypedesc",
33
+ description="Parse LabVIEW type-descriptor byte strings into a readable type.",
34
+ epilog=_EXAMPLES,
35
+ formatter_class=argparse.RawDescriptionHelpFormatter,
36
+ )
37
+ parser.add_argument("--version", action="version", version=f"lvtypedesc {__version__}")
38
+ sub = parser.add_subparsers(dest="command", required=True)
39
+
40
+ decode = sub.add_parser(
41
+ "decode",
42
+ help="decode a hex type descriptor into a readable type",
43
+ description="Decode a hex type descriptor. Whitespace in the hex is ignored.",
44
+ )
45
+ decode.add_argument("hex", help="the type descriptor as hex, for example 00040003")
46
+ decode.add_argument(
47
+ "--tree",
48
+ action="store_true",
49
+ help="print an indented tree instead of a one-line summary",
50
+ )
51
+
52
+ sub.add_parser(
53
+ "codes",
54
+ help="list every type code the library recognises",
55
+ description="List every recognised type code, with its name and category.",
56
+ )
57
+ return parser
58
+
59
+
60
+ def _run_decode(hex_text: str, tree: bool) -> int:
61
+ try:
62
+ data = bytes.fromhex(hex_text)
63
+ except ValueError as exc:
64
+ print(f"error: not valid hex: {exc}", file=sys.stderr)
65
+ return 1
66
+ try:
67
+ node = parse_type_descriptor(data)
68
+ except TypeDescError as exc:
69
+ print(f"error: {exc}", file=sys.stderr)
70
+ return 1
71
+ print(pretty(node) if tree else describe(node))
72
+ return 0
73
+
74
+
75
+ def _run_codes() -> int:
76
+ for code, name, category in known_codes():
77
+ print(f"0x{code:02X} {name:<10} {category.value}")
78
+ return 0
79
+
80
+
81
+ def main(argv: Sequence[str] | None = None) -> int:
82
+ """Entry point for the ``lvtypedesc`` console script."""
83
+ parser = _build_parser()
84
+ args = parser.parse_args(argv)
85
+ if args.command == "decode":
86
+ return _run_decode(args.hex, args.tree)
87
+ return _run_codes()
88
+
89
+
90
+ if __name__ == "__main__": # pragma: no cover
91
+ raise SystemExit(main())