hexconv 0.2.4__tar.gz → 0.2.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexconv
3
- Version: 0.2.4
3
+ Version: 0.2.5
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,77 @@ It is built around ergonomic one-liners and composable format objects.
41
41
  pip install hexconv
42
42
  ```
43
43
 
44
- ## Fast compose
44
+ ## Simple usage
45
45
 
46
- The intended interface is small pieces snapped together:
47
- `source format >> optional transforms >> target format`.
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
+ hx.from_auto("0x64617461").text
53
+ # 'data'
54
+
55
+ hx.from_bytes_array([0xde, 0xad, 0xbe, 0xef]).int
56
+ # 3735928559
57
+
58
+ hx.from_text("data").hex
59
+ # '64617461'
60
+ ```
61
+
62
+ ## Fast compose
63
+
64
+ Create your own converters by snapping together
65
+ `source format >> optional transforms >> target format`.
66
+
67
+ ```python
52
68
  to_bytes = hx.Hex() >> hx.Bytes()
53
- to_bytes("de ad be ef")
54
- # b'\xde\xad\xbe\xef'
69
+ to_bytes("68 69")
70
+ # b'hi'
55
71
 
56
72
  spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
57
- spaced_hex(b"\xde\xad\xbe\xef")
58
- # 'de ad be ef'
73
+ spaced_hex(b"data")
74
+ # '64 61 74 61'
59
75
 
60
- words = hx.Hex(sep=" ") >> hx.Chunk(2) >> hx.IntArray(width=2)
76
+ words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
61
77
  words("12 34 56 78")
62
78
  # [4660, 22136]
63
79
 
64
- text_to_urlsafe = hx.Text() >> hx.Base64(urlsafe=True, padding=False)
65
- text_to_urlsafe("data")
66
- # 'ZGF0YQ'
80
+ pack_to_hex = hx.Struct(">HH") >> hx.Hex()
81
+ pack_to_hex((0x1234, 0x5678))
82
+ # '12345678'
67
83
  ```
68
84
 
69
- Hex input accepts common separators, so spaced or copied hex usually does not
70
- need manual cleanup before conversion. `Hex(sep=" ")` is also how you emit
71
- space-separated hex.
85
+ ## Configure when needed
72
86
 
73
- ## Quick tour
74
-
75
- Use direct helpers when you want one obvious conversion across many
76
- representations:
87
+ Formats work bare by default. Add arguments only for the conversion that needs
88
+ them:
77
89
 
78
90
  ```python
79
- hx.from_hex("de ad be ef").bytes
91
+ hx.convert("de ad be ef", hx.Hex(), hx.Bytes())
80
92
  # b'\xde\xad\xbe\xef'
81
93
 
82
- hx.from_text("data").to_hex_array(prefix=True)
83
- # ['0x64', '0x61', '0x74', '0x61']
84
-
85
- hx.from_int(0xdeadbeef).bytes
86
- # b'\xde\xad\xbe\xef'
94
+ hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ", prefix=True, uppercase=True))
95
+ # '0xDE AD BE EF'
87
96
 
88
- hx.from_int_array([0x1234, 0x5678], width=2).to_hex_array(width=2)
89
- # ['1234', '5678']
97
+ hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
98
+ # 'cf80'
90
99
 
91
- hx.from_binary("01100100 01100001 01110100 01100001").text
92
- # 'data'
100
+ hx.from_int(0x12345678, endian="little").hex
101
+ # '78563412'
93
102
 
94
- hx.from_base64("ZGF0YQ==").text
103
+ hx.convert("ZGF0YQ", hx.Base64(padding=False), hx.Text())
95
104
  # 'data'
96
105
 
97
- hx.from_escaped(r"\xde\xad").hex
98
- # 'dead'
99
-
100
- hx.from_struct((0x1234, 0x5678), fmt=">HH").hex
101
- # '12345678'
102
-
103
- hx.from_bits("101", pad=True).hex
104
- # '05'
106
+ hx.from_binary("00010110 10010110", bit_order="lsb").text
107
+ # 'hi'
105
108
  ```
106
109
 
