lvbitpack 1.0.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.
- lvbitpack-1.0.0/.gitignore +12 -0
- lvbitpack-1.0.0/CHANGELOG.md +81 -0
- lvbitpack-1.0.0/CONTRIBUTING.md +19 -0
- lvbitpack-1.0.0/GUIDE.md +200 -0
- lvbitpack-1.0.0/LICENSE +21 -0
- lvbitpack-1.0.0/PKG-INFO +142 -0
- lvbitpack-1.0.0/README.md +103 -0
- lvbitpack-1.0.0/pyproject.toml +77 -0
- lvbitpack-1.0.0/requirements-dev.txt +3 -0
- lvbitpack-1.0.0/src/lvbitpack/__init__.py +60 -0
- lvbitpack-1.0.0/src/lvbitpack/__main__.py +7 -0
- lvbitpack-1.0.0/src/lvbitpack/_bytes.py +96 -0
- lvbitpack-1.0.0/src/lvbitpack/_cli.py +154 -0
- lvbitpack-1.0.0/src/lvbitpack/_cli_io.py +83 -0
- lvbitpack-1.0.0/src/lvbitpack/_errors.py +138 -0
- lvbitpack-1.0.0/src/lvbitpack/_flatten.py +56 -0
- lvbitpack-1.0.0/src/lvbitpack/_modes.py +49 -0
- lvbitpack-1.0.0/src/lvbitpack/_number.py +88 -0
- lvbitpack-1.0.0/src/lvbitpack/_pack.py +72 -0
- lvbitpack-1.0.0/src/lvbitpack/_text.py +63 -0
- lvbitpack-1.0.0/src/lvbitpack/_types.py +150 -0
- lvbitpack-1.0.0/src/lvbitpack/py.typed +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/boolean_array_to_number.json +1 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len0.dat +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len1.dat +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len17.dat +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len300.dat +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len8.dat +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len9.dat +0 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/manifest.json +200 -0
- lvbitpack-1.0.0/tests/fixtures/labview2026/number_to_boolean_array.json +1 -0
- lvbitpack-1.0.0/tests/test_bytes.py +104 -0
- lvbitpack-1.0.0/tests/test_cli.py +122 -0
- lvbitpack-1.0.0/tests/test_cli_process.py +50 -0
- lvbitpack-1.0.0/tests/test_contract.py +172 -0
- lvbitpack-1.0.0/tests/test_contract_edges.py +47 -0
- lvbitpack-1.0.0/tests/test_contract_strength.py +145 -0
- lvbitpack-1.0.0/tests/test_contract_strength_cli.py +128 -0
- lvbitpack-1.0.0/tests/test_contract_strength_three.py +137 -0
- lvbitpack-1.0.0/tests/test_contract_strength_two.py +105 -0
- lvbitpack-1.0.0/tests/test_docs.py +105 -0
- lvbitpack-1.0.0/tests/test_flatten.py +64 -0
- lvbitpack-1.0.0/tests/test_fuzz.py +113 -0
- lvbitpack-1.0.0/tests/test_hostile.py +186 -0
- lvbitpack-1.0.0/tests/test_hostile_five.py +64 -0
- lvbitpack-1.0.0/tests/test_hostile_four.py +121 -0
- lvbitpack-1.0.0/tests/test_hostile_more.py +128 -0
- lvbitpack-1.0.0/tests/test_hostile_three.py +138 -0
- lvbitpack-1.0.0/tests/test_number.py +147 -0
- lvbitpack-1.0.0/tests/test_pack.py +87 -0
- lvbitpack-1.0.0/tests/test_packaging.py +90 -0
- lvbitpack-1.0.0/tests/test_real_cli_e2e.py +113 -0
- lvbitpack-1.0.0/tests/test_real_labview.py +102 -0
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
## [1.0.0] - 2026-10-14
|
|
10
|
+
|
|
11
|
+
First stable release, and the first one published on PyPI. The public API is the 21 names
|
|
12
|
+
in `lvbitpack.__all__`; their names, signatures, defaults and error type are now stable.
|
|
13
|
+
|
|
14
|
+
### Changed (from 0.2.0)
|
|
15
|
+
|
|
16
|
+
- `pack_flattened` and `unpack_flattened` now read and write LabVIEW's real flattened
|
|
17
|
+
Boolean array: a big-endian I32 count, then one byte per element (0x01 / 0x00). In 0.2.0
|
|
18
|
+
they wrote the count followed by bit-packed bytes, which LabVIEW cannot unflatten.
|
|
19
|
+
`unpack_flattened` reads any nonzero byte as TRUE and refuses a negative count, missing
|
|
20
|
+
elements and bytes left over after the last element.
|
|
21
|
+
- The byte functions (`number_to_bytes`, `bytes_to_number`, `bools_to_bytes`,
|
|
22
|
+
`bytes_to_bools`) default to big-endian, LabVIEW's flattened order; 0.2.0 defaulted to
|
|
23
|
+
little-endian.
|
|
24
|
+
- Every refusal raises the new `BitpackError` (a `ValueError`). In 0.2.0 some bad input
|
|
25
|
+
leaked `TypeError` or `AttributeError` from inside, and some was silently accepted.
|
|
26
|
+
- Boolean arrays are checked: a `str` or bytes object is refused (0.2.0 read each
|
|
27
|
+
character's truthiness, so `"0101"` gave 15), and elements must be booleans or 0/1.
|
|
28
|
+
- Arrays may be any finite iterable; 0.2.0 needed indexing, so generators failed, and
|
|
29
|
+
`bools_to_number` refused NumPy arrays ("truth value of an array is ambiguous").
|
|
30
|
+
- `IntType` refuses a non-`int` or `bool` width and a non-`bool` `signed`; 0.2.0 accepted
|
|
31
|
+
`IntType(8.0, "yes")`. `IntType.parse` accepts only the eight names (with `u`, `uint`, `i`,
|
|
32
|
+
`int` prefixes); 0.2.0 accepted forms such as `"u08"`. A NumPy or other `__index__`
|
|
33
|
+
width is accepted and stored as `int`, and a NumPy boolean `signed` is stored as `bool`.
|
|
34
|
+
`IntType.contains` answers False for `bool` and floats (0.2.0 said True for
|
|
35
|
+
`contains(1.0)` and `contains(True)`) and True for in-range `__index__` integers.
|
|
36
|
+
- Byte arguments accept any buffer-protocol object and read its raw bytes, so a buffer
|
|
37
|
+
of wide items (an `array.array("H")`, a NumPy `uint16` array) is measured in bytes; 0.2.0
|
|
38
|
+
measured it in items. Lists of integers, which 0.2.0 read as bytes, are refused, and so
|
|
39
|
+
are buffers of Python objects (a NumPy `object` array), which 0.2.0 read as pointer bytes.
|
|
40
|
+
- `number` and `count` are typed `SupportsIndex`, so NumPy integers pass a type checker.
|
|
41
|
+
- `SignExtension(...)` with anything but a member or one of its three names raises
|
|
42
|
+
`BitpackError` (still a `ValueError`). `sign_extension=` takes only a `SignExtension`
|
|
43
|
+
member: 0.2.0 silently treated a string such as `"never"` as `IF_SIGNED`.
|
|
44
|
+
- `IntType` and `SignExtension` are defined in private modules other than 0.2.0's
|
|
45
|
+
(`lvbitpack._types`, `lvbitpack._modes`); pickles made by 0.2.0 still load.
|
|
46
|
+
- Numbers are checked: `bool` and non-integers are refused, `__index__` integers (NumPy)
|
|
47
|
+
accepted, and messages never print a huge integer (0.2.0 failed inside its own message
|
|
48
|
+
for integers beyond Python's 4300-digit printing limit).
|
|
49
|
+
- The command line prefixes errors with `lvbitpack: error:`, bounds every message, exits 1
|
|
50
|
+
quietly when its output pipe closes, and runs as `python -m lvbitpack`. An unknown
|
|
51
|
+
`--type` is a usage error (exit 2, was 1). Bit strings also ignore `_`; `unpack` refuses
|
|
52
|
+
numbers longer than 100 characters or written with non-ASCII digits, and reads negative
|
|
53
|
+
prefixed numbers such as `-0x2` on every supported Python.
|
|
54
|
+
- Public functions report `lvbitpack` as their module in `help()`; the classes keep the
|
|
55
|
+
module they are defined in, so source lookup and pickles find them.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- `BitpackError`.
|
|
60
|
+
- `bit_order="msb" | "lsb"` on `pack_bools` and `unpack_bools`; `unpack_bools`'s `count` is
|
|
61
|
+
now optional (every bit by default).
|
|
62
|
+
- `flatten` and `unflatten` commands.
|
|
63
|
+
- `GUIDE.md` with executed examples.
|
|
64
|
+
|
|
65
|
+
## [0.2.0] - not released
|
|
66
|
+
|
|
67
|
+
- `bools_to_number` and `number_to_bools`: LabVIEW Boolean Array To Number and
|
|
68
|
+
Number To Boolean Array, element 0 the least significant bit, U8/U16/U32/U64 and
|
|
69
|
+
signed I8/I16/I32/I64 two's complement, extra elements dropped, and the three
|
|
70
|
+
documented sign-extension modes for short arrays.
|
|
71
|
+
- `SignExtension` enum and the typed `IntType` value object with the `U8` to `I64`
|
|
72
|
+
presets, bounds, mask, and a `parse` helper.
|
|
73
|
+
- `number_to_bytes`, `bytes_to_number`, `bools_to_bytes`, and `bytes_to_bools`.
|
|
74
|
+
- `lvbitpack` command line interface: `pack` and `unpack`.
|
|
75
|
+
|
|
76
|
+
## [0.1.0] - not released
|
|
77
|
+
|
|
78
|
+
- `pack_bools` and `unpack_bools`: MSB-first bit packing with zero padding of the
|
|
79
|
+
final byte and count-aware unpacking.
|
|
80
|
+
- `pack_flattened` and `unpack_flattened`: a four-byte big-endian element count
|
|
81
|
+
followed by the packed bits.
|
|
@@ -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/lvbitpack/__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.
|
lvbitpack-1.0.0/GUIDE.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# lvbitpack guide
|
|
2
|
+
|
|
3
|
+
Every example below runs as written; the test suite executes them.
|
|
4
|
+
|
|
5
|
+
## Boolean Array To Number
|
|
6
|
+
|
|
7
|
+
`bools_to_number(values, int_type, *, sign_extension=SignExtension.IF_SIGNED)` reads the
|
|
8
|
+
array as the binary form of one integer. Element 0 is the least significant bit.
|
|
9
|
+
|
|
10
|
+
```pycon
|
|
11
|
+
>>> from lvbitpack import I8, I16, U8, SignExtension, bools_to_number
|
|
12
|
+
>>> bools_to_number([False, True], U8)
|
|
13
|
+
2
|
|
14
|
+
>>> bools_to_number([True] * 8 + [False, True], U8)
|
|
15
|
+
255
|
|
16
|
+
>>> bools_to_number([], I16)
|
|
17
|
+
0
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Elements past the width are dropped. A shorter array is padded above its last element as
|
|
22
|
+
the sign extension mode (set by right-clicking the node in LabVIEW) says:
|
|
23
|
+
|
|
24
|
+
| Mode | Unsigned output pads with | Signed output pads with |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `SignExtension.NEVER` | FALSE | FALSE |
|
|
27
|
+
| `SignExtension.IF_SIGNED` (LabVIEW's default) | FALSE | the last element |
|
|
28
|
+
| `SignExtension.ALWAYS` | the last element | the last element |
|
|
29
|
+
|
|
30
|
+
```pycon
|
|
31
|
+
>>> from lvbitpack import I8, U8, SignExtension, bools_to_number
|
|
32
|
+
>>> array = [False, False, True, False, True]
|
|
33
|
+
>>> [bools_to_number(array, U8, sign_extension=mode) for mode in SignExtension]
|
|
34
|
+
[20, 20, 244]
|
|
35
|
+
>>> [bools_to_number(array, I8, sign_extension=mode) for mode in SignExtension]
|
|
36
|
+
[20, -12, -12]
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A signed output is the two's complement value; an empty array is 0 in every mode. The test
|
|
41
|
+
suite replays about 2,900 results of this node and 200 of Number To Boolean Array produced
|
|
42
|
+
by LabVIEW 2026 (all eight types, the three modes, arrays of 0 to 65 elements; see
|
|
43
|
+
`tests/fixtures/labview2026`). There a node with no output representation set gave a U32
|
|
44
|
+
(it was inserted by editing a VI file; NI does not document the default).
|
|
45
|
+
|
|
46
|
+
## Number To Boolean Array
|
|
47
|
+
|
|
48
|
+
`number_to_bools(number, int_type)` returns one element per bit of the type, element 0 the
|
|
49
|
+
least significant bit of the two's complement form. A number outside the type raises
|
|
50
|
+
`BitpackError` (LabVIEW would have coerced it to the wire's type first).
|
|
51
|
+
|
|
52
|
+
```pycon
|
|
53
|
+
>>> from lvbitpack import I8, U64, number_to_bools
|
|
54
|
+
>>> number_to_bools(-2, I8)
|
|
55
|
+
[False, True, True, True, True, True, True, True]
|
|
56
|
+
>>> len(number_to_bools(5, U64))
|
|
57
|
+
64
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Integer types
|
|
62
|
+
|
|
63
|
+
`IntType(bits, signed)` takes 8, 16, 32 or 64 bits. `U8`, `U16`, `U32`, `U64`, `I8`, `I16`,
|
|
64
|
+
`I32` and `I64` are ready-made, and `IntType.parse` reads names in any case.
|
|
65
|
+
|
|
66
|
+
```pycon
|
|
67
|
+
>>> from lvbitpack import IntType, I32
|
|
68
|
+
>>> IntType.parse("int32") == I32
|
|
69
|
+
True
|
|
70
|
+
>>> I32.name, I32.min, I32.max, hex(I32.mask)
|
|
71
|
+
('i32', -2147483648, 2147483647, '0xffffffff')
|
|
72
|
+
>>> I32.contains(2**31)
|
|
73
|
+
False
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Bytes of the number
|
|
78
|
+
|
|
79
|
+
`number_to_bytes`, `bytes_to_number`, `bools_to_bytes` and `bytes_to_bools` lay the number
|
|
80
|
+
out in exactly `bits // 8` bytes. The default is big-endian, LabVIEW's flattened order;
|
|
81
|
+
pass `byteorder="little"` for the memory order of x86 and ARM machines. The element to bit
|
|
82
|
+
mapping never changes with byte order.
|
|
83
|
+
|
|
84
|
+
```pycon
|
|
85
|
+
>>> from lvbitpack import I32, U16, bools_to_bytes, bytes_to_number, number_to_bytes
|
|
86
|
+
>>> number_to_bytes(-19, I32).hex()
|
|
87
|
+
'ffffffed'
|
|
88
|
+
>>> bytes_to_number(bytes.fromhex("edffffff"), I32, byteorder="little")
|
|
89
|
+
-19
|
|
90
|
+
>>> bools_to_bytes([False, True], U16, byteorder="little")
|
|
91
|
+
b'\x02\x00'
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Flattened Boolean arrays
|
|
96
|
+
|
|
97
|
+
`pack_flattened` writes what LabVIEW's Flatten To String writes for a 1-D Boolean array with
|
|
98
|
+
its "prepend array or string size?" input left at its default (TRUE): a big-endian I32
|
|
99
|
+
element count, then one byte per element, 0x01 for TRUE and 0x00 for FALSE.
|
|
100
|
+
`unpack_flattened` reads it back; any nonzero element byte is TRUE, and the input must hold
|
|
101
|
+
exactly the counted elements.
|
|
102
|
+
|
|
103
|
+
```pycon
|
|
104
|
+
>>> from lvbitpack import pack_flattened, unpack_flattened
|
|
105
|
+
>>> pack_flattened([True, False, True, True]).hex()
|
|
106
|
+
'0000000401000101'
|
|
107
|
+
>>> unpack_flattened(bytes.fromhex("00000002ff00"))
|
|
108
|
+
[True, False]
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Dense bit packing
|
|
113
|
+
|
|
114
|
+
`pack_bools` stores eight elements per byte with no header, filling unused bits of the last
|
|
115
|
+
byte with zeros. `bit_order="msb"` (default) puts element 0 in bit 7 of byte 0; `"lsb"` puts
|
|
116
|
+
it in bit 0, so each byte equals Boolean Array To Number of its eight elements as a U8.
|
|
117
|
+
`unpack_bools(data, count=None)` reads `count` elements, or every bit when `count` is None.
|
|
118
|
+
Current LabVIEW stores and flattens a Boolean as a whole byte; this form is for protocols
|
|
119
|
+
and files that pack bits.
|
|
120
|
+
|
|
121
|
+
```pycon
|
|
122
|
+
>>> from lvbitpack import pack_bools, unpack_bools
|
|
123
|
+
>>> pack_bools([True] * 9), pack_bools([True] * 9, bit_order="lsb")
|
|
124
|
+
(b'\xff\x80', b'\xff\x01')
|
|
125
|
+
>>> unpack_bools(b"\xd0", 4)
|
|
126
|
+
[True, True, False, True]
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## What counts as input
|
|
131
|
+
|
|
132
|
+
- A Boolean array is any finite iterable (list, tuple, generator, NumPy array) of `True`,
|
|
133
|
+
`False`, 0, 1 (or `__index__` objects giving 0 or 1) or NumPy booleans; the hints say
|
|
134
|
+
`Iterable[bool | int]`. `str`, `bytes`, `bytearray` and `memoryview` are refused: `"0101"`
|
|
135
|
+
is not four Booleans. A set has no element order, so pass a sequence.
|
|
136
|
+
- Numbers, counts and `IntType` widths accept any `__index__` integer except `bool`
|
|
137
|
+
(`number` and `count` are typed `SupportsIndex`; elsewhere a type checker wants `int(...)`
|
|
138
|
+
or `bool(...)` around NumPy scalars). `IntType.contains` answers False for anything else.
|
|
139
|
+
- Bytes are any buffer-protocol object (`bytes`, `bytearray`, `memoryview`, `array.array`,
|
|
140
|
+
NumPy arrays, ctypes objects), read as raw bytes in memory order. Text, lists and buffers
|
|
141
|
+
of Python objects (a NumPy `object` array) are refused.
|
|
142
|
+
|
|
143
|
+
```pycon
|
|
144
|
+
>>> import array
|
|
145
|
+
>>> from lvbitpack import U16, bytes_to_number
|
|
146
|
+
>>> bytes_to_number(array.array("B", [1, 0]), U16)
|
|
147
|
+
256
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Errors
|
|
152
|
+
|
|
153
|
+
Every refusal raises `BitpackError`, a subclass of `ValueError`, including
|
|
154
|
+
`SignExtension(value)` for any value that is not a member or one of its three names.
|
|
155
|
+
Messages are printable ASCII, quote at most 40 characters of a bad value and describe
|
|
156
|
+
integers wider than 128 bits by their size. Checks use an object's real type, never a
|
|
157
|
+
`__class__` it claims. An object that cannot be iterated, indexed or read as a buffer at
|
|
158
|
+
all is refused; any other exception your own object raises (from `__index__`, `__iter__`,
|
|
159
|
+
while iterating, or from `__buffer__` on Python 3.12 and later, except a `ValueError`)
|
|
160
|
+
passes through unchanged, as do ordinary call mistakes such as a missing argument.
|
|
161
|
+
|
|
162
|
+
| Situation | Message starts with |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| number outside the type | `<n> is out of range for <type>` |
|
|
165
|
+
| element that is not a Boolean | `element <i> must be a boolean` |
|
|
166
|
+
| text or bytes as the array | `values must be an iterable of booleans` |
|
|
167
|
+
| wrong byte count for a number | `<type> needs exactly <n> bytes` |
|
|
168
|
+
| short flattened array | `a flattened array starts`, `the count says` |
|
|
169
|
+
| negative count, extra bytes | `the element count is negative`, `<n> byte(s) follow(s)` |
|
|
170
|
+
| bad option or `int_type` | the parameter's name |
|
|
171
|
+
|
|
172
|
+
## Command line
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
$ lvbitpack pack 01 --type u8
|
|
176
|
+
2
|
|
177
|
+
$ lvbitpack pack 01 --type i8
|
|
178
|
+
-2
|
|
179
|
+
$ lvbitpack pack 0101 --type i8 --sign-extension always
|
|
180
|
+
-6
|
|
181
|
+
$ lvbitpack pack 1 --type u16 --base hex
|
|
182
|
+
0x1
|
|
183
|
+
$ lvbitpack unpack 0x80 --type u8
|
|
184
|
+
00000001
|
|
185
|
+
$ lvbitpack flatten 1011
|
|
186
|
+
0000000401000101
|
|
187
|
+
$ lvbitpack unflatten 0000000401000101
|
|
188
|
+
1011
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Bit strings list element 0 first; `1`, `t`, `T` are TRUE, `0`, `f`, `F` FALSE, and spaces,
|
|
192
|
+
commas and underscores are ignored. `unpack` reads decimal or `0x`, `0o`, `0b` prefixed
|
|
193
|
+
numbers in ASCII digits, negative ones included (`unpack -0x80 -t i8`). Data errors print
|
|
194
|
+
`lvbitpack: error: ...` and exit 1; usage errors, an unknown `--type` included, exit 2.
|
|
195
|
+
|
|
196
|
+
## Scope
|
|
197
|
+
|
|
198
|
+
Boolean Array To Number can also produce a fixed-point number, and Number To Boolean Array
|
|
199
|
+
accepts one; lvbitpack covers the eight integer types. Multi-dimensional arrays are not
|
|
200
|
+
flattened here.
|
lvbitpack-1.0.0/LICENSE
ADDED
|
@@ -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.
|
lvbitpack-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lvbitpack
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: LabVIEW Boolean arrays in pure Python: Boolean Array To Number with its sign extension modes, Number To Boolean Array, flattening and bit packing
|
|
5
|
+
Project-URL: Homepage, https://lianes8studio.com
|
|
6
|
+
Project-URL: Source, https://github.com/JaviLianes8/lvbitpack
|
|
7
|
+
Project-URL: Issues, https://github.com/JaviLianes8/lvbitpack/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/JaviLianes8/lvbitpack/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
|
+
Keywords: binary,bit,bitpack,boolean,flatten,labview,ni,sign-extension
|
|
15
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: build; extra == 'dev'
|
|
32
|
+
Requires-Dist: editables; extra == 'dev'
|
|
33
|
+
Requires-Dist: hatchling; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
37
|
+
Requires-Dist: twine; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# lvbitpack
|
|
41
|
+
|
|
42
|
+
LabVIEW Boolean arrays in pure Python: **Boolean Array To Number** with its three sign
|
|
43
|
+
extension modes, **Number To Boolean Array**, the flattened form LabVIEW writes for a Boolean
|
|
44
|
+
array, and compact bit packing. No dependencies, fully typed, Python 3.10 or newer.
|
|
45
|
+
|
|
46
|
+
The full reference is the [guide](https://github.com/JaviLianes8/lvbitpack/blob/main/GUIDE.md);
|
|
47
|
+
every release is described in the
|
|
48
|
+
[changelog](https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md).
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pip install lvbitpack
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## The problem
|
|
57
|
+
|
|
58
|
+
A LabVIEW program that reads digital lines, status registers or flag words often turns a
|
|
59
|
+
Boolean array into one integer with Boolean Array To Number, and back with Number To Boolean
|
|
60
|
+
Array. Element 0 is the least significant bit, elements past the width are dropped, and a
|
|
61
|
+
short array is padded by a per-node setting (Sign Extension Mode) that most people never
|
|
62
|
+
look at, yet it decides whether `{FALSE, TRUE}` as an I8 is 2 or -2. Getting the same
|
|
63
|
+
numbers in Python means reproducing those rules exactly.
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from lvbitpack import I8, U8, U16, SignExtension, bools_to_number, number_to_bools
|
|
69
|
+
|
|
70
|
+
# Element 0 is the least significant bit.
|
|
71
|
+
assert bools_to_number([False, True], U8) == 2
|
|
72
|
+
|
|
73
|
+
# LabVIEW's default mode pads a signed output with the last element: {F, T} as I8 is -2.
|
|
74
|
+
assert bools_to_number([False, True], I8) == -2
|
|
75
|
+
assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.NEVER) == 10
|
|
76
|
+
assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.ALWAYS) == -6
|
|
77
|
+
|
|
78
|
+
# Elements beyond the width are dropped.
|
|
79
|
+
assert bools_to_number([True] * 9, U8) == 255
|
|
80
|
+
|
|
81
|
+
# The inverse returns one element per bit of the type.
|
|
82
|
+
assert number_to_bools(-2, I8) == [False, True, True, True, True, True, True, True]
|
|
83
|
+
assert len(number_to_bools(1, U16)) == 16
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Bytes, LabVIEW's flattened array and dense bit packing:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from lvbitpack import U16, bools_to_bytes, pack_bools, pack_flattened, unpack_flattened
|
|
90
|
+
|
|
91
|
+
# The number's bytes, big-endian by default like LabVIEW's Flatten To String.
|
|
92
|
+
assert bools_to_bytes([False, True], U16) == b"\x00\x02"
|
|
93
|
+
assert bools_to_bytes([False, True], U16, byteorder="little") == b"\x02\x00"
|
|
94
|
+
|
|
95
|
+
# Flatten To String of a Boolean array: an I32 count, then one byte per element.
|
|
96
|
+
assert pack_flattened([True, False, True, True]).hex() == "0000000401000101"
|
|
97
|
+
assert unpack_flattened(bytes.fromhex("0000000401000101")) == [True, False, True, True]
|
|
98
|
+
|
|
99
|
+
# Eight elements per byte, first element in the high bit (or the low bit with "lsb").
|
|
100
|
+
assert pack_bools([True, True, False, True]) == b"\xd0"
|
|
101
|
+
assert pack_bools([True, True, False, True], bit_order="lsb") == b"\x0b"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Anything refused (a value out of range, a string where a Boolean array belongs, a truncated
|
|
105
|
+
flattened array) raises `lvbitpack.BitpackError`, a `ValueError`.
|
|
106
|
+
|
|
107
|
+
## Command line
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
$ lvbitpack pack 01 --type i8
|
|
111
|
+
-2
|
|
112
|
+
$ lvbitpack pack 0101 --type i8 --sign-extension never
|
|
113
|
+
10
|
|
114
|
+
$ lvbitpack unpack -2 --type i8
|
|
115
|
+
01111111
|
|
116
|
+
$ lvbitpack flatten 1011
|
|
117
|
+
0000000401000101
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Bit strings list element 0 first. `python -m lvbitpack` works too.
|
|
121
|
+
|
|
122
|
+
## Why this library
|
|
123
|
+
|
|
124
|
+
- It follows LabVIEW's own rules: the three Sign Extension Mode settings, truncation of long
|
|
125
|
+
arrays, the 8, 16, 32 and 64-bit signed and unsigned outputs, and the flattened Boolean
|
|
126
|
+
array layout, checked against NI's documentation and against output of LabVIEW 2026.
|
|
127
|
+
- NumPy's `packbits` packs 0/1 arrays into `uint8` bytes (high or low bit first) but has no
|
|
128
|
+
notion of LabVIEW's integer widths or sign extension, and needs NumPy. `bitarray` (a C
|
|
129
|
+
extension) and `bitstring` (which depends on `tibs`) are general bit containers; neither
|
|
130
|
+
reproduces Boolean Array To Number. lvbitpack is small, pure Python and dependency free.
|
|
131
|
+
|
|
132
|
+
What it does not cover: Boolean Array To Number with a fixed-point output (see the guide).
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT. See [LICENSE](https://github.com/JaviLianes8/lvbitpack/blob/main/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,103 @@
|
|
|
1
|
+
# lvbitpack
|
|
2
|
+
|
|
3
|
+
LabVIEW Boolean arrays in pure Python: **Boolean Array To Number** with its three sign
|
|
4
|
+
extension modes, **Number To Boolean Array**, the flattened form LabVIEW writes for a Boolean
|
|
5
|
+
array, and compact bit packing. No dependencies, fully typed, Python 3.10 or newer.
|
|
6
|
+
|
|
7
|
+
The full reference is the [guide](https://github.com/JaviLianes8/lvbitpack/blob/main/GUIDE.md);
|
|
8
|
+
every release is described in the
|
|
9
|
+
[changelog](https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install lvbitpack
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The problem
|
|
18
|
+
|
|
19
|
+
A LabVIEW program that reads digital lines, status registers or flag words often turns a
|
|
20
|
+
Boolean array into one integer with Boolean Array To Number, and back with Number To Boolean
|
|
21
|
+
Array. Element 0 is the least significant bit, elements past the width are dropped, and a
|
|
22
|
+
short array is padded by a per-node setting (Sign Extension Mode) that most people never
|
|
23
|
+
look at, yet it decides whether `{FALSE, TRUE}` as an I8 is 2 or -2. Getting the same
|
|
24
|
+
numbers in Python means reproducing those rules exactly.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from lvbitpack import I8, U8, U16, SignExtension, bools_to_number, number_to_bools
|
|
30
|
+
|
|
31
|
+
# Element 0 is the least significant bit.
|
|
32
|
+
assert bools_to_number([False, True], U8) == 2
|
|
33
|
+
|
|
34
|
+
# LabVIEW's default mode pads a signed output with the last element: {F, T} as I8 is -2.
|
|
35
|
+
assert bools_to_number([False, True], I8) == -2
|
|
36
|
+
assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.NEVER) == 10
|
|
37
|
+
assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.ALWAYS) == -6
|
|
38
|
+
|
|
39
|
+
# Elements beyond the width are dropped.
|
|
40
|
+
assert bools_to_number([True] * 9, U8) == 255
|
|
41
|
+
|
|
42
|
+
# The inverse returns one element per bit of the type.
|
|
43
|
+
assert number_to_bools(-2, I8) == [False, True, True, True, True, True, True, True]
|
|
44
|
+
assert len(number_to_bools(1, U16)) == 16
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Bytes, LabVIEW's flattened array and dense bit packing:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from lvbitpack import U16, bools_to_bytes, pack_bools, pack_flattened, unpack_flattened
|
|
51
|
+
|
|
52
|
+
# The number's bytes, big-endian by default like LabVIEW's Flatten To String.
|
|
53
|
+
assert bools_to_bytes([False, True], U16) == b"\x00\x02"
|
|
54
|
+
assert bools_to_bytes([False, True], U16, byteorder="little") == b"\x02\x00"
|
|
55
|
+
|
|
56
|
+
# Flatten To String of a Boolean array: an I32 count, then one byte per element.
|
|
57
|
+
assert pack_flattened([True, False, True, True]).hex() == "0000000401000101"
|
|
58
|
+
assert unpack_flattened(bytes.fromhex("0000000401000101")) == [True, False, True, True]
|
|
59
|
+
|
|
60
|
+
# Eight elements per byte, first element in the high bit (or the low bit with "lsb").
|
|
61
|
+
assert pack_bools([True, True, False, True]) == b"\xd0"
|
|
62
|
+
assert pack_bools([True, True, False, True], bit_order="lsb") == b"\x0b"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Anything refused (a value out of range, a string where a Boolean array belongs, a truncated
|
|
66
|
+
flattened array) raises `lvbitpack.BitpackError`, a `ValueError`.
|
|
67
|
+
|
|
68
|
+
## Command line
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
$ lvbitpack pack 01 --type i8
|
|
72
|
+
-2
|
|
73
|
+
$ lvbitpack pack 0101 --type i8 --sign-extension never
|
|
74
|
+
10
|
|
75
|
+
$ lvbitpack unpack -2 --type i8
|
|
76
|
+
01111111
|
|
77
|
+
$ lvbitpack flatten 1011
|
|
78
|
+
0000000401000101
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Bit strings list element 0 first. `python -m lvbitpack` works too.
|
|
82
|
+
|
|
83
|
+
## Why this library
|
|
84
|
+
|
|
85
|
+
- It follows LabVIEW's own rules: the three Sign Extension Mode settings, truncation of long
|
|
86
|
+
arrays, the 8, 16, 32 and 64-bit signed and unsigned outputs, and the flattened Boolean
|
|
87
|
+
array layout, checked against NI's documentation and against output of LabVIEW 2026.
|
|
88
|
+
- NumPy's `packbits` packs 0/1 arrays into `uint8` bytes (high or low bit first) but has no
|
|
89
|
+
notion of LabVIEW's integer widths or sign extension, and needs NumPy. `bitarray` (a C
|
|
90
|
+
extension) and `bitstring` (which depends on `tibs`) are general bit containers; neither
|
|
91
|
+
reproduces Boolean Array To Number. lvbitpack is small, pure Python and dependency free.
|
|
92
|
+
|
|
93
|
+
What it does not cover: Boolean Array To Number with a fixed-point output (see the guide).
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT. See [LICENSE](https://github.com/JaviLianes8/lvbitpack/blob/main/LICENSE).
|
|
98
|
+
|
|
99
|
+
## Links
|
|
100
|
+
|
|
101
|
+
- Website: https://lianes8studio.com
|
|
102
|
+
- GitHub: https://github.com/JaviLianes8
|
|
103
|
+
- LinkedIn: https://www.linkedin.com/in/jlianes/
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lvbitpack"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "LabVIEW Boolean arrays in pure Python: Boolean Array To Number with its sign extension modes, Number To Boolean Array, flattening and bit packing"
|
|
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 = ["labview", "boolean", "bit", "bitpack", "flatten", "sign-extension", "ni", "binary"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Scientific/Engineering",
|
|
28
|
+
"Topic :: Software Development :: Libraries",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["ruff", "mypy", "pytest", "build", "twine", "hatchling", "editables"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
lvbitpack = "lvbitpack._cli:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://lianes8studio.com"
|
|
41
|
+
Source = "https://github.com/JaviLianes8/lvbitpack"
|
|
42
|
+
Issues = "https://github.com/JaviLianes8/lvbitpack/issues"
|
|
43
|
+
Changelog = "https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md"
|
|
44
|
+
LinkedIn = "https://www.linkedin.com/in/jlianes/"
|
|
45
|
+
Funding = "https://buymeacoffee.com/jlianesglrs"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/lvbitpack"]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.sdist]
|
|
51
|
+
include = [
|
|
52
|
+
"src",
|
|
53
|
+
"tests",
|
|
54
|
+
"README.md",
|
|
55
|
+
"GUIDE.md",
|
|
56
|
+
"CHANGELOG.md",
|
|
57
|
+
"CONTRIBUTING.md",
|
|
58
|
+
"LICENSE",
|
|
59
|
+
"pyproject.toml",
|
|
60
|
+
"requirements-dev.txt",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 100
|
|
65
|
+
target-version = "py310"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
69
|
+
|
|
70
|
+
[tool.mypy]
|
|
71
|
+
python_version = "3.10"
|
|
72
|
+
strict = true
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
pythonpath = ["src"]
|
|
77
|
+
addopts = "-q"
|