nfcscript 0.0.13__tar.gz → 0.0.14__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.
Files changed (31) hide show
  1. {nfcscript-0.0.13 → nfcscript-0.0.14}/PKG-INFO +3 -2
  2. {nfcscript-0.0.13 → nfcscript-0.0.14}/README.md +2 -1
  3. {nfcscript-0.0.13 → nfcscript-0.0.14}/pyproject.toml +2 -2
  4. nfcscript-0.0.14/src/nfc/hex_util.py +189 -0
  5. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfcscript.egg-info/PKG-INFO +3 -2
  6. nfcscript-0.0.14/tests/test_hex_util.py +245 -0
  7. nfcscript-0.0.13/src/nfc/hex_util.py +0 -131
  8. nfcscript-0.0.13/tests/test_hex_util.py +0 -168
  9. {nfcscript-0.0.13 → nfcscript-0.0.14}/setup.cfg +0 -0
  10. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/__init__.py +0 -0
  11. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/assertions.py +0 -0
  12. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/bits.py +0 -0
  13. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/checksum.py +0 -0
  14. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/cli.py +0 -0
  15. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/delay.py +0 -0
  16. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/nfc_cli.py +0 -0
  17. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/reader.py +0 -0
  18. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfc/trace.py +0 -0
  19. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfcscript.egg-info/SOURCES.txt +0 -0
  20. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfcscript.egg-info/dependency_links.txt +0 -0
  21. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfcscript.egg-info/entry_points.txt +0 -0
  22. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfcscript.egg-info/requires.txt +0 -0
  23. {nfcscript-0.0.13 → nfcscript-0.0.14}/src/nfcscript.egg-info/top_level.txt +0 -0
  24. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_assertions.py +0 -0
  25. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_bits.py +0 -0
  26. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_checksum.py +0 -0
  27. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_cli.py +0 -0
  28. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_cli_collect.py +0 -0
  29. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_delay.py +0 -0
  30. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_reader.py +0 -0
  31. {nfcscript-0.0.13 → nfcscript-0.0.14}/tests/test_trace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nfcscript
3
- Version: 0.0.13
3
+ Version: 0.0.14
4
4
  Summary: A plugin-based core engine for DSL-style NFC testing automation.
5
5
  Author: crthu
6
6
  Requires-Python: >=3.14
@@ -211,7 +211,8 @@ with session() as reader:
211
211
 
212
212
  | 函数 | 说明 |
213
213
  |------|------|
214
- | `PARSE_HEX(text)` | 解析 hex 字符串 |
214
+ | `PARSE_HEX(text)` | 解析纯 hex,返回 `list[int]` |
215
+ | `PARSE_HEX_BITS(text, last_bits=None)` | 解析 hex + Verilog 位标记,返回 `tuple[list[int], int]` |
215
216
  | `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
216
217
 
217
218
  ### checksum.py
@@ -201,7 +201,8 @@ with session() as reader:
201
201
 
202
202
  | 函数 | 说明 |
203
203
  |------|------|
204
- | `PARSE_HEX(text)` | 解析 hex 字符串 |
204
+ | `PARSE_HEX(text)` | 解析纯 hex,返回 `list[int]` |
205
+ | `PARSE_HEX_BITS(text, last_bits=None)` | 解析 hex + Verilog 位标记,返回 `tuple[list[int], int]` |
205
206
  | `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
206
207
 
207
208
  ### checksum.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nfcscript"
3
- version = "0.0.13"
3
+ version = "0.0.14"
4
4
  description = "A plugin-based core engine for DSL-style NFC testing automation."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -34,7 +34,7 @@ dev = [
34
34
  ]
35
35
 
36
36
  [tool.bumpversion]
37
- current_version = "0.0.13"
37
+ current_version = "0.0.14"
38
38
  commit = true
39
39
  tag = true
40
40
  allow_dirty = true