107
- Use `from_auto` when you want a quick best-effort parse:
108
-
109
- ```python
110
- hx.from_auto("0xdeadbeef").bytes
111
- # b'\xde\xad\xbe\xef'
112
-
113
- hx.from_auto(b"\xde\xad").hex
114
- # 'dead'
115
-
116
- hx.from_auto([0xde, 0xad, 0xbe, 0xef]).to(hx.Hex(prefix=True))
117
- # '0xdeadbeef'
118
- ```
110
+ Hex strings may include common separators, so you do not need to manually strip
111
+ spaces, colons, dashes, or `0x` prefixes before converting. `Hex(sep=" ")` is
112
+ how you emit space-separated hex.
119
113
 
120
- ## Composable interface
114
+ ## Full pipeline guide
121
115
 
122
116
  `hexconv` is built around small format objects. A format can parse directly:
123
117
 
@@ -128,8 +122,15 @@ hx.Hex()("de ad be ef").bytes
128
122
  hx.Text()("data").hex
129
123
  # '64617461'
130
124
 
131
- hx.Int(width=4, endian="little")(0x12345678).hex
125
+ hx.Int(endian="little")(0x12345678).hex
132
126
  # '78563412'
127
+
128
+ pack_words = hx.Struct(">HH")
129
+ pack_words((0x1234, 0x5678)).hex
130
+ # '12345678'
131
+
132
+ hx.Hex()("12345678").to(pack_words)
133
+ # (4660, 22136)
133
134
  ```
134
135
 
135
136
  Formats compose with `>>`, so reusable converters are normal Python objects:
@@ -142,12 +143,20 @@ to_text([0x48, 0x65, 0x6C, 0x6C, 0x6F])
142
143
  to_hex = hx.Text() >> hx.Hex(prefix=True)
143
144
  to_hex("data")
144
145
  # '0x64617461'
146
+
147
+ pack_to_hex = hx.Struct(">HH") >> hx.Hex()
148
+ pack_to_hex((0x1234, 0x5678))
149
+ # '12345678'
150
+
151
+ unpack_from_hex = hx.Hex() >> hx.Struct(">HH")
152
+ unpack_from_hex("12345678")
153
+ # (4660, 22136)
145
154
  ```
146
155
 
147
156
  Transforms sit between formats when data needs shaping:
148
157
 
149
158
  ```python
150
- words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray(width=2)
159
+ words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
151
160
  words("12345678")
152
161
  # [4660, 22136]
153
162
 
@@ -174,33 +183,7 @@ conv("12345678")
174
183
  # ['0x1234', '0x5678']
175
184
  ```
176
185
 
177
- ## Configurable one-shot conversions
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:
186
+ More conversion patterns:
204
187
 
205
188
  ```python
206
189
  hx.Text()("hello").bytes
@@ -209,54 +192,24 @@ hx.Text()("hello").bytes
209
192
  hx.Hex()("68656c6c6f").text
210
193
  # 'hello'
211
194
 
212
- hx.BytesArray()([0x68, 0x69]).to(hx.Text())
213
- # 'hi'
214
- ```
215
-
216
- Integer packing:
195
+ hx.BytesArray()([0x4F, 0x4B]).to(hx.Text())
196
+ # 'OK'
217
197
 
218
- ```python
219
- hx.Int(width=2)(0x1234).hex
220
- # '1234'
221
-
222
- hx.Int(width=2, endian="little")(0x1234).hex
198
+ hx.Int(endian="little")(0x1234).hex
223
199
  # '3412'
224
200
 
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
201
  hx.Text()("data").to(hx.Base32(padding=False))
236
202
  # 'MRQXIYI'
237
203
 
238
- hx.Base64(urlsafe=True, padding=False)("-_8").hex
239
- # 'fbff'
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'
204
+ dump = hx.Text()("data").to(hx.Hexdump(width=2))
205
+ hx.Hexdump()(dump).text
206
+ # 'data'
254
207
 
255
208
  hx.Hex()("12345678").to(hx.Struct(">HH"))
256
209
  # (4660, 22136)
257
210
 
258
- hx.Bits()([1, 0, 1]).hex
259
- # '05'
211
+ hx.Bits()("0100111101001011").text
212
+ # 'OK'
260
213
  ```
261
214
 
262
215
  ## Why explicit formats?
@@ -304,10 +257,10 @@ result.reason
304
257
  `from_auto(..., explain=True)` returns the same explanation object:
305
258
 
