mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05:00
Oh, sorry. I forgot about these.
This commit is contained in:
parent
ebadc9bf9e
commit
68913c8c7d
129
contrib/python/elinks_maint.py
Normal file
129
contrib/python/elinks_maint.py
Normal file
@ -0,0 +1,129 @@
|
||||
"""Additional Python code for ELinks maintainers.
|
||||
|
||||
This module is intended for ELinks maintainers. If you modify or add to
|
||||
the Python APIs in src/scripting/python/*.c and/or contrib/python/hooks.py,
|
||||
you should update the accompanying docstrings to reflect your changes and
|
||||
then generate a new version of the file doc/python.txt (which serves as a
|
||||
reference manual for the browser's Python APIs). The embedded interpreter
|
||||
can use introspection to regenerate the python.txt document for you; just
|
||||
copy this file into your ~/.elinks directory and add something like the
|
||||
following to ~/.elinks/hooks.py:
|
||||
|
||||
import elinks_maint
|
||||
elinks.bind_key('F2', elinks_maint.generate_python_txt)
|
||||
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import tempfile
|
||||
import types
|
||||
|
||||
preface = """\
|
||||
Python programmers can customize the behavior of ELinks by creating a Python
|
||||
hooks module. The embedded Python interpreter provides an internal module
|
||||
called elinks that can be used by the hooks module to create keystroke
|
||||
bindings for Python code, obtain information about the document being
|
||||
viewed, display simple dialog boxes and menus, load documents into the
|
||||
ELinks cache, or display documents to the user. These two modules are
|
||||
described below.
|
||||
|
||||
"""
|
||||
|
||||
module_template = """
|
||||
MODULE
|
||||
%s - %s
|
||||
|
||||
DESCRIPTION
|
||||
%s
|
||||
|
||||
FUNCTIONS
|
||||
%s
|
||||
"""
|
||||
|
||||
separator = '-' * 78 + '\n'
|
||||
|
||||
def document_modules(*modules):
|
||||
"""Format the internal documentation found in one or more Python modules."""
|
||||
output = []
|
||||
for module in modules:
|
||||
name, doc, namespace = module.__name__, module.__doc__, module.__dict__
|
||||
if not name or not namespace:
|
||||
continue
|
||||
try:
|
||||
summary, junk, description = doc.rstrip().split('\n', 2)
|
||||
except:
|
||||
summary, description = '?', '(no description available)'
|
||||
functions = document_functions(namespace)
|
||||
output.append(module_template % (name, summary, indent(description),
|
||||
indent(functions)))
|
||||
return separator.join(output)
|
||||
|
||||
def document_functions(namespace):
|
||||
"""Format the internal documentation for all functions in a namespace."""
|
||||
objects = namespace.items()
|
||||
objects.sort()
|
||||
output = []
|
||||
for name, object in objects:
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
species = type(object)
|
||||
if species == types.BuiltinFunctionType:
|
||||
args = '(...)'
|
||||
elif species == types.FunctionType:
|
||||
args = inspect.formatargspec(*inspect.getargspec(object))
|
||||
else:
|
||||
continue
|
||||
description = inspect.getdoc(object)
|
||||
output.append('%s%s\n%s\n' % (name, args, indent(description)))
|
||||
return '\n'.join(output)
|
||||
|
||||
def generate_python_txt():
|
||||
"""Generate documentation for the hooks and elinks modules."""
|
||||
import elinks
|
||||
import hooks
|
||||
|
||||
# Remove anything that doesn't belong in the API documentation.
|
||||
#
|
||||
hooks_api_functions = (
|
||||
'follow_url_hook',
|
||||
'goto_url_hook',
|
||||
'pre_format_html_hook',
|
||||
'proxy_for_hook',
|
||||
'quit_hook',
|
||||
)
|
||||
for key in hooks.__dict__.keys():
|
||||
if key not in hooks_api_functions and not key.startswith('_'):
|
||||
del hooks.__dict__[key]
|
||||
hooks.__doc__ = hooks.__doc__.replace('Example Python', 'Python')
|
||||
|
||||
# Generate the documentation.
|
||||
#
|
||||
try:
|
||||
output = separator.join((preface, document_modules(hooks, elinks)))
|
||||
finally:
|
||||
# Restore the hooks module to a sane state.
|
||||
reload(hooks)
|
||||
|
||||
# View the documentation.
|
||||
#
|
||||
path = write_tempfile(output)
|
||||
elinks.open(path)
|
||||
|
||||
def indent(text):
|
||||
"""Return indented text."""
|
||||
indent = ' ' * 4
|
||||
return '\n'.join([indent + line for line in text.split('\n')])
|
||||
|
||||
def write_tempfile(text):
|
||||
"""Write a string to a temporary file and return the file's name."""
|
||||
output = tempfile.NamedTemporaryFile(prefix='elinks_maint', suffix='.txt')
|
||||
output.write(text)
|
||||
output.flush()
|
||||
_tempfiles[text] = output
|
||||
return output.name
|
||||
|
||||
# Temp files are stashed in this dictionary to prevent them from being closed
|
||||
# before ELinks has a chance to read them; they will be automatically deleted
|
||||
# when the dictionary is garbage-collected at exit time.
|
||||
#
|
||||
_tempfiles = {}
|
248
src/scripting/python/dialogs.c
Normal file
248
src/scripting/python/dialogs.c
Normal file
@ -0,0 +1,248 @@
|
||||
/* Dialog boxes for Python. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "bfu/inpfield.h"
|
||||
#include "bfu/msgbox.h"
|
||||
#include "intl/gettext/libintl.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "session/session.h"
|
||||
#include "util/error.h"
|
||||
#include "util/memlist.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
/* Python interface for displaying information to the user. */
|
||||
|
||||
static char python_info_box_doc[] =
|
||||
PYTHON_DOCSTRING("info_box(text[, title]) -> None\n\
|
||||
\n\
|
||||
Display information to the user in a dialog box.\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
text -- The text to be displayed in the dialog box. This argument can\n\
|
||||
be a string or any object that has a string representation as\n\
|
||||
returned by str(object).\n\
|
||||
\n\
|
||||
Optional arguments:\n\
|
||||
\n\
|
||||
title -- A string containing a title for the dialog box. By default\n\
|
||||
the string \"Info\" is used.\n");
|
||||
|
||||
static PyObject *
|
||||
python_info_box(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
/* [gettext_accelerator_context(python_info_box)] */
|
||||
unsigned char *title = N_("Info");
|
||||
PyObject *object, *string_object;
|
||||
unsigned char *text;
|
||||
static char *kwlist[] = {"text", "title", NULL};
|
||||
|
||||
if (!python_ses) {
|
||||
PyErr_SetString(python_elinks_err, "No session");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|s:info_box", kwlist,
|
||||
&object, &title))
|
||||
return NULL;
|
||||
|
||||
assert(object);
|
||||
if_assert_failed {
|
||||
PyErr_SetString(python_elinks_err, N_("Internal error"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a string representation of the object, then copy that string's
|
||||
* contents.
|
||||
*/
|
||||
string_object = PyObject_Str(object);
|
||||
if (!string_object) return NULL;
|
||||
text = (unsigned char *) PyString_AS_STRING(string_object);
|
||||
if (!text) {
|
||||
Py_DECREF(string_object);
|
||||
return NULL;
|
||||
}
|
||||
text = stracpy(text);
|
||||
Py_DECREF(string_object);
|
||||
if (!text) goto mem_error;
|
||||
|
||||
title = stracpy(title);
|
||||
if (!title) goto free_text;
|
||||
|
||||
(void) msg_box(python_ses->tab->term, getml(title, NULL),
|
||||
MSGBOX_NO_INTL | MSGBOX_SCROLLABLE | MSGBOX_FREE_TEXT,
|
||||
title, ALIGN_LEFT,
|
||||
text,
|
||||
NULL, 1,
|
||||
N_("~OK"), NULL, B_ENTER | B_ESC);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
|
||||
free_text:
|
||||
mem_free(text);
|
||||
|
||||
mem_error:
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
|
||||
struct python_input_callback_hop {
|
||||
struct session *ses;
|
||||
PyObject *callback;
|
||||
};
|
||||
|
||||
/*
|
||||
* C wrapper that invokes Python callbacks for input_dialog() OK button.
|
||||
*
|
||||
* This is also used indirectly for the Cancel button, with a NULL @text
|
||||
* argument. See invoke_input_cancel_callback() below.
|
||||
*/
|
||||
|
||||
static void
|
||||
invoke_input_ok_callback(void *data, unsigned char *text)
|
||||
{
|
||||
struct python_input_callback_hop *hop = data;
|
||||
struct session *saved_python_ses = python_ses;
|
||||
PyObject *result;
|
||||
|
||||
assert(hop && hop->callback);
|
||||
if_assert_failed return;
|
||||
|
||||
python_ses = hop->ses;
|
||||
|
||||
/* If @text is NULL, the "s" format will create a None reference. */
|
||||
result = PyObject_CallFunction(hop->callback, "s", text);
|
||||
if (result)
|
||||
Py_DECREF(result);
|
||||
else
|
||||
alert_python_error();
|
||||
|
||||
Py_DECREF(hop->callback);
|
||||
mem_free(hop);
|
||||
|
||||
python_ses = saved_python_ses;
|
||||
}
|
||||
|
||||
/* C wrapper that invokes Python callbacks for input_dialog() cancel button. */
|
||||
|
||||
static void
|
||||
invoke_input_cancel_callback(void *data)
|
||||
{
|
||||
invoke_input_ok_callback(data, NULL);
|
||||
}
|
||||
|
||||
/* Python interface for getting input from the user. */
|
||||
|
||||
static char python_input_box_doc[] =
|
||||
PYTHON_DOCSTRING(
|
||||
"input_box(prompt, callback, title=\"User dialog\", initial=\"\") -> None\n\
|
||||
\n\
|
||||
Display a dialog box to prompt for user input.\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
prompt -- A string containing a prompt for the dialog box.\n\
|
||||
callback -- A callable object to be called after the dialog is\n\
|
||||
finished. It will be called with a single argument, which\n\
|
||||
will be either a string provided by the user or else None\n\
|
||||
if the user canceled the dialog.\n\
|
||||
\n\
|
||||
Optional keyword arguments:\n\
|
||||
\n\
|
||||
title -- A string containing a title for the dialog box. By default\n\
|
||||
the string \"User dialog\" is used.\n\
|
||||
initial -- A string containing an initial value for the text entry\n\
|
||||
field. By default the entry field is initially empty.\n");
|
||||
|
||||
static PyObject *
|
||||
python_input_box(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
unsigned char *prompt;
|
||||
PyObject *callback;
|
||||
unsigned char *title = N_("User dialog");
|
||||
unsigned char *initial = NULL;
|
||||
struct python_input_callback_hop *hop;
|
||||
static char *kwlist[] = {"prompt", "callback", "title", "initial", NULL};
|
||||
|
||||
if (!python_ses) {
|
||||
PyErr_SetString(python_elinks_err, "No session");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|ss:input_box",
|
||||
kwlist, &prompt, &callback, &title,
|
||||
&initial))
|
||||
return NULL;
|
||||
|
||||
assert(prompt && callback && title);
|
||||
if_assert_failed {
|
||||
PyErr_SetString(python_elinks_err, N_("Internal error"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
prompt = stracpy(prompt);
|
||||
if (!prompt) goto mem_error;
|
||||
|
||||
title = stracpy(title);
|
||||
if (!title) goto free_prompt;
|
||||
|
||||
if (initial) {
|
||||
initial = stracpy(initial);
|
||||
if (!initial) goto free_title;
|
||||
}
|
||||
|
||||
hop = mem_alloc(sizeof(*hop));
|
||||
if (!hop) goto free_initial;
|
||||
hop->ses = python_ses;
|
||||
hop->callback = callback;
|
||||
Py_INCREF(callback);
|
||||
|
||||
input_dialog(python_ses->tab->term, getml(prompt, title, initial, NULL),
|
||||
title, prompt,
|
||||
hop, NULL,
|
||||
MAX_STR_LEN, initial, 0, 0, NULL,
|
||||
invoke_input_ok_callback,
|
||||
invoke_input_cancel_callback);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
|
||||
free_initial:
|
||||
mem_free_if(initial);
|
||||
|
||||
free_title:
|
||||
mem_free(title);
|
||||
|
||||
free_prompt:
|
||||
mem_free(prompt);
|
||||
|
||||
mem_error:
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
|
||||
static PyMethodDef dialogs_methods[] = {
|
||||
{"info_box", (PyCFunction) python_info_box,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
python_info_box_doc},
|
||||
|
||||
{"input_box", (PyCFunction) python_input_box,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
python_input_box_doc},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
int
|
||||
python_init_dialogs_interface(PyObject *dict, PyObject *name)
|
||||
{
|
||||
return add_python_methods(dict, name, dialogs_methods);
|
||||
}
|
8
src/scripting/python/dialogs.h
Normal file
8
src/scripting/python/dialogs.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_DIALOGS_H
|
||||
#define EL__SCRIPTING_PYTHON_DIALOGS_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int python_init_dialogs_interface(PyObject *dict, PyObject *name);
|
||||
|
||||
#endif
|
144
src/scripting/python/document.c
Normal file
144
src/scripting/python/document.c
Normal file
@ -0,0 +1,144 @@
|
||||
/* Information about current document and current link for Python. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "cache/cache.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "session/location.h"
|
||||
#include "session/session.h"
|
||||
|
||||
/* Python interface to get the current document's body. */
|
||||
|
||||
static char python_current_document_doc[] =
|
||||
PYTHON_DOCSTRING("current_document() -> string or None\n\
|
||||
\n\
|
||||
If a document is being viewed, return its body; otherwise return None.\n");
|
||||
|
||||
static PyObject *
|
||||
python_current_document(PyObject *self, PyObject *args)
|
||||
{
|
||||
if (python_ses && have_location(python_ses)) {
|
||||
struct cache_entry *cached = find_in_cache(cur_loc(python_ses)->vs.uri);
|
||||
struct fragment *f = cached ? cached->frag.next : NULL;
|
||||
|
||||
if (f) return PyString_FromStringAndSize(f->data, f->length);
|
||||
}
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/* Python interface to get the current document's header. */
|
||||
|
||||
static char python_current_header_doc[] =
|
||||
PYTHON_DOCSTRING("current_header() -> string or None\n\
|
||||
\n\
|
||||
If a document is being viewed and it has a header, return the header;\n\
|
||||
otherwise return None.\n");
|
||||
|
||||
static PyObject *
|
||||
python_current_header(PyObject *self, PyObject *args)
|
||||
{
|
||||
if (python_ses && have_location(python_ses)) {
|
||||
struct cache_entry *cached = find_in_cache(cur_loc(python_ses)->vs.uri);
|
||||
|
||||
if (cached && cached->head)
|
||||
return PyString_FromString(cached->head);
|
||||
}
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/* Python interface to get the currently-selected link's URL. */
|
||||
|
||||
static char python_current_link_url_doc[] =
|
||||
PYTHON_DOCSTRING("current_link_url() -> string or None\n\
|
||||
\n\
|
||||
If a link is selected, return its URL; otherwise return None.\n");
|
||||
|
||||
static PyObject *
|
||||
python_current_link_url(PyObject *self, PyObject *args)
|
||||
{
|
||||
unsigned char url[MAX_STR_LEN];
|
||||
|
||||
if (python_ses && get_current_link_url(python_ses, url, MAX_STR_LEN))
|
||||
return PyString_FromString(url);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/* Python interface to get the current document's title. */
|
||||
|
||||
static char python_current_title_doc[] =
|
||||
PYTHON_DOCSTRING("current_title() -> string or None\n\
|
||||
\n\
|
||||
If a document is being viewed, return its title; otherwise return None.\n");
|
||||
|
||||
static PyObject *
|
||||
python_current_title(PyObject *self, PyObject *args)
|
||||
{
|
||||
unsigned char title[MAX_STR_LEN];
|
||||
|
||||
if (python_ses && get_current_title(python_ses, title, MAX_STR_LEN))
|
||||
return PyString_FromString(title);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/* Python interface to get the current document's URL. */
|
||||
|
||||
static char python_current_url_doc[] =
|
||||
PYTHON_DOCSTRING("current_url() -> string or None\n\
|
||||
\n\
|
||||
If a document is being viewed, return its URL; otherwise return None.\n");
|
||||
|
||||
static PyObject *
|
||||
python_current_url(PyObject *self, PyObject *args)
|
||||
{
|
||||
unsigned char url[MAX_STR_LEN];
|
||||
|
||||
if (python_ses && get_current_url(python_ses, url, MAX_STR_LEN))
|
||||
return PyString_FromString(url);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyMethodDef document_methods[] = {
|
||||
{"current_document", python_current_document,
|
||||
METH_NOARGS,
|
||||
python_current_document_doc},
|
||||
|
||||
{"current_header", python_current_header,
|
||||
METH_NOARGS,
|
||||
python_current_header_doc},
|
||||
|
||||
{"current_link_url", python_current_link_url,
|
||||
METH_NOARGS,
|
||||
python_current_link_url_doc},
|
||||
|
||||
{"current_title", python_current_title,
|
||||
METH_NOARGS,
|
||||
python_current_title_doc},
|
||||
|
||||
{"current_url", python_current_url,
|
||||
METH_NOARGS,
|
||||
python_current_url_doc},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
int
|
||||
python_init_document_interface(PyObject *dict, PyObject *name)
|
||||
{
|
||||
return add_python_methods(dict, name, document_methods);
|
||||
}
|
8
src/scripting/python/document.h
Normal file
8
src/scripting/python/document.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_DOCUMENT_H
|
||||
#define EL__SCRIPTING_PYTHON_DOCUMENT_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int python_init_document_interface(PyObject *dict, PyObject *name);
|
||||
|
||||
#endif
|
198
src/scripting/python/keybinding.c
Normal file
198
src/scripting/python/keybinding.c
Normal file
@ -0,0 +1,198 @@
|
||||
/* Keystroke bindings for Python. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "config/kbdbind.h"
|
||||
#include "intl/gettext/libintl.h"
|
||||
#include "main/event.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "session/session.h"
|
||||
#include "util/error.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static PyObject *keybindings = NULL;
|
||||
|
||||
/* C wrapper that invokes Python callbacks for bind_key_to_event_name(). */
|
||||
|
||||
static enum evhook_status
|
||||
invoke_keybinding_callback(va_list ap, void *data)
|
||||
{
|
||||
PyObject *callback = data;
|
||||
struct session *saved_python_ses = python_ses;
|
||||
PyObject *result;
|
||||
|
||||
python_ses = va_arg(ap, struct session *);
|
||||
|
||||
result = PyObject_CallFunction(callback, NULL);
|
||||
if (result)
|
||||
Py_DECREF(result);
|
||||
else
|
||||
alert_python_error();
|
||||
|
||||
python_ses = saved_python_ses;
|
||||
|
||||
return EVENT_HOOK_STATUS_NEXT;
|
||||
}
|
||||
|
||||
/* Check that a keymap name is valid. */
|
||||
|
||||
static int
|
||||
keymap_is_valid(const unsigned char *keymap)
|
||||
{
|
||||
enum keymap_id keymap_id;
|
||||
|
||||
for (keymap_id = 0; keymap_id < KEYMAP_MAX; ++keymap_id)
|
||||
if (!strcmp(keymap, get_keymap_name(keymap_id)))
|
||||
break;
|
||||
return (keymap_id != KEYMAP_MAX);
|
||||
}
|
||||
|
||||
/* Python interface for binding keystrokes to callable objects. */
|
||||
|
||||
static char python_bind_key_doc[] =
|
||||
PYTHON_DOCSTRING("bind_key(keystroke, callback[, keymap]) -> None\n\
|
||||
\n\
|
||||
Bind a keystroke to a callable object.\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
keystroke -- A string containing a keystroke. The syntax for\n\
|
||||
keystrokes is described in the elinkskeys(5) man page.\n\
|
||||
callback -- A callable object to be called when the keystroke is\n\
|
||||
typed. It will be called without any arguments.\n\
|
||||
\n\
|
||||
Optional arguments:\n\
|
||||
\n\
|
||||
keymap -- A string containing the name of a keymap. Valid keymap\n\
|
||||
names can be found in the elinkskeys(5) man page. By\n\
|
||||
default the \"main\" keymap is used.\n");
|
||||
|
||||
static PyObject *
|
||||
python_bind_key(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
const unsigned char *keystroke;
|
||||
PyObject *callback;
|
||||
unsigned char *keymap = "main";
|
||||
PyObject *key_tuple;
|
||||
PyObject *old_callback;
|
||||
struct string event_name;
|
||||
int event_id;
|
||||
unsigned char *error_msg;
|
||||
static char *kwlist[] = {"keystroke", "callback", "keymap", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|s:bind_key", kwlist,
|
||||
&keystroke, &callback, &keymap))
|
||||
return NULL;
|
||||
|
||||
assert(keystroke && callback && keymap);
|
||||
if_assert_failed {
|
||||
PyErr_SetString(python_elinks_err, N_("Internal error"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!keymap_is_valid(keymap)) {
|
||||
PyErr_Format(python_elinks_err, "%s \"%s\"",
|
||||
N_("Unrecognised keymap"), keymap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The callback object needs to be kept alive for as long as the
|
||||
* keystroke is bound, so we stash a reference to it in a dictionary.
|
||||
* We don't need to use the dictionary to find callbacks; its sole
|
||||
* purpose is to prevent these objects from being garbage-collected
|
||||
* by the Python interpreter.
|
||||
*
|
||||
* If binding the key fails for any reason after this point then
|
||||
* we'll need to restore the dictionary to its previous state, which
|
||||
* is temporarily preserved in @old_callback.
|
||||
*/
|
||||
key_tuple = Py_BuildValue("ss", keymap, keystroke);
|
||||
if (!key_tuple)
|
||||
return NULL;
|
||||
old_callback = PyDict_GetItem(keybindings, key_tuple);
|
||||
Py_XINCREF(old_callback);
|
||||
if (PyDict_SetItem(keybindings, key_tuple, callback) != 0) {
|
||||
Py_DECREF(key_tuple);
|
||||
Py_XDECREF(old_callback);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!init_string(&event_name)) {
|
||||
PyErr_NoMemory();
|
||||
goto rollback;
|
||||
}
|
||||
if (!add_format_to_string(&event_name, "python-func %p", callback)) {
|
||||
PyErr_SetFromErrno(python_elinks_err);
|
||||
done_string(&event_name);
|
||||
goto rollback;
|
||||
}
|
||||
event_id = bind_key_to_event_name(keymap, keystroke, event_name.source,
|
||||
&error_msg);
|
||||
done_string(&event_name);
|
||||
if (error_msg) {
|
||||
PyErr_SetString(python_elinks_err, error_msg);
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
event_id = register_event_hook(event_id, invoke_keybinding_callback, 0,
|
||||
callback);
|
||||
if (event_id == EVENT_NONE) {
|
||||
PyErr_SetString(python_elinks_err,
|
||||
N_("Error registering event hook"));
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
Py_DECREF(key_tuple);
|
||||
Py_XDECREF(old_callback);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
|
||||
rollback:
|
||||
/*
|
||||
* If an error occurred, try to restore the keybindings dictionary
|
||||
* to its previous state.
|
||||
*/
|
||||
if (old_callback) {
|
||||
(void) PyDict_SetItem(keybindings, key_tuple, old_callback);
|
||||
Py_DECREF(old_callback);
|
||||
} else {
|
||||
(void) PyDict_DelItem(keybindings, key_tuple);
|
||||
}
|
||||
|
||||
Py_DECREF(key_tuple);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyMethodDef keybinding_methods[] = {
|
||||
{"bind_key", (PyCFunction) python_bind_key,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
python_bind_key_doc},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
int
|
||||
python_init_keybinding_interface(PyObject *dict, PyObject *name)
|
||||
{
|
||||
keybindings = PyDict_New();
|
||||
if (!keybindings) return -1;
|
||||
|
||||
return add_python_methods(dict, name, keybinding_methods);
|
||||
}
|
||||
|
||||
void
|
||||
python_done_keybinding_interface(void)
|
||||
{
|
||||
Py_XDECREF(keybindings);
|
||||
}
|
9
src/scripting/python/keybinding.h
Normal file
9
src/scripting/python/keybinding.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_KEYBINDING_H
|
||||
#define EL__SCRIPTING_PYTHON_KEYBINDING_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int python_init_keybinding_interface(PyObject *dict, PyObject *name);
|
||||
void python_done_keybinding_interface(void);
|
||||
|
||||
#endif
|
167
src/scripting/python/load.c
Normal file
167
src/scripting/python/load.c
Normal file
@ -0,0 +1,167 @@
|
||||
/* Document loading for Python. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "cache/cache.h"
|
||||
#include "intl/gettext/libintl.h"
|
||||
#include "network/connection.h"
|
||||
#include "network/state.h"
|
||||
#include "protocol/uri.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "session/download.h"
|
||||
#include "session/session.h"
|
||||
#include "session/task.h"
|
||||
#include "util/error.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
struct python_load_uri_callback_hop {
|
||||
struct session *ses;
|
||||
PyObject *callback;
|
||||
};
|
||||
|
||||
/* C wrapper that invokes Python callbacks for load_uri(). */
|
||||
|
||||
static void
|
||||
invoke_load_uri_callback(struct download *download, void *data)
|
||||
{
|
||||
struct python_load_uri_callback_hop *hop = data;
|
||||
struct session *saved_python_ses = python_ses;
|
||||
|
||||
assert(download);
|
||||
if_assert_failed {
|
||||
if (hop && hop->callback) {
|
||||
Py_DECREF(hop->callback);
|
||||
}
|
||||
mem_free_if(hop);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_in_progress_state(download->state)) return;
|
||||
|
||||
assert(hop && hop->callback);
|
||||
if_assert_failed {
|
||||
mem_free(download);
|
||||
mem_free_if(hop);
|
||||
return;
|
||||
}
|
||||
|
||||
if (download->cached) {
|
||||
PyObject *result;
|
||||
struct fragment *f = download->cached->frag.next;
|
||||
|
||||
python_ses = hop->ses;
|
||||
|
||||
result = PyObject_CallFunction(hop->callback, "ss#",
|
||||
download->cached->head,
|
||||
f ? f->data : NULL,
|
||||
f ? f->length : 0);
|
||||
if (result)
|
||||
Py_DECREF(result);
|
||||
else
|
||||
alert_python_error();
|
||||
}
|
||||
|
||||
Py_DECREF(hop->callback);
|
||||
mem_free(hop);
|
||||
mem_free(download);
|
||||
|
||||
python_ses = saved_python_ses;
|
||||
}
|
||||
|
||||
/* Python interface for loading a document. */
|
||||
|
||||
static char python_load_doc[] =
|
||||
PYTHON_DOCSTRING("load(url, callback) -> None\n\
|
||||
\n\
|
||||
Load a document into the ELinks cache and pass its contents to a\n\
|
||||
callable object.\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
url -- A string containing the URL to load.\n\
|
||||
callback -- A callable object to be called after the document has\n\
|
||||
been loaded. It will be called with two arguments: the first\n\
|
||||
will be a string representing the document's header, or None\n\
|
||||
if it has no header; the second will be a string representing\n\
|
||||
the document's body, or None if it has no body.\n");
|
||||
|
||||
static PyObject *
|
||||
python_load(PyObject *self, PyObject *args)
|
||||
{
|
||||
unsigned char *uristring;
|
||||
PyObject *callback;
|
||||
struct uri *uri;
|
||||
struct download *download;
|
||||
struct python_load_uri_callback_hop *hop;
|
||||
|
||||
if (!python_ses) {
|
||||
PyErr_SetString(python_elinks_err, "No session");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PyArg_ParseTuple(args, "sO:load", &uristring, &callback))
|
||||
return NULL;
|
||||
|
||||
assert(uristring && callback);
|
||||
if_assert_failed {
|
||||
PyErr_SetString(python_elinks_err, N_("Internal error"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uri = get_translated_uri(uristring, python_ses->tab->term->cwd);
|
||||
if (!uri) {
|
||||
PyErr_SetString(python_elinks_err, N_("Bad URL syntax"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
download = mem_alloc(sizeof(*download));
|
||||
if (!download) goto mem_error;
|
||||
|
||||
hop = mem_alloc(sizeof(*hop));
|
||||
if (!hop) goto free_download;
|
||||
hop->ses = python_ses;
|
||||
hop->callback = callback;
|
||||
Py_INCREF(callback);
|
||||
|
||||
download->data = hop;
|
||||
download->callback = (download_callback_T *) invoke_load_uri_callback;
|
||||
if (load_uri(uri, NULL, download, PRI_MAIN, CACHE_MODE_NORMAL, -1) != 0) {
|
||||
PyErr_SetString(python_elinks_err,
|
||||
get_state_message(download->state,
|
||||
python_ses->tab->term));
|
||||
done_uri(uri);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
done_uri(uri);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
|
||||
free_download:
|
||||
mem_free(download);
|
||||
|
||||
mem_error:
|
||||
done_uri(uri);
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef load_methods[] = {
|
||||
{"load", python_load,
|
||||
METH_VARARGS,
|
||||
python_load_doc},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
int
|
||||
python_init_load_interface(PyObject *dict, PyObject *name)
|
||||
{
|
||||
return add_python_methods(dict, name, load_methods);
|
||||
}
|
8
src/scripting/python/load.h
Normal file
8
src/scripting/python/load.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_LOAD_H
|
||||
#define EL__SCRIPTING_PYTHON_LOAD_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int python_init_load_interface(PyObject *dict, PyObject *name);
|
||||
|
||||
#endif
|
241
src/scripting/python/menu.c
Normal file
241
src/scripting/python/menu.c
Normal file
@ -0,0 +1,241 @@
|
||||
/* Simple menus for Python. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "bfu/menu.h"
|
||||
#include "document/document.h"
|
||||
#include "document/view.h"
|
||||
#include "intl/gettext/libintl.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "session/session.h"
|
||||
#include "terminal/window.h"
|
||||
#include "util/error.h"
|
||||
#include "util/memlist.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "viewer/text/view.h"
|
||||
|
||||
/* C wrapper that invokes Python callbacks for menu items. */
|
||||
|
||||
static void
|
||||
invoke_menu_callback(struct terminal *term, void *data, void *ses)
|
||||
{
|
||||
PyObject *callback = data;
|
||||
struct session *saved_python_ses = python_ses;
|
||||
PyObject *result;
|
||||
|
||||
python_ses = ses;
|
||||
|
||||
result = PyObject_CallFunction(callback, NULL);
|
||||
if (result)
|
||||
Py_DECREF(result);
|
||||
else
|
||||
alert_python_error();
|
||||
|
||||
Py_DECREF(callback);
|
||||
|
||||
python_ses = saved_python_ses;
|
||||
}
|
||||
|
||||
enum python_menu_type {
|
||||
PYTHON_MENU_DEFAULT,
|
||||
PYTHON_MENU_LINK,
|
||||
PYTHON_MENU_TAB,
|
||||
PYTHON_MENU_MAX
|
||||
};
|
||||
|
||||
/* Python interface for displaying simple menus. */
|
||||
|
||||
static char python_menu_doc[] =
|
||||
PYTHON_DOCSTRING("menu(items[, type]) -> None\n\
|
||||
\n\
|
||||
Display a menu.\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
items -- A sequence of tuples. Each tuple must have two elements: a\n\
|
||||
string containing the name of a menu item, and a callable\n\
|
||||
object that will be called without any arguments if the user\n\
|
||||
selects that menu item.\n\
|
||||
\n\
|
||||
Optional arguments:\n\
|
||||
\n\
|
||||
type -- A constant specifying the type of menu to display. By default\n\
|
||||
the menu is displayed at the top of the screen, but if this\n\
|
||||
argument's value is the constant elinks.MENU_TAB then the menu\n\
|
||||
is displayed in the same location as the ELinks tab menu. If\n\
|
||||
its value is the constant elinks.MENU_LINK then the menu is\n\
|
||||
displayed in the same location as the ELinks link menu and is\n\
|
||||
not displayed unless a link is currently selected.\n");
|
||||
|
||||
static PyObject *
|
||||
python_menu(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
PyObject *items;
|
||||
enum python_menu_type menu_type = PYTHON_MENU_DEFAULT;
|
||||
int length, i;
|
||||
struct menu_item *menu;
|
||||
struct memory_list *ml = NULL;
|
||||
static char *kwlist[] = {"items", "type", NULL};
|
||||
|
||||
if (!python_ses) {
|
||||
PyErr_SetString(python_elinks_err, "No session");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i:menu",
|
||||
kwlist, &items, &menu_type))
|
||||
return NULL;
|
||||
|
||||
assert(items);
|
||||
if_assert_failed {
|
||||
PyErr_SetString(python_elinks_err, N_("Internal error"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PySequence_Check(items)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Argument must be a sequence");
|
||||
return NULL;
|
||||
}
|
||||
length = PySequence_Length(items);
|
||||
if (length == -1) return NULL;
|
||||
else if (length == 0) goto success;
|
||||
|
||||
if (menu_type < 0 || menu_type >= PYTHON_MENU_MAX) {
|
||||
PyErr_Format(python_elinks_err, "%s %d",
|
||||
N_("Bad number"), menu_type);
|
||||
return NULL;
|
||||
|
||||
} else if (menu_type == PYTHON_MENU_LINK) {
|
||||
if (!get_current_link(current_frame(python_ses)))
|
||||
goto success;
|
||||
|
||||
} else if (menu_type == PYTHON_MENU_TAB
|
||||
&& python_ses->status.show_tabs_bar) {
|
||||
int y;
|
||||
|
||||
if (python_ses->status.show_tabs_bar_at_top)
|
||||
y = python_ses->status.show_title_bar;
|
||||
else
|
||||
y = python_ses->tab->term->height - length
|
||||
- python_ses->status.show_status_bar - 2;
|
||||
|
||||
set_window_ptr(python_ses->tab, python_ses->tab->xpos,
|
||||
int_max(y, 0));
|
||||
|
||||
} else {
|
||||
set_window_ptr(python_ses->tab, 0, 0);
|
||||
}
|
||||
|
||||
menu = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
|
||||
if (!menu) return PyErr_NoMemory();
|
||||
|
||||
/*
|
||||
* Keep track of all the memory we allocate so we'll be able to free
|
||||
* it in case any error prevents us from displaying the menu.
|
||||
*/
|
||||
ml = getml(menu, NULL);
|
||||
if (!ml) {
|
||||
mem_free(menu);
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
PyObject *tuple = PySequence_GetItem(items, i);
|
||||
PyObject *name, *callback;
|
||||
unsigned char *contents;
|
||||
|
||||
if (!tuple) goto error;
|
||||
|
||||
if (!PyTuple_Check(tuple)) {
|
||||
Py_DECREF(tuple);
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"Argument must be sequence of tuples");
|
||||
goto error;
|
||||
}
|
||||
name = PyTuple_GetItem(tuple, 0);
|
||||
callback = PyTuple_GetItem(tuple, 1);
|
||||
Py_DECREF(tuple);
|
||||
if (!name || !callback) goto error;
|
||||
|
||||
contents = (unsigned char *) PyString_AsString(name);
|
||||
if (!contents) goto error;
|
||||
|
||||
contents = stracpy(contents);
|
||||
if (!contents) {
|
||||
PyErr_NoMemory();
|
||||
goto error;
|
||||
}
|
||||
add_one_to_ml(&ml, contents);
|
||||
|
||||
/*
|
||||
* FIXME: We need to increment the reference counts for
|
||||
* callbacks so they won't be garbage-collected by the Python
|
||||
* interpreter before they're called. But for any callback
|
||||
* that isn't called (because the user doesn't select the
|
||||
* corresponding menu item) we'll never have an opportunity
|
||||
* to decrement the reference count again, so this code leaks
|
||||
* references. It probably can't be fixed without changes to
|
||||
* the menu machinery in bfu/menu.c, e.g. to call an arbitrary
|
||||
* clean-up function when a menu is destroyed.
|
||||
*
|
||||
* The good news is that in a typical usage case, where the
|
||||
* callback objects wouldn't be garbage-collected anyway until
|
||||
* the Python interpreter exits, this makes no difference at
|
||||
* all. But it's not strictly correct, and it could leak memory
|
||||
* in more elaborate usage where callback objects are created
|
||||
* and thrown away on the fly.
|
||||
*/
|
||||
Py_INCREF(callback);
|
||||
add_to_menu(&menu, contents, NULL, ACT_MAIN_NONE,
|
||||
invoke_menu_callback, callback, 0);
|
||||
}
|
||||
|
||||
do_menu(python_ses->tab->term, menu, python_ses, 1);
|
||||
|
||||
success:
|
||||
mem_free_if(ml);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
|
||||
error:
|
||||
freeml(ml);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyMethodDef menu_methods[] = {
|
||||
{"menu", (PyCFunction) python_menu,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
python_menu_doc},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static int
|
||||
add_constant(PyObject *dict, const char *key, int value)
|
||||
{
|
||||
PyObject *constant = PyInt_FromLong(value);
|
||||
int result;
|
||||
|
||||
if (!constant) return -1;
|
||||
result = PyDict_SetItemString(dict, key, constant);
|
||||
Py_DECREF(constant);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
python_init_menu_interface(PyObject *dict, PyObject *name)
|
||||
{
|
||||
if (add_constant(dict, "MENU_LINK", PYTHON_MENU_LINK) != 0) return -1;
|
||||
if (add_constant(dict, "MENU_TAB", PYTHON_MENU_TAB) != 0) return -1;
|
||||
|
||||
return add_python_methods(dict, name, menu_methods);
|
||||
}
|
8
src/scripting/python/menu.h
Normal file
8
src/scripting/python/menu.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_MENU_H
|
||||
#define EL__SCRIPTING_PYTHON_MENU_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int python_init_menu_interface(PyObject *dict, PyObject *name);
|
||||
|
||||
#endif
|
92
src/scripting/python/open.c
Normal file
92
src/scripting/python/open.c
Normal file
@ -0,0 +1,92 @@
|
||||
/* Document viewing for Python. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "elinks.h"
|
||||
|
||||
#include "intl/gettext/libintl.h"
|
||||
#include "protocol/uri.h"
|
||||
#include "scripting/python/core.h"
|
||||
#include "session/task.h"
|
||||
#include "terminal/tab.h"
|
||||
#include "util/error.h"
|
||||
|
||||
/* Python interface for viewing a document. */
|
||||
|
||||
static char python_open_doc[] =
|
||||
PYTHON_DOCSTRING("open(url, new_tab=False, background=False) -> None\n\
|
||||
\n\
|
||||
View a document in either the current tab or a new tab.\n\
|
||||
\n\
|
||||
Arguments:\n\
|
||||
\n\
|
||||
url -- A string containing the URL to view.\n\
|
||||
\n\
|
||||
Optional keyword arguments:\n\
|
||||
\n\
|
||||
new_tab -- By default the URL is opened in the current tab. If this\n\
|
||||
argument's value is the boolean True then the URL is instead\n\
|
||||
opened in a new tab.\n\
|
||||
background -- By default a new tab is opened in the foreground. If\n\
|
||||
this argument's value is the boolean True then a new tab is\n\
|
||||
instead opened in the background. This argument is ignored\n\
|
||||
unless new_tab's value is True.\n");
|
||||
|
||||
static PyObject *
|
||||
python_open(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
unsigned char *url;
|
||||
int new_tab = 0, background = 0;
|
||||
struct uri *uri;
|
||||
static char *kwlist[] = {"url", "new_tab", "background", NULL};
|
||||
|
||||
if (!python_ses) {
|
||||
PyErr_SetString(python_elinks_err, "No session");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ii:open",
|
||||
kwlist, &url,
|
||||
&new_tab, &background))
|
||||
return NULL;
|
||||
|
||||
assert(url);
|
||||
if_assert_failed {
|
||||
PyErr_SetString(python_elinks_err, N_("Internal error"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uri = get_translated_uri(url, python_ses->tab->term->cwd);
|
||||
if (!uri) {
|
||||
PyErr_SetString(python_elinks_err, N_("Bad URL syntax"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (new_tab)
|
||||
open_uri_in_new_tab(python_ses, uri, background, 0);
|
||||
else
|
||||
goto_uri(python_ses, uri);
|
||||
|
||||
done_uri(uri);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
static PyMethodDef open_methods[] = {
|
||||
{"open", (PyCFunction) python_open,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
python_open_doc},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
int
|
||||
python_init_open_interface(PyObject *dict, PyObject *name)
|
||||
{
|
||||
return add_python_methods(dict, name, open_methods);
|
||||
}
|
8
src/scripting/python/open.h
Normal file
8
src/scripting/python/open.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef EL__SCRIPTING_PYTHON_OPEN_H
|
||||
#define EL__SCRIPTING_PYTHON_OPEN_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int python_init_open_interface(PyObject *dict, PyObject *name);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user