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