306
259
  ```python
307
- result = hx.from_auto("0b101", explain=True)
260
+ result = hx.from_auto("0b0100111101001011", explain=True)
308
261
 
309
- result.value.hex
310
- # '05'
262
+ result.value.text
263
+ # 'OK'
311
264
  ```
312
265
 
313
266
  ## Design priorities
@@ -331,7 +284,7 @@ conv("deadbeef")
331
284
  # 3735928559
332
285
  ```
333
286
 
334
- Available format specs:
287
+ Available format specs all work in their simplest bare form:
335
288
 
336
289
  - `Bytes()`
337
290
  - `BytesArray()`
@@ -339,8 +292,8 @@ Available format specs:
339
292
  - `Hex()`
340
293
  - `HexArray()`
341
294
  - `HexInt()` / `LargeHexNumber()`
342
- - `Int()`
343
- - `IntArray()`
295
+ - `Int()` / `DecimalInt()`
296
+ - `IntArray()` / `DecimalIntArray()`
344
297
  - `Text()`
345
298
  - `Binary()`
346
299
  - `Base64()`
@@ -351,6 +304,20 @@ Available format specs:
351
304
  - `Hexdump()`
352
305
  - `Struct(fmt)`
353
306
  - `Bits()`
307
+ - `Auto()`
308
+
309
+ Add options only when you need control over formatting or parsing:
310
+
311
+ - Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
312
+ - Integer packing: `Int(width=4)`, `Int(endian="little")`, `IntArray(width=2)`.
313
+ - Text: `Text(encoding="utf-8", errors="replace")`.
314
+ - Base encodings: `Base64(urlsafe=True, padding=False)`, `Base32(padding=False)`.
315
+ - Binary/bits: `Binary(sep=" ", prefix=True, bit_order="lsb")`, `Bits(pad_side="right")`.
316
+ - Dumps/escapes/structs: `Hexdump(width=8)`, `Escaped(uppercase=True)`, `Struct(">HH")`.
317
+
318
+ Some options only matter in one direction. For example, `Hex(sep=" ")`
319
+ controls hex output formatting, while hex input already accepts common
320
+ separators such as spaces, colons, dashes, underscores, and `0x` prefixes.
354
321
 
355
322
  ## Transforms
356
323
 
@@ -358,7 +325,7 @@ Transforms are byte-to-byte steps that sit between input and output formats.
358
325
 
359
326
  ```python
360
327
  hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
361
- hx.Hex() >> hx.Group(2) >> hx.IntArray(width=2)
328
+ hx.Hex() >> hx.Group(2) >> hx.IntArray()
362
329
  hx.Hex() >> hx.Pad.left(width=8) >> hx.Hex()
363
330
  hx.Hex() >> hx.Pad.right(block_size=8, byte=0xFF) >> hx.Hex()
364
331
  hx.Hex() >> hx.Reverse() >> hx.Hex()
@@ -12,83 +12,77 @@ It is built around ergonomic one-liners and composable format objects.
12
12
  pip install hexconv
13
13
  ```
14
14
 
15
- ## Fast compose
15
+ ## Simple usage
16
16
 
17
- The intended interface is small pieces snapped together:
18
- `source format >> optional transforms >> target format`.
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
+ hx.from_auto("0x64617461").text
24
+ # 'data'
25
+
26
+ hx.from_bytes_array([0xde, 0xad, 0xbe, 0xef]).int
27
+ # 3735928559
28
+
29
+ hx.from_text("data").hex
30
+ # '64617461'
31
+ ```
32
+
33
+ ## Fast compose
34
+
35
+ Create your own converters by snapping together
36
+ `source format >> optional transforms >> target format`.
37
+
38
+ ```python
23
39
  to_bytes = hx.Hex() >> hx.Bytes()
24
- to_bytes("de ad be ef")
25
- # b'\xde\xad\xbe\xef'
40
+ to_bytes("68 69")
41
+ # b'hi'
26
42
 
27
43
  spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
28
- spaced_hex(b"\xde\xad\xbe\xef")
29
- # 'de ad be ef'
44
+ spaced_hex(b"data")
45
+ # '64 61 74 61'
30
46
 
31
- words = hx.Hex(sep=" ") >> hx.Chunk(2) >> hx.IntArray(width=2)
47
+ words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
32
48
  words("12 34 56 78")
33
49
  # [4660, 22136]
34
50
 
35
- text_to_urlsafe = hx.Text() >> hx.Base64(urlsafe=True, padding=False)
36
- text_to_urlsafe("data")
37
- # 'ZGF0YQ'
51
+ pack_to_hex = hx.Struct(">HH") >> hx.Hex()
52
+ pack_to_hex((0x1234, 0x5678))
53
+ # '12345678'
38
54
  ```
