persian-devkit 1.0.0__py3-none-any.whl

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.
Files changed (51) hide show
  1. persian_devkit/__init__.py +4 -0
  2. persian_devkit/__main__.py +5 -0
  3. persian_devkit/cli.py +55 -0
  4. persian_devkit/commands/__init__.py +16 -0
  5. persian_devkit/commands/base64_cmd.py +51 -0
  6. persian_devkit/commands/color_cmd.py +143 -0
  7. persian_devkit/commands/csv_cmd.py +121 -0
  8. persian_devkit/commands/date_cmd.py +98 -0
  9. persian_devkit/commands/env_cmd.py +100 -0
  10. persian_devkit/commands/gitignore_cmd.py +68 -0
  11. persian_devkit/commands/hash_cmd.py +71 -0
  12. persian_devkit/commands/image_cmd.py +88 -0
  13. persian_devkit/commands/jq_cmd.py +92 -0
  14. persian_devkit/commands/json_cmd.py +135 -0
  15. persian_devkit/commands/license_cmd.py +67 -0
  16. persian_devkit/commands/lorem_cmd.py +53 -0
  17. persian_devkit/commands/name_cmd.py +98 -0
  18. persian_devkit/commands/number_cmd.py +65 -0
  19. persian_devkit/commands/password_cmd.py +55 -0
  20. persian_devkit/commands/qrcode_cmd.py +46 -0
  21. persian_devkit/commands/random_cmd.py +110 -0
  22. persian_devkit/commands/scaffold_cmd.py +155 -0
  23. persian_devkit/commands/template_cmd.py +87 -0
  24. persian_devkit/commands/text_cmd.py +157 -0
  25. persian_devkit/commands/time_cmd.py +96 -0
  26. persian_devkit/commands/toml_cmd.py +85 -0
  27. persian_devkit/commands/url_cmd.py +102 -0
  28. persian_devkit/commands/uuid_cmd.py +45 -0
  29. persian_devkit/commands/yaml_cmd.py +94 -0
  30. persian_devkit/main.py +63 -0
  31. persian_devkit/py.typed +0 -0
  32. persian_devkit/utils/__init__.py +1 -0
  33. persian_devkit/utils/color_utils.py +112 -0
  34. persian_devkit/utils/crypto_utils.py +157 -0
  35. persian_devkit/utils/data_utils.py +146 -0
  36. persian_devkit/utils/date_utils.py +58 -0
  37. persian_devkit/utils/fake_utils.py +144 -0
  38. persian_devkit/utils/gitignore_data.py +211 -0
  39. persian_devkit/utils/image_utils.py +37 -0
  40. persian_devkit/utils/jsonpath_utils.py +94 -0
  41. persian_devkit/utils/license_data.py +190 -0
  42. persian_devkit/utils/number_utils.py +78 -0
  43. persian_devkit/utils/qrcode_utils.py +84 -0
  44. persian_devkit/utils/text_utils.py +166 -0
  45. persian_devkit/utils/time_utils.py +81 -0
  46. persian_devkit/utils/url_utils.py +54 -0
  47. persian_devkit-1.0.0.dist-info/METADATA +702 -0
  48. persian_devkit-1.0.0.dist-info/RECORD +51 -0
  49. persian_devkit-1.0.0.dist-info/WHEEL +4 -0
  50. persian_devkit-1.0.0.dist-info/entry_points.txt +2 -0
  51. persian_devkit-1.0.0.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,166 @@
