hexconv 0.2.4__tar.gz → 0.2.6__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.
- {hexconv-0.2.4/src/hexconv.egg-info → hexconv-0.2.6}/PKG-INFO +108 -128
- {hexconv-0.2.4 → hexconv-0.2.6}/README.md +107 -127
- {hexconv-0.2.4 → hexconv-0.2.6}/pyproject.toml +1 -1
- {hexconv-0.2.4 → hexconv-0.2.6/src/hexconv.egg-info}/PKG-INFO +108 -128
- {hexconv-0.2.4 → hexconv-0.2.6}/tests/test_hexconv.py +51 -3
- {hexconv-0.2.4 → hexconv-0.2.6}/LICENSE +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/MANIFEST.in +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/setup.cfg +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv/__init__.py +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv/_core.py +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv/py.typed +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv.egg-info/SOURCES.txt +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv.egg-info/dependency_links.txt +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv.egg-info/requires.txt +0 -0
- {hexconv-0.2.4 → hexconv-0.2.6}/src/hexconv.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexconv
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Pythonic conversion toolkit for bytes, hex, integers, arrays, binary strings, base64, and text.
|
|
5
5
|
Author: hexconv contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -41,83 +41,96 @@ It is built around ergonomic one-liners and composable format objects.
|
|
|
41
41
|
pip install hexconv
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Simple usage
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
Most conversions are one-liners. Use `from_auto` when you want a quick
|
|
47
|
+
best-effort parse, or pick an explicit helper when the input is ambiguous.
|
|
48
48
|
|
|
49
49
|
```python
|
|
50
50
|
import hexconv as hx
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# b'\xde\xad\xbe\xef'
|
|
55
|
-
|
|
56
|
-
spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
|
|
57
|
-
spaced_hex(b"\xde\xad\xbe\xef")
|
|
58
|
-
# 'de ad be ef'
|
|
52
|
+
hx.from_auto("0x64617461").text
|
|
53
|
+
# 'data'
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# [4660, 22136]
|
|
55
|
+
hx.from_bytes_array([0xde, 0xad, 0xbe, 0xef]).int
|
|
56
|
+
# 3735928559
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# 'ZGF0YQ'
|
|
58
|
+
hx.from_text("data").hex
|
|
59
|
+
# '64617461'
|
|
67
60
|
```
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
space-separated hex.
|
|
62
|
+
The bare `.hex`, `.text`, and `.bytes` properties are shortcuts for zero-argument
|
|
63
|
+
`to_*` methods. Call the method form when you need options:
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
```python
|
|
66
|
+
value = hx.from_hex("deadbeef")
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
value.to_hex()
|
|
69
|
+
# 'deadbeef' (same as value.hex)
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
value.to_hex(sep=" ", uppercase=True)
|
|
72
|
+
# 'DE AD BE EF'
|
|
73
|
+
|
|
74
|
+
value.to_bytes()
|
|
80
75
|
# b'\xde\xad\xbe\xef'
|
|
81
76
|
|
|
82
|
-
hx.
|
|
83
|
-
#
|
|
77
|
+
hx.from_hex("68656c6c6f").to_text()
|
|
78
|
+
# 'hello'
|
|
79
|
+
```
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
# b'\xde\xad\xbe\xef'
|
|
81
|
+
## Fast compose
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
Create your own converters by snapping together
|
|
84
|
+
`source format >> optional transforms >> target format`.
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
```python
|
|
87
|
+
to_bytes = hx.Hex() >> hx.Bytes()
|
|
88
|
+
to_bytes("68 69")
|
|
89
|
+
# b'hi'
|
|
93
90
|
|
|
94
|
-
hx.
|
|
95
|
-
|
|
91
|
+
spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
|
|
92
|
+
spaced_hex(b"data")
|
|
93
|
+
# '64 61 74 61'
|
|
96
94
|
|
|
97
|
-
hx.
|
|
98
|
-
|
|
95
|
+
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
|
|
96
|
+
words("12 34 56 78")
|
|
97
|
+
# [4660, 22136]
|
|
99
98
|
|
|
100
|
-
hx.
|
|
99
|
+
pack_to_hex = hx.Struct(">HH") >> hx.Hex()
|
|
100
|
+
pack_to_hex((0x1234, 0x5678))
|
|
101
101
|
# '12345678'
|
|
102
|
-
|
|
103
|
-
hx.from_bits("101", pad=True).hex
|
|
104
|
-
# '05'
|
|
105
102
|
```
|
|
106
103
|
|
|
107
|
-
|
|
104
|
+
## Configure when needed
|
|
105
|
+
|
|
106
|
+
Formats work bare by default. Add arguments only for the conversion that needs
|
|
107
|
+
them:
|
|
108
108
|
|
|
109
109
|
```python
|
|
110
|
-
hx.
|
|
110
|
+
hx.convert("de ad be ef", hx.Hex(), hx.Bytes())
|
|
111
111
|
# b'\xde\xad\xbe\xef'
|
|
112
112
|
|
|
113
|
-
hx.
|
|
114
|
-
# '
|
|
113
|
+
hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ", prefix=True, uppercase=True))
|
|
114
|
+
# '0xDE AD BE EF'
|
|
115
|
+
|
|
116
|
+
hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
|
|
117
|
+
# 'cf80'
|
|
115
118
|
|
|
116
|
-
hx.
|
|
117
|
-
# '
|
|
119
|
+
hx.from_int(0x12345678, endian="little").hex
|
|
120
|
+
# '78563412'
|
|
121
|
+
|
|
122
|
+
hx.convert("ZGF0YQ", hx.Base64(padding=False), hx.Text())
|
|
123
|
+
# 'data'
|
|
124
|
+
|
|
125
|
+
hx.from_binary("00010110 10010110", bit_order="lsb").text
|
|
126
|
+
# 'hi'
|
|
118
127
|
```
|
|
119
128
|
|
|
120
|
-
|
|
129
|
+
Hex strings may include common separators, so you do not need to manually strip
|
|
130
|
+
spaces, colons, dashes, or `0x` prefixes before converting. `Hex(sep=" ")` is
|
|
131
|
+
how you emit space-separated hex.
|
|
132
|
+
|
|
133
|
+
## Full pipeline guide
|
|
121
134
|
|
|
122
135
|
`hexconv` is built around small format objects. A format can parse directly:
|
|
123
136
|
|
|
@@ -128,8 +141,15 @@ hx.Hex()("de ad be ef").bytes
|
|
|
128
141
|
hx.Text()("data").hex
|
|
129
142
|
# '64617461'
|
|
130
143
|
|
|
131
|
-
hx.Int(
|
|
144
|
+
hx.Int(endian="little")(0x12345678).hex
|
|
132
145
|
# '78563412'
|
|
146
|
+
|
|
147
|
+
pack_words = hx.Struct(">HH")
|
|
148
|
+
pack_words((0x1234, 0x5678)).hex
|
|
149
|
+
# '12345678'
|
|
150
|
+
|
|
151
|
+
hx.Hex()("12345678").to(pack_words)
|
|
152
|
+
# (4660, 22136)
|
|
133
153
|
```
|
|
134
154
|
|
|
135
155
|
Formats compose with `>>`, so reusable converters are normal Python objects:
|
|
@@ -142,12 +162,20 @@ to_text([0x48, 0x65, 0x6C, 0x6C, 0x6F])
|
|
|
142
162
|
to_hex = hx.Text() >> hx.Hex(prefix=True)
|
|
143
163
|
to_hex("data")
|
|
144
164
|
# '0x64617461'
|
|
165
|
+
|
|
166
|
+
pack_to_hex = hx.Struct(">HH") >> hx.Hex()
|
|
167
|
+
pack_to_hex((0x1234, 0x5678))
|
|
168
|
+
# '12345678'
|
|
169
|
+
|
|
170
|
+
unpack_from_hex = hx.Hex() >> hx.Struct(">HH")
|
|
171
|
+
unpack_from_hex("12345678")
|
|
172
|
+
# (4660, 22136)
|
|
145
173
|
```
|
|
146
174
|
|
|
147
175
|
Transforms sit between formats when data needs shaping:
|
|
148
176
|
|
|
149
177
|
```python
|
|
150
|
-
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray(
|
|
178
|
+
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
|
|
151
179
|
words("12345678")
|
|
152
180
|
# [4660, 22136]
|
|
153
181
|
|
|
@@ -174,89 +202,27 @@ conv("12345678")
|
|
|
174
202
|
# ['0x1234', '0x5678']
|
|
175
203
|
```
|
|
176
204
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Use `convert` when you want an explicit source and target in one expression:
|
|
180
|
-
|
|
181
|
-
```python
|
|
182
|
-
hx.convert("de ad be ef", hx.Hex(sep=" "), hx.Bytes())
|
|
183
|
-
# b'\xde\xad\xbe\xef'
|
|
184
|
-
|
|
185
|
-
hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" "))
|
|
186
|
-
# 'de ad be ef'
|
|
187
|
-
|
|
188
|
-
hx.convert("data", hx.Text(), hx.Hex(prefix=True))
|
|
189
|
-
# '0x64617461'
|
|
190
|
-
|
|
191
|
-
hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
|
|
192
|
-
# 'cf80'
|
|
193
|
-
|
|
194
|
-
hx.convert("-_8", hx.Base64(urlsafe=True, padding=False), hx.Hex())
|
|
195
|
-
# 'fbff'
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Hex strings may include common separators, so you do not need to manually strip
|
|
199
|
-
spaces, colons, dashes, or `0x` prefixes before converting.
|
|
200
|
-
|
|
201
|
-
## Common conversion patterns
|
|
202
|
-
|
|
203
|
-
Text, bytes, and hex:
|
|
204
|
-
|
|
205
|
-
```python
|
|
206
|
-
hx.Text()("hello").bytes
|
|
207
|
-
# b'hello'
|
|
208
|
-
|
|
209
|
-
hx.Hex()("68656c6c6f").text
|
|
210
|
-
# 'hello'
|
|
211
|
-
|
|
212
|
-
hx.BytesArray()([0x68, 0x69]).to(hx.Text())
|
|
213
|
-
# 'hi'
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Integer packing:
|
|
205
|
+
More conversion patterns:
|
|
217
206
|
|
|
218
207
|
```python
|
|
219
|
-
hx.
|
|
220
|
-
# '
|
|
208
|
+
hx.BytesArray()([0x4F, 0x4B]).to(hx.Text())
|
|
209
|
+
# 'OK'
|
|
221
210
|
|
|
222
|
-
hx.Int(
|
|
211
|
+
hx.Int(endian="little")(0x1234).hex
|
|
223
212
|
# '3412'
|
|
224
213
|
|
|
225
|
-
(hx.Hex() >> hx.Chunk(2) >> hx.IntArray(width=2))("12345678")
|
|
226
|
-
# [4660, 22136]
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Base encodings:
|
|
230
|
-
|
|
231
|
-
```python
|
|
232
|
-
hx.Text()("data").to(hx.Base64())
|
|
233
|
-
# 'ZGF0YQ=='
|
|
234
|
-
|
|
235
214
|
hx.Text()("data").to(hx.Base32(padding=False))
|
|
236
215
|
# 'MRQXIYI'
|
|
237
216
|
|
|
238
|
-
hx.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Escaped strings, hexdumps, structs, and bits:
|
|
243
|
-
|
|
244
|
-
```python
|
|
245
|
-
hx.Escaped()(r"\xde\xad").hex
|
|
246
|
-
# 'dead'
|
|
247
|
-
|
|
248
|
-
dump = hx.Hex()("deadbeef").to(hx.Hexdump(width=2))
|
|
249
|
-
hx.Hexdump()(dump).hex
|
|
250
|
-
# 'deadbeef'
|
|
251
|
-
|
|
252
|
-
hx.Struct(">HH")((0x1234, 0x5678)).hex
|
|
253
|
-
# '12345678'
|
|
217
|
+
dump = hx.Text()("data").to(hx.Hexdump(width=2))
|
|
218
|
+
hx.Hexdump()(dump).text
|
|
219
|
+
# 'data'
|
|
254
220
|
|
|
255
221
|
hx.Hex()("12345678").to(hx.Struct(">HH"))
|
|
256
222
|
# (4660, 22136)
|
|
257
223
|
|
|
258
|
-
hx.Bits()(
|
|
259
|
-
# '
|
|
224
|
+
hx.Bits()("0100111101001011").text
|
|
225
|
+
# 'OK'
|
|
260
226
|
```
|
|
261
227
|
|
|
262
228
|
## Why explicit formats?
|
|
@@ -304,10 +270,10 @@ result.reason
|
|
|
304
270
|
`from_auto(..., explain=True)` returns the same explanation object:
|
|
305
271
|
|
|
306
272
|
```python
|
|
307
|
-
result = hx.from_auto("
|
|
273
|
+
result = hx.from_auto("0b0100111101001011", explain=True)
|
|
308
274
|
|
|
309
|
-
result.value.
|
|
310
|
-
# '
|
|
275
|
+
result.value.text
|
|
276
|
+
# 'OK'
|
|
311
277
|
```
|
|
312
278
|
|
|
313
279
|
## Design priorities
|
|
@@ -331,7 +297,7 @@ conv("deadbeef")
|
|
|
331
297
|
# 3735928559
|
|
332
298
|
```
|
|
333
299
|
|
|
334
|
-
Available format specs:
|
|
300
|
+
Available format specs all work in their simplest bare form:
|
|
335
301
|
|
|
336
302
|
- `Bytes()`
|
|
337
303
|
- `BytesArray()`
|
|
@@ -339,8 +305,8 @@ Available format specs:
|
|
|
339
305
|
- `Hex()`
|
|
340
306
|
- `HexArray()`
|
|
341
307
|
- `HexInt()` / `LargeHexNumber()`
|
|
342
|
-
- `Int()`
|
|
343
|
-
- `IntArray()`
|
|
308
|
+
- `Int()` / `DecimalInt()`
|
|
309
|
+
- `IntArray()` / `DecimalIntArray()`
|
|
344
310
|
- `Text()`
|
|
345
311
|
- `Binary()`
|
|
346
312
|
- `Base64()`
|
|
@@ -351,6 +317,20 @@ Available format specs:
|
|
|
351
317
|
- `Hexdump()`
|
|
352
318
|
- `Struct(fmt)`
|
|
353
319
|
- `Bits()`
|
|
320
|
+
- `Auto()`
|
|
321
|
+
|
|
322
|
+
Add options only when you need control over formatting or parsing:
|
|
323
|
+
|
|
324
|
+
- Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
|
|
325
|
+
- Integer packing: `Int(width=4)`, `Int(endian="little")`, `IntArray(width=2)`.
|
|
326
|
+
- Text: `Text(encoding="utf-8", errors="replace")`.
|
|
327
|
+
- Base encodings: `Base64(urlsafe=True, padding=False)`, `Base32(padding=False)`.
|
|
328
|
+
- Binary/bits: `Binary(sep=" ", prefix=True, bit_order="lsb")`, `Bits(pad_side="right")`.
|
|
329
|
+
- Dumps/escapes/structs: `Hexdump(width=8)`, `Escaped(uppercase=True)`, `Struct(">HH")`.
|
|
330
|
+
|
|
331
|
+
Some options only matter in one direction. For example, `Hex(sep=" ")`
|
|
332
|
+
controls hex output formatting, while hex input already accepts common
|
|
333
|
+
separators such as spaces, colons, dashes, underscores, and `0x` prefixes.
|
|
354
334
|
|
|
355
335
|
## Transforms
|
|
356
336
|
|
|
@@ -358,7 +338,7 @@ Transforms are byte-to-byte steps that sit between input and output formats.
|
|
|
358
338
|
|
|
359
339
|
```python
|
|
360
340
|
hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
|
|
361
|
-
hx.Hex() >> hx.Group(2) >> hx.IntArray(
|
|
341
|
+
hx.Hex() >> hx.Group(2) >> hx.IntArray()
|
|
362
342
|
hx.Hex() >> hx.Pad.left(width=8) >> hx.Hex()
|
|
363
343
|
hx.Hex() >> hx.Pad.right(block_size=8, byte=0xFF) >> hx.Hex()
|
|
364
344
|
hx.Hex() >> hx.Reverse() >> hx.Hex()
|
|
@@ -12,83 +12,96 @@ It is built around ergonomic one-liners and composable format objects.
|
|
|
12
12
|
pip install hexconv
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Simple usage
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
Most conversions are one-liners. Use `from_auto` when you want a quick
|
|
18
|
+
best-effort parse, or pick an explicit helper when the input is ambiguous.
|
|
19
19
|
|
|
20
20
|
```python
|
|
21
21
|
import hexconv as hx
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# b'\xde\xad\xbe\xef'
|
|
26
|
-
|
|
27
|
-
spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
|
|
28
|
-
spaced_hex(b"\xde\xad\xbe\xef")
|
|
29
|
-
# 'de ad be ef'
|
|
23
|
+
hx.from_auto("0x64617461").text
|
|
24
|
+
# 'data'
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
# [4660, 22136]
|
|
26
|
+
hx.from_bytes_array([0xde, 0xad, 0xbe, 0xef]).int
|
|
27
|
+
# 3735928559
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# 'ZGF0YQ'
|
|
29
|
+
hx.from_text("data").hex
|
|
30
|
+
# '64617461'
|
|
38
31
|
```
|
|
39
32
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
space-separated hex.
|
|
33
|
+
The bare `.hex`, `.text`, and `.bytes` properties are shortcuts for zero-argument
|
|
34
|
+
`to_*` methods. Call the method form when you need options:
|
|
43
35
|
|
|
44
|
-
|
|
36
|
+
```python
|
|
37
|
+
value = hx.from_hex("deadbeef")
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
value.to_hex()
|
|
40
|
+
# 'deadbeef' (same as value.hex)
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
value.to_hex(sep=" ", uppercase=True)
|
|
43
|
+
# 'DE AD BE EF'
|
|
44
|
+
|
|
45
|
+
value.to_bytes()
|
|
51
46
|
# b'\xde\xad\xbe\xef'
|
|
52
47
|
|
|
53
|
-
hx.
|
|
54
|
-
#
|
|
48
|
+
hx.from_hex("68656c6c6f").to_text()
|
|
49
|
+
# 'hello'
|
|
50
|
+
```
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
# b'\xde\xad\xbe\xef'
|
|
52
|
+
## Fast compose
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
Create your own converters by snapping together
|
|
55
|
+
`source format >> optional transforms >> target format`.
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
```python
|
|
58
|
+
to_bytes = hx.Hex() >> hx.Bytes()
|
|
59
|
+
to_bytes("68 69")
|
|
60
|
+
# b'hi'
|
|
64
61
|
|
|
65
|
-
hx.
|
|
66
|
-
|
|
62
|
+
spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
|
|
63
|
+
spaced_hex(b"data")
|
|
64
|
+
# '64 61 74 61'
|
|
67
65
|
|
|
68
|
-
hx.
|
|
69
|
-
|
|
66
|
+
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
|
|
67
|
+
words("12 34 56 78")
|
|
68
|
+
# [4660, 22136]
|
|
70
69
|
|
|
71
|
-
hx.
|
|
70
|
+
pack_to_hex = hx.Struct(">HH") >> hx.Hex()
|
|
71
|
+
pack_to_hex((0x1234, 0x5678))
|
|
72
72
|
# '12345678'
|
|
73
|
-
|
|
74
|
-
hx.from_bits("101", pad=True).hex
|
|
75
|
-
# '05'
|
|
76
73
|
```
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
## Configure when needed
|
|
76
|
+
|
|
77
|
+
Formats work bare by default. Add arguments only for the conversion that needs
|
|
78
|
+
them:
|
|
79
79
|
|
|
80
80
|
```python
|
|
81
|
-
hx.
|
|
81
|
+
hx.convert("de ad be ef", hx.Hex(), hx.Bytes())
|
|
82
82
|
# b'\xde\xad\xbe\xef'
|
|
83
83
|
|
|
84
|
-
hx.
|
|
85
|
-
# '
|
|
84
|
+
hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ", prefix=True, uppercase=True))
|
|
85
|
+
# '0xDE AD BE EF'
|
|
86
|
+
|
|
87
|
+
hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
|
|
88
|
+
# 'cf80'
|
|
86
89
|
|
|
87
|
-
hx.
|
|
88
|
-
# '
|
|
90
|
+
hx.from_int(0x12345678, endian="little").hex
|
|
91
|
+
# '78563412'
|
|
92
|
+
|
|
93
|
+
hx.convert("ZGF0YQ", hx.Base64(padding=False), hx.Text())
|
|
94
|
+
# 'data'
|
|
95
|
+
|
|
96
|
+
hx.from_binary("00010110 10010110", bit_order="lsb").text
|
|
97
|
+
# 'hi'
|
|
89
98
|
```
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
Hex strings may include common separators, so you do not need to manually strip
|
|
101
|
+
spaces, colons, dashes, or `0x` prefixes before converting. `Hex(sep=" ")` is
|
|
102
|
+
how you emit space-separated hex.
|
|
103
|
+
|
|
104
|
+
## Full pipeline guide
|
|
92
105
|
|
|
93
106
|
`hexconv` is built around small format objects. A format can parse directly:
|
|
94
107
|
|
|
@@ -99,8 +112,15 @@ hx.Hex()("de ad be ef").bytes
|
|
|
99
112
|
hx.Text()("data").hex
|
|
100
113
|
# '64617461'
|
|
101
114
|
|
|
102
|
-
hx.Int(
|
|
115
|
+
hx.Int(endian="little")(0x12345678).hex
|
|
103
116
|
# '78563412'
|
|
117
|
+
|
|
118
|
+
pack_words = hx.Struct(">HH")
|
|
119
|
+
pack_words((0x1234, 0x5678)).hex
|
|
120
|
+
# '12345678'
|
|
121
|
+
|
|
122
|
+
hx.Hex()("12345678").to(pack_words)
|
|
123
|
+
# (4660, 22136)
|
|
104
124
|
```
|
|
105
125
|
|
|
106
126
|
Formats compose with `>>`, so reusable converters are normal Python objects:
|
|
@@ -113,12 +133,20 @@ to_text([0x48, 0x65, 0x6C, 0x6C, 0x6F])
|
|
|
113
133
|
to_hex = hx.Text() >> hx.Hex(prefix=True)
|
|
114
134
|
to_hex("data")
|
|
115
135
|
# '0x64617461'
|
|
136
|
+
|
|
137
|
+
pack_to_hex = hx.Struct(">HH") >> hx.Hex()
|
|
138
|
+
pack_to_hex((0x1234, 0x5678))
|
|
139
|
+
# '12345678'
|
|
140
|
+
|
|
141
|
+
unpack_from_hex = hx.Hex() >> hx.Struct(">HH")
|
|
142
|
+
unpack_from_hex("12345678")
|
|
143
|
+
# (4660, 22136)
|
|
116
144
|
```
|
|
117
145
|
|
|
118
146
|
Transforms sit between formats when data needs shaping:
|
|
119
147
|
|
|
120
148
|
```python
|
|
121
|
-
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray(
|
|
149
|
+
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
|
|
122
150
|
words("12345678")
|
|
123
151
|
# [4660, 22136]
|
|
124
152
|
|
|
@@ -145,89 +173,27 @@ conv("12345678")
|
|
|
145
173
|
# ['0x1234', '0x5678']
|
|
146
174
|
```
|
|
147
175
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Use `convert` when you want an explicit source and target in one expression:
|
|
151
|
-
|
|
152
|
-
```python
|
|
153
|
-
hx.convert("de ad be ef", hx.Hex(sep=" "), hx.Bytes())
|
|
154
|
-
# b'\xde\xad\xbe\xef'
|
|
155
|
-
|
|
156
|
-
hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" "))
|
|
157
|
-
# 'de ad be ef'
|
|
158
|
-
|
|
159
|
-
hx.convert("data", hx.Text(), hx.Hex(prefix=True))
|
|
160
|
-
# '0x64617461'
|
|
161
|
-
|
|
162
|
-
hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
|
|
163
|
-
# 'cf80'
|
|
164
|
-
|
|
165
|
-
hx.convert("-_8", hx.Base64(urlsafe=True, padding=False), hx.Hex())
|
|
166
|
-
# 'fbff'
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Hex strings may include common separators, so you do not need to manually strip
|
|
170
|
-
spaces, colons, dashes, or `0x` prefixes before converting.
|
|
171
|
-
|
|
172
|
-
## Common conversion patterns
|
|
173
|
-
|
|
174
|
-
Text, bytes, and hex:
|
|
175
|
-
|
|
176
|
-
```python
|
|
177
|
-
hx.Text()("hello").bytes
|
|
178
|
-
# b'hello'
|
|
179
|
-
|
|
180
|
-
hx.Hex()("68656c6c6f").text
|
|
181
|
-
# 'hello'
|
|
182
|
-
|
|
183
|
-
hx.BytesArray()([0x68, 0x69]).to(hx.Text())
|
|
184
|
-
# 'hi'
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Integer packing:
|
|
176
|
+
More conversion patterns:
|
|
188
177
|
|
|
189
178
|
```python
|
|
190
|
-
hx.
|
|
191
|
-
# '
|
|
179
|
+
hx.BytesArray()([0x4F, 0x4B]).to(hx.Text())
|
|
180
|
+
# 'OK'
|
|
192
181
|
|
|
193
|
-
hx.Int(
|
|
182
|
+
hx.Int(endian="little")(0x1234).hex
|
|
194
183
|
# '3412'
|
|
195
184
|
|
|
196
|
-
(hx.Hex() >> hx.Chunk(2) >> hx.IntArray(width=2))("12345678")
|
|
197
|
-
# [4660, 22136]
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Base encodings:
|
|
201
|
-
|
|
202
|
-
```python
|
|
203
|
-
hx.Text()("data").to(hx.Base64())
|
|
204
|
-
# 'ZGF0YQ=='
|
|
205
|
-
|
|
206
185
|
hx.Text()("data").to(hx.Base32(padding=False))
|
|
207
186
|
# 'MRQXIYI'
|
|
208
187
|
|
|
209
|
-
hx.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
Escaped strings, hexdumps, structs, and bits:
|
|
214
|
-
|
|
215
|
-
```python
|
|
216
|
-
hx.Escaped()(r"\xde\xad").hex
|
|
217
|
-
# 'dead'
|
|
218
|
-
|
|
219
|
-
dump = hx.Hex()("deadbeef").to(hx.Hexdump(width=2))
|
|
220
|
-
hx.Hexdump()(dump).hex
|
|
221
|
-
# 'deadbeef'
|
|
222
|
-
|
|
223
|
-
hx.Struct(">HH")((0x1234, 0x5678)).hex
|
|
224
|
-
# '12345678'
|
|
188
|
+
dump = hx.Text()("data").to(hx.Hexdump(width=2))
|
|
189
|
+
hx.Hexdump()(dump).text
|
|
190
|
+
# 'data'
|
|
225
191
|
|
|
226
192
|
hx.Hex()("12345678").to(hx.Struct(">HH"))
|
|
227
193
|
# (4660, 22136)
|
|
228
194
|
|
|
229
|
-
hx.Bits()(
|
|
230
|
-
# '
|
|
195
|
+
hx.Bits()("0100111101001011").text
|
|
196
|
+
# 'OK'
|
|
231
197
|
```
|
|
232
198
|
|
|
233
199
|
## Why explicit formats?
|
|
@@ -275,10 +241,10 @@ result.reason
|
|
|
275
241
|
`from_auto(..., explain=True)` returns the same explanation object:
|
|
276
242
|
|
|
277
243
|
```python
|
|
278
|
-
result = hx.from_auto("
|
|
244
|
+
result = hx.from_auto("0b0100111101001011", explain=True)
|
|
279
245
|
|
|
280
|
-
result.value.
|
|
281
|
-
# '
|
|
246
|
+
result.value.text
|
|
247
|
+
# 'OK'
|
|
282
248
|
```
|
|
283
249
|
|
|
284
250
|
## Design priorities
|
|
@@ -302,7 +268,7 @@ conv("deadbeef")
|
|
|
302
268
|
# 3735928559
|
|
303
269
|
```
|
|
304
270
|
|
|
305
|
-
Available format specs:
|
|
271
|
+
Available format specs all work in their simplest bare form:
|
|
306
272
|
|
|
307
273
|
- `Bytes()`
|
|
308
274
|
- `BytesArray()`
|
|
@@ -310,8 +276,8 @@ Available format specs:
|
|
|
310
276
|
- `Hex()`
|
|
311
277
|
- `HexArray()`
|
|
312
278
|
- `HexInt()` / `LargeHexNumber()`
|
|
313
|
-
- `Int()`
|
|
314
|
-
- `IntArray()`
|
|
279
|
+
- `Int()` / `DecimalInt()`
|
|
280
|
+
- `IntArray()` / `DecimalIntArray()`
|
|
315
281
|
- `Text()`
|
|
316
282
|
- `Binary()`
|
|
317
283
|
- `Base64()`
|
|
@@ -322,6 +288,20 @@ Available format specs:
|
|
|
322
288
|
- `Hexdump()`
|
|
323
289
|
- `Struct(fmt)`
|
|
324
290
|
- `Bits()`
|
|
291
|
+
- `Auto()`
|
|
292
|
+
|
|
293
|
+
Add options only when you need control over formatting or parsing:
|
|
294
|
+
|
|
295
|
+
- Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
|
|
296
|
+
- Integer packing: `Int(width=4)`, `Int(endian="little")`, `IntArray(width=2)`.
|
|
297
|
+
- Text: `Text(encoding="utf-8", errors="replace")`.
|
|
298
|
+
- Base encodings: `Base64(urlsafe=True, padding=False)`, `Base32(padding=False)`.
|
|
299
|
+
- Binary/bits: `Binary(sep=" ", prefix=True, bit_order="lsb")`, `Bits(pad_side="right")`.
|
|
300
|
+
- Dumps/escapes/structs: `Hexdump(width=8)`, `Escaped(uppercase=True)`, `Struct(">HH")`.
|
|
301
|
+
|
|
302
|
+
Some options only matter in one direction. For example, `Hex(sep=" ")`
|
|
303
|
+
controls hex output formatting, while hex input already accepts common
|
|
304
|
+
separators such as spaces, colons, dashes, underscores, and `0x` prefixes.
|
|
325
305
|
|
|
326
306
|
## Transforms
|
|
327
307
|
|
|
@@ -329,7 +309,7 @@ Transforms are byte-to-byte steps that sit between input and output formats.
|
|
|
329
309
|
|
|
330
310
|
```python
|
|
331
311
|
hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
|
|
332
|
-
hx.Hex() >> hx.Group(2) >> hx.IntArray(
|
|
312
|
+
hx.Hex() >> hx.Group(2) >> hx.IntArray()
|
|
333
313
|
hx.Hex() >> hx.Pad.left(width=8) >> hx.Hex()
|
|
334
314
|
hx.Hex() >> hx.Pad.right(block_size=8, byte=0xFF) >> hx.Hex()
|
|
335
315
|
hx.Hex() >> hx.Reverse() >> hx.Hex()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hexconv"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "Pythonic conversion toolkit for bytes, hex, integers, arrays, binary strings, base64, and text."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexconv
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Pythonic conversion toolkit for bytes, hex, integers, arrays, binary strings, base64, and text.
|
|
5
5
|
Author: hexconv contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -41,83 +41,96 @@ It is built around ergonomic one-liners and composable format objects.
|
|
|
41
41
|
pip install hexconv
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Simple usage
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
Most conversions are one-liners. Use `from_auto` when you want a quick
|
|
47
|
+
best-effort parse, or pick an explicit helper when the input is ambiguous.
|
|
48
48
|
|
|
49
49
|
```python
|
|
50
50
|
import hexconv as hx
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# b'\xde\xad\xbe\xef'
|
|
55
|
-
|
|
56
|
-
spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
|
|
57
|
-
spaced_hex(b"\xde\xad\xbe\xef")
|
|
58
|
-
# 'de ad be ef'
|
|
52
|
+
hx.from_auto("0x64617461").text
|
|
53
|
+
# 'data'
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# [4660, 22136]
|
|
55
|
+
hx.from_bytes_array([0xde, 0xad, 0xbe, 0xef]).int
|
|
56
|
+
# 3735928559
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# 'ZGF0YQ'
|
|
58
|
+
hx.from_text("data").hex
|
|
59
|
+
# '64617461'
|
|
67
60
|
```
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
space-separated hex.
|
|
62
|
+
The bare `.hex`, `.text`, and `.bytes` properties are shortcuts for zero-argument
|
|
63
|
+
`to_*` methods. Call the method form when you need options:
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
```python
|
|
66
|
+
value = hx.from_hex("deadbeef")
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
value.to_hex()
|
|
69
|
+
# 'deadbeef' (same as value.hex)
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
value.to_hex(sep=" ", uppercase=True)
|
|
72
|
+
# 'DE AD BE EF'
|
|
73
|
+
|
|
74
|
+
value.to_bytes()
|
|
80
75
|
# b'\xde\xad\xbe\xef'
|
|
81
76
|
|
|
82
|
-
hx.
|
|
83
|
-
#
|
|
77
|
+
hx.from_hex("68656c6c6f").to_text()
|
|
78
|
+
# 'hello'
|
|
79
|
+
```
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
# b'\xde\xad\xbe\xef'
|
|
81
|
+
## Fast compose
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
Create your own converters by snapping together
|
|
84
|
+
`source format >> optional transforms >> target format`.
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
```python
|
|
87
|
+
to_bytes = hx.Hex() >> hx.Bytes()
|
|
88
|
+
to_bytes("68 69")
|
|
89
|
+
# b'hi'
|
|
93
90
|
|
|
94
|
-
hx.
|
|
95
|
-
|
|
91
|
+
spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
|
|
92
|
+
spaced_hex(b"data")
|
|
93
|
+
# '64 61 74 61'
|
|
96
94
|
|
|
97
|
-
hx.
|
|
98
|
-
|
|
95
|
+
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
|
|
96
|
+
words("12 34 56 78")
|
|
97
|
+
# [4660, 22136]
|
|
99
98
|
|
|
100
|
-
hx.
|
|
99
|
+
pack_to_hex = hx.Struct(">HH") >> hx.Hex()
|
|
100
|
+
pack_to_hex((0x1234, 0x5678))
|
|
101
101
|
# '12345678'
|
|
102
|
-
|
|
103
|
-
hx.from_bits("101", pad=True).hex
|
|
104
|
-
# '05'
|
|
105
102
|
```
|
|
106
103
|
|
|
107
|
-
|
|
104
|
+
## Configure when needed
|
|
105
|
+
|
|
106
|
+
Formats work bare by default. Add arguments only for the conversion that needs
|
|
107
|
+
them:
|
|
108
108
|
|
|
109
109
|
```python
|
|
110
|
-
hx.
|
|
110
|
+
hx.convert("de ad be ef", hx.Hex(), hx.Bytes())
|
|
111
111
|
# b'\xde\xad\xbe\xef'
|
|
112
112
|
|
|
113
|
-
hx.
|
|
114
|
-
# '
|
|
113
|
+
hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ", prefix=True, uppercase=True))
|
|
114
|
+
# '0xDE AD BE EF'
|
|
115
|
+
|
|
116
|
+
hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
|
|
117
|
+
# 'cf80'
|
|
115
118
|
|
|
116
|
-
hx.
|
|
117
|
-
# '
|
|
119
|
+
hx.from_int(0x12345678, endian="little").hex
|
|
120
|
+
# '78563412'
|
|
121
|
+
|
|
122
|
+
hx.convert("ZGF0YQ", hx.Base64(padding=False), hx.Text())
|
|
123
|
+
# 'data'
|
|
124
|
+
|
|
125
|
+
hx.from_binary("00010110 10010110", bit_order="lsb").text
|
|
126
|
+
# 'hi'
|
|
118
127
|
```
|
|
119
128
|
|
|
120
|
-
|
|
129
|
+
Hex strings may include common separators, so you do not need to manually strip
|
|
130
|
+
spaces, colons, dashes, or `0x` prefixes before converting. `Hex(sep=" ")` is
|
|
131
|
+
how you emit space-separated hex.
|
|
132
|
+
|
|
133
|
+
## Full pipeline guide
|
|
121
134
|
|
|
122
135
|
`hexconv` is built around small format objects. A format can parse directly:
|
|
123
136
|
|
|
@@ -128,8 +141,15 @@ hx.Hex()("de ad be ef").bytes
|
|
|
128
141
|
hx.Text()("data").hex
|
|
129
142
|
# '64617461'
|
|
130
143
|
|
|
131
|
-
hx.Int(
|
|
144
|
+
hx.Int(endian="little")(0x12345678).hex
|
|
132
145
|
# '78563412'
|
|
146
|
+
|
|
147
|
+
pack_words = hx.Struct(">HH")
|
|
148
|
+
pack_words((0x1234, 0x5678)).hex
|
|
149
|
+
# '12345678'
|
|
150
|
+
|
|
151
|
+
hx.Hex()("12345678").to(pack_words)
|
|
152
|
+
# (4660, 22136)
|
|
133
153
|
```
|
|
134
154
|
|
|
135
155
|
Formats compose with `>>`, so reusable converters are normal Python objects:
|
|
@@ -142,12 +162,20 @@ to_text([0x48, 0x65, 0x6C, 0x6C, 0x6F])
|
|
|
142
162
|
to_hex = hx.Text() >> hx.Hex(prefix=True)
|
|
143
163
|
to_hex("data")
|
|
144
164
|
# '0x64617461'
|
|
165
|
+
|
|
166
|
+
pack_to_hex = hx.Struct(">HH") >> hx.Hex()
|
|
167
|
+
pack_to_hex((0x1234, 0x5678))
|
|
168
|
+
# '12345678'
|
|
169
|
+
|
|
170
|
+
unpack_from_hex = hx.Hex() >> hx.Struct(">HH")
|
|
171
|
+
unpack_from_hex("12345678")
|
|
172
|
+
# (4660, 22136)
|
|
145
173
|
```
|
|
146
174
|
|
|
147
175
|
Transforms sit between formats when data needs shaping:
|
|
148
176
|
|
|
149
177
|
```python
|
|
150
|
-
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray(
|
|
178
|
+
words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
|
|
151
179
|
words("12345678")
|
|
152
180
|
# [4660, 22136]
|
|
153
181
|
|
|
@@ -174,89 +202,27 @@ conv("12345678")
|
|
|
174
202
|
# ['0x1234', '0x5678']
|
|
175
203
|
```
|
|
176
204
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Use `convert` when you want an explicit source and target in one expression:
|
|
180
|
-
|
|
181
|
-
```python
|
|
182
|
-
hx.convert("de ad be ef", hx.Hex(sep=" "), hx.Bytes())
|
|
183
|
-
# b'\xde\xad\xbe\xef'
|
|
184
|
-
|
|
185
|
-
hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" "))
|
|
186
|
-
# 'de ad be ef'
|
|
187
|
-
|
|
188
|
-
hx.convert("data", hx.Text(), hx.Hex(prefix=True))
|
|
189
|
-
# '0x64617461'
|
|
190
|
-
|
|
191
|
-
hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
|
|
192
|
-
# 'cf80'
|
|
193
|
-
|
|
194
|
-
hx.convert("-_8", hx.Base64(urlsafe=True, padding=False), hx.Hex())
|
|
195
|
-
# 'fbff'
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Hex strings may include common separators, so you do not need to manually strip
|
|
199
|
-
spaces, colons, dashes, or `0x` prefixes before converting.
|
|
200
|
-
|
|
201
|
-
## Common conversion patterns
|
|
202
|
-
|
|
203
|
-
Text, bytes, and hex:
|
|
204
|
-
|
|
205
|
-
```python
|
|
206
|
-
hx.Text()("hello").bytes
|
|
207
|
-
# b'hello'
|
|
208
|
-
|
|
209
|
-
hx.Hex()("68656c6c6f").text
|
|
210
|
-
# 'hello'
|
|
211
|
-
|
|
212
|
-
hx.BytesArray()([0x68, 0x69]).to(hx.Text())
|
|
213
|
-
# 'hi'
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Integer packing:
|
|
205
|
+
More conversion patterns:
|
|
217
206
|
|
|
218
207
|
```python
|
|
219
|
-
hx.
|
|
220
|
-
# '
|
|
208
|
+
hx.BytesArray()([0x4F, 0x4B]).to(hx.Text())
|
|
209
|
+
# 'OK'
|
|
221
210
|
|
|
222
|
-
hx.Int(
|
|
211
|
+
hx.Int(endian="little")(0x1234).hex
|
|
223
212
|
# '3412'
|
|
224
213
|
|
|
225
|
-
(hx.Hex() >> hx.Chunk(2) >> hx.IntArray(width=2))("12345678")
|
|
226
|
-
# [4660, 22136]
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Base encodings:
|
|
230
|
-
|
|
231
|
-
```python
|
|
232
|
-
hx.Text()("data").to(hx.Base64())
|
|
233
|
-
# 'ZGF0YQ=='
|
|
234
|
-
|
|
235
214
|
hx.Text()("data").to(hx.Base32(padding=False))
|
|
236
215
|
# 'MRQXIYI'
|
|
237
216
|
|
|
238
|
-
hx.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Escaped strings, hexdumps, structs, and bits:
|
|
243
|
-
|
|
244
|
-
```python
|
|
245
|
-
hx.Escaped()(r"\xde\xad").hex
|
|
246
|
-
# 'dead'
|
|
247
|
-
|
|
248
|
-
dump = hx.Hex()("deadbeef").to(hx.Hexdump(width=2))
|
|
249
|
-
hx.Hexdump()(dump).hex
|
|
250
|
-
# 'deadbeef'
|
|
251
|
-
|
|
252
|
-
hx.Struct(">HH")((0x1234, 0x5678)).hex
|
|
253
|
-
# '12345678'
|
|
217
|
+
dump = hx.Text()("data").to(hx.Hexdump(width=2))
|
|
218
|
+
hx.Hexdump()(dump).text
|
|
219
|
+
# 'data'
|
|
254
220
|
|
|
255
221
|
hx.Hex()("12345678").to(hx.Struct(">HH"))
|
|
256
222
|
# (4660, 22136)
|
|
257
223
|
|
|
258
|
-
hx.Bits()(
|
|
259
|
-
# '
|
|
224
|
+
hx.Bits()("0100111101001011").text
|
|
225
|
+
# 'OK'
|
|
260
226
|
```
|
|
261
227
|
|
|
262
228
|
## Why explicit formats?
|
|
@@ -304,10 +270,10 @@ result.reason
|
|
|
304
270
|
`from_auto(..., explain=True)` returns the same explanation object:
|
|
305
271
|
|
|
306
272
|
```python
|
|
307
|
-
result = hx.from_auto("
|
|
273
|
+
result = hx.from_auto("0b0100111101001011", explain=True)
|
|
308
274
|
|
|
309
|
-
result.value.
|
|
310
|
-
# '
|
|
275
|
+
result.value.text
|
|
276
|
+
# 'OK'
|
|
311
277
|
```
|
|
312
278
|
|
|
313
279
|
## Design priorities
|
|
@@ -331,7 +297,7 @@ conv("deadbeef")
|
|
|
331
297
|
# 3735928559
|
|
332
298
|
```
|
|
333
299
|
|
|
334
|
-
Available format specs:
|
|
300
|
+
Available format specs all work in their simplest bare form:
|
|
335
301
|
|
|
336
302
|
- `Bytes()`
|
|
337
303
|
- `BytesArray()`
|
|
@@ -339,8 +305,8 @@ Available format specs:
|
|
|
339
305
|
- `Hex()`
|
|
340
306
|
- `HexArray()`
|
|
341
307
|
- `HexInt()` / `LargeHexNumber()`
|
|
342
|
-
- `Int()`
|
|
343
|
-
- `IntArray()`
|
|
308
|
+
- `Int()` / `DecimalInt()`
|
|
309
|
+
- `IntArray()` / `DecimalIntArray()`
|
|
344
310
|
- `Text()`
|
|
345
311
|
- `Binary()`
|
|
346
312
|
- `Base64()`
|
|
@@ -351,6 +317,20 @@ Available format specs:
|
|
|
351
317
|
- `Hexdump()`
|
|
352
318
|
- `Struct(fmt)`
|
|
353
319
|
- `Bits()`
|
|
320
|
+
- `Auto()`
|
|
321
|
+
|
|
322
|
+
Add options only when you need control over formatting or parsing:
|
|
323
|
+
|
|
324
|
+
- Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
|
|
325
|
+
- Integer packing: `Int(width=4)`, `Int(endian="little")`, `IntArray(width=2)`.
|
|
326
|
+
- Text: `Text(encoding="utf-8", errors="replace")`.
|
|
327
|
+
- Base encodings: `Base64(urlsafe=True, padding=False)`, `Base32(padding=False)`.
|
|
328
|
+
- Binary/bits: `Binary(sep=" ", prefix=True, bit_order="lsb")`, `Bits(pad_side="right")`.
|
|
329
|
+
- Dumps/escapes/structs: `Hexdump(width=8)`, `Escaped(uppercase=True)`, `Struct(">HH")`.
|
|
330
|
+
|
|
331
|
+
Some options only matter in one direction. For example, `Hex(sep=" ")`
|
|
332
|
+
controls hex output formatting, while hex input already accepts common
|
|
333
|
+
separators such as spaces, colons, dashes, underscores, and `0x` prefixes.
|
|
354
334
|
|
|
355
335
|
## Transforms
|
|
356
336
|
|
|
@@ -358,7 +338,7 @@ Transforms are byte-to-byte steps that sit between input and output formats.
|
|
|
358
338
|
|
|
359
339
|
```python
|
|
360
340
|
hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
|
|
361
|
-
hx.Hex() >> hx.Group(2) >> hx.IntArray(
|
|
341
|
+
hx.Hex() >> hx.Group(2) >> hx.IntArray()
|
|
362
342
|
hx.Hex() >> hx.Pad.left(width=8) >> hx.Hex()
|
|
363
343
|
hx.Hex() >> hx.Pad.right(block_size=8, byte=0xFF) >> hx.Hex()
|
|
364
344
|
hx.Hex() >> hx.Reverse() >> hx.Hex()
|
|
@@ -45,11 +45,56 @@ def test_converter_class_matches_proposed_usage():
|
|
|
45
45
|
|
|
46
46
|
def test_convert_one_shot():
|
|
47
47
|
assert hx.convert("data", from_=hx.Text, to=hx.HexString) == "64617461"
|
|
48
|
-
assert hx.convert("de ad be ef", hx.Hex(
|
|
48
|
+
assert hx.convert("de ad be ef", hx.Hex(), hx.Bytes()) == b"\xde\xad\xbe\xef"
|
|
49
49
|
assert hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ")) == "de ad be ef"
|
|
50
50
|
assert hx.convert("data", hx.Text(), hx.Hex(prefix=True)) == "0x64617461"
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def test_documented_format_options_quick_tour():
|
|
54
|
+
assert hx.from_hex("de ad:be-ef").bytes == b"\xde\xad\xbe\xef"
|
|
55
|
+
assert hx.from_bytes(b"\xde\xad\xbe\xef").to(hx.Hex(sep=":", uppercase=True)) == "DE:AD:BE:EF"
|
|
56
|
+
assert hx.from_bytes_array([0xDE, 0xAD, 0xBE, 0xEF]).to(
|
|
57
|
+
hx.HexArray(width=2, prefix=True, uppercase=True)
|
|
58
|
+
) == ["0xDEAD", "0xBEEF"]
|
|
59
|
+
assert hx.from_text("π", encoding="utf-8").to(hx.Hex(prefix=True)) == "0xcf80"
|
|
60
|
+
assert hx.from_int(0x12345678, endian="little").hex == "78563412"
|
|
61
|
+
assert hx.from_int_array([0x1234, 0x5678], width=2).to(hx.HexArray(width=2)) == [
|
|
62
|
+
"1234",
|
|
63
|
+
"5678",
|
|
64
|
+
]
|
|
65
|
+
assert hx.from_binary("01100100 01100001 01110100 01100001").text == "data"
|
|
66
|
+
assert hx.from_base64("-_8", urlsafe=True, padding=False).hex == "fbff"
|
|
67
|
+
assert hx.from_base32("MRQXIYI", padding=False).text == "data"
|
|
68
|
+
assert hx.from_escaped(r"\xDE\xAD").hex == "dead"
|
|
69
|
+
assert hx.from_struct((0x1234, 0x5678), fmt=">HH").hex == "12345678"
|
|
70
|
+
assert hx.from_bits("101", pad_side="right").hex == "a0"
|
|
71
|
+
|
|
72
|
+
assert hx.convert("de ad be ef", hx.Hex(), hx.Bytes()) == b"\xde\xad\xbe\xef"
|
|
73
|
+
assert (
|
|
74
|
+
hx.convert(
|
|
75
|
+
b"\xde\xad\xbe\xef",
|
|
76
|
+
hx.Bytes(),
|
|
77
|
+
hx.Hex(sep=" ", prefix=True, uppercase=True),
|
|
78
|
+
)
|
|
79
|
+
== "0xDE AD BE EF"
|
|
80
|
+
)
|
|
81
|
+
assert hx.convert(["0xDEAD", "0xBEEF"], hx.HexArray(width=2), hx.IntArray(width=2)) == [
|
|
82
|
+
57005,
|
|
83
|
+
48879,
|
|
84
|
+
]
|
|
85
|
+
assert hx.convert("78563412", hx.Hex(), hx.Int(endian="little")) == 305419896
|
|
86
|
+
assert hx.convert("0b10100000", hx.Binary(bit_order="lsb"), hx.Hex()) == "05"
|
|
87
|
+
assert hx.from_text("data").to(hx.Base64(urlsafe=True, padding=False)) == "ZGF0YQ"
|
|
88
|
+
assert hx.from_text("data").to(hx.Base85()) == "WMOn+"
|
|
89
|
+
assert hx.from_text("data").to(hx.Ascii85(adobe=True)) == "<~A79Rg~>"
|
|
90
|
+
assert hx.from_hex("deadbeef").to(hx.Hexdump(width=2, offset=False, ascii=False)) == "de ad\nbe ef"
|
|
91
|
+
assert hx.from_hex("dead").to(hx.Escaped(uppercase=True)) == r"\xDE\xAD"
|
|
92
|
+
assert hx.from_hex("0506").to(hx.Binary(sep=" ", prefix=True, bit_order="lsb")) == (
|
|
93
|
+
"0b10100000 0b01100000"
|
|
94
|
+
)
|
|
95
|
+
assert hx.from_hex("05").to(hx.Bits(bit_order="lsb")) == [1, 0, 1, 0, 0, 0, 0, 0]
|
|
96
|
+
|
|
97
|
+
|
|
53
98
|
def test_convert_rejects_mixed_positional_and_keyword_formats():
|
|
54
99
|
with pytest.raises(hx.HexConvError, match="from_"):
|
|
55
100
|
hx.convert("data", hx.Text(), from_=hx.Text(), to=hx.Hex())
|
|
@@ -115,10 +160,10 @@ def test_constructable_hex_and_text_specs():
|
|
|
115
160
|
|
|
116
161
|
|
|
117
162
|
def test_pipeline_builder_and_shift_composition():
|
|
118
|
-
conv = hx.pipeline().from_(hx.Hex(
|
|
163
|
+
conv = hx.pipeline().from_(hx.Hex()).chunk(2).to(hx.IntArray())
|
|
119
164
|
assert conv("12 34 56 78") == [0x1234, 0x5678]
|
|
120
165
|
|
|
121
|
-
shift_conv = hx.Hex(
|
|
166
|
+
shift_conv = hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
|
|
122
167
|
assert shift_conv("12 34 56 78") == ["1234", "5678"]
|
|
123
168
|
|
|
124
169
|
|
|
@@ -161,6 +206,9 @@ def test_struct_format_pack_and_unpack():
|
|
|
161
206
|
assert packed.hex == "12345678"
|
|
162
207
|
assert packed.to(hx.Struct(">HH")) == (0x1234, 0x5678)
|
|
163
208
|
assert hx.convert((0x1234, 0x5678), from_=hx.Struct(">HH"), to=hx.Hex()) == "12345678"
|
|
209
|
+
assert hx.Struct(">HH")((0x1234, 0x5678)).hex == "12345678"
|
|
210
|
+
assert (hx.Struct(">HH") >> hx.Hex())((0x1234, 0x5678)) == "12345678"
|
|
211
|
+
assert (hx.Hex() >> hx.Struct(">HH"))("12345678") == (0x1234, 0x5678)
|
|
164
212
|
|
|
165
213
|
|
|
166
214
|
def test_bit_array_and_lsb_bit_order():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|