39
55
 
40
- Hex input accepts common separators, so spaced or copied hex usually does not
41
- need manual cleanup before conversion. `Hex(sep=" ")` is also how you emit
42
- space-separated hex.
56
+ ## Configure when needed
43
57
 
44
- ## Quick tour
45
-
46
- Use direct helpers when you want one obvious conversion across many
47
- representations:
58
+ Formats work bare by default. Add arguments only for the conversion that needs
59
+ them:
48
60
 
49
61
  ```python
50
- hx.from_hex("de ad be ef").bytes
62
+ hx.convert("de ad be ef", hx.Hex(), hx.Bytes())
51
63
  # b'\xde\xad\xbe\xef'
52
64
 
53
- hx.from_text("data").to_hex_array(prefix=True)
54
- # ['0x64', '0x61', '0x74', '0x61']
55
-
56
- hx.from_int(0xdeadbeef).bytes
57
- # b'\xde\xad\xbe\xef'
65
+ hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ", prefix=True, uppercase=True))
66
+ # '0xDE AD BE EF'
58
67
 
59
- hx.from_int_array([0x1234, 0x5678], width=2).to_hex_array(width=2)
60
- # ['1234', '5678']
68
+ hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
69
+ # 'cf80'
61
70
 
62
- hx.from_binary("01100100 01100001 01110100 01100001").text
63
- # 'data'
71
+ hx.from_int(0x12345678, endian="little").hex
72
+ # '78563412'
64
73
 
65
- hx.from_base64("ZGF0YQ==").text
74
+ hx.convert("ZGF0YQ", hx.Base64(padding=False), hx.Text())
66
75
  # 'data'
67
76
 
68
- hx.from_escaped(r"\xde\xad").hex
69
- # 'dead'
70
-
71
- hx.from_struct((0x1234, 0x5678), fmt=">HH").hex
72
- # '12345678'
73
-
74
- hx.from_bits("101", pad=True).hex
75
- # '05'
77
+ hx.from_binary("00010110 10010110", bit_order="lsb").text
78
+ # 'hi'
76
79
  ```
77
80
 
78
- Use `from_auto` when you want a quick best-effort parse:
79
-
80
- ```python
81
- hx.from_auto("0xdeadbeef").bytes
82
- # b'\xde\xad\xbe\xef'
83
-
84
- hx.from_auto(b"\xde\xad").hex
85
- # 'dead'
86
-
87
- hx.from_auto([0xde, 0xad, 0xbe, 0xef]).to(hx.Hex(prefix=True))
88
- # '0xdeadbeef'
89
- ```
81
+ Hex strings may include common separators, so you do not need to manually strip
82
+ spaces, colons, dashes, or `0x` prefixes before converting. `Hex(sep=" ")` is
83
+ how you emit space-separated hex.
90
84
 
91
- ## Composable interface
85
+ ## Full pipeline guide
92
86
 
93
87
  `hexconv` is built around small format objects. A format can parse directly:
94
88
 
@@ -99,8 +93,15 @@ hx.Hex()("de ad be ef").bytes
99
93
  hx.Text()("data").hex
100
94
  # '64617461'
101
95
 
102
- hx.Int(width=4, endian="little")(0x12345678).hex
96
+ hx.Int(endian="little")(0x12345678).hex
103
97
  # '78563412'
98
+
99
+ pack_words = hx.Struct(">HH")
100
+ pack_words((0x1234, 0x5678)).hex
101
+ # '12345678'
102
+
103
+ hx.Hex()("12345678").to(pack_words)
104
+ # (4660, 22136)
104
105
  ```
105
106
 
106
107
  Formats compose with `>>`, so reusable converters are normal Python objects:
@@ -113,12 +114,20 @@ to_text([0x48, 0x65, 0x6C, 0x6C, 0x6F])
113
114
  to_hex = hx.Text() >> hx.Hex(prefix=True)
114
115
  to_hex("data")
115
116
  # '0x64617461'
117
+
118
+ pack_to_hex = hx.Struct(">HH") >> hx.Hex()
119
+ pack_to_hex((0x1234, 0x5678))
120
+ # '12345678'
121
+
122
+ unpack_from_hex = hx.Hex() >> hx.Struct(">HH")
123
+ unpack_from_hex("12345678")
124
+ # (4660, 22136)
116
125
  ```
