gui.workers.build_worker
1# Copyright (C) 2026 David Vaquero <pepesan@gmail.com> 2# 3# This program is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16import sys 17import traceback 18from pathlib import Path 19from types import SimpleNamespace 20 21from PySide6.QtCore import QObject, Signal 22 23from gui.utils.path_setup import setup_path 24setup_path() 25 26from cli.__main__ import ( 27 generar, _cmd_clean, _cmd_version, _cmd_new, 28 _cmd_generate, _cmd_import, 29) 30 31 32class _StdoutCapture: 33 """Redirige sys.stdout → emite cada línea al callback.""" 34 35 def __init__(self, callback): 36 self._cb = callback 37 self._buf = '' 38 self._orig = sys.stdout 39 40 def write(self, text: str) -> None: 41 self._buf += text 42 while '\n' in self._buf: 43 line, self._buf = self._buf.split('\n', 1) 44 self._cb(line) 45 46 def flush(self) -> None: 47 pass 48 49 def __enter__(self): 50 sys.stdout = self 51 return self 52 53 def __exit__(self, *_) -> None: 54 if self._buf: 55 self._cb(self._buf) 56 self._buf = '' 57 sys.stdout = self._orig 58 59 60class BuildWorker(QObject): 61 """Ejecuta operaciones de build/clean/version/new en un QThread. 62 63 Uso: 64 worker = BuildWorker('build', yamls=[Path(...)]) 65 thread = QThread() 66 worker.moveToThread(thread) 67 thread.started.connect(worker.run) 68 worker.finished.connect(thread.quit) 69 thread.start() 70 """ 71 72 log_line = Signal(str, str) # (texto, nivel: 'info' | 'ok' | 'error') 73 finished = Signal(bool) # True = éxito 74 progress = Signal(int, int) # (actual, total) 75 files_generated = Signal(list) # list[tuple[Path, Path | None]] 76 77 def __init__(self, task: str, **kwargs): 78 super().__init__() 79 self._task = task 80 self._kwargs = kwargs 81 82 # ── Punto de entrada del hilo ───────────────────────────────────────────── 83 84 def run(self) -> None: 85 try: 86 with _StdoutCapture(self._on_stdout): 87 self._dispatch() 88 self.finished.emit(True) 89 except SystemExit as exc: 90 code = exc.code 91 if code and code != 0: 92 self.log_line.emit(f'Error: código de salida {code}', 'error') 93 self.finished.emit(False) 94 else: 95 self.finished.emit(True) 96 except Exception: 97 for line in traceback.format_exc().splitlines(): 98 self.log_line.emit(line, 'error') 99 self.finished.emit(False) 100 101 # ── Despacho de tareas ──────────────────────────────────────────────────── 102 103 def _dispatch(self) -> None: 104 task = self._task 105 106 if task == 'build': 107 yamls = self._kwargs.get('yamls', []) 108 _cmd_generate( 109 SimpleNamespace(targets=yamls, template=None), 110 progress_callback=lambda cur, tot: self.progress.emit(cur, tot), 111 results_callback=lambda r: self.files_generated.emit(r), 112 ) 113 114 elif task == 'clean': 115 _cmd_clean(SimpleNamespace( 116 targets=self._kwargs.get('course_dirs', []), 117 tipo=self._kwargs.get('tipo', None), 118 )) 119 120 elif task == 'version': 121 _cmd_version(SimpleNamespace( 122 targets=self._kwargs.get('course_dirs', []), 123 )) 124 125 elif task == 'new': 126 _cmd_new(SimpleNamespace(name=self._kwargs['name'])) 127 128 elif task == 'import_odp': 129 _cmd_import(Path(self._kwargs['odp_path'])) 130 131 elif task == 'watch': 132 self._run_watch() 133 134 else: 135 raise ValueError(f'Tarea desconocida: {self._task!r}') 136 137 def _run_watch(self) -> None: 138 """Watch reactivo: regenera YAMLs al guardar usando lib.watcher.""" 139 from lib.watcher import start_watch, WatchdogNotAvailable 140 from cli.__main__ import generar, _course_dir_from_yaml 141 142 yamls: list[Path] = [Path(y) for y in self._kwargs.get('yamls', [])] 143 no_pdf = self._kwargs.get('no_pdf', False) 144 template = self._kwargs.get('template', None) 145 146 def _on_change(yaml_path: Path) -> None: 147 self.log_line.emit(f'[watch] {yaml_path.name}', 'info') 148 try: 149 generar(yaml_path, template=template, no_pdf=no_pdf) 150 self.log_line.emit(f'✓ {yaml_path.name}', 'ok') 151 except Exception as exc: 152 self.log_line.emit(f'Error: {exc}', 'error') 153 154 try: 155 start_watch(yamls, _on_change) 156 except WatchdogNotAvailable as exc: 157 self.log_line.emit(str(exc), 'error') 158 raise SystemExit(1) 159 160 # ── Clasificador de nivel de log ────────────────────────────────────────── 161 162 def _on_stdout(self, line: str) -> None: 163 if not line.strip(): 164 return 165 low = line.lower() 166 if any(k in low for k in ('error', 'traceback', 'exception')): 167 level = 'error' 168 elif any(k in low for k in ('generado', 'success', 'ok', 'creado', 'versión')): 169 level = 'ok' 170 else: 171 level = 'info' 172 self.log_line.emit(line, level)
class
BuildWorker(PySide6.QtCore.QObject):
61class BuildWorker(QObject): 62 """Ejecuta operaciones de build/clean/version/new en un QThread. 63 64 Uso: 65 worker = BuildWorker('build', yamls=[Path(...)]) 66 thread = QThread() 67 worker.moveToThread(thread) 68 thread.started.connect(worker.run) 69 worker.finished.connect(thread.quit) 70 thread.start() 71 """ 72 73 log_line = Signal(str, str) # (texto, nivel: 'info' | 'ok' | 'error') 74 finished = Signal(bool) # True = éxito 75 progress = Signal(int, int) # (actual, total) 76 files_generated = Signal(list) # list[tuple[Path, Path | None]] 77 78 def __init__(self, task: str, **kwargs): 79 super().__init__() 80 self._task = task 81 self._kwargs = kwargs 82 83 # ── Punto de entrada del hilo ───────────────────────────────────────────── 84 85 def run(self) -> None: 86 try: 87 with _StdoutCapture(self._on_stdout): 88 self._dispatch() 89 self.finished.emit(True) 90 except SystemExit as exc: 91 code = exc.code 92 if code and code != 0: 93 self.log_line.emit(f'Error: código de salida {code}', 'error') 94 self.finished.emit(False) 95 else: 96 self.finished.emit(True) 97 except Exception: 98 for line in traceback.format_exc().splitlines(): 99 self.log_line.emit(line, 'error') 100 self.finished.emit(False) 101 102 # ── Despacho de tareas ──────────────────────────────────────────────────── 103 104 def _dispatch(self) -> None: 105 task = self._task 106 107 if task == 'build': 108 yamls = self._kwargs.get('yamls', []) 109 _cmd_generate( 110 SimpleNamespace(targets=yamls, template=None), 111 progress_callback=lambda cur, tot: self.progress.emit(cur, tot), 112 results_callback=lambda r: self.files_generated.emit(r), 113 ) 114 115 elif task == 'clean': 116 _cmd_clean(SimpleNamespace( 117 targets=self._kwargs.get('course_dirs', []), 118 tipo=self._kwargs.get('tipo', None), 119 )) 120 121 elif task == 'version': 122 _cmd_version(SimpleNamespace( 123 targets=self._kwargs.get('course_dirs', []), 124 )) 125 126 elif task == 'new': 127 _cmd_new(SimpleNamespace(name=self._kwargs['name'])) 128 129 elif task == 'import_odp': 130 _cmd_import(Path(self._kwargs['odp_path'])) 131 132 elif task == 'watch': 133 self._run_watch() 134 135 else: 136 raise ValueError(f'Tarea desconocida: {self._task!r}') 137 138 def _run_watch(self) -> None: 139 """Watch reactivo: regenera YAMLs al guardar usando lib.watcher.""" 140 from lib.watcher import start_watch, WatchdogNotAvailable 141 from cli.__main__ import generar, _course_dir_from_yaml 142 143 yamls: list[Path] = [Path(y) for y in self._kwargs.get('yamls', [])] 144 no_pdf = self._kwargs.get('no_pdf', False) 145 template = self._kwargs.get('template', None) 146 147 def _on_change(yaml_path: Path) -> None: 148 self.log_line.emit(f'[watch] {yaml_path.name}', 'info') 149 try: 150 generar(yaml_path, template=template, no_pdf=no_pdf) 151 self.log_line.emit(f'✓ {yaml_path.name}', 'ok') 152 except Exception as exc: 153 self.log_line.emit(f'Error: {exc}', 'error') 154 155 try: 156 start_watch(yamls, _on_change) 157 except WatchdogNotAvailable as exc: 158 self.log_line.emit(str(exc), 'error') 159 raise SystemExit(1) 160 161 # ── Clasificador de nivel de log ────────────────────────────────────────── 162 163 def _on_stdout(self, line: str) -> None: 164 if not line.strip(): 165 return 166 low = line.lower() 167 if any(k in low for k in ('error', 'traceback', 'exception')): 168 level = 'error' 169 elif any(k in low for k in ('generado', 'success', 'ok', 'creado', 'versión')): 170 level = 'ok' 171 else: 172 level = 'info' 173 self.log_line.emit(line, level)
Ejecuta operaciones de build/clean/version/new en un QThread.
Uso:
worker = BuildWorker('build', yamls=[Path(...)]) thread = QThread() worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(thread.quit) thread.start()
def
run(self) -> None:
85 def run(self) -> None: 86 try: 87 with _StdoutCapture(self._on_stdout): 88 self._dispatch() 89 self.finished.emit(True) 90 except SystemExit as exc: 91 code = exc.code 92 if code and code != 0: 93 self.log_line.emit(f'Error: código de salida {code}', 'error') 94 self.finished.emit(False) 95 else: 96 self.finished.emit(True) 97 except Exception: 98 for line in traceback.format_exc().splitlines(): 99 self.log_line.emit(line, 'error') 100 self.finished.emit(False)
staticMetaObject =
PySide6.QtCore.QMetaObject("BuildWorker" inherits "QObject":
Methods:
#4 type=Signal, signature=log_line(QString,QString), parameters=QString, QString
#5 type=Signal, signature=finished(bool), parameters=bool
#6 type=Signal, signature=progress(int,int), parameters=int, int
#7 type=Signal, signature=files_generated(QVariantList), parameters=QVariantList
)