cjkfold 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cjkfold-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cdcailf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cjkfold-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: cjkfold
3
+ Version: 0.1.0
4
+ Summary: Fold look-alike CJK glyphs (Kangxi radicals, CJK radical supplements) so text extracted from PDFs matches again.
5
+ Author: cdcailf
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cdcailf/cjkfold
8
+ Project-URL: Repository, https://github.com/cdcailf/cjkfold
9
+ Project-URL: Issues, https://github.com/cdcailf/cjkfold/issues
10
+ Keywords: cjk,chinese,unicode,nfkc,normalization,pdf-extraction,rag,retrieval,text-matching
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: Chinese (Simplified)
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Text Processing :: Linguistic
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # cjkfold
31
+
32
+ Text pulled out of a PDF often contains characters that **look** like ordinary
33
+ Han characters but are not them. Comparison against them quietly fails.
34
+
35
+ ```
36
+ ⻆ U+2EC6 CJK RADICAL SIMPLIFIED HORN renders as 角 U+89D2
37
+ ⾊ U+2F8A KANGXI RADICAL COLOR renders as 色 U+8272
38
+ ⺮ U+2EAE CJK RADICAL BAMBOO folds to 𥫗 U+25AD7
39
+ ```
40
+
41
+ `cjkfold` folds them back onto the characters you meant:
42
+
43
+ ```python
44
+ from cjkfold import fold
45
+
46
+ fold("标准颜⾊值与⻆色按钮")
47
+ # '标准颜色值与角色按钮'
48
+
49
+ "颜色" in "标准颜⾊值" # False ← the bug
50
+ "颜色" in fold("标准颜⾊值") # True
51
+ ```
52
+
53
+ Zero runtime dependencies. Pure functions. Python 3.9+.
54
+
55
+ ---
56
+
57
+ **背景阅读 / Background:** [PDF 抽出来的中文,为什么搜不到](docs/why-cjk-text-breaks.md)
58
+ —— 完整的排查经过(中文),包括我自己踩的四个坑。
59
+ 社区版:[掘金](https://juejin.cn/post/7695671592383807542)
60
+
61
+ **Built on this:** [corpusfold](https://github.com/cdcailf/corpusfold) — this
62
+ character layer wired into a full PDF → knowledge-base pipeline (抽取 / 分栏 /
63
+ 表格 / 分块 / 混合检索).
64
+
65
+ ## The problem
66
+
67
+ Nothing raises. The text displays correctly, because the glyphs really are
68
+ identical. What breaks is everything downstream that compares characters:
69
+
70
+ - `"紫色" in text` returns `False` for a document that shows 紫色
71
+ - a keyword search misses
72
+ - a vector retriever ranks an unrelated *short* document above the one that
73
+ contains the query verbatim, because a short document's embedding is tighter
74
+ and scores a higher cosine — measured on a real corpus, the relevant document
75
+ scored **0.29** against a decoy's **0.36**
76
+ - deduplication, caching and ground-truth checks all start disagreeing
77
+
78
+ ## Why NFKC is not enough
79
+
80
+ The reflex is `unicodedata.normalize("NFKC", text)`. It handles the **Kangxi
81
+ Radicals** block completely — all 214 characters carry compatibility
82
+ decompositions — and then stops:
83
+
84
+ | Block | Characters | Repaired by NFKC | Repaired by cjkfold |
85
+ |---|---|---|---|
86
+ | Kangxi Radicals `U+2F00–U+2FDF` | 214 | **214** | 214 |
87
+ | CJK Radicals Supplement `U+2E80–U+2EFF` | 115 | **2** | **114** |
88
+
89
+ Unicode withholds compatibility mappings in the supplement block *on purpose*:
90
+ those glyphs are distinct encoded radicals, not compatibility variants of an
91
+ existing character. So NFKC leaves 113 of the 115 standing.
92
+
93
+ `cjkfold` closes that gap with the `EquivalentUnifiedIdeograph` property from
94
+ the Unicode Character Database (Unicode 15.1+), which maps 114 of them onto a
95
+ unified ideograph. The one remainder, `⺀ U+2E80 CJK RADICAL REPEAT`, has no
96
+ ideograph equivalent and is left alone.
97
+
98
+ ## Install
99
+
100
+ Not published on PyPI yet, so install from a checkout:
101
+
102
+ ```bash
103
+ git clone https://github.com/cdcailf/cjkfold
104
+ cd cjkfold
105
+ uv venv .venv --python 3.11
106
+ uv pip install -e .
107
+ ```
108
+
109
+ Once it is published this collapses to `pip install cjkfold`.
110
+
111
+ ## Usage
112
+
113
+ ```python
114
+ from cjkfold import fold, has_lookalikes, iter_lookalikes
115
+
116
+ fold("紫⾊的⻆落") # '紫色的角落'
117
+ fold("普通文本") # unchanged; returns the same object
118
+
119
+ has_lookalikes("⻆") # True
120
+ has_lookalikes("角") # False
121
+
122
+ for item in iter_lookalikes("第一行\n第⼆行⻆"):
123
+ print(item)
124
+ # 2:2 ⼆ U+2F06 -> 二 U+4E8C (KANGXI RADICAL TWO)
125
+ # 2:4 ⻆ U+2EC6 -> 角 U+89D2 (CJK RADICAL SIMPLIFIED HORN)
126
+ ```
127
+
128
+ `fold()` is idempotent, and it is deliberately **not** an NFKC pass: it will
129
+ not touch full-width punctuation (`,` `;` `(` `)`), compatibility characters
130
+ or ligatures, so it is safe to apply to text that is still going to be stored or
131
+ displayed. Combine with NFKC yourself if you want both:
132
+
133
+ ```python
134
+ import unicodedata
135
+ clean = fold(unicodedata.normalize("NFKC", text))
136
+ ```
137
+
138
+ ### Command line
139
+
140
+ ```bash
141
+ cjkfold check document.txt # report look-alikes, exit 1 if any (CI-friendly)
142
+ cjkfold check --quiet docs/*.txt
143
+ cjkfold fold document.txt # print the folded text
144
+ cjkfold fold -w document.txt # rewrite in place
145
+ cat document.txt | cjkfold check -
146
+ ```
147
+
148
+ ```
149
+ $ cjkfold check extracted.txt
150
+ extracted.txt: 3 look-alike character(s) (1 kangxi, 2 radical-supplement)
151
+ 1:4 ⾊ U+2F8A -> 色 U+8272 (KANGXI RADICAL COLOR)
152
+ 1:7 ⻆ U+2EC6 -> 角 U+89D2 (CJK RADICAL SIMPLIFIED HORN)
153
+ 2:5 ⺮ U+2EAE -> 𥫗 U+25AD7 (CJK RADICAL BAMBOO)
154
+ ```
155
+
156
+ Drop `cjkfold check` into an ingest pipeline to catch extraction regressions
157
+ before they reach the index.
158
+
159
+ ## Retrieval helper
160
+
161
+ Folding is the fix; ranking is the follow-up. Dense retrieval is bad at literal
162
+ tokens, so `exact_boost` adds a bonus for query n-grams that appear verbatim in a
163
+ document:
164
+
165
+ ```python
166
+ from cjkfold import exact_boost
167
+
168
+ score = vector_similarity(query, document) + exact_boost(query, document)
169
+ ```
170
+
171
+ Both sides are folded inside the helper. That is not a convenience — folding the
172
+ index but not the query (or the reverse) silently disables the whole mechanism,
173
+ because nothing ever matches. The API gives you no way to get it wrong.
174
+
175
+ Queries shorter than the shingle size fall back to a containment test, so a
176
+ two-character query is not silently scored zero.
177
+
178
+ ## Wiring it into a pipeline
179
+
180
+ Three touch points, zero framework dependencies:
181
+
182
+ ```python
183
+ from cjkfold import fold, fold_documents, rerank
184
+
185
+ # 1. on the way in -- clean the corpus before it is embedded
186
+ documents = fold_documents(loader.load())
187
+
188
+ # 2. on the way out -- fold the query so it can match
189
+ hits = vectorstore.similarity_search_with_score(fold(question), k=20)
190
+
191
+ # 3. before you trust the order -- restore the verbatim matches
192
+ documents, scores = zip(*hits)
193
+ for document, score in rerank(question, documents, scores):
194
+ ...
195
+ ```
196
+
197
+ `fold_documents` and `rerank` recognise LangChain documents (`page_content`),
198
+ LlamaIndex nodes (`text`) and plain strings -- and import none of them. Making
199
+ you install a framework in order to fold a string would be a bad trade:
200
+ framework APIs move, string folding does not.
201
+
202
+ ## Caveats
203
+
204
+ - **Glyph folding only.** No traditional⇄simplified conversion, no z-variant
205
+ normalisation. Those are different problems with different trade-offs.
206
+ - **Table follows the latest UCD**, which can run ahead of the `unicodedata`
207
+ database bundled with an older Python — 3.9 ships Unicode 13, where one target
208
+ (`U+2B739`) is not yet assigned. Folding is unaffected (it is a plain code point
209
+ substitution), but `unicodedata.name()` on that result will come up empty.
210
+ - **Existing stored text is not retroactively repaired.** Fold on write; if you
211
+ have already indexed unfolded text, re-ingest or re-fold it.
212
+ - Characters above the BMP are written as `\U000xxxxx` in the table. A `\uXXXX`
213
+ escape silently truncates there and turns one character into two — the table
214
+ test suite guards against exactly that regression.
215
+
216
+ ## Regenerating the table
217
+
218
+ `src/cjkfold/_table.py` is generated, never hand-edited:
219
+
220
+ ```bash
221
+ python scripts/build_table.py # fetch from unicode.org
222
+ python scripts/build_table.py CJKRadicals.txt # or use a local copy
223
+ ```
224
+
225
+ ## Development
226
+
227
+ ```bash
228
+ uv venv .venv --python 3.9
229
+ uv pip install -e ".[dev]"
230
+ .venv/bin/python -m pytest
231
+ ```
232
+
233
+ ## 中文简述
234
+
235
+ PDF 抽出来的中文常常混着**形近异码**字符:`⻆` 和 `角` 长得一样、码位不同,于是
236
+ `"颜色" in 文本` 返回 `False`,检索排错、去重失效、缓存打不中。
237
+
238
+ `unicodedata.normalize("NFKC", ...)` 只能解决康熙部首区(214 个全都能还原),
239
+ **解决不了 CJK 部首补充区**——那个区 115 个字符里 NFKC 只救得了 2 个。
240
+
241
+ `cjkfold` 用 Unicode 官方的 `EquivalentUnifiedIdeograph` 属性补上这个缺口,
242
+ 115 个里能还原 114 个。同时提供 `exact_boost()`,把"查询片段在文档里逐字命中"
243
+ 补进向量相似度,避免短文档靠余弦虚高把真正命中的长文档挤下去。
244
+
245
+ ## License
246
+
247
+ MIT
@@ -0,0 +1,218 @@
1
+ # cjkfold
2
+
3
+ Text pulled out of a PDF often contains characters that **look** like ordinary
4
+ Han characters but are not them. Comparison against them quietly fails.
5
+
6
+ ```
7
+ ⻆ U+2EC6 CJK RADICAL SIMPLIFIED HORN renders as 角 U+89D2
8
+ ⾊ U+2F8A KANGXI RADICAL COLOR renders as 色 U+8272
9
+ ⺮ U+2EAE CJK RADICAL BAMBOO folds to 𥫗 U+25AD7
10
+ ```
11
+
12
+ `cjkfold` folds them back onto the characters you meant:
13
+
14
+ ```python
15
+ from cjkfold import fold
16
+
17
+ fold("标准颜⾊值与⻆色按钮")
18
+ # '标准颜色值与角色按钮'
19
+
20
+ "颜色" in "标准颜⾊值" # False ← the bug
21
+ "颜色" in fold("标准颜⾊值") # True
22
+ ```
23
+
24
+ Zero runtime dependencies. Pure functions. Python 3.9+.
25
+
26
+ ---
27
+
28
+ **背景阅读 / Background:** [PDF 抽出来的中文,为什么搜不到](docs/why-cjk-text-breaks.md)
29
+ —— 完整的排查经过(中文),包括我自己踩的四个坑。
30
+ 社区版:[掘金](https://juejin.cn/post/7695671592383807542)
31
+
32
+ **Built on this:** [corpusfold](https://github.com/cdcailf/corpusfold) — this
33
+ character layer wired into a full PDF → knowledge-base pipeline (抽取 / 分栏 /
34
+ 表格 / 分块 / 混合检索).
35
+
36
+ ## The problem
37
+
38
+ Nothing raises. The text displays correctly, because the glyphs really are
39
+ identical. What breaks is everything downstream that compares characters:
40
+
41
+ - `"紫色" in text` returns `False` for a document that shows 紫色
42
+ - a keyword search misses
43
+ - a vector retriever ranks an unrelated *short* document above the one that
44
+ contains the query verbatim, because a short document's embedding is tighter
45
+ and scores a higher cosine — measured on a real corpus, the relevant document
46
+ scored **0.29** against a decoy's **0.36**
47
+ - deduplication, caching and ground-truth checks all start disagreeing
48
+
49
+ ## Why NFKC is not enough
50
+
51
+ The reflex is `unicodedata.normalize("NFKC", text)`. It handles the **Kangxi
52
+ Radicals** block completely — all 214 characters carry compatibility
53
+ decompositions — and then stops:
54
+
55
+ | Block | Characters | Repaired by NFKC | Repaired by cjkfold |
56
+ |---|---|---|---|
57
+ | Kangxi Radicals `U+2F00–U+2FDF` | 214 | **214** | 214 |
58
+ | CJK Radicals Supplement `U+2E80–U+2EFF` | 115 | **2** | **114** |
59
+
60
+ Unicode withholds compatibility mappings in the supplement block *on purpose*:
61
+ those glyphs are distinct encoded radicals, not compatibility variants of an
62
+ existing character. So NFKC leaves 113 of the 115 standing.
63
+
64
+ `cjkfold` closes that gap with the `EquivalentUnifiedIdeograph` property from
65
+ the Unicode Character Database (Unicode 15.1+), which maps 114 of them onto a
66
+ unified ideograph. The one remainder, `⺀ U+2E80 CJK RADICAL REPEAT`, has no
67
+ ideograph equivalent and is left alone.
68
+
69
+ ## Install
70
+
71
+ Not published on PyPI yet, so install from a checkout:
72
+
73
+ ```bash
74
+ git clone https://github.com/cdcailf/cjkfold
75
+ cd cjkfold
76
+ uv venv .venv --python 3.11
77
+ uv pip install -e .
78
+ ```
79
+
80
+ Once it is published this collapses to `pip install cjkfold`.
81
+
82
+ ## Usage
83
+
84
+ ```python
85
+ from cjkfold import fold, has_lookalikes, iter_lookalikes
86
+
87
+ fold("紫⾊的⻆落") # '紫色的角落'
88
+ fold("普通文本") # unchanged; returns the same object
89
+
90
+ has_lookalikes("⻆") # True
91
+ has_lookalikes("角") # False
92
+
93
+ for item in iter_lookalikes("第一行\n第⼆行⻆"):
94
+ print(item)
95
+ # 2:2 ⼆ U+2F06 -> 二 U+4E8C (KANGXI RADICAL TWO)
96
+ # 2:4 ⻆ U+2EC6 -> 角 U+89D2 (CJK RADICAL SIMPLIFIED HORN)
97
+ ```
98
+
99
+ `fold()` is idempotent, and it is deliberately **not** an NFKC pass: it will
100
+ not touch full-width punctuation (`,` `;` `(` `)`), compatibility characters
101
+ or ligatures, so it is safe to apply to text that is still going to be stored or
102
+ displayed. Combine with NFKC yourself if you want both:
103
+
104
+ ```python
105
+ import unicodedata
106
+ clean = fold(unicodedata.normalize("NFKC", text))
107
+ ```
108
+
109
+ ### Command line
110
+
111
+ ```bash
112
+ cjkfold check document.txt # report look-alikes, exit 1 if any (CI-friendly)
113
+ cjkfold check --quiet docs/*.txt
114
+ cjkfold fold document.txt # print the folded text
115
+ cjkfold fold -w document.txt # rewrite in place
116
+ cat document.txt | cjkfold check -
117
+ ```
118
+
119
+ ```
120
+ $ cjkfold check extracted.txt
121
+ extracted.txt: 3 look-alike character(s) (1 kangxi, 2 radical-supplement)
122
+ 1:4 ⾊ U+2F8A -> 色 U+8272 (KANGXI RADICAL COLOR)
123
+ 1:7 ⻆ U+2EC6 -> 角 U+89D2 (CJK RADICAL SIMPLIFIED HORN)
124
+ 2:5 ⺮ U+2EAE -> 𥫗 U+25AD7 (CJK RADICAL BAMBOO)
125
+ ```
126
+
127
+ Drop `cjkfold check` into an ingest pipeline to catch extraction regressions
128
+ before they reach the index.
129
+
130
+ ## Retrieval helper
131
+
132
+ Folding is the fix; ranking is the follow-up. Dense retrieval is bad at literal
133
+ tokens, so `exact_boost` adds a bonus for query n-grams that appear verbatim in a
134
+ document:
135
+
136
+ ```python
137
+ from cjkfold import exact_boost
138
+
139
+ score = vector_similarity(query, document) + exact_boost(query, document)
140
+ ```
141
+
142
+ Both sides are folded inside the helper. That is not a convenience — folding the
143
+ index but not the query (or the reverse) silently disables the whole mechanism,
144
+ because nothing ever matches. The API gives you no way to get it wrong.
145
+
146
+ Queries shorter than the shingle size fall back to a containment test, so a
147
+ two-character query is not silently scored zero.
148
+
149
+ ## Wiring it into a pipeline
150
+
151
+ Three touch points, zero framework dependencies:
152
+
153
+ ```python
154
+ from cjkfold import fold, fold_documents, rerank
155
+
156
+ # 1. on the way in -- clean the corpus before it is embedded
157
+ documents = fold_documents(loader.load())
158
+
159
+ # 2. on the way out -- fold the query so it can match
160
+ hits = vectorstore.similarity_search_with_score(fold(question), k=20)
161
+
162
+ # 3. before you trust the order -- restore the verbatim matches
163
+ documents, scores = zip(*hits)
164
+ for document, score in rerank(question, documents, scores):
165
+ ...
166
+ ```
167
+
168
+ `fold_documents` and `rerank` recognise LangChain documents (`page_content`),
169
+ LlamaIndex nodes (`text`) and plain strings -- and import none of them. Making
170
+ you install a framework in order to fold a string would be a bad trade:
171
+ framework APIs move, string folding does not.
172
+
173
+ ## Caveats
174
+
175
+ - **Glyph folding only.** No traditional⇄simplified conversion, no z-variant
176
+ normalisation. Those are different problems with different trade-offs.
177
+ - **Table follows the latest UCD**, which can run ahead of the `unicodedata`
178
+ database bundled with an older Python — 3.9 ships Unicode 13, where one target
179
+ (`U+2B739`) is not yet assigned. Folding is unaffected (it is a plain code point
180
+ substitution), but `unicodedata.name()` on that result will come up empty.
181
+ - **Existing stored text is not retroactively repaired.** Fold on write; if you
182
+ have already indexed unfolded text, re-ingest or re-fold it.
183
+ - Characters above the BMP are written as `\U000xxxxx` in the table. A `\uXXXX`
184
+ escape silently truncates there and turns one character into two — the table
185
+ test suite guards against exactly that regression.
186
+
187
+ ## Regenerating the table
188
+
189
+ `src/cjkfold/_table.py` is generated, never hand-edited:
190
+
191
+ ```bash
192
+ python scripts/build_table.py # fetch from unicode.org
193
+ python scripts/build_table.py CJKRadicals.txt # or use a local copy
194
+ ```
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ uv venv .venv --python 3.9
200
+ uv pip install -e ".[dev]"
201
+ .venv/bin/python -m pytest
202
+ ```
203
+
204
+ ## 中文简述
205
+
206
+ PDF 抽出来的中文常常混着**形近异码**字符:`⻆` 和 `角` 长得一样、码位不同,于是
207
+ `"颜色" in 文本` 返回 `False`,检索排错、去重失效、缓存打不中。
208
+
209
+ `unicodedata.normalize("NFKC", ...)` 只能解决康熙部首区(214 个全都能还原),
210
+ **解决不了 CJK 部首补充区**——那个区 115 个字符里 NFKC 只救得了 2 个。
211
+
212
+ `cjkfold` 用 Unicode 官方的 `EquivalentUnifiedIdeograph` 属性补上这个缺口,
213
+ 115 个里能还原 114 个。同时提供 `exact_boost()`,把"查询片段在文档里逐字命中"
214
+ 补进向量相似度,避免短文档靠余弦虚高把真正命中的长文档挤下去。
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cjkfold"
7
+ version = "0.1.0"
8
+ description = "Fold look-alike CJK glyphs (Kangxi radicals, CJK radical supplements) so text extracted from PDFs matches again."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "cdcailf" }]
13
+ keywords = [
14
+ "cjk", "chinese", "unicode", "nfkc", "normalization",
15
+ "pdf-extraction", "rag", "retrieval", "text-matching",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Natural Language :: Chinese (Simplified)",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: Text Processing :: Linguistic",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/cdcailf/cjkfold"
34
+ Repository = "https://github.com/cdcailf/cjkfold"
35
+ Issues = "https://github.com/cdcailf/cjkfold/issues"
36
+
37
+ [project.scripts]
38
+ cjkfold = "cjkfold.cli:main"
39
+
40
+ [project.optional-dependencies]
41
+ dev = ["pytest>=7"]
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,36 @@
1
+ """cjkfold -- fold look-alike CJK glyphs so extracted text matches again.
2
+
3
+ >>> from cjkfold import fold
4
+ >>> fold("⻆⺁⼀") == "角厂一"
5
+ True
6
+
7
+ See :mod:`cjkfold.normalize` for the background, :mod:`cjkfold.score` for the
8
+ retrieval-ranking helper that pairs with it, and :mod:`cjkfold.integrations` for
9
+ framework-agnostic adapters that fold a whole document set.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from ._table import EQUIVALENT_IDEOGRAPH
15
+ from .integrations import fold_document, fold_documents, rerank, text_of
16
+ from .normalize import Lookalike, fold, has_lookalikes, iter_lookalikes, kind_of
17
+ from .score import exact_boost, ngram_overlap, ngrams
18
+
19
+ __version__ = "0.1.0"
20
+
21
+ __all__ = [
22
+ "fold",
23
+ "has_lookalikes",
24
+ "iter_lookalikes",
25
+ "Lookalike",
26
+ "kind_of",
27
+ "ngrams",
28
+ "ngram_overlap",
29
+ "exact_boost",
30
+ "text_of",
31
+ "fold_document",
32
+ "fold_documents",
33
+ "rerank",
34
+ "EQUIVALENT_IDEOGRAPH",
35
+ "__version__",
36
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())