From b7ce89944e69ded1b11104f1527e7007c1232ede Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 2 Oct 2026 09:04:06 +1300 Subject: [PATCH] Scripting: qet.apiSignatures(), the calls a script can make Read from the meta-object, so the list is the one the running build has: for someone writing a script, and for an assistant that has to write one without the source at hand. Co-Authored-By: Claude Opus 5.5 --- sources/scripting/qetscriptapi.cpp | 34 +++++++++++++++++++++++++ sources/scripting/qetscriptapi.h | 3 +++ tests/qttest/tst_scriptcurrentfolio.cpp | 22 ++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index 3face8331..a5fdec93f 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -122,6 +122,40 @@ int QetScriptApi::folioCount() const folio, and the first one stands in for it so a script written for the editor can still be tried headless; -1 if the project has none. */ +/** + @brief QetScriptApi::apiSignatures + Every call a script can make, as "returnType name(type param, ...)", + read from the meta-object rather than written out by hand, so the list + is the one this build has and cannot drift from it: for a person + writing a script, and for an assistant that has to write one without + the source at hand. A call with default arguments is listed once, with + all of them. +*/ +QStringList QetScriptApi::apiSignatures() const +{ + QStringList list; + const QMetaObject *meta = metaObject(); + for (int i = meta->methodOffset(); i < meta->methodCount(); ++i) { + const QMetaMethod method = meta->method(i); + if (method.methodType() != QMetaMethod::Method + || method.access() != QMetaMethod::Public + || (method.attributes() & QMetaMethod::Cloned)) { + continue; + } + const QList types = method.parameterTypes(); + const QList names = method.parameterNames(); + QStringList params; + for (int p = 0; p < types.size(); ++p) { + params << QString::fromLatin1(types.at(p) + ' ' + names.value(p)); + } + list << QStringLiteral("%1 %2(%3)") + .arg(QString::fromLatin1(method.typeName()), + QString::fromLatin1(method.name()), + params.join(QStringLiteral(", "))); + } + return list; +} + int QetScriptApi::currentFolio() const { if (!m_project) return -1; diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index e79d44a84..44abbfe2a 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -579,6 +579,9 @@ class QetScriptApi : public QObject // -- logging: a script has no console of its own -- Q_INVOKABLE void log(const QString &message); + // -- what a script can call, read from this class itself -- + Q_INVOKABLE QStringList apiSignatures() const; + void setUndoGrouped(bool grouped); private: diff --git a/tests/qttest/tst_scriptcurrentfolio.cpp b/tests/qttest/tst_scriptcurrentfolio.cpp index 418e93e01..928700042 100644 --- a/tests/qttest/tst_scriptcurrentfolio.cpp +++ b/tests/qttest/tst_scriptcurrentfolio.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -88,6 +89,27 @@ private slots: QCOMPARE(r.value(QStringLiteral("current")).toInt(), -1); } + // The list is read from the build itself: the calls a script uses + // are in it with their parameter names, each once. + void apiSignaturesListed() + { + const QJsonObject r = run(QStringLiteral( + "qet.log('PROBE ' + JSON.stringify({sigs: qet.apiSignatures()}));\n"), + QStringLiteral(QET_EXAMPLES_DIR "/perceuse.qet")); + QVERIFY2(!r.isEmpty(), "the script logged nothing"); + QStringList sigs; + for (const QJsonValue &v : r.value(QStringLiteral("sigs")).toArray()) + sigs << v.toString(); + QVERIFY(sigs.size() > 100); + QVERIFY(sigs.contains(QStringLiteral("int currentFolio()"))); + QVERIFY(sigs.contains(QStringLiteral( + "int addText(int folioIndex, QString text, double x, double y)"))); + QVERIFY(sigs.contains(QStringLiteral( + "bool exportPdf(QString output, bool showTerminals)"))); + QCOMPARE(sigs.filter(QStringLiteral(" exportPdf(")).size(), 1); + QVERIFY(sigs.filter(QStringLiteral("setUndoGrouped")).isEmpty()); + } + void headlessUndoStaysPerCall() { const QJsonObject r = run(QStringLiteral(