gui.workers.build_worker

  1import sys
  2import traceback
  3from pathlib import Path
  4from types import SimpleNamespace
  5
  6from PySide6.QtCore import QObject, Signal
  7
  8from gui.utils.path_setup import setup_path
  9setup_path()
 10
 11from cli.__main__ import (
 12    generar, _cmd_clean, _cmd_version, _cmd_new,
 13    _cmd_generate, _cmd_import,
 14)
 15
 16
 17class _StdoutCapture:
 18    """Redirige sys.stdout → emite cada línea al callback."""
 19
 20    def __init__(self, callback):
 21        self._cb   = callback
 22        self._buf  = ''
 23        self._orig = sys.stdout
 24
 25    def write(self, text: str) -> None:
 26        self._buf += text
 27        while '\n' in self._buf:
 28            line, self._buf = self._buf.split('\n', 1)
 29            self._cb(line)
 30
 31    def flush(self) -> None:
 32        pass
 33
 34    def __enter__(self):
 35        sys.stdout = self
 36        return self
 37
 38    def __exit__(self, *_) -> None:
 39        if self._buf:
 40            self._cb(self._buf)
 41            self._buf = ''
 42        sys.stdout = self._orig
 43
 44
 45class BuildWorker(QObject):
 46    """Ejecuta operaciones de build/clean/version/new en un QThread.
 47
 48    Uso:
 49        worker = BuildWorker('build', yamls=[Path(...)])
 50        thread = QThread()
 51        worker.moveToThread(thread)
 52        thread.started.connect(worker.run)
 53        worker.finished.connect(thread.quit)
 54        thread.start()
 55    """
 56
 57    log_line        = Signal(str, str)   # (texto, nivel: 'info' | 'ok' | 'error')
 58    finished        = Signal(bool)       # True = éxito
 59    progress        = Signal(int, int)   # (actual, total)
 60    files_generated = Signal(list)       # list[tuple[Path, Path | None]]
 61
 62    def __init__(self, task: str, **kwargs):
 63        super().__init__()
 64        self._task   = task
 65        self._kwargs = kwargs
 66
 67    # ── Punto de entrada del hilo ─────────────────────────────────────────────
 68
 69    def run(self) -> None:
 70        try:
 71            with _StdoutCapture(self._on_stdout):
 72                self._dispatch()
 73            self.finished.emit(True)
 74        except SystemExit as exc:
 75            code = exc.code
 76            if code and code != 0:
 77                self.log_line.emit(f'Error: código de salida {code}', 'error')
 78                self.finished.emit(False)
 79            else:
 80                self.finished.emit(True)
 81        except Exception:
 82            for line in traceback.format_exc().splitlines():
 83                self.log_line.emit(line, 'error')
 84            self.finished.emit(False)
 85
 86    # ── Despacho de tareas ────────────────────────────────────────────────────
 87
 88    def _dispatch(self) -> None:
 89        task = self._task
 90
 91        if task == 'build':
 92            yamls = self._kwargs.get('yamls', [])
 93            _cmd_generate(
 94                SimpleNamespace(targets=yamls, template=None),
 95                progress_callback=lambda cur, tot: self.progress.emit(cur, tot),
 96                results_callback=lambda r: self.files_generated.emit(r),
 97            )
 98
 99        elif task == 'clean':
