|
15 | 15 |
|
16 | 16 | import functools |
17 | 17 | import configparser |
| 18 | +import importlib.util |
18 | 19 | import json |
19 | 20 | import os |
20 | 21 | import platform |
21 | 22 | import re |
22 | 23 | import subprocess |
| 24 | +import sys |
| 25 | +import sysconfig |
23 | 26 | import yaml |
24 | 27 | from importlib.metadata import distribution, PackageNotFoundError |
25 | 28 |
|
@@ -142,6 +145,89 @@ def _load_excluded_stdlib() -> set[str]: |
142 | 145 | EXCLUDED_STDLIB = _load_excluded_stdlib() |
143 | 146 |
|
144 | 147 |
|
| 148 | +def _normalize_realpath(path: str) -> str: |
| 149 | + try: |
| 150 | + return os.path.realpath(os.path.abspath(path)) |
| 151 | + except Exception: |
| 152 | + return path |
| 153 | + |
| 154 | + |
| 155 | +def _is_path_under(path: str, root: str) -> bool: |
| 156 | + if not path or not root: |
| 157 | + return False |
| 158 | + try: |
| 159 | + return os.path.commonpath([_normalize_realpath(path), _normalize_realpath(root)]) == _normalize_realpath(root) |
| 160 | + except Exception: |
| 161 | + return False |
| 162 | + |
| 163 | + |
| 164 | +def _collect_workspace_module_roots(filtered_files: list[str], workspace_dir: str) -> set[str]: |
| 165 | + roots: set[str] = set() |
| 166 | + ws_root = _normalize_realpath(workspace_dir) |
| 167 | + if not ws_root: |
| 168 | + return roots |
| 169 | + for file_path in filtered_files: |
| 170 | + norm = _normalize_realpath(file_path) |
| 171 | + if not _is_path_under(norm, ws_root): |
| 172 | + continue |
| 173 | + rel_path = os.path.relpath(norm, ws_root) |
| 174 | + rel_no_ext = os.path.splitext(rel_path)[0] |
| 175 | + parts = [p for p in rel_no_ext.split(os.sep) if p] |
| 176 | + if not parts: |
| 177 | + continue |
| 178 | + # src/lib/python layouts: prefer first meaningful package component. |
| 179 | + if parts[0] in {"src", "lib", "python"} and len(parts) > 1: |
| 180 | + roots.add(parts[1]) |
| 181 | + roots.add(parts[0]) |
| 182 | + if len(parts) > 1 and parts[-1] == "__init__": |
| 183 | + roots.add(parts[-2]) |
| 184 | + return roots |
| 185 | + |
| 186 | + |
| 187 | +@functools.lru_cache(maxsize=1024) |
| 188 | +def _classify_module_origin(module_name: str, workspace_dir: str) -> str: |
| 189 | + if not module_name: |
| 190 | + return "unknown" |
| 191 | + if module_name in EXCLUDED_STDLIB: |
| 192 | + return "stdlib" |
| 193 | + if module_name in sys.builtin_module_names: |
| 194 | + return "stdlib" |
| 195 | + try: |
| 196 | + spec = importlib.util.find_spec(module_name) |
| 197 | + except Exception: |
| 198 | + return "unknown" |
| 199 | + if spec is None: |
| 200 | + return "unknown" |
| 201 | + if getattr(spec, "origin", None) in ("built-in", "frozen"): |
| 202 | + return "stdlib" |
| 203 | + |
| 204 | + candidates = [] |
| 205 | + if getattr(spec, "origin", None): |
| 206 | + candidates.append(_normalize_realpath(spec.origin)) |
| 207 | + for loc in getattr(spec, "submodule_search_locations", []) or []: |
| 208 | + candidates.append(_normalize_realpath(loc)) |
| 209 | + |
| 210 | + ws_root = _normalize_realpath(workspace_dir) |
| 211 | + for c in candidates: |
| 212 | + if _is_path_under(c, ws_root): |
| 213 | + return "internal" |
| 214 | + |
| 215 | + stdlib_path = _normalize_realpath(sysconfig.get_path("stdlib") or "") |
| 216 | + for c in candidates: |
| 217 | + if _is_path_under(c, stdlib_path): |
| 218 | + return "stdlib" |
| 219 | + |
| 220 | + purelib = _normalize_realpath(sysconfig.get_path("purelib") or "") |
| 221 | + platlib = _normalize_realpath(sysconfig.get_path("platlib") or "") |
| 222 | + for c in candidates: |
| 223 | + if _is_path_under(c, purelib) or _is_path_under(c, platlib): |
| 224 | + return "third_party" |
| 225 | + if "site-packages" in c or "dist-packages" in c: |
| 226 | + return "third_party" |
| 227 | + |
| 228 | + return "unknown" |
| 229 | + |
| 230 | + |
145 | 231 | @functools.lru_cache(maxsize=256) |
146 | 232 | def _is_stdlib_module(module_name: str) -> bool: |
147 | 233 | """ |
@@ -627,8 +713,7 @@ def _t(_key: str, fr: str, en: str) -> str: |
627 | 713 | if analysis_progress: |
628 | 714 | analysis_progress.set_message(self.tr("Analyse terminée", "Analysis completed")) |
629 | 715 | analysis_progress.set_progress(len(filtered_files), len(filtered_files)) |
630 | | - # Classify imports as stdlib/internal/third-party/unknown. |
631 | | - ws_root = _normalize_realpath(getattr(self, "workspace_dir", None) or "") |
| 716 | + ws_root = _normalize_realpath(self.workspace_dir or "") |
632 | 717 | internal_modules = _collect_workspace_module_roots(filtered_files, ws_root) |
633 | 718 | # Mise à jour du message de progression |
634 | 719 | if analysis_progress: |
|
0 commit comments