From 1676b285ae726eb858d4d7ed089496133ce3de4b Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Thu, 28 Jun 2018 17:56:44 +0200 Subject: adapted python-console for easier use --- 3rdparty/python-console/modified/pyconsole.cc | 294 ++++++++++++++++++++++ 3rdparty/python-console/modified/pyconsole.h | 77 ++++++ 3rdparty/python-console/modified/pyinterpreter.cc | 155 ++++++++++++ 3rdparty/python-console/modified/pyinterpreter.h | 36 +++ 3rdparty/python-console/modified/pyredirector.cc | 112 +++++++++ 3rdparty/python-console/modified/pyredirector.h | 32 +++ 6 files changed, 706 insertions(+) create mode 100644 3rdparty/python-console/modified/pyconsole.cc create mode 100644 3rdparty/python-console/modified/pyconsole.h create mode 100644 3rdparty/python-console/modified/pyinterpreter.cc create mode 100644 3rdparty/python-console/modified/pyinterpreter.h create mode 100644 3rdparty/python-console/modified/pyredirector.cc create mode 100644 3rdparty/python-console/modified/pyredirector.h (limited to '3rdparty/python-console/modified') diff --git a/3rdparty/python-console/modified/pyconsole.cc b/3rdparty/python-console/modified/pyconsole.cc new file mode 100644 index 00000000..35f3e930 --- /dev/null +++ b/3rdparty/python-console/modified/pyconsole.cc @@ -0,0 +1,294 @@ +#include "pyconsole.h" +#include "pyinterpreter.h" +#include "ColumnFormatter.h" + +#include +#include +#include + +#include "Utils.h" + +const QString PythonConsole::PROMPT = ">>> "; +const QString PythonConsole::MULTILINE_PROMPT = "... "; +const QColor PythonConsole::NORMAL_COLOR = QColor::fromRgbF( 0, 0, 0 ); +const QColor PythonConsole::ERROR_COLOR = QColor::fromRgbF( 1.0, 0, 0 ); +const QColor PythonConsole::OUTPUT_COLOR = QColor::fromRgbF( 0, 0, 1.0 ); + +PythonConsole::PythonConsole( QWidget* parent ): + QTextEdit( parent ) +{ + QFont font("unexistent"); + font.setStyleHint(QFont::Monospace); + setFont(font); + m_parseHelper.subscribe( this ); +} + +void PythonConsole::keyPressEvent( QKeyEvent* e ) +{ + switch ( e->key() ) + { + case Qt::Key_Return: + handleReturnKeyPress( ); + return; + + case Qt::Key_Tab: + autocomplete( ); + return; + + case Qt::Key_Backspace: + if ( ! canBackspace( ) ) + return; + break; + + case Qt::Key_Up: + previousHistory( ); + return; + + case Qt::Key_Down: + nextHistory( ); + return; + + case Qt::Key_Left: + if ( ! canGoLeft( ) ) + return; + } + + QTextEdit::keyPressEvent( e ); +} + +void PythonConsole::handleReturnKeyPress( ) +{ + if ( ! cursorIsOnInputLine( ) ) + { + return; + } + + QString line = getLine( ); + + m_parseHelper.process( line.toStdString( ) ); + if ( m_parseHelper.buffered( ) ) + { + append(""); + displayPrompt( ); + } + if ( line.size( ) ) + { + m_historyBuffer.push_back( line.toStdString( ) ); + m_historyIt = m_historyBuffer.end(); + } + moveCursorToEnd( ); +} + +void PythonConsole::parseEvent( const ParseMessage& message ) +{ + // handle invalid user input + if ( message.errorCode ) + { + setTextColor( ERROR_COLOR ); + append(message.message.c_str()); + + setTextColor( NORMAL_COLOR ); + append(""); + displayPrompt( ); + return; + } + + // interpret valid user input + int errorCode; + std::string res; + if ( message.message.size() ) + res = pyinterpreter_execute( message.message, &errorCode ); + if ( errorCode ) + { + setTextColor( ERROR_COLOR ); + } + else + { + setTextColor( OUTPUT_COLOR ); + } + + if ( res.size( ) ) + { + append(res.c_str()); + } + + setTextColor( NORMAL_COLOR ); + + // set up the next line on the console + append(""); + displayPrompt( ); +} + +QString PythonConsole::getLine( ) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PythonConsole::PROMPT.size( ) ); + cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); + QString line = cursor.selectedText( ); + cursor.clearSelection( ); + return line; +} + +bool PythonConsole::cursorIsOnInputLine( ) +{ + int cursorBlock = textCursor( ).blockNumber( ); + QTextCursor bottomCursor = textCursor( ); + bottomCursor.movePosition( QTextCursor::End ); + int bottomBlock = bottomCursor.blockNumber( ); + return ( cursorBlock == bottomBlock ); +} + +bool PythonConsole::inputLineIsEmpty( ) +{ + QTextCursor bottomCursor = textCursor( ); + bottomCursor.movePosition( QTextCursor::End ); + int col = bottomCursor.columnNumber( ); + return ( col == PythonConsole::PROMPT.size( ) ); +} + +bool PythonConsole::canBackspace( ) +{ + if ( ! cursorIsOnInputLine( ) ) + { + return false; + } + + if ( inputLineIsEmpty( ) ) + { + return false; + } + + return true; +} + +bool PythonConsole::canGoLeft( ) +{ + if ( cursorIsOnInputLine( ) ) + { + QTextCursor bottomCursor = textCursor( ); + int col = bottomCursor.columnNumber( ); + return (col > PythonConsole::PROMPT.size( )); + } + return true; +} + +void PythonConsole::displayPrompt( ) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::End ); + if ( m_parseHelper.buffered( ) ) + { + cursor.insertText( PythonConsole::MULTILINE_PROMPT ); + } + else + { + cursor.insertText( PythonConsole::PROMPT ); + } + cursor.movePosition( QTextCursor::EndOfLine ); +} + +void PythonConsole::displayString(QString text) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::End ); + cursor.insertText( text ); + cursor.movePosition( QTextCursor::EndOfLine ); +} + +void PythonConsole::autocomplete( ) +{ + if ( ! cursorIsOnInputLine( ) ) + return; + + QString line = getLine( ); + const std::list& suggestions = + pyinterpreter_suggest( line.toStdString( ) ); + if (suggestions.size() == 1) + { + line = suggestions.back().c_str(); + } + else + { + // try to complete to longest common prefix + std::string prefix = + LongestCommonPrefix(suggestions.begin(), suggestions.end()); + if (prefix.size() > (size_t)line.size()) + { + line = prefix.c_str(); + } + else + { + ColumnFormatter fmt; + fmt.setItems(suggestions.begin(), suggestions.end()); + fmt.format(width() / 10); + setTextColor( OUTPUT_COLOR ); + const std::list& formatted = fmt.formattedOutput(); + for (std::list::const_iterator it = formatted.begin(); + it != formatted.end(); ++it) + { + append(it->c_str()); + } + setTextColor( NORMAL_COLOR ); + } + } + + // set up the next line on the console + append(""); + displayPrompt( ); + moveCursorToEnd( ); + QTextCursor cursor = textCursor(); + cursor.insertText( line ); + moveCursorToEnd( ); +} + +void PythonConsole::previousHistory( ) +{ + if ( ! cursorIsOnInputLine( ) ) + return; + + if ( ! m_historyBuffer.size( ) ) + return; + + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PythonConsole::PROMPT.size( ) ); + cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); + cursor.removeSelectedText( ); + if ( m_historyIt != m_historyBuffer.begin( ) ) + { + --m_historyIt; + } + cursor.insertText( m_historyIt->c_str() ); +} + +void PythonConsole::nextHistory( ) +{ + if ( ! cursorIsOnInputLine( ) ) + return; + + if ( ! m_historyBuffer.size( ) ) + return; + if ( m_historyIt == m_historyBuffer.end( ) ) + { + return; + } + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, PythonConsole::PROMPT.size( ) ); + cursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor ); + cursor.removeSelectedText( ); + ++m_historyIt; + if ( m_historyIt == m_historyBuffer.end( ) ) + { + return; + } + cursor.insertText( m_historyIt->c_str() ); +} + +void PythonConsole::moveCursorToEnd( ) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition( QTextCursor::End ); + setTextCursor( cursor ); +} diff --git a/3rdparty/python-console/modified/pyconsole.h b/3rdparty/python-console/modified/pyconsole.h new file mode 100644 index 00000000..eb2c98a4 --- /dev/null +++ b/3rdparty/python-console/modified/pyconsole.h @@ -0,0 +1,77 @@ +/** +python-console +Copyright (C) 2018 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef PYCONSOLE_H +#define PYCONSOLE_H +#include +#include +#include "ParseHelper.h" +#include "ParseListener.h" + +class QWidget; +class QKeyEvent; + +class PythonConsole : public QTextEdit, ParseListener +{ + Q_OBJECT + + public: + PythonConsole(QWidget *parent = 0); + + void displayPrompt(); + void displayString(QString text); + + protected: + // override QTextEdit + virtual void keyPressEvent(QKeyEvent *e); + + virtual void handleReturnKeyPress(); + + /** + Handle a compilable chunk of Python user input. + */ + virtual void parseEvent(const ParseMessage &message); + + QString getLine(); + bool cursorIsOnInputLine(); + bool inputLineIsEmpty(); + bool canBackspace(); + bool canGoLeft(); + void autocomplete(); + void previousHistory(); + void nextHistory(); + void moveCursorToEnd(); + + static const QString PROMPT; + static const QString MULTILINE_PROMPT; + + static const QColor NORMAL_COLOR; + static const QColor ERROR_COLOR; + static const QColor OUTPUT_COLOR; + + ParseHelper m_parseHelper; + std::list m_historyBuffer; + std::list::const_iterator m_historyIt; +}; + +#endif // PYCONSOLE_H diff --git a/3rdparty/python-console/modified/pyinterpreter.cc b/3rdparty/python-console/modified/pyinterpreter.cc new file mode 100644 index 00000000..ee8c4aa8 --- /dev/null +++ b/3rdparty/python-console/modified/pyinterpreter.cc @@ -0,0 +1,155 @@ +/** +python-console +Copyright (C) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "pyinterpreter.h" +#include +#include +#include +#include +#include "pyredirector.h" + +static PyThreadState *MainThreadState = NULL; + +static PyThreadState *m_threadState = NULL; +static PyObject *glb = NULL; +static PyObject *loc = NULL; + +static std::list m_suggestions; + +template std::string string_format(const std::string &format, Args... args) +{ + size_t size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} + +std::string pyinterpreter_execute(const std::string &command, int *errorCode) +{ + PyEval_AcquireThread(m_threadState); + *errorCode = 0; + + PyObject *py_result; + PyObject *dum; + std::string res; + py_result = Py_CompileString(command.c_str(), "", Py_single_input); + if (py_result == 0) { + if (PyErr_Occurred()) { + *errorCode = 1; + PyErr_Print(); + res = redirector_take_output(m_threadState); + } + + PyEval_ReleaseThread(m_threadState); + return res; + } + dum = PyEval_EvalCode(py_result, glb, loc); + Py_XDECREF(dum); + Py_XDECREF(py_result); + if (PyErr_Occurred()) { + *errorCode = 1; + PyErr_Print(); + } + + res = redirector_take_output(m_threadState); + + PyEval_ReleaseThread(m_threadState); + return res; +} + +const std::list &pyinterpreter_suggest(const std::string &hint) +{ + PyEval_AcquireThread(m_threadState); + m_suggestions.clear(); + int i = 0; + std::string command = string_format("sys.completer.complete('%s', %d)\n", hint.c_str(), i); + std::string res; + do { + PyObject *py_result; + PyObject *dum; + py_result = Py_CompileString(command.c_str(), "", Py_single_input); + dum = PyEval_EvalCode(py_result, glb, loc); + Py_XDECREF(dum); + Py_XDECREF(py_result); + res = redirector_take_output(m_threadState); + + ++i; + command = string_format("sys.completer.complete('%s', %d)\n", hint.c_str(), i); + if (res.size()) { + // throw away the newline + res = res.substr(1, res.size() - 3); + m_suggestions.push_back(res); + } + } while (res.size()); + + PyEval_ReleaseThread(m_threadState); + return m_suggestions; +} + +void pyinterpreter_preinit() +{ + m_suggestions.clear(); + inittab_redirector(); +} + +void pyinterpreter_initialize() +{ + PyEval_InitThreads(); + MainThreadState = PyEval_SaveThread(); + PyEval_AcquireThread(MainThreadState); + m_threadState = Py_NewInterpreter(); + + PyObject *module = PyImport_ImportModule("__main__"); + loc = glb = PyModule_GetDict(module); + + PyRun_SimpleString("import sys\n" + "import redirector\n" + "sys.path.insert(0, \".\")\n" // add current path + "sys.stdout = redirector.redirector()\n" + "sys.stderr = sys.stdout\n" + "import rlcompleter\n" + "sys.completer = rlcompleter.Completer()\n"); + + PyEval_ReleaseThread(m_threadState); +} + +void pyinterpreter_finalize() +{ + m_suggestions.clear(); + + PyEval_AcquireThread(m_threadState); + Py_EndInterpreter(m_threadState); + PyEval_ReleaseLock(); + + PyEval_RestoreThread(MainThreadState); +} + +void pyinterpreter_aquire() +{ + PyEval_AcquireThread(m_threadState); +} + +void pyinterpreter_release() +{ + PyEval_ReleaseThread(m_threadState); +} diff --git a/3rdparty/python-console/modified/pyinterpreter.h b/3rdparty/python-console/modified/pyinterpreter.h new file mode 100644 index 00000000..1a85c1fb --- /dev/null +++ b/3rdparty/python-console/modified/pyinterpreter.h @@ -0,0 +1,36 @@ +/** +python-console +Copyright (C) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef PYINTERPRETER_H +#define PYINTERPRETER_H +#include +#include + +std::string pyinterpreter_execute(const std::string &command, int *errorCode); +const std::list &pyinterpreter_suggest(const std::string &hint); +void pyinterpreter_preinit(); +void pyinterpreter_initialize(); +void pyinterpreter_finalize(); +void pyinterpreter_aquire(); +void pyinterpreter_release(); +#endif // PYINTERPRETER_H diff --git a/3rdparty/python-console/modified/pyredirector.cc b/3rdparty/python-console/modified/pyredirector.cc new file mode 100644 index 00000000..5f6c43a1 --- /dev/null +++ b/3rdparty/python-console/modified/pyredirector.cc @@ -0,0 +1,112 @@ +/** +python-console +Copyright (C) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "pyredirector.h" +#include + +static std::map thread_strings; + +static std::string &redirector_string(PyThreadState *threadState) +{ + if (!thread_strings.count(threadState)) { + thread_strings[threadState] = ""; + } + return thread_strings[threadState]; +} + +std::string redirector_take_output(PyThreadState *threadState) +{ + std::string res = redirector_string(threadState); + redirector_string(threadState) = ""; + return res; +} + +static PyObject *redirector_init(PyObject *, PyObject *) +{ + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *redirector_write(PyObject *, PyObject *args) +{ + char *output; + PyObject *selfi; + + if (!PyArg_ParseTuple(args, "Os", &selfi, &output)) { + return NULL; + } + + std::string outputString(output); + PyThreadState *currentThread = PyThreadState_Get(); + std::string &resultString = redirector_string(currentThread); + resultString = resultString + outputString; + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef redirector_methods[] = { + {"__init__", redirector_init, METH_VARARGS, "initialize the stdout/err redirector"}, + {"write", redirector_write, METH_VARARGS, "implement the write method to redirect stdout/err"}, + {NULL, NULL, 0, NULL}, +}; + +static PyObject *createClassObject(const char *name, PyMethodDef methods[]) +{ + PyObject *pClassName = PyUnicode_FromString(name); + PyObject *pClassBases = PyTuple_New(0); // An empty tuple for bases is equivalent to `(object,)` + PyObject *pClassDic = PyDict_New(); + + PyMethodDef *def; + // add methods to class + for (def = methods; def->ml_name != NULL; def++) { + PyObject *func = PyCFunction_New(def, NULL); + PyObject *method = PyInstanceMethod_New(func); + PyDict_SetItemString(pClassDic, def->ml_name, method); + Py_DECREF(func); + Py_DECREF(method); + } + + // pClass = type(pClassName, pClassBases, pClassDic) + PyObject *pClass = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, pClassName, pClassBases, pClassDic, NULL); + + Py_DECREF(pClassName); + Py_DECREF(pClassBases); + Py_DECREF(pClassDic); + + return pClass; +} + +static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "redirector", 0, -1, 0}; + +PyMODINIT_FUNC PyInit_redirector(void) +{ + PyObject *m = PyModule_Create(&moduledef); + if (m) { + PyObject *fooClass = createClassObject("redirector", redirector_methods); + PyModule_AddObject(m, "redirector", fooClass); + Py_DECREF(fooClass); + } + return m; +} + +void inittab_redirector() { PyImport_AppendInittab("redirector", PyInit_redirector); } \ No newline at end of file diff --git a/3rdparty/python-console/modified/pyredirector.h b/3rdparty/python-console/modified/pyredirector.h new file mode 100644 index 00000000..21e20a62 --- /dev/null +++ b/3rdparty/python-console/modified/pyredirector.h @@ -0,0 +1,32 @@ +/** +python-console +Copyright (C) 2014 Alex Tsui + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef PYREDIRECTOR_H +#define PYREDIRECTOR_H +#include +#include + +std::string redirector_take_output(PyThreadState *threadState); +void inittab_redirector(); + +#endif // PYREDIRECTOR_H -- cgit v1.2.3