Skip to content

Commit 5f643f2

Browse files
committed
P0: finaliser parite IDE/classic et durcir l'analyseur deps
- Extrait un parseur d'imports testable dans deps_analyser et l'utilise dans suggest_missing_dependencies. - Ajoute la couverture des imports relatifs et dynamiques dans les tests heuristiques de l'analyseur. - Documente la matrice de parite IDE vs classique et corrige les ecarts identifies (i18n menu (...) + tooltip). - Met a jour TODO.md en cochant les taches P0 realisees.
1 parent d2ce17f commit 5f643f2

5 files changed

Lines changed: 128 additions & 37 deletions

File tree

Core/IdeLikeGui/connections.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -269,72 +269,84 @@ def _setup_more_tools_menu(self) -> None:
269269
more_btn = getattr(self, "toolButton_more", None)
270270
if more_btn is None:
271271
return
272+
_t = getattr(self, "tr", None)
273+
274+
def _tr(fr: str, en: str) -> str:
275+
try:
276+
if callable(_t):
277+
return _t(fr, en)
278+
except Exception:
279+
pass
280+
return en
272281

273282
try:
274283
menu = QMenu(more_btn)
275284

276-
act_workspace = QAction("Select workspace", menu)
285+
act_workspace = QAction(
286+
_tr("Selectionner workspace", "Select workspace"), menu
287+
)
277288
act_workspace.triggered.connect(
278289
lambda: getattr(self, "select_workspace", lambda: None)()
279290
)
280291
menu.addAction(act_workspace)
281292

282-
act_venv = QAction("Select venv", menu)
293+
act_venv = QAction(_tr("Selectionner venv", "Select venv"), menu)
283294
act_venv.triggered.connect(
284295
lambda: getattr(self, "select_venv_manually", lambda: None)()
285296
)
286297
menu.addAction(act_venv)
287298

288-
act_add_files = QAction("Add files", menu)
299+
act_add_files = QAction(_tr("Ajouter fichiers", "Add files"), menu)
289300
act_add_files.triggered.connect(
290301
lambda: getattr(self, "select_files_manually", lambda: None)()
291302
)
292303
menu.addAction(act_add_files)
293304

294-
act_clear_workspace = QAction("Clear workspace", menu)
305+
act_clear_workspace = QAction(_tr("Vider workspace", "Clear workspace"), menu)
295306
act_clear_workspace.triggered.connect(
296307
lambda: getattr(self, "clear_workspace", lambda: None)()
297308
)
298309
menu.addAction(act_clear_workspace)
299310

300-
act_stats = QAction("Statistics", menu)
311+
act_stats = QAction(_tr("Statistiques", "Statistics"), menu)
301312
act_stats.triggered.connect(
302313
lambda: getattr(self, "show_statistics", lambda: None)()
303314
)
304315
menu.addAction(act_stats)
305316

306317
menu.addSeparator()
307318

308-
act_language = QAction("Language", menu)
319+
act_language = QAction(_tr("Langue", "Language"), menu)
309320
act_language.triggered.connect(lambda: getattr(self, "show_language_dialog", lambda: None)())
310321
menu.addAction(act_language)
311322

312-
act_theme = QAction("Theme", menu)
323+
act_theme = QAction(_tr("Theme", "Theme"), menu)
313324
act_theme.triggered.connect(lambda: _open_theme_dialog(self))
314325
menu.addAction(act_theme)
315326

316327
menu.addSeparator()
317328

318-
act_advanced = QAction("Advanced config", menu)
329+
act_advanced = QAction(_tr("Config avancee", "Advanced config"), menu)
319330
act_advanced.triggered.connect(
320331
lambda: getattr(self, "open_advanced_config_editor", lambda: None)()
321332
)
322333
menu.addAction(act_advanced)
323334

324-
act_export = QAction("Export config", menu)
335+
act_export = QAction(_tr("Exporter config", "Export config"), menu)
325336
act_export.triggered.connect(lambda: getattr(self, "export_config", lambda: None)())
326337
menu.addAction(act_export)
327338

328-
act_import = QAction("Import config", menu)
339+
act_import = QAction(_tr("Importer config", "Import config"), menu)
329340
act_import.triggered.connect(lambda: getattr(self, "import_config", lambda: None)())
330341
menu.addAction(act_import)
331342

332-
act_help = QAction("Help", menu)
343+
act_help = QAction(_tr("Aide", "Help"), menu)
333344
act_help.triggered.connect(lambda: getattr(self, "show_help_dialog", lambda: None)())
334345
menu.addAction(act_help)
335346

336347
more_btn.setMenu(menu)
337348
more_btn.setPopupMode(QToolButton.InstantPopup)
349+
more_btn.setToolTip(_tr("Plus d'actions", "More actions"))
338350
except Exception:
339351
pass
340352
_apply_activity_buttons_theme(self)

