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.
- persian_devkit/__init__.py +4 -0
- persian_devkit/__main__.py +5 -0
- persian_devkit/cli.py +55 -0
- persian_devkit/commands/__init__.py +16 -0
- persian_devkit/commands/base64_cmd.py +51 -0
- persian_devkit/commands/color_cmd.py +143 -0
- persian_devkit/commands/csv_cmd.py +121 -0
- persian_devkit/commands/date_cmd.py +98 -0
- persian_devkit/commands/env_cmd.py +100 -0
- persian_devkit/commands/gitignore_cmd.py +68 -0
- persian_devkit/commands/hash_cmd.py +71 -0
- persian_devkit/commands/image_cmd.py +88 -0
- persian_devkit/commands/jq_cmd.py +92 -0
- persian_devkit/commands/json_cmd.py +135 -0
- persian_devkit/commands/license_cmd.py +67 -0
- persian_devkit/commands/lorem_cmd.py +53 -0
- persian_devkit/commands/name_cmd.py +98 -0
- persian_devkit/commands/number_cmd.py +65 -0
- persian_devkit/commands/password_cmd.py +55 -0
- persian_devkit/commands/qrcode_cmd.py +46 -0
- persian_devkit/commands/random_cmd.py +110 -0
- persian_devkit/commands/scaffold_cmd.py +155 -0
- persian_devkit/commands/template_cmd.py +87 -0
- persian_devkit/commands/text_cmd.py +157 -0
- persian_devkit/commands/time_cmd.py +96 -0
- persian_devkit/commands/toml_cmd.py +85 -0
- persian_devkit/commands/url_cmd.py +102 -0
- persian_devkit/commands/uuid_cmd.py +45 -0
- persian_devkit/commands/yaml_cmd.py +94 -0
- persian_devkit/main.py +63 -0
- persian_devkit/py.typed +0 -0
- persian_devkit/utils/__init__.py +1 -0
- persian_devkit/utils/color_utils.py +112 -0
- persian_devkit/utils/crypto_utils.py +157 -0
- persian_devkit/utils/data_utils.py +146 -0
- persian_devkit/utils/date_utils.py +58 -0
- persian_devkit/utils/fake_utils.py +144 -0
- persian_devkit/utils/gitignore_data.py +211 -0
- persian_devkit/utils/image_utils.py +37 -0
- persian_devkit/utils/jsonpath_utils.py +94 -0
- persian_devkit/utils/license_data.py +190 -0
- persian_devkit/utils/number_utils.py +78 -0
- persian_devkit/utils/qrcode_utils.py +84 -0
- persian_devkit/utils/text_utils.py +166 -0
- persian_devkit/utils/time_utils.py +81 -0
- persian_devkit/utils/url_utils.py +54 -0
- persian_devkit-1.0.0.dist-info/METADATA +702 -0
- persian_devkit-1.0.0.dist-info/RECORD +51 -0
- persian_devkit-1.0.0.dist-info/WHEEL +4 -0
- persian_devkit-1.0.0.dist-info/entry_points.txt +2 -0
- persian_devkit-1.0.0.dist-info/licenses/LICENSE.txt +21 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""دستور pdev hash — محاسبهٔ هش متن یا فایل."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
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.crypto_utils import HASH_ALGOS, hash_file, hash_text
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="محاسبهٔ هش متن یا فایل.", no_args_is_help=True)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _read(value: Optional[str]) -> str:
|
|
19
|
+
if value is not None and value != "":
|
|
20
|
+
return value
|
|
21
|
+
if not sys.stdin.isatty():
|
|
22
|
+
data = sys.stdin.read()
|
|
23
|
+
if data.strip():
|
|
24
|
+
return data.rstrip("\n")
|
|
25
|
+
console.print("[red]✗ خطا:[/red] متنی وارد نشده.")
|
|
26
|
+
raise typer.Exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("text")
|
|
30
|
+
def text_cmd(
|
|
31
|
+
text: Optional[str] = typer.Argument(None, help="متن ورودی (یا از stdin)."),
|
|
32
|
+
algo: str = typer.Option(
|
|
33
|
+
"sha256", "--algo", "-a", help=f"الگوریتم: {'/'.join(HASH_ALGOS)}"
|
|
34
|
+
),
|
|
35
|
+
) -> None:
|
|
36
|
+
"""محاسبهٔ هش یک متن."""
|
|
37
|
+
if algo not in HASH_ALGOS:
|
|
38
|
+
console.print(f"[red]✗ خطا:[/red] الگوریتم ناشناخته: {algo}")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
console.print(hash_text(_read(text), algo=algo))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command("file")
|
|
44
|
+
def file_cmd(
|
|
45
|
+
path: Path = typer.Argument(..., help="مسیر فایل."),
|
|
46
|
+
algo: str = typer.Option(
|
|
47
|
+
"sha256", "--algo", "-a", help=f"الگوریتم: {'/'.join(HASH_ALGOS)}"
|
|
48
|
+
),
|
|
49
|
+
) -> None:
|
|
50
|
+
"""محاسبهٔ هش یک فایل."""
|
|
51
|
+
if not path.exists():
|
|
52
|
+
console.print(f"[red]✗ خطا:[/red] فایل یافت نشد: {path}")
|
|
53
|
+
raise typer.Exit(1)
|
|
54
|
+
if algo not in HASH_ALGOS:
|
|
55
|
+
console.print(f"[red]✗ خطا:[/red] الگوریتم ناشناخته: {algo}")
|
|
56
|
+
raise typer.Exit(1)
|
|
57
|
+
console.print(hash_file(path, algo=algo))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command("all")
|
|
61
|
+
def all_cmd(
|
|
62
|
+
text: Optional[str] = typer.Argument(None, help="متن ورودی (یا از stdin)."),
|
|
63
|
+
) -> None:
|
|
64
|
+
"""محاسبهٔ همهٔ الگوریتمها روی یک متن."""
|
|
65
|
+
value = _read(text)
|
|
66
|
+
table = Table(title=" هشها", title_style="bold cyan")
|
|
67
|
+
table.add_column("الگوریتم", style="bold")
|
|
68
|
+
table.add_column("مقدار", overflow="fold")
|
|
69
|
+
for algo in HASH_ALGOS:
|
|
70
|
+
table.add_row(algo, hash_text(value, algo=algo))
|
|
71
|
+
console.print(table)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""دستور pdev image — اطلاعات و تبدیل تصاویر."""
|
|
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.image_utils import convert_image, image_info, resize_image
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="اطلاعات و تبدیل تصاویر.", no_args_is_help=True)
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _human_size(n: int) -> str:
|
|
18
|
+
"""اندازهٔ خوانا برای بایت."""
|
|
19
|
+
value = float(n)
|
|
20
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
21
|
+
if value < 1024:
|
|
22
|
+
return f"{value:.1f} {unit}"
|
|
23
|
+
value /= 1024
|
|
24
|
+
return f"{value:.1f} TB"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command("info")
|
|
28
|
+
def info_cmd(
|
|
29
|
+
path: Path = typer.Argument(..., help="مسیر فایل تصویری."),
|
|
30
|
+
) -> None:
|
|
31
|
+
"""نمایش اطلاعات تصویر."""
|
|
32
|
+
if not path.exists():
|
|
33
|
+
console.print(f"[red]✗ خطا:[/red] فایل یافت نشد: {path}")
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
try:
|
|
36
|
+
info = image_info(path)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
console.print(f"[red]✗ خطا:[/red] {e}")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
table = Table(title=f"🖼 {path.name}", title_style="bold cyan", show_header=False)
|
|
42
|
+
table.add_column("شاخص", style="bold")
|
|
43
|
+
table.add_column("مقدار")
|
|
44
|
+
table.add_row("فرمت", info["format"])
|
|
45
|
+
table.add_row("حالت رنگی", info["mode"])
|
|
46
|
+
table.add_row("ابعاد", f"{info['width']} × {info['height']}")
|
|
47
|
+
table.add_row("اندازه", _human_size(info["size_bytes"]))
|
|
48
|
+
console.print(table)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("resize")
|
|
52
|
+
def resize_cmd(
|
|
53
|
+
src: Path = typer.Argument(..., help="تصویر ورودی."),
|
|
54
|
+
width: int = typer.Argument(..., help="عرض جدید."),
|
|
55
|
+
height: Optional[int] = typer.Argument(None, help="ارتفاع جدید (اختیاری)."),
|
|
56
|
+
output: Optional[Path] = typer.Option(None, "--output", "-o", help="مسیر خروجی."),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""تغییر اندازهٔ تصویر (اگر ارتفاع ندهی، نسبت حفظ میشود)."""
|
|
59
|
+
if not src.exists():
|
|
60
|
+
console.print(f"[red]✗ خطا:[/red] فایل یافت نشد: {src}")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
dst = output or src.with_stem(f"{src.stem}_resized")
|
|
63
|
+
try:
|
|
64
|
+
resize_image(src, dst, width, height)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
console.print(f"[red]✗ خطا:[/red] {e}")
|
|
67
|
+
raise typer.Exit(1)
|
|
68
|
+
console.print(f"[green]✓ ذخیره شد:[/green] {dst}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command("convert")
|
|
72
|
+
def convert_cmd(
|
|
73
|
+
src: Path = typer.Argument(..., help="تصویر ورودی."),
|
|
74
|
+
output: Path = typer.Argument(..., help="مسیر خروجی (فرمت از پسوند)."),
|
|
75
|
+
quality: int = typer.Option(
|
|
76
|
+
90, "--quality", "-q", min=1, max=100, help="کیفیت JPEG."
|
|
77
|
+
),
|
|
78
|
+
) -> None:
|
|
79
|
+
"""تبدیل فرمت تصویر (مثلاً PNG → JPEG)."""
|
|
80
|
+
if not src.exists():
|
|
81
|
+
console.print(f"[red]✗ خطا:[/red] فایل یافت نشد: {src}")
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
try:
|
|
84
|
+
convert_image(src, output, quality=quality)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
console.print(f"[red]✗ خطا:[/red] {e}")
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
console.print(f"[green]✓ ذخیره شد:[/green] {output}")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""دستور pdev jq — کوئری JSON با JSONPath ساده."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.syntax import Syntax
|
|
12
|
+
|
|
13
|
+
from persian_devkit.utils.jsonpath_utils import JSONPathError, query
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def jq_command(
|
|
19
|
+
path: str = typer.Argument(
|
|
20
|
+
..., help="مسیر JSONPath (مثل .user.name یا .items[0])."
|
|
21
|
+
),
|
|
22
|
+
file: Optional[Path] = typer.Option(
|
|
23
|
+
None, "--file", "-f", help="فایل JSON ورودی (یا از stdin)."
|
|
24
|
+
),
|
|
25
|
+
raw: bool = typer.Option(
|
|
26
|
+
False, "--raw", "-r", help="خروجی خام (بدون رنگ و بدون JSON encoding)."
|
|
27
|
+
),
|
|
28
|
+
compact: bool = typer.Option(False, "--compact", "-c", help="خروجی فشرده."),
|
|
29
|
+
) -> None:
|
|
30
|
+
"""کوئری روی JSON با JSONPath.
|
|
31
|
+
|
|
32
|
+
مسیرها:
|
|
33
|
+
.key → دسترسی به کلید
|
|
34
|
+
.a.b.c → تودرتو
|
|
35
|
+
.items[0] → اندیس آرایه
|
|
36
|
+
.items[*].name → همهٔ نامها
|
|
37
|
+
["key with space"] → کلید با فاصله
|
|
38
|
+
"""
|
|
39
|
+
# خواندن داده
|
|
40
|
+
if file is not None:
|
|
41
|
+
if not file.exists():
|
|
42
|
+
console.print(f"[red]✗ خطا:[/red] فایل یافت نشد: {file}")
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
text = file.read_text(encoding="utf-8")
|
|
45
|
+
else:
|
|
46
|
+
if sys.stdin.isatty():
|
|
47
|
+
console.print(
|
|
48
|
+
"[red]✗ خطا:[/red] فایل JSON بده یا از stdin استفاده کن."
|
|
49
|
+
)
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
text = sys.stdin.read()
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
data = json.loads(text)
|
|
55
|
+
except json.JSONDecodeError as e:
|
|
56
|
+
console.print(f"[red]✗ خطا:[/red] JSON نامعتبر: {e}")
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
result = query(data, path)
|
|
61
|
+
except JSONPathError as e:
|
|
62
|
+
console.print(f"[red]✗ خطا:[/red] {e}")
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
# خروجی
|
|
66
|
+
if raw:
|
|
67
|
+
if isinstance(result, str):
|
|
68
|
+
console.print(result, highlight=False)
|
|
69
|
+
elif isinstance(result, (int, float, bool)) or result is None:
|
|
70
|
+
console.print(str(result), highlight=False)
|
|
71
|
+
else:
|
|
72
|
+
console.print(
|
|
73
|
+
json.dumps(result, ensure_ascii=False), highlight=False
|
|
74
|
+
)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if compact:
|
|
78
|
+
console.print(
|
|
79
|
+
json.dumps(result, ensure_ascii=False, separators=(",", ":")),
|
|
80
|
+
highlight=False,
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if isinstance(result, str):
|
|
85
|
+
console.print(result)
|
|
86
|
+
elif isinstance(result, (int, float, bool)) or result is None:
|
|
87
|
+
console.print(str(result))
|
|
88
|
+
else:
|
|
89
|
+
text_out = json.dumps(result, ensure_ascii=False, indent=2)
|
|
90
|
+
console.print(
|
|
91
|
+
Syntax(text_out, "json", word_wrap=True, background_color="default")
|
|
92
|
+
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""دستور `pdev json` — کار با فایلهای JSON."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="کار با فایلهای JSON.", no_args_is_help=True)
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _fail(message: str) -> None:
|
|
16
|
+
console.print(f"[red]✗ خطا:[/red] {message}")
|
|
17
|
+
raise typer.Exit(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load(path: Path) -> Any:
|
|
21
|
+
"""فایل JSON را میخواند و پارس میکند."""
|
|
22
|
+
try:
|
|
23
|
+
raw = path.read_text(encoding="utf-8")
|
|
24
|
+
except FileNotFoundError:
|
|
25
|
+
_fail(f"فایل یافت نشد: {path}")
|
|
26
|
+
except OSError as e:
|
|
27
|
+
_fail(f"خطا در خواندن فایل: {e}")
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(raw)
|
|
30
|
+
except json.JSONDecodeError as e:
|
|
31
|
+
_fail(f"JSON نامعتبر: {e}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_quiet(ctx: typer.Context) -> bool:
|
|
35
|
+
"""آیا حالت quiet فعال است؟"""
|
|
36
|
+
try:
|
|
37
|
+
return bool(ctx.obj and ctx.obj.get("quiet"))
|
|
38
|
+
except Exception:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("pretty")
|
|
43
|
+
def pretty_cmd(
|
|
44
|
+
ctx: typer.Context,
|
|
45
|
+
path: Path = typer.Argument(..., help="مسیر فایل JSON."),
|
|
46
|
+
indent: int = typer.Option(2, "--indent", "-i", min=0, max=10, help="تعداد فاصله."),
|
|
47
|
+
output: Optional[Path] = typer.Option(
|
|
48
|
+
None, "--output", "-o", help="ذخیره در فایل خروجی."
|
|
49
|
+
),
|
|
50
|
+
) -> None:
|
|
51
|
+
"""زیباسازی JSON."""
|
|
52
|
+
data = _load(path)
|
|
53
|
+
text = json.dumps(data, ensure_ascii=False, indent=indent)
|
|
54
|
+
if output is not None:
|
|
55
|
+
output.write_text(text, encoding="utf-8")
|
|
56
|
+
if not _is_quiet(ctx):
|
|
57
|
+
console.print(f"[green]✓ ذخیره شد:[/green] {output}")
|
|
58
|
+
else:
|
|
59
|
+
console.print(text, highlight=False)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command("minify")
|
|
63
|
+
def minify_cmd(
|
|
64
|
+
ctx: typer.Context,
|
|
65
|
+
path: Path = typer.Argument(..., help="مسیر فایل JSON."),
|
|
66
|
+
output: Optional[Path] = typer.Option(
|
|
67
|
+
None, "--output", "-o", help="ذخیره در فایل خروجی."
|
|
68
|
+
),
|
|
69
|
+
) -> None:
|
|
70
|
+
"""فشردهسازی JSON."""
|
|
71
|
+
data = _load(path)
|
|
72
|
+
text = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
|
|
73
|
+
if output is not None:
|
|
74
|
+
output.write_text(text, encoding="utf-8")
|
|
75
|
+
if not _is_quiet(ctx):
|
|
76
|
+
console.print(f"[green]✓ ذخیره شد:[/green] {output}")
|
|
77
|
+
else:
|
|
78
|
+
console.print(text, highlight=False)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@app.command("validate")
|
|
82
|
+
def validate_cmd(
|
|
83
|
+
ctx: typer.Context,
|
|
84
|
+
path: Path = typer.Argument(..., help="مسیر فایل JSON."),
|
|
85
|
+
) -> None:
|
|
86
|
+
"""بررسی صحت ساختار JSON."""
|
|
87
|
+
try:
|
|
88
|
+
raw = path.read_text(encoding="utf-8")
|
|
89
|
+
except FileNotFoundError:
|
|
90
|
+
_fail(f"فایل یافت نشد: {path}")
|
|
91
|
+
try:
|
|
92
|
+
json.loads(raw)
|
|
93
|
+
except json.JSONDecodeError as e:
|
|
94
|
+
_fail(f"JSON نامعتبر است: {e}")
|
|
95
|
+
if not _is_quiet(ctx):
|
|
96
|
+
console.print("[green]✓ JSON معتبر است.[/green]")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@app.command("keys")
|
|
100
|
+
def keys_cmd(
|
|
101
|
+
path: Path = typer.Argument(..., help="مسیر فایل JSON."),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""نمایش کلیدهای سطح اول."""
|
|
104
|
+
data = _load(path)
|
|
105
|
+
if not isinstance(data, dict):
|
|
106
|
+
console.print("[yellow]⚠ هشدار:[/yellow] ساختار سطح اول شیء (object) نیست.")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
for key in data.keys():
|
|
109
|
+
console.print(f"• {key}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _flatten(obj: Any, prefix: str = "") -> dict[str, Any]:
|
|
113
|
+
"""مسطحسازی JSON تودرتو به کلیدهای نقطهای."""
|
|
114
|
+
result: dict[str, Any] = {}
|
|
115
|
+
if isinstance(obj, dict):
|
|
116
|
+
for key, value in obj.items():
|
|
117
|
+
new_key = f"{prefix}.{key}" if prefix else str(key)
|
|
118
|
+
result.update(_flatten(value, new_key))
|
|
119
|
+
elif isinstance(obj, list):
|
|
120
|
+
for idx, value in enumerate(obj):
|
|
121
|
+
new_key = f"{prefix}[{idx}]"
|
|
122
|
+
result.update(_flatten(value, new_key))
|
|
123
|
+
else:
|
|
124
|
+
result[prefix] = obj
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@app.command("flatten")
|
|
129
|
+
def flatten_cmd(
|
|
130
|
+
path: Path = typer.Argument(..., help="مسیر فایل JSON."),
|
|
131
|
+
) -> None:
|
|
132
|
+
"""مسطحسازی JSON تودرتو به کلیدهای نقطهای."""
|
|
133
|
+
data = _load(path)
|
|
134
|
+
flat = _flatten(data)
|
|
135
|
+
console.print(json.dumps(flat, ensure_ascii=False, indent=2), highlight=False)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""دستور pdev license — تولید فایل لایسنس."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
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.license_data import LICENSES, list_licenses, render_license
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="تولید فایل لایسنس.", no_args_is_help=True)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command("list")
|
|
19
|
+
def list_cmd() -> None:
|
|
20
|
+
"""نمایش لایسنسهای موجود."""
|
|
21
|
+
table = Table(title="📜 لایسنسها", title_style="bold cyan")
|
|
22
|
+
table.add_column("SPDX", style="bold")
|
|
23
|
+
table.add_column("نام")
|
|
24
|
+
for item in list_licenses():
|
|
25
|
+
table.add_row(item["spdx"], item["name"])
|
|
26
|
+
console.print(table)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("show")
|
|
30
|
+
def show_cmd(
|
|
31
|
+
spdx: str = typer.Argument(..., help="نام SPDX (مثل MIT، Apache-2.0)."),
|
|
32
|
+
author: str = typer.Option("Your Name", "--author", "-a", help="نام نویسنده."),
|
|
33
|
+
year: Optional[int] = typer.Option(None, "--year", "-y", help="سال (پیشفرض: الان)."),
|
|
34
|
+
) -> None:
|
|
35
|
+
"""نمایش متن لایسنس."""
|
|
36
|
+
if year is None:
|
|
37
|
+
year = datetime.now().year
|
|
38
|
+
try:
|
|
39
|
+
console.print(render_license(spdx, author, year), highlight=False)
|
|
40
|
+
except ValueError as e:
|
|
41
|
+
console.print(f"[red]✗ خطا:[/red] {e}")
|
|
42
|
+
raise typer.Exit(1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("new")
|
|
46
|
+
def new_cmd(
|
|
47
|
+
spdx: str = typer.Argument(..., help="نام SPDX."),
|
|
48
|
+
author: str = typer.Option("Your Name", "--author", "-a", help="نام نویسنده."),
|
|
49
|
+
year: Optional[int] = typer.Option(None, "--year", "-y"),
|
|
50
|
+
output: Path = typer.Option(
|
|
51
|
+
Path("LICENSE"), "--output", "-o", help="مسیر فایل خروجی."
|
|
52
|
+
),
|
|
53
|
+
force: bool = typer.Option(False, "--force", "-f", help="بازنویسی فایل موجود."),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""ذخیرهٔ لایسنس در فایل."""
|
|
56
|
+
if year is None:
|
|
57
|
+
year = datetime.now().year
|
|
58
|
+
if output.exists() and not force:
|
|
59
|
+
console.print(f"[red]✗ خطا:[/red] فایل موجود است: {output} (از --force استفاده کن)")
|
|
60
|
+
raise typer.Exit(1)
|
|
61
|
+
try:
|
|
62
|
+
text = render_license(spdx, author, year)
|
|
63
|
+
except ValueError as e:
|
|
64
|
+
console.print(f"[red]✗ خطا:[/red] {e}")
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
output.write_text(text, encoding="utf-8")
|
|
67
|
+
console.print(f"[green]✓ ذخیره شد:[/green] {output}")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""دستور pdev lorem — تولید متن نمونهٔ فارسی."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
from persian_devkit.utils.fake_utils import (
|
|
8
|
+
lorem_ipsum,
|
|
9
|
+
random_paragraph,
|
|
10
|
+
random_sentence,
|
|
11
|
+
random_words,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="تولید متن نمونهٔ فارسی.", no_args_is_help=True)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command("paragraph")
|
|
19
|
+
def paragraph_cmd(
|
|
20
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=100),
|
|
21
|
+
sentences: int = typer.Option(4, "--sentences", "-s", min=1, max=20),
|
|
22
|
+
) -> None:
|
|
23
|
+
"""تولید پاراگراف متن نمونه."""
|
|
24
|
+
for i in range(count):
|
|
25
|
+
if i > 0:
|
|
26
|
+
console.print()
|
|
27
|
+
console.print(random_paragraph(sentences))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("sentence")
|
|
31
|
+
def sentence_cmd(
|
|
32
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=100),
|
|
33
|
+
) -> None:
|
|
34
|
+
"""تولید جملهٔ تصادفی."""
|
|
35
|
+
for _ in range(count):
|
|
36
|
+
console.print(random_sentence())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command("words")
|
|
40
|
+
def words_cmd(
|
|
41
|
+
count: int = typer.Option(5, "--count", "-c", min=1, max=200),
|
|
42
|
+
) -> None:
|
|
43
|
+
"""تولید چند کلمهٔ تصادفی فارسی."""
|
|
44
|
+
console.print(random_words(count))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("all")
|
|
48
|
+
def all_cmd(
|
|
49
|
+
paragraphs: int = typer.Option(3, "--paragraphs", "-p", min=1, max=20),
|
|
50
|
+
sentences: int = typer.Option(4, "--sentences", "-s", min=1, max=20),
|
|
51
|
+
) -> None:
|
|
52
|
+
"""تولید چند پاراگراف متن کامل."""
|
|
53
|
+
console.print(lorem_ipsum(paragraphs=paragraphs, sentences_per_paragraph=sentences))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""دستور pdev name — تولید نام، ایمیل و دادههای نمونهٔ فارسی."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from persian_devkit.utils.fake_utils import (
|
|
11
|
+
random_city,
|
|
12
|
+
random_email,
|
|
13
|
+
random_first_name,
|
|
14
|
+
random_full_name,
|
|
15
|
+
random_last_name,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(help="تولید نام و دادههای نمونهٔ فارسی.", no_args_is_help=True)
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command("full")
|
|
23
|
+
def full_cmd(
|
|
24
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=200),
|
|
25
|
+
gender: str = typer.Option("any", "--gender", "-g", help="any|male|female"),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""تولید نام و نام خانوادگی."""
|
|
28
|
+
if gender not in ("any", "male", "female"):
|
|
29
|
+
console.print(f"[red]✗ خطا:[/red] gender نامعتبر: {gender}")
|
|
30
|
+
raise typer.Exit(1)
|
|
31
|
+
for _ in range(count):
|
|
32
|
+
console.print(random_full_name(gender))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command("first")
|
|
36
|
+
def first_cmd(
|
|
37
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=200),
|
|
38
|
+
gender: str = typer.Option("any", "--gender", "-g"),
|
|
39
|
+
) -> None:
|
|
40
|
+
"""تولید نام کوچک."""
|
|
41
|
+
for _ in range(count):
|
|
42
|
+
console.print(random_first_name(gender))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("last")
|
|
46
|
+
def last_cmd(
|
|
47
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=200),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""تولید نام خانوادگی."""
|
|
50
|
+
for _ in range(count):
|
|
51
|
+
console.print(random_last_name())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command("city")
|
|
55
|
+
def city_cmd(
|
|
56
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=200),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""تولید نام شهر ایرانی."""
|
|
59
|
+
for _ in range(count):
|
|
60
|
+
console.print(random_city())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command("email")
|
|
64
|
+
def email_cmd(
|
|
65
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=200),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""تولید ایمیل نمونه."""
|
|
68
|
+
for _ in range(count):
|
|
69
|
+
console.print(random_email())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command("profile")
|
|
73
|
+
def profile_cmd(
|
|
74
|
+
count: int = typer.Option(1, "--count", "-c", min=1, max=100),
|
|
75
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="خروجی JSON."),
|
|
76
|
+
) -> None:
|
|
77
|
+
"""پروفایل کامل نمونه (نام، ایمیل، شهر)."""
|
|
78
|
+
profiles = [
|
|
79
|
+
{
|
|
80
|
+
"name": random_full_name(),
|
|
81
|
+
"email": random_email(),
|
|
82
|
+
"city": random_city(),
|
|
83
|
+
}
|
|
84
|
+
for _ in range(count)
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
if output_json:
|
|
88
|
+
console.print(json.dumps(profiles, ensure_ascii=False, indent=2), highlight=False)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
table = Table(title="👤 پروفایلهای نمونه", title_style="bold cyan")
|
|
92
|
+
table.add_column("#", style="dim")
|
|
93
|
+
table.add_column("نام", style="bold")
|
|
94
|
+
table.add_column("ایمیل")
|
|
95
|
+
table.add_column("شهر")
|
|
96
|
+
for i, p in enumerate(profiles, 1):
|
|
97
|
+
table.add_row(str(i), p["name"], p["email"], p["city"])
|
|
98
|
+
console.print(table)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""دستور `pdev number` — تبدیل و قالببندی اعداد."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
from persian_devkit.utils.number_utils import (
|
|
8
|
+
format_thousands,
|
|
9
|
+
number_to_words,
|
|
10
|
+
to_english_digits,
|
|
11
|
+
to_persian_digits,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="تبدیل و قالببندی اعداد.", no_args_is_help=True)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _fail(message: str) -> None:
|
|
19
|
+
console.print(f"[red]✗ خطا:[/red] {message}")
|
|
20
|
+
raise typer.Exit(1)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command("to-persian")
|
|
24
|
+
def to_persian(
|
|
25
|
+
value: str = typer.Argument(..., help="عدد یا رشته با ارقام لاتین."),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""تبدیل ارقام لاتین/عربی به ارقام فارسی."""
|
|
28
|
+
console.print(to_persian_digits(value))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command("to-english")
|
|
32
|
+
def to_english(
|
|
33
|
+
value: str = typer.Argument(..., help="عدد یا رشته با ارقام فارسی/عربی."),
|
|
34
|
+
) -> None:
|
|
35
|
+
"""تبدیل ارقام فارسی/عربی به ارقام لاتین."""
|
|
36
|
+
console.print(to_english_digits(value))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command("format")
|
|
40
|
+
def format_cmd(
|
|
41
|
+
value: str = typer.Argument(..., help="عدد برای قالببندی با جداکنندهٔ هزارگان."),
|
|
42
|
+
) -> None:
|
|
43
|
+
"""قالببندی عدد با جداکنندهٔ هزارگان."""
|
|
44
|
+
cleaned = to_english_digits(value).replace(",", "").replace("٫", ".").strip()
|
|
45
|
+
try:
|
|
46
|
+
if "." in cleaned:
|
|
47
|
+
n_float = float(cleaned)
|
|
48
|
+
console.print(f"{n_float:,.2f}")
|
|
49
|
+
else:
|
|
50
|
+
n_int = int(cleaned)
|
|
51
|
+
console.print(format_thousands(n_int))
|
|
52
|
+
except ValueError:
|
|
53
|
+
_fail(f"عدد نامعتبر: {value}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.command("words")
|
|
57
|
+
def words_cmd(
|
|
58
|
+
value: str = typer.Argument(..., help="عدد صحیح برای تبدیل به حروف فارسی."),
|
|
59
|
+
) -> None:
|
|
60
|
+
"""تبدیل عدد صحیح به معادل نوشتاری فارسی."""
|
|
61
|
+
try:
|
|
62
|
+
n = int(to_english_digits(value).strip())
|
|
63
|
+
except ValueError:
|
|
64
|
+
_fail(f"عدد نامعتبر: {value}")
|
|
65
|
+
console.print(number_to_words(n))
|