Add JavaScript scripting: --run and "Run Script..." (bugtracker #162)

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 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-16 19:44:14 +12:00
parent 2eed3acd3d
commit eba258f6cd
9 changed files with 540 additions and 0 deletions
+16
View File
@@ -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
+14
View File
@@ -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
+14
View File
@@ -16,6 +16,9 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <script.js> <project.qet> (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
+44
View File
@@ -16,6 +16,9 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qetdiagrameditor.h"
#ifdef QET_HAS_SCRIPTING
#include "scripting/qetscripting.h"
#endif
#include <QCoreApplication>
#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
+6
View File
@@ -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
+170
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "qetscriptapi.h"
#include "../cli_export.h"
#include "../diagram.h"
#include "../diagramcontent.h"
#include "../qetproject.h"
#include <QTextStream>
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<Diagram *> 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<Diagram *> 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<Diagram *> 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";
}
+89
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef QET_SCRIPT_API_H
#define QET_SCRIPT_API_H
#include <QObject>
#include <QString>
#include <QStringList>
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
+126
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "qetscripting.h"
#include "qetscriptapi.h"
#include "../qetproject.h"
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#ifdef QET_HAS_SCRIPTING
#include <QJSEngine>
#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 <script.js> <project.qet>\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
}
+61
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef QET_SCRIPTING_H
#define QET_SCRIPTING_H
#include <QStringList>
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 <script.js> <project.qet>`
invocation.
*/
bool isRunRequest(const QStringList &args);
/**
@brief Run the script named in @p args against the project also
named there, headless.
Usage: qelectrotech --run <script.js> <project.qet>
@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