Core/deps_analyser/analyser.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,38 @@ def _find_pip_executable(venv_path: str = None, workspace_dir: str = None) -> tu
460460
return (sys.executable, ["-m", "pip"])
461461

462462

463+
def _extract_import_roots_from_source(source: str, filename: str = "<memory>") -> set[str]:
464+
"""Extract top-level import roots from Python source text."""
465+
import ast
466+
467+
modules: set[str] = set()
468+
try:
469+
tree = ast.parse(source, filename=filename)
470+
except Exception:
471+
return modules
472+
473+
for node in ast.walk(tree):
474+
if isinstance(node, ast.Import):
475+
for alias in node.names:
476+
modules.add(alias.name.split(".")[0])
477+
elif isinstance(node, ast.ImportFrom):
478+
if node.module:
479+
modules.add(node.module.split(".")[0])
480+
elif getattr(node, "level", 0) > 0:
481+
for alias in getattr(node, "names", []) or []:
482+
name = getattr(alias, "name", "")
483+
if name:
484+
modules.add(name.split(".")[0])
485+
486+
dynamic_imports = re.findall(r"__import__\(['\"]([\w\.]+)['\"]\)", source)
487+
modules.update([mod.split(".")[0] for mod in dynamic_imports])
488+
importlib_imports = re.findall(
489+
r"importlib\.import_module\(['\"]([\w\.]+)['\"]\)", source
490+
)
491+
modules.update([mod.split(".")[0] for mod in importlib_imports])
492+
return modules
493+
494+
463495
def suggest_missing_dependencies(self):
464496
"""
465497
Analyse les fichiers principaux à compiler, détecte les modules importés,
@@ -523,8 +555,6 @@ def _t(_key: str, fr: str, en: str) -> str:
523555
except Exception:
524556
pass
525557
return
526-
import ast
527-
528558
modules = set()
529559
# Détermine la liste des fichiers à analyser (sélectionnés ou tous les fichiers du projet)
530560
files = self.selected_files if self.selected_files else self.python_files
@@ -599,27 +629,7 @@ def _t(_key: str, fr: str, en: str) -> str:
599629

600630
with open(file, encoding="utf-8") as f:
601631
source = f.read()
602-
tree = ast.parse(source, filename=file)
603-
# Imports classiques (import ... / from ... import ...)
604-
for node in ast.walk(tree):
605-
if isinstance(node, ast.Import):
606-
for alias in node.names:
607-
modules.add(alias.name.split(".")[0])
608-
elif isinstance(node, ast.ImportFrom):
609-
if node.module:
610-
modules.add(node.module.split(".")[0])
611-
elif getattr(node, "level", 0) > 0:
612-
for alias in getattr(node, "names", []) or []:
613-
name = getattr(alias, "name", "")
614-
if name:
615-
modules.add(name.split(".")[0])
616-
# Imports dynamiques via __import__ ou importlib.import_module
617-
dynamic_imports = re.findall(r"__import__\(['\"]([\w\.]+)['\"]\)", source)
618-
modules.update([mod.split(".")[0] for mod in dynamic_imports])
619-
importlib_imports = re.findall(
620-
r"importlib\.import_module\(['\"]([\w\.]+)['\"]\)", source
621-
)
622-
modules.update([mod.split(".")[0] for mod in importlib_imports])
632+
modules.update(_extract_import_roots_from_source(source, filename=file))
623633
except Exception as e:
624634
_log_append(self, f"⚠️ Erreur analyse dépendances dans {file} : {e}")
625635

TODO.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
- [x] Couvrir les layouts `src/`, `lib/`, `python/`.
1818
- [x] Exploiter `pyproject.toml` / `setup.cfg` pour identifier les modules projet.
1919
- [x] Corriger le filtrage des fichiers (venv, caches, build/dist, egg-info).
20-
- [ ] Ajouter des cas de test pour imports relatifs et imports dynamiques.
20+
- [x] Ajouter des cas de test pour imports relatifs et imports dynamiques.
2121

2222
### Phase 4 - Parite IDE GUI vs GUI classique (P0)
2323
- [x] Relier l'IDE GUI aux fonctions Core existantes (sans duplication de logique).
2424
- [x] Reutiliser le cablage principal `UiConnection._connect_signals`.
2525
- [x] Activer les actions manquantes en IDE (deps, icones, entrypoint selector).
26-
- [ ] Produire une matrice de parite complete (action par action: classique vs IDE).
27-
- [ ] Corriger les ecarts restants trouves pendant la matrice.
26+
- [x] Produire une matrice de parite complete (action par action: classique vs IDE).
27+
- [x] Corriger les ecarts restants trouves pendant la matrice.
2828

2929
### Phase 5 - Durcir la qualite code (P1)
3030
- [ ] Activer `ruff` sur le projet (regles de base + corrections prioritaires).

docs/ide_gui_parity_matrix.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# IDE GUI Parity Matrix (Classic vs IDE-like)
2+
3+
This matrix tracks functional parity between the classic GUI (`ui_design.ui`)
4+
and the IDE-like GUI (`ui_ide_design2.ui`).
5+
6+
## Scope
7+
8+
- Wiring parity only: the IDE-like GUI must reuse existing Core handlers.
9+
- No business logic duplication.
10+
- UX differences are allowed if they preserve capability.
11+
12+
## Actions Parity
13+
14+
| Capability | Classic GUI | IDE-like GUI | Status | Notes |
15+
|---|---|---|---|---|
16+
| Select workspace | `select_workspace` | `select_workspace` | OK | Also available in `(...)` menu. |
17+
| Select venv | `select_venv_manually` | `select_venv_manually` | OK | Also available in `(...)` menu. |
18+
| Add files | `select_files_manually` | `select_files_manually` | OK | Also available in `(...)` menu. |
19+
| Remove selected file | `remove_selected_file` | `remove_selected_file` | OK | Direct button in IDE panel. |
20+
| Clear workspace | `clear_workspace` | `clear_workspace` | OK | Also available in `(...)` menu. |
21+
| Compile all | `compile_all` | `compile_all` | OK | Same Core flow. |
22+
| Cancel compilation | `cancel_all_compilations` | `cancel_all_compilations` | OK | Same Core flow. |
23+
| Dependency analysis | `suggest_missing_dependencies` | `suggest_missing_dependencies` | OK | Button + activity icon. |
24+
| BCASL loader | `open_bc_loader_dialog` | `open_bc_loader_dialog` | OK | Same handler. |
25+
| Statistics | `show_statistics` | `show_statistics` | OK | Also available in `(...)` menu. |
26+
| Language dialog | `show_language_dialog` | `show_language_dialog` | OK | Also available in `(...)` menu. |
27+
| Theme dialog | `show_theme_dialog` | `show_theme_dialog` | OK | Also available in `(...)` menu. |
28+
| Advanced config | `open_advanced_config_editor` | `open_advanced_config_editor` | OK | Also available in `(...)` menu. |
29+
| Export config | `export_config` | `export_config` | OK | Moved to `(...)` menu in IDE mode. |
30+
| Import config | `import_config` | `import_config` | OK | Moved to `(...)` menu in IDE mode. |
31+
| Help dialog | `show_help_dialog` | `show_help_dialog` | OK | Also available in `(...)` menu. |
32+
| App icon selection | `select_icon` | `select_icon` | OK | Explicitly wired in IDE connections. |
33+
| Nuitka icon selection | `select_nuitka_icon` | `select_nuitka_icon` | OK | Explicitly wired in IDE connections. |
34+
| Entrypoint selector | `setup_entrypoint_selector` | `setup_entrypoint_selector` | OK | Called during IDE init. |
35+
| Engines tabs binding | `registry.bind_tabs` | `registry.bind_tabs` | OK | Same EngineLoader registry. |
36+
37+
## Gaps Found During Review
38+
39+
1. IDE `(...)` menu labels were hardcoded in English.
40+
1. `(...)` button lacked explicit tooltip in some themes.
41+
42+
## Applied Corrections
43+
44+
1. `(...)` menu actions now use `self.tr(fr, en)` for i18n parity.
45+
1. `(...)` button now has a translated tooltip: "More actions".
46+
47+
## Remaining Verification (manual smoke)
48+
49+
1. Validate all actions while a compilation is running (enabled/disabled states).
50+
1. Validate parity on Linux + Windows with light and dark themes.

tests/test_deps_analyser_heuristics.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,22 @@ def test_classify_module_origin_third_party_from_site_packages(monkeypatch) -> N
139139
)
140140

141141
assert da._classify_module_origin("requests", "/tmp/workspace") == "third_party"
142+
143+
144+
def test_extract_import_roots_from_source_supports_relative_and_dynamic() -> None:
145+
source = """
146+
import os
147+
from pkg.sub import tool
148+
from .local_mod import run
149+
from ..feature import x
150+
m1 = __import__("numpy.linalg")
151+
m2 = importlib.import_module("pandas.core")
152+
"""
153+
mods = da._extract_import_roots_from_source(source, filename="sample.py")
154+
155+
assert "os" in mods
156+
assert "pkg" in mods
157+
assert "local_mod" in mods
158+
assert "feature" in mods
159+
assert "numpy" in mods
160+
assert "pandas" in mods

0 commit comments

Comments
 (0)