1
+ """توابع کمکی پاک‌سازی و پردازش متن فارسی."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ # ی و ک عربی به فارسی
7
+ _ARABIC_TO_PERSIAN = str.maketrans(
8
+ {
9
+ "\u064a": "\u06cc", # ي → ی
10
+ "\u0643": "\u06a9", # ك → ک
11
+ "\u0649": "\u06cc", # ى → ی
12
+ "\u0629": "\u0647", # ة → ه
13
+ "\u06c0": "\u0647", # ۀ → ه
14
+ }
15
+ )
16
+
17
+ _DIACRITICS_RE = re.compile(r"[\u064b-\u065f\u0670]")
18
+ _MULTI_SPACE_RE = re.compile(r"[ \t]+")
19
+ _MULTI_NEWLINE_RE = re.compile(r"\n{3,}")
20
+ _SENTENCE_END_RE = re.compile(r"[.!?؟]+")
21
+
22
+ _ZWNJ = "\u200c"
23
+ _PREFIXES = ("نمی", "می") # ترتیب مهم است (بلندتر اول)
24
+ _SUFFIXES = ("هایی", "های", "ترین", "ها", "تر")
25
+
26
+
27
+ def fix_halfspace(text: str) -> str:
28
+ """اصلاح نیم‌فاصله در متن فارسی (می‌روم، کتاب‌ها، بهتر‌ترین)."""
29
+ result = text
30
+ # پیشوندها: می روم → می‌روم
31
+ for prefix in _PREFIXES:
32
+ result = re.sub(rf"(?<!\S){re.escape(prefix)}\s+", rf"{prefix}{_ZWNJ}", result)
33
+ # پسوندها: کتاب ها → کتاب‌ها
34
+ suffix_pattern = "|".join(re.escape(s) for s in _SUFFIXES)
35
+ result = re.sub(rf"\s+({suffix_pattern})(?!\S)", rf"{_ZWNJ}\1", result)
36
+ return result
37
+
38
+
39
+ def normalize(text: str) -> str:
40
+ """یکسان‌سازی حروف عربی به فارسی، حذف اعراب و فاصله‌های اضافی."""
41
+ result = text.translate(_ARABIC_TO_PERSIAN)
42
+ result = _DIACRITICS_RE.sub("", result)
43
+ result = _MULTI_SPACE_RE.sub(" ", result)
44
+ result = _MULTI_NEWLINE_RE.sub("\n\n", result)
45
+ return result.strip()
46
+
47
+
48
+ def reverse_text(text: str) -> str:
49
+ """معکوس‌سازی کاراکترهای متن."""
50
+ return text[::-1]
51
+
52
+
53
+ def text_stats(text: str) -> dict[str, int]:
54
+ """محاسبهٔ آمار متن: کاراکتر، کلمه، خط، جمله."""
55
+ lines = text.splitlines() or [""]
56
+ words = text.split()
57
+ chars = len(text)
58
+ chars_no_space = sum(1 for c in text if not c.isspace())
59
+ sentences = len(_SENTENCE_END_RE.findall(text))
60
+ if sentences == 0 and text.strip():
61
+ sentences = 1
62
+ return {
63
+ "chars": chars,
64
+ "chars_no_space": chars_no_space,
65
+ "words": len(words),
66
+ "lines": len(lines),
67
+ "sentences": sentences,
68
+ }
69
+
70
+ #slug & case
71
+
72
+ _SLUG_KEEP = re.compile(r"[^\w\s\-]", re.UNICODE)
73
+ _SLUG_SPACES = re.compile(r"[\s\-_]+")
74
+
75
+
76
+ def to_slug(text: str, separator: str = "-", max_length: int = 0) -> str:
77
+ """تبدیل متن (فارسی یا انگلیسی) به slug قابل استفاده در URL.
78
+
79
+ نیم‌فاصله، اعراب و کاراکترهای غیرمجاز حذف یا جایگزین می‌شوند.
80
+ """
81
+ # اول نرمال‌سازی ی/ک عربی
82
+ result = text.translate(_ARABIC_TO_PERSIAN)
83
+ # حذف اعراب
84
+ result = _DIACRITICS_RE.sub("", result)
85
+ # نیم‌فاصله به فاصله (چون بعداً با separator یکی می‌شود)
86
+ result = result.replace(_ZWNJ, " ")
87
+ # حذف کاراکترهای غیرمجاز (نگه‌داشتن حروف، اعداد، خط تیره، آندرلاین)
88
+ result = _SLUG_KEEP.sub("", result)
89
+ # فاصله‌ها و جداکننده‌های تکراری
90
+ result = _SLUG_SPACES.sub(separator, result).strip(separator)
91
+ result = result.lower()
92
+ if max_length > 0 and len(result) > max_length:
93
+ result = result[:max_length].rstrip(separator)
94
+ return result
95
+
96
+
97
+ _CASE_MODES = ("upper", "lower", "title", "capitalize", "swap")
98
+
99
+
100
+ def change_case(text: str, mode: str) -> str:
101
+ """تغییر حالت حروف متن.
102
+
103
+ حالت‌های پشتیبانی‌شده: upper, lower, title, capitalize, swap
104
+ """
105
+ if mode == "upper":
106
+ return text.upper()
107
+ if mode == "lower":
108
+ return text.lower()
109
+ if mode == "title":
110
+ return text.title()
111
+ if mode == "capitalize":
112
+ return text.capitalize()
113
+ if mode == "swap":
114
+ return text.swapcase()
115
+ raise ValueError(f"حالت ناشناخته: {mode}. مجاز: {', '.join(_CASE_MODES)}")
116
+
117
+
118
+ #diff
119
+
120
+
121
+ def diff_lines(a: str, b: str) -> list[tuple[str, str]]:
122
+ """مقایسهٔ خطی دو متن.
123
+
124
+ خروجی: لیستی از (علامت, خط) که علامت یکی از ' ', '-', '+' است.
125
+ """
126
+ import difflib
127
+
128
+ a_lines = a.splitlines(keepends=False)
129
+ b_lines = b.splitlines(keepends=False)
130
+ result: list[tuple[str, str]] = []
131
+ for line in difflib.unified_diff(a_lines, b_lines, lineterm="", n=0):
132
+ if line.startswith("---") or line.startswith("+++"):
133
+ continue
134
+ if line.startswith("@@"):
135
+ result.append((" ", line))
136
+ elif line.startswith("-"):
137
+ result.append(("-", line[1:]))
138
+ elif line.startswith("+"):
139
+ result.append(("+", line[1:]))
140
+ else:
141
+ result.append((" ", line[1:] if line.startswith(" ") else line))
142
+ return result
143
+
144
+
145
+ #regex
146
+
147
+
148
+ def regex_test(pattern: str, text: str) -> dict:
149
+ """اجرای regex روی متن و برگرداندن نتایج.
150
+
151
+ خروجی: دیکشنری شامل match, groups و تعداد.
152
+ """
153
+ import re as _re
154
+
155
+ try:
156
+ compiled = _re.compile(pattern)
157
+ except _re.error as exc:
158
+ raise ValueError(f"الگوی regex نامعتبر: {exc}") from exc
159
+
160
+ matches = compiled.findall(text)
161
+ return {
162
+ "pattern": pattern,
163
+ "count": len(matches),
164
+ "matches": [str(m) for m in matches],
165
+ "is_match": bool(compiled.search(text)),
166
+ }
@@ -0,0 +1,81 @@
1
+ """توابع کمکی کار با زمان و timestamp."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from datetime import datetime, timezone
6
+
7
+ _DURATION_RE = re.compile(
8
+ r"(?P<value>\d+)\s*(?P<unit>s|m|h|d|w|ثانیه|دقیقه|ساعت|روز|هفته)",
9
+ re.IGNORECASE,
10
+ )
11
+
12
+ _UNIT_SECONDS = {
13
+ "s": 1,
14
+ "m": 60,
15
+ "h": 3600,
16
+ "d": 86400,
17
+ "w": 604800,
18
+ "ثانیه": 1,
19
+ "دقیقه": 60,
20
+ "ساعت": 3600,
21
+ "روز": 86400,
22
+ "هفته": 604800,
23
+ }
24
+
25
+
26
+ def now_unix() -> int:
27
+ """timestamp فعلی به ثانیه."""
28
+ return int(datetime.now(tz=timezone.utc).timestamp())
29
+
30
+
31
+ def to_unix(dt: datetime) -> int:
32
+ """تبدیل datetime به timestamp (اگر timezone نداشته باشد، UTC فرض می‌شود)."""
33
+ if dt.tzinfo is None:
34
+ dt = dt.replace(tzinfo=timezone.utc)
35
+ return int(dt.timestamp())
36
+
37
+
38
+ def from_unix(ts: int) -> datetime:
39
+ """تبدیل timestamp به datetime (UTC)."""
40
+ return datetime.fromtimestamp(ts, tz=timezone.utc)
41
+
42
+
43
+ def parse_duration(text: str) -> int:
44
+ """تبدیل رشتهٔ مدت زمان (مثل '2h30m' یا '2 ساعت و 30 دقیقه') به ثانیه."""
45
+ if not text or not text.strip():
46
+ raise ValueError("رشتهٔ مدت زمان خالی است.")
47
+
48
+ total = 0
49
+ found = False
50
+ for match in _DURATION_RE.finditer(text):
51
+ value = int(match.group("value"))
52
+ unit = match.group("unit").lower()
53
+ if unit not in _UNIT_SECONDS:
54
+ raise ValueError(f"واحد ناشناخته: {unit}")
55
+ total += value * _UNIT_SECONDS[unit]
56
+ found = True
57
+
58
+ if not found:
59
+ raise ValueError(f"فرمت مدت زمان نامعتبر: {text}")
60
+ return total
61
+
62
+
63
+ def humanize_duration(seconds: int) -> str:
64
+ """تبدیل ثانیه به رشتهٔ خوانا (فارسی)."""
65
+ if seconds < 0:
66
+ return "منفی " + humanize_duration(-seconds)
67
+ if seconds == 0:
68
+ return "۰ ثانیه"
69
+
70
+ parts: list[str] = []
71
+ for label, unit in (
72
+ ("هفته", 604800),
73
+ ("روز", 86400),
74
+ ("ساعت", 3600),
75
+ ("دقیقه", 60),
76
+ ("ثانیه", 1),
77
+ ):
78
+ if seconds >= unit:
79
+ value, seconds = divmod(seconds, unit)
80
+ parts.append(f"{value} {label}")
81
+ return " و ".join(parts)
@@ -0,0 +1,54 @@
1
+ """توابع کمکی کار با URL."""
2
+ from __future__ import annotations
3
+
4
+ from urllib.parse import (
5
+ parse_qs,
6
+ parse_qsl,
7
+ quote,
8
+ unquote,
9
+ urlencode,
10
+ urlparse,
11
+ urlunparse,
12
+ )
13
+
14
+
15
+ def url_encode(text: str, safe: str = "") -> str:
16
+ """درصد-رمزگذاری متن برای استفاده در URL."""
17
+ return quote(text, safe=safe)
18
+
19
+
20
+ def url_decode(text: str) -> str:
21
+ """رمزگشایی متن درصد-رمزگذاری‌شده."""
22
+ return unquote(text)
23
+
24
+
25
+ def parse_url(url: str) -> dict:
26
+ """تجزیهٔ URL به اجزای سازنده."""
27
+ parsed = urlparse(url)
28
+ query = dict(parse_qsl(parsed.query, keep_blank_values=True))
29
+ return {
30
+ "scheme": parsed.scheme,
31
+ "netloc": parsed.netloc,
32
+ "host": parsed.hostname or "",
33
+ "port": parsed.port,
34
+ "path": parsed.path,
35
+ "query": query,
36
+ "fragment": parsed.fragment,
37
+ "username": parsed.username,
38
+ "password": parsed.password,
39
+ }
40
+
41
+
42
+ def build_query(params: dict[str, str]) -> str:
43
+ """ساخت query string از دیکشنری."""
44
+ return urlencode(params)
45
+
46
+
47
+ def encode_query(params: dict[str, str]) -> str:
48
+ """مترادف build_query برای خوانایی."""
49
+ return build_query(params)
50
+
51
+
52
+ def decode_query(text: str) -> dict[str, list[str]]:
53
+ """تجزیهٔ query string به دیکشنری (مقادیر چندگانه → لیست)."""
54
+ return parse_qs(text, keep_blank_values=True)