mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05:00
Additional functionality for Python backend.
This commit is contained in:
parent
b52583902f
commit
ebadc9bf9e
@ -1,5 +1,29 @@
|
||||
import re
|
||||
import sys
|
||||
"""Example Python hooks for ELinks.
|
||||
|
||||
If ELinks is compiled with an embedded Python interpreter, it will try
|
||||
to import a Python module called hooks when the browser starts up. To
|
||||
use Python code from within ELinks, create a file called hooks.py in
|
||||
the ~/.elinks directory, or in the system-wide configuration directory
|
||||
(defined when ELinks was compiled), or in the standard Python search path.
|
||||
An example hooks.py file can be found in the contrib/python directory of
|
||||
the ELinks source distribution.
|
||||
|
||||
The hooks module may implement any of several functions that will be
|
||||
called automatically by ELinks in appropriate circumstances; it may also
|
||||
bind ELinks keystrokes to callable Python objects so that arbitrary Python
|
||||
code can be invoked at the whim of the user.
|
||||
|
||||
Functions that will be automatically called by ELinks (if they're defined):
|
||||
|
||||
follow_url_hook() -- Rewrite a URL for a link that's about to be followed.
|
||||
goto_url_hook() -- Rewrite a URL received from a "Go to URL" dialog box.
|
||||
pre_format_html_hook() -- Rewrite a document's body before it's formatted.
|
||||
proxy_for_hook() -- Determine what proxy server to use for a given URL.
|
||||
quit_hook() -- Clean up before ELinks exits.
|
||||
|
||||
"""
|
||||
|
||||
import elinks
|
||||
|
||||
dumbprefixes = {
|
||||
"7th" : "http://7thguard.net/",
|
||||
@ -22,39 +46,221 @@ dumbprefixes = {
|
||||
"sd" : "http://www.slashdot.org/"
|
||||
}
|
||||
|
||||
cygwin = re.compile("cygwin\.com")
|
||||
cygwin_sub1 = re.compile('<body bgcolor="#000000" color="#000000"')
|
||||
cygwin_sub2 = '<body bgcolor="#ffffff" color="#000000"'
|
||||
mbank = re.compile('^https://www\.mbank\.com\.pl/ib_navibar_3\.asp')
|
||||
mbank_sub1 = re.compile('<td valign="top"><img')
|
||||
mbank_sub2 = '<tr><td valign="top"><img'
|
||||
google_redirect = re.compile('^http://www\.google\.com/url\?sa=D&q=(.*)')
|
||||
|
||||
def goto_url_hook(url, current_url):
|
||||
global dumbprefixes
|
||||
"""Rewrite a URL that was entered in a "Go to URL" dialog box.
|
||||
|
||||
if dumbprefixes.has_key(url):
|
||||
return dumbprefixes[url];
|
||||
else:
|
||||
return None
|
||||
This function should return a URL for ELinks to follow, or None if
|
||||
ELinks should follow the original URL.
|
||||
|
||||
Arguments:
|
||||
|
||||
url -- The URL provided by the user.
|
||||
current_url -- The URL of the document being viewed, or None if no
|
||||
document is being viewed.
|
||||
|
||||
"""
|
||||
if url in dumbprefixes:
|
||||
return dumbprefixes[url]
|
||||
|
||||
def follow_url_hook(url):
|
||||
m = google_redirect.search(url)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
"""Rewrite a URL for a link that's about to be followed.
|
||||
|
||||
This function should return a URL for ELinks to follow, or None if
|
||||
ELinks should follow the original URL.
|
||||
|
||||
Arguments:
|
||||
|
||||
url -- The URL of the link.
|
||||
|
||||
"""
|
||||
google_redirect = 'http://www.google.com/url?sa=D&q='
|
||||
if url.startswith(google_redirect):
|
||||
return url.replace(google_redirect, '')
|
||||
|
||||
def pre_format_html_hook(url, html):
|
||||
if cygwin.search(url):
|
||||
html2 = cygwin_sub1.sub(cygwin_sub2, html)
|
||||
return html2
|
||||
if mbank.search(url):
|
||||
html2 = mbank_sub1.sub(mbank_sub2, html)
|
||||
return html2
|
||||
return None
|
||||
"""Rewrite the body of a document before it's formatted.
|
||||
|
||||
This function should return a string for ELinks to format, or None
|
||||
if ELinks should format the original document body. It can be used
|
||||
to repair bad HTML, alter the layout or colors of a document, etc.
|
||||
|
||||
Arguments:
|
||||
|
||||
url -- The URL of the document.
|
||||
html -- The body of the document.
|
||||
|
||||
"""
|
||||
if "cygwin.com" in url:
|
||||
return html.replace('<body bgcolor="#000000" color="#000000"',
|
||||
'<body bgcolor="#ffffff" color="#000000"')
|
||||
elif url.startswith("https://www.mbank.com.pl/ib_navibar_3.asp"):
|
||||
return html.replace('<td valign="top"><img',
|
||||
'<tr><td valign="top"><img')
|
||||
|
||||
def proxy_for_hook(url):
|
||||
return None
|
||||
"""Determine what proxy server to use for a given URL.
|
||||
|
||||
This function should return a string of the form "hostname:portnumber"
|
||||
identifying a proxy server to use, or an empty string if the request
|
||||
shouldn't use any proxy server, or None if ELinks should use its
|
||||
default proxy server.
|
||||
|
||||
Arguments:
|
||||
|
||||
url -- The URL that is about to be followed.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def quit_hook():
|
||||
return None
|
||||
"""Clean up before ELinks exits.
|
||||
|
||||
This function should handle any clean-up tasks that need to be
|
||||
performed before ELinks exits. Its return value is ignored.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# The rest of this file demonstrates some of the functionality available
|
||||
# within hooks.py through the embedded Python interpreter's elinks module.
|
||||
# Full documentation for the elinks module (as well as the hooks module)
|
||||
# can be found in doc/python.txt in the ELinks source distribution.
|
||||
|
||||
|
||||
# This class shows how to use elinks.input_box() and elinks.open(). It
|
||||
# creates a dialog box to prompt the user for a URL, and provides a callback
|
||||
# function that opens the URL in a new tab.
|
||||
#
|
||||
class goto_url_in_new_tab:
|
||||
"""Prompter that opens a given URL in a new tab."""
|
||||
def __init__(self):
|
||||
"""Prompt for a URL."""
|
||||
self.current_location = elinks.current_url()
|
||||
elinks.input_box("Enter URL", self._callback, title="Go to URL")
|
||||
def _callback(self, url):
|
||||
"""Open the given URL in a new tab."""
|
||||
if 'goto_url_hook' in globals():
|
||||
# Mimic the standard "Go to URL" dialog by calling goto_url_hook().
|
||||
url = goto_url_hook(url, self.current_location) or url
|
||||
if url:
|
||||
elinks.open(url, new_tab=True)
|
||||
# The elinks.bind_key() function can be used to create a keystroke binding
|
||||
# that will instantiate the class.
|
||||
#
|
||||
elinks.bind_key("Ctrl-g", goto_url_in_new_tab)
|
||||
|
||||
|
||||
# This class can be used to enter arbitrary Python expressions for the embedded
|
||||
# interpreter to evaluate. (Note that it can only evalute Python expressions,
|
||||
# not execute arbitrary Python code.) The callback function will use
|
||||
# elinks.info_box() to display the results.
|
||||
#
|
||||
class simple_console:
|
||||
"""Simple console for passing expressions to the Python interpreter."""
|
||||
def __init__(self):
|
||||
"""Prompt for a Python expression."""
|
||||
elinks.input_box("Enter Python expression", self._callback, "Console")
|
||||
def _callback(self, input):
|
||||
"""Evalute input and display the result (unless it's None)."""
|
||||
if input is None:
|
||||
# The user canceled.
|
||||
return
|
||||
result = eval(input)
|
||||
if result is not None:
|
||||
elinks.info_box(result, "Result")
|
||||
elinks.bind_key("F5", simple_console)
|
||||
|
||||
|
||||
# If you edit ~/.elinks/hooks.py while the browser is running, you can use
|
||||
# this function to make your changes take effect immediately (without having
|
||||
# to restart ELinks).
|
||||
#
|
||||
def reload_hooks():
|
||||
"""Reload the ELinks Python hooks."""
|
||||
import hooks
|
||||
reload(hooks)
|
||||
elinks.bind_key("F6", reload_hooks)
|
||||
|
||||
|
||||
# This example demonstrates how to create a menu by providing a sequence of
|
||||
# (string, function) tuples specifying each menu item.
|
||||
#
|
||||
def menu():
|
||||
"""Let the user choose from a menu of Python functions."""
|
||||
items = (
|
||||
("~Go to URL in new tab", goto_url_in_new_tab),
|
||||
("Simple Python ~console", simple_console),
|
||||
("~Reload Python hooks", reload_hooks),
|
||||
("Read my favorite RSS/ATOM ~feeds", feedreader),
|
||||
)
|
||||
elinks.menu(items)
|
||||
elinks.bind_key("F4", menu)
|
||||
|
||||
|
||||
# Finally, a more elaborate demonstration: If you install the add-on Python
|
||||
# module from http://feedparser.org/ this class can use it to parse a list
|
||||
# of your favorite RSS/ATOM feeds, figure out which entries you haven't seen
|
||||
# yet, open each of those entries in a new tab, and report statistics on what
|
||||
# it fetched. The class is instantiated by pressing the "!" key.
|
||||
#
|
||||
# This class demonstrates the elinks.load() function, which can be used to
|
||||
# load a document into the ELinks cache without displaying it to the user;
|
||||
# the document's contents are passed to a Python callback function.
|
||||
|
||||
my_favorite_feeds = (
|
||||
"http://rss.gmane.org/messages/excerpts/gmane.comp.web.elinks.user",
|
||||
# ... add any other RSS or ATOM feeds that interest you ...
|
||||
# "http://elinks.cz/news.rss",
|
||||
# "http://www.python.org/channews.rdf",
|
||||
)
|
||||
|
||||
class feedreader:
|
||||
|
||||
"""RSS/ATOM feed reader."""
|
||||
|
||||
def __init__(self, feeds=my_favorite_feeds):
|
||||
"""Constructor."""
|
||||
self._results = {}
|
||||
self._feeds = feeds
|
||||
for feed in feeds:
|
||||
elinks.load(feed, self._callback)
|
||||
|
||||
def _callback(self, header, body):
|
||||
"""Read a feed, identify unseen entries, and open them in new tabs."""
|
||||
import anydbm
|
||||
import feedparser # you need to get this module from feedparser.org
|
||||
import os
|
||||
|
||||
if not body:
|
||||
return
|
||||
seen = anydbm.open(os.path.join(elinks.home, "rss.seen"), "c")
|
||||
feed = feedparser.parse(body)
|
||||
new = 0
|
||||
errors = 0
|
||||
for entry in feed.entries:
|
||||
try:
|
||||
if not seen.has_key(entry.link):
|
||||
elinks.open(entry.link, new_tab=True)
|
||||
seen[entry.link] = ""
|
||||
new += 1
|
||||
except:
|
||||
errors += 1
|
||||
seen.close()
|
||||
self._tally(feed.channel.title, new, errors)
|
||||
|
||||
def _tally(self, title, new, errors):
|
||||
"""Record and report feed statistics."""
|
||||
self._results[title] = (new, errors)
|
||||
if len(self._results) == len(self._feeds):
|
||||
feeds = self._results.keys()
|
||||
feeds.sort()
|
||||
width = max([len(title) for title in feeds])
|
||||
fmt = "%*s new entries: %2d errors: %2d\n"
|
||||
summary = ""
|
||||
for title in feeds:
|
||||
new, errors = self._results[title]
|
||||
summary += fmt % (-width, title, new, errors)
|
||||
elinks.info_box(summary, "Feed Statistics")
|
||||
|
||||
elinks.bind_key("!", feedreader)
|
||||
|
@ -3,6 +3,15 @@ include $(top_builddir)/Makefile.config
|
||||
|
||||
INCLUDES += $(PYTHON_CFLAGS)
|
||||
|
||||
OBJS = python.o hooks.o core.o
|
||||
OBJS = \
|
||||
core.o \
|
||||
dialogs.o \
|
||||
document.o \
|
||||
hooks.o \
|
||||
keybinding.o \
|
||||
load.o \
|
||||
menu.o \
|
||||
open.o \
|
||||
python.o
|
||||
|
||||
include $(top_srcdir)/Makefile.lib
|
||||
|
@ -5,37 +5,42 @@
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
#include <osdefs.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "config/home.h"
|
||||
#include "main/module.h"
|
||||
#include "scripting/scripting.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "scripting/python/dialogs.h"
|
||||
#include "scripting/python/document.h"
|
||||
#include "scripting/python/keybinding.h"
|
||||
#include "scripting/python/load.h"
|
||||
#include "scripting/python/menu.h"
|
||||
#include "scripting/python/open.h"
|
||||
#include "scripting/python/python.h"
|
||||
#include "scripting/scripting.h"
|
||||
#include "session/session.h"
|
||||
#include "util/env.h"
|
||||
#include "util/file.h"
|
||||
#include "util/string.h"
|
||||
|
||||
struct session *python_ses = NULL;
|
||||
|
||||
PyObject *pDict = NULL, *pModule = NULL;
|
||||
PyObject *python_elinks_err = NULL;
|
||||
PyObject *python_hooks = NULL;
|
||||
|
||||
/* Error reporting. */
|
||||
|
||||
void
|
||||
alert_python_error(struct session *ses)
|
||||
alert_python_error(void)
|
||||
{
|
||||
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;
|
||||
|
||||
@ -46,17 +51,15 @@ alert_python_error(struct session *ses)
|
||||
*/
|
||||
PyErr_Fetch(&err_type, &err_value, &err_traceback);
|
||||
PyErr_NormalizeException(&err_type, &err_value, &err_traceback);
|
||||
if (!err_traceback) goto end;
|
||||
if (!err_type) 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);
|
||||
msg_list = PyObject_CallMethod(tb_module, "format_exception", "OOO",
|
||||
err_type,
|
||||
err_value ? err_value : Py_None,
|
||||
err_traceback ? err_traceback : Py_None);
|
||||
if (!msg_list) goto end;
|
||||
|
||||
/*
|
||||
@ -67,17 +70,14 @@ alert_python_error(struct session *ses)
|
||||
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);
|
||||
msg_string = PyObject_CallMethod(empty_string, "join", "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);
|
||||
report_scripting_error(&python_scripting_module, python_ses, msg);
|
||||
|
||||
Py_XDECREF(err_type);
|
||||
Py_XDECREF(err_value);
|
||||
@ -85,31 +85,69 @@ end:
|
||||
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)
|
||||
/* Prepend ELinks directories to Python's search path. */
|
||||
|
||||
static int
|
||||
set_python_search_path(void)
|
||||
{
|
||||
if (Py_IsInitialized()) {
|
||||
Py_XDECREF(pDict);
|
||||
Py_XDECREF(pModule);
|
||||
Py_Finalize();
|
||||
}
|
||||
struct string new_python_path, *okay;
|
||||
unsigned char *old_python_path;
|
||||
int result = -1;
|
||||
|
||||
if (!init_string(&new_python_path)) return result;
|
||||
|
||||
old_python_path = (unsigned char *) getenv("PYTHONPATH");
|
||||
if (old_python_path)
|
||||
okay = add_format_to_string(&new_python_path, "%s%c%s%c%s",
|
||||
elinks_home, DELIM, CONFDIR,
|
||||
DELIM, old_python_path);
|
||||
else
|
||||
okay = add_format_to_string(&new_python_path, "%s%c%s",
|
||||
elinks_home, DELIM, CONFDIR);
|
||||
if (okay) result = env_set("PYTHONPATH", new_python_path.source, -1);
|
||||
done_string(&new_python_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Module-level documentation for the Python interpreter's elinks module. */
|
||||
|
||||
static char module_doc[] =
|
||||
PYTHON_DOCSTRING("Interface to the ELinks web browser.\n\
|
||||
\n\
|
||||
Functions:\n\
|
||||
\n\
|
||||
bind_key() -- Bind a keystroke to a callable object.\n\
|
||||
current_document() -- Return the body of the document being viewed.\n\
|
||||
current_header() -- Return the header of the document being viewed.\n\
|
||||
current_link_url() -- Return the URL of the currently selected link.\n\
|
||||
current_title() -- Return the title of the document being viewed.\n\
|
||||
current_url() -- Return the URL of the document being viewed.\n\
|
||||
info_box() -- Display information to the user.\n\
|
||||
input_box() -- Prompt for user input.\n\
|
||||
load() -- Load a document into the ELinks cache.\n\
|
||||
menu() -- Display a menu.\n\
|
||||
open() -- View a document.\n\
|
||||
\n\
|
||||
Exception classes:\n\
|
||||
\n\
|
||||
error -- Errors internal to ELinks.\n\
|
||||
\n\
|
||||
Other public objects:\n\
|
||||
\n\
|
||||
home -- A string containing the pathname of the ~/.elinks directory.\n");
|
||||
|
||||
void
|
||||
init_python(struct module *module)
|
||||
{
|
||||
unsigned char *python_path = straconcat(elinks_home, ":", CONFDIR, NULL);
|
||||
PyObject *elinks_module, *module_dict, *module_name;
|
||||
|
||||
if (!python_path) return;
|
||||
env_set("PYTHONPATH", python_path, -1);
|
||||
mem_free(python_path);
|
||||
if (set_python_search_path() != 0) return;
|
||||
|
||||
/* Treat warnings as errors so they can be caught and handled;
|
||||
* otherwise they would be printed to stderr.
|
||||
@ -123,12 +161,66 @@ init_python(struct module *module)
|
||||
PySys_AddWarnOption("error");
|
||||
|
||||
Py_Initialize();
|
||||
pModule = PyImport_ImportModule("hooks");
|
||||
|
||||
if (pModule) {
|
||||
pDict = PyModule_GetDict(pModule);
|
||||
Py_INCREF(pDict);
|
||||
} else {
|
||||
alert_python_error(NULL);
|
||||
elinks_module = Py_InitModule3("elinks", NULL, module_doc);
|
||||
if (!elinks_module) goto python_error;
|
||||
|
||||
if (PyModule_AddStringConstant(elinks_module, "home", elinks_home) != 0)
|
||||
goto python_error;
|
||||
|
||||
python_elinks_err = PyErr_NewException("elinks.error", NULL, NULL);
|
||||
if (!python_elinks_err) goto python_error;
|
||||
|
||||
if (PyModule_AddObject(elinks_module, "error", python_elinks_err) != 0)
|
||||
goto python_error;
|
||||
|
||||
module_dict = PyModule_GetDict(elinks_module);
|
||||
if (!module_dict) goto python_error;
|
||||
module_name = PyString_FromString("elinks");
|
||||
if (!module_name) goto python_error;
|
||||
|
||||
if (python_init_dialogs_interface(module_dict, module_name) != 0
|
||||
|| python_init_document_interface(module_dict, module_name) != 0
|
||||
|| python_init_keybinding_interface(module_dict, module_name) != 0
|
||||
|| python_init_load_interface(module_dict, module_name) != 0
|
||||
|| python_init_menu_interface(module_dict, module_name) != 0
|
||||
|| python_init_open_interface(module_dict, module_name) != 0)
|
||||
goto python_error;
|
||||
|
||||
python_hooks = PyImport_ImportModule("hooks");
|
||||
if (!python_hooks) goto python_error;
|
||||
|
||||
return;
|
||||
|
||||
python_error:
|
||||
alert_python_error();
|
||||
}
|
||||
|
||||
void
|
||||
cleanup_python(struct module *module)
|
||||
{
|
||||
if (Py_IsInitialized()) {
|
||||
python_done_keybinding_interface();
|
||||
Py_XDECREF(python_hooks);
|
||||
Py_Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/* Add methods to a Python extension module. */
|
||||
|
||||
int
|
||||
add_python_methods(PyObject *dict, PyObject *name, PyMethodDef *methods)
|
||||
{
|
||||
PyMethodDef *method;
|
||||
|
||||
for (method = methods; method && method->ml_name; method++) {
|
||||
PyObject *function = PyCFunction_NewEx(method, NULL, name);
|
||||
int result;
|
||||
|
||||
if (!function) return -1;
|
||||
result = PyDict_SetItemString(dict, method->ml_name, function);
|
||||
Py_DECREF(function);
|
||||
if (result != 0) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -2,11 +2,26 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_CORE_H
|
||||
#define EL__SCRIPTING_PYTHON_CORE_H
|
||||
|
||||
struct module;
|
||||
struct session;
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
struct module;
|
||||
|
||||
extern struct session *python_ses;
|
||||
extern PyObject *python_elinks_err;
|
||||
|
||||
void alert_python_error(void);
|
||||
|
||||
void alert_python_error(struct session *ses);
|
||||
void init_python(struct module *module);
|
||||
void cleanup_python(struct module *module);
|
||||
|
||||
int add_python_methods(PyObject *dict, PyObject *name, PyMethodDef *methods);
|
||||
|
||||
#ifndef CONFIG_SMALL
|
||||
#define PYTHON_DOCSTRING(str) str
|
||||
#else
|
||||
#define PYTHON_DOCSTRING(str) ""
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -4,6 +4,10 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
@ -11,133 +15,89 @@
|
||||
#include "main/event.h"
|
||||
#include "protocol/uri.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "scripting/python/hooks.h"
|
||||
#include "session/location.h"
|
||||
#include "session/session.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#undef _POSIX_C_SOURCE
|
||||
#include <Python.h>
|
||||
extern PyObject *python_hooks;
|
||||
|
||||
/* The events that will trigger the functions below and what they are expected
|
||||
* to do is explained in doc/events.txt */
|
||||
/*
|
||||
* A utility function for script_hook_url() and script_hook_get_proxy():
|
||||
* Free a char * and replace it with the contents of a Python string.
|
||||
* (Py_None is ignored.)
|
||||
*/
|
||||
|
||||
extern PyObject *pDict;
|
||||
extern PyObject *pModule;
|
||||
|
||||
static void
|
||||
do_script_hook_goto_url(struct session *ses, unsigned char **url)
|
||||
static PyObject *
|
||||
replace_with_python_string(unsigned char **dest, PyObject *object)
|
||||
{
|
||||
PyObject *pFunc = PyDict_GetItemString(pDict, "goto_url_hook");
|
||||
unsigned char *str;
|
||||
|
||||
if (pFunc && PyCallable_Check(pFunc)) {
|
||||
PyObject *pValue;
|
||||
unsigned char *current_url;
|
||||
if (object == Py_None) return object;
|
||||
|
||||
if (!ses || !have_location(ses)) {
|
||||
current_url = NULL;
|
||||
} else {
|
||||
str = (unsigned char *) PyString_AsString(object);
|
||||
if (!str) return NULL;
|
||||
|
||||
str = stracpy(str);
|
||||
if (!str) return PyErr_NoMemory();
|
||||
|
||||
mem_free_set(dest, str);
|
||||
return object;
|
||||
}
|
||||
|
||||
/* Call a Python hook for a goto-url or follow-url event. */
|
||||
|
||||
static enum evhook_status
|
||||
script_hook_url(va_list ap, void *data)
|
||||
{
|
||||
unsigned char **url = va_arg(ap, unsigned char **);
|
||||
struct session *ses = va_arg(ap, struct session *);
|
||||
char *method = data;
|
||||
struct session *saved_python_ses = python_ses;
|
||||
PyObject *result;
|
||||
|
||||
evhook_use_params(url && ses);
|
||||
|
||||
if (!python_hooks || !url || !*url
|
||||
|| !PyObject_HasAttrString(python_hooks, method))
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
|
||||
python_ses = ses;
|
||||
|
||||
/*
|
||||
* Historical note: The only reason the goto and follow hooks are
|
||||
* treated differently is to maintain backwards compatibility for
|
||||
* people who already have a goto_url_hook() function in hooks.py
|
||||
* that expects a second argument. If we were starting over from
|
||||
* scratch, we could treat the goto and follow hooks identically and
|
||||
* simply pass @url as the sole argument in both cases; the Python
|
||||
* code for the goto hook no longer needs its @current_url argument
|
||||
* since it could instead determine the current URL by calling the
|
||||
* Python interpreter's elinks.current_url() function.
|
||||
*/
|
||||
if (!strcmp(method, "goto_url_hook")) {
|
||||
unsigned char *current_url = NULL;
|
||||
|
||||
if (python_ses && have_location(python_ses))
|
||||
current_url = struri(cur_loc(ses)->vs.uri);
|
||||
}
|
||||
|
||||
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);
|
||||
result = PyObject_CallMethod(python_hooks, method, "ss", *url,
|
||||
current_url);
|
||||
} else {
|
||||
alert_python_error(ses);
|
||||
}
|
||||
}
|
||||
result = PyObject_CallMethod(python_hooks, method, "s", *url);
|
||||
}
|
||||
|
||||
static enum evhook_status
|
||||
script_hook_goto_url(va_list ap, void *data)
|
||||
{
|
||||
unsigned char **url = va_arg(ap, unsigned char **);
|
||||
struct session *ses = va_arg(ap, struct session *);
|
||||
if (!result || !replace_with_python_string(url, result))
|
||||
alert_python_error();
|
||||
|
||||
if (pDict && *url)
|
||||
do_script_hook_goto_url(ses, url);
|
||||
Py_XDECREF(result);
|
||||
|
||||
python_ses = saved_python_ses;
|
||||
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
}
|
||||
|
||||
static void
|
||||
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) {
|
||||
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 {
|
||||
alert_python_error(ses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(ses, url);
|
||||
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
}
|
||||
|
||||
static void
|
||||
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");
|
||||
|
||||
if (pFunc && PyCallable_Check(pFunc)) {
|
||||
PyObject *pValue = PyObject_CallFunction(pFunc, "ss#", url,
|
||||
fragment->data,
|
||||
fragment->length);
|
||||
|
||||
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 {
|
||||
alert_python_error(ses);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Call a Python hook for a pre-format-html event. */
|
||||
|
||||
static enum evhook_status
|
||||
script_hook_pre_format_html(va_list ap, void *data)
|
||||
@ -146,78 +106,94 @@ script_hook_pre_format_html(va_list ap, void *data)
|
||||
struct cache_entry *cached = va_arg(ap, struct cache_entry *);
|
||||
struct fragment *fragment = get_cache_fragment(cached);
|
||||
unsigned char *url = struri(cached->uri);
|
||||
char *method = "pre_format_html_hook";
|
||||
struct session *saved_python_ses = python_ses;
|
||||
PyObject *result;
|
||||
int success = 0;
|
||||
|
||||
if (pDict && ses && url && cached->length && *fragment->data)
|
||||
do_script_hook_pre_format_html(ses, url, cached, fragment);
|
||||
evhook_use_params(ses && cached);
|
||||
|
||||
if (!python_hooks || !cached->length || !*fragment->data
|
||||
|| !PyObject_HasAttrString(python_hooks, method))
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
|
||||
python_ses = ses;
|
||||
|
||||
result = PyObject_CallMethod(python_hooks, method, "ss#", url,
|
||||
fragment->data, fragment->length);
|
||||
if (!result) goto error;
|
||||
|
||||
if (result != Py_None) {
|
||||
unsigned char *str;
|
||||
int len;
|
||||
|
||||
if (PyString_AsStringAndSize(result, (char **) &str, &len) != 0)
|
||||
goto error;
|
||||
|
||||
(void) add_fragment(cached, 0, str, len);
|
||||
normalize_cache_entry(cached, len);
|
||||
}
|
||||
|
||||
success = 1;
|
||||
|
||||
error:
|
||||
if (!success) alert_python_error();
|
||||
|
||||
Py_XDECREF(result);
|
||||
|
||||
python_ses = saved_python_ses;
|
||||
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
}
|
||||
|
||||
static inline void
|
||||
do_script_hook_get_proxy(unsigned char **new_proxy_url, unsigned char *url)
|
||||
{
|
||||
PyObject *pFunc = PyDict_GetItemString(pDict, "proxy_for_hook");
|
||||
|
||||
if (pFunc && PyCallable_Check(pFunc)) {
|
||||
PyObject *pValue = PyObject_CallFunction(pFunc, "s", 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 {
|
||||
alert_python_error(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Call a Python hook for a get-proxy event. */
|
||||
|
||||
static enum evhook_status
|
||||
script_hook_get_proxy(va_list ap, void *data)
|
||||
{
|
||||
unsigned char **new_proxy_url = va_arg(ap, unsigned char **);
|
||||
unsigned char **proxy = va_arg(ap, unsigned char **);
|
||||
unsigned char *url = va_arg(ap, unsigned char *);
|
||||
char *method = "proxy_for_hook";
|
||||
PyObject *result;
|
||||
|
||||
if (pDict && new_proxy_url && url)
|
||||
do_script_hook_get_proxy(new_proxy_url, url);
|
||||
evhook_use_params(proxy && url);
|
||||
|
||||
if (!python_hooks || !proxy || !url
|
||||
|| !PyObject_HasAttrString(python_hooks, method))
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
|
||||
result = PyObject_CallMethod(python_hooks, method, "s", url);
|
||||
|
||||
if (!result || !replace_with_python_string(proxy, result))
|
||||
alert_python_error();
|
||||
|
||||
Py_XDECREF(result);
|
||||
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
}
|
||||
|
||||
static void
|
||||
do_script_hook_quit(void)
|
||||
{
|
||||
PyObject *pFunc = PyDict_GetItemString(pDict, "quit_hook");
|
||||
|
||||
if (pFunc && PyCallable_Check(pFunc)) {
|
||||
PyObject *pValue = PyObject_CallFunction(pFunc, NULL);
|
||||
|
||||
if (pValue) {
|
||||
Py_DECREF(pValue);
|
||||
} else {
|
||||
alert_python_error(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Call a Python hook for a quit event. */
|
||||
|
||||
static enum evhook_status
|
||||
script_hook_quit(va_list ap, void *data)
|
||||
{
|
||||
if (pDict) do_script_hook_quit();
|
||||
char *method = "quit_hook";
|
||||
PyObject *result;
|
||||
|
||||
if (!python_hooks || !PyObject_HasAttrString(python_hooks, method))
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
|
||||
result = PyObject_CallMethod(python_hooks, method, NULL);
|
||||
if (!result) alert_python_error();
|
||||
|
||||
Py_XDECREF(result);
|
||||
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
}
|
||||
|
||||
struct event_hook_info python_scripting_hooks[] = {
|
||||
{ "goto-url", 0, script_hook_goto_url, NULL },
|
||||
{ "follow-url", 0, script_hook_follow_url, NULL },
|
||||
{ "goto-url", 0, script_hook_url, "goto_url_hook" },
|
||||
{ "follow-url", 0, script_hook_url, "follow_url_hook" },
|
||||
{ "pre-format-html", 0, script_hook_pre_format_html, NULL },
|
||||
{ "get-proxy", 0, script_hook_get_proxy, NULL },
|
||||
{ "quit", 0, script_hook_quit, NULL },
|
||||
|
@ -4,11 +4,12 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "scripting/python/core.h"
|
||||
#include <Python.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "main/module.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "scripting/python/hooks.h"
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user