PageRenderTime 84ms CodeModel.GetById 70ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/src/scripting/python/core.c

https://github.com/nabetaro/elinks
C | 337 lines | 238 code | 68 blank | 31 comment | 55 complexity | 2d7a4d3acc2b5e3e781c447afc775e79 MD5 | raw file
  1/* Python scripting engine */
  2
  3#ifdef HAVE_CONFIG_H
  4#include "config.h"
  5#endif
  6
  7#include <Python.h>
  8#include <osdefs.h>
  9
 10#include <stdlib.h>
 11
 12#include "elinks.h"
 13
 14#include "config/home.h"
 15#include "main/module.h"
 16#include "scripting/python/core.h"
 17#include "scripting/python/dialogs.h"
 18#include "scripting/python/document.h"
 19#include "scripting/python/keybinding.h"
 20#include "scripting/python/load.h"
 21#include "scripting/python/menu.h"
 22#include "scripting/python/open.h"
 23#include "scripting/python/python.h"
 24#include "scripting/scripting.h"
 25#include "session/session.h"
 26#include "util/env.h"
 27#include "util/string.h"
 28
 29struct session *python_ses = NULL;
 30
 31PyObject *python_elinks_err = NULL;
 32PyObject *python_hooks = NULL;
 33
 34/* Error reporting. */
 35
 36void
 37alert_python_error(void)
 38{
 39	unsigned char *msg = "(no traceback available)";
 40	PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL;
 41	PyObject *tb_module = NULL;
 42	PyObject *msg_list = NULL;
 43	PyObject *empty_string = NULL;
 44	PyObject *msg_string = NULL;
 45	unsigned char *temp;
 46
 47	/*
 48	 * Retrieve the current error indicator and use the format_exception()
 49	 * function in Python's traceback module to produce an informative
 50	 * error message. It returns a list of Python string objects.
 51	 */
 52	PyErr_Fetch(&err_type, &err_value, &err_traceback);
 53	PyErr_NormalizeException(&err_type, &err_value, &err_traceback);
 54	if (!err_type) goto end;
 55
 56	tb_module = PyImport_ImportModule("traceback");
 57	if (!tb_module) goto end;
 58
 59	msg_list = PyObject_CallMethod(tb_module, "format_exception", "OOO",
 60				       err_type,
 61				       err_value ? err_value : Py_None,
 62				       err_traceback ? err_traceback : Py_None);
 63	if (!msg_list) goto end;
 64
 65	/*
 66	 * Use the join() method of an empty Python string to join the list
 67	 * of strings into one Python string containing the entire error
 68	 * message. Then get the contents of the Python string.
 69	 */
 70	empty_string = PyString_FromString("");
 71	if (!empty_string) goto end;
 72
 73	msg_string = PyObject_CallMethod(empty_string, "join", "O", msg_list);
 74	if (!msg_string) goto end;
 75
 76	temp = (unsigned char *) PyString_AsString(msg_string);
 77	if (temp) msg = temp;
 78
 79end:
 80	report_scripting_error(&python_scripting_module, python_ses, msg);
 81
 82	Py_XDECREF(err_type);
 83	Py_XDECREF(err_value);
 84	Py_XDECREF(err_traceback);
 85	Py_XDECREF(tb_module);
 86	Py_XDECREF(msg_list);
 87	Py_XDECREF(empty_string);
 88	Py_XDECREF(msg_string);
 89
 90	/* In case another error occurred while reporting the original error: */
 91	PyErr_Clear();
 92}
 93
 94/* Prepend ELinks directories to Python's search path. */
 95
 96static int
 97set_python_search_path(void)
 98{
 99	struct string new_python_path;
100	unsigned char *old_python_path;
101	int result = -1;
102
103	if (!init_string(&new_python_path)) return result;
104
105	if (elinks_home && !add_format_to_string(&new_python_path, "%s%c",
106						 elinks_home, DELIM))
107		goto end;
108
109	if (!add_to_string(&new_python_path, CONFDIR))
110		goto end;
111
112	old_python_path = (unsigned char *) getenv("PYTHONPATH");
113	if (old_python_path && !add_format_to_string(&new_python_path, "%c%s",
114						     DELIM, old_python_path))
115		goto end;
116
117	result = env_set("PYTHONPATH", new_python_path.source, -1);
118
119end:
120	done_string(&new_python_path);
121	return result;
122}
123
124/* Determine whether or not a user-supplied hooks module exists. */
125
126static int
127hooks_module_exists(void)
128{
129	PyObject *imp_module = NULL, *result = NULL;
130	int found_hooks = 0;
131
132	/*
133	 * Use the find_module() function in Python's imp module to look for
134	 * the hooks module in Python's search path. An ImportError exception
135	 * indicates that no such module was found; any other exception will
136	 * be reported as an error.
137	 */
138	imp_module = PyImport_ImportModule("imp");
139	if (!imp_module) goto python_error;
140
141	result = PyObject_CallMethod(imp_module, "find_module", "s", "hooks");
142	if (result) {
143		found_hooks = 1;
144		goto end;
145	} else if (PyErr_ExceptionMatches(PyExc_ImportError)) {
146		PyErr_Clear();
147		goto end;
148	}
149
150python_error:
151	alert_python_error();
152
153end:
154	Py_XDECREF(imp_module);
155	Py_XDECREF(result);
156	return found_hooks;
157}
158
159/*
160 * Turn any warnings that aren't filtered into exceptions so they can be caught
161 * and handled; otherwise they would be printed to stderr.
162 */
163
164static char python_showwarnings_doc[] =
165PYTHON_DOCSTRING("showwarnings(message, category, filename, lineno, \
166file=None, line=None)\n\
167\n\
168Report a Python warning as an ELinks scripting error.\n\
169\n\
170Arguments:\n\
171\n\
172message -- An instance of the class Warning (or a subclass).\n\
173\n\
174All other arguments are ignored.\n");
175
176static PyCFunction *
177python_showwarning(PyObject *self, PyObject *args, PyObject *kwargs)
178{
179	PyObject *warning;
180
181	if (!PyTuple_Check(args)) {
182		PyErr_Format(PyExc_TypeError, "expected a tuple, got '%s'",
183			     args->ob_type->tp_name);
184		return NULL;
185	}
186	warning = PyTuple_GetItem(args, 0);
187	if (warning) PyErr_SetObject((PyObject *) warning->ob_type, warning);
188	return NULL;
189}
190
191static PyMethodDef warning_methods[] = {
192	{"showwarning",		(PyCFunction) python_showwarning,
193				METH_VARARGS | METH_KEYWORDS,
194				python_showwarnings_doc},
195
196	{NULL,			NULL, 0, NULL}
197};
198
199/* Replace the standard warnings.showwarning() function. */
200
201static int
202replace_showwarning(void)
203{
204	PyObject *warnings_module = NULL, *module_name = NULL, *module_dict;
205	int result = -1;
206
207	warnings_module = PyImport_ImportModule("warnings");
208	if (!warnings_module) goto end;
209	module_name = PyString_FromString("warnings");
210	if (!module_name) goto end;
211	module_dict = PyModule_GetDict(warnings_module);
212	if (!module_dict) goto end;
213
214	if (add_python_methods(module_dict, module_name, warning_methods) != 0)
215		goto end;
216
217	result = 0;
218
219end:
220	Py_XDECREF(warnings_module);
221	Py_XDECREF(module_name);
222	return result;
223}
224
225/* Module-level documentation for the Python interpreter's elinks module. */
226
227static char module_doc[] =
228PYTHON_DOCSTRING("Interface to the ELinks web browser.\n\
229\n\
230Functions:\n\
231\n\
232bind_key() -- Bind a keystroke to a callable object.\n\
233current_document() -- Return the body of the document being viewed.\n\
234current_header() -- Return the header of the document being viewed.\n\
235current_link_url() -- Return the URL of the currently selected link.\n\
236current_title() -- Return the title of the document being viewed.\n\
237current_url() -- Return the URL of the document being viewed.\n\
238info_box() -- Display information to the user.\n\
239input_box() -- Prompt for user input.\n\
240load() -- Load a document into the ELinks cache.\n\
241menu() -- Display a menu.\n\
242open() -- View a document.\n\
243\n\
244Exception classes:\n\
245\n\
246error -- Errors internal to ELinks.\n\
247\n\
248Other public objects:\n\
249\n\
250home -- A string containing the pathname of the ~/.elinks directory, or\n\
251        None if ELinks has no configuration directory.\n");
252
253void
254init_python(struct module *module)
255{
256	PyObject *elinks_module, *module_dict, *module_name;
257
258	if (set_python_search_path() != 0) return;
259
260	Py_Initialize();
261
262	if (!hooks_module_exists()) return;
263
264	if (replace_showwarning() != 0) goto python_error;
265
266	elinks_module = Py_InitModule3("elinks", NULL, module_doc);
267	if (!elinks_module) goto python_error;
268
269	/* If @elinks_home is NULL, Py_BuildValue() returns a None reference. */
270	if (PyModule_AddObject(elinks_module, "home",
271			       Py_BuildValue("s", elinks_home)) != 0)
272		goto python_error;
273
274	python_elinks_err = PyErr_NewException("elinks.error", NULL, NULL);
275	if (!python_elinks_err) goto python_error;
276
277	if (PyModule_AddObject(elinks_module, "error", python_elinks_err) != 0)
278		goto python_error;
279
280	module_dict = PyModule_GetDict(elinks_module);
281	if (!module_dict) goto python_error;
282	module_name = PyString_FromString("elinks");
283	if (!module_name) goto python_error;
284
285	if (python_init_dialogs_interface(module_dict, module_name) != 0
286	    || python_init_document_interface(module_dict, module_name) != 0
287	    || python_init_keybinding_interface(module_dict, module_name) != 0
288	    || python_init_load_interface(module_dict, module_name) != 0
289	    || python_init_menu_interface(module_dict, module_name) != 0
290	    || python_init_open_interface(module_dict, module_name) != 0)
291		goto python_error;
292
293	python_hooks = PyImport_ImportModule("hooks");
294	if (!python_hooks) goto python_error;
295
296	return;
297
298python_error:
299	alert_python_error();
300}
301
302void
303cleanup_python(struct module *module)
304{
305	if (Py_IsInitialized()) {
306		PyObject *temp;
307
308		python_done_keybinding_interface();
309
310		/* This is equivalent to Py_CLEAR(), but it works with older
311		 * versions of Python predating that macro: */
312		temp = python_hooks;
313		python_hooks = NULL;
314		Py_XDECREF(temp);
315
316		Py_Finalize();
317	}
318}
319
320/* Add methods to a Python extension module. */
321
322int
323add_python_methods(PyObject *dict, PyObject *name, PyMethodDef *methods)
324{
325	PyMethodDef *method;
326
327	for (method = methods; method && method->ml_name; method++) {
328		PyObject *function = PyCFunction_NewEx(method, NULL, name);
329		int result;
330
331		if (!function) return -1;
332		result = PyDict_SetItemString(dict, method->ml_name, function);
333		Py_DECREF(function);
334		if (result != 0) return -1;
335	}
336	return 0;
337}