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.
@@ -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,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
+ 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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )