fetchit-engine 0.1.2__tar.gz → 0.2.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.
- {fetchit_engine-0.1.2/fetchit_engine.egg-info → fetchit_engine-0.2.0}/PKG-INFO +1 -1
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine/core.py +74 -20
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0/fetchit_engine.egg-info}/PKG-INFO +1 -1
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/pyproject.toml +1 -1
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/LICENSE +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/NOTICE +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/README.md +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine/__init__.py +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine/ruleset.json +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine/spell_core.py +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine.egg-info/SOURCES.txt +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine.egg-info/dependency_links.txt +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine.egg-info/requires.txt +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/fetchit_engine.egg-info/top_level.txt +0 -0
- {fetchit_engine-0.1.2 → fetchit_engine-0.2.0}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ import json
|
|
|
15
15
|
import os
|
|
16
16
|
import re
|
|
17
17
|
|
|
18
|
-
ENGINE_VERSION = "0.
|
|
18
|
+
ENGINE_VERSION = "0.2.0"
|
|
19
19
|
|
|
20
20
|
# --- ruleset (single source of truth, shared with the JS package) -----------
|
|
21
21
|
_RULESET_PATH = os.path.join(
|
|
@@ -43,10 +43,12 @@ _LEVEL_MODERATE = _T["levelModerate"]
|
|
|
43
43
|
_LEVEL_HIGH = _T["levelHigh"]
|
|
44
44
|
|
|
45
45
|
# --- regexes that are logic, not data (kept in code; may contain dashes) -----
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
|
|
46
|
+
# A RUN of em dashes (U+2014) / horizontal bars (U+2015), optionally
|
|
47
|
+
# space-separated, is one match, so "a——b" produces one comma, not two edits;
|
|
48
|
+
# an en dash (U+2013) matches only when spaced on both sides, so numeric
|
|
49
|
+
# ranges like 3–5 survive. Only spaces/tabs are consumed so line breaks
|
|
50
|
+
# survive. Mirrors EM_DASH_RE in the JS engine.
|
|
51
|
+
_EM_DASH = re.compile(r"[ \t]*[—―](?:[ \t]*[—―])*[ \t]*|[ \t]+–[ \t]+")
|
|
50
52
|
_MULTI_SPACE = re.compile(r"[ \t]{2,}")
|
|
51
53
|
_SPACE_BEFORE_PUNCT = re.compile(r"[ \t]+([,.;:!?])")
|
|
52
54
|
|
|
@@ -85,10 +87,54 @@ def _split_ws(s):
|
|
|
85
87
|
return [w for w in re.split(r"[ \t\n\r\f\v]+", s) if w]
|
|
86
88
|
|
|
87
89
|
# Rule metadata for edits produced by the dash/space normalization passes.
|
|
88
|
-
|
|
90
|
+
_DASH_RULE_ID = "dash.spaced"
|
|
91
|
+
_DASH_COMMA_RULE = (_DASH_RULE_ID, "dash", "Replaced a dash with a comma")
|
|
92
|
+
_DASH_SPACE_RULE = (_DASH_RULE_ID, "dash", "Replaced a spaced dash with a space")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _dash_rule_for(rep):
|
|
96
|
+
return _DASH_COMMA_RULE if rep[:1] == "," else _DASH_SPACE_RULE
|
|
97
|
+
|
|
98
|
+
|
|
89
99
|
_COLLAPSE_RULE = ("space.collapse", "space", "Collapsed repeated spaces")
|
|
90
100
|
_SPACE_BEFORE_RULE = ("punct.space-before", "space", "Removed a space before punctuation")
|
|
91
101
|
|
|
102
|
+
# What a matched dash run becomes. A clause dash reads as a pause, so the
|
|
103
|
+
# default replacement is a comma. The comma is withheld (single space instead,
|
|
104
|
+
# the old behavior, tidied by the collapse and space-before-punct passes) when
|
|
105
|
+
# a comma cannot sit there: at a text or line boundary, next to punctuation or
|
|
106
|
+
# a bracket it would double up against, next to a dash the match could not
|
|
107
|
+
# consume, or between digits, where the dash is a range rather than a pause.
|
|
108
|
+
# When the next character is whitespace the comma takes no trailing space, so
|
|
109
|
+
# it hugs the word before a line break. Mirrors dashReplacement in the JS
|
|
110
|
+
# engine.
|
|
111
|
+
_DASH_CP = frozenset((0x2014, 0x2015, 0x2013))
|
|
112
|
+
_NO_COMMA_BEFORE = frozenset(ord(c) for c in ",.;:!?([{")
|
|
113
|
+
_NO_COMMA_AFTER = frozenset(ord(c) for c in ",.;:!?)]}")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _cp_before(s, i):
|
|
117
|
+
return ord(s[i - 1]) if i > 0 else -1
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _cp_after(s, i):
|
|
121
|
+
return ord(s[i]) if i < len(s) else -1
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _dash_replacement(before_cp, after_cp):
|
|
125
|
+
blocked_before = (before_cp < 0 or before_cp in _WS
|
|
126
|
+
or before_cp in _NO_COMMA_BEFORE or before_cp in _DASH_CP)
|
|
127
|
+
blocked_after = after_cp < 0 or after_cp in _NO_COMMA_AFTER or after_cp in _DASH_CP
|
|
128
|
+
if blocked_before or blocked_after:
|
|
129
|
+
return " "
|
|
130
|
+
if 0x30 <= before_cp <= 0x39 and 0x30 <= after_cp <= 0x39:
|
|
131
|
+
return " "
|
|
132
|
+
return "," if after_cp in _WS else ", "
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _dash_match_replacement(m):
|
|
136
|
+
return _dash_replacement(_cp_before(m.string, m.start()), _cp_after(m.string, m.end()))
|
|
137
|
+
|
|
92
138
|
# Priority when several passes touch the same characters and their cells merge.
|
|
93
139
|
# Higher wins the label. Dash beats space cleanup beats invisible.
|
|
94
140
|
_RULE_PRIORITY = {
|
|
@@ -181,14 +227,22 @@ def _regex_pass(cells, regex, replacement, rule):
|
|
|
181
227
|
merged = cells[ci:cj]
|
|
182
228
|
src0 = merged[0].src0
|
|
183
229
|
src1 = merged[-1].src1
|
|
230
|
+
if callable(replacement):
|
|
231
|
+
rep = replacement(m)
|
|
232
|
+
elif "\\" in replacement:
|
|
233
|
+
rep = m.expand(replacement)
|
|
234
|
+
else:
|
|
235
|
+
rep = replacement
|
|
236
|
+
# `rule` may depend on the replacement chosen (the dash pass labels
|
|
237
|
+
# comma and space outcomes differently while keeping one ruleId).
|
|
238
|
+
base = rule(rep) if callable(rule) else rule
|
|
184
239
|
# if any merged cell already carried a higher-priority rule, keep it
|
|
185
|
-
best =
|
|
186
|
-
best_pri = _RULE_PRIORITY.get(
|
|
240
|
+
best = base
|
|
241
|
+
best_pri = _RULE_PRIORITY.get(base[0], 0)
|
|
187
242
|
for c in merged:
|
|
188
243
|
if c.rule and _RULE_PRIORITY.get(c.rule[0], 0) > best_pri:
|
|
189
244
|
best = c.rule
|
|
190
245
|
best_pri = _RULE_PRIORITY.get(c.rule[0], 0)
|
|
191
|
-
rep = m.expand(replacement) if "\\" in replacement else replacement
|
|
192
246
|
out.append(_Cell(rep, src0, src1, best))
|
|
193
247
|
last = cj
|
|
194
248
|
changed = True
|
|
@@ -247,17 +301,17 @@ def _clean_cells(text, disabled=frozenset()):
|
|
|
247
301
|
cells = _build_cells(text, disabled)
|
|
248
302
|
current = "".join(c.text for c in cells)
|
|
249
303
|
# Run the dash/space passes to a FIXED POINT, not once. A single pass is
|
|
250
|
-
# not idempotent:
|
|
251
|
-
# the spaced-en-dash rule ("X—– Y" -> "X – Y", and only
|
|
252
|
-
#
|
|
253
|
-
# contract. Each enabled dash pass strictly
|
|
254
|
-
#
|
|
255
|
-
# Mirrors cleanCells() in the JS engine
|
|
304
|
+
# not idempotent: replacing an em dash can manufacture the spacing that
|
|
305
|
+
# arms the spaced-en-dash rule ("X—– Y" -> "X – Y", and only the next
|
|
306
|
+
# iteration reaches "X, Y"), which would break the documented
|
|
307
|
+
# clean(clean(x)) == clean(x) contract. Each enabled dash pass strictly
|
|
308
|
+
# reduces the dash count, so this terminates; the equality check breaks
|
|
309
|
+
# when the dash rule is disabled. Mirrors cleanCells() in the JS engine.
|
|
256
310
|
for _guard in range(8):
|
|
257
311
|
if not _EM_DASH.search(current):
|
|
258
312
|
break
|
|
259
|
-
if
|
|
260
|
-
cells, _ = _regex_pass(cells, _EM_DASH,
|
|
313
|
+
if _DASH_RULE_ID not in disabled:
|
|
314
|
+
cells, _ = _regex_pass(cells, _EM_DASH, _dash_match_replacement, _dash_rule_for)
|
|
261
315
|
if _COLLAPSE_RULE[0] not in disabled:
|
|
262
316
|
cells, _ = _regex_pass(cells, _MULTI_SPACE, " ", _COLLAPSE_RULE)
|
|
263
317
|
if _SPACE_BEFORE_RULE[0] not in disabled:
|
|
@@ -281,12 +335,12 @@ def rebuild_text(text):
|
|
|
281
335
|
|
|
282
336
|
|
|
283
337
|
def remove_em_dashes(text):
|
|
284
|
-
"""Replace
|
|
285
|
-
|
|
338
|
+
"""Replace clause dashes with a comma where one fits, else a space.
|
|
339
|
+
Returns (new_text, count); count is the number of matched dash groups."""
|
|
286
340
|
count = len(_EM_DASH.findall(text))
|
|
287
341
|
if not count:
|
|
288
342
|
return text, 0
|
|
289
|
-
new = _EM_DASH.sub(
|
|
343
|
+
new = _EM_DASH.sub(_dash_match_replacement, text)
|
|
290
344
|
new = _MULTI_SPACE.sub(" ", new)
|
|
291
345
|
new = _SPACE_BEFORE_PUNCT.sub(r"\1", new)
|
|
292
346
|
return new, count
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fetchit-engine"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Deterministic text cleanup and AI-writing heuristics. Runs entirely in your process; text never leaves it."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|