@@ -0,0 +1,189 @@
1
+ import re
2
+
3
+ # 位标记正则:匹配 N'hXX 格式 (如 7'h26, 4'hC)
4
+ BIT_PATTERN = re.compile(r"(\d+)\s*\'h\s*([0-9a-fA-F]+)")
5
+
6
+
7
+ def PARSE_HEX(text: str) -> list[int]:
8
+ """
9
+ 解析纯 hex 字符串为 list[int]。
10
+
11
+ Args:
12
+ text: 十六进制文本,支持空格分隔 (如 "AA BB CC" 或 "AABBCC")。
13
+
14
+ Returns:
15
+ list[int]: 解析后的字节列表。
16
+
17
+ Raises:
18
+ ValueError: 不合法的十六进制数据时抛出。
19
+
20
+ Examples:
21
+ >>> PARSE_HEX("AA BB CC")
22
+ [0xAA, 0xBB, 0xCC]
23
+ >>> PARSE_HEX("0011")
24
+ [0x00, 0x11]
25
+ """
26
+ # 去空格得到纯 hex
27
+ clean_hex = text.replace(" ", "").upper()
28
+
29
+ # 简单的合法性检查
30
+ if clean_hex:
31
+ try:
32
+ return list(bytes.fromhex(clean_hex))
33
+ except ValueError:
34
+ raise ValueError(f"不合法的十六进制数据: {clean_hex}")
35
+ return []
36
+
37
+
38
+ def PARSE_HEX_BITS(text: str, last_bits: int | None = None) -> tuple[list[int], int]:
39
+ """
40
+ 解析 hex 字符串为 tuple[list[int], int],支持 Verilog 位标记。
41
+
42
+ 支持格式:
43
+ - 纯 hex: "AA BB CC" → ([0xAA, 0xBB, 0xCC], 0)
44
+ - Verilog 标记: "AA 4'hBB" → ([0xAA, 0x0B], 4)
45
+ - 混合: "AA BB 7'h03" → ([0xAA, 0xBB, 0x03], 7)
46
+ - 手动指定: "AA BB", last_bits=4 → ([0xAA, 0xBB], 4)
47
+
48
+ Args:
49
+ text: 十六进制文本,支持空格分隔和 N'hXX 位标记。
50
+ last_bits: 可选的最后字节有效位数,与 Verilog 标记冲突时报错。
51
+
52
+ Returns:
53
+ tuple[list[int], int]: (字节列表, 最后字节有效位数)。
54
+
55
+ Raises:
56
+ ValueError: 不合法的十六进制数据、位标记冲突或多标记时报错。
57
+
58
+ Examples:
59
+ >>> PARSE_HEX_BITS("AA BB CC")
60
+ ([0xAA, 0xBB, 0xCC], 0)
61
+ >>> PARSE_HEX_BITS("AA 4'hBB")
62
+ ([0xAA, 0x0B], 4)
63
+ >>> PARSE_HEX_BITS("0011", last_bits=4)
64
+ ([0x00, 0x01], 4)
65
+ >>> PARSE_HEX_BITS("AA 4'hBB", last_bits=4)
66
+ ValueError: 冲突
67
+ """
68
+ # 冲突检测:只允许一个 Verilog 标记
69
+ matches = list(BIT_PATTERN.finditer(text))
70
+ if len(matches) > 1:
71
+ raise ValueError("只允许一个 Verilog 位标记")
72
+ if matches and last_bits is not None:
73
+ raise ValueError("last_bits 参数与 Verilog 位标记冲突")
74
+
75
+ bits = last_bits if last_bits is not None else 0
76
+
77
+ # 处理 Verilog 位标记
78
+ if matches:
79
+ m = matches[0]
80
+ n_bits = int(m.group(1))
81
+ bits = n_bits
82
+ # 按 bits 截断: 4'hCA → 0x0A
83
+ val = int(m.group(2), 16) & ((1 << n_bits) - 1)
84
+ hex_val = format(val, 'X')
85
+ if len(hex_val) % 2 != 0:
86
+ hex_val = '0' + hex_val
87
+ text = text[:m.start()] + hex_val + text[m.end():]
88
+
89
+ # 去空格得到纯 hex
90
+ clean_hex = text.replace(" ", "").upper()
91
+
92
+ # 解析 hex 数据
93
+ if clean_hex:
94
+ try:
95
+ data = list(bytes.fromhex(clean_hex))
96
+ except ValueError:
97
+ raise ValueError(f"不合法的十六进制数据: {clean_hex}")
98
+ else:
99
+ data = []
100
+
101
+ # last_bits 截断最后一个字节
102
+ if bits and data:
103
+ data[-1] = data[-1] & ((1 << bits) - 1)
104
+
105
+ return data, bits
106
+
107
+
108
+ def FORMAT_HEX(data: int | bytes | list[int] | str, last_bits: int = 0) -> str:
109
+ """
110
+ 格式化数据为带位标记的可视化格式。
111
+
112
+ 支持输入类型: int, bytes, list[int], str (纯 hex)。
113
+
114
+ Args:
115
+ data: 待格式化的数据。
116
+ last_bits: 最后一个字节的有效位数 (1-7),0 表示整字节。
117
+
118
+ Returns:
119
+ str: 格式化后的字符串。
120
+
121
+ Raises:
122
+ TypeError: 不支持的输入类型时抛出。
123
+
124
+ Examples:
125
+ >>> FORMAT_HEX(0xAABB03, 7)
126
+ "AA BB 7'h03"
127
+ >>> FORMAT_HEX([0xAA, 0xBB, 0x03])
128
+ 'AA BB 03'
129
+ >>> FORMAT_HEX("AABB", 4)
130
+ "AA 4'hBB"
131
+ """
132
+ # 1. 统一转换为纯十六进制字符串
133
+ if isinstance(data, int):
134
+ hex_str = data.to_bytes(max(1, (data.bit_length() + 7) // 8)).hex().upper()
135
+ elif isinstance(data, bytes):
136
+ hex_str = data.hex().upper()
137
+ elif isinstance(data, list):
138
+ hex_str = bytes(data).hex().upper()
139
+ elif isinstance(data, str):
140
+ hex_str = data.replace(" ", "").upper()
141
+ else:
142
+ raise TypeError(f"不支持的输入类型: {type(data)}")
143
+
144
+ if not hex_str:
145
+ return ""
146
+
147
+ # 2. 如果是整字节 (无 last_bits)
148
+ if last_bits == 0:
149
+ return ' '.join(hex_str[i:i+2] for i in range(0, len(hex_str), 2))
150
+
151
+ # 3. 有 last_bits,处理最后一个字节
152
+ if len(hex_str) <= 2:
153
+ return f"{last_bits}'h{hex_str}"
154
+
155
+ prefix = hex_str[:-2]
156
+ last_byte = hex_str[-2:]
157
+ formatted_prefix = ' '.join(prefix[i:i+2] for i in range(0, len(prefix), 2))
158
+ return f"{formatted_prefix} {last_bits}'h{last_byte}"
159
+
160
+
161
+ if __name__ == "__main__":
162
+ # --- 测试例程 ---
163
+ print("=== Hex 工具测试 ===")
164
+
165
+ # 测试用例: (data, last_bits, 类型名)
166
+ test_cases = [
167
+ ("AABB03", 7, "str"),
168
+ (0xAABB03, 7, "int"),
169
+ (b'\xAA\xBB\x03', 7, "bytes"),
170
+ ([0xAA, 0xBB, 0x03], 7, "list"),
171
+ ("FF", 4, "str"),
172
+ (0xFF, 4, "int"),
173
+ ("AABBCC", 0, "str"),
174
+ (0xAABBCC, 0, "int"),
175
+ (b'\xAA\xBB\xCC', 0, "bytes"),
176
+ ]
177
+
178
+ for data, b, typename in test_cases:
179
+ formatted = FORMAT_HEX(data, b)
180
+ print(f"输入: ({data}, {b}, {typename}) -> 格式化: {formatted}")
181
+
182
+ # 反向解析验证
183
+ if b:
184
+ parsed, bits = PARSE_HEX_BITS(formatted)
185
+ print(f"解析: {formatted} -> Hex: {parsed}, Bits: {bits}")
186
+ else:
187
+ parsed = PARSE_HEX(formatted)
188
+ print(f"解析: {formatted} -> Hex: {parsed}")
189
+ print("-" * 20)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nfcscript
3
- Version: 0.0.13
3
+ Version: 0.0.14
4
4
  Summary: A plugin-based core engine for DSL-style NFC testing automation.
5
5
  Author: crthu
6
6
  Requires-Python: >=3.14
@@ -211,7 +211,8 @@ with session() as reader:
211
211
 
212
212
  | 函数 | 说明 |
213
213
  |------|------|
214
- | `PARSE_HEX(text)` | 解析 hex 字符串 |
214
+ | `PARSE_HEX(text)` | 解析纯 hex,返回 `list[int]` |
215
+ | `PARSE_HEX_BITS(text, last_bits=None)` | 解析 hex + Verilog 位标记,返回 `tuple[list[int], int]` |
215
216
  | `FORMAT_HEX(data, last_bits)` | 格式化为可视化 hex |
216
217
 
217
218
  ### checksum.py
@@ -0,0 +1,245 @@
1
+ """PARSE_HEX / PARSE_HEX_BITS / FORMAT_HEX 测试"""
2
+ import pytest
3
+ from nfc.hex_util import PARSE_HEX, PARSE_HEX_BITS, FORMAT_HEX
4
+
5
+
6
+ class TestPARSE_HEX:
7
+ """PARSE_HEX: 纯 hex 解析,返回 list[int]"""
8
+
9
+ def test_simple_hex(self):
10
+ assert PARSE_HEX("AA BB CC") == [0xAA, 0xBB, 0xCC]
11
+
12
+ def test_no_space(self):
13
+ assert PARSE_HEX("AABBCC") == [0xAA, 0xBB, 0xCC]
14
+
15
+ def test_empty_string(self):
16
+ assert PARSE_HEX("") == []
17
+
18
+ def test_whitespace_only(self):
19
+ assert PARSE_HEX(" ") == []
20
+
21
+ def test_single_byte(self):
22
+ assert PARSE_HEX("FF") == [0xFF]
23
+
24
+ def test_lowercase(self):
25
+ assert PARSE_HEX("ff aa") == [0xFF, 0xAA]
26
+
27
+ def test_invalid_hex_raises(self):
28
+ with pytest.raises(ValueError, match="不合法的十六进制数据"):
29
+ PARSE_HEX("ZZZZ")
30
+
31
+ def test_odd_hex_digits_raises(self):
32
+ with pytest.raises(ValueError):
33
+ PARSE_HEX("ABC")
34
+
35
+
36
+ class TestPARSE_HEX_BITS:
37
+ """PARSE_HEX_BITS: 返回 tuple[list[int], int],支持位截断"""
38
+
39
+ # --- 基础测试 ---
40
+
41
+ def test_simple_hex(self):
42
+ assert PARSE_HEX_BITS("AA BB CC") == ([0xAA, 0xBB, 0xCC], 0)
43
+
44
+ def test_empty_string(self):
45
+ assert PARSE_HEX_BITS("") == ([], 0)
46
+
47
+ def test_whitespace_only(self):
48
+ assert PARSE_HEX_BITS(" ") == ([], 0)
49
+
50
+ def test_invalid_hex_raises(self):
51
+ with pytest.raises(ValueError, match="不合法的十六进制数据"):
52
+ PARSE_HEX_BITS("ZZZZ")
53
+
54
+ # --- last_bits 参数测试 ---
55
+
56
+ def test_with_last_bits(self):
57
+ """last_bits=4 截断最后一个字节: 0x11 → 0x01"""
58
+ assert PARSE_HEX_BITS("00 11", last_bits=4) == ([0x00, 0x01], 4)
59
+
60
+ def test_with_last_bits_single_byte(self):
61
+ """last_bits=7 截断: 0xFF → 0x7F"""
62
+ assert PARSE_HEX_BITS("FF", last_bits=7) == ([0x7F], 7)
63
+
64
+ def test_with_last_bits_no_space(self):
65
+ assert PARSE_HEX_BITS("0011", last_bits=4) == ([0x00, 0x01], 4)
66
+
67
+ # --- Verilog 标记:无截断 ---
68
+
69
+ def test_verilog_no_truncate(self):
70
+ """值在 bits 范围内,无需截断"""
71
+ assert PARSE_HEX_BITS("7'h03") == ([0x03], 7)
72
+
73
+ def test_verilog_no_truncate_even(self):
74
+ """偶数位 hex,无需填充"""
75
+ assert PARSE_HEX_BITS("8'hFF") == ([0xFF], 8)
76
+
77
+ def test_verilog_no_truncate_4bits(self):
78
+ """4 位 hex 值,无需截断"""
79
+ assert PARSE_HEX_BITS("4'hA") == ([0x0A], 4)
80
+
81
+ def test_verilog_no_truncate_4bits_max(self):
82
+ """4 位最大值 F,无需截断"""
83
+ assert PARSE_HEX_BITS("4'hF") == ([0x0F], 4)
84
+
85
+ # --- Verilog 标记:截断高位 ---
86
+
87
+ def test_verilog_truncate_4bits(self):
88
+ """4'hCA → 截断高位 C,只保留 4 位 → 0x0A"""
89
+ assert PARSE_HEX_BITS("4'hCA") == ([0x0A], 4)
90
+
91
+ def test_verilog_truncate_7bits(self):
92
+ """7'hFF → FF 是 8 位,截断为 7 位 → 0x7F"""
93
+ assert PARSE_HEX_BITS("7'hFF") == ([0x7F], 7)
94
+
95
+ def test_verilog_truncate_4bits_bigger(self):
96
+ """4'hFF → 截断为 4 位 → 0x0F"""
97
+ assert PARSE_HEX_BITS("4'hFF") == ([0x0F], 4)
98
+
99
+ def test_verilog_truncate_1bit(self):
100
+ """1'h1 → 1 位最大值 1"""
101
+ assert PARSE_HEX_BITS("1'h1") == ([0x01], 1)
102
+
103
+ def test_verilog_truncate_1bit_zero(self):
104
+ """1'h0 → 1 位最小值 0"""
105
+ assert PARSE_HEX_BITS("1'h0") == ([0x00], 1)
106
+
107
+ # --- Verilog 标记:奇数位填充 ---
108
+
109
+ def test_verilog_odd_pad(self):
110
+ """4'hB → 奇数位填充为 0B"""
111
+ assert PARSE_HEX_BITS("4'hB") == ([0x0B], 4)
112
+
113
+ def test_verilog_odd_pad_7bits(self):
114
+ """7'hA → 奇数位填充为 0A,7 位无需截断"""
115
+ assert PARSE_HEX_BITS("7'hA") == ([0x0A], 7)
116
+
117
+ def test_verilog_odd_pad_truncate(self):
118
+ """4'hCB → 奇数位填充后截断 → 0x0B"""
119
+ assert PARSE_HEX_BITS("4'hCB") == ([0x0B], 4)
120
+
121
+ # --- 混合格式 ---
122
+
123
+ def test_mixed_hex_and_verilog(self):
124
+ """4'hBB → 截断为 4 位 → 0x0B"""
125
+ assert PARSE_HEX_BITS("AA 4'hBB") == ([0xAA, 0x0B], 4)
126
+
127
+ def test_mixed_verilog_last(self):
128
+ """Verilog 标记在最后"""
129
+ assert PARSE_HEX_BITS("AA BB 7'h03") == ([0xAA, 0xBB, 0x03], 7)
130
+
131
+ def test_mixed_no_space_verilog_end(self):
132
+ """AABB7'h26 → 正常解析"""
133
+ assert PARSE_HEX_BITS("AABB7'h26") == ([0xAA, 0xBB, 0x26], 7)
134
+
135
+ # --- 冲突检测 ---
136
+
137
+ def test_verilog_and_last_bits_conflict(self):
138
+ with pytest.raises(ValueError, match="冲突"):
139
+ PARSE_HEX_BITS("AA 4'hBB", last_bits=4)
140
+
141
+ def test_multiple_verilog_marks_error(self):
142
+ """多个 Verilog 标记报错"""
143
+ with pytest.raises(ValueError, match="只允许一个"):
144
+ PARSE_HEX_BITS("7'h03 AA 4'hC")
145
+
146
+ # --- 小写 hex ---
147
+
148
+ def test_verilog_lower_hex(self):
149
+ assert PARSE_HEX_BITS("7'h0a") == ([0x0A], 7)
150
+
151
+ def test_verilog_lower_hex_truncate(self):
152
+ """4'hca → 截断为 4 位 → 0x0A"""
153
+ assert PARSE_HEX_BITS("4'hca") == ([0x0A], 4)
154
+
155
+
156
+ class TestFORMAT_HEX:
157
+ """FORMAT_HEX: 格式化为可视化 hex"""
158
+
159
+ def test_int_no_bits(self):
160
+ assert FORMAT_HEX(0xAABBCC) == "AA BB CC"
161
+
162
+ def test_int_with_bits(self):
163
+ assert FORMAT_HEX(0xAABB03, 7) == "AA BB 7'h03"
164
+
165
+ def test_int_single_byte_bits(self):
166
+ assert FORMAT_HEX(0xFF, 4) == "4'hFF"
167
+
168
+ def test_bytes_no_bits(self):
169
+ assert FORMAT_HEX(b'\xAA\xBB\xCC') == "AA BB CC"
170
+
171
+ def test_bytes_with_bits(self):
172
+ assert FORMAT_HEX(b'\xAA\xBB\x03', 7) == "AA BB 7'h03"
173
+
174
+ def test_list_no_bits(self):
175
+ assert FORMAT_HEX([0xAA, 0xBB, 0xCC]) == "AA BB CC"
176
+
177
+ def test_list_with_bits(self):
178
+ assert FORMAT_HEX([0xAA, 0xBB, 0x03], 7) == "AA BB 7'h03"
179
+
180
+ def test_str_no_bits(self):
181
+ assert FORMAT_HEX("AABBCC") == "AA BB CC"
182
+
183
+ def test_str_with_bits(self):
184
+ assert FORMAT_HEX("AABB03", 7) == "AA BB 7'h03"
185
+
186
+ def test_empty_string(self):
187
+ assert FORMAT_HEX("") == ""
188
+
189
+ def test_empty_bytes(self):
190
+ assert FORMAT_HEX(b'') == ""
191
+
192
+ def test_empty_list(self):
193
+ assert FORMAT_HEX([]) == ""
194
+
195
+ def test_unsupported_type(self):
196
+ with pytest.raises(TypeError, match="不支持的输入类型"):
197
+ FORMAT_HEX(3.14)
198
+
199
+ def test_hex_str_with_spaces(self):
200
+ assert FORMAT_HEX("AA BB CC") == "AA BB CC"
201
+
202
+ def test_lowercase_int(self):
203
+ assert FORMAT_HEX(0xff) == "FF"
204
+
205
+ def test_int_zero(self):
206
+ assert FORMAT_HEX(0) == "00"
207
+
208
+ def test_bytes_single(self):
209
+ assert FORMAT_HEX(b'\xFF') == "FF"
210
+
211
+ def test_list_single(self):
212
+ assert FORMAT_HEX([0xFF]) == "FF"
213
+
214
+ def test_int_large(self):
215
+ assert FORMAT_HEX(0xAABBCCDD) == "AA BB CC DD"
216
+
217
+ def test_bits_1(self):
218
+ assert FORMAT_HEX(0x01, 1) == "1'h01"
219
+
220
+ def test_bits_7(self):
221
+ assert FORMAT_HEX(0x26, 7) == "7'h26"
222
+
223
+
224
+ class TestRoundtrip:
225
+ """PARSE_HEX <-> FORMAT_HEX 往返测试"""
226
+
227
+ def test_roundtrip_no_bits(self):
228
+ data = [0xAA, 0xBB, 0xCC]
229
+ formatted = FORMAT_HEX(data)
230
+ parsed = PARSE_HEX(formatted)
231
+ assert parsed == data
232
+
233
+ def test_roundtrip_with_bits(self):
234
+ data = [0xAA, 0xBB, 0x03]
235
+ formatted = FORMAT_HEX(data, 7)
236
+ parsed, bits = PARSE_HEX_BITS(formatted)
237
+ assert parsed == data
238
+ assert bits == 7
239
+
240
+ def test_roundtrip_single_byte(self):
241
+ """4'hFF 会被截断为 4 位 → 0x0F"""
242
+ formatted = FORMAT_HEX(0xFF, 4)
243
+ parsed, bits = PARSE_HEX_BITS(formatted)
244
+ assert parsed == [0x0F]
245
+ assert bits == 4
@@ -1,131 +0,0 @@
1
- import re
2
-
3
- # 位标记正则:匹配 N'hXX 格式 (如 7'h26, 4'hC)
4
- BIT_PATTERN = re.compile(r"(\d+)\s*\'h\s*([0-9a-fA-F]+)")
5
-
6
- def PARSE_HEX(text: str) -> tuple[str, int]:
7
- """
8
- 解析带位标记的 hex 字符串。
9
-
10
- Args:
11
- text: 十六进制文本。
12
-
13
- Returns:
14
- tuple[str, int]: (纯十六进制字符串, 最后字节有效位数)。
15
-
16
- Raises:
17
- ValueError: 不合法的十六进制数据时抛出。
18
-
19
- Examples:
20
- >>> PARSE_HEX("AA BB 7'h03")
21
- ('AABB03', 7)
22
- >>> PARSE_HEX("FF 4'h0C")
23
- ('FF0C', 4)
24
- >>> PARSE_HEX("AABBCC")
25
- ('AABBCC', 0)
26
- """
27
- last_bits = 0
28
- cleaned = text.strip()
29
-
30
- # 查找所有位标记,从右往左替换避免偏移
31
- matches = list(BIT_PATTERN.finditer(cleaned))
32
- if matches:
33
- last_match = matches[-1]
34
- last_bits = int(last_match.group(1))
35
-
36
- for m in reversed(matches):
37
- hex_val = m.group(2)
38
- if len(hex_val) % 2 != 0:
39
- hex_val = "0" + hex_val
40
- cleaned = cleaned[:m.start()] + hex_val + cleaned[m.end():]
41
-
42
- # 去空格得到纯 hex
43
- clean_hex = cleaned.replace(" ", "")
44
-
45
- # 简单的合法性检查
46
- if clean_hex:
47
- try:
48
- bytes.fromhex(clean_hex)
49
- except ValueError:
50
- raise ValueError(f"不合法的十六进制数据: {clean_hex}")
51
-
52
- return clean_hex, last_bits
53
-
54
- def FORMAT_HEX(data: int | bytes | list[int] | str, last_bits: int = 0) -> str:
55
- """
56
- 格式化数据为带位标记的可视化格式。
57
-
58
- 支持输入类型: int, bytes, list[int], str (纯 hex)。
59
-
60
- Args:
61
- data: 待格式化的数据。
62
- last_bits: 最后一个字节的有效位数 (1-7),0 表示整字节。
63
-
64
- Returns:
65
- str: 格式化后的字符串。
66
-
67
- Raises:
68
- TypeError: 不支持的输入类型时抛出。
69
-
70
- Examples:
71
- >>> FORMAT_HEX(0xAABB03, 7)
72
- "AA BB 7'h03"
73
- >>> FORMAT_HEX([0xAA, 0xBB, 0x03])
74
- 'AA BB 03'
75
- >>> FORMAT_HEX("AABB", 4)
76
- "AA 4'hBB"
77
- """
78
- # 1. 统一转换为纯十六进制字符串
79
- if isinstance(data, int):
80
- hex_str = data.to_bytes(max(1, (data.bit_length() + 7) // 8)).hex().upper()
81
- elif isinstance(data, bytes):
82
- hex_str = data.hex().upper()
83
- elif isinstance(data, list):
84
- hex_str = bytes(data).hex().upper()
85
- elif isinstance(data, str):
86
- hex_str = data.replace(" ", "").upper()
87
- else:
88
- raise TypeError(f"不支持的输入类型: {type(data)}")
89
-
90
- if not hex_str:
91
- return ""
92
-
93
- # 2. 如果是整字节 (无 last_bits)
94
- if last_bits == 0:
95
- return ' '.join(hex_str[i:i+2] for i in range(0, len(hex_str), 2))
96
-
97
- # 3. 有 last_bits,处理最后一个字节
98
- if len(hex_str) <= 2:
99
- return f"{last_bits}'h{hex_str}"
100
-
101
- prefix = hex_str[:-2]
102
- last_byte = hex_str[-2:]
103
-
104
- formatted_prefix = ' '.join(prefix[i:i+2] for i in range(0, len(prefix), 2))
105
- return f"{formatted_prefix} {last_bits}'h{last_byte}"
106
-
107
- if __name__ == "__main__":
108
- # --- 测试例程 ---
109
- print("=== Hex 工具测试 ===")
110
-
111
- # 测试用例: (data, last_bits, 类型名)
112
- test_cases = [
113
- ("AABB03", 7, "str"),
114
- (0xAABB03, 7, "int"),
115
- (b'\xAA\xBB\x03', 7, "bytes"),
116
- ([0xAA, 0xBB, 0x03], 7, "list"),
117
- ("FF", 4, "str"),
118
- (0xFF, 4, "int"),
119
- ("AABBCC", 0, "str"),
120
- (0xAABBCC, 0, "int"),
121
- (b'\xAA\xBB\xCC', 0, "bytes"),
122
- ]
123
-
124
- for data, b, typename in test_cases:
125
- formatted = FORMAT_HEX(data, b)
126
- print(f"输入: ({data}, {b}, {typename}) -> 格式化: {formatted}")
127
-
128
- # 反向解析验证
129
- parsed_h, parsed_b = PARSE_HEX(formatted)
130
- print(f"解析: {formatted} -> Hex: {parsed_h}, Bits: {parsed_b}")
131
- print("-" * 20)
@@ -1,168 +0,0 @@
1
- """PARSE_HEX / FORMAT_HEX 测试"""
2
- import pytest
3
- from nfc.hex_util import PARSE_HEX, FORMAT_HEX
4
-
5
-
6
- class TestPARSE_HEX:
7
-
8
- def test_simple_hex(self):
9
- assert PARSE_HEX("AA BB CC") == ("AABBCC", 0)
10
-
11
- def test_bit_mark(self):
12
- hex_str, bits = PARSE_HEX("AA BB 7'h03")
13
- assert hex_str == "AABB03"
14
- assert bits == 7
15
-
16
- def test_single_byte_two_digits(self):
17
- hex_str, bits = PARSE_HEX("4'h0C")
18
- assert hex_str == "0C"
19
- assert bits == 4
20
-
21
- def test_single_byte_odd_hex_padded(self):
22
- hex_str, bits = PARSE_HEX("4'hC")
23
- assert hex_str == "0C"
24
- assert bits == 4
25
-
26
- def test_single_byte_odd_hex_a(self):
27
- hex_str, bits = PARSE_HEX("7'h3")
28
- assert hex_str == "03"
29
- assert bits == 7
30
-
31
- def test_no_space(self):
32
- assert PARSE_HEX("AABBCC") == ("AABBCC", 0)
33
-
34
- def test_empty_string(self):
35
- assert PARSE_HEX("") == ("", 0)
36
-
37
- def test_whitespace_only(self):
38
- assert PARSE_HEX(" ") == ("", 0)
39
-
40
- def test_multiple_bit_marks_all_replaced(self):
41
- hex_str, bits = PARSE_HEX("7'h03 AA 4'hC")
42
- assert hex_str == "03AA0C"
43
- assert bits == 4
44
-
45
- def test_multiple_bit_marks_first_and_last(self):
46
- hex_str, bits = PARSE_HEX("4'hA BB 7'h03")
47
- assert hex_str == "0ABB03"
48
- assert bits == 7
49
-
50
- def test_three_bit_marks(self):
51
- hex_str, bits = PARSE_HEX("4'h1 4'h2 4'h3")
52
- assert hex_str == "010203"
53
- assert bits == 4
54
-
55
- def test_invalid_hex_raises(self):
56
- with pytest.raises(ValueError, match="不合法的十六进制数据"):
57
- PARSE_HEX("ZZZZ")
58
-
59
- def test_odd_hex_digits_raises(self):
60
- with pytest.raises(ValueError):
61
- PARSE_HEX("ABC")
62
-
63
- def test_hex_with_uppercase(self):
64
- assert PARSE_HEX("FF AA") == ("FFAA", 0)
65
-
66
- def test_hex_with_lowercase(self):
67
- assert PARSE_HEX("ff aa") == ("ffaa", 0)
68
-
69
- def test_bit_mark_no_space_around(self):
70
- hex_str, bits = PARSE_HEX("AA7'h03BB")
71
- assert hex_str == "AA03BB"
72
- assert bits == 7
73
-
74
- def test_bit_mark_only(self):
75
- hex_str, bits = PARSE_HEX("8'hFF")
76
- assert hex_str == "FF"
77
- assert bits == 8
78
-
79
- def test_bit_mark_with_lower_hex(self):
80
- hex_str, bits = PARSE_HEX("7'h0a")
81
- assert hex_str == "0a"
82
- assert bits == 7
83
-
84
- def test_roundtrip_with_format(self):
85
- data = [0xAA, 0xBB, 0x03]
86
- formatted = FORMAT_HEX(data, 7)
87
- parsed, bits = PARSE_HEX(formatted)
88
- assert bits == 7
89
- assert bytes.fromhex(parsed) == bytes(data)
90
-
91
- def test_roundtrip_single_byte(self):
92
- formatted = FORMAT_HEX(0xFF, 4)
93
- parsed, bits = PARSE_HEX(formatted)
94
- assert bits == 4
95
- assert parsed == "FF"
96
-
97
- def test_roundtrip_no_bits(self):
98
- formatted = FORMAT_HEX([0x01, 0x02, 0x03])
99
- parsed, bits = PARSE_HEX(formatted)
100
- assert bits == 0
101
- assert parsed == "010203"
102
-
103
-
104
- class TestFORMAT_HEX:
105
-
106
- def test_int_no_bits(self):
107
- assert FORMAT_HEX(0xAABBCC) == "AA BB CC"
108
-
109
- def test_int_with_bits(self):
110
- assert FORMAT_HEX(0xAABB03, 7) == "AA BB 7'h03"
111
-
112
- def test_int_single_byte_bits(self):
113
- assert FORMAT_HEX(0xFF, 4) == "4'hFF"
114
-
115
- def test_bytes_no_bits(self):
116
- assert FORMAT_HEX(b'\xAA\xBB\xCC') == "AA BB CC"
117
-
118
- def test_bytes_with_bits(self):
119
- assert FORMAT_HEX(b'\xAA\xBB\x03', 7) == "AA BB 7'h03"
120
-
121
- def test_list_no_bits(self):
122
- assert FORMAT_HEX([0xAA, 0xBB, 0xCC]) == "AA BB CC"
123
-
124
- def test_list_with_bits(self):
125
- assert FORMAT_HEX([0xAA, 0xBB, 0x03], 7) == "AA BB 7'h03"
126
-
127
- def test_str_no_bits(self):
128
- assert FORMAT_HEX("AABBCC") == "AA BB CC"
129
-
130
- def test_str_with_bits(self):
131
- assert FORMAT_HEX("AABB03", 7) == "AA BB 7'h03"
132
-
133
- def test_empty_string(self):
134
- assert FORMAT_HEX("") == ""
135
-
136
- def test_empty_bytes(self):
137
- assert FORMAT_HEX(b'') == ""
138
-
139
- def test_empty_list(self):
140
- assert FORMAT_HEX([]) == ""
141
-
142
- def test_unsupported_type(self):
143
- with pytest.raises(TypeError, match="不支持的输入类型"):
144
- FORMAT_HEX(3.14)
145
-
146
- def test_hex_str_with_spaces(self):
147
- assert FORMAT_HEX("AA BB CC") == "AA BB CC"
148
-
149
- def test_lowercase_int(self):
150
- assert FORMAT_HEX(0xff) == "FF"
151
-
152
- def test_int_zero(self):
153
- assert FORMAT_HEX(0) == "00"
154
-
155
- def test_bytes_single(self):
156
- assert FORMAT_HEX(b'\xFF') == "FF"
157
-
158
- def test_list_single(self):
159
- assert FORMAT_HEX([0xFF]) == "FF"
160
-
161
- def test_int_large(self):
162
- assert FORMAT_HEX(0xAABBCCDD) == "AA BB CC DD"
163
-
164
- def test_bits_1(self):
165
- assert FORMAT_HEX(0x01, 1) == "1'h01"
166
-
167
- def test_bits_7(self):
168
- assert FORMAT_HEX(0x26, 7) == "7'h26"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes