Я использую QtScript для автоматизации частей моего приложения в целях разработки и тестирования. Я пришел к тому, что я хочу проверить утверждения, и на основе «автономные библиотеки утверждений?» и то, что я мог найти в репозиториях Debian, я выбрал Should.js.
У меня проблемы с загрузкой в мое приложение Qt, так как это зависит от узлов require()
функция. Я попытался реализовать версию этого, начиная с «Поддержка require () CommonJS» и заканчивая с кодом ниже.
Можно ли заставить его работать, или я обречен на такой подход? Может быть, мне лучше скопировать биты should.js в один файл? Я бы предпочел не брать на себя ответственность за поддержание актуальности. (Лицензирование не является проблемой, так как я не собираюсь распространять этот код).
Вот мой MCVE; извините, я не мог получить его короче!
#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>
? Слоты управляют этим, так что я должен быть в состоянии, верно?)
Я рассмотрел это немного подробнее, и переписывание C ++ Qt требует системы — это немного больше времени, чем я думал изначально. Существует также проблема с библиотеками, имеющими require
модули ядра (которые в свою очередь require
нативные модули, которые приводят к неопределенному поведению — читайте: вероятно, не будет работать).
Реализовать на заказ node
require()
в C ++ Qt, как это было начато в вашем вопросе и ссылке. Подробности работы node.js
require()
может быть найден Вот. Вам нужно иметь ядро node
модули в вашем require()
путь поиска (вы можете получить их из node.js
исходный репозиторий).
Поскольку проблема, которую мы пытаемся решить в # 1, заключается в основном в загрузке и кэшировании файлов JavaScript, почему бы не использовать то, что уже существует для той же цели. Таким образом, мы можем избежать ручной работы и связки javascript
у нас есть четкие признаки того, что он будет работать в браузере (тогда более ограниченная среда node.js
).
$ npm install -g browserify
$ npm install expect
index.js
var expect = require('expect');
expect(1).toEqual(1);
И беги browserify
:
$ browserify index.js -o bundle.js
В вашем Qt C++
:
QString script = loadFile("/path/to/bundle.js");
engine.evaluate(script);
Мы пришли к решению проблемы require()
однако я не уверен во взаимодействии. с Qt
, Кроме того, я столкнулся с некоторыми Syntax Error
из QtScript для некоторых js
модули, так что это не серебряная пуля, даже на первый взгляд может показаться так.
Примечание: это тоже интересный проект: https://github.com/svalaskevicius/qtjs-generator.
Других решений пока нет …