100            _cmd_clean(SimpleNamespace(
101                targets=self._kwargs.get('course_dirs', []),
102                tipo=self._kwargs.get('tipo', None),
103            ))
104
105        elif task == 'version':
106            _cmd_version(SimpleNamespace(
107                targets=self._kwargs.get('course_dirs', []),
108            ))
109
110        elif task == 'new':
111            _cmd_new(SimpleNamespace(name=self._kwargs['name']))
112
113        elif task == 'import_odp':
114            _cmd_import(Path(self._kwargs['odp_path']))
115
116        elif task == 'watch':
117            self._run_watch()
118
119        else:
120            raise ValueError(f'Tarea desconocida: {self._task!r}')
121
122    def _run_watch(self) -> None:
123        """Watch reactivo: regenera YAMLs al guardar usando lib.watcher."""
124        from lib.watcher import start_watch, WatchdogNotAvailable
125        from cli.__main__ import generar, _course_dir_from_yaml
126
127        yamls: list[Path] = [Path(y) for y in self._kwargs.get('yamls', [])]
128        no_pdf   = self._kwargs.get('no_pdf', False)
129        template = self._kwargs.get('template', None)
130
131        def _on_change(yaml_path: Path) -> None:
132            self.log_line.emit(f'[watch] {yaml_path.name}', 'info')
133            try:
134                generar(yaml_path, template=template, no_pdf=no_pdf)
135                self.log_line.emit(f'✓ {yaml_path.name}', 'ok')
136            except Exception as exc:
137                self.log_line.emit(f'Error: {exc}', 'error')
138
139        try:
140            start_watch(yamls, _on_change)
141        except WatchdogNotAvailable as exc:
142            self.log_line.emit(str(exc), 'error')
143            raise SystemExit(1)
144
145    # ── Clasificador de nivel de log ──────────────────────────────────────────
146
147    def _on_stdout(self, line: str) -> None:
148        if not line.strip():
149            return
150        low = line.lower()
151        if any(k in low for k in ('error', 'traceback', 'exception')):
152            level = 'error'
153        elif any(k in low for k in ('generado', 'success', 'ok', 'creado', 'versión')):
154            level = 'ok'
155        else:
156            level = 'info'
157        self.log_line.emit(line, level)
class BuildWorker(PySide6.QtCore.QObject):
 46class BuildWorker(QObject):
 47    """Ejecuta operaciones de build/clean/version/new en un QThread.
 48
 49    Uso:
 50        worker = BuildWorker('build', yamls=[Path(...)])
 51        thread = QThread()
 52        worker.moveToThread(thread)
 53        thread.started.connect(worker.run)
 54        worker.finished.connect(thread.quit)
 55        thread.start()
 56    """
 57
 58    log_line        = Signal(str, str)   # (texto, nivel: 'info' | 'ok' | 'error')
 59    finished        = Signal(bool)       # True = éxito
 60    progress        = Signal(int, int)   # (actual, total)
 61    files_generated = Signal(list)       # list[tuple[Path, Path | None]]
 62
 63    def __init__(self, task: str, **kwargs):
 64        super().__init__()
 65        self._task   = task
 66        self._kwargs = kwargs
 67
 68    # ── Punto de entrada del hilo ─────────────────────────────────────────────
 69
 70    def run(self) -> None:
 71        try:
 72            with _StdoutCapture(self._on_stdout):
 73                self._dispatch()
 74            self.finished.emit(True)
 75        except SystemExit as exc:
 76            code = exc.code
 77            if code and code != 0:
 78                self.log_line.emit(f'Error: código de salida {code}', 'error')
 79                self.finished.emit(False)
 80            else:
 81                self.finished.emit(True)
 82        except Exception:
 83            for line in traceback.format_exc().splitlines():
 84                self.log_line.emit(line, 'error')
 85            self.finished.emit(False)
 86
 87    # ── Despacho de tareas ────────────────────────────────────────────────────
 88
 89    def _dispatch(self) -> None:
 90        task = self._task
 91
 92        if task == 'build':
 93            yamls = self._kwargs.get('yamls', [])
 94            _cmd_generate(
 95                SimpleNamespace(targets=yamls, template=None),
 96                progress_callback=lambda cur, tot: self.progress.emit(cur, tot),
 97                results_callback=lambda r: self.files_generated.emit(r),
 98            )
 99
100        elif task == 'clean':
101            _cmd_clean(SimpleNamespace(
102                targets=self._kwargs.get('course_dirs', []),
103                tipo=self._kwargs.get('tipo', None),
104            ))
105
106        elif task == 'version':
107            _cmd_version(SimpleNamespace(
108                targets=self._kwargs.get('course_dirs', []),
109            ))
110
111        elif task == 'new':
112            _cmd_new(SimpleNamespace(name=self._kwargs['name']))
113
114        elif task == 'import_odp':
115            _cmd_import(Path(self._kwargs['odp_path']))
116
117        elif task == 'watch':
118            self._run_watch()
119
120        else:
121            raise ValueError(f'Tarea desconocida: {self._task!r}')
122
123    def _run_watch(self) -> None:
124        """Watch reactivo: regenera YAMLs al guardar usando lib.watcher."""
125        from lib.watcher import start_watch, WatchdogNotAvailable
126        from cli.__main__ import generar, _course_dir_from_yaml
127
128        yamls: list[Path] = [Path(y) for y in self._kwargs.get('yamls', [])]
129        no_pdf   = self._kwargs.get('no_pdf', False)
130        template = self._kwargs.get('template', None)
131
132        def _on_change(yaml_path: Path) -> None:
133            self.log_line.emit(f'[watch] {yaml_path.name}', 'info')
134            try:
135                generar(yaml_path, template=template, no_pdf=no_pdf)
136                self.log_line.emit(f'✓ {yaml_path.name}', 'ok')
137            except Exception as exc:
138                self.log_line.emit(f'Error: {exc}', 'error')
139
140        try:
141            start_watch(yamls, _on_change)
142        except WatchdogNotAvailable as exc:
143            self.log_line.emit(str(exc), 'error')
144            raise SystemExit(1)
145
146    # ── Clasificador de nivel de log ──────────────────────────────────────────
147
148    def _on_stdout(self, line: str) -> None:
149        if not line.strip():
150            return
151        low = line.lower()
152        if any(k in low for k in ('error', 'traceback', 'exception')):
153            level = 'error'
154        elif any(k in low for k in ('generado', 'success', 'ok', 'creado', 'versión')):
155            level = 'ok'
156        else:
157            level = 'info'
158        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()

BuildWorker(task: str, **kwargs)
63    def __init__(self, task: str, **kwargs):
64        super().__init__()
65        self._task   = task
66        self._kwargs = kwargs
def log_line(unknown):
def finished(unknown):
def progress(unknown):
def files_generated(unknown):
def run(self) -> None:
70    def run(self) -> None:
71        try:
72            with _StdoutCapture(self._on_stdout):
73                self._dispatch()
74            self.finished.emit(True)
75        except SystemExit as exc:
76            code = exc.code
77            if code and code != 0:
78                self.log_line.emit(f'Error: código de salida {code}', 'error')
79                self.finished.emit(False)
80            else:
81                self.finished.emit(True)
82        except Exception:
83            for line in traceback.format_exc().splitlines():
84                self.log_line.emit(line, 'error')
85            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 )