parsikit 2.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.
- parsikit-2.1.0/PKG-INFO +153 -0
- parsikit-2.1.0/README.md +136 -0
- parsikit-2.1.0/parsikit/__init__.py +58 -0
- parsikit-2.1.0/parsikit/currency.py +128 -0
- parsikit-2.1.0/parsikit/number.py +133 -0
- parsikit-2.1.0/parsikit/reshaper.py +191 -0
- parsikit-2.1.0/parsikit/text.py +94 -0
- parsikit-2.1.0/parsikit/validators.py +148 -0
- parsikit-2.1.0/parsikit.egg-info/PKG-INFO +153 -0
- parsikit-2.1.0/parsikit.egg-info/SOURCES.txt +13 -0
- parsikit-2.1.0/parsikit.egg-info/dependency_links.txt +1 -0
- parsikit-2.1.0/parsikit.egg-info/top_level.txt +1 -0
- parsikit-2.1.0/pyproject.toml +25 -0
- parsikit-2.1.0/setup.cfg +4 -0
- parsikit-2.1.0/setup.py +23 -0
parsikit-2.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parsikit
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: A comprehensive and pure Python library for Persian data formatting, validation, and graphical reshaping.
|
|
5
|
+
Author: Ali Kamrani
|
|
6
|
+
Author-email: Ali Kamrani <kamrani.exe@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Natural Language :: Persian
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: requires-python
|
|
17
|
+
|
|
18
|
+
# ParsiKit
|
|
19
|
+
|
|
20
|
+
A lightweight, zero-dependency, pure Python library designed for Persian text standardization, layout reshaping for legacy graphics engines, financial calculations, and Iranian standard format validations.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Key Features
|
|
25
|
+
|
|
26
|
+
- **Text Normalization & Correction**: Character mapping (Arabic `ي`/`ك` to Persian `ی`/`ک`), robust Zero-Width Non-Joiner (ZWNJ / نیمفاصله) formatting for verbs and suffixes, diacritics stripping, and English-to-Persian keyboard layout correction.
|
|
27
|
+
- **Digit & Number Conversions**: Convert digits between English, Persian, and Arabic formats, and translate numbers up to quadrillions into written Persian words.
|
|
28
|
+
- **Currency & Financial Utilities**: Thousand-separator formatting, Rial and Toman conversions, VAT calculator, loan monthly installment calculator, and verbal currency representations.
|
|
29
|
+
- **Iranian Standards Validators**: Real-time validation and clean formatting for National Codes, Mobile Numbers, Bank Cards (with Luhn checksum validation), and Sheba (IBAN) codes.
|
|
30
|
+
- **Graphical Text Reshaper**: A zero-dependency Persian reshaper that connects letters and handles RTL bidirectional flow for rendering text on Left-to-Right graphics engines (such as PIL/Pillow, Unity, Pygame, OpenCV, Matplotlib, or After Effects).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
You can install the package directly from source:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/MRThugh/ParsiKit.git
|
|
40
|
+
cd ParsiKit
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start & Examples
|
|
47
|
+
|
|
48
|
+
### 1. Text Normalization & Keyboard Layout Correction
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import parsikit
|
|
52
|
+
|
|
53
|
+
# Standardize Persian characters and ZWNJ formatting
|
|
54
|
+
raw_text = "ي كافيه ك کتاب ها ميباشد سَلامٌ"
|
|
55
|
+
standardized = parsikit.standardize_persian(raw_text)
|
|
56
|
+
print(standardized) # "ی کافیه ک کتابها میباشد سلام"
|
|
57
|
+
|
|
58
|
+
# Strip diacritics (Harakat)
|
|
59
|
+
clean_text = parsikit.strip_diacritics("سَلامٌ")
|
|
60
|
+
print(clean_text) # "سلام"
|
|
61
|
+
|
|
62
|
+
# Correct mistyped English layout text (e.g., typing standard words on wrong layout)
|
|
63
|
+
corrected = parsikit.correct_keyboard_layout("sghl")
|
|
64
|
+
print(corrected) # "سلام"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Digit Conversion & Verbal Number Representation
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import parsikit
|
|
71
|
+
|
|
72
|
+
# Convert digits
|
|
73
|
+
fa_digits = parsikit.english_to_persian("Price: 12500 Toman")
|
|
74
|
+
print(fa_digits) # "Price: ۱۲۵۰۰ Toman"
|
|
75
|
+
|
|
76
|
+
# Convert numeric values to written Persian words
|
|
77
|
+
words = parsikit.number_to_words(1453200)
|
|
78
|
+
print(words) # "یک میلیون و چهارصد و پنجاه و سه هزار و دویست"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Currency & Loan Installment Planning
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import parsikit
|
|
85
|
+
|
|
86
|
+
# Format with thousands separators
|
|
87
|
+
formatted = parsikit.format_currency("1500000", currency="toman", persian_digits=True)
|
|
88
|
+
print(formatted) # "۱،۵۰۰،۰۰۰ تومان"
|
|
89
|
+
|
|
90
|
+
# Convert currency directly into words
|
|
91
|
+
words = parsikit.format_currency_to_words(1000000, currency="toman")
|
|
92
|
+
print(words) # "یک میلیون تومان"
|
|
93
|
+
|
|
94
|
+
# Add Value Added Tax (VAT - Default is 10%)
|
|
95
|
+
total_with_tax = parsikit.add_tax_and_toll(100000)
|
|
96
|
+
print(total_with_tax) # 110000
|
|
97
|
+
|
|
98
|
+
# Calculate monthly loan installments (e.g., 10M loan at 18% interest over 12 months)
|
|
99
|
+
monthly_payment = parsikit.calculate_installments(10000000, annual_interest_rate=18.0, months=12)
|
|
100
|
+
print(monthly_payment) # 916799
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 4. Identity & Banking Validators
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import parsikit
|
|
107
|
+
|
|
108
|
+
# National Code Checksum Validation & Formatting
|
|
109
|
+
if parsikit.is_valid_national_code("7730123452"):
|
|
110
|
+
formatted_nc = parsikit.format_national_code("7730123452")
|
|
111
|
+
print(formatted_nc) # "773-012345-2"
|
|
112
|
+
|
|
113
|
+
# Mobile Normalization (Standardizes input with various country code formats)
|
|
114
|
+
if parsikit.is_valid_mobile("+989123456789"):
|
|
115
|
+
local_mobile = parsikit.normalize_mobile("+989123456789", prefix="0")
|
|
116
|
+
print(local_mobile) # "09123456789"
|
|
117
|
+
|
|
118
|
+
# Luhn Checksum Card Verification
|
|
119
|
+
is_card_valid = parsikit.is_valid_card_number("6037991122334455")
|
|
120
|
+
print(is_card_valid) # True
|
|
121
|
+
|
|
122
|
+
# Standard Sheba (IBAN) Checksum Validation & Formatting
|
|
123
|
+
if parsikit.is_valid_sheba("050170000000123456789012"):
|
|
124
|
+
readable_sheba = parsikit.format_sheba("050170000000123456789012", format_type="spaced")
|
|
125
|
+
print(readable_sheba) # "IR05 0170 0000 0012 3456 7890 12"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 5. Layout Reshaping for Graphical Renderers
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
import parsikit
|
|
132
|
+
|
|
133
|
+
# Forces Left-to-Right layout simulation engines (like PIL/Pillow or Pygame)
|
|
134
|
+
# to render Persian words connected and beautifully aligned.
|
|
135
|
+
graphical_text = parsikit.reshape_for_graphics("سلام Hello جهان", reverse=True)
|
|
136
|
+
print(graphical_text) # "ﻡﻼﺳ Hello ﻥﺎﻬﺟ"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Running Tests
|
|
142
|
+
|
|
143
|
+
To verify package integrity, execute the comprehensive test suite locally:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python test.py
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
parsikit-2.1.0/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# ParsiKit
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-dependency, pure Python library designed for Persian text standardization, layout reshaping for legacy graphics engines, financial calculations, and Iranian standard format validations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- **Text Normalization & Correction**: Character mapping (Arabic `ي`/`ك` to Persian `ی`/`ک`), robust Zero-Width Non-Joiner (ZWNJ / نیمفاصله) formatting for verbs and suffixes, diacritics stripping, and English-to-Persian keyboard layout correction.
|
|
10
|
+
- **Digit & Number Conversions**: Convert digits between English, Persian, and Arabic formats, and translate numbers up to quadrillions into written Persian words.
|
|
11
|
+
- **Currency & Financial Utilities**: Thousand-separator formatting, Rial and Toman conversions, VAT calculator, loan monthly installment calculator, and verbal currency representations.
|
|
12
|
+
- **Iranian Standards Validators**: Real-time validation and clean formatting for National Codes, Mobile Numbers, Bank Cards (with Luhn checksum validation), and Sheba (IBAN) codes.
|
|
13
|
+
- **Graphical Text Reshaper**: A zero-dependency Persian reshaper that connects letters and handles RTL bidirectional flow for rendering text on Left-to-Right graphics engines (such as PIL/Pillow, Unity, Pygame, OpenCV, Matplotlib, or After Effects).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
You can install the package directly from source:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/MRThugh/ParsiKit.git
|
|
23
|
+
cd ParsiKit
|
|
24
|
+
pip install .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start & Examples
|
|
30
|
+
|
|
31
|
+
### 1. Text Normalization & Keyboard Layout Correction
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import parsikit
|
|
35
|
+
|
|
36
|
+
# Standardize Persian characters and ZWNJ formatting
|
|
37
|
+
raw_text = "ي كافيه ك کتاب ها ميباشد سَلامٌ"
|
|
38
|
+
standardized = parsikit.standardize_persian(raw_text)
|
|
39
|
+
print(standardized) # "ی کافیه ک کتابها میباشد سلام"
|
|
40
|
+
|
|
41
|
+
# Strip diacritics (Harakat)
|
|
42
|
+
clean_text = parsikit.strip_diacritics("سَلامٌ")
|
|
43
|
+
print(clean_text) # "سلام"
|
|
44
|
+
|
|
45
|
+
# Correct mistyped English layout text (e.g., typing standard words on wrong layout)
|
|
46
|
+
corrected = parsikit.correct_keyboard_layout("sghl")
|
|
47
|
+
print(corrected) # "سلام"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Digit Conversion & Verbal Number Representation
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import parsikit
|
|
54
|
+
|
|
55
|
+
# Convert digits
|
|
56
|
+
fa_digits = parsikit.english_to_persian("Price: 12500 Toman")
|
|
57
|
+
print(fa_digits) # "Price: ۱۲۵۰۰ Toman"
|
|
58
|
+
|
|
59
|
+
# Convert numeric values to written Persian words
|
|
60
|
+
words = parsikit.number_to_words(1453200)
|
|
61
|
+
print(words) # "یک میلیون و چهارصد و پنجاه و سه هزار و دویست"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Currency & Loan Installment Planning
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import parsikit
|
|
68
|
+
|
|
69
|
+
# Format with thousands separators
|
|
70
|
+
formatted = parsikit.format_currency("1500000", currency="toman", persian_digits=True)
|
|
71
|
+
print(formatted) # "۱،۵۰۰،۰۰۰ تومان"
|
|
72
|
+
|
|
73
|
+
# Convert currency directly into words
|
|
74
|
+
words = parsikit.format_currency_to_words(1000000, currency="toman")
|
|
75
|
+
print(words) # "یک میلیون تومان"
|
|
76
|
+
|
|
77
|
+
# Add Value Added Tax (VAT - Default is 10%)
|
|
78
|
+
total_with_tax = parsikit.add_tax_and_toll(100000)
|
|
79
|
+
print(total_with_tax) # 110000
|
|
80
|
+
|
|
81
|
+
# Calculate monthly loan installments (e.g., 10M loan at 18% interest over 12 months)
|
|
82
|
+
monthly_payment = parsikit.calculate_installments(10000000, annual_interest_rate=18.0, months=12)
|
|
83
|
+
print(monthly_payment) # 916799
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Identity & Banking Validators
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import parsikit
|
|
90
|
+
|
|
91
|
+
# National Code Checksum Validation & Formatting
|
|
92
|
+
if parsikit.is_valid_national_code("7730123452"):
|
|
93
|
+
formatted_nc = parsikit.format_national_code("7730123452")
|
|
94
|
+
print(formatted_nc) # "773-012345-2"
|
|
95
|
+
|
|
96
|
+
# Mobile Normalization (Standardizes input with various country code formats)
|
|
97
|
+
if parsikit.is_valid_mobile("+989123456789"):
|
|
98
|
+
local_mobile = parsikit.normalize_mobile("+989123456789", prefix="0")
|
|
99
|
+
print(local_mobile) # "09123456789"
|
|
100
|
+
|
|
101
|
+
# Luhn Checksum Card Verification
|
|
102
|
+
is_card_valid = parsikit.is_valid_card_number("6037991122334455")
|
|
103
|
+
print(is_card_valid) # True
|
|
104
|
+
|
|
105
|
+
# Standard Sheba (IBAN) Checksum Validation & Formatting
|
|
106
|
+
if parsikit.is_valid_sheba("050170000000123456789012"):
|
|
107
|
+
readable_sheba = parsikit.format_sheba("050170000000123456789012", format_type="spaced")
|
|
108
|
+
print(readable_sheba) # "IR05 0170 0000 0012 3456 7890 12"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 5. Layout Reshaping for Graphical Renderers
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import parsikit
|
|
115
|
+
|
|
116
|
+
# Forces Left-to-Right layout simulation engines (like PIL/Pillow or Pygame)
|
|
117
|
+
# to render Persian words connected and beautifully aligned.
|
|
118
|
+
graphical_text = parsikit.reshape_for_graphics("سلام Hello جهان", reverse=True)
|
|
119
|
+
print(graphical_text) # "ﻡﻼﺳ Hello ﻥﺎﻬﺟ"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Running Tests
|
|
125
|
+
|
|
126
|
+
To verify package integrity, execute the comprehensive test suite locally:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python test.py
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parsikit
|
|
3
|
+
~~~~~~~~
|
|
4
|
+
A pure Python library for Persian data formatting, validation, and normalization.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from parsikit.text import standardize_persian, strip_diacritics, is_persian, correct_keyboard_layout
|
|
8
|
+
from parsikit.number import english_to_persian, persian_to_english, number_to_words
|
|
9
|
+
from parsikit.currency import (
|
|
10
|
+
format_currency,
|
|
11
|
+
rial_to_toman,
|
|
12
|
+
toman_to_rial,
|
|
13
|
+
format_currency_to_words,
|
|
14
|
+
add_tax_and_toll,
|
|
15
|
+
calculate_installments,
|
|
16
|
+
)
|
|
17
|
+
from parsikit.validators import (
|
|
18
|
+
is_valid_national_code,
|
|
19
|
+
format_national_code,
|
|
20
|
+
is_valid_mobile,
|
|
21
|
+
normalize_mobile,
|
|
22
|
+
is_valid_card_number,
|
|
23
|
+
format_card_number,
|
|
24
|
+
is_valid_sheba,
|
|
25
|
+
format_sheba,
|
|
26
|
+
)
|
|
27
|
+
from parsikit.reshaper import reshape_for_graphics
|
|
28
|
+
|
|
29
|
+
__version__ = "2.1.0"
|
|
30
|
+
__all__ = [
|
|
31
|
+
# text
|
|
32
|
+
"standardize_persian",
|
|
33
|
+
"strip_diacritics",
|
|
34
|
+
"is_persian",
|
|
35
|
+
"correct_keyboard_layout",
|
|
36
|
+
# number
|
|
37
|
+
"english_to_persian",
|
|
38
|
+
"persian_to_english",
|
|
39
|
+
"number_to_words",
|
|
40
|
+
# currency
|
|
41
|
+
"format_currency",
|
|
42
|
+
"rial_to_toman",
|
|
43
|
+
"toman_to_rial",
|
|
44
|
+
"format_currency_to_words",
|
|
45
|
+
"add_tax_and_toll",
|
|
46
|
+
"calculate_installments",
|
|
47
|
+
# validators
|
|
48
|
+
"is_valid_national_code",
|
|
49
|
+
"format_national_code",
|
|
50
|
+
"is_valid_mobile",
|
|
51
|
+
"normalize_mobile",
|
|
52
|
+
"is_valid_card_number",
|
|
53
|
+
"format_card_number",
|
|
54
|
+
"is_valid_sheba",
|
|
55
|
+
"format_sheba",
|
|
56
|
+
# reshaper
|
|
57
|
+
"reshape_for_graphics",
|
|
58
|
+
]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parsikit.currency
|
|
3
|
+
~~~~~~~~~~~~~~~~~
|
|
4
|
+
Monetary utilities, unit conversions, tax calculations, and loan installment planning.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from parsikit.number import number_to_words
|
|
9
|
+
|
|
10
|
+
_CURRENCY_LABELS = {
|
|
11
|
+
"toman": "تومان",
|
|
12
|
+
"rial": "ریال",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def format_currency(
|
|
17
|
+
amount: int | str,
|
|
18
|
+
currency: str = "toman",
|
|
19
|
+
*,
|
|
20
|
+
persian_digits: bool = False,
|
|
21
|
+
) -> str:
|
|
22
|
+
"""Format a numeric amount as a readable currency with thousands separators."""
|
|
23
|
+
currency = currency.lower()
|
|
24
|
+
if currency not in _CURRENCY_LABELS:
|
|
25
|
+
raise ValueError(f"Unknown currency '{currency}'")
|
|
26
|
+
|
|
27
|
+
_persian_to_ascii = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
|
|
28
|
+
normalized = str(amount).translate(_persian_to_ascii)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
value = int(normalized)
|
|
32
|
+
except ValueError:
|
|
33
|
+
raise ValueError(f"Cannot convert '{amount}' to an integer amount.") from None
|
|
34
|
+
|
|
35
|
+
label = _CURRENCY_LABELS[currency]
|
|
36
|
+
|
|
37
|
+
if persian_digits:
|
|
38
|
+
_to_persian = str.maketrans("0123456789,", "۰۱۲۳۴۵۶۷۸۹،")
|
|
39
|
+
formatted = f"{value:,}".translate(_to_persian)
|
|
40
|
+
return f"{formatted} {label}"
|
|
41
|
+
|
|
42
|
+
return f"{value:,} {label}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def rial_to_toman(amount: int) -> int:
|
|
46
|
+
"""Convert Iranian Rial to Toman."""
|
|
47
|
+
if amount < 0:
|
|
48
|
+
raise ValueError("Amount must be non-negative.")
|
|
49
|
+
return amount // 10
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def toman_to_rial(amount: int) -> int:
|
|
53
|
+
"""Convert Toman to Iranian Rial."""
|
|
54
|
+
if amount < 0:
|
|
55
|
+
raise ValueError("Amount must be non-negative.")
|
|
56
|
+
return amount * 10
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_currency_to_words(amount: int | str, currency: str = "toman") -> str:
|
|
60
|
+
"""Convert monetary values to written Persian words with proper currency label."""
|
|
61
|
+
currency = currency.lower()
|
|
62
|
+
if currency not in _CURRENCY_LABELS:
|
|
63
|
+
raise ValueError(f"Unknown currency '{currency}'")
|
|
64
|
+
|
|
65
|
+
_persian_to_ascii = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
|
|
66
|
+
normalized = str(amount).translate(_persian_to_ascii)
|
|
67
|
+
try:
|
|
68
|
+
value = int(normalized)
|
|
69
|
+
except ValueError:
|
|
70
|
+
raise ValueError(f"Cannot convert '{amount}' to an integer amount.") from None
|
|
71
|
+
|
|
72
|
+
words_part = number_to_words(value)
|
|
73
|
+
label = _CURRENCY_LABELS[currency]
|
|
74
|
+
return f"{words_part} {label}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def add_tax_and_toll(amount: int | str, tax_rate: float = 0.10) -> int:
|
|
78
|
+
"""Calculate total amount including Value Added Tax (VAT). Default is 10%.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
amount: The price amount as int or string.
|
|
82
|
+
tax_rate: Tax rate as float (e.g. 0.10 for 10%).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The total price including tax as an integer.
|
|
86
|
+
"""
|
|
87
|
+
_persian_to_ascii = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
|
|
88
|
+
normalized = str(amount).translate(_persian_to_ascii)
|
|
89
|
+
try:
|
|
90
|
+
val = int(normalized)
|
|
91
|
+
except ValueError:
|
|
92
|
+
raise ValueError(f"Invalid numeric input '{amount}' for tax calculations.") from None
|
|
93
|
+
|
|
94
|
+
if val < 0:
|
|
95
|
+
raise ValueError("Amount must be non-negative.")
|
|
96
|
+
|
|
97
|
+
return int(val * (1 + tax_rate))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def calculate_installments(principal: int | str, annual_interest_rate: float, months: int) -> int:
|
|
101
|
+
"""Calculate the monthly installment amount for loan amortization.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
principal: The total loan amount.
|
|
105
|
+
annual_interest_rate: Annual interest percentage (e.g. 18.0 or 23.0).
|
|
106
|
+
months: Number of payment months.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
The exact monthly installment amount as integer.
|
|
110
|
+
"""
|
|
111
|
+
_persian_to_ascii = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
|
|
112
|
+
normalized = str(principal).translate(_persian_to_ascii)
|
|
113
|
+
try:
|
|
114
|
+
p = int(normalized)
|
|
115
|
+
except ValueError:
|
|
116
|
+
raise ValueError(f"Invalid loan principal '{principal}'.") from None
|
|
117
|
+
|
|
118
|
+
if p <= 0 or months <= 0:
|
|
119
|
+
raise ValueError("Loan principal and months must be greater than zero.")
|
|
120
|
+
|
|
121
|
+
if annual_interest_rate == 0:
|
|
122
|
+
return int(p / months)
|
|
123
|
+
|
|
124
|
+
# Convert yearly percentage rate to a monthly decimal rate
|
|
125
|
+
r = (annual_interest_rate / 100) / 12
|
|
126
|
+
numerator = p * r * ((1 + r) ** months)
|
|
127
|
+
denominator = ((1 + r) ** months) - 1
|
|
128
|
+
return int(numerator / denominator)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parsikit.number
|
|
3
|
+
~~~~~~~~~~~~~~~
|
|
4
|
+
Number conversion utilities and textual representations of numbers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
_TO_PERSIAN_TABLE = str.maketrans(
|
|
8
|
+
{
|
|
9
|
+
"0": "\u06F0", "1": "\u06F1", "2": "\u06F2",
|
|
10
|
+
"3": "\u06F3", "4": "\u06F4", "5": "\u06F5",
|
|
11
|
+
"6": "\u06F6", "7": "\u06F7", "8": "\u06F8",
|
|
12
|
+
"9": "\u06F9",
|
|
13
|
+
"\u0660": "\u06F0", "\u0661": "\u06F1", "\u0662": "\u06F2",
|
|
14
|
+
"\u0663": "\u06F3", "\u0664": "\u06F4", "\u0665": "\u06F5",
|
|
15
|
+
"\u0666": "\u06F6", "\u0667": "\u06F7", "\u0668": "\u06F8",
|
|
16
|
+
"\u0669": "\u06F9",
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_TO_ENGLISH_TABLE = str.maketrans(
|
|
21
|
+
{
|
|
22
|
+
"\u06F0": "0", "\u06F1": "1", "\u06F2": "2",
|
|
23
|
+
"\u06F3": "3", "\u06F4": "4", "\u06F5": "5",
|
|
24
|
+
"\u06F6": "6", "\u06F7": "7", "\u06F8": "8",
|
|
25
|
+
"\u06F9": "9",
|
|
26
|
+
"\u0660": "0", "\u0661": "1", "\u0662": "2",
|
|
27
|
+
"\u0663": "3", "\u0664": "4", "\u0665": "5",
|
|
28
|
+
"\u0666": "6", "\u0667": "7", "\u0668": "8",
|
|
29
|
+
"\u0669": "9",
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def english_to_persian(text: str) -> str:
|
|
35
|
+
"""Convert English/Arabic digits inside a string to Persian equivalents."""
|
|
36
|
+
if not text:
|
|
37
|
+
return text
|
|
38
|
+
return text.translate(_TO_PERSIAN_TABLE)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def persian_to_english(text: str) -> str:
|
|
42
|
+
"""Convert Persian/Arabic digits inside a string to English equivalents."""
|
|
43
|
+
if not text:
|
|
44
|
+
return text
|
|
45
|
+
return text.translate(_TO_ENGLISH_TABLE)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def number_to_words(number: int | str) -> str:
|
|
49
|
+
"""Convert numeric values to written Persian words."""
|
|
50
|
+
if isinstance(number, str):
|
|
51
|
+
clean_num = number.translate(_TO_ENGLISH_TABLE)
|
|
52
|
+
try:
|
|
53
|
+
num = int(clean_num)
|
|
54
|
+
except ValueError:
|
|
55
|
+
raise ValueError(f"Cannot convert '{number}' to a valid integer.") from None
|
|
56
|
+
else:
|
|
57
|
+
num = int(number)
|
|
58
|
+
|
|
59
|
+
if num == 0:
|
|
60
|
+
return "صفر"
|
|
61
|
+
|
|
62
|
+
is_negative = False
|
|
63
|
+
if num < 0:
|
|
64
|
+
is_negative = True
|
|
65
|
+
num = abs(num)
|
|
66
|
+
|
|
67
|
+
ones = {
|
|
68
|
+
1: "یک", 2: "دو", 3: "سه", 4: "چهار", 5: "پنج",
|
|
69
|
+
6: "شش", 7: "هفت", 8: "هشت", 9: "نه"
|
|
70
|
+
}
|
|
71
|
+
tens = {
|
|
72
|
+
10: "ده", 11: "یازده", 12: "دوازده", 13: "سیزده", 14: "چهارده",
|
|
73
|
+
15: "پانزده", 16: "شانزده", 17: "هفده", 18: "هجده", 19: "نوزده"
|
|
74
|
+
}
|
|
75
|
+
twenties = {
|
|
76
|
+
2: "بیست", 3: "سی", 4: "چهل", 5: "پنجاه",
|
|
77
|
+
6: "شصت", 7: "هفتاد", 8: "هشتاد", 9: "نود"
|
|
78
|
+
}
|
|
79
|
+
hundreds = {
|
|
80
|
+
1: "یکصد", 2: "دویست", 3: "سیصد", 4: "چهارصد", 5: "پانصد",
|
|
81
|
+
6: "ششصد", 7: "هفتصد", 8: "هشتصد", 9: "نهصد"
|
|
82
|
+
}
|
|
83
|
+
thousands = [
|
|
84
|
+
"", "هزار", "میلیون", "میلیارد", "تریلیون", "کوآدریلیون"
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
def _convert_group(n: int) -> str:
|
|
88
|
+
parts = []
|
|
89
|
+
h = n // 100
|
|
90
|
+
t_o = n % 100
|
|
91
|
+
|
|
92
|
+
if h > 0:
|
|
93
|
+
parts.append(hundreds[h])
|
|
94
|
+
|
|
95
|
+
if t_o > 0:
|
|
96
|
+
if 10 <= t_o <= 19:
|
|
97
|
+
parts.append(tens[t_o])
|
|
98
|
+
else:
|
|
99
|
+
t = t_o // 10
|
|
100
|
+
o = t_o % 10
|
|
101
|
+
if t > 0:
|
|
102
|
+
parts.append(twenties[t])
|
|
103
|
+
if o > 0:
|
|
104
|
+
parts.append(ones[o])
|
|
105
|
+
|
|
106
|
+
return " و ".join(parts)
|
|
107
|
+
|
|
108
|
+
chunks = []
|
|
109
|
+
temp = num
|
|
110
|
+
while temp > 0:
|
|
111
|
+
chunks.append(temp % 1000)
|
|
112
|
+
temp //= 1000
|
|
113
|
+
|
|
114
|
+
words_list = []
|
|
115
|
+
for i, chunk in enumerate(chunks):
|
|
116
|
+
if chunk > 0:
|
|
117
|
+
chunk_word = _convert_group(chunk)
|
|
118
|
+
unit = thousands[i]
|
|
119
|
+
if unit:
|
|
120
|
+
if i == 1 and chunk == 1:
|
|
121
|
+
words_list.append(unit)
|
|
122
|
+
else:
|
|
123
|
+
words_list.append(f"{chunk_word} {unit}".strip())
|
|
124
|
+
else:
|
|
125
|
+
words_list.append(chunk_word)
|
|
126
|
+
|
|
127
|
+
words_list.reverse()
|
|
128
|
+
result = " و ".join(words_list)
|
|
129
|
+
|
|
130
|
+
if is_negative:
|
|
131
|
+
result = "منفی " + result
|
|
132
|
+
|
|
133
|
+
return result
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parsikit.reshaper
|
|
3
|
+
~~~~~~~~~~~~~~~~~
|
|
4
|
+
A lightweight, zero-dependency Persian text shaper and layout reorganizer for graphical applications.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# Standard Persian and Arabic characters presentation forms mapping
|
|
10
|
+
# Format: 'char': (Isolated, Initial, Medial, Final, connects_right, connects_left)
|
|
11
|
+
_SHAPES: dict[str, tuple[str, str, str, str, bool, bool]] = {
|
|
12
|
+
'آ': ('\uFE81', '', '', '\uFE82', True, False),
|
|
13
|
+
'ا': ('\uFE8D', '', '', '\uFE8E', True, False),
|
|
14
|
+
'ب': ('\uFE8F', '\uFE91', '\uFE92', '\uFE90', True, True),
|
|
15
|
+
'پ': ('\uFB56', '\uFB58', '\uFB59', '\uFB57', True, True),
|
|
16
|
+
'ت': ('\uFE95', '\uFE97', '\uFE98', '\uFE96', True, True),
|
|
17
|
+
'ث': ('\uFE99', '\uFE9B', '\uFE9C', '\uFE9A', True, True),
|
|
18
|
+
'ج': ('\uFE9D', '\uFE9F', '\uFEA0', '\uFE9E', True, True),
|
|
19
|
+
'چ': ('\uFB7A', '\uFB7C', '\uFB7D', '\uFB7B', True, True),
|
|
20
|
+
'ح': ('\uFEA1', '\uFEA3', '\uFEA4', '\uFEA2', True, True),
|
|
21
|
+
'خ': ('\uFEA5', '\uFEA7', '\uFEA8', '\uFEA6', True, True),
|
|
22
|
+
'د': ('\uFEA9', '', '', '\uFEAA', True, False),
|
|
23
|
+
'ذ': ('\uFEAB', '', '', '\uFEAC', True, False),
|
|
24
|
+
'ر': ('\uFEAD', '', '', '\uFEAE', True, False),
|
|
25
|
+
'ز': ('\uFEAF', '', '', '\uFEB0', True, False),
|
|
26
|
+
'ژ': ('\uFB8A', '', '', '\uFB8B', True, False),
|
|
27
|
+
'س': ('\uFEB1', '\uFEB3', '\uFEB4', '\uFEB2', True, True),
|
|
28
|
+
'ش': ('\uFEB5', '\uFEB7', '\uFEB8', '\uFEB6', True, True),
|
|
29
|
+
'ص': ('\uFEB9', '\uFEBB', '\uFEBC', '\uFEBA', True, True),
|
|
30
|
+
'ض': ('\uFEBD', '\uFEBF', '\uFEC0', '\uFEBE', True, True),
|
|
31
|
+
'ط': ('\uFEC1', '\uFEC3', '\uFEC4', '\uFEC2', True, True),
|
|
32
|
+
'ظ': ('\uFEC5', '\uFEC7', '\uFEC8', '\uFEC6', True, True),
|
|
33
|
+
'ع': ('\uFEC9', '\uFECB', '\uFECC', '\uFECA', True, True),
|
|
34
|
+
'غ': ('\uFECD', '\uFECF', '\uFED0', '\uFECE', True, True),
|
|
35
|
+
'ف': ('\uFED1', '\uFED3', '\uFED4', '\uFED2', True, True),
|
|
36
|
+
'ق': ('\uFED5', '\uFED7', '\uFED8', '\uFED6', True, True),
|
|
37
|
+
'ک': ('\uFED9', '\uFEDB', '\uFEDC', '\uFEDA', True, True),
|
|
38
|
+
'گ': ('\uFB92', '\uFB94', '\uFB95', '\uFB93', True, True),
|
|
39
|
+
'ل': ('\uFEDD', '\uFEDF', '\uFEE0', '\uFEDE', True, True),
|
|
40
|
+
'م': ('\uFEE1', '\uFEE3', '\uFEE4', '\uFEE2', True, True),
|
|
41
|
+
'ن': ('\uFEE5', '\uFEE7', '\uFEE8', '\uFEE6', True, True),
|
|
42
|
+
'و': ('\uFEED', '', '', '\uFEEE', True, False),
|
|
43
|
+
'ه': ('\uFEE9', '\uFEEB', '\uFEEC', '\uFEEA', True, True),
|
|
44
|
+
'ی': ('\uFEF1', '\uFEF3', '\uFEF4', '\uFEF2', True, True),
|
|
45
|
+
'ئ': ('\uFE89', '\uFE8B', '\uFE8C', '\uFE8A', True, True),
|
|
46
|
+
'ء': ('\uFE80', '', '', '', False, False),
|
|
47
|
+
'ة': ('\uFE93', '', '', '\uFE94', True, False),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Standard Lam-Alef ligatures: (Isolated, Final)
|
|
51
|
+
_LIGATURES: dict[tuple[str, str], tuple[str, str]] = {
|
|
52
|
+
('ل', 'ا'): ('\uFEFB', '\uFEFC'),
|
|
53
|
+
('ل', 'آ'): ('\uFEF5', '\uFEF6'),
|
|
54
|
+
('ل', 'أ'): ('\uFEF7', '\uFEF8'),
|
|
55
|
+
('ل', 'إ'): ('\uFEF9', '\uFEFA'),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _is_rtl_char(c: str) -> bool:
|
|
60
|
+
"""Check if the given character belongs to the RTL Persian/Arabic Unicode blocks."""
|
|
61
|
+
o = ord(c)
|
|
62
|
+
return (0x0600 <= o <= 0x06FF) or (0xFB50 <= o <= 0xFDFF) or (0xFE70 <= o <= 0xFEFF) or c == '\u200C'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _reshape_word(text: str) -> str:
|
|
66
|
+
"""Core logic to shape Persian letters according to their surrounding characters."""
|
|
67
|
+
n = len(text)
|
|
68
|
+
if n == 0:
|
|
69
|
+
return text
|
|
70
|
+
|
|
71
|
+
# Pre-process to identify Lam-Alef ligatures
|
|
72
|
+
processed_chars: list[str | tuple[str, str]] = []
|
|
73
|
+
i = 0
|
|
74
|
+
while i < n:
|
|
75
|
+
if i < n - 1 and (text[i], text[i+1]) in _LIGATURES:
|
|
76
|
+
processed_chars.append((text[i], text[i+1]))
|
|
77
|
+
i += 2
|
|
78
|
+
else:
|
|
79
|
+
processed_chars.append(text[i])
|
|
80
|
+
i += 1
|
|
81
|
+
|
|
82
|
+
n_processed = len(processed_chars)
|
|
83
|
+
result: list[str] = []
|
|
84
|
+
|
|
85
|
+
for i in range(n_processed):
|
|
86
|
+
item = processed_chars[i]
|
|
87
|
+
|
|
88
|
+
if isinstance(item, tuple):
|
|
89
|
+
# Process Lam-Alef ligature
|
|
90
|
+
connects_prev = False
|
|
91
|
+
if i > 0:
|
|
92
|
+
prev_item = processed_chars[i-1]
|
|
93
|
+
if not isinstance(prev_item, tuple) and prev_item in _SHAPES:
|
|
94
|
+
connects_prev = _SHAPES[prev_item][5] # connects_left of previous char
|
|
95
|
+
|
|
96
|
+
isolated, final = _LIGATURES[item]
|
|
97
|
+
shape = final if connects_prev else isolated
|
|
98
|
+
result.append(shape)
|
|
99
|
+
else:
|
|
100
|
+
char = item
|
|
101
|
+
if char not in _SHAPES:
|
|
102
|
+
result.append(char)
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# Check previous character connection
|
|
106
|
+
connects_prev = False
|
|
107
|
+
if i > 0:
|
|
108
|
+
prev_item = processed_chars[i-1]
|
|
109
|
+
if isinstance(prev_item, tuple):
|
|
110
|
+
# Previous was ligature (ends with Alef which doesn't connect left)
|
|
111
|
+
connects_prev = False
|
|
112
|
+
elif prev_item in _SHAPES:
|
|
113
|
+
connects_prev = _SHAPES[prev_item][5] and _SHAPES[char][4]
|
|
114
|
+
|
|
115
|
+
# Check next character connection
|
|
116
|
+
connects_next = False
|
|
117
|
+
if i < n_processed - 1:
|
|
118
|
+
next_item = processed_chars[i+1]
|
|
119
|
+
if isinstance(next_item, tuple):
|
|
120
|
+
# Next is ligature (starts with Lam which connects right)
|
|
121
|
+
connects_next = _SHAPES[char][5] and True
|
|
122
|
+
elif next_item in _SHAPES:
|
|
123
|
+
connects_next = _SHAPES[char][5] and _SHAPES[next_item][4]
|
|
124
|
+
|
|
125
|
+
isolated, initial, medial, final, _, _ = _SHAPES[char]
|
|
126
|
+
|
|
127
|
+
if connects_prev and connects_next:
|
|
128
|
+
shape = medial or initial or isolated
|
|
129
|
+
elif connects_prev:
|
|
130
|
+
shape = final or isolated
|
|
131
|
+
elif connects_next:
|
|
132
|
+
shape = initial or isolated
|
|
133
|
+
else:
|
|
134
|
+
shape = isolated
|
|
135
|
+
|
|
136
|
+
result.append(shape)
|
|
137
|
+
|
|
138
|
+
return "".join(result)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def reshape_for_graphics(text: str, reverse: bool = True) -> str:
|
|
142
|
+
"""Prepare Persian text for rendering inside engines that lack RTL/Shaping support.
|
|
143
|
+
|
|
144
|
+
Separates RTL and LTR blocks, shapes the Persian characters, and optionally
|
|
145
|
+
reverses the RTL parts so they display in correct reading order on LTR canvases.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
text: Standard Persian/English input string.
|
|
149
|
+
reverse: If True, reverses the layout of the RTL chunks for LTR graphic cards.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The transformed string containing reshaped presentation forms.
|
|
153
|
+
"""
|
|
154
|
+
if not text:
|
|
155
|
+
return text
|
|
156
|
+
|
|
157
|
+
# Shape the standard letters
|
|
158
|
+
shaped_text = _reshape_word(text)
|
|
159
|
+
|
|
160
|
+
if not reverse:
|
|
161
|
+
return shaped_text
|
|
162
|
+
|
|
163
|
+
# Split string into RTL and LTR blocks to avoid reversing English text
|
|
164
|
+
blocks: list[tuple[bool, str]] = []
|
|
165
|
+
current_block: list[str] = []
|
|
166
|
+
current_is_rtl: bool | None = None
|
|
167
|
+
|
|
168
|
+
for char in shaped_text:
|
|
169
|
+
is_rtl = _is_rtl_char(char)
|
|
170
|
+
if current_is_rtl is None:
|
|
171
|
+
current_is_rtl = is_rtl
|
|
172
|
+
current_block.append(char)
|
|
173
|
+
elif is_rtl == current_is_rtl:
|
|
174
|
+
current_block.append(char)
|
|
175
|
+
else:
|
|
176
|
+
blocks.append((current_is_rtl, "".join(current_block)))
|
|
177
|
+
current_block = [char]
|
|
178
|
+
current_is_rtl = is_rtl
|
|
179
|
+
|
|
180
|
+
if current_block:
|
|
181
|
+
blocks.append((current_is_rtl, "".join(current_block)))
|
|
182
|
+
|
|
183
|
+
# Reconstruct blocks (reverse RTL character order while preserving LTR blocks)
|
|
184
|
+
output: list[str] = []
|
|
185
|
+
for is_rtl, block_text in blocks:
|
|
186
|
+
if is_rtl:
|
|
187
|
+
output.append("".join(reversed(block_text)))
|
|
188
|
+
else:
|
|
189
|
+
output.append(block_text)
|
|
190
|
+
|
|
191
|
+
return "".join(output)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parsikit.text
|
|
3
|
+
~~~~~~~~~~~~~
|
|
4
|
+
Text standardization, normalization, and keyboard layout correction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
# Table for converting Arabic characters to Persian equivalents
|
|
10
|
+
_ARABIC_TO_PERSIAN_TABLE: dict[int, int] = str.maketrans(
|
|
11
|
+
{
|
|
12
|
+
"\u064A": "\u06CC", # ي → ی
|
|
13
|
+
"\u0649": "\u06CC", # ى → ی
|
|
14
|
+
"\u0643": "\u06A9", # ك → ک
|
|
15
|
+
"\u0660": "\u06F0", "\u0661": "\u06F1", "\u0662": "\u06F2", "\u0663": "\u06F3",
|
|
16
|
+
"\u0664": "\u06F4", "\u0665": "\u06F5", "\u0666": "\u06F6", "\u0667": "\u06F7",
|
|
17
|
+
"\u0668": "\u06F8", "\u0669": "\u06F9",
|
|
18
|
+
"\u06D5": "\u0647", # ە → ه
|
|
19
|
+
"\u0629": "\u0647", # ة → ه
|
|
20
|
+
"\u06C0": "\u0647\u0654", # ۀ → هٔ
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Standard QWERTY keys mapped to standard Persian keyboard
|
|
25
|
+
_QWERTY_TO_PERSIAN_MAP = {
|
|
26
|
+
'q': 'ض', 'w': 'ص', 'e': 'ث', 'r': 'ق', 't': 'ف', 'y': 'غ', 'u': 'ع', 'i': 'ه', 'o': 'خ', 'p': 'ح', '[': 'ج', ']': 'چ',
|
|
27
|
+
'a': 'ش', 's': 'س', 'd': 'ی', 'f': 'ب', 'g': 'ل', 'h': 'ا', 'j': 'ت', 'k': 'ن', 'l': 'م', ';': 'ک', "'": 'گ',
|
|
28
|
+
'z': 'ظ', 'x': 'ط', 'c': 'ز', 'v': 'ر', 'b': 'ذ', 'n': 'د', 'm': 'پ', ',': 'و',
|
|
29
|
+
'Q': 'ض', 'W': 'ص', 'E': 'ث', 'R': 'ق', 'T': 'ف', 'Y': 'غ', 'U': 'ع', 'I': 'ه', 'O': 'خ', 'P': 'ح', '{': 'ج', '}': 'چ',
|
|
30
|
+
'A': 'ش', 'S': 'س', 'D': 'ی', 'F': 'ب', 'G': 'ل', 'H': 'ا', 'J': 'ت', 'K': 'ن', 'L': 'م', ':': 'ک', '"': 'گ',
|
|
31
|
+
'Z': 'ظ', 'X': 'ط', 'C': 'ز', 'V': 'ر', 'B': 'ذ', 'N': 'د', 'M': 'پ', '<': 'و',
|
|
32
|
+
}
|
|
33
|
+
_KEYBOARD_TABLE = str.maketrans(_QWERTY_TO_PERSIAN_MAP)
|
|
34
|
+
|
|
35
|
+
_ZWNJ = "\u200C"
|
|
36
|
+
|
|
37
|
+
# Robust prefixes (Noisy ones like "هم" or "هر" are removed to prevent false-positives)
|
|
38
|
+
_PREFIX_PATTERNS = [
|
|
39
|
+
(re.compile(r"\b(می|نمی)\s+(?=\S)"), r"\1" + _ZWNJ),
|
|
40
|
+
(re.compile(r"\b(بی)\s+(?=\S)"), r"\1" + _ZWNJ),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
# Suffixes including comparative adjectives "تر" and "ترین"
|
|
44
|
+
_SUFFIX_PATTERNS = [
|
|
45
|
+
(
|
|
46
|
+
re.compile(r"(?<=\S)\s+(ها|های|هایی|تر|ترین|ای|ام|ات|اش|ایم|اید|اند)\b"),
|
|
47
|
+
_ZWNJ + r"\1",
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
_ZWNJ_PATTERNS = _PREFIX_PATTERNS + _SUFFIX_PATTERNS
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def strip_diacritics(text: str) -> str:
|
|
55
|
+
"""Remove Arabic/Persian diacritics (Fatha, Kasra, Damma, Tanween, Tashdeed, Sukuun)."""
|
|
56
|
+
if not text:
|
|
57
|
+
return text
|
|
58
|
+
diacritics_pattern = re.compile(r"[\u064B-\u065F\u0670]")
|
|
59
|
+
return diacritics_pattern.sub("", text)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_persian(text: str) -> bool:
|
|
63
|
+
"""Check if the text contains at least one Persian/Arabic script character."""
|
|
64
|
+
if not text:
|
|
65
|
+
return False
|
|
66
|
+
return bool(re.search(r"[\u0600-\u06FF]", text))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def correct_keyboard_layout(text: str) -> str:
|
|
70
|
+
"""Correct English layout typed text into Persian layout (e.g. 'sghl' -> 'سلام')."""
|
|
71
|
+
if not text:
|
|
72
|
+
return text
|
|
73
|
+
return text.translate(_KEYBOARD_TABLE)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def standardize_persian(text: str, *, strip_diacritics_opt: bool = False) -> str:
|
|
77
|
+
"""Normalize and standardize Persian text layout, character codes, and spaces."""
|
|
78
|
+
if not text:
|
|
79
|
+
return text
|
|
80
|
+
|
|
81
|
+
if strip_diacritics_opt:
|
|
82
|
+
text = strip_diacritics(text)
|
|
83
|
+
|
|
84
|
+
# Convert characters
|
|
85
|
+
text = text.translate(_ARABIC_TO_PERSIAN_TABLE)
|
|
86
|
+
|
|
87
|
+
# Adjust ZWNJs
|
|
88
|
+
for pattern, replacement in _ZWNJ_PATTERNS:
|
|
89
|
+
text = pattern.sub(replacement, text)
|
|
90
|
+
|
|
91
|
+
# Collapse redundant spaces
|
|
92
|
+
text = re.sub(r" {2,}", " ", text).strip()
|
|
93
|
+
|
|
94
|
+
return text
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
parsikit.validators
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Identity, banking, and telephone format validations for Iranian standards.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
_TO_ENGLISH = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_valid_national_code(code: str) -> bool:
|
|
13
|
+
"""Check if the provided code is a valid 10-digit Iranian National Code."""
|
|
14
|
+
if not code:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
clean = "".join(c for c in str(code).translate(_TO_ENGLISH) if c.isdigit())
|
|
18
|
+
|
|
19
|
+
if len(clean) != 10:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
# Block patterns with repeating single digits (e.g., 1111111111)
|
|
23
|
+
if len(set(clean)) == 1:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
digits = [int(d) for d in clean]
|
|
27
|
+
check_digit = digits[-1]
|
|
28
|
+
|
|
29
|
+
s = sum(digits[i] * (10 - i) for i in range(9))
|
|
30
|
+
r = s % 11
|
|
31
|
+
|
|
32
|
+
if r < 2:
|
|
33
|
+
return check_digit == r
|
|
34
|
+
|
|
35
|
+
return check_digit == (11 - r)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_national_code(code: str) -> str:
|
|
39
|
+
"""Format national code into standardized format (e.g. XXX-XXXXXX-X)."""
|
|
40
|
+
clean = "".join(c for c in str(code).translate(_TO_ENGLISH) if c.isdigit())
|
|
41
|
+
|
|
42
|
+
if len(clean) < 10:
|
|
43
|
+
clean = clean.zfill(10)
|
|
44
|
+
elif len(clean) > 10:
|
|
45
|
+
raise ValueError("National code must not exceed 10 digits.")
|
|
46
|
+
|
|
47
|
+
return f"{clean[:3]}-{clean[3:9]}-{clean[9]}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_valid_mobile(phone: str) -> bool:
|
|
51
|
+
"""Validate Iranian mobile numbers (supports +98, 0098, 98, 0 and bare prefixes)."""
|
|
52
|
+
if not phone:
|
|
53
|
+
return False
|
|
54
|
+
clean = "".join(c for c in str(phone).translate(_TO_ENGLISH) if c.isdigit() or c == "+")
|
|
55
|
+
pattern = re.compile(r"^(?:\+98|0098|98|0)?9\d{9}$")
|
|
56
|
+
return bool(pattern.match(clean))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def normalize_mobile(phone: str, prefix: str = "0") -> str:
|
|
60
|
+
"""Standardize mobile formats to specified layout prefixes (e.g., '0', '+98', '98')."""
|
|
61
|
+
if not is_valid_mobile(phone):
|
|
62
|
+
raise ValueError("Invalid Iranian mobile number layout.")
|
|
63
|
+
|
|
64
|
+
clean = "".join(c for c in str(phone).translate(_TO_ENGLISH) if c.isdigit())
|
|
65
|
+
base = clean[-10:]
|
|
66
|
+
|
|
67
|
+
if prefix == "0":
|
|
68
|
+
return f"0{base}"
|
|
69
|
+
elif prefix == "+98":
|
|
70
|
+
return f"+98{base}"
|
|
71
|
+
elif prefix == "98":
|
|
72
|
+
return f"98{base}"
|
|
73
|
+
|
|
74
|
+
raise ValueError("Unsupported prefix format. Choose '0', '+98', or '98'.")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_valid_card_number(card: str) -> bool:
|
|
78
|
+
"""Validate 16-digit bank card numbers using Luhn checksum algorithm."""
|
|
79
|
+
if not card:
|
|
80
|
+
return False
|
|
81
|
+
clean = "".join(c for c in str(card).translate(_TO_ENGLISH) if c.isdigit())
|
|
82
|
+
|
|
83
|
+
if len(clean) != 16:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
digits = [int(x) for x in clean]
|
|
87
|
+
for i in range(0, 16, 2):
|
|
88
|
+
val = digits[i] * 2
|
|
89
|
+
if val > 9:
|
|
90
|
+
val -= 9
|
|
91
|
+
digits[i] = val
|
|
92
|
+
|
|
93
|
+
return sum(digits) % 10 == 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def format_card_number(card: str, separator: str = "-") -> str:
|
|
97
|
+
"""Format bank card numbers into standard four-chunk groups."""
|
|
98
|
+
clean = "".join(c for c in str(card).translate(_TO_ENGLISH) if c.isdigit())
|
|
99
|
+
if len(clean) != 16:
|
|
100
|
+
raise ValueError("Card number must contain exactly 16 digits.")
|
|
101
|
+
return separator.join([clean[i:i+4] for i in range(0, 16, 4)])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_valid_sheba(sheba: str) -> bool:
|
|
105
|
+
"""Validate Iranian Sheba (IBAN) format (starts with IR followed by 24 digits)."""
|
|
106
|
+
if not sheba:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
clean = str(sheba).translate(_TO_ENGLISH).upper().replace(" ", "").replace("-", "")
|
|
110
|
+
|
|
111
|
+
if len(clean) == 24 and clean.isdigit():
|
|
112
|
+
clean = "IR" + clean
|
|
113
|
+
|
|
114
|
+
if len(clean) != 26 or not clean.startswith("IR") or not clean[2:].isdigit():
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
# Move 'IRXX' to end: 'IRXXYYYY...' -> 'YYYY...IRXX'
|
|
118
|
+
rearranged = clean[4:] + clean[:4]
|
|
119
|
+
|
|
120
|
+
# Translate letters to numbers (I -> 18, R -> 27)
|
|
121
|
+
num_str = ""
|
|
122
|
+
for char in rearranged:
|
|
123
|
+
if char.isalpha():
|
|
124
|
+
num_str += str(ord(char) - ord('A') + 10)
|
|
125
|
+
else:
|
|
126
|
+
num_str += char
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
return int(num_str) % 97 == 1
|
|
130
|
+
except ValueError:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def format_sheba(sheba: str, format_type: str = "spaced") -> str:
|
|
135
|
+
"""Format Sheba values cleanly or into four-character readable blocks."""
|
|
136
|
+
clean = str(sheba).translate(_TO_ENGLISH).upper().replace(" ", "").replace("-", "")
|
|
137
|
+
|
|
138
|
+
if len(clean) == 24 and clean.isdigit():
|
|
139
|
+
clean = "IR" + clean
|
|
140
|
+
|
|
141
|
+
if len(clean) != 26 or not clean.startswith("IR") or not clean[2:].isdigit():
|
|
142
|
+
raise ValueError("Invalid Sheba structure.")
|
|
143
|
+
|
|
144
|
+
if format_type == "clean":
|
|
145
|
+
return clean
|
|
146
|
+
|
|
147
|
+
# spaced chunk format
|
|
148
|
+
return " ".join([clean[i:i+4] for i in range(0, 26, 4)])
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parsikit
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: A comprehensive and pure Python library for Persian data formatting, validation, and graphical reshaping.
|
|
5
|
+
Author: Ali Kamrani
|
|
6
|
+
Author-email: Ali Kamrani <kamrani.exe@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Natural Language :: Persian
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: requires-python
|
|
17
|
+
|
|
18
|
+
# ParsiKit
|
|
19
|
+
|
|
20
|
+
A lightweight, zero-dependency, pure Python library designed for Persian text standardization, layout reshaping for legacy graphics engines, financial calculations, and Iranian standard format validations.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Key Features
|
|
25
|
+
|
|
26
|
+
- **Text Normalization & Correction**: Character mapping (Arabic `ي`/`ك` to Persian `ی`/`ک`), robust Zero-Width Non-Joiner (ZWNJ / نیمفاصله) formatting for verbs and suffixes, diacritics stripping, and English-to-Persian keyboard layout correction.
|
|
27
|
+
- **Digit & Number Conversions**: Convert digits between English, Persian, and Arabic formats, and translate numbers up to quadrillions into written Persian words.
|
|
28
|
+
- **Currency & Financial Utilities**: Thousand-separator formatting, Rial and Toman conversions, VAT calculator, loan monthly installment calculator, and verbal currency representations.
|
|
29
|
+
- **Iranian Standards Validators**: Real-time validation and clean formatting for National Codes, Mobile Numbers, Bank Cards (with Luhn checksum validation), and Sheba (IBAN) codes.
|
|
30
|
+
- **Graphical Text Reshaper**: A zero-dependency Persian reshaper that connects letters and handles RTL bidirectional flow for rendering text on Left-to-Right graphics engines (such as PIL/Pillow, Unity, Pygame, OpenCV, Matplotlib, or After Effects).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
You can install the package directly from source:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/MRThugh/ParsiKit.git
|
|
40
|
+
cd ParsiKit
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start & Examples
|
|
47
|
+
|
|
48
|
+
### 1. Text Normalization & Keyboard Layout Correction
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import parsikit
|
|
52
|
+
|
|
53
|
+
# Standardize Persian characters and ZWNJ formatting
|
|
54
|
+
raw_text = "ي كافيه ك کتاب ها ميباشد سَلامٌ"
|
|
55
|
+
standardized = parsikit.standardize_persian(raw_text)
|
|
56
|
+
print(standardized) # "ی کافیه ک کتابها میباشد سلام"
|
|
57
|
+
|
|
58
|
+
# Strip diacritics (Harakat)
|
|
59
|
+
clean_text = parsikit.strip_diacritics("سَلامٌ")
|
|
60
|
+
print(clean_text) # "سلام"
|
|
61
|
+
|
|
62
|
+
# Correct mistyped English layout text (e.g., typing standard words on wrong layout)
|
|
63
|
+
corrected = parsikit.correct_keyboard_layout("sghl")
|
|
64
|
+
print(corrected) # "سلام"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Digit Conversion & Verbal Number Representation
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import parsikit
|
|
71
|
+
|
|
72
|
+
# Convert digits
|
|
73
|
+
fa_digits = parsikit.english_to_persian("Price: 12500 Toman")
|
|
74
|
+
print(fa_digits) # "Price: ۱۲۵۰۰ Toman"
|
|
75
|
+
|
|
76
|
+
# Convert numeric values to written Persian words
|
|
77
|
+
words = parsikit.number_to_words(1453200)
|
|
78
|
+
print(words) # "یک میلیون و چهارصد و پنجاه و سه هزار و دویست"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Currency & Loan Installment Planning
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import parsikit
|
|
85
|
+
|
|
86
|
+
# Format with thousands separators
|
|
87
|
+
formatted = parsikit.format_currency("1500000", currency="toman", persian_digits=True)
|
|
88
|
+
print(formatted) # "۱،۵۰۰،۰۰۰ تومان"
|
|
89
|
+
|
|
90
|
+
# Convert currency directly into words
|
|
91
|
+
words = parsikit.format_currency_to_words(1000000, currency="toman")
|
|
92
|
+
print(words) # "یک میلیون تومان"
|
|
93
|
+
|
|
94
|
+
# Add Value Added Tax (VAT - Default is 10%)
|
|
95
|
+
total_with_tax = parsikit.add_tax_and_toll(100000)
|
|
96
|
+
print(total_with_tax) # 110000
|
|
97
|
+
|
|
98
|
+
# Calculate monthly loan installments (e.g., 10M loan at 18% interest over 12 months)
|
|
99
|
+
monthly_payment = parsikit.calculate_installments(10000000, annual_interest_rate=18.0, months=12)
|
|
100
|
+
print(monthly_payment) # 916799
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 4. Identity & Banking Validators
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import parsikit
|
|
107
|
+
|
|
108
|
+
# National Code Checksum Validation & Formatting
|
|
109
|
+
if parsikit.is_valid_national_code("7730123452"):
|
|
110
|
+
formatted_nc = parsikit.format_national_code("7730123452")
|
|
111
|
+
print(formatted_nc) # "773-012345-2"
|
|
112
|
+
|
|
113
|
+
# Mobile Normalization (Standardizes input with various country code formats)
|
|
114
|
+
if parsikit.is_valid_mobile("+989123456789"):
|
|
115
|
+
local_mobile = parsikit.normalize_mobile("+989123456789", prefix="0")
|
|
116
|
+
print(local_mobile) # "09123456789"
|
|
117
|
+
|
|
118
|
+
# Luhn Checksum Card Verification
|
|
119
|
+
is_card_valid = parsikit.is_valid_card_number("6037991122334455")
|
|
120
|
+
print(is_card_valid) # True
|
|
121
|
+
|
|
122
|
+
# Standard Sheba (IBAN) Checksum Validation & Formatting
|
|
123
|
+
if parsikit.is_valid_sheba("050170000000123456789012"):
|
|
124
|
+
readable_sheba = parsikit.format_sheba("050170000000123456789012", format_type="spaced")
|
|
125
|
+
print(readable_sheba) # "IR05 0170 0000 0012 3456 7890 12"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 5. Layout Reshaping for Graphical Renderers
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
import parsikit
|
|
132
|
+
|
|
133
|
+
# Forces Left-to-Right layout simulation engines (like PIL/Pillow or Pygame)
|
|
134
|
+
# to render Persian words connected and beautifully aligned.
|
|
135
|
+
graphical_text = parsikit.reshape_for_graphics("سلام Hello جهان", reverse=True)
|
|
136
|
+
print(graphical_text) # "ﻡﻼﺳ Hello ﻥﺎﻬﺟ"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Running Tests
|
|
142
|
+
|
|
143
|
+
To verify package integrity, execute the comprehensive test suite locally:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python test.py
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
parsikit/__init__.py
|
|
5
|
+
parsikit/currency.py
|
|
6
|
+
parsikit/number.py
|
|
7
|
+
parsikit/reshaper.py
|
|
8
|
+
parsikit/text.py
|
|
9
|
+
parsikit/validators.py
|
|
10
|
+
parsikit.egg-info/PKG-INFO
|
|
11
|
+
parsikit.egg-info/SOURCES.txt
|
|
12
|
+
parsikit.egg-info/dependency_links.txt
|
|
13
|
+
parsikit.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parsikit
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "parsikit"
|
|
7
|
+
version = "2.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Ali Kamrani", email = "kamrani.exe@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "A comprehensive and pure Python library for Persian data formatting, validation, and graphical reshaping."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Natural Language :: Persian",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["."]
|
|
25
|
+
include = ["parsikit*"]
|
parsikit-2.1.0/setup.cfg
ADDED
parsikit-2.1.0/setup.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
README = Path(__file__).parent / "README.md"
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="parsikit",
|
|
8
|
+
version="0.2.0",
|
|
9
|
+
author="Ali Kamrani",
|
|
10
|
+
author_email="kamrani.exe@gmail.com",
|
|
11
|
+
description="A pure Python library for Persian data formatting.",
|
|
12
|
+
long_description=README.read_text(encoding="utf-8") if README.exists() else "",
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
packages=find_packages(),
|
|
15
|
+
python_requires=">=3.10",
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Natural Language :: Persian",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
],
|
|
23
|
+
)
|