A klammer set's Python speaks through the log
Assembled from development commit d5f9c20b94b3. Since 5c29da5: a print() in
@eval, or in any Python a klammer set runs, is a log line -- on the
error stream under the command's name at -v 1, silent otherwise, never
in the document -- and K_log(level, text) is the builtin behind it; a
Python script using the binding keeps its own stdout. Previously such
output was lost, or on a terminal landed in the document.
This commit is contained in:
1 parent
5c29da52dc
commit
fc2e848e91
7 files changed
+126
-3
No files matched your search
@@ -6,6 +6,65 @@
|
||||
std::regex Eval_python::statement_delimiter("\\s*;\\s*");
|
||||
PyObject* Eval_python::s_last_exception = nullptr;
|
||||
|
||||
static bool s_python_output_to_log = false;
|
||||
|
||||
void set_python_output_to_log(bool on)
|
||||
{
|
||||
s_python_output_to_log = on;
|
||||
}
|
||||
|
||||
// K_log(level, text): the engine's K::log, for Python. A builtin, so a
|
||||
// klammer set's modules reach it without an import; absent outside a
|
||||
// Machine (kutil.msg falls back to stderr then).
|
||||
static PyObject* py_K_log(PyObject*, PyObject* args)
|
||||
{
|
||||
int level = 1;
|
||||
const char* text = nullptr;
|
||||
if (!PyArg_ParseTuple(args, "is", &level, &text)) {
|
||||
return nullptr;
|
||||
}
|
||||
(void)K::log(level, std::string(text));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef K_log_method = {
|
||||
"K_log", py_K_log, METH_VARARGS,
|
||||
"K_log(level, text): write text to the Klammertext log, shown at -v level or higher."};
|
||||
|
||||
static void install_python_log(PyObject* globals)
|
||||
{
|
||||
PyObject* builtins = PyEval_GetBuiltins(); // borrowed
|
||||
if (!PyDict_GetItemString(builtins, "K_log")) {
|
||||
PyObject* f = PyCFunction_New(&K_log_method, nullptr);
|
||||
if (f) {
|
||||
PyDict_SetItemString(builtins, "K_log", f);
|
||||
Py_DECREF(f);
|
||||
}
|
||||
}
|
||||
if (!s_python_output_to_log) {
|
||||
return;
|
||||
}
|
||||
// sys.stdout -> the log, line by line, once per process (the class
|
||||
// attribute marks ours; a second Eval_python leaves it in place).
|
||||
PyRun_String(
|
||||
"import sys\n"
|
||||
"if not getattr(sys.stdout, '_klammertext_log', False):\n"
|
||||
" class _K_log_stream:\n"
|
||||
" _klammertext_log = True\n"
|
||||
" def __init__(self): self._buf = ''\n"
|
||||
" def write(self, s):\n"
|
||||
" self._buf += s\n"
|
||||
" while '\\n' in self._buf:\n"
|
||||
" line, self._buf = self._buf.split('\\n', 1)\n"
|
||||
" K_log(1, line)\n"
|
||||
" return len(s)\n"
|
||||
" def flush(self):\n"
|
||||
" if self._buf: K_log(1, self._buf); self._buf = ''\n"
|
||||
" def isatty(self): return False\n"
|
||||
" sys.stdout = _K_log_stream()\n",
|
||||
Py_file_input, globals, globals);
|
||||
}
|
||||
|
||||
Eval_python::Eval_python(Machine& machine, const Locator& loc)
|
||||
: m_machine(machine)
|
||||
, m_loc(loc)
|
||||
@@ -30,6 +89,7 @@ Eval_python::Eval_python(Machine& machine, const Locator& loc)
|
||||
m_globals = PyDict_New();
|
||||
m_locals = PyDict_New();
|
||||
PyDict_SetItemString(m_globals, "__builtins__", PyEval_GetBuiltins());
|
||||
install_python_log(m_globals);
|
||||
// The machine's result text, so :after_apply phase functions can take it
|
||||
// as an argument (the Python counterpart of a :cpp phase function reading
|
||||
// machine.m_result). Set directly rather than through the state's
|
||||
|
||||
Reference in new issue
Block a user