117
126
 
118
127
  Transforms sit between formats when data needs shaping:
119
128
 
120
129
  ```python
121
- words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray(width=2)
130
+ words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
122
131
  words("12345678")
123
132
  # [4660, 22136]
124
133
 
@@ -145,33 +154,7 @@ conv("12345678")
145
154
  # ['0x1234', '0x5678']
146
155
  ```
147
156
 
148
- ## Configurable one-shot conversions
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:
157
+ More conversion patterns:
175
158
 
176
159
  ```python
177
160
  hx.Text()("hello").bytes
@@ -180,54 +163,24 @@ hx.Text()("hello").bytes
180
163
  hx.Hex()("68656c6c6f").text
181
164
  # 'hello'
182
165
 
183
- hx.BytesArray()([0x68, 0x69]).to(hx.Text())
184
- # 'hi'
185
- ```
186
-
187
- Integer packing:
166
+ hx.BytesArray()([0x4F, 0x4B]).to(hx.Text())
167
+ # 'OK'
188
168
 
189
- ```python
190
- hx.Int(width=2)(0x1234).hex
191
- # '1234'
192
-
193
- hx.Int(width=2, endian="little")(0x1234).hex
169
+ hx.Int(endian="little")(0x1234).hex
194
170
  # '3412'
195
171
 
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
172
  hx.Text()("data").to(hx.Base32(padding=False))
207
173
  # 'MRQXIYI'
208
174
 
209
- hx.Base64(urlsafe=True, padding=False)("-_8").hex
210
- # 'fbff'
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'
175
+ dump = hx.Text()("data").to(hx.Hexdump(width=2))
176
+ hx.Hexdump()(dump).text
177
+ # 'data'
225
178
 
226
179
  hx.Hex()("12345678").to(hx.Struct(">HH"))
227
180
  # (4660, 22136)
228
181
 
229
- hx.Bits()([1, 0, 1]).hex
230
- # '05'
182
+ hx.Bits()("0100111101001011").text
183
+ # 'OK'
231
184
  ```
232
185
 
233
186
  ## Why explicit formats?
@@ -275,10 +228,10 @@ result.reason
275
228
  `from_auto(..., explain=True)` returns the same explanation object:
276
229
 
277
230
  ```python
278
- result = hx.from_auto("0b101", explain=True)
231
+ result = hx.from_auto("0b0100111101001011", explain=True)
279
232
 
280
- result.value.hex
281
- # '05'
233
+ result.value.text
234
+ # 'OK'
282
235
  ```
283
236
 
284
237
  ## Design priorities
@@ -302,7 +255,7 @@ conv("deadbeef")
302
255
  # 3735928559
303
256
  ```
304
257
 
305
- Available format specs:
258
+ Available format specs all work in their simplest bare form:
306
259
 
307
260
  - `Bytes()`
308
261
  - `BytesArray()`
@@ -310,8 +263,8 @@ Available format specs:
310
263
  - `Hex()`
311
264
  - `HexArray()`
312
265
  - `HexInt()` / `LargeHexNumber()`
313
- - `Int()`
314
- - `IntArray()`
266
+ - `Int()` / `DecimalInt()`
267
+ - `IntArray()` / `DecimalIntArray()`
315
268
  - `Text()`
316
269
  - `Binary()`
317
270
  - `Base64()`
@@ -322,6 +275,20 @@ Available format specs:
322
275
  - `Hexdump()`
323
276
  - `Struct(fmt)`
324
277
  - `Bits()`
278
+ - `Auto()`
279
+
280
+ Add options only when you need control over formatting or parsing:
281
+
282
+ - Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
283
+ - Integer packing: `Int(width=4)`, `Int(endian="little")`, `IntArray(width=2)`.
284
+ - Text: `Text(encoding="utf-8", errors="replace")`.
285
+ - Base encodings: `Base64(urlsafe=True, padding=False)`, `Base32(padding=False)`.
286
+ - Binary/bits: `Binary(sep=" ", prefix=True, bit_order="lsb")`, `Bits(pad_side="right")`.
287
+ - Dumps/escapes/structs: `Hexdump(width=8)`, `Escaped(uppercase=True)`, `Struct(">HH")`.
288
+
289
+ Some options only matter in one direction. For example, `Hex(sep=" ")`
290
+ controls hex output formatting, while hex input already accepts common
291
+ separators such as spaces, colons, dashes, underscores, and `0x` prefixes.
325
292
 
326
293
  ## Transforms
327
294
 
@@ -329,7 +296,7 @@ Transforms are byte-to-byte steps that sit between input and output formats.
329
296
 
330
297
  ```python
