Skip to content

Commit bcbcb57

Browse files
committed
premiere version du only mod de bcasl
1 parent 64336e8 commit bcbcb57

3 files changed

Lines changed: 354 additions & 0 deletions

File tree

bcasl/only_mod/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2025 Ague Samuel Amen
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""
17+
BCASL Standalone Module (only_mod)
18+
19+
Permet d'utiliser BCASL de manière indépendante sans lancer l'application complète.
20+
Réutilise les fonctions du package bcasl et fournit une interface GUI simple.
21+
22+
Usage:
23+
python -m bcasl.only_mod [workspace_path]
24+
"""
25+
26+
from __future__ import annotations
27+
28+
__version__ = "1.0.0"
29+
__all__ = ["BcaslStandaloneApp", "main"]

bcasl/only_mod/__main__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2025 Ague Samuel Amen
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""
17+
BCASL Standalone Module Entry Point
18+
19+
Allows running BCASL as a standalone application:
20+
python -m bcasl.only_mod [workspace_path]
21+
"""
22+
23+
from .app import main
24+
25+
if __name__ == "__main__":
26+
main()

bcasl/only_mod/app.py

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2025 Ague Samuel Amen
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""
17+
BCASL Standalone GUI Application
18+
19+
Interface minimale pour exécuter BCASL indépendamment du compilateur principal.
20+
"""
21+
22+
from __future__ import annotations
23+
24+
import os
25+
import sys
26+
from pathlib import Path
27+
from typing import Optional
28+
29+
try:
30+
from PySide6.QtWidgets import (
31+
QApplication,
32+
QMainWindow,
33+
QWidget,
34+
QVBoxLayout,
35+
QHBoxLayout,
36+
QPushButton,
37+
QLabel,
38+
QTextEdit,
39+
QFileDialog,
40+
QMessageBox,
41+
QProgressBar,
42+
QCheckBox,
43+
)
44+
from PySide6.QtCore import Qt, QThread, Signal, Slot
45+
except ImportError:
46+
print("Error: PySide6 is required. Install it with: pip install PySide6")
47+
sys.exit(1)
48+
49+
from bcasl import (
50+
run_pre_compile_async,
51+
run_pre_compile,
52+
ensure_bcasl_thread_stopped,
53+
open_bc_loader_dialog,
54+
resolve_bcasl_timeout,
55+
)
56+
from bcasl.Loader import _load_workspace_config
57+
58+
59+
class BcaslStandaloneApp(QMainWindow):
60+
"""Application autonome pour exécuter BCASL."""
61+
62+
def __init__(self, workspace_dir: Optional[str] = None):
63+
super().__init__()
64+
self.workspace_dir = workspace_dir
65+
self.log = None
66+
self._bcasl_thread = None
67+
self._bcasl_worker = None
68+
self._bcasl_ui_bridge = None
69+
self._is_running = False
70+
71+
self.setWindowTitle("BCASL Standalone - Before Compilation Actions System Loader")
72+
self.setGeometry(100, 100, 900, 700)
73+
74+
# Central widget
75+
central = QWidget()
76+
self.setCentralWidget(central)
77+
layout = QVBoxLayout(central)
78+
79+
# Workspace selection
80+
ws_layout = QHBoxLayout()
81+
ws_label = QLabel("Workspace:")
82+
self.ws_display = QLabel(workspace_dir or "No workspace selected")
83+
self.ws_display.setStyleSheet("color: #0066cc; font-weight: bold;")
84+
ws_btn = QPushButton("Browse...")
85+
ws_btn.clicked.connect(self._select_workspace)
86+
ws_layout.addWidget(ws_label)
87+
ws_layout.addWidget(self.ws_display)
88+
ws_layout.addStretch()
89+
ws_layout.addWidget(ws_btn)
90+
layout.addLayout(ws_layout)
91+
92+
# Config info
93+
self.config_info = QLabel("No configuration loaded")
94+
self.config_info.setStyleSheet("color: #666; font-size: 10px;")
95+
layout.addWidget(self.config_info)
96+
97+
# Log output
98+
log_label = QLabel("Execution Log:")
99+
layout.addWidget(log_label)
100+
self.log = QTextEdit()
101+
self.log.setReadOnly(True)
102+
self.log.setStyleSheet("font-family: monospace; font-size: 9px;")
103+
layout.addWidget(self.log)
104+
105+
# Progress bar
106+
self.progress = QProgressBar()
107+
self.progress.setVisible(False)
108+
layout.addWidget(self.progress)
109+
110+
# Options
111+
options_layout = QHBoxLayout()
112+
self.chk_async = QCheckBox("Run asynchronously")
113+
self.chk_async.setChecked(True)
114+
options_layout.addWidget(self.chk_async)
115+
options_layout.addStretch()
116+
layout.addLayout(options_layout)
117+
118+
# Buttons
119+
btn_layout = QHBoxLayout()
120+
self.btn_config = QPushButton("⚙️ Configure Plugins")
121+
self.btn_config.clicked.connect(self._open_config_dialog)
122+
self.btn_run = QPushButton("▶️ Run BCASL")
123+
self.btn_run.clicked.connect(self._run_bcasl)
124+
self.btn_clear = QPushButton("🗑️ Clear Log")
125+
self.btn_clear.clicked.connect(self.log.clear)
126+
self.btn_exit = QPushButton("Exit")
127+
self.btn_exit.clicked.connect(self.close)
128+
129+
btn_layout.addWidget(self.btn_config)
130+
btn_layout.addWidget(self.btn_run)
131+
btn_layout.addWidget(self.btn_clear)
132+
btn_layout.addStretch()
133+
btn_layout.addWidget(self.btn_exit)
134+
layout.addLayout(btn_layout)
135+
136+
# Status bar
137+
self.statusBar().showMessage("Ready")
138+
139+
# Load initial config if workspace provided
140+
if workspace_dir:
141+
self._load_config_info()
142+
143+
def _select_workspace(self):
144+
"""Select workspace directory."""
145+
folder = QFileDialog.getExistingDirectory(
146+
self,
147+
"Select Workspace Directory",
148+
self.workspace_dir or os.path.expanduser("~"),
149+
)
150+
if folder:
151+
self.workspace_dir = folder
152+
self.ws_display.setText(folder)
153+
self._load_config_info()
154+
self.log.append(f"✅ Workspace selected: {folder}\n")
155+
156+
def _load_config_info(self):
157+
"""Load and display configuration info."""
158+
if not self.workspace_dir:
159+
self.config_info.setText("No workspace selected")
160+
return
161+
162+
try:
163+
cfg = _load_workspace_config(Path(self.workspace_dir))
164+
plugins = cfg.get("plugins", {})
165+
enabled_count = sum(
166+
1
167+
for v in plugins.values()
168+
if isinstance(v, dict) and v.get("enabled", True)
169+
or isinstance(v, bool) and v
170+
)
171+
total_count = len(plugins)
172+
file_patterns = cfg.get("file_patterns", [])
173+
exclude_patterns = cfg.get("exclude_patterns", [])
174+
175+
info = (
176+
f"Plugins: {enabled_count}/{total_count} enabled | "
177+
f"File patterns: {len(file_patterns)} | "
178+
f"Exclude patterns: {len(exclude_patterns)}"
179+
)
180+
self.config_info.setText(info)
181+
except Exception as e:
182+
self.config_info.setText(f"Error loading config: {e}")
183+
184+
def _open_config_dialog(self):
185+
"""Open plugin configuration dialog."""
186+
if not self.workspace_dir:
187+
QMessageBox.warning(
188+
self,
189+
"Warning",
190+
"Please select a workspace first.",
191+
)
192+
return
193+
try:
194+
open_bc_loader_dialog(self)
195+
self._load_config_info()
196+
except Exception as e:
197+
QMessageBox.critical(self, "Error", f"Failed to open config dialog: {e}")
198+
199+
def _run_bcasl(self):
200+
"""Run BCASL."""
201+
if not self.workspace_dir:
202+
QMessageBox.warning(
203+
self,
204+
"Warning",
205+
"Please select a workspace first.",
206+
)
207+
return
208+
209+
if self._is_running:
210+
QMessageBox.information(
211+
self,
212+
"Information",
213+
"BCASL is already running. Please wait for it to complete.",
214+
)
215+
return
216+
217+
self._is_running = True
218+
self.btn_run.setEnabled(False)
219+
self.btn_config.setEnabled(False)
220+
self.progress.setVisible(True)
221+
self.progress.setMaximum(0) # Indeterminate progress
222+
self.statusBar().showMessage("Running BCASL...")
223+
self.log.append("\n" + "=" * 60)
224+
self.log.append("Starting BCASL execution...")
225+
self.log.append("=" * 60 + "\n")
226+
227+
def on_done(report):
228+
"""Callback when BCASL completes."""
229+
self._is_running = False
230+
self.btn_run.setEnabled(True)
231+
self.btn_config.setEnabled(True)
232+
self.progress.setVisible(False)
233+
234+
if report is None:
235+
self.log.append("\n❌ BCASL execution failed or was cancelled.\n")
236+
self.statusBar().showMessage("Failed")
237+
else:
238+
try:
239+
self.log.append("\n" + "=" * 60)
240+
self.log.append("BCASL Execution Report:")
241+
self.log.append("=" * 60 + "\n")
242+
for item in report:
243+
status = "✅ OK" if item.success else f"❌ FAIL: {item.error}"
244+
self.log.append(
245+
f" {item.plugin_id}: {status} ({item.duration_ms:.1f}ms)\n"
246+
)
247+
self.log.append("\n" + report.summary() + "\n")
248+
self.statusBar().showMessage(
249+
"Completed" if report.ok else "Completed with errors"
250+
)
251+
except Exception as e:
252+
self.log.append(f"\n⚠️ Error displaying report: {e}\n")
253+
self.statusBar().showMessage("Completed")
254+
255+
try:
256+
if self.chk_async.isChecked():
257+
run_pre_compile_async(self, on_done)
258+
else:
259+
report = run_pre_compile(self)
260+
on_done(report)
261+
except Exception as e:
262+
self.log.append(f"\n❌ Error: {e}\n")
263+
self._is_running = False
264+
self.btn_run.setEnabled(True)
265+
self.btn_config.setEnabled(True)
266+
self.progress.setVisible(False)
267+
self.statusBar().showMessage("Error")
268+
269+
def closeEvent(self, event):
270+
"""Handle window close."""
271+
try:
272+
ensure_bcasl_thread_stopped(self)
273+
except Exception:
274+
pass
275+
event.accept()
276+
277+
278+
def main():
279+
"""Main entry point for standalone BCASL application."""
280+
import argparse
281+
282+
parser = argparse.ArgumentParser(
283+
description="BCASL Standalone - Before Compilation Actions System Loader"
284+
)
285+
parser.add_argument(
286+
"workspace",
287+
nargs="?",
288+
help="Path to workspace directory (optional)",
289+
)
290+
args = parser.parse_args()
291+
292+
app = QApplication(sys.argv)
293+
window = BcaslStandaloneApp(workspace_dir=args.workspace)
294+
window.show()
295+
sys.exit(app.exec())
296+
297+
298+
if __name__ == "__main__":
299+
main()

0 commit comments

Comments
 (0)