pymulakat 0.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.
- pymulakat-0.1.0/MANIFEST.in +1 -0
- pymulakat-0.1.0/PKG-INFO +11 -0
- pymulakat-0.1.0/README.md +15 -0
- pymulakat-0.1.0/pyproject.toml +29 -0
- pymulakat-0.1.0/setup.cfg +4 -0
- pymulakat-0.1.0/setup.py +16 -0
- pymulakat-0.1.0/src/pymulakat/commands/__init__.py +1 -0
- pymulakat-0.1.0/src/pymulakat/commands/auth.py +124 -0
- pymulakat-0.1.0/src/pymulakat/commands/config.py +75 -0
- pymulakat-0.1.0/src/pymulakat/commands/download.py +164 -0
- pymulakat-0.1.0/src/pymulakat/commands/questions.py +72 -0
- pymulakat-0.1.0/src/pymulakat/commands/submit.py +163 -0
- pymulakat-0.1.0/src/pymulakat/commands/test.py +96 -0
- pymulakat-0.1.0/src/pymulakat/core/__init__.py +1 -0
- pymulakat-0.1.0/src/pymulakat/core/api.py +96 -0
- pymulakat-0.1.0/src/pymulakat/core/auth.py +58 -0
- pymulakat-0.1.0/src/pymulakat/core/config.py +34 -0
- pymulakat-0.1.0/src/pymulakat/core/fs.py +180 -0
- pymulakat-0.1.0/src/pymulakat/core/runner.py +104 -0
- pymulakat-0.1.0/src/pymulakat/core/ui.py +58 -0
- pymulakat-0.1.0/src/pymulakat/main.c +8647 -0
- pymulakat-0.1.0/src/pymulakat/main.pyx +42 -0
- pymulakat-0.1.0/src/pymulakat.egg-info/PKG-INFO +11 -0
- pymulakat-0.1.0/src/pymulakat.egg-info/SOURCES.txt +27 -0
- pymulakat-0.1.0/src/pymulakat.egg-info/dependency_links.txt +1 -0
- pymulakat-0.1.0/src/pymulakat.egg-info/entry_points.txt +2 -0
- pymulakat-0.1.0/src/pymulakat.egg-info/requires.txt +5 -0
- pymulakat-0.1.0/src/pymulakat.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include src/pymulakat/*.pyx
|
pymulakat-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pymulakat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Terminal tabanlı güvenli Python mülakat hazırlık aracı
|
|
5
|
+
Author-email: Kaleminkuheylani <eposta@adresiniz.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: click
|
|
8
|
+
Requires-Dist: rich
|
|
9
|
+
Requires-Dist: requests
|
|
10
|
+
Requires-Dist: python-dotenv
|
|
11
|
+
Requires-Dist: pyjwt
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# PyMulakat KİMDİR?
|
|
2
|
+
|
|
3
|
+
Teknoloji ve yapay zeka çağı her köşeye sızıyor — bunu fark edenlerin sayısı ise hâlâ bir avuç.
|
|
4
|
+
|
|
5
|
+
Python deyince akla veri bilimi geliyor, veri bilimi deyince akla sermaye geliyor. Oysa bir terminal penceresi, bir API anahtarı ve öğrenme iradesi; en güçlü altyapıya sahip şirketlerle aynı oyunu oynamana yetiyor. pythonmulakat.com tam da bu gerçekten doğdu: düşük sermayeyle Python web ortamı kurmanın, VSCode ekranında bir vizyonun vücut bulmasının hikâyesi.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Türkçe, uzun yıllar teknik düşüncenin dışında bırakıldı. AlgoritmaLAR Türkçe yazılmıyor,değişekn isimleri utf-8 olmasına karşın noktalı harflere iki yüzlü yaklaşıyor. . Bu bir kader değil, bir alışkanlıktı — ve alışkanlıklar değişebilir öyle değil mi?
|
|
9
|
+
|
|
10
|
+
Bir dilin gücü, onunla düşünebildiğin derinlikte saklıdır. Yapay zeka çağını Türkçeyle yakalamak; sadece çeviri yapmak değil, üretmek, sorgulamak, inşa etmek demek. pythonmulakat.com bu inancın somut adımı: Türkçe düşünen, Python yazan, VSCode'da sabaha kadar hata ayıklayan herkes için.
|
|
11
|
+
|
|
12
|
+
Çağı yakalamak için erken doğmak gerekmez. Sadece başlamak gerekir.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "Cython>=3.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pymulakat"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Terminal tabanlı güvenli Python mülakat hazırlık aracı"
|
|
9
|
+
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Kaleminkuheylani", email = "eposta@adresiniz.com" }
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"click",
|
|
16
|
+
"rich",
|
|
17
|
+
"requests",
|
|
18
|
+
"python-dotenv",
|
|
19
|
+
"pyjwt"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
pymulakat = "pymulakat.main:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
pymulakat-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from setuptools import setup, Extension, find_packages
|
|
2
|
+
from Cython.Build import cythonize
|
|
3
|
+
|
|
4
|
+
extensions = [
|
|
5
|
+
Extension(
|
|
6
|
+
name="pymulakat.main",
|
|
7
|
+
sources=["src/pymulakat/main.pyx"],
|
|
8
|
+
)
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
setup(
|
|
12
|
+
package_dir={"": "src"},
|
|
13
|
+
packages=find_packages(where="src"),
|
|
14
|
+
ext_modules=cythonize(extensions, language_level="3"),
|
|
15
|
+
include_package_data=True
|
|
16
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""pymulakat.commands — CLI komut modülleri"""
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
🔐 Auth komutları: login, logout
|
|
3
|
+
"""
|
|
4
|
+
import http.server
|
|
5
|
+
import socketserver
|
|
6
|
+
import urllib.parse
|
|
7
|
+
import secrets
|
|
8
|
+
import time
|
|
9
|
+
from threading import Thread
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
+
|
|
15
|
+
from ..core.ui import console, show_header, show_success, show_error
|
|
16
|
+
from ..core.fs import get_token, save_token, TOKEN_FILE
|
|
17
|
+
from ..core.api import api_request
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(cli):
|
|
21
|
+
|
|
22
|
+
@cli.command()
|
|
23
|
+
@click.option("-e", "--email", prompt="E-posta", help="Kullanıcı e-posta adresi")
|
|
24
|
+
@click.option("-p", "--password", prompt="Şifre", hide_input=True, help="Kullanıcı şifresi")
|
|
25
|
+
def login(email: str, password: str):
|
|
26
|
+
"""🔐 CLI üzerinden giriş yap (OAuth flow)"""
|
|
27
|
+
show_header("🔐 Giriş", "Hesabınıza bağlanılıyor...")
|
|
28
|
+
|
|
29
|
+
token_received = None
|
|
30
|
+
|
|
31
|
+
with Progress(SpinnerColumn(), TextColumn("[primary]Doğrulanıyor...[/]"), console=console) as progress:
|
|
32
|
+
progress.add_task("", total=None)
|
|
33
|
+
try:
|
|
34
|
+
data = api_request("POST", "/auth/cli/init", json={"email": email, "password": password})
|
|
35
|
+
session_id = data.get("session_id")
|
|
36
|
+
if not session_id:
|
|
37
|
+
show_error("session_id alınamadı")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
state = secrets.token_urlsafe(16)
|
|
41
|
+
port = 3000 + secrets.randbelow(1000)
|
|
42
|
+
redirect_uri = f"http://localhost:{port}/callback"
|
|
43
|
+
auth_url = (
|
|
44
|
+
f"http://www.pythonmulakat.com/authorize"
|
|
45
|
+
f"?session={urllib.parse.quote(session_id)}"
|
|
46
|
+
f"&redirect_uri={urllib.parse.quote(redirect_uri)}"
|
|
47
|
+
f"&state={state}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
console.print(Panel(
|
|
51
|
+
f"[info]Lütfen aşağıdaki linki tarayıcınızda açın:[/]\n\n"
|
|
52
|
+
f"[link={auth_url}]{auth_url}[/]\n\n"
|
|
53
|
+
f"[secondary]Giriş yapıp onay verdikten sonra token otomatik alınacaktır.[/]",
|
|
54
|
+
title="🌐 Tarayıcı Onayı", border_style="cyan"
|
|
55
|
+
))
|
|
56
|
+
|
|
57
|
+
class CallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
58
|
+
def do_GET(self):
|
|
59
|
+
nonlocal token_received
|
|
60
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
61
|
+
if parsed.path == "/callback":
|
|
62
|
+
params = urllib.parse.parse_qs(parsed.query)
|
|
63
|
+
returned_token = params.get("token", [None])[0]
|
|
64
|
+
returned_state = params.get("state", [None])[0]
|
|
65
|
+
if returned_token and returned_state == state:
|
|
66
|
+
token_received = returned_token
|
|
67
|
+
self.send_response(200)
|
|
68
|
+
self.send_header("Content-type", "text/html")
|
|
69
|
+
self.end_headers()
|
|
70
|
+
self.wfile.write(b"Login successful!")
|
|
71
|
+
else:
|
|
72
|
+
self.send_response(400)
|
|
73
|
+
self.end_headers()
|
|
74
|
+
self.wfile.write("Onay alınamadı.".encode("utf-8"))
|
|
75
|
+
else:
|
|
76
|
+
self.send_response(404)
|
|
77
|
+
self.end_headers()
|
|
78
|
+
|
|
79
|
+
def log_message(self, format, *args):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def run_server():
|
|
83
|
+
with socketserver.TCPServer(("localhost", port), CallbackHandler) as httpd:
|
|
84
|
+
httpd.handle_request()
|
|
85
|
+
|
|
86
|
+
Thread(target=run_server, daemon=True).start()
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
import webbrowser
|
|
90
|
+
webbrowser.open(auth_url)
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
show_error(str(e))
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
with Progress(SpinnerColumn(), TextColumn("[primary]Onay bekleniyor...[/]"), console=console) as progress:
|
|
99
|
+
progress.add_task("", total=None)
|
|
100
|
+
for _ in range(60):
|
|
101
|
+
if token_received:
|
|
102
|
+
break
|
|
103
|
+
time.sleep(1)
|
|
104
|
+
|
|
105
|
+
if token_received:
|
|
106
|
+
try:
|
|
107
|
+
save_token(token_received)
|
|
108
|
+
show_success("Giriş başarılı! Token kaydedildi.", "✓ Oturum Açıldı")
|
|
109
|
+
except (PermissionError, OSError) as e:
|
|
110
|
+
show_error(str(e), "Token Kaydedilemedi")
|
|
111
|
+
else:
|
|
112
|
+
show_error("Onay süresi doldu. Lütfen tekrar deneyin.")
|
|
113
|
+
|
|
114
|
+
@cli.command()
|
|
115
|
+
def logout():
|
|
116
|
+
"""🚪 Çıkış yap ve token'ı sil"""
|
|
117
|
+
if TOKEN_FILE.exists():
|
|
118
|
+
try:
|
|
119
|
+
TOKEN_FILE.unlink()
|
|
120
|
+
show_success("Çıkış yapıldı. Token silindi.", "✓ Oturum Kapatıldı")
|
|
121
|
+
except PermissionError as e:
|
|
122
|
+
show_error(f"Token silinemedi: {e}")
|
|
123
|
+
else:
|
|
124
|
+
console.print("[warning]⚠ Zaten giriş yapılmamış.[/]")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
⚙️ Config komutu — dosya izinlerini yapılandır
|
|
3
|
+
"""
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.box import ROUNDED
|
|
11
|
+
from rich.prompt import Prompt
|
|
12
|
+
|
|
13
|
+
from ..core.ui import console, show_header, show_success
|
|
14
|
+
from ..core.config import PERMISSION_PRESETS, TOKEN_FILE, CONFIG_FILE, META_FILE, PYMULAKAT_DIR
|
|
15
|
+
from ..core.fs import load_permission_config, save_permission_config, _set_mode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(cli):
|
|
19
|
+
|
|
20
|
+
@cli.command("config")
|
|
21
|
+
@click.option("--preset", type=click.Choice(["private", "standard", "shared"]),
|
|
22
|
+
default=None, help="Hazır izin şablonu seç")
|
|
23
|
+
@click.option("--show", is_flag=True, help="Mevcut ayarları göster")
|
|
24
|
+
def config_cmd(preset: Optional[str], show: bool):
|
|
25
|
+
"""⚙️ Dosya izinlerini yapılandır (kullanıcı tarafından)"""
|
|
26
|
+
show_header("⚙️ İzin Yapılandırması", "Dosya erişim izinlerini özelleştirin")
|
|
27
|
+
|
|
28
|
+
perms = load_permission_config()
|
|
29
|
+
|
|
30
|
+
if show:
|
|
31
|
+
descriptions = {
|
|
32
|
+
"token_file": "JWT token (~/.pymulakat/token)",
|
|
33
|
+
"config_file": "Bu config dosyası",
|
|
34
|
+
"solution_file": "İndirilen çözüm dosyaları",
|
|
35
|
+
"test_file": "Gizli test dosyaları",
|
|
36
|
+
"meta_file": "Oturum meta verisi",
|
|
37
|
+
"pymulakat_dir": "~/.pymulakat dizini",
|
|
38
|
+
}
|
|
39
|
+
table = Table(box=ROUNDED, title="Mevcut İzin Ayarları", header_style="bold blue")
|
|
40
|
+
table.add_column("Dosya Türü", style="cyan")
|
|
41
|
+
table.add_column("Octal İzin", style="yellow")
|
|
42
|
+
table.add_column("Açıklama", style="dim")
|
|
43
|
+
for key, val in perms.items():
|
|
44
|
+
table.add_row(key, oct(val), descriptions.get(key, ""))
|
|
45
|
+
console.print(table)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
if preset:
|
|
49
|
+
perms.update(PERMISSION_PRESETS[preset])
|
|
50
|
+
console.print(f"[success]✓[/] '{preset}' şablonu uygulandı.")
|
|
51
|
+
else:
|
|
52
|
+
console.print("[info]Her dosya türü için izin girin (örn: 0o644) veya boş bırakıp varsayılanı kullanın.[/]\n")
|
|
53
|
+
for key, current in perms.items():
|
|
54
|
+
raw = Prompt.ask(
|
|
55
|
+
f" [cyan]{key}[/]",
|
|
56
|
+
default=oct(current),
|
|
57
|
+
show_default=True,
|
|
58
|
+
).strip()
|
|
59
|
+
try:
|
|
60
|
+
perms[key] = int(raw, 8)
|
|
61
|
+
except ValueError:
|
|
62
|
+
console.print(f" [warning]Geçersiz değer, {oct(current)} korundu.[/]")
|
|
63
|
+
|
|
64
|
+
save_permission_config(perms)
|
|
65
|
+
show_success(f"İzin ayarları kaydedildi → {CONFIG_FILE}", "✓ Kaydedildi")
|
|
66
|
+
|
|
67
|
+
# Var olan dosyalara hemen uygula
|
|
68
|
+
for path, key in [
|
|
69
|
+
(TOKEN_FILE, "token_file"),
|
|
70
|
+
(CONFIG_FILE, "config_file"),
|
|
71
|
+
(META_FILE, "meta_file"),
|
|
72
|
+
(PYMULAKAT_DIR, "pymulakat_dir"),
|
|
73
|
+
]:
|
|
74
|
+
if Path(path).exists():
|
|
75
|
+
_set_mode(Path(path), perms[key])
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
📥 Download komutu — soru listesinden seçim yap ve indir
|
|
3
|
+
"""
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.box import ROUNDED
|
|
12
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
13
|
+
from rich.prompt import Prompt, Confirm
|
|
14
|
+
|
|
15
|
+
from ..core.ui import console, show_header, show_error
|
|
16
|
+
from ..core.config import FREE_TIER_LIMIT
|
|
17
|
+
from ..core.fs import (
|
|
18
|
+
get_token, load_permission_config, _atomic_write, _set_mode,
|
|
19
|
+
save_interview_meta, get_next_solution_filename
|
|
20
|
+
)
|
|
21
|
+
from ..core.api import api_request, extract_question_number, decode_base64_content
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def register(cli):
|
|
25
|
+
|
|
26
|
+
@cli.command("download")
|
|
27
|
+
@click.option("-l", "--lib", default=None, help="Kütüphane filtresi")
|
|
28
|
+
def download_interactive(lib: Optional[str]):
|
|
29
|
+
"""📥 Soru listesinden seçim yap ve indir (İlk 20 ücretsiz)"""
|
|
30
|
+
show_header("📥 Soru Seçimi", "İlk 20 soru ücretsiz • Sadece ID gösteriliyor")
|
|
31
|
+
|
|
32
|
+
token = get_token()
|
|
33
|
+
params = {"lib": lib, "limit": FREE_TIER_LIMIT} if lib else {"limit": FREE_TIER_LIMIT}
|
|
34
|
+
|
|
35
|
+
if token and Confirm.ask("🔓 Premium soruları da listelemek ister misiniz?", default=False):
|
|
36
|
+
params = {"lib": lib} if lib else {}
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
data = api_request("GET", "/interviews/", params=params)
|
|
40
|
+
except SystemExit:
|
|
41
|
+
if not token:
|
|
42
|
+
try:
|
|
43
|
+
data = api_request("GET", "/interviews/", params={**params, "guest": "true"})
|
|
44
|
+
except Exception:
|
|
45
|
+
show_error("Bağlantı hatası. İnternetinizi kontrol edin.")
|
|
46
|
+
return
|
|
47
|
+
else:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if not data:
|
|
51
|
+
show_error("Bu kriterlerde soru bulunamadı.")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# ── Tablo ────────────────────────────────────────────
|
|
55
|
+
console.print(f"[info]📋 Mevcut Sorular (Ücretsiz: 1-{min(len(data), FREE_TIER_LIMIT)}):[/]")
|
|
56
|
+
table = Table(box=ROUNDED, show_header=True, header_style="bold blue", show_lines=True)
|
|
57
|
+
table.add_column("ID", style="cyan", width=10)
|
|
58
|
+
table.add_column("Konu", style="white", width=35)
|
|
59
|
+
table.add_column("Kütüphane", style="magenta", width=12)
|
|
60
|
+
table.add_column("Zorluk", style="yellow", width=12)
|
|
61
|
+
|
|
62
|
+
for item in data:
|
|
63
|
+
q_num = extract_question_number(item["id"])
|
|
64
|
+
id_display = f"#{item['id']}" + ("[yellow]🔒[/]" if q_num > FREE_TIER_LIMIT else "")
|
|
65
|
+
difficulty_badge = {
|
|
66
|
+
"Beginner": "[green]🟢 Beginner[/]",
|
|
67
|
+
"Junior-Mid": "[yellow]🟡 Junior-Mid[/]",
|
|
68
|
+
"Mid-Senior": "[red]🔴 Mid-Senior[/]",
|
|
69
|
+
}.get(item.get("difficulty", ""), "⚪ Belirtilmemiş")
|
|
70
|
+
topic = item.get("topic", "Başlıksız")
|
|
71
|
+
table.add_row(
|
|
72
|
+
id_display,
|
|
73
|
+
topic[:32] + ("..." if len(topic) > 32 else ""),
|
|
74
|
+
item.get("lib", "unknown"),
|
|
75
|
+
difficulty_badge,
|
|
76
|
+
)
|
|
77
|
+
console.print(table)
|
|
78
|
+
|
|
79
|
+
# ── Seçim ────────────────────────────────────────────
|
|
80
|
+
qid = Prompt.ask("\n📌 İndirmek istediğiniz soru ID'si (örn: pandas_001)", default="")
|
|
81
|
+
if not qid:
|
|
82
|
+
console.print("[warning]⚠ Seçim yapılmadı.[/]")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
selected = next((item for item in data if item["id"] == qid), None)
|
|
86
|
+
if not selected:
|
|
87
|
+
show_error(f"Soru ID bulunamadı: {qid}")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
q_num = extract_question_number(qid)
|
|
91
|
+
if q_num > FREE_TIER_LIMIT and not token:
|
|
92
|
+
console.print(Panel(
|
|
93
|
+
f"[error]❌ Soru #{qid} Premium içeriktir.[/]\n"
|
|
94
|
+
f"[info]👉 Erişmek için:[/] [cyan]pymulakat login[/]",
|
|
95
|
+
title="🔒 Premium Gerekiyor", border_style="yellow"
|
|
96
|
+
))
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
# ── İndirme ──────────────────────────────────────────
|
|
100
|
+
q_lib = selected["lib"]
|
|
101
|
+
output_file = get_next_solution_filename()
|
|
102
|
+
|
|
103
|
+
console.print(f"\n[info]⬇️ İndiriliyor: {q_lib.upper()} / #{qid}[/]")
|
|
104
|
+
with Progress(SpinnerColumn(), TextColumn("[primary]Kodlar hazırlanıyor...[/]"), console=console) as p:
|
|
105
|
+
p.add_task("", total=None)
|
|
106
|
+
try:
|
|
107
|
+
resp = api_request("GET", f"/interviews/download/{q_lib}/{qid}")
|
|
108
|
+
except SystemExit:
|
|
109
|
+
if not token and q_num <= FREE_TIER_LIMIT:
|
|
110
|
+
try:
|
|
111
|
+
resp = api_request("GET", f"/interviews/download/{q_lib}/{qid}", params={"guest": "true"})
|
|
112
|
+
except Exception:
|
|
113
|
+
show_error("İndirme başarısız. Lütfen tekrar deneyin.")
|
|
114
|
+
return
|
|
115
|
+
else:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
perms = load_permission_config()
|
|
119
|
+
|
|
120
|
+
if resp.get("starter_encoded"):
|
|
121
|
+
starter = decode_base64_content(resp["starter_encoded"])
|
|
122
|
+
try:
|
|
123
|
+
_atomic_write(Path(output_file), starter)
|
|
124
|
+
_set_mode(Path(output_file), perms["solution_file"])
|
|
125
|
+
console.print(f"[success]✓[/] Çözüm dosyası: [cyan]{Path(output_file).resolve()}[/]")
|
|
126
|
+
except (PermissionError, OSError) as e:
|
|
127
|
+
show_error(str(e), "Dosya Yazılamadı")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if resp.get("test_encoded"):
|
|
131
|
+
tests = decode_base64_content(resp["test_encoded"])
|
|
132
|
+
test_path = Path(f".test_{qid}.py")
|
|
133
|
+
try:
|
|
134
|
+
_atomic_write(test_path, tests)
|
|
135
|
+
_set_mode(test_path, perms["test_file"])
|
|
136
|
+
except (PermissionError, OSError) as e:
|
|
137
|
+
console.print(f"[warning]⚠ Test dosyası kaydedilemedi: {e}[/]")
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
save_interview_meta(qid, q_lib, selected.get("topic", ""), output_file)
|
|
141
|
+
except (PermissionError, OSError) as e:
|
|
142
|
+
console.print(f"[warning]⚠ Meta kaydedilemedi: {e}[/]")
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
subprocess.Popen(["code", output_file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
146
|
+
console.print(f"[success]✓[/] VSCode açılıyor: [cyan]{output_file}[/]")
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
if not token and q_num <= FREE_TIER_LIMIT:
|
|
151
|
+
console.print(Panel(
|
|
152
|
+
f"[info]💡 Guest moddasınız. İlerlemenizi kaydetmek için:[/]\n"
|
|
153
|
+
f"[cyan]pymulakat login[/]\n"
|
|
154
|
+
f"[secondary]Sonra: pymulakat submit {qid}[/]",
|
|
155
|
+
title="📌 Hatırlatma", border_style="cyan"
|
|
156
|
+
))
|
|
157
|
+
else:
|
|
158
|
+
console.print(Panel(
|
|
159
|
+
"[info]📌 Sonraki Adımlar:[/]\n"
|
|
160
|
+
f" 1. [cyan]{output_file}[/] dosyasını düzenle\n"
|
|
161
|
+
f" 2. Lokal test: [cyan]pymulakat test {qid}[/]\n"
|
|
162
|
+
f" 3. Sunucuya gönder: [cyan]pymulakat submit {qid}[/]",
|
|
163
|
+
title="🚀 Devam Et", border_style="green"
|
|
164
|
+
))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
📋 Soru ve ilerleme komutları: get, progress
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.box import ROUNDED
|
|
11
|
+
from rich.markdown import Markdown
|
|
12
|
+
|
|
13
|
+
from ..core.ui import console, show_header
|
|
14
|
+
from ..core.auth import require_login
|
|
15
|
+
from ..core.api import api_request, _format_duration
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(cli):
|
|
19
|
+
|
|
20
|
+
@cli.command("get")
|
|
21
|
+
@click.argument("question_id")
|
|
22
|
+
@click.option("-l", "--lib", required=True, help="Kütüphane adı (zorunlu)")
|
|
23
|
+
|
|
24
|
+
def get_question(question_id: str, lib: str):
|
|
25
|
+
"""🔍 Belirli bir sorunun detaylarını görüntüle"""
|
|
26
|
+
show_header(f"📝 Soru #{question_id}", f"Kategori: {lib}")
|
|
27
|
+
data = api_request("GET", f"/interviews/{lib}/{question_id}")
|
|
28
|
+
console.print(Panel(
|
|
29
|
+
Markdown(data.get("question", data.get("description", "Açıklama yok"))),
|
|
30
|
+
title="🎯 Görev", border_style="blue"
|
|
31
|
+
))
|
|
32
|
+
meta_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
33
|
+
meta_table.add_row("[accent]Zorluk:[/]", data.get("difficulty", "Belirtilmemiş"))
|
|
34
|
+
meta_table.add_row("[accent]Konu:[/]", data.get("topic", "Belirtilmemiş"))
|
|
35
|
+
console.print(meta_table)
|
|
36
|
+
if data.get("hints"):
|
|
37
|
+
console.print("\n[info]💡 İpuçları:[/]")
|
|
38
|
+
for i, hint in enumerate(data["hints"], 1):
|
|
39
|
+
console.print(f" [secondary]{i}.[/] {hint}")
|
|
40
|
+
|
|
41
|
+
@cli.command("progress")
|
|
42
|
+
@click.option("-l", "--lib", required=True, help="Kütüphane adı")
|
|
43
|
+
@click.option("-j", "--json", "as_json", is_flag=True, help="JSON formatında çıktı")
|
|
44
|
+
|
|
45
|
+
def show_progress(lib: str, as_json: bool):
|
|
46
|
+
"""📈 Kullanıcı ilerleme durumunu görüntüle"""
|
|
47
|
+
show_header("📈 İlerleme Raporu", f"Kategori: {lib.upper()}")
|
|
48
|
+
data = api_request("GET", "/auth/my-progress", params={"lib": lib})
|
|
49
|
+
|
|
50
|
+
if as_json:
|
|
51
|
+
console.print_json(json.dumps(data, indent=2, ensure_ascii=False))
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
summary = Table(box=ROUNDED, show_header=False)
|
|
55
|
+
summary.add_row("[accent]📦 Toplam Soru:[/]", f"[bold]{data.get('total', 0)}[/]")
|
|
56
|
+
completed = data.get("completed", [])
|
|
57
|
+
summary.add_row("[success]✅ Çözülen:[/]", f"[bold green]{len(completed)}[/]")
|
|
58
|
+
if completed:
|
|
59
|
+
accuracy = len(completed) / data.get("total", 1) * 100
|
|
60
|
+
summary.add_row("[info]🎯 Başarı Oranı:[/]", f"[bold cyan]{accuracy:.1f}%[/]")
|
|
61
|
+
console.print(summary)
|
|
62
|
+
|
|
63
|
+
if completed:
|
|
64
|
+
console.print("\n[info]🔹 Tamamlanan Sorular:[/]")
|
|
65
|
+
for item in completed:
|
|
66
|
+
attempt = item.get("attempt", {})
|
|
67
|
+
time_str = _format_duration(attempt.get("time_taken", 0))
|
|
68
|
+
points = attempt.get("points_gained", 0)
|
|
69
|
+
console.print(
|
|
70
|
+
f" [success]✓[/] [cyan]#{item['id']}[/] {item.get('topic')} "
|
|
71
|
+
f"[secondary]→[/] ⏱️ {time_str} | 🏆 +{points} puan"
|
|
72
|
+
)
|