331
298
  hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
332
- hx.Hex() >> hx.Group(2) >> hx.IntArray(width=2)
299
+ hx.Hex() >> hx.Group(2) >> hx.IntArray()
333
300
  hx.Hex() >> hx.Pad.left(width=8) >> hx.Hex()
334
301
  hx.Hex() >> hx.Pad.right(block_size=8, byte=0xFF) >> hx.Hex()
335
302
  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.4"
7
+ version = "0.2.5"
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.4
3
+ Version: 0.2.5
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,77 @@ It is built around ergonomic one-liners and composable format objects.
41
41
  pip install hexconv
42
42
  ```
43
43
 
44
- ## Fast compose
44
+ ## Simple usage
45
45
 
46
- The intended interface is small pieces snapped together:
47
- `source format >> optional transforms >> target format`.
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
+ hx.from_auto("0x64617461").text
53
+ # 'data'
54
+
55
+ hx.from_bytes_array([0xde, 0xad, 0xbe, 0xef]).int
56
+ # 3735928559
57
+
58
+ hx.from_text("data").hex
59
+ # '64617461'
60
+ ```
61
+
62
+ ## Fast compose
63
+
64
+ Create your own converters by snapping together
65
+ `source format >> optional transforms >> target format`.
66
+
67
+ ```python
52
68
  to_bytes = hx.Hex() >> hx.Bytes()
53
- to_bytes("de ad be ef")
54
- # b'\xde\xad\xbe\xef'
69
+ to_bytes("68 69")
70
+ # b'hi'
55
71
 
56
72
  spaced_hex = hx.Bytes() >> hx.Hex(sep=" ")
57
- spaced_hex(b"\xde\xad\xbe\xef")
58
- # 'de ad be ef'
73
+ spaced_hex(b"data")
74
+ # '64 61 74 61'
59
75
 
60
- words = hx.Hex(sep=" ") >> hx.Chunk(2) >> hx.IntArray(width=2)
76
+ words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
61
77
  words("12 34 56 78")
62
78
  # [4660, 22136]
63
79
 
64
- text_to_urlsafe = hx.Text() >> hx.Base64(urlsafe=True, padding=False)
65
- text_to_urlsafe("data")
66
- # 'ZGF0YQ'
80
+ pack_to_hex = hx.Struct(">HH") >> hx.Hex()
81
+ pack_to_hex((0x1234, 0x5678))
82
+ # '12345678'
67
83
  ```
68
84
 
69
- Hex input accepts common separators, so spaced or copied hex usually does not
70
- need manual cleanup before conversion. `Hex(sep=" ")` is also how you emit
71
- space-separated hex.
85
+ ## Configure when needed
72
86
 
73
- ## Quick tour
74
-
75
- Use direct helpers when you want one obvious conversion across many
76
- representations:
87
+ Formats work bare by default. Add arguments only for the conversion that needs
88
+ them:
77
89
 
