farsflow 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.
- farsflow-0.1.0/LICENSE +21 -0
- farsflow-0.1.0/PKG-INFO +126 -0
- farsflow-0.1.0/README.md +96 -0
- farsflow-0.1.0/pyproject.toml +82 -0
- farsflow-0.1.0/setup.cfg +4 -0
- farsflow-0.1.0/src/farsflow/__init__.py +25 -0
- farsflow-0.1.0/src/farsflow/__version__.py +1 -0
- farsflow-0.1.0/src/farsflow/pipeline/__init__.py +0 -0
- farsflow-0.1.0/src/farsflow/pipeline/engine.py +54 -0
- farsflow-0.1.0/src/farsflow/processors/__init__.py +0 -0
- farsflow-0.1.0/src/farsflow/processors/joiner_fixer.py +86 -0
- farsflow-0.1.0/src/farsflow/processors/normalizer.py +62 -0
- farsflow-0.1.0/src/farsflow/processors/space_cleaner.py +54 -0
- farsflow-0.1.0/src/farsflow/profiles/__init__.py +0 -0
- farsflow-0.1.0/src/farsflow.egg-info/PKG-INFO +126 -0
- farsflow-0.1.0/src/farsflow.egg-info/SOURCES.txt +17 -0
- farsflow-0.1.0/src/farsflow.egg-info/dependency_links.txt +1 -0
- farsflow-0.1.0/src/farsflow.egg-info/requires.txt +3 -0
- farsflow-0.1.0/src/farsflow.egg-info/top_level.txt +1 -0
farsflow-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mahdi Hosseini
|
|
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.
|
farsflow-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: farsflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast, modern, and modular Persian text preprocessing library.
|
|
5
|
+
Author-email: Mahdi Hosseini <mhossza@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mhhoss/farsflow
|
|
8
|
+
Project-URL: Documentation, https://github.com/mhhoss/farsflow
|
|
9
|
+
Project-URL: Source, https://github.com/mhhoss/farsflow
|
|
10
|
+
Project-URL: Issues, https://github.com/mhhoss/farsflow/issues
|
|
11
|
+
Keywords: farsi,Persian,preprocessing,nlp,text-cleaning,normalization,tokenization,machine-learning,llm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
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
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# farsflow
|
|
32
|
+
|
|
33
|
+
farsflow is a small library for **Persian text preprocessing**, designed for practical NLP and LLM workflows.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Features
|
|
38
|
+
|
|
39
|
+
- Deterministic pipeline (same input -> same output)
|
|
40
|
+
- Safe character normalization
|
|
41
|
+
- Joiner (ZWNJ) fixes
|
|
42
|
+
- Whitespace & punctuation cleanup
|
|
43
|
+
- Modular processors (use only the components you need)
|
|
44
|
+
- Real‑world style sample tests
|
|
45
|
+
- Zero dependencies
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📦 Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install farsflow
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## ✨ Quick Start
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import farsflow as ff
|
|
59
|
+
|
|
60
|
+
text = "سلام دنیا! این یك تست است که می نویسم ۴۵۶"
|
|
61
|
+
cleaned = ff.clean(text)
|
|
62
|
+
print(cleaned)
|
|
63
|
+
```
|
|
64
|
+
Expected output: سلام دنیا! این یک تست است که مینویسم 456
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🧩 Pipeline Components
|
|
69
|
+
|
|
70
|
+
farsflow ships with a set of modular, composable components:
|
|
71
|
+
|
|
72
|
+
- **Normalizer** — safe character normalization
|
|
73
|
+
- **JoinerFixer** — fixes ZWNJ usage without over-correction
|
|
74
|
+
- **SpaceCleaner** — trims redundant whitespace and punctuation spacing
|
|
75
|
+
- **Pipeline** — orchestrates components in a deterministic order
|
|
76
|
+
|
|
77
|
+
You can customize the pipeline:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from farsflow import Pipeline, Normalizer, JoinerFixer
|
|
81
|
+
|
|
82
|
+
pipeline = Pipeline(
|
|
83
|
+
Normalizer(),
|
|
84
|
+
JoinerFixer(),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
text = "متن تستی"
|
|
88
|
+
pipeline(text)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
🧪 Testing
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pytest
|
|
97
|
+
# or:
|
|
98
|
+
pytest path/to/test_file.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
🗺 Roadmap (v0.2.0)
|
|
104
|
+
|
|
105
|
+
- [ ] formalize the behavior of "ff.clean" as a safe and deterministic baseline
|
|
106
|
+
- [ ] add optional normalization controls (e.g. digit normalization)
|
|
107
|
+
- [ ] add optional noise-cleaning emoji and url processors
|
|
108
|
+
- [ ] introduce simple profiles (ff.llm.clean, ff.embedding.query, ff.embedding.index) built on top of the same core
|
|
109
|
+
- [ ] expand real-world test cases to ensure stable behavior across informal, mixed, and noisy Persian text
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
📄 License
|
|
114
|
+
|
|
115
|
+
MIT License — see [LICENSE](LICENSE).
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
🤝 Contributing
|
|
120
|
+
|
|
121
|
+
Contributions are welcome.
|
|
122
|
+
Please open an issue or submit a pull request on GitHub.
|
|
123
|
+
|
|
124
|
+
📝 Changelog
|
|
125
|
+
|
|
126
|
+
See [CHANGELOG](CHANGELOG) for version history.
|
farsflow-0.1.0/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# farsflow
|
|
2
|
+
|
|
3
|
+
farsflow is a small library for **Persian text preprocessing**, designed for practical NLP and LLM workflows.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Features
|
|
8
|
+
|
|
9
|
+
- Deterministic pipeline (same input -> same output)
|
|
10
|
+
- Safe character normalization
|
|
11
|
+
- Joiner (ZWNJ) fixes
|
|
12
|
+
- Whitespace & punctuation cleanup
|
|
13
|
+
- Modular processors (use only the components you need)
|
|
14
|
+
- Real‑world style sample tests
|
|
15
|
+
- Zero dependencies
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install farsflow
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## ✨ Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import farsflow as ff
|
|
29
|
+
|
|
30
|
+
text = "سلام دنیا! این یك تست است که می نویسم ۴۵۶"
|
|
31
|
+
cleaned = ff.clean(text)
|
|
32
|
+
print(cleaned)
|
|
33
|
+
```
|
|
34
|
+
Expected output: سلام دنیا! این یک تست است که مینویسم 456
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🧩 Pipeline Components
|
|
39
|
+
|
|
40
|
+
farsflow ships with a set of modular, composable components:
|
|
41
|
+
|
|
42
|
+
- **Normalizer** — safe character normalization
|
|
43
|
+
- **JoinerFixer** — fixes ZWNJ usage without over-correction
|
|
44
|
+
- **SpaceCleaner** — trims redundant whitespace and punctuation spacing
|
|
45
|
+
- **Pipeline** — orchestrates components in a deterministic order
|
|
46
|
+
|
|
47
|
+
You can customize the pipeline:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from farsflow import Pipeline, Normalizer, JoinerFixer
|
|
51
|
+
|
|
52
|
+
pipeline = Pipeline(
|
|
53
|
+
Normalizer(),
|
|
54
|
+
JoinerFixer(),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
text = "متن تستی"
|
|
58
|
+
pipeline(text)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
🧪 Testing
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pytest
|
|
67
|
+
# or:
|
|
68
|
+
pytest path/to/test_file.py
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
🗺 Roadmap (v0.2.0)
|
|
74
|
+
|
|
75
|
+
- [ ] formalize the behavior of "ff.clean" as a safe and deterministic baseline
|
|
76
|
+
- [ ] add optional normalization controls (e.g. digit normalization)
|
|
77
|
+
- [ ] add optional noise-cleaning emoji and url processors
|
|
78
|
+
- [ ] introduce simple profiles (ff.llm.clean, ff.embedding.query, ff.embedding.index) built on top of the same core
|
|
79
|
+
- [ ] expand real-world test cases to ensure stable behavior across informal, mixed, and noisy Persian text
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
📄 License
|
|
84
|
+
|
|
85
|
+
MIT License — see [LICENSE](LICENSE).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
🤝 Contributing
|
|
90
|
+
|
|
91
|
+
Contributions are welcome.
|
|
92
|
+
Please open an issue or submit a pull request on GitHub.
|
|
93
|
+
|
|
94
|
+
📝 Changelog
|
|
95
|
+
|
|
96
|
+
See [CHANGELOG](CHANGELOG) for version history.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "farsflow"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Fast, modern, and modular Persian text preprocessing library."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Mahdi Hosseini", email = "mhossza@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
keywords = [
|
|
17
|
+
"farsi",
|
|
18
|
+
"Persian",
|
|
19
|
+
"preprocessing",
|
|
20
|
+
"nlp",
|
|
21
|
+
"text-cleaning",
|
|
22
|
+
"normalization",
|
|
23
|
+
"tokenization",
|
|
24
|
+
"machine-learning",
|
|
25
|
+
"llm",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 3 - Alpha",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Programming Language :: Python",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3.8",
|
|
34
|
+
"Programming Language :: Python :: 3.9",
|
|
35
|
+
"Programming Language :: Python :: 3.10",
|
|
36
|
+
"Programming Language :: Python :: 3.11",
|
|
37
|
+
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Programming Language :: Python :: 3.13",
|
|
39
|
+
"Topic :: Text Processing :: Linguistic",
|
|
40
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
dependencies = [
|
|
44
|
+
# intentionally empty - farsflow is dependency-free
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/mhhoss/farsflow"
|
|
49
|
+
Documentation = "https://github.com/mhhoss/farsflow"
|
|
50
|
+
Source = "https://github.com/mhhoss/farsflow"
|
|
51
|
+
Issues = "https://github.com/mhhoss/farsflow/issues"
|
|
52
|
+
|
|
53
|
+
[tool.setuptools]
|
|
54
|
+
package-dir = {"" = "src"}
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
|
58
|
+
include = ["farsflow*"]
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.package-data]
|
|
61
|
+
"farsflow" = ["data/*.txt", "data/*.json"]
|
|
62
|
+
|
|
63
|
+
[tool.black]
|
|
64
|
+
line-length = 88
|
|
65
|
+
target-version = ["py38"]
|
|
66
|
+
|
|
67
|
+
[tool.isort]
|
|
68
|
+
profile = "black"
|
|
69
|
+
|
|
70
|
+
[tool.pytest.ini_options]
|
|
71
|
+
testpaths = ["tests"]
|
|
72
|
+
|
|
73
|
+
[dependency-groups]
|
|
74
|
+
dev = [
|
|
75
|
+
"build>=1.2.2.post1",
|
|
76
|
+
"twine>=6.1.0",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[project.optional-dependencies]
|
|
80
|
+
dev = [
|
|
81
|
+
"pytest>=8.3.5",
|
|
82
|
+
]
|
farsflow-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .pipeline.engine import Pipeline, create_pipeline
|
|
2
|
+
from .processors.normalizer import NormalizerProcessor as Normalizer
|
|
3
|
+
from .processors.joiner_fixer import JoinerFixerProcessor as JoinerFixer
|
|
4
|
+
from .processors.space_cleaner import SpaceCleanerProcessor as SpaceCleaner
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def clean(text: str) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Run the default farsflow pipeline on the given text.
|
|
10
|
+
|
|
11
|
+
This is a convenience shortcut for simple use cases.
|
|
12
|
+
For more control, use `create_pipeline()` or `Pipeline` directly.
|
|
13
|
+
"""
|
|
14
|
+
pipeline = create_pipeline()
|
|
15
|
+
return pipeline.run(text)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Pipeline",
|
|
20
|
+
"create_pipeline",
|
|
21
|
+
"Normalizer",
|
|
22
|
+
"JoinerFixer",
|
|
23
|
+
"SpaceCleaner",
|
|
24
|
+
"clean",
|
|
25
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import List, Protocol
|
|
2
|
+
|
|
3
|
+
from farsflow.processors.normalizer import NormalizerProcessor
|
|
4
|
+
from farsflow.processors.joiner_fixer import JoinerFixerProcessor
|
|
5
|
+
from farsflow.processors.space_cleaner import SpaceCleanerProcessor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Processor(Protocol):
|
|
9
|
+
"""Interface for all processors in farsflow."""
|
|
10
|
+
def process(self, text: str) -> str:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Pipeline:
|
|
15
|
+
"""
|
|
16
|
+
A simple, modular pipeline engine for farsflow.
|
|
17
|
+
Executes a sequence of processors on input text.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, processors: List[Processor]):
|
|
21
|
+
self.processors = processors
|
|
22
|
+
|
|
23
|
+
def run(self, text: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Run the pipeline on the given text.
|
|
26
|
+
Each processor receives the output of the previous one.
|
|
27
|
+
"""
|
|
28
|
+
for processor in self.processors:
|
|
29
|
+
text = processor.process(text)
|
|
30
|
+
return text
|
|
31
|
+
|
|
32
|
+
def __call__(self, text: str) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Allow calling the pipeline instance directly: pipeline(text).
|
|
35
|
+
"""
|
|
36
|
+
return self.run(text)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_pipeline(processors: List[Processor] = None) -> Pipeline:
|
|
40
|
+
"""
|
|
41
|
+
Creates a farsflow text-processing pipeline.
|
|
42
|
+
|
|
43
|
+
If no processors are provided, the default architecture is used:
|
|
44
|
+
Normalizer -> JoinerFixer -> SpaceCleaner
|
|
45
|
+
"""
|
|
46
|
+
if processors is None:
|
|
47
|
+
processors = [
|
|
48
|
+
NormalizerProcessor(),
|
|
49
|
+
JoinerFixerProcessor(),
|
|
50
|
+
SpaceCleanerProcessor(),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
return Pipeline(processors)
|
|
54
|
+
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
ZWNJ = "\u200c"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JoinerFixerProcessor:
|
|
7
|
+
"""
|
|
8
|
+
Restores correct Persian half-spaces (ZWNJ) based on safe, rule-based patterns.
|
|
9
|
+
|
|
10
|
+
- Only applies patterns that are highly reliable
|
|
11
|
+
- Does NOT try to be "smart" or guess semantics
|
|
12
|
+
- Keeps behavior deterministic and idempotent
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def process(self, text: str) -> str:
|
|
16
|
+
# 0) Cleanup existing joiners/spaces to get a stable base
|
|
17
|
+
text = self._cleanup_wrong_joiners(text)
|
|
18
|
+
|
|
19
|
+
# 1) Prefix-based patterns (می / نمی)
|
|
20
|
+
text = self._apply_prefix_rules(text)
|
|
21
|
+
|
|
22
|
+
# 2) Suffix-based patterns (ها، فعل+ام/ای/اند)
|
|
23
|
+
text = self._apply_suffix_rules(text)
|
|
24
|
+
|
|
25
|
+
# 3) Verb conjugation patterns (رفتهام، گفتهای، دیدهاند)
|
|
26
|
+
text = self._apply_verb_conjugation_rules(text)
|
|
27
|
+
|
|
28
|
+
# 4) Common, high-confidence compounds
|
|
29
|
+
text = self._apply_common_compounds(text)
|
|
30
|
+
|
|
31
|
+
# 5) Final cleanup (in case new patterns introduced extra spaces)
|
|
32
|
+
text = self._cleanup_wrong_joiners(text)
|
|
33
|
+
|
|
34
|
+
return text
|
|
35
|
+
|
|
36
|
+
def _apply_prefix_rules(self, text: str) -> str:
|
|
37
|
+
# می + فاصله + فعل/کلمه فارسی -> میفعل
|
|
38
|
+
text = re.sub(r"\bمی\s+(?=[آ-ی])", "می" + ZWNJ, text)
|
|
39
|
+
|
|
40
|
+
# نمی + فاصله + فعل/کلمه فارسی -> نمیفعل
|
|
41
|
+
text = re.sub(r"\bنمی\s+(?=[آ-ی])", "نمی" + ZWNJ, text)
|
|
42
|
+
|
|
43
|
+
return text
|
|
44
|
+
|
|
45
|
+
def _apply_suffix_rules(self, text: str) -> str:
|
|
46
|
+
# جمع "ها" — فقط اگر قبلش حرف/عدد باشد
|
|
47
|
+
text = re.sub(r"([آ-یA-Za-z0-9])\s+ها\b", r"\1" + ZWNJ + "ها", text)
|
|
48
|
+
|
|
49
|
+
# فعل/اسمِ ختمشده به «ه» + ام/ای/اند → هام/های/هاند
|
|
50
|
+
# (ضمیر ملکی عمومی مثل "ایران ام" را دستکاری نمیکنیم)
|
|
51
|
+
text = re.sub(r"([آ-ی]{2,}ه)\s+ام\b", r"\1" + ZWNJ + "ام", text)
|
|
52
|
+
text = re.sub(r"([آ-ی]{2,}ه)\s+ای\b", r"\1" + ZWNJ + "ای", text)
|
|
53
|
+
text = re.sub(r"([آ-ی]{2,}ه)\s+اند\b", r"\1" + ZWNJ + "اند", text)
|
|
54
|
+
|
|
55
|
+
return text
|
|
56
|
+
|
|
57
|
+
def _apply_verb_conjugation_rules(self, text: str) -> str:
|
|
58
|
+
# Note: این تابع فعلاً روی همان الگوهای بالا تکیه دارد؛
|
|
59
|
+
# اگر در آینده الگوهای بیشتری اضافه شود، اینجا گسترش مییابد.
|
|
60
|
+
return text
|
|
61
|
+
|
|
62
|
+
def _apply_common_compounds(self, text: str) -> str:
|
|
63
|
+
|
|
64
|
+
# به کارگیری -> بهکارگیری
|
|
65
|
+
text = re.sub(r"\bبه\s+کارگیری\b", "به" + ZWNJ + "کارگیری", text)
|
|
66
|
+
|
|
67
|
+
# دست خط -> دستخط
|
|
68
|
+
text = re.sub(r"\bدست\s+خط\b", "دست" + ZWNJ + "خط", text)
|
|
69
|
+
|
|
70
|
+
# Note: Only includes a few high‑confidence compounds in v0.1.0.
|
|
71
|
+
# This list will expand gradually in future versions.
|
|
72
|
+
|
|
73
|
+
return text
|
|
74
|
+
|
|
75
|
+
def _cleanup_wrong_joiners(self, text: str) -> str:
|
|
76
|
+
# ۱) ZWNJهای تکراری -> یک ZWNJ
|
|
77
|
+
text = re.sub(ZWNJ + r"{2,}", ZWNJ, text)
|
|
78
|
+
|
|
79
|
+
# ۲) حذف فاصله بعد از ZWNJ
|
|
80
|
+
text = re.sub(ZWNJ + r"\s+", ZWNJ, text)
|
|
81
|
+
|
|
82
|
+
# ۳) حذف فاصله قبل از ZWNJ
|
|
83
|
+
text = re.sub(r"\s+" + ZWNJ, ZWNJ, text)
|
|
84
|
+
|
|
85
|
+
return text
|
|
86
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class NormalizerProcessor:
|
|
2
|
+
"""
|
|
3
|
+
Architecture-level Persian normalizer for farsflow.
|
|
4
|
+
|
|
5
|
+
- Minimal, deterministic, and semantic-safe
|
|
6
|
+
- Converts Arabic letters to Persian equivalents
|
|
7
|
+
- Removes unwanted invisible/control characters
|
|
8
|
+
- Optionally normalizes Persian/Arabic digits to ASCII digits
|
|
9
|
+
- Does NOT touch spacing, punctuation, or ZWNJ
|
|
10
|
+
- Suitable as a shared layer for ML/LLM/RAG/Embedding
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
CHAR_MAP = str.maketrans({
|
|
14
|
+
"ي": "ی",
|
|
15
|
+
"ى": "ی",
|
|
16
|
+
"ك": "ک",
|
|
17
|
+
"ؤ": "و",
|
|
18
|
+
"أ": "ا",
|
|
19
|
+
"إ": "ا",
|
|
20
|
+
"ة": "ه",
|
|
21
|
+
"ۀ": "ه",
|
|
22
|
+
# Note: "آ "هرگز تغییر نمیکند
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
# Remove or normalize only harmful/invisible characters
|
|
26
|
+
INVISIBLE_MAP = str.maketrans({
|
|
27
|
+
"\u200d": "", # ZWJ
|
|
28
|
+
"\u200e": "", # LRM
|
|
29
|
+
"\u200f": "", # RLM
|
|
30
|
+
"\ufeff": "", # BOM
|
|
31
|
+
"\u200b": "", # zero-width space
|
|
32
|
+
"\u2060": "", # word joiner
|
|
33
|
+
"\u00a0": " ", # NBSP → normal space
|
|
34
|
+
# Note: ZWNJ (\u200c) حذف یا تبدیل نمیشود
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
# Persian + Arabic digits -> ASCII digits
|
|
38
|
+
DIGIT_MAP = str.maketrans({
|
|
39
|
+
"۰": "0", "۱": "1", "۲": "2", "۳": "3", "۴": "4",
|
|
40
|
+
"۵": "5", "۶": "6", "۷": "7", "۸": "8", "۹": "9",
|
|
41
|
+
"٠": "0", "١": "1", "٢": "2", "٣": "3", "٤": "4",
|
|
42
|
+
"٥": "5", "٦": "6", "٧": "7", "٨": "8", "٩": "9",
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
def __init__(self, normalize_digits: bool = True) -> None:
|
|
46
|
+
self.normalize_digits = normalize_digits
|
|
47
|
+
|
|
48
|
+
def process(self, text: str) -> str:
|
|
49
|
+
# 1) Arabic -> Persian letters
|
|
50
|
+
text = text.translate(self.CHAR_MAP)
|
|
51
|
+
|
|
52
|
+
# 2) Remove/normalize invisible/control characters (NOT ZWNJ)
|
|
53
|
+
text = text.translate(self.INVISIBLE_MAP)
|
|
54
|
+
|
|
55
|
+
# 3) Digit normalization (optional)
|
|
56
|
+
if self.normalize_digits:
|
|
57
|
+
text = text.translate(self.DIGIT_MAP)
|
|
58
|
+
|
|
59
|
+
# 4) SpaceCleanerProcessor handles spacing
|
|
60
|
+
# 5) JoinerFixerProcessor handles ZWNJ logic
|
|
61
|
+
|
|
62
|
+
return text
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SpaceCleanerProcessor:
|
|
5
|
+
"""
|
|
6
|
+
Cleans and standardizes spacing and punctuation for Persian text.
|
|
7
|
+
- Normalizes spaces
|
|
8
|
+
- Fixes spacing around punctuation
|
|
9
|
+
- Converts English punctuation to Persian equivalents
|
|
10
|
+
- Handles parentheses spacing safely
|
|
11
|
+
- Does NOT touch ZWNJ or semantic structure
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
PUNCT_MAP = str.maketrans({
|
|
15
|
+
",": "،",
|
|
16
|
+
";": "؛",
|
|
17
|
+
"?": "؟",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
# Persian punctuation that must NOT have space before them
|
|
21
|
+
NO_SPACE_BEFORE = r"[،؛:؟!.,]"
|
|
22
|
+
|
|
23
|
+
# Punctuation that must have exactly one space after them
|
|
24
|
+
NEED_SPACE_AFTER = r"[،؛:؟!()]"
|
|
25
|
+
|
|
26
|
+
def process(self, text: str) -> str:
|
|
27
|
+
|
|
28
|
+
# 1) Convert English punctuation to Persian equivalents
|
|
29
|
+
text = text.translate(self.PUNCT_MAP)
|
|
30
|
+
|
|
31
|
+
# 2) Remove spaces before punctuation
|
|
32
|
+
text = re.sub(r"\s+(?=" + self.NO_SPACE_BEFORE + ")", "", text)
|
|
33
|
+
|
|
34
|
+
# 3) Ensure exactly one space after punctuation
|
|
35
|
+
text = re.sub(r"(" + self.NEED_SPACE_AFTER + r")(?=\S)", r"\1 ", text)
|
|
36
|
+
|
|
37
|
+
# 4) Fix spacing inside parentheses
|
|
38
|
+
text = re.sub(r"\(\s+", "(", text)
|
|
39
|
+
text = re.sub(r"\s+\)", ")", text)
|
|
40
|
+
|
|
41
|
+
# 5) Add missing space before "("
|
|
42
|
+
text = re.sub(r"([^\s(])\(", r"\1 (", text)
|
|
43
|
+
|
|
44
|
+
# 6) Add missing space after ")"
|
|
45
|
+
text = re.sub(r"\)([^\s)])", r") \1", text)
|
|
46
|
+
|
|
47
|
+
# 7) Remove space before punctuation after ")"
|
|
48
|
+
text = re.sub(r"\)\s+(?=" + self.NO_SPACE_BEFORE + ")", r")", text)
|
|
49
|
+
|
|
50
|
+
# 8) Collapse multiple spaces
|
|
51
|
+
text = " ".join(text.split())
|
|
52
|
+
|
|
53
|
+
return text
|
|
54
|
+
|
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: farsflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast, modern, and modular Persian text preprocessing library.
|
|
5
|
+
Author-email: Mahdi Hosseini <mhossza@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mhhoss/farsflow
|
|
8
|
+
Project-URL: Documentation, https://github.com/mhhoss/farsflow
|
|
9
|
+
Project-URL: Source, https://github.com/mhhoss/farsflow
|
|
10
|
+
Project-URL: Issues, https://github.com/mhhoss/farsflow/issues
|
|
11
|
+
Keywords: farsi,Persian,preprocessing,nlp,text-cleaning,normalization,tokenization,machine-learning,llm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
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
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# farsflow
|
|
32
|
+
|
|
33
|
+
farsflow is a small library for **Persian text preprocessing**, designed for practical NLP and LLM workflows.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Features
|
|
38
|
+
|
|
39
|
+
- Deterministic pipeline (same input -> same output)
|
|
40
|
+
- Safe character normalization
|
|
41
|
+
- Joiner (ZWNJ) fixes
|
|
42
|
+
- Whitespace & punctuation cleanup
|
|
43
|
+
- Modular processors (use only the components you need)
|
|
44
|
+
- Real‑world style sample tests
|
|
45
|
+
- Zero dependencies
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📦 Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install farsflow
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## ✨ Quick Start
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import farsflow as ff
|
|
59
|
+
|
|
60
|
+
text = "سلام دنیا! این یك تست است که می نویسم ۴۵۶"
|
|
61
|
+
cleaned = ff.clean(text)
|
|
62
|
+
print(cleaned)
|
|
63
|
+
```
|
|
64
|
+
Expected output: سلام دنیا! این یک تست است که مینویسم 456
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🧩 Pipeline Components
|
|
69
|
+
|
|
70
|
+
farsflow ships with a set of modular, composable components:
|
|
71
|
+
|
|
72
|
+
- **Normalizer** — safe character normalization
|
|
73
|
+
- **JoinerFixer** — fixes ZWNJ usage without over-correction
|
|
74
|
+
- **SpaceCleaner** — trims redundant whitespace and punctuation spacing
|
|
75
|
+
- **Pipeline** — orchestrates components in a deterministic order
|
|
76
|
+
|
|
77
|
+
You can customize the pipeline:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from farsflow import Pipeline, Normalizer, JoinerFixer
|
|
81
|
+
|
|
82
|
+
pipeline = Pipeline(
|
|
83
|
+
Normalizer(),
|
|
84
|
+
JoinerFixer(),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
text = "متن تستی"
|
|
88
|
+
pipeline(text)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
🧪 Testing
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pytest
|
|
97
|
+
# or:
|
|
98
|
+
pytest path/to/test_file.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
🗺 Roadmap (v0.2.0)
|
|
104
|
+
|
|
105
|
+
- [ ] formalize the behavior of "ff.clean" as a safe and deterministic baseline
|
|
106
|
+
- [ ] add optional normalization controls (e.g. digit normalization)
|
|
107
|
+
- [ ] add optional noise-cleaning emoji and url processors
|
|
108
|
+
- [ ] introduce simple profiles (ff.llm.clean, ff.embedding.query, ff.embedding.index) built on top of the same core
|
|
109
|
+
- [ ] expand real-world test cases to ensure stable behavior across informal, mixed, and noisy Persian text
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
📄 License
|
|
114
|
+
|
|
115
|
+
MIT License — see [LICENSE](LICENSE).
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
🤝 Contributing
|
|
120
|
+
|
|
121
|
+
Contributions are welcome.
|
|
122
|
+
Please open an issue or submit a pull request on GitHub.
|
|
123
|
+
|
|
124
|
+
📝 Changelog
|
|
125
|
+
|
|
126
|
+
See [CHANGELOG](CHANGELOG) for version history.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/farsflow/__init__.py
|
|
5
|
+
src/farsflow/__version__.py
|
|
6
|
+
src/farsflow.egg-info/PKG-INFO
|
|
7
|
+
src/farsflow.egg-info/SOURCES.txt
|
|
8
|
+
src/farsflow.egg-info/dependency_links.txt
|
|
9
|
+
src/farsflow.egg-info/requires.txt
|
|
10
|
+
src/farsflow.egg-info/top_level.txt
|
|
11
|
+
src/farsflow/pipeline/__init__.py
|
|
12
|
+
src/farsflow/pipeline/engine.py
|
|
13
|
+
src/farsflow/processors/__init__.py
|
|
14
|
+
src/farsflow/processors/joiner_fixer.py
|
|
15
|
+
src/farsflow/processors/normalizer.py
|
|
16
|
+
src/farsflow/processors/space_cleaner.py
|
|
17
|
+
src/farsflow/profiles/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
farsflow
|