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,4 @@
1
+ """persian-devkit — جعبه‌ابزار خط فرمان برای توسعه‌دهندگان فارسی‌زبان."""
2
+
3
+ __version__ = "1.0.0"
4
+ __all__ = ["__version__"]
@@ -0,0 +1,5 @@
1
+ """اجرای پکیج با `python -m persian_devkit`."""
2
+ from persian_devkit.main import app
3
+
4
+ if __name__ == "__main__":
5
+ app()
persian_devkit/cli.py ADDED
@@ -0,0 +1,55 @@
1
+ """ثبت تمام زیرفرمان‌ها روی اپ اصلی Typer."""
2
+ from __future__ import annotations
3
+
4
+ import typer
5
+
6
+ from persian_devkit.commands import (
7
+ base64_cmd, color_cmd, csv_cmd, date_cmd, env_cmd, gitignore_cmd,
8
+ hash_cmd, image_cmd, jq_cmd, json_cmd, license_cmd, lorem_cmd,
9
+ name_cmd, number_cmd, password_cmd, qrcode_cmd, random_cmd,
10
+ scaffold_cmd, template_cmd, text_cmd, time_cmd, toml_cmd,
11
+ url_cmd, uuid_cmd, yaml_cmd,
12
+ )
13
+ from persian_devkit.commands.jq_cmd import jq_command
14
+ from persian_devkit.commands.scaffold_cmd import scaffold_command
15
+ from persian_devkit.commands.template_cmd import template_command
16
+
17
+
18
+ def register_commands(app: typer.Typer) -> None:
19
+ """همهٔ زیرفرمان‌ها را به اپ اضافه می‌کند."""
20
+ # فارسی و عمومی (Batch 1-2)
21
+ app.add_typer(date_cmd.app, name="date", help="📅 تاریخ.")
22
+ app.add_typer(number_cmd.app, name="number", help="🔢 اعداد.")
23
+ app.add_typer(text_cmd.app, name="text", help="✍️ متن.")
24
+ app.add_typer(uuid_cmd.app, name="uuid", help="🆔 UUID.")
25
+ app.add_typer(json_cmd.app, name="json", help="🗂️ JSON.")
26
+ app.add_typer(base64_cmd.app, name="base64", help="🔐 Base64.")
27
+ app.add_typer(hash_cmd.app, name="hash", help="🧮 هش.")
28
+ app.add_typer(password_cmd.app, name="password", help="🔑 رمز.")
29
+ app.add_typer(random_cmd.app, name="random", help="🎲 تصادفی.")
30
+
31
+ # شبکه و وب (Batch 3)
32
+ app.add_typer(url_cmd.app, name="url", help="🔗 URL.")
33
+ app.add_typer(time_cmd.app, name="time", help="⏱ زمان.")
34
+
35
+ # بصری (Batch 3)
36
+ app.add_typer(color_cmd.app, name="color", help="🎨 رنگ.")
37
+ app.add_typer(image_cmd.app, name="image", help="🖼 تصویر.")
38
+ app.command(name="qrcode", help="📱 QR Code.")(qrcode_cmd.qrcode_command)
39
+
40
+ # داده (Batch 4-5)
41
+ app.add_typer(yaml_cmd.app, name="yaml", help="📄 YAML.")
42
+ app.add_typer(toml_cmd.app, name="toml", help="⚙️ TOML.")
43
+ app.add_typer(csv_cmd.app, name="csv", help="📊 CSV.")
44
+ app.add_typer(env_cmd.app, name="env", help="🔐 .env.")
45
+ app.command(name="jq", help="🔍 کوئری JSON.")(jq_command)
46
+
47
+ # دادهٔ نمونه (Batch 5)
48
+ app.add_typer(lorem_cmd.app, name="lorem", help="📝 متن نمونهٔ فارسی.")
49
+ app.add_typer(name_cmd.app, name="name", help="👤 نام نمونهٔ فارسی.")
50
+
51
+ # ابزار پروژه (Batch 6)
52
+ app.add_typer(license_cmd.app, name="license", help="📜 لایسنس.")
53
+ app.add_typer(gitignore_cmd.app, name="gitignore", help="🚫 .gitignore.")
54
+ app.command(name="scaffold", help="🏗 ساخت پروژه از قالب.")(scaffold_command)
55
+ app.command(name="template", help="📋 رندر قالب متنی.")(template_command)
@@ -0,0 +1,16 @@
1
+ """زیرفرمان‌های pdev."""
2
+ from persian_devkit.commands import (
3
+ base64_cmd, color_cmd, csv_cmd, date_cmd, env_cmd, gitignore_cmd,
4
+ hash_cmd, image_cmd, jq_cmd, json_cmd, license_cmd, lorem_cmd,
5
+ name_cmd, number_cmd, password_cmd, qrcode_cmd, random_cmd,
6
+ scaffold_cmd, template_cmd, text_cmd, time_cmd, toml_cmd,
7
+ url_cmd, uuid_cmd, yaml_cmd,
8
+ )
9
+
10
+ __all__ = [
11
+ "base64_cmd", "color_cmd", "csv_cmd", "date_cmd", "env_cmd",
12
+ "gitignore_cmd", "hash_cmd", "image_cmd", "jq_cmd", "json_cmd",
13
+ "license_cmd", "lorem_cmd", "name_cmd", "number_cmd", "password_cmd",
14
+ "qrcode_cmd", "random_cmd", "scaffold_cmd", "template_cmd",
15
+ "text_cmd", "time_cmd", "toml_cmd", "url_cmd", "uuid_cmd", "yaml_cmd",
16
+ ]
@@ -0,0 +1,51 @@
1
+ """دستور pdev base64 — رمزگذاری و رمزگشایی Base64."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+ from typing import Optional
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from persian_devkit.utils.crypto_utils import b64_decode, b64_encode
11
+
12
+ app = typer.Typer(help="رمزگذاری و رمزگشایی Base64.", no_args_is_help=True)
13
+ console = Console()
14
+
15
+
16
+ def _read(value: Optional[str]) -> str:
17
+ """متن را از آرگومان یا stdin می‌خواند."""
18
+ if value is not None and value != "":
19
+ return value
20
+ if not sys.stdin.isatty():
21
+ data = sys.stdin.read()
22
+ if data.strip():
23
+ return data.rstrip("\n")
24
+ console.print("[red]✗ خطا:[/red] متنی وارد نشده.")
25
+ raise typer.Exit(1)
26
+
27
+
28
+ @app.command("encode")
29
+ def encode_cmd(
30
+ text: Optional[str] = typer.Argument(None, help="متن ورودی (یا از stdin)."),
31
+ url_safe: bool = typer.Option(
32
+ False, "--url-safe", "-u", help="استفاده از الفبای URL-safe."
33
+ ),
34
+ ) -> None:
35
+ """رمزگذاری متن به Base64."""
36
+ console.print(b64_encode(_read(text), url_safe=url_safe))
37
+
38
+
39
+ @app.command("decode")
40
+ def decode_cmd(
41
+ text: Optional[str] = typer.Argument(None, help="رشتهٔ Base64 (یا از stdin)."),
42
+ url_safe: bool = typer.Option(
43
+ False, "--url-safe", "-u", help="ورودی با الفبای URL-safe."
44
+ ),
45
+ ) -> None:
46
+ """رمزگشایی Base64 به متن."""
47
+ try:
48
+ console.print(b64_decode(_read(text), url_safe=url_safe))
49
+ except ValueError as e:
50
+ console.print(f"[red]✗ خطا:[/red] {e}")
51
+ raise typer.Exit(1)
@@ -0,0 +1,143 @@
1
+ """دستور pdev color — کار با رنگ‌ها (HEX, RGB, HSL)."""
2
+ from __future__ import annotations
3
+
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+ from rich.text import Text
8
+
9
+ from persian_devkit.utils.color_utils import (
10
+ best_text_color,
11
+ complementary,
12
+ contrast_ratio,
13
+ generate_palette,
14
+ parse_color,
15
+ pleasant_color,
16
+ random_color,
17
+ rgb_to_hex,
18
+ rgb_to_hsl,
19
+ )
20
+
21
+ app = typer.Typer(help="کار با رنگ‌ها (HEX, RGB, HSL).", no_args_is_help=True)
22
+ console = Console()
23
+
24
+
25
+ def _fail(message: str) -> None:
26
+ console.print(f"[red]✗ خطا:[/red] {message}")
27
+ raise typer.Exit(1)
28
+
29
+
30
+ def _swatch(rgb: tuple[int, int, int], text: str = " ") -> Text:
31
+ """نمایش یک بلوک رنگی در ترمینال."""
32
+ r, g, b = rgb
33
+ fg = best_text_color(rgb)
34
+ return Text(text, style=f"{fg} on rgb({r},{g},{b})")
35
+
36
+
37
+ @app.command("convert")
38
+ def convert_cmd(
39
+ color: str = typer.Argument(
40
+ ..., help="رنگ: #fff، #ffffff، rgb(255,0,0) یا 255,0,0"
41
+ ),
42
+ ) -> None:
43
+ """تبدیل رنگ بین فرمت‌های HEX، RGB و HSL."""
44
+ try:
45
+ rgb = parse_color(color)
46
+ except ValueError as e:
47
+ _fail(str(e))
48
+
49
+ h, s, l = rgb_to_hsl(rgb)
50
+
51
+ table = Table(title=f"🎨 {color}", title_style="bold cyan", show_header=False)
52
+ table.add_column("فرمت", style="bold")
53
+ table.add_column("مقدار")
54
+ table.add_row("HEX", rgb_to_hex(rgb))
55
+ table.add_row("RGB", f"rgb({rgb[0]}, {rgb[1]}, {rgb[2]})")
56
+ table.add_row("HSL", f"hsl({h:.0f}, {s * 100:.0f}%, {l * 100:.0f}%)")
57
+ table.add_row("روشنایی", f"{l * 100:.1f}%")
58
+ table.add_row("نمایش", _swatch(rgb, " "))
59
+ console.print(table)
60
+
61
+
62
+ @app.command("palette")
63
+ def palette_cmd(
64
+ color: str = typer.Argument(..., help="رنگ پایه."),
65
+ count: int = typer.Option(5, "--count", "-c", min=2, max=12, help="تعداد."),
66
+ ) -> None:
67
+ """تولید پالت رنگی از یک رنگ پایه."""
68
+ try:
69
+ base = parse_color(color)
70
+ except ValueError as e:
71
+ _fail(str(e))
72
+
73
+ palette = generate_palette(base, count=count)
74
+
75
+ table = Table(title="🎨 پالت رنگ", title_style="bold cyan")
76
+ table.add_column("نمونه", justify="center")
77
+ table.add_column("HEX")
78
+ table.add_column("RGB")
79
+
80
+ for c in palette:
81
+ table.add_row(
82
+ _swatch(c, " "),
83
+ rgb_to_hex(c),
84
+ f"{c[0]:3d}, {c[1]:3d}, {c[2]:3d}",
85
+ )
86
+ console.print(table)
87
+
88
+
89
+ @app.command("random")
90
+ def random_cmd(
91
+ count: int = typer.Option(1, "--count", "-c", min=1, max=20, help="تعداد."),
92
+ pleasant: bool = typer.Option(
93
+ False, "--pleasant", "-p", help="رنگ‌های دلپذیر (اشباع کنترل‌شده)."
94
+ ),
95
+ ) -> None:
96
+ """تولید رنگ تصادفی."""
97
+ for _ in range(count):
98
+ rgb = pleasant_color() if pleasant else random_color()
99
+ console.print(f"{_swatch(rgb, ' ')} {rgb_to_hex(rgb)} rgb{rgb}")
100
+
101
+
102
+ @app.command("contrast")
103
+ def contrast_cmd(
104
+ a: str = typer.Argument(..., help="رنگ اول."),
105
+ b: str = typer.Argument(..., help="رنگ دوم."),
106
+ ) -> None:
107
+ """محاسبهٔ نسبت کنتراست بین دو رنگ (استاندارد WCAG)."""
108
+ try:
109
+ ca = parse_color(a)
110
+ cb = parse_color(b)
111
+ except ValueError as e:
112
+ _fail(str(e))
113
+
114
+ ratio = contrast_ratio(ca, cb)
115
+
116
+ if ratio >= 7:
117
+ level = "[green]AAA ✓[/green]"
118
+ elif ratio >= 4.5:
119
+ level = "[green]AA ✓[/green]"
120
+ elif ratio >= 3:
121
+ level = "[yellow]A (فقط متن بزرگ)[/yellow]"
122
+ else:
123
+ level = "[red]قابل قبول نیست[/red]"
124
+
125
+ console.print(f"نسبت کنتراست: [bold]{ratio:.2f}:1[/bold]")
126
+ console.print(f"سطح WCAG: {level}")
127
+
128
+
129
+ @app.command("complement")
130
+ def complement_cmd(
131
+ color: str = typer.Argument(..., help="رنگ پایه."),
132
+ ) -> None:
133
+ """نمایش رنگ مکمل."""
134
+ try:
135
+ rgb = parse_color(color)
136
+ except ValueError as e:
137
+ _fail(str(e))
138
+
139
+ comp = complementary(rgb)
140
+ console.print(
141
+ f"پایه: {_swatch(rgb, ' ')} {rgb_to_hex(rgb)}\n"
142
+ f"مکمل: {_swatch(comp, ' ')} {rgb_to_hex(comp)}"
143
+ )
@@ -0,0 +1,121 @@
1
+ """دستور pdev csv — خواندن و تبدیل فایل‌های CSV."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from persian_devkit.utils.data_utils import dump_json, read_csv, write_csv
13
+
14
+ app = typer.Typer(help="خواندن و تبدیل فایل‌های CSV.", no_args_is_help=True)
15
+ console = Console()
16
+
17
+
18
+ def _fail(msg: str) -> None:
19
+ console.print(f"[red]✗ خطا:[/red] {msg}")
20
+ raise typer.Exit(1)
21
+
22
+
23
+ def _load(path: Path, delimiter: str):
24
+ if not path.exists():
25
+ _fail(f"فایل یافت نشد: {path}")
26
+ try:
27
+ return read_csv(path, delimiter=delimiter)
28
+ except Exception as e:
29
+ _fail(str(e))
30
+
31
+
32
+ @app.command("show")
33
+ def show_cmd(
34
+ path: Path = typer.Argument(..., help="مسیر فایل CSV."),
35
+ delimiter: str = typer.Option(",", "--delimiter", "-d"),
36
+ limit: int = typer.Option(20, "--limit", "-n", min=1, help="حداکثر ردیف."),
37
+ ) -> None:
38
+ """نمایش جدولی محتوای CSV."""
39
+ headers, rows = _load(path, delimiter)
40
+ if not headers:
41
+ console.print("[yellow]⚠ فایل خالی است.[/yellow]")
42
+ return
43
+
44
+ table = Table(title=f" {path.name}", title_style="bold cyan")
45
+ for h in headers:
46
+ table.add_column(h)
47
+ for row in rows[:limit]:
48
+ table.add_row(*[str(row.get(h, "")) for h in headers])
49
+ console.print(table)
50
+
51
+ if len(rows) > limit:
52
+ console.print(f"[dim]... و {len(rows) - limit} ردیف دیگر[/dim]")
53
+
54
+
55
+ @app.command("to-json")
56
+ def to_json_cmd(
57
+ path: Path = typer.Argument(..., help="مسیر فایل CSV."),
58
+ delimiter: str = typer.Option(",", "--delimiter", "-d"),
59
+ output: Optional[Path] = typer.Option(None, "--output", "-o"),
60
+ indent: int = typer.Option(2, "--indent", "-i", min=0, max=10),
61
+ ) -> None:
62
+ """تبدیل CSV به آرایه‌ای از اشیاء JSON."""
63
+ _, rows = _load(path, delimiter)
64
+ text = dump_json(rows, indent=indent)
65
+ if output:
66
+ output.write_text(text, encoding="utf-8")
67
+ console.print(f"[green]✓ ذخیره شد:[/green] {output}")
68
+ else:
69
+ console.print(text, highlight=False)
70
+
71
+
72
+ @app.command("columns")
73
+ def columns_cmd(
74
+ path: Path = typer.Argument(..., help="مسیر فایل CSV."),
75
+ delimiter: str = typer.Option(",", "--delimiter", "-d"),
76
+ ) -> None:
77
+ """نمایش نام ستون‌ها و تعداد ردیف‌ها."""
78
+ headers, rows = _load(path, delimiter)
79
+ table = Table(title=" ستون‌ها", title_style="bold cyan")
80
+ table.add_column("ستون", style="bold")
81
+ table.add_column("نمونه", style="dim")
82
+ for h in headers:
83
+ sample = str(rows[0].get(h, "")) if rows else ""
84
+ table.add_row(h, sample[:40])
85
+ console.print(table)
86
+ console.print(f"تعداد ردیف: [bold]{len(rows)}[/bold]")
87
+
88
+
89
+ @app.command("filter")
90
+ def filter_cmd(
91
+ path: Path = typer.Argument(..., help="مسیر فایل CSV."),
92
+ column: str = typer.Argument(..., help="نام ستون."),
93
+ pattern: str = typer.Argument(..., help="الگوی regex برای فیلتر."),
94
+ delimiter: str = typer.Option(",", "--delimiter", "-d"),
95
+ ignore_case: bool = typer.Option(False, "--ignore-case", "-i"),
96
+ output: Optional[Path] = typer.Option(None, "--output", "-o"),
97
+ ) -> None:
98
+ """فیلتر ردیف‌ها بر اساس regex روی یک ستون."""
99
+ headers, rows = _load(path, delimiter)
100
+ if column not in headers:
101
+ _fail(f"ستون یافت نشد: {column}")
102
+
103
+ flags = re.IGNORECASE if ignore_case else 0
104
+ try:
105
+ rx = re.compile(pattern, flags)
106
+ except re.error as e:
107
+ _fail(f"regex نامعتبر: {e}")
108
+
109
+ kept = [r for r in rows if rx.search(str(r.get(column, "")))]
110
+ console.print(f"ردیف‌های مطابق: [bold]{len(kept)}[/bold] از {len(rows)}")
111
+
112
+ if output:
113
+ write_csv(output, headers, kept, delimiter=delimiter)
114
+ console.print(f"[green] ذخیره شد:[/green] {output}")
115
+ else:
116
+ table = Table(title=f" {column} ~ {pattern}", title_style="bold cyan")
117
+ for h in headers:
118
+ table.add_column(h)
119
+ for r in kept[:50]:
120
+ table.add_row(*[str(r.get(h, "")) for h in headers])
121
+ console.print(table)
@@ -0,0 +1,98 @@
1
+ """دستور pdev date — تبدیل و محاسبهٔ تاریخ شمسی و میلادی."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import date, datetime
5
+
6
+ import jdatetime
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from persian_devkit.utils.date_utils import (
12
+ DateParseError,
13
+ diff_days,
14
+ format_gregorian,
15
+ format_jalali,
16
+ gregorian_to_jalali,
17
+ jalali_to_gregorian,
18
+ parse_gregorian,
19
+ parse_jalali,
20
+ )
21
+
22
+ app = typer.Typer(help="تبدیل و محاسبهٔ تاریخ شمسی/میلادی.", no_args_is_help=True)
23
+ console = Console()
24
+
25
+ _WEEKDAYS_FA = ["شنبه", "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه"]
26
+
27
+
28
+ def _fail(message: str) -> None:
29
+ console.print(f"[red]✗ خطا:[/red] {message}")
30
+ raise typer.Exit(1)
31
+
32
+
33
+ @app.command("to-jalali")
34
+ def to_jalali(
35
+ value: str = typer.Argument(..., help="تاریخ میلادی به شکل YYYY-MM-DD."),
36
+ ) -> None:
37
+ """تبدیل تاریخ میلادی به شمسی."""
38
+ try:
39
+ g = parse_gregorian(value)
40
+ except DateParseError as e:
41
+ _fail(str(e))
42
+ j = gregorian_to_jalali(g)
43
+ console.print(f"[green]{format_jalali(j)}[/green]")
44
+
45
+
46
+ @app.command("to-gregorian")
47
+ def to_gregorian(
48
+ value: str = typer.Argument(..., help="تاریخ شمسی به شکل YYYY/MM/DD یا YYYY-MM-DD."),
49
+ ) -> None:
50
+ """تبدیل تاریخ شمسی به میلادی."""
51
+ try:
52
+ j = parse_jalali(value)
53
+ except DateParseError as e:
54
+ _fail(str(e))
55
+ g = jalali_to_gregorian(j)
56
+ console.print(f"[green]{format_gregorian(g)}[/green]")
57
+
58
+
59
+ @app.command("now")
60
+ def now_cmd() -> None:
61
+ """نمایش تاریخ و ساعت فعلی به هر دو تقویم."""
62
+ now = datetime.now()
63
+ j = jdatetime.datetime.fromgregorian(datetime=now)
64
+
65
+ table = Table(title=" اکنون", show_header=False, title_style="bold cyan")
66
+ table.add_column("تقویم", style="bold")
67
+ table.add_column("مقدار")
68
+ table.add_row("میلادی", now.strftime("%Y-%m-%d %H:%M:%S"))
69
+ table.add_row("شمسی", j.strftime("%Y/%m/%d %H:%M:%S"))
70
+ table.add_row("روز هفته", _WEEKDAYS_FA[j.weekday()])
71
+ console.print(table)
72
+
73
+
74
+ def _auto_parse(value: str) -> date:
75
+ """تشخیص خودکار نوع تاریخ: اگر / داشت شمسی، وگرنه میلادی."""
76
+ if "/" in value:
77
+ return jalali_to_gregorian(parse_jalali(value))
78
+ return parse_gregorian(value)
79
+
80
+
81
+ @app.command("diff")
82
+ def diff_cmd(
83
+ a: str = typer.Argument(..., help="تاریخ اول (شمسی یا میلادی)."),
84
+ b: str = typer.Argument(..., help="تاریخ دوم (شمسی یا میلادی)."),
85
+ ) -> None:
86
+ """محاسبهٔ اختلاف بین دو تاریخ (به روز)."""
87
+ try:
88
+ da = _auto_parse(a)
89
+ db = _auto_parse(b)
90
+ except DateParseError as e:
91
+ _fail(str(e))
92
+
93
+ days = diff_days(da, db)
94
+ sign = "بعد" if days >= 0 else "قبل"
95
+ console.print(
96
+ f"[cyan]اختلاف:[/cyan] [bold]{abs(days)}[/bold] روز "
97
+ f"({b} {sign} از {a})"
98
+ )
@@ -0,0 +1,100 @@
1
+ """دستور pdev env — کار با فایل‌های .env."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from persian_devkit.utils.data_utils import dump_env, parse_env, write_text
12
+
13
+ app = typer.Typer(help="کار با فایل‌های .env.", no_args_is_help=True)
14
+ console = Console()
15
+
16
+
17
+ def _fail(msg: str) -> None:
18
+ console.print(f"[red]✗ خطا:[/red] {msg}")
19
+ raise typer.Exit(1)
20
+
21
+
22
+ def _load(path: Path) -> dict[str, str]:
23
+ if not path.exists():
24
+ _fail(f"فایل یافت نشد: {path}")
25
+ return parse_env(path.read_text(encoding="utf-8"))
26
+
27
+
28
+ @app.command("show")
29
+ def show_cmd(
30
+ path: Path = typer.Argument(Path(".env"), help="مسیر فایل env."),
31
+ reveal: bool = typer.Option(
32
+ False, "--reveal", "-r", help="نمایش مقادیر (پیش‌فرض مخفی)."
33
+ ),
34
+ ) -> None:
35
+ """نمایش متغیرهای .env (مقادیر به‌صورت پیش‌فرض مخفی)."""
36
+ data = _load(path)
37
+ if not data:
38
+ console.print("[yellow] فایل خالی است.[/yellow]")
39
+ return
40
+
41
+ table = Table(title=f" {path.name}", title_style="bold cyan")
42
+ table.add_column("کلید", style="bold")
43
+ table.add_column("مقدار")
44
+ for k, v in data.items():
45
+ if not reveal and any(s in k.upper() for s in ("PASS", "TOKEN", "SECRET", "KEY")):
46
+ v = "***"
47
+ table.add_row(k, v)
48
+ console.print(table)
49
+
50
+
51
+ @app.command("get")
52
+ def get_cmd(
53
+ key: str = typer.Argument(..., help="نام متغیر."),
54
+ path: Path = typer.Option(Path(".env"), "--file", "-f", help="مسیر فایل env."),
55
+ ) -> None:
56
+ """خواندن یک متغیر خاص."""
57
+ data = _load(path)
58
+ if key not in data:
59
+ console.print(f"[red]✗ خطا:[/red] متغیر یافت نشد: {key}")
60
+ raise typer.Exit(1)
61
+ console.print(data[key])
62
+
63
+
64
+ @app.command("to-json")
65
+ def to_json_cmd(
66
+ path: Path = typer.Argument(Path(".env"), help="مسیر فایل env."),
67
+ ) -> None:
68
+ """تبدیل .env به JSON."""
69
+ import json
70
+
71
+ data = _load(path)
72
+ console.print(json.dumps(data, ensure_ascii=False, indent=2), highlight=False)
73
+
74
+
75
+ @app.command("to-shell")
76
+ def to_shell_cmd(
77
+ path: Path = typer.Argument(Path(".env"), help="مسیر فایل env."),
78
+ export: bool = typer.Option(True, "--export/--no-export"),
79
+ ) -> None:
80
+ """تبدیل .env به دستورات export برای bash/zsh."""
81
+ data = _load(path)
82
+ prefix = "export " if export else ""
83
+ for k, v in data.items():
84
+ console.print(f"{prefix}{k}={v!r}")
85
+
86
+
87
+ @app.command("sort")
88
+ def sort_cmd(
89
+ path: Path = typer.Argument(..., help="مسیر فایل env."),
90
+ output: Optional[Path] = typer.Option(None, "--output", "-o"),
91
+ ) -> None:
92
+ """مرتب‌سازی کلیدهای .env به ترتیب الفبا."""
93
+ data = _load(path)
94
+ sorted_data = dict(sorted(data.items()))
95
+ text = dump_env(sorted_data)
96
+ if output:
97
+ write_text(output, text)
98
+ console.print(f"[green] ذخیره شد:[/green] {output}")
99
+ else:
100
+ console.print(text, highlight=False)
@@ -0,0 +1,68 @@
1
+ """دستور pdev gitignore — تولید فایل .gitignore."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ import typer
7
+ from rich.console import Console
8
+
9
+ from persian_devkit.utils.gitignore_data import (
10
+ combine_gitignores,
11
+ list_gitignores,
12
+ )
13
+
14
+ app = typer.Typer(help="تولید فایل .gitignore.", no_args_is_help=True)
15
+ console = Console()
16
+
17
+
18
+ @app.command("list")
19
+ def list_cmd() -> None:
20
+ """نمایش زبان‌ها و ابزارهای موجود."""
21
+ langs = list_gitignores()
22
+ console.print("زبان‌ها و ابزارهای پشتیبانی‌شده:")
23
+ for lang in langs:
24
+ console.print(f" • {lang}")
25
+
26
+
27
+ @app.command("show")
28
+ def show_cmd(
29
+ langs: list[str] = typer.Argument(..., help="نام یک یا چند زبان/ابزار."),
30
+ ) -> None:
31
+ """نمایش محتوای .gitignore برای زبان‌های داده‌شده."""
32
+ try:
33
+ console.print(combine_gitignores(langs), highlight=False)
34
+ except ValueError as e:
35
+ console.print(f"[red]✗ خطا:[/red] {e}")
36
+ raise typer.Exit(1)
37
+
38
+
39
+ @app.command("new")
40
+ def new_cmd(
41
+ langs: list[str] = typer.Argument(..., help="نام یک یا چند زبان/ابزار."),
42
+ output: Path = typer.Option(
43
+ Path(".gitignore"), "--output", "-o", help="مسیر فایل خروجی."
44
+ ),
45
+ append: bool = typer.Option(
46
+ False, "--append", "-a", help="افزودن به فایل موجود."
47
+ ),
48
+ ) -> None:
49
+ """ساخت فایل .gitignore با ترکیب چند زبان."""
50
+ try:
51
+ text = combine_gitignores(langs)
52
+ except ValueError as e:
53
+ console.print(f"[red]✗ خطا:[/red] {e}")
54
+ raise typer.Exit(1)
55
+
56
+ if output.exists() and not append:
57
+ console.print(
58
+ f"[yellow]⚠ فایل موجود است:[/yellow] {output} (با --append ادغام می‌شود)"
59
+ )
60
+ raise typer.Exit(1)
61
+
62
+ if append and output.exists():
63
+ with output.open("a", encoding="utf-8") as f:
64
+ f.write("\n" + text)
65
+ else:
66
+ output.write_text(text, encoding="utf-8")
67
+
68
+ console.print(f"[green]✓ ذخیره شد:[/green] {output}")