From 8d92f92025693888b31a0f4f547e1b27246680e9 Mon Sep 17 00:00:00 2001 From: Paul I Date: Sun, 30 Sep 2018 21:30:25 +0300 Subject: [PATCH] Wrap Python API --- src/Cutter.pro | 6 +- src/common/JupyterConnection.cpp | 33 ++--------- src/common/NestedIPyKernel.cpp | 29 +--------- src/common/PythonAPI.cpp | 29 +--------- src/common/QtResImporter.cpp | 99 ++++++++++++++++++++++++++++++++ src/common/QtResImporter.h | 10 ++++ src/python/cutter.py | 6 ++ src/python/reg_qtres_importer.py | 31 ++++++++++ src/resources.qrc | 2 + 9 files changed, 160 insertions(+), 85 deletions(-) create mode 100644 src/common/QtResImporter.cpp create mode 100644 src/common/QtResImporter.h create mode 100644 src/python/cutter.py create mode 100644 src/python/reg_qtres_importer.py diff --git a/src/Cutter.pro b/src/Cutter.pro index afd228a4..fd817c28 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -218,7 +218,8 @@ SOURCES += \ dialogs/EditFunctionDialog.cpp \ widgets/CutterTreeView.cpp \ widgets/ComboQuickFilterView.cpp \ - dialogs/HexdumpRangeDialog.cpp + dialogs/HexdumpRangeDialog.cpp \ + common/QtResImporter.cpp HEADERS += \ Cutter.h \ @@ -320,7 +321,8 @@ HEADERS += \ dialogs/EditFunctionDialog.h \ widgets/CutterTreeView.h \ widgets/ComboQuickFilterView.h \ - dialogs/HexdumpRangeDialog.h + dialogs/HexdumpRangeDialog.h \ + common/QtResImporter.h FORMS += \ dialogs/AboutDialog.ui \ diff --git a/src/common/JupyterConnection.cpp b/src/common/JupyterConnection.cpp index 693c8247..dd5dd197 100644 --- a/src/common/JupyterConnection.cpp +++ b/src/common/JupyterConnection.cpp @@ -1,19 +1,18 @@ #ifdef CUTTER_ENABLE_JUPYTER #include -#include #include #include #include #include #include -#include #include #include #include "JupyterConnection.h" #include "NestedIPyKernel.h" +#include "QtResImporter.h" #include "PythonAPI.h" Q_GLOBAL_STATIC(JupyterConnection, uniqueInstance) @@ -79,8 +78,9 @@ void JupyterConnection::initPythonHome() void JupyterConnection::initPython() { - PyImport_AppendInittab("cutter", &PyInit_api); + PyImport_AppendInittab("_cutter", &PyInit_api); PyImport_AppendInittab("cutter_internal", &PyInit_api_internal); + PyImport_AppendInittab("_qtres", &PyInit_qtres); Py_Initialize(); PyEval_InitThreads(); @@ -93,31 +93,7 @@ void JupyterConnection::createCutterJupyterModule() PyEval_RestoreThread(pyThreadState); } - QFile moduleFile(":/python/cutter_jupyter.pyc"); - bool isBytecode = moduleFile.exists(); - if (!isBytecode) { - moduleFile.setFileName(":/python/cutter_jupyter.py"); - } - moduleFile.open(QIODevice::ReadOnly); - QByteArray moduleCode = moduleFile.readAll(); - moduleFile.close(); - - PyObject *moduleCodeObject; - if (isBytecode) { - moduleCodeObject = PyMarshal_ReadObjectFromString(moduleCode.constData() + 12, - moduleCode.size() - 12); - } else { - moduleCodeObject = Py_CompileString(moduleCode.constData(), "cutter_jupyter.py", - Py_file_input); - } - if (!moduleCodeObject) { - PyErr_Print(); - qWarning() << "Could not compile cutter_jupyter."; - emit creationFailed(); - pyThreadState = PyEval_SaveThread(); - return; - } - cutterJupyterModule = PyImport_ExecCodeModule("cutter_jupyter", moduleCodeObject); + cutterJupyterModule = QtResImport("cutter_jupyter"); if (!cutterJupyterModule) { PyErr_Print(); qWarning() << "Could not import cutter_jupyter."; @@ -125,7 +101,6 @@ void JupyterConnection::createCutterJupyterModule() pyThreadState = PyEval_SaveThread(); return; } - Py_DECREF(moduleCodeObject); pyThreadState = PyEval_SaveThread(); } diff --git a/src/common/NestedIPyKernel.cpp b/src/common/NestedIPyKernel.cpp index f72dbffb..8737bfd9 100644 --- a/src/common/NestedIPyKernel.cpp +++ b/src/common/NestedIPyKernel.cpp @@ -2,13 +2,11 @@ #ifdef CUTTER_ENABLE_JUPYTER #include -#include - -#include #include #include "Cutter.h" #include "NestedIPyKernel.h" +#include "QtResImporter.h" NestedIPyKernel *NestedIPyKernel::start(const QStringList &argv) { @@ -20,29 +18,8 @@ NestedIPyKernel *NestedIPyKernel::start(const QStringList &argv) return nullptr; } - QFile moduleFile(":/python/cutter_ipykernel.pyc"); - bool isBytecode = moduleFile.exists(); - if (!isBytecode) { - moduleFile.setFileName(":/python/cutter_ipykernel.py"); - } - moduleFile.open(QIODevice::ReadOnly); - QByteArray moduleCode = moduleFile.readAll(); - moduleFile.close(); - - PyObject *moduleCodeObject; - if (isBytecode) { - moduleCodeObject = PyMarshal_ReadObjectFromString(moduleCode.constData() + 12, - moduleCode.size() - 12); - } else { - moduleCodeObject = Py_CompileString(moduleCode.constData(), "cutter_ipykernel.py", - Py_file_input); - } - if (!moduleCodeObject) { - qWarning() << "Could not compile cutter_ipykernel."; - return nullptr; - } - auto cutterIPykernelModule = PyImport_ExecCodeModule("cutter_ipykernel", moduleCodeObject); - Py_DECREF(moduleCodeObject); + RegQtResImporter(); + auto cutterIPykernelModule = QtResImport("cutter_ipykernel"); if (!cutterIPykernelModule) { qWarning() << "Could not import cutter_ipykernel."; return nullptr; diff --git a/src/common/PythonAPI.cpp b/src/common/PythonAPI.cpp index 3c337bf9..79e8c808 100644 --- a/src/common/PythonAPI.cpp +++ b/src/common/PythonAPI.cpp @@ -32,28 +32,6 @@ PyObject *api_cmd(PyObject *self, PyObject *args) return PyUnicode_FromString(result); } -PyObject *api_cmdj(PyObject *self, PyObject *args) -{ - Q_UNUSED(self); - char *command; - char *result = (char *) ""; - QString cmdRes; - QByteArray cmdBytes; - if (PyArg_ParseTuple(args, "s:command", &command)) { - cmdRes = Core()->cmd(command); - cmdBytes = cmdRes.toLocal8Bit(); - result = cmdBytes.data(); - PyObject *jsonModule = PyImport_ImportModule("json"); - PyObject *loadsFunc = PyObject_GetAttrString(jsonModule, "loads"); - if (!PyCallable_Check(loadsFunc)) { - PyErr_SetString(PyExc_SystemError, "Could not parse JSON."); - return NULL; - } - return PyEval_CallFunction(loadsFunc, "(s)", result); - } - return Py_None; -} - PyObject *api_refresh(PyObject *self, PyObject *args) { Q_UNUSED(self); @@ -87,10 +65,6 @@ PyMethodDef CutterMethods[] = { "cmd", api_cmd, METH_VARARGS, "Execute a command inside Cutter" }, - { - "cmdj", api_cmdj, METH_VARARGS, - "Execute a JSON command and return the result as a dictionnary" - }, { "refresh", api_refresh, METH_NOARGS, "Refresh Cutter widgets" @@ -103,7 +77,7 @@ PyMethodDef CutterMethods[] = { }; PyModuleDef CutterModule = { - PyModuleDef_HEAD_INIT, "cutter", NULL, -1, CutterMethods, + PyModuleDef_HEAD_INIT, "_cutter", NULL, -1, CutterMethods, NULL, NULL, NULL, NULL }; @@ -112,7 +86,6 @@ PyObject *PyInit_api() return PyModule_Create(&CutterModule); } - // ----------------------------- diff --git a/src/common/QtResImporter.cpp b/src/common/QtResImporter.cpp new file mode 100644 index 00000000..69d5da04 --- /dev/null +++ b/src/common/QtResImporter.cpp @@ -0,0 +1,99 @@ +#include +#include + +#include "QtResImporter.h" + +#include +#include + +int QtResExists(const char *name, QFile &file) +{ + QString fname = QString::asprintf(":/python/%s.py", name); + file.setFileName(fname); + if (file.exists()) + return 1; + fname.append('c'); + file.setFileName(fname); + if (file.exists()) + return 2; + return 0; +} + +PyObject *QtResGetCode(const char *name) +{ + QFile moduleFile; + bool isBytecode = false; + + switch (QtResExists(name, moduleFile)) { + case 0: + return nullptr; + case 2: + isBytecode = true; + } + + moduleFile.open(QIODevice::ReadOnly); + QByteArray data = moduleFile.readAll(); + moduleFile.close(); + + PyObject *codeObject; + if (isBytecode) { + codeObject = PyMarshal_ReadObjectFromString(data.constData() + 12, + data.size() - 12); + } else { + codeObject = Py_CompileString(data.constData(), + moduleFile.fileName().toLocal8Bit().constData(), + Py_file_input); + } + if (!codeObject) { + qWarning() << "Couldn't unmarshal/compile " << moduleFile.fileName(); + } + return codeObject; +} + +PyObject *QtResImport(const char *name) +{ + PyObject *codeObject = QtResGetCode(name); + if (!codeObject) + return nullptr; + PyObject *module = PyImport_ExecCodeModule(name, codeObject); + Py_DECREF(codeObject); + return module; +} + +PyObject *qtres_exists(PyObject *self, PyObject *args) +{ + Q_UNUSED(self) + char *name; + QFile resFile; + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + return PyBool_FromLong(QtResExists(name, resFile)); +} + +PyObject *qtres_get_code(PyObject *self, PyObject *args) +{ + Q_UNUSED(self) + char *name; + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + PyObject *ret = QtResGetCode(name); + if (ret) + return ret; + Py_RETURN_NONE; +} + +PyMethodDef QtResMethods[] = { + { "exists", qtres_exists, METH_VARARGS, NULL }, + { "get_code", qtres_get_code, METH_VARARGS, NULL }, + {NULL, NULL, 0, NULL} +}; + +PyModuleDef QtResModule = { + PyModuleDef_HEAD_INIT, "_qtres", NULL, -1, QtResMethods, + NULL, NULL, NULL, NULL +}; + +PyObject *PyInit_qtres() +{ + return PyModule_Create(&QtResModule); +} diff --git a/src/common/QtResImporter.h b/src/common/QtResImporter.h new file mode 100644 index 00000000..f0c81568 --- /dev/null +++ b/src/common/QtResImporter.h @@ -0,0 +1,10 @@ +#ifndef QTRESIMPORTER_H +#define QTRESIMPORTER_H + +PyObject *PyInit_qtres(); + +PyObject *QtResImport(const char *name); + +#define RegQtResImporter() Py_DecRef(QtResImport("reg_qtres_importer")) + +#endif // QTRESIMPORTER_H diff --git a/src/python/cutter.py b/src/python/cutter.py new file mode 100644 index 00000000..7884d705 --- /dev/null +++ b/src/python/cutter.py @@ -0,0 +1,6 @@ +import json +from _cutter import * + +def cmdj(command): + '''Execute a JSON command and return the result as a dictionnary''' + return json.loads(cmd(command)) diff --git a/src/python/reg_qtres_importer.py b/src/python/reg_qtres_importer.py new file mode 100644 index 00000000..45f17c37 --- /dev/null +++ b/src/python/reg_qtres_importer.py @@ -0,0 +1,31 @@ +import sys +import importlib +import _qtres + +class QtResLoader: + @classmethod + def get_code(cls, name): + return _qtres.get_code(name) + + @classmethod + def create_module(cls, spec): + return None + + @classmethod + def exec_module(cls, module): + code = cls.get_code(module.__name__) + if code is None: + raise ImportError("get_code() failed") + return + exec(code, module.__dict__) + +class QtResFinder: + @classmethod + def find_spec(cls, fullname, path=None, target=None): + if path or target: + return None + if not _qtres.exists(fullname): + return None + return importlib._bootstrap.ModuleSpec(fullname, QtResLoader) + +sys.meta_path.append(QtResFinder) diff --git a/src/resources.qrc b/src/resources.qrc index 424788d4..7962f22c 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -82,5 +82,7 @@ img/icons/copy.svg img/icons/delete.svg img/icons/previous.svg + python/cutter.py + python/reg_qtres_importer.py