78
90
  ```python
79
- hx.from_hex("de ad be ef").bytes
91
+ hx.convert("de ad be ef", hx.Hex(), hx.Bytes())
80
92
  # b'\xde\xad\xbe\xef'
81
93
 
82
- hx.from_text("data").to_hex_array(prefix=True)
83
- # ['0x64', '0x61', '0x74', '0x61']
84
-
85
- hx.from_int(0xdeadbeef).bytes
86
- # b'\xde\xad\xbe\xef'
94
+ hx.convert(b"\xde\xad\xbe\xef", hx.Bytes(), hx.Hex(sep=" ", prefix=True, uppercase=True))
95
+ # '0xDE AD BE EF'
87
96
 
88
- hx.from_int_array([0x1234, 0x5678], width=2).to_hex_array(width=2)
89
- # ['1234', '5678']
97
+ hx.convert("π", hx.Text(encoding="utf-8"), hx.Hex())
98
+ # 'cf80'
90
99
 
91
- hx.from_binary("01100100 01100001 01110100 01100001").text
92
- # 'data'
100
+ hx.from_int(0x12345678, endian="little").hex
101
+ # '78563412'
93
102
 
94
- hx.from_base64("ZGF0YQ==").text
103
+ hx.convert("ZGF0YQ", hx.Base64(padding=False), hx.Text())
95
104
  # 'data'
96
105
 
97
- hx.from_escaped(r"\xde\xad").hex
98
- # 'dead'
99
-
100
- hx.from_struct((0x1234, 0x5678), fmt=">HH").hex
101
- # '12345678'
102
-
103
- hx.from_bits("101", pad=True).hex
104
- # '05'
106
+ hx.from_binary("00010110 10010110", bit_order="lsb").text
107
+ # 'hi'
105
108
  ```
106
109
 
107
- Use `from_auto` when you want a quick best-effort parse:
108
-
109
- ```python
110
- hx.from_auto("0xdeadbeef").bytes
111
- # b'\xde\xad\xbe\xef'
112
-
113
- hx.from_auto(b"\xde\xad").hex
114
- # 'dead'
115
-
116
- hx.from_auto([0xde, 0xad, 0xbe, 0xef]).to(hx.Hex(prefix=True))
117
- # '0xdeadbeef'
118
- ```
110
+ Hex strings may include common separators, so you do not need to manually strip
111
+ spaces, colons, dashes, or `0x` prefixes before converting. `Hex(sep=" ")` is
112
+ how you emit space-separated hex.
119
113
 
120
- ## Composable interface
114
+ ## Full pipeline guide
121
115
 
122
116
  `hexconv` is built around small format objects. A format can parse directly:
123
117
 
@@ -128,8 +122,15 @@ hx.Hex()("de ad be ef").bytes
128
122
  hx.Text()("data").hex
129
123
  # '64617461'
130
124
 
131
- hx.Int(width=4, endian="little")(0x12345678).hex
125
+ hx.Int(endian="little")(0x12345678).hex
132
126
  # '78563412'
127
+
128
+ pack_words = hx.Struct(">HH")
129
+ pack_words((0x1234, 0x5678)).hex
130
+ # '12345678'
131
+
132
+ hx.Hex()("12345678").to(pack_words)
133
+ # (4660, 22136)
133
134
  ```
134
135
 
135
136
  Formats compose with `>>`, so reusable converters are normal Python objects:
@@ -142,12 +143,20 @@ to_text([0x48, 0x65, 0x6C, 0x6C, 0x6F])
142
143
  to_hex = hx.Text() >> hx.Hex(prefix=True)
143
144
  to_hex("data")
144
145
  # '0x64617461'
146
+
147
+ pack_to_hex = hx.Struct(">HH") >> hx.Hex()
148
+ pack_to_hex((0x1234, 0x5678))
149
+ # '12345678'
150
+
151
+ unpack_from_hex = hx.Hex() >> hx.Struct(">HH")
152
+ unpack_from_hex("12345678")
153
+ # (4660, 22136)
145
154
  ```
146
155
 
147
156
  Transforms sit between formats when data needs shaping:
148
157
 
149
158
  ```python
150
- words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray(width=2)
159
+ words = hx.Hex() >> hx.Chunk(2) >> hx.IntArray()
151
160
  words("12345678")
152
161
  # [4660, 22136]
153
162
 
@@ -174,33 +183,7 @@ conv("12345678")
174
183
  # ['0x1234', '0x5678']
175
184
  ```
176
185
 
177
- ## Configurable one-shot conversions
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:
186
+ More conversion patterns:
204
187
 
205
188
  ```python
206
189
  hx.Text()("hello").bytes
@@ -209,54 +192,24 @@ hx.Text()("hello").bytes
209
192
  hx.Hex()("68656c6c6f").text
210
193
  # 'hello'
211
194
 
212
- hx.BytesArray()([0x68, 0x69]).to(hx.Text())
213
- # 'hi'
214
- ```
215
-
216
- Integer packing:
195
+ hx.BytesArray()([0x4F, 0x4B]).to(hx.Text())
196
+ # 'OK'
217
197
 
