Included below is a minor reworking of the Python scripting backend to
address four issues:
1. Errors (including informative tracebacks) are now presented by calling
the report_scripting_error() function in src/scripting/scripting.c.
Also, a trivial change to report_scripting_error(): When it needs to
resort to calling usrerror() it should sleep for a moment, just as
alert_lua_error() does, so the user will have enough time to notice the
error before the screen is cleared.
2. Several functions in src/scripting/python/hooks.c were leaking references
because Py_None's reference count needs to be decremented when we're
finished with it (just like most other Python objects).
3. The pDict pointer could potentially be compared to NULL even if it was
uninitialized. And just to err on the side of paranoia, pDict's reference
count should be incremented to make sure the Python interpreter won't
garbage-collect the object while it's still in use.
4. ./configure --with-python didn't work because the Python tests were
trying an incorrect library name and didn't produce all the necessary
compiler and linker flags. (Also, they were setting CFLAGS where CPPFLAGS
is probably more appropriate.)
Note that newer versions of python will come with a convenient
python-config script; relying on that would have made the tests
simpler, but the more verbose tests that I've provided have one
advantage: They're likely to work with older Python versions as well.
While I was in the neighborhood, I also rearranged the Python tests in
configure.in to look somewhat like the SEE and Guile tests for ease of
future maintenance. It's still pretty ugly, but at least now it's a bit
more consistent. :-)
With these changes, ./configure --with-python now works for me, as do the
Python hooks I've tried (but testing the configure script on more diverse
platforms would be desirable).
I hope this is helpful. Thanks so much for all your terrific work on Elinks!
diff --git a/configure.in b/configure.in
index ad2831d..fbfb5da 100644
--- a/configure.in
+++ b/configure.in
@@ -770,51 +770,32 @@ dnl Check for Python
dnl ===================================================================
enable_python="no";
-AC_ARG_WITH(python, [ --with-python=[prefix] enable Python support],
- [
-if test "$withval" != no; then
- # FIXME: If withval is a valid directory append it to PATH
- # so that you can specify one of several Python installations.
- if test "$withval" != yes; then
- python_prefix="$withval"
- else
- python_prefix=""
- fi
- enable_python=yes
- cat <<EOF
-***********************************************************************
-The Python support is incomplete and not so well integrated to ELinks
-yet. That means, e.g.., that you have no Python console and there might
-not be all the necessary hooks. Also, the Python interface is not too
-well tested (success stories heartily welcomed!).
-***********************************************************************
-EOF
-fi
- ])
-
+AC_ARG_WITH(python, [ --with-python enable Python support],
+ [ if test "x$withval" != xno; then enable_python=yes; fi ])
+EL_SAVE_FLAGS
cf_result=no
-EL_SAVE_FLAGS
+AC_MSG_CHECKING([for Python])
if test "$enable_python" = "yes"; then
- if test -n "$python_prefix" && test -d "$python_prefix/bin"; then
- PYTHON_PATH="$python_prefix/bin:$PATH"
+ AC_MSG_RESULT(yes);
+
+ if test -d "$withval"; then
+ PYTHON_PATH="$withval:$PATH"
else
PYTHON_PATH="$PATH"
fi
+
AC_PATH_PROG(PYTHON, python, no, $PYTHON_PATH)
- if test "$PYTHON" = "no" ; then
- cf_result=no
- else
- PYTHON_CFLAGS="-I`$PYTHON -c 'from distutils import sysconfig;
print sysconfig.get_python_inc()' 2> /dev/null`"
- if test -n "$python_prefix" && test -d "$python_prefix/lib";
then
- PYTHON_LIBS="-L$python_prefix/lib -lpython"
- else
- PYTHON_LIBS="-lpython"
- fi
+
+ if test "$PYTHON" != no; then
+ cf_result="yes";
+
+ PYTHON_CFLAGS="`$PYTHON -c 'from distutils import sysconfig;
print "-I%s -I%s" % (sysconfig.get_python_inc(),
sysconfig.get_python_inc(plat_specific=True))'`"
+ PYTHON_LIBS="`$PYTHON -c 'from distutils import sysconfig; var
= sysconfig.get_config_var; print "%s %s %s -L%s -lpython%s" %
(var("LINKFORSHARED"), var("LIBS"), var("SYSLIBS"), var("LIBPL"),
var("VERSION"))'`"
LIBS="$PYTHON_LIBS $LIBS"
- CFLAGS="$PYTHON_CFLAGS $CFLAGS"
+ CPPFLAGS="$CPPFLAGS $PYTHON_CFLAGS"
AC_TRY_LINK([#include <Python.h>],
[Py_Initialize();],
cf_result=yes, cf_result=no)
@@ -823,17 +804,28 @@ if test "$enable_python" = "yes"; then
EL_RESTORE_FLAGS
else
EL_CONFIG(CONFIG_SCRIPTING_PYTHON, [Python])
-
- CFLAGS="$CFLAGS_X"
AC_SUBST(PYTHON_LIBS)
AC_SUBST(PYTHON_CFLAGS)
+ cat <<EOF
+***********************************************************************
+The Python support is incomplete and not so well integrated to ELinks
+yet. That means, e.g.., that you have no Python console and there might
+not be all the necessary hooks. Also, the Python interface is not too
+well tested (success stories heartily welcomed!).
+***********************************************************************
+EOF
+ fi
+ else
+ if test -n "$withval" && test "x$withval" != xno; then
+ AC_MSG_ERROR([Python not found])
+ else
+ AC_MSG_WARN([Python support disabled])
fi
fi
+else
+ AC_MSG_RESULT(no);
fi
-AC_MSG_CHECKING([for Python])
-if test "$cf_result"; then AC_MSG_RESULT($cf_result); fi
-
dnl ===================================================================
dnl Check for Lua, optional even if installed.
diff --git a/contrib/python/README.Python b/contrib/python/README.Python
index 9fde5ab..862b60a 100644
--- a/contrib/python/README.Python
+++ b/contrib/python/README.Python
@@ -1,13 +1,8 @@
-If you want to use Python scripting with ELinks add
---with-python to the configure invocation copy hooks.py to ~/.elinks
-When your Python installation is your own build, you could give prefix
-to the configure, eg.
---with-python=/usr/local when Python binary is placed in /usr/local/bin, etc.
+If you want to use Python scripting with ELinks, add --with-python to the
+configure invocation and copy hooks.py to your ~/.elinks directory.
-When 'configure' cannot find -lpython make symbolic link to the appropriate
-library, eg.
-# cd /usr/local/lib
-# ln -s libpython2.4.so.1.0 libpython.so
+If configure cannot find Python you can supply a path, e.g.
+--with-python=/usr/local/bin if your Python binary is in /usr/local/bin, etc.
For the present hooks.py is not very usable. You are welcome to make it better.
Good Luck!
diff --git a/src/scripting/python/core.c b/src/scripting/python/core.c
index fda8218..83eb2c3 100644
--- a/src/scripting/python/core.c
+++ b/src/scripting/python/core.c
@@ -4,7 +4,6 @@ #ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#include "scripting/python/core.h"
#include <Python.h>
#include <stdio.h>
@@ -14,24 +13,91 @@ #include "elinks.h"
#include "config/home.h"
#include "main/module.h"
+#include "scripting/scripting.h"
+#include "scripting/python/core.h"
+#include "scripting/python/python.h"
#include "util/env.h"
#include "util/file.h"
#include "util/string.h"
-PyObject *pDict, *pModule;
+PyObject *pDict = NULL, *pModule = NULL;
+
+/* Error reporting. */
+
+void
+alert_python_error(struct session *ses)
+{
+ unsigned char *msg = "(no traceback available)";
+ PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL;
+ PyObject *tb_module = NULL;
+ PyObject *tb_dict;
+ PyObject *format_function;
+ PyObject *msg_list = NULL;
+ PyObject *empty_string = NULL;
+ PyObject *join_method = NULL;
+ PyObject *msg_string = NULL;
+ unsigned char *temp;
+
+ /*
+ * Retrieve the current error indicator and use the format_exception()
+ * function in Python's traceback module to produce an informative
+ * error message. It returns a list of Python string objects.
+ */
+ PyErr_Fetch(&err_type, &err_value, &err_traceback);
+ PyErr_NormalizeException(&err_type, &err_value, &err_traceback);
+ if (!err_traceback) goto end;
+
+ tb_module = PyImport_ImportModule("traceback");
+ if (!tb_module) goto end;
+
+ tb_dict = PyModule_GetDict(tb_module);
+ format_function = PyDict_GetItemString(tb_dict, "format_exception");
+ if (!format_function || !PyCallable_Check(format_function)) goto end;
+
+ msg_list = PyObject_CallFunction(format_function, "OOO",
+ err_type, err_value, err_traceback);
+ if (!msg_list) goto end;
+
+ /*
+ * Use the join() method of an empty Python string to join the list
+ * of strings into one Python string containing the entire error
+ * message. Then get the contents of the Python string.
+ */
+ empty_string = PyString_FromString("");
+ if (!empty_string) goto end;
+
+ join_method = PyObject_GetAttrString(empty_string, "join");
+ if (!join_method || !PyCallable_Check(join_method)) goto end;
+
+ msg_string = PyObject_CallFunction(join_method, "O", msg_list);
+ if (!msg_string) goto end;
+
+ temp = (unsigned char *)PyString_AsString(msg_string);
+ if (temp) msg = temp;
+
+end:
+ report_scripting_error(&python_scripting_module, ses, msg);
+
+ Py_XDECREF(err_type);
+ Py_XDECREF(err_value);
+ Py_XDECREF(err_traceback);
+ Py_XDECREF(tb_module);
+ Py_XDECREF(msg_list);
+ Py_XDECREF(empty_string);
+ Py_XDECREF(join_method);
+ Py_XDECREF(msg_string);
+
+ /* In case another error occurred while reporting the original error: */
+ PyErr_Clear();
+}
void
cleanup_python(struct module *module)
{
if (Py_IsInitialized()) {
- if (pModule) {
- Py_DECREF(pModule);
- }
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ Py_XDECREF(pDict);
+ Py_XDECREF(pModule);
Py_Finalize();
}
}
@@ -44,15 +110,25 @@ init_python(struct module *module)
if (!python_path) return;
env_set("PYTHONPATH", python_path, -1);
mem_free(python_path);
+
+ /* Treat warnings as errors so they can be caught and handled;
+ * otherwise they would be printed to stderr.
+ *
+ * NOTE: PySys_ResetWarnOptions() and PySys_AddWarnOption() have been
+ * available and stable for many years but they're not officially
+ * documented as part of Python's public API, so in theory these two
+ * functions might no longer be available in some hypothetical future
+ * version of Python. */
+ PySys_ResetWarnOptions();
+ PySys_AddWarnOption("error");
+
Py_Initialize();
pModule = PyImport_ImportModule("hooks");
if (pModule) {
pDict = PyModule_GetDict(pModule);
+ Py_INCREF(pDict);
} else {
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ alert_python_error(NULL);
}
}
diff --git a/src/scripting/python/core.h b/src/scripting/python/core.h
index 00b08de..4450313 100644
--- a/src/scripting/python/core.h
+++ b/src/scripting/python/core.h
@@ -3,7 +3,9 @@ #ifndef EL__SCRIPTING_PYTHON_CORE_H
#define EL__SCRIPTING_PYTHON_CORE_H
struct module;
+struct session;
+void alert_python_error(struct session *ses);
void init_python(struct module *module);
void cleanup_python(struct module *module);
diff --git a/src/scripting/python/hooks.c b/src/scripting/python/hooks.c
index 9f3d095..af9b618 100644
--- a/src/scripting/python/hooks.c
+++ b/src/scripting/python/hooks.c
@@ -32,29 +32,29 @@ do_script_hook_goto_url(struct session *
if (pFunc && PyCallable_Check(pFunc)) {
PyObject *pValue;
- unsigned char *str;
+ unsigned char *current_url;
if (!ses || !have_location(ses)) {
- str = NULL;
+ current_url = NULL;
} else {
- str = struri(cur_loc(ses)->vs.uri);
+ current_url = struri(cur_loc(ses)->vs.uri);
}
- pValue = PyObject_CallFunction(pFunc, "ss", *url, str);
- if (pValue && (pValue != Py_None)) {
- const unsigned char *res = PyString_AsString(pValue);
-
- if (res) {
- unsigned char *new_url = stracpy((unsigned char
*)res);
-
- if (new_url) mem_free_set(url, new_url);
+ pValue = PyObject_CallFunction(pFunc, "ss", *url, current_url);
+ if (pValue) {
+ if (pValue != Py_None) {
+ const unsigned char *str;
+ unsigned char *new_url;
+
+ str = PyString_AsString(pValue);
+ if (str) {
+ new_url = stracpy((unsigned char *)str);
+ if (new_url) mem_free_set(url, new_url);
+ }
}
Py_DECREF(pValue);
} else {
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ alert_python_error(ses);
}
}
}
@@ -72,26 +72,26 @@ script_hook_goto_url(va_list ap, void *d
}
static void
-do_script_hook_follow_url(unsigned char **url)
+do_script_hook_follow_url(struct session *ses, unsigned char **url)
{
PyObject *pFunc = PyDict_GetItemString(pDict, "follow_url_hook");
if (pFunc && PyCallable_Check(pFunc)) {
PyObject *pValue = PyObject_CallFunction(pFunc, "s", *url);
- if (pValue && (pValue != Py_None)) {
- const unsigned char *str = PyString_AsString(pValue);
- unsigned char *new_url;
-
- if (str) {
- new_url = stracpy((unsigned char *)str);
- if (new_url) mem_free_set(url, new_url);
+ if (pValue) {
+ if (pValue != Py_None) {
+ const unsigned char *str;
+ unsigned char *new_url;
+
+ str = PyString_AsString(pValue);
+ if (str) {
+ new_url = stracpy((unsigned char *)str);
+ if (new_url) mem_free_set(url, new_url);
+ }
}
Py_DECREF(pValue);
} else {
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ alert_python_error(ses);
}
}
}
@@ -100,15 +100,17 @@ static enum evhook_status
script_hook_follow_url(va_list ap, void *data)
{
unsigned char **url = va_arg(ap, unsigned char **);
+ struct session *ses = va_arg(ap, struct session *);
if (pDict && *url)
- do_script_hook_follow_url(url);
+ do_script_hook_follow_url(ses, url);
return EVENT_HOOK_STATUS_NEXT;
}
static void
-do_script_hook_pre_format_html(unsigned char *url, struct cache_entry *cached,
+do_script_hook_pre_format_html(struct session *ses, unsigned char *url,
+ struct cache_entry *cached,
struct fragment *fragment)
{
PyObject *pFunc = PyDict_GetItemString(pDict, "pre_format_html_hook");
@@ -118,21 +120,21 @@ do_script_hook_pre_format_html(unsigned
fragment->data,
fragment->length);
- if (pValue && (pValue != Py_None)) {
- const unsigned char *str = PyString_AsString(pValue);
-
- if (str) {
- int len = PyString_Size(pValue); /*
strlen(str); */
-
- add_fragment(cached, 0, str, len);
- normalize_cache_entry(cached, len);
+ if (pValue) {
+ if (pValue != Py_None) {
+ const unsigned char *str;
+ int len;
+
+ str = PyString_AsString(pValue);
+ if (str) {
+ len = PyString_Size(pValue);
+ add_fragment(cached, 0, str, len);
+ normalize_cache_entry(cached, len);
+ }
}
Py_DECREF(pValue);
} else {
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ alert_python_error(ses);
}
}
}
@@ -146,7 +148,7 @@ script_hook_pre_format_html(va_list ap,
unsigned char *url = struri(cached->uri);
if (pDict && ses && url && cached->length && *fragment->data)
- do_script_hook_pre_format_html(url, cached, fragment);
+ do_script_hook_pre_format_html(ses, url, cached, fragment);
return EVENT_HOOK_STATUS_NEXT;
}
@@ -159,20 +161,21 @@ do_script_hook_get_proxy(unsigned char *
if (pFunc && PyCallable_Check(pFunc)) {
PyObject *pValue = PyObject_CallFunction(pFunc, "s", url);
- if (pValue && (pValue != Py_None)) {
- const unsigned char *str = PyString_AsString(pValue);
-
- if (str) {
- unsigned char *new_url = stracpy((unsigned char
*)str);
-
- if (new_url) mem_free_set(new_proxy_url,
new_url);
+ if (pValue) {
+ if (pValue != Py_None) {
+ const unsigned char *str;
+ unsigned char *new_url;
+
+ str = PyString_AsString(pValue);
+ if (str) {
+ new_url = stracpy((unsigned char *)str);
+ if (new_url) mem_free_set(new_proxy_url,
+ new_url);
+ }
}
Py_DECREF(pValue);
} else {
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ alert_python_error(NULL);
}
}
}
@@ -198,14 +201,9 @@ do_script_hook_quit(void)
PyObject *pValue = PyObject_CallFunction(pFunc, NULL);
if (pValue) {
- if (pValue != Py_None) {
- Py_DECREF(pValue);
- }
+ Py_DECREF(pValue);
} else {
- if (PyErr_Occurred()) {
- PyErr_Print();
- PyErr_Clear();
- }
+ alert_python_error(NULL);
}
}
}
diff --git a/src/scripting/scripting.c b/src/scripting/scripting.c
index 4a3360c..78ff5b9 100644
--- a/src/scripting/scripting.c
+++ b/src/scripting/scripting.c
@@ -24,10 +24,14 @@ #include "scripting/python/python.h"
#include "scripting/ruby/ruby.h"
#include "scripting/smjs/smjs.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
/* Error reporting. */
-#if defined(CONFIG_SCRIPTING_RUBY) || defined(CONFIG_SCRIPTING_SPIDERMONKEY)
+#if defined(CONFIG_SCRIPTING_RUBY) || defined(CONFIG_SCRIPTING_SPIDERMONKEY)
|| defined(CONFIG_SCRIPTING_PYTHON)
void
report_scripting_error(struct module *module, struct session *ses,
unsigned char *msg)
@@ -38,6 +42,7 @@ report_scripting_error(struct module *mo
if (!ses) {
if (list_empty(terminals)) {
usrerror("[%s error] %s", module->name, msg);
+ sleep(3);
return;
}
_______________________________________________
elinks-dev mailing list
[email protected]
http://linuxfromscratch.org/mailman/listinfo/elinks-dev