QtScript を使用して、開発およびテストの目的でアプリケーションの一部を自動化しています。アサーションをテストしたいところまで来ました。「スタンドアロン アサーション ライブラリ」に基づいていますか? そして、Debian リポジトリで見つけたものは、Should.js に行きました。
require()
ノードの機能に依存するため、Qt アプリケーションにロードするのに問題があります。「CommonJSのrequire()をサポートする」から始めて、以下のコードで終わる、これのバージョンを実装してみました。
それを機能させることはできますか、それともこのアプローチでは運命づけられていますか? should.js のビットを 1 つのファイルにコピーしたほうがよいでしょうか? フォークを最新の状態に保つ責任を負いたくありません。(このコードを再配布するつもりはないので、ライセンスは問題ではありません)。
これが私の MCVE です。すみません、これ以上短くできませんでした!
should.cpp
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QScriptEngine>
#include <QScriptContext>
#include <QScriptContextInfo>
#include <QTextStream>
// Primitive implementation of Node.js require().
// N.B. Supports only .js sources.
QScriptValue require(QScriptContext* context, QScriptEngine* engine)
{
const QString moduleName = context->argument(0).toString();
// First, look in our modules cache
QScriptValue modules = engine->globalObject().property("$MODULES");
QScriptValue module = modules.property(moduleName);
if (module.isValid()) {
auto cached_file = module.property("filename");
auto time_stamp = module.property("timestamp");
auto code = module.property("code");
if (code.isObject() && cached_file.isString() && time_stamp.isDate()) {
if (QFileInfo(cached_file.toString()).lastModified() == time_stamp.toDateTime()) {
qDebug() << "found up-to-date module for require of" << moduleName;
return code;
} else {
qDebug() << "cache stale for" << moduleName;
}
}
} else {
// Prepare a cache entry, as some modules recursively include each
// other. This way, they at least get the partial definition of the
// other, rather than a stack overflow.
module = engine->newObject();
modules.setProperty(moduleName, module);
}
qDebug() << "require" << moduleName;
// resolve filename relative to the calling script
QString filename = moduleName + ".js";
for (auto *p = context; p; p = p->parentContext()) {
QScriptContextInfo info(p);
auto parent_file = info.fileName();
if (parent_file.isEmpty())
continue;
// else, we reached a context with a filename
QDir base_dir = QFileInfo(parent_file).dir();
filename = base_dir.filePath(filename);
if (QFile::exists(filename)) {
break;
}
}
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
return context->throwValue(QString("Failed to open %0").arg(moduleName));
}
QTextStream in(&file);
in.setCodec("UTF-8");
auto script = in.readAll();
file.close();
#if 0
// I had to disable this, because it barfs on "get not()" definition - is
// that a Node extension? Will it cause me problems even if I get require()
// working?
auto syntax_check = QScriptEngine::checkSyntax(script);
if (syntax_check.state() != QScriptSyntaxCheckResult::Valid) {
return context->throwValue(QString("%2:%0:%1: Syntax error: %3")
.arg(syntax_check.errorLineNumber())
.arg(syntax_check.errorColumnNumber())
.arg(filename, syntax_check.errorMessage()));
}
#endif
// create a new context, and capture the module's exports
QScriptContext* newContext = engine->pushContext();
QScriptValue exports = engine->newObject();
newContext->activationObject().setProperty("exports", exports);
module.setProperty("code", exports);
module.setProperty("filename", filename);
module.setProperty("timestamp", engine->newDate(QFileInfo(filename).lastModified()));
// run the script
engine->evaluate(script, filename);
// get the exports
module.setProperty("code", newContext->activationObject().property("exports"));
engine->popContext();
if (engine->hasUncaughtException())
return engine->uncaughtException();
qDebug() << "loaded" << moduleName;
return exports;
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QScriptEngine engine;
// register global require() function
auto global = engine.globalObject();
global.setProperty("require", engine.newFunction(require));
global.setProperty("$MODULES", engine.newObject());
engine.evaluate("var should = require('/usr/lib/nodejs/should/lib/should');");
if (engine.hasUncaughtException()) {
qCritical() << engine.uncaughtException().toString().toStdString().c_str();
qWarning() << engine.uncaughtExceptionBacktrace().join("\n").toStdString().c_str();
return 1;
}
return 0;
}
メイクファイル
check: should
./should
CXXFLAGS += -std=c++11 -Wall -Wextra -Werror
CXXFLAGS += -fPIC
CXXFLAGS += $(shell pkg-config --cflags Qt5Script)
LDLIBS += $(shell pkg-config --libs Qt5Script)
出力は
require "/usr/lib/nodejs/should/lib/should"
require "./util"
require "./inspect"
found up-to-date module for require of "./util"
loaded "./inspect"
require "assert"
Failed to open assert
<eval>() at /usr/lib/nodejs/should/lib/./util.js:126
<native>() at -1
<native>('./util') at -1
<eval>() at /usr/lib/nodejs/should/lib/should.js:8
<native>() at -1
<native>('/usr/lib/nodejs/should/lib/should') at -1
<global>() at 1
require
(ついでに -代わりにスタックトレースで実際の関数名を取得するにはどうすれ<native>
ばよいですか? スロットがこれを管理するので、できるはずですよね?)