-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub_parser.py
More file actions
147 lines (130 loc) · 5.69 KB
/
Copy pathgithub_parser.py
File metadata and controls
147 lines (130 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import requests
import os
from typing import Optional, Tuple
from urllib.parse import urlparse
IGNORED_PATHS = [
'node_modules/', '.venv/', 'venv/', 'env/', '__pycache__/',
'.git/', '.github/', 'dist/', 'build/', '.next/'
]
TARGET_EXTENSIONS = ('.php', '.py', '.js', '.go', '.rb', '.cs', '.java')
PRIORITY_KEYWORDS = ('main', 'app', 'index', 'controller', 'config', 'init', 'server')
class GitHubParser:
def __init__(self, timeout: int = 30):
self.timeout = timeout
self.api_key = os.getenv("GITHUB_API_KEY")
self.headers = {}
if self.api_key:
self.headers["Authorization"] = f"token {self.api_key}"
self.session = requests.Session()
self.session.headers.update(self.headers)
@staticmethod
def _parse_repo(repo_url: str) -> Optional[Tuple[str, str]]:
raw = repo_url.strip()
if not raw.startswith(("http://", "https://")):
raw = "https://" + raw
parsed = urlparse(raw)
parts = [p for p in parsed.path.strip("/").split("/") if p]
if len(parts) < 2:
return None
user, repo = parts[0], parts[1]
if repo.endswith(".git"):
repo = repo[:-4]
if not user or not repo:
return None
return user, repo
def _get(self, url: str) -> requests.Response:
return self.session.get(url, timeout=self.timeout)
@staticmethod
def _error_message(status_code: int) -> str:
if status_code == 401:
return "Token GitHub non valido o scaduto"
if status_code == 403:
return "Rate-limit raggiunto o accesso negato"
if status_code == 404:
return "Repository inesistente o privata"
return f"Errore API GitHub (status {status_code})"
def get_repo_data(self, repo_url: str) -> dict:
parsed = self._parse_repo(repo_url)
if parsed is None:
return {"error": f"URL repository non valido: {repo_url}"}
user, repo = parsed
api_url = f"https://api.github.com/repos/{user}/{repo}"
# Metadati repo -> default_branch
try:
repo_res = self._get(api_url)
except requests.RequestException:
return {"error": "Impossibile contattare la GitHub API"}
if repo_res.status_code != 200:
return {"error": self._error_message(repo_res.status_code)}
default_branch = repo_res.json().get("default_branch", "HEAD")
data = {}
# README
data["readme"] = "Nessun readme trovato"
try:
readme_res = self._get(f"{api_url}/readme")
if readme_res.status_code == 200:
download_url = readme_res.json().get("download_url")
if download_url:
data["readme"] = self._get(download_url).text
except requests.RequestException:
pass
# Struttura file
data["files"] = "Nessuna struttura trovata"
try:
struttura_res = self._get(f"{api_url}/git/trees/{default_branch}?recursive=1")
if struttura_res.status_code == 200:
payload = struttura_res.json()
albero = payload.get("tree", [])
filenames = [item["path"] for item in albero if item.get("type") == "blob"]
file_filtrati = [
path for path in filenames
if not any(ignored in path for ignored in IGNORED_PATHS)
]
suffix = " (struttura troncata, repo molto grande)" if payload.get("truncated") else ""
data["files"] = ", ".join(file_filtrati[:150]) + suffix
data["languages"] = self._detect_languages(api_url)
data["code_context"] = self._build_code_context(user, repo, default_branch, file_filtrati)
else:
data["files"] = self._error_message(struttura_res.status_code)
except requests.RequestException:
data["languages"] = "Non rilevati"
data["code_context"] = "Nessun file di codice trovato"
return data
def _detect_languages(self, api_url: str) -> str:
try:
lang_res = self._get(f"{api_url}/languages")
if lang_res.status_code == 200:
langs = lang_res.json()
if langs:
return ", ".join(
f"{name} ({size} byte)" for name, size in langs.items()
)
except requests.RequestException:
pass
return "Non rilevati"
def _build_code_context(self, user: str, repo: str, branch: str, file_filtrati: list, max_files: int = 5) -> str:
code_files = [f for f in file_filtrati if f.endswith(TARGET_EXTENSIONS)]
def rank(path: str) -> int:
low = path.lower()
base = len(PRIORITY_KEYWORDS) + 1
for i, keyword in enumerate(PRIORITY_KEYWORDS):
if keyword in low:
base = i
break
if "test" in low:
base += 10
return base
code_files.sort(key=rank)
code_files = code_files[:max_files]
code_context = ""
for file_path in code_files:
raw_url = f"https://raw.githubusercontent.com/{user}/{repo}/{branch}/{file_path}"
try:
file_res = self._get(raw_url)
if file_res.status_code == 200:
code_context += f"\n\n--- INIZIO FILE: {file_path} ---\n"
code_context += file_res.text[:1500]
code_context += "\n--- FINE FILE ---\n"
except requests.RequestException:
continue
return code_context if code_context else "Nessun file di codice trovato"