From eba258f6cdbb66306b15c9b233ee1aa6690537c5 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Wed, 16 Sep 2026 19:44:14 +1200 Subject: [PATCH] Add JavaScript scripting: --run and "Run Script..." (bugtracker #162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following up on my own comments there: a deliberately small, mostly read-only scripting surface, exposed to scripts as a single global `qet` object (QetScriptApi) built on QJSEngine rather than an embedded Python interpreter -- no new toolchain to package (QJSEngine ships in every Qt SDK QET already targets, via the Qml module), no GIL, no version pinning, automatic reflection of the QObject-derived core classes' own methods with no hand-written binding layer. ## What a script can do - Read the model: project title, file path, folio count/titles, element/conductor counts per folio. - Trigger the same operations the --export-* CLI flags already do (pdf/png/svg/cables/wires/bom/wiring/nets/links/info), plus set-titleblock and save -- thin wrappers around CLIExport::run(), reusing its already-tested logic rather than duplicating it. Deliberately NOT in this version: creating or editing diagram geometry, undo integration, driving the GUI. All explicitly out of scope per the discussion on #162. ## Two entry points, both built and tested - `qelectrotech --run script.js project.qet` -- headless/CI. - Projet > "Exécuter un script..." -- an interactive macro against the currently open project. Export/save calls act on the project's file on disk (see QetScriptApi's class comment for why), so unsaved GUI edits aren't visible to the script; save first if that matters. ## Optional dependency, not a hard requirement Qt::Qml is probed the same way QtPdf already is in this codebase: QUIET, non-fatal, behind a QET_HAS_SCRIPTING compile definition. A build without it compiles and links identically; the CLI flag and menu action are simply absent (main.cpp) or compile to a clear "not available" stderr message rather than silently disappearing (qetscripting.cpp), matching the existing QtPdf pattern rather than introducing a new one. One real bug caught building this, not assumed away: my first pass conditionally excluded the new source files from QET_SRC_FILES behind `if(QET_HAS_SCRIPTING)` inside qet_compilation_vars.cmake -- but that file is included before QET_HAS_SCRIPTING is set in the top-level CMakeLists.txt, so the variable didn't exist yet at that point and the files were silently never compiled, only caught by an undefined-symbol link error. Fixed by following the QtPdf file's own precedent: compile the files unconditionally, guard their Qt::Qml-dependent content internally instead. ## Verified Qt6, build clean, ctest 6/6. - Headless: a script reading project/folio/element/conductor counts, calling exportInfo() and exportPdf() against a real project -- correct JSON, a real single-page PDF confirmed with `file`. Error paths: a thrown script exception reports file:line:message and exit 1; missing script/project arguments exit 2 (matching CLIExport's own usage-error convention); a missing project file is reported and does not hang. - Corpus: the same read-model script run against all 24 shipped example projects, 0 failures. - GUI: "Exécuter un script..." opens a real file dialog filtered to *.js, running the picked script against the live open project produced the exact expected JSON export file, and the application was still fully responsive afterward. Refs #162. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 16 +++ cmake/qet_compilation_vars.cmake | 14 +++ sources/main.cpp | 14 +++ sources/qetdiagrameditor.cpp | 44 ++++++++ sources/qetdiagrameditor.h | 6 + sources/scripting/qetscriptapi.cpp | 170 +++++++++++++++++++++++++++++ sources/scripting/qetscriptapi.h | 89 +++++++++++++++ sources/scripting/qetscripting.cpp | 126 +++++++++++++++++++++ sources/scripting/qetscripting.h | 61 +++++++++++ 9 files changed, 540 insertions(+) create mode 100644 sources/scripting/qetscriptapi.cpp create mode 100644 sources/scripting/qetscriptapi.h create mode 100644 sources/scripting/qetscripting.cpp create mode 100644 sources/scripting/qetscripting.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6440cc257..60e606b81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,22 @@ if(QT_VERSION_MAJOR GREATER_EQUAL 6) endif() endif() +# JavaScript scripting (bugtracker #162: `--run script.js`, and later a +# "Run Script..." menu action) needs QJSEngine, in the Qml module. As with +# QtPdf above, this is not fatal when missing: some minimal Qt6 packagings +# may not ship it, and scripting is optional functionality nothing else in +# the application depends on. When it's missing the feature is silently +# disabled - see the QET_HAS_SCRIPTING guard in qetscripting.*. +set(QET_HAS_SCRIPTING FALSE) +find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS Qml) +if(TARGET Qt${QT_VERSION_MAJOR}::Qml) + set(QET_HAS_SCRIPTING TRUE) + list(APPEND QET_PRIVATE_LIBRARIES Qt::Qml) + add_compile_definitions(QET_HAS_SCRIPTING) +else() + message(STATUS "Qt Qml module not available: JavaScript scripting (--run) disabled") +endif() + find_package(SQLite3 REQUIRED) # CMake < 4.3 only creates the SQLite::SQLite3 target (no SQLite3::SQLite3 diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index a8654b903..189011bf5 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -849,6 +849,20 @@ if(QT_VERSION_MAJOR GREATER_EQUAL 6) ) endif() +# JavaScript scripting (bugtracker #162). Unconditionally in the source +# list, like the QtPdf files above: this file is included before the +# QET_HAS_SCRIPTING probe runs in the top-level CMakeLists.txt, so the +# variable isn't set yet here. Same pattern as QtPdf: always compiled, the +# actual Qt::Qml dependent code is behind #ifdef QET_HAS_SCRIPTING inside +# qetscripting.cpp/qetscriptapi.cpp themselves, compiling to a harmless +# stub when the module wasn't found. +list(APPEND QET_SRC_FILES + ${QET_DIR}/sources/scripting/qetscriptapi.cpp + ${QET_DIR}/sources/scripting/qetscriptapi.h + ${QET_DIR}/sources/scripting/qetscripting.cpp + ${QET_DIR}/sources/scripting/qetscripting.h +) + set(TS_FILES ${QET_DIR}/lang/qet_ar.ts ${QET_DIR}/lang/qet_ca.ts diff --git a/sources/main.cpp b/sources/main.cpp index a6590ec93..b8dabc258 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -16,6 +16,9 @@ along with QElectroTech. If not, see . */ #include "cli_export.h" +#ifdef QET_HAS_SCRIPTING +#include "scripting/qetscripting.h" +#endif #include "logging/eventloopwatchdog.h" #include "logging/qetlogger.h" #include "machine_info.h" @@ -135,6 +138,17 @@ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFacto QET::QetMessageBox::setNonInteractive(true); return CLIExport::run(export_app.arguments()); } +#ifdef QET_HAS_SCRIPTING + // Headless scripting: --run (bugtracker + // #162). Same reasoning as the export branch above for running + // before SingleApplication and answering message boxes headlessly. + if (QetScripting::isRunRequest(raw_args)) { + QApplication script_app(argc, argv); + QETProject::setBackupEnabled(false); + QET::QetMessageBox::setNonInteractive(true); + return QetScripting::run(script_app.arguments()); + } +#endif } // Resolve the logger's state (log directory, session filename, open diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 7c21742c4..d7f668e62 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -16,6 +16,9 @@ along with QElectroTech. If not, see . */ #include "qetdiagrameditor.h" +#ifdef QET_HAS_SCRIPTING +#include "scripting/qetscripting.h" +#endif #include #include "ElementsCollection/elementscollectionwidget.h" #include "QWidgetAnimation/qwidgetanimation.h" @@ -549,6 +552,15 @@ void QETDiagramEditor::setUpActions() m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this); connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering); +#ifdef QET_HAS_SCRIPTING + // Run a JavaScript macro against the current project (bugtracker #162). + m_run_script = new QAction(tr("Exécuter un script..."), this); + m_run_script->setStatusTip( + tr("Exécute un script JavaScript sur le projet courant (voir qet.*" + " dans le script pour l'API disponible)")); + connect(m_run_script, &QAction::triggered, this, &QETDiagramEditor::slot_runScript); +#endif + #ifdef QET_EXPORT_PROJECT_DB m_export_project_db = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter la base de donnée interne du projet"), this); connect(m_export_project_db, &QAction::triggered, [this]() { @@ -1002,6 +1014,9 @@ void QETDiagramEditor::setUpMenu() menu_project -> addAction(m_project_export_wiring_list); menu_project -> addAction(m_project_wiring_list_view); menu_project -> addAction(m_terminal_numbering); +#ifdef QET_HAS_SCRIPTING + menu_project -> addAction(m_run_script); +#endif #ifdef QET_EXPORT_PROJECT_DB menu_project -> addSeparator(); menu_project -> addAction(m_export_project_db); @@ -1856,6 +1871,9 @@ void QETDiagramEditor::slot_updateActions() m_project_export_wiring_list -> setEnabled(opened_project); m_project_wiring_list_view -> setEnabled(opened_project); m_terminal_numbering -> setEnabled(editable_project); +#ifdef QET_HAS_SCRIPTING + m_run_script -> setEnabled(opened_project); +#endif #ifdef QET_EXPORT_PROJECT_DB m_export_project_db -> setEnabled(editable_project); #endif @@ -2970,3 +2988,29 @@ void QETDiagramEditor::slot_terminalNumbering() { } } } + +#ifdef QET_HAS_SCRIPTING +/** + @brief QETDiagramEditor::slot_runScript + Run a JavaScript macro against the current project (bugtracker #162). + See QetScriptApi for what a script can do -- read-mostly: folio/element/ + conductor counts and the same export operations the --export-* CLI + flags provide. Export/save calls inside the script act on this + project's file on disk, so unsaved edits in the open editor are not + visible to the script; save first if that matters. +*/ +void QETDiagramEditor::slot_runScript() { + QETProject *project = currentProject(); + if (!project) return; + + const QString script_path = QFileDialog::getOpenFileName( + this, + tr("Exécuter un script"), + QString(), + tr("Scripts JavaScript (*.js);;Tous les fichiers (*)") + ); + if (script_path.isEmpty()) return; + + QetScripting::runOnProject(script_path, project); +} +#endif diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index ed07a2395..4a8b3f1fe 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -136,6 +136,9 @@ class QETDiagramEditor : public QETMainWindow void editProjectProperties(ProjectView *); void editProjectProperties(QETProject *); void slot_terminalNumbering(); +#ifdef QET_HAS_SCRIPTING + void slot_runScript(); +#endif void editDiagramProperties(DiagramView *); void editDiagramProperties(Diagram *); void addDiagramToProject(QETProject *); @@ -211,6 +214,9 @@ class QETDiagramEditor : public QETMainWindow *m_project_export_wiring_list, ///< Action to export the wiring list *m_project_wiring_list_view, ///< Action to show the wiring list read from the project database *m_terminal_numbering, ///< Action to launch terminal numbering +#ifdef QET_HAS_SCRIPTING + *m_run_script, ///< Action to run a JavaScript macro against the current project +#endif *m_export_project_db, ///Export to file the internal database of the current project *m_tile_window, ///< Show MDI subwindows as tile *m_cascade_window, ///< Show MDI subwindows as cascade diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp new file mode 100644 index 000000000..af6dedaa4 --- /dev/null +++ b/sources/scripting/qetscriptapi.cpp @@ -0,0 +1,170 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "qetscriptapi.h" + +#include "../cli_export.h" +#include "../diagram.h" +#include "../diagramcontent.h" +#include "../qetproject.h" + +#include + +QetScriptApi::QetScriptApi(QETProject *project, QObject *parent) : + QObject(parent), + m_project(project) +{ +} + +QString QetScriptApi::projectTitle() const +{ + return m_project ? m_project->title() : QString(); +} + +QString QetScriptApi::filePath() const +{ + return m_project ? m_project->filePath() : QString(); +} + +int QetScriptApi::folioCount() const +{ + return m_project ? m_project->diagrams().count() : 0; +} + +QString QetScriptApi::folioTitle(int index) const +{ + if (!m_project) return QString(); + const QList diagrams = m_project->diagrams(); + if (index < 0 || index >= diagrams.count()) return QString(); + return diagrams.at(index)->title(); +} + +int QetScriptApi::elementCount(int folioIndex) const +{ + if (!m_project) return 0; + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) return 0; + DiagramContent content(diagrams.at(folioIndex), false); + return content.m_elements.count(); +} + +int QetScriptApi::conductorCount(int folioIndex) const +{ + if (!m_project) return 0; + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) return 0; + DiagramContent content(diagrams.at(folioIndex), false); + return content.conductors(DiagramContent::AnyConductor).count(); +} + +/** + @brief QetScriptApi::runFlag + Build a CLIExport::run() argument list from this project's own file + path plus @p args, and run it. Reopens the project from disk -- see the + class comment for why that trade-off was made. + @param flag one of the --export-* / --resave / --set-titleblock flags + @param args the flag's own positional arguments (output path, etc.) + @return true if CLIExport::run() returned 0 (success) +*/ +bool QetScriptApi::runFlag(const QString &flag, const QStringList &args) +{ + if (!m_project) { + log(QStringLiteral("qet.%1: no project").arg(flag)); + return false; + } + const QString path = m_project->filePath(); + if (path.isEmpty()) { + log(QStringLiteral("qet.%1: project has no file path -- save it first").arg(flag)); + return false; + } + QStringList full_args; + full_args << flag << path << args; + return CLIExport::run(full_args) == 0; +} + +bool QetScriptApi::exportPdf(const QString &output, bool showTerminals) +{ + QStringList args{output}; + if (showTerminals) args << QStringLiteral("--show-terminals"); + return runFlag(QStringLiteral("--export-pdf"), args); +} + +bool QetScriptApi::exportPng(const QString &outDir, bool showTerminals) +{ + QStringList args{outDir}; + if (showTerminals) args << QStringLiteral("--show-terminals"); + return runFlag(QStringLiteral("--export-png"), args); +} + +bool QetScriptApi::exportSvg(const QString &outDir, bool showTerminals) +{ + QStringList args{outDir}; + if (showTerminals) args << QStringLiteral("--show-terminals"); + return runFlag(QStringLiteral("--export-svg"), args); +} + +bool QetScriptApi::exportCables(const QString &output) +{ + return runFlag(QStringLiteral("--export-cables"), {output}); +} + +bool QetScriptApi::exportWires(const QString &output) +{ + return runFlag(QStringLiteral("--export-wires"), {output}); +} + +bool QetScriptApi::exportBom(const QString &output) +{ + return runFlag(QStringLiteral("--export-bom"), {output}); +} + +bool QetScriptApi::exportWiring(const QString &output) +{ + return runFlag(QStringLiteral("--export-wiring"), {output}); +} + +bool QetScriptApi::exportNets(const QString &output) +{ + return runFlag(QStringLiteral("--export-nets"), {output}); +} + +bool QetScriptApi::exportLinks(const QString &output) +{ + return runFlag(QStringLiteral("--export-links"), {output}); +} + +bool QetScriptApi::exportInfo(const QString &output) +{ + return runFlag(QStringLiteral("--info"), output.isEmpty() ? QStringList{} : QStringList{output}); +} + +bool QetScriptApi::setTitleBlock(const QString &output, const QStringList &assignments) +{ + QStringList args{output}; + args << assignments; + return runFlag(QStringLiteral("--set-titleblock"), args); +} + +bool QetScriptApi::save(const QString &output) +{ + return runFlag(QStringLiteral("--resave"), {output}); +} + +void QetScriptApi::log(const QString &message) +{ + QTextStream(stderr) << message << "\n"; +} diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h new file mode 100644 index 000000000..e454e1d3b --- /dev/null +++ b/sources/scripting/qetscriptapi.h @@ -0,0 +1,89 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef QET_SCRIPT_API_H +#define QET_SCRIPT_API_H + +#include +#include +#include + +class QETProject; + +/** + @brief The QetScriptApi class + The object a script sees as `qet` (bugtracker #162): a small, + deliberately read-mostly surface over an open project, for scripted + batch/CI work and human-written macros. + + @b Scope, on purpose: this reads the model (folio count, titles, element + and conductor counts -- the same data the `--info` CLI export already + reports) and can trigger the same export/save operations the `--export-*` + CLI flags already do. It does not create or edit diagram geometry, does + not touch the undo stack, and does not drive the GUI. Those are all + explicitly out of scope for this first version; see the discussion on + bugtracker #162. + + Export/save methods are thin wrappers around CLIExport::run() -- the + exact same, already-tested code path the `--export-*` flags use -- built + from the project's own file path, not the live in-memory instance. That + keeps this file free of any dependency on cli_export.cpp's internals, + at the cost of re-opening the project from disk for each call: fine for + the batch/CI use case this targets, and for the headless `--run` entry + point it's exactly what a second `--export-*` invocation would have + done anyway. A macro acting on unsaved GUI edits should call save() + first. +*/ +class QetScriptApi : public QObject +{ + Q_OBJECT + + public: + explicit QetScriptApi(QETProject *project, QObject *parent = nullptr); + + // -- read the model -- + Q_INVOKABLE QString projectTitle() const; + Q_INVOKABLE QString filePath() const; + Q_INVOKABLE int folioCount() const; + Q_INVOKABLE QString folioTitle(int index) const; + Q_INVOKABLE int elementCount(int folioIndex) const; + Q_INVOKABLE int conductorCount(int folioIndex) const; + + // -- export / save: thin wrappers around the --export-* CLI paths -- + Q_INVOKABLE bool exportPdf(const QString &output, bool showTerminals = false); + Q_INVOKABLE bool exportPng(const QString &outDir, bool showTerminals = false); + Q_INVOKABLE bool exportSvg(const QString &outDir, bool showTerminals = false); + Q_INVOKABLE bool exportCables(const QString &output); + Q_INVOKABLE bool exportWires(const QString &output); + Q_INVOKABLE bool exportBom(const QString &output); + Q_INVOKABLE bool exportWiring(const QString &output); + Q_INVOKABLE bool exportNets(const QString &output); + Q_INVOKABLE bool exportLinks(const QString &output); + Q_INVOKABLE bool exportInfo(const QString &output); + Q_INVOKABLE bool setTitleBlock(const QString &output, const QStringList &assignments); + Q_INVOKABLE bool save(const QString &output); + + // -- logging: a script has no console of its own -- + Q_INVOKABLE void log(const QString &message); + + private: + bool runFlag(const QString &flag, const QStringList &args); + + QETProject *m_project; +}; + +#endif // QET_SCRIPT_API_H diff --git a/sources/scripting/qetscripting.cpp b/sources/scripting/qetscripting.cpp new file mode 100644 index 000000000..7a6addbf9 --- /dev/null +++ b/sources/scripting/qetscripting.cpp @@ -0,0 +1,126 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "qetscripting.h" + +#include "qetscriptapi.h" +#include "../qetproject.h" + +#include +#include +#include + +#ifdef QET_HAS_SCRIPTING +#include +#endif + +namespace { + QTextStream out(stdout); + QTextStream err(stderr); +} + +namespace QetScripting { + +bool isRunRequest(const QStringList &args) +{ + return args.contains(QStringLiteral("--run")); +} + +#ifdef QET_HAS_SCRIPTING + +int run(const QStringList &args) +{ + const int idx = args.indexOf(QStringLiteral("--run")); + const QString script_path = args.value(idx + 1); + const QString project_path = args.value(idx + 2); + if (script_path.isEmpty() || project_path.isEmpty()) { + err << "Usage: qelectrotech --run \n"; + return 2; + } + if (!QFileInfo::exists(script_path)) { + err << "Script not found: " << script_path << "\n"; + return 2; + } + if (!QFileInfo::exists(project_path)) { + err << "Project not found: " << project_path << "\n"; + return 2; + } + + QETProject project(project_path); + if (project.state() != QETProject::Ok) { + err << "Failed to open project: " << project_path + << " (state " << project.state() << ")\n"; + return 1; + } + + return runOnProject(script_path, &project) ? 0 : 1; +} + +bool runOnProject(const QString &scriptPath, QETProject *project) +{ + QFile file(scriptPath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + err << "Cannot open script: " << scriptPath << "\n"; + return false; + } + const QString source = QString::fromUtf8(file.readAll()); + file.close(); + + QJSEngine engine; + auto *api = new QetScriptApi(project, &engine); + QJSValue qet_value = engine.newQObject(api); + // newQObject() takes ownership by default (QJSEngine::JavaScriptOwnership), + // which would delete api as soon as the engine's GC decides to -- api's + // real owner is the engine itself via the parent-child relationship set + // above, so keep the engine, not the GC, in charge of its lifetime. + engine.setObjectOwnership(api, QJSEngine::CppOwnership); + engine.globalObject().setProperty(QStringLiteral("qet"), qet_value); + + QJSValue result = engine.evaluate(source, scriptPath); + if (result.isError()) { + err << "Script error: " << scriptPath << ":" + << result.property(QStringLiteral("lineNumber")).toInt() << ": " + << result.toString() << "\n"; + return false; + } + return true; +} + +#else // !QET_HAS_SCRIPTING + +// Qt::Qml was not found at configure time (see the QET_HAS_SCRIPTING probe +// in the top-level CMakeLists.txt). Compile to a clear, non-silent failure +// rather than omitting these symbols -- the same "always compiled, the +// disabled path says why" shape as the QtPdf feature guard. + +int run(const QStringList &) +{ + err << "This build of QElectroTech was compiled without the Qt Qml " + "module, so JavaScript scripting (--run) is not available.\n"; + return 1; +} + +bool runOnProject(const QString &, QETProject *) +{ + err << "This build of QElectroTech was compiled without the Qt Qml " + "module, so JavaScript scripting is not available.\n"; + return false; +} + +#endif // QET_HAS_SCRIPTING + +} diff --git a/sources/scripting/qetscripting.h b/sources/scripting/qetscripting.h new file mode 100644 index 000000000..ac7e1e51a --- /dev/null +++ b/sources/scripting/qetscripting.h @@ -0,0 +1,61 @@ +/* + Copyright 2006-2026 The QElectroTech Team + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef QET_SCRIPTING_H +#define QET_SCRIPTING_H + +#include + +class QETProject; + +/** + @brief JavaScript scripting entry points (bugtracker #162). + + A script sees a single global, `qet` (see QetScriptApi), exposing a + deliberately small, mostly read-only surface: folio/element/conductor + counts and the same export operations the `--export-*` CLI flags + provide. See QetScriptApi's class comment for what is and is not in + scope for this first version. +*/ +namespace QetScripting { + + /** + @brief True if @p args is a `--run ` + invocation. + */ + bool isRunRequest(const QStringList &args); + + /** + @brief Run the script named in @p args against the project also + named there, headless. + Usage: qelectrotech --run + @return process exit code: 0 on success, 1 if the project failed to + open or the script threw, 2 on a usage error. + */ + int run(const QStringList &args); + + /** + @brief Run @p scriptPath against an already-open @p project (the + "Run Script..." GUI macro path). Errors go to stderr; there is no + modal reporting in this first version. + @return true if the script ran without throwing. + */ + bool runOnProject(const QString &scriptPath, QETProject *project); + +} + +#endif // QET_SCRIPTING_H