# task_runner.py import os import sys import json import subprocess import time import shutil import requests from pathlib import Path from datetime import datetime # ---------- إعدادات عامة ---------- REPO_DIR = Path(os.environ.get("REPO_DIR", ".")) # جذر المستودع TASKS_FILE = REPO_DIR / "tasks.json" # ملف المهام GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") # PAT مطلوب لإنشاء PR أو push لremote محمي GITHUB_API = "https://api.github.com" DEFAULT_BASE_BRANCH = "main" # فرع الأساس عند إنشاء PR # ---------- دوال مساعدة ---------- def run(cmd, cwd=REPO_DIR, check=True): print(f"> {' '.join(cmd)}") return subprocess.run(cmd, cwd=str(cwd), check=check, text=True, capture_output=True) def git_current_branch(): res = run(["git", "rev-parse", "--abbrev-ref", "HEAD"], check=True) return res.stdout.strip() def ensure_branch(branch): cur = git_current_branch() if cur == branch: return # إذا الفرع موجود محليًا branches = run(["git", "branch", "--list", branch], check=True).stdout if branches.strip(): run(["git", "checkout", branch], check=True) else: # إنشاء فرع محلي من الفرع الحالي أو من base run(["git", "checkout", "-b", branch], check=True) def commit_and_push(branch, message, push=True, remote="origin"): run(["git", "add", "."], check=True) try: run(["git", "commit", "-m", message], check=True) except subprocess.CalledProcessError: print("لا توجد تغييرات جديدة للالتزام.") return if push: run(["git", "push", "-u", remote, branch], check=True) def create_pr(owner, repo, head, base, title, body=""): if not GITHUB_TOKEN: raise RuntimeError("GITHUB_TOKEN غير موجود في متغيرات البيئة.") url = f"{GITHUB_API}/repos/{owner}/{repo}/pulls" headers = {"Authorization": f"token {GITHUB_TOKEN}"} data = {"title": title, "head": head, "base": base, "body": body} r = requests.post(url, json=data, headers=headers) if r.status_code in (200,201): print("PR created:", r.json().get("html_url")) return r.json() else: print("فشل إنشاء PR:", r.status_code, r.text) r.raise_for_status() # ---------- تنفيذ مهمة واحدة ---------- def execute_task(task): """ نموذج مهمة (task): { "id": "task-1", "type": "create_files" | "create_dirs" | "import_file" | "modify_file" | "merge_branch" | "checkout_branch", "branch": "push-test", "confirm": true, "auto": false, "payload": { ... } # حسب النوع } """ ttype = task.get("type") branch = task.get("branch") confirm = task.get("confirm", True) auto = task.get("auto", False) payload = task.get("payload", {}) print(f"\n=== تنفيذ المهمة {task.get('id')} نوع: {ttype} ===") # تأكيد المستخدم إذا مطلوب if confirm and not auto: ans = input("تنفيذ المهمة الآن؟ (y/N): ").strip().lower() if ans != "y": print("تم إلغاء المهمة بناءً على اختيار المستخدم.") return # تأكد من الفرع المطلوب if branch: ensure_branch(branch) # أنواع المهام if ttype == "create_dirs": dirs = payload.get("dirs", []) for d in dirs: p = REPO_DIR / d p.mkdir(parents=True, exist_ok=True) print("تم إنشاء/التأكد من وجود:", p) elif ttype == "create_files": files = payload.get("files", []) for f in files: p = REPO_DIR / f["path"] p.parent.mkdir(parents=True, exist_ok=True) content = f.get("content", "") with open(p, "w", encoding="utf-8") as fh: fh.write(content) print("تم إنشاء/تعديل الملف:", p) elif ttype == "import_file": # import from local path to repo src = Path(payload.get("src")) dest = REPO_DIR / payload.get("dest", src.name) if not src.exists(): print("ملف المصدر غير موجود:", src) else: dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dest) print(f"تم نسخ {src} إلى {dest}") elif ttype == "modify_file": path = REPO_DIR / payload.get("path") if not path.exists(): print("الملف غير موجود:", path) else: # يمكن أن يكون التعديل عبر استبدال كامل أو إضافة سطور mode = payload.get("mode", "replace") # replace | append content = payload.get("content", "") if mode == "append": with open(path, "a", encoding="utf-8") as fh: fh.write(content) else: with open(path, "w", encoding="utf-8") as fh: fh.write(content) print("تم تعديل الملف:", path) elif ttype == "checkout_branch": target = payload.get("target") if not target: print("لم يتم تحديد اسم الفرع الهدف.") else: ensure_branch(target) print("الانتقال إلى الفرع:", target) elif ttype == "merge_branch": source = payload.get("source") target = payload.get("target", git_current_branch()) if not source: print("لم يتم تحديد فرع المصدر للدمج.") else: # انتقل إلى target ثم ادمج source ensure_branch(target) run(["git", "merge", "--no-ff", source], check=True) print(f"تم دمج {source} إلى {target}") else: print("نوع المهمة غير مدعوم:", ttype) return # بعد التعديلات: التزام ودفع حسب إعداد المهمة commit_msg = payload.get("commit_message", f"Auto: {task.get('id')}") push = payload.get("push", True) if payload.get("create_branch_for_changes"): # إنشاء فرع جديد باسم مخصص new_branch = payload.get("new_branch_name", f"auto/{int(time.time())}") run(["git", "checkout", "-b", new_branch], check=True) commit_and_push(new_branch, commit_msg, push=push) # إنشاء PR إذا مطلوب if payload.get("create_pr"): owner = payload.get("owner") repo = payload.get("repo") base = payload.get("base", DEFAULT_BASE_BRANCH) title = payload.get("pr_title", commit_msg) create_pr(owner, repo, new_branch, base, title, payload.get("pr_body", "")) else: # التزام على الفرع الحالي cur = git_current_branch() commit_and_push(cur, commit_msg, push=push) # ---------- قراءة ملف المهام وتنفيذها ---------- def process_tasks_once(): if not TASKS_FILE.exists(): print("لم يتم العثور على ملف المهام:", TASKS_FILE) return with open(TASKS_FILE, "r", encoding="utf-8") as fh: data = json.load(fh) tasks = data.get("tasks", []) for t in tasks: if t.get("status") in ("done","skipped"): continue try: execute_task(t) t["status"] = "done" t["completed_at"] = datetime.utcnow().isoformat() + "Z" except Exception as e: print("خطأ أثناء تنفيذ المهمة:", e) t["status"] = "error" t["error"] = str(e) # حفظ التغييرات في ملف المهام with open(TASKS_FILE, "w", encoding="utf-8") as fh: json.dump(data, fh, indent=2, ensure_ascii=False) print("انتهى معالجة المهام.") # ---------- تشغيل كخدمة مراقبة (اختياري) ---------- def watch_tasks(poll_interval=3): last_mtime = TASKS_FILE.stat().st_mtime if TASKS_FILE.exists() else 0 print("تشغيل وضع المراقبة على", TASKS_FILE) try: while True: if TASKS_FILE.exists(): m = TASKS_FILE.stat().st_mtime if m != last_mtime: print("تغيّر ملف المهام — معالجة...") process_tasks_once() last_mtime = m time.sleep(poll_interval) except KeyboardInterrupt: print("تم إيقاف المراقبة.") # ---------- CLI بسيط ---------- def print_usage(): print("Usage:") print(" python task_runner.py run_once # تنفيذ المهام الموجودة في tasks.json ثم الخروج") print(" python task_runner.py watch # مراقبة tasks.json وتنفيذ المهام الجديدة") print(" python task_runner.py exec # تنفيذ ملف مهمة مفرد (json) ثم الخروج") if __name__ == "__main__": if len(sys.argv) < 2: print_usage(); sys.exit(1) cmd = sys.argv[1] if cmd == "run_once": process_tasks_once() elif cmd == "watch": watch_tasks() elif cmd == "exec" and len(sys.argv) == 3: # تنفيذ ملف مهمة مفرد tf = Path(sys.argv[2]) if not tf.exists(): print("ملف المهمة غير موجود:", tf); sys.exit(1) with open(tf, "r", encoding="utf-8") as fh: task = json.load(fh) execute_task(task) else: print_usage()