218
- ```python
219
- hx.Int(width=2)(0x1234).hex
220
- # '1234'
221
-
222
- hx.Int(width=2, endian="little")(0x1234).hex
198
+ hx.Int(endian="little")(0x1234).hex
223
199
  # '3412'
224
200
 
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
201
  hx.Text()("data").to(hx.Base32(padding=False))
236
202
  # 'MRQXIYI'
237
203
 
238
- hx.Base64(urlsafe=True, padding=False)("-_8").hex
239
- # 'fbff'
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'
204
+ dump = hx.Text()("data").to(hx.Hexdump(width=2))
205
+ hx.Hexdump()(dump).text
206
+ # 'data'
254
207
 
255
208
  hx.Hex()("12345678").to(hx.Struct(">HH"))
256
209
  # (4660, 22136)
257
210
 
258
- hx.Bits()([1, 0, 1]).hex
259
- # '05'
211
+ hx.Bits()("0100111101001011").text
212
+ # 'OK'
260
213
  ```
261
214
 
262
215
  ## Why explicit formats?
@@ -304,10 +257,10 @@ result.reason
304
257
  `from_auto(..., explain=True)` returns the same explanation object:
305
258
 
306
259
  ```python
307
- result = hx.from_auto("0b101", explain=True)
260
+ result = hx.from_auto("0b0100111101001011", explain=True)
308
261
 
309
- result.value.hex
310
- # '05'
262
+ result.value.text
263
+ # 'OK'
311
264
  ```
312
265
 
313
266
  ## Design priorities
@@ -331,7 +284,7 @@ conv("deadbeef")
331
284
  # 3735928559
332
285
  ```
333
286
 
334
- Available format specs:
287
+ Available format specs all work in their simplest bare form:
335
288
 
336
289
  - `Bytes()`
337
290
  - `BytesArray()`
@@ -339,8 +292,8 @@ Available format specs:
339
292
  - `Hex()`
340
293
  - `HexArray()`
341
294
  - `HexInt()` / `LargeHexNumber()`
342
- - `Int()`
343
- - `IntArray()`
295
+ - `Int()` / `DecimalInt()`
296
+ - `IntArray()` / `DecimalIntArray()`
344
297
  - `Text()`
345
298
  - `Binary()`
346
299
  - `Base64()`
@@ -351,6 +304,20 @@ Available format specs:
351
304
  - `Hexdump()`
352
305
  - `Struct(fmt)`
353
306
  - `Bits()`
307
+ - `Auto()`
308
+
309
+ Add options only when you need control over formatting or parsing:
310
+
311
+ - Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
312
+ - Integer packing: `Int(width=4)`, `Int(endian="little")`, `IntArray(width=2)`.
313
+ - Text: `Text(encoding="utf-8", errors="replace")`.
314
+ - Base encodings: `Base64(urlsafe=True, padding=False)`, `Base32(padding=False)`.
315
+ - Binary/bits: `Binary(sep=" ", prefix=True, bit_order="lsb")`, `Bits(pad_side="right")`.
316
+ - Dumps/escapes/structs: `Hexdump(width=8)`, `Escaped(uppercase=True)`, `Struct(">HH")`.
317
+
318
+ Some options only matter in one direction. For example, `Hex(sep=" ")`
319
+ controls hex output formatting, while hex input already accepts common
320
+ separators such as spaces, colons, dashes, underscores, and `0x` prefixes.
354
321
 
355
322
  ## Transforms
356
323
 
@@ -358,7 +325,7 @@ Transforms are byte-to-byte steps that sit between input and output formats.
358
325
 
359
326
  ```python
360
327
  hx.Hex() >> hx.Chunk(2) >> hx.HexArray()
361
- hx.Hex() >> hx.Group(2) >> hx.IntArray(width=2)
328
+ hx.Hex() >> hx.Group(2) >> hx.IntArray()
362
329
  hx.Hex() >> hx.Pad.left(width=8) >> hx.Hex()
363
330
  hx.Hex() >> hx.Pad.right(block_size=8, byte=0xFF) >> hx.Hex()
364
331
  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(sep=" "), hx.Bytes()) == b"\xde\xad\xbe\xef"
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(sep=" ")).chunk(2).to(hx.IntArray(width=2))
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(sep=" ") >> hx.Chunk(2) >> hx.HexArray()
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