Skip to content

Instantly share code, notes, and snippets.

@felixbuenemann
Created June 16, 2013 04:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felixbuenemann/5790777 to your computer and use it in GitHub Desktop.
Save felixbuenemann/5790777 to your computer and use it in GitHub Desktop.
diff --git a/Doc/Makefile.in b/Doc/Makefile.in
index dacbd51..69b7b04 100644
--- a/Doc/Makefile.in
+++ b/Doc/Makefile.in
@@ -66,8 +66,8 @@ Zsh/mod_sched.yo Zsh/mod_socket.yo \
Zsh/mod_stat.yo Zsh/mod_system.yo Zsh/mod_tcp.yo \
Zsh/mod_termcap.yo Zsh/mod_terminfo.yo \
Zsh/mod_zftp.yo Zsh/mod_zle.yo Zsh/mod_zleparameter.yo \
-Zsh/mod_zprof.yo Zsh/mod_zpty.yo Zsh/mod_zselect.yo \
-Zsh/mod_zutil.yo
+Zsh/mod_zprof.yo Zsh/mod_zpty.yo Zsh/mod_zpython.yo \
+Zsh/mod_zselect.yo Zsh/mod_zutil.yo
YODLSRC = zmacros.yo zman.yo ztexi.yo Zsh/arith.yo Zsh/builtins.yo \
Zsh/calsys.yo \
diff --git a/Doc/Zsh/mod_zpython.yo b/Doc/Zsh/mod_zpython.yo
new file mode 100644
index 0000000..29cf47e
--- /dev/null
+++ b/Doc/Zsh/mod_zpython.yo
@@ -0,0 +1,98 @@
+COMMENT(!MOD!zsh/parameter
+Python bindings for zsh
+!MOD!)
+cindex(zpython, special)
+The tt(zsh/zpython) module provides zpython builtin that runs python2 code
+from zsh command-line and zsh python module that is able to define special
+parameters.
+
+Note: by "string" it is meant either tt(str) or tt(unicode) object LPAR()python
+2 RPAR() or tt(bytes) object. Any strings received from zsh are converted to
+tt(str) LPAR()python 2 RPAR() or tt(bytes) LPAR()python 3 RPAR(): zsh strings
+can hold arbitrary data, not only valid unicode, thus tt(str) objects is the
+wrong choice on python 3.
+
+startitem()
+findex(zpython)
+item(tt(zpython) var(code))(
+Execute python code that is listed in var(code). Code is executed as if it were
+in python file.
+)
+cindex(python module, zsh)
+cindex(zsh python module)
+pindex(zsh.eval)
+item(tt(zsh.eval))(
+Evaluate zsh code without launching subshell. Output is not captured.
+)
+pindex(zsh.last_exit_code)
+item(tt(zsh.last_exit_code))(
+Returns the integer containing exit code of last launched command.
+)
+pindex(zsh.pipestatus)
+item(tt(zsh.pipestatus))(
+Returns the list of integers containing exit codes of all commands in last pipeline.
+)
+pindex(zsh.columns)
+item(tt(zsh.columns))(
+Returns the number of columns for this terminal session.
+)
+pindex(zsh.lines)
+item(tt(zsh.lines))(
+Returns the number of lines for this terminal session.
+)
+pindex(zsh.subshell)
+item(tt(zsh.subshell))(
+Returns subshell recursion depth.
+)
+pindex(zsh.getvalue)
+item(tt(zsh.getvalue)LPAR()var(param)RPAR())(
+Returns parameter value. Returned types: str for scalars, long integers for
+integers, float for floating-point integers, list of str for arrays and
+dict with str keys and values for associative arrays.
+)
+pindex(zsh.setvalue)
+item(tt(zsh.setvalue)LPAR()var(param), var(value)RPAR())(
+Set parameter value. Supported types: str, long, int, dict and anything
+implementing sequence protocol.
+)
+pindex(zsh.set_special)
+pindex(zsh.set_special_string)
+item(tt(zsh.set_special_string)LPAR()var(param), var(value)RPAR())(
+Bind object var(value) to parameter named var(param). When this parameter is
+accessed from zsh it returns result from __str__ object method. When parameter
+is set in zsh __call__ object method is called. If __call__ method is absent
+then variable is considered to be read-only.
+)
+pindex(zsh.set_special_integer)
+item(tt(zsh.set_special_float)LPAR()var(param), var(value)RPAR())(
+Bind object var(value) to parameter named var(param). When this parameter is
+accessed from zsh it returns result from coercing object to long integer. When
+parameter is set in zsh __call__ object method is called receiving a long value.
+If __call__ method is absent then variable is considered to be read-only.
+)
+pindex(zsh.set_special_float)
+item(tt(zsh.set_special_float)LPAR()var(param), var(value)RPAR())(
+Bind object var(value) to parameter named var(param). When this parameter is
+accessed from zsh it returns result from coercing object to float. When
+parameter is set in zsh __call__ object method is called receiving a float
+value. If __call__ method is absent then variable is considered to be read-only.
+)
+pindex(zsh.set_special_array)
+item(tt(zsh.set_special_array)LPAR()var(param), var(value)RPAR())(
+Bind object var(value) to parameter named var(param). Parameter must implement
+sequence protocol, each item in sequence must have str type. When parameter is
+accessed in zsh sequence is completely converted to zsh array, no matter how
+many values are actually needed. When parameter is set in zsh __call__ object
+method is called receiving a list of str. If __call__ method is absent then
+variable is considered to be read-only.
+)
+pindex(zsh.set_special_hash)
+item(tt(zsh.set_special_array)LPAR()var(param), var(value)RPAR())(
+Bind object var(value) to parameter named var(param). Parameter must implement
+mapping protocol, be iterable by keys, each key must be a string and each value
+object must be a string or coercible to it. Unlike other special parameters
+sequence protocol is used to assign items or the whole array, so no need to
+implement __call__ method. In case it is needed array is cleared by iterating
+over all keys and deleting them.
+)
+enditem()
diff --git a/Src/Modules/zpython.c b/Src/Modules/zpython.c
new file mode 100644
index 0000000..5f93dd1
--- /dev/null
+++ b/Src/Modules/zpython.c
@@ -0,0 +1,1846 @@
+#include "zpython.mdh"
+#include "zpython.pro"
+#include <Python.h>
+
+#if PY_MAJOR_VERSION >= 3
+# define PyString_Check PyBytes_Check
+# define PyString_FromString PyBytes_FromString
+# define PyString_FromStringAndSize PyBytes_FromStringAndSize
+# define PyString_AsStringAndSize PyBytes_AsStringAndSize
+#endif
+
+#define PYTHON_SAVE_THREAD PyGILState_Release(pygilstate)
+#define PYTHON_RESTORE_THREAD pygilstate = PyGILState_Ensure()
+
+struct specialparam {
+ char *name;
+ Param pm;
+ struct specialparam *next;
+ struct specialparam *prev;
+};
+
+struct special_data {
+ struct specialparam *sp;
+ PyObject *obj;
+};
+
+struct obj_hash_node {
+ HashNode next;
+ char *nam;
+ int flags;
+ PyObject *obj;
+ struct specialparam *sp;
+};
+
+static PyObject *globals;
+static zlong zpython_subshell;
+static PyObject *hashdict = NULL;
+static struct specialparam *first_assigned_param = NULL;
+static struct specialparam *last_assigned_param = NULL;
+static PyGILState_STATE pygilstate = PyGILState_UNLOCKED;
+
+
+static void
+after_fork()
+{
+ zpython_subshell = zsh_subshell;
+ hashdict = NULL;
+ PyOS_AfterFork();
+}
+
+#define PYTHON_INIT(failval) \
+ PYTHON_RESTORE_THREAD; \
+ \
+ if (zsh_subshell > zpython_subshell) { \
+ after_fork(); \
+ }
+
+#if PY_MAJOR_VERSION >= 3
+static void
+run_flush(PyObject *err)
+{
+ PyObject *flush, *ret;
+
+ if (!err)
+ return;
+
+ if (!(flush = PyObject_GetAttrString(err, "flush"))) {
+ PyErr_Clear();
+ return;
+ }
+
+ if (!(ret = PyObject_CallObject(flush, NULL))) {
+ PyErr_Clear();
+ Py_DECREF(flush);
+ return;
+ }
+
+ Py_DECREF(ret);
+ Py_DECREF(flush);
+}
+#endif
+
+static void
+flush_io()
+{
+#if PY_MAJOR_VERSION >= 3
+ run_flush(PySys_GetObject("stderr"));
+ run_flush(PySys_GetObject("stdout"));
+#else
+ fflush(stderr);
+ fflush(stdout);
+#endif
+}
+
+#define PYTHON_FINISH \
+ flush_io(); \
+ PYTHON_SAVE_THREAD
+
+/**/
+static int
+do_zpython(char *nam, char **args, Options ops, int func)
+{
+ PyObject *result;
+ int exit_code = 0;
+
+ PYTHON_INIT(2);
+
+ result = PyRun_String(*args, Py_file_input, globals, globals);
+ if (result == NULL)
+ {
+ if (PyErr_Occurred()) {
+ PyErr_PrintEx(0);
+ exit_code = 1;
+ }
+ }
+ else
+ Py_DECREF(result);
+ PyErr_Clear();
+
+ PYTHON_FINISH;
+ return exit_code;
+}
+
+typedef void *(*Allocator) (size_t);
+
+static char *
+get_chars(PyObject *string, Allocator alloc)
+{
+ char *str, *buf, *bufstart;
+ Py_ssize_t len = 0;
+ Py_ssize_t i = 0;
+ Py_ssize_t buflen = 1;
+
+ if (PyString_Check(string)) {
+ if (PyString_AsStringAndSize(string, &str, &len) == -1)
+ return NULL;
+ }
+ else {
+#if defined(PY_VERSION_HEX) && PY_VERSION_HEX >= 0x03030000
+ if (!(str = PyUnicode_AsUTF8AndSize(string, &len)))
+ return NULL;
+#else
+ PyObject *bytes;
+ if (!(bytes = PyUnicode_AsUTF8String(string)))
+ return NULL;
+
+ if (PyString_AsStringAndSize(bytes, &str, &len) == -1) {
+ Py_DECREF(bytes);
+ return NULL;
+ }
+ Py_DECREF(bytes);
+#endif
+ }
+
+ while (i < len)
+ buflen += 1 + (imeta(str[i++]) ? 1 : 0);
+
+ buf = alloc(buflen * sizeof(char));
+ bufstart = buf;
+
+ while (len) {
+ if (imeta(*str)) {
+ *buf++ = Meta;
+ *buf++ = *str ^ 32;
+ }
+ else
+ *buf++ = *str;
+ str++;
+ len--;
+ }
+ *buf = '\0';
+
+ return bufstart;
+}
+
+static PyObject *
+ZshEval(UNUSED(PyObject *self), PyObject *obj)
+{
+ char *command;
+
+ if (!(command = get_chars(obj, PyMem_Malloc)))
+ return NULL;
+
+ execstring(command, 1, 0, "zpython");
+
+ PyMem_Free(command);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+get_string(const char *s)
+{
+ char *buf, *bufstart;
+ PyObject *r;
+ /* No need in \0 byte at the end since we are using
+ * PyString_FromStringAndSize */
+ if (!(buf = PyMem_New(char, strlen(s))))
+ return NULL;
+ bufstart = buf;
+ while (*s) {
+ *buf++ = (*s == Meta) ? (*++s ^ 32) : (*s);
+ ++s;
+ }
+ r = PyString_FromStringAndSize(bufstart, (Py_ssize_t) (buf - bufstart));
+ PyMem_Free(bufstart);
+ return r;
+}
+
+static void
+scanhashdict(HashNode hn, UNUSED(int flags))
+{
+ struct value v;
+ PyObject *key, *val;
+
+ if (hashdict == NULL)
+ return;
+
+ v.pm = (Param) hn;
+
+ if (!(key = get_string(v.pm->node.nam))) {
+ hashdict = NULL;
+ return;
+ }
+
+ v.isarr = (PM_TYPE(v.pm->node.flags) & (PM_ARRAY|PM_HASHED));
+ v.flags = 0;
+ v.start = 0;
+ v.end = -1;
+ if (!(val = get_string(getstrvalue(&v)))) {
+ hashdict = NULL;
+ Py_DECREF(key);
+ return;
+ }
+
+ if (PyDict_SetItem(hashdict, key, val) == -1)
+ hashdict = NULL;
+
+ Py_DECREF(key);
+ Py_DECREF(val);
+}
+
+static PyObject *
+get_array(char **ss)
+{
+ PyObject *r = PyList_New(arrlen(ss));
+ size_t i = 0;
+ while (*ss) {
+ PyObject *str;
+ if (!(str = get_string(*ss++))) {
+ Py_DECREF(r);
+ return NULL;
+ }
+ if (PyList_SetItem(r, i++, str) == -1) {
+ Py_DECREF(r);
+ return NULL;
+ }
+ }
+ return r;
+}
+
+static PyObject *
+get_hash(HashTable ht)
+{
+ PyObject *hd;
+
+ if (hashdict) {
+ PyErr_SetString(PyExc_RuntimeError, "hashdict already used. "
+ "Do not try to get two hashes simultaneously in "
+ "separate threads, zsh is not thread-safe");
+ return NULL;
+ }
+
+ hashdict = PyDict_New();
+ hd = hashdict;
+
+ scanhashtable(ht, 0, 0, 0, scanhashdict, 0);
+ if (hashdict == NULL) {
+ Py_DECREF(hd);
+ return NULL;
+ }
+
+ hashdict = NULL;
+ return hd;
+}
+
+static PyObject *
+ZshGetValue(UNUSED(PyObject *self), PyObject *args)
+{
+ char *name;
+ struct value vbuf;
+ Value v;
+
+ if (!PyArg_ParseTuple(args, "s", &name))
+ return NULL;
+
+ if (!isident(name)) {
+ PyErr_SetString(PyExc_KeyError, "Parameter name is not an identifier");
+ return NULL;
+ }
+
+ if (!(v = getvalue(&vbuf, &name, 1))) {
+ PyErr_SetString(PyExc_IndexError, "Failed to find parameter");
+ return NULL;
+ }
+
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_HASHED:
+ return get_hash(v->pm->gsu.h->getfn(v->pm));
+ case PM_ARRAY:
+ v->arr = v->pm->gsu.a->getfn(v->pm);
+ if (v->isarr) {
+ return get_array(v->arr);
+ }
+ else {
+ char *s;
+ PyObject *str, *r;
+
+ if (v->start < 0)
+ v->start += arrlen(v->arr);
+ s = (v->start >= arrlen(v->arr) || v->start < 0) ?
+ (char *) "" : v->arr[v->start];
+ if (!(str = get_string(s)))
+ return NULL;
+ r = PyList_New(1);
+ if (PyList_SetItem(r, 0, str) == -1) {
+ Py_DECREF(r);
+ return NULL;
+ }
+ return r;
+ }
+ case PM_INTEGER:
+ return PyLong_FromLong((long) v->pm->gsu.i->getfn(v->pm));
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ return PyFloat_FromDouble(v->pm->gsu.f->getfn(v->pm));
+ case PM_SCALAR:
+ return get_string(v->pm->gsu.s->getfn(v->pm));
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "Parameter has unknown type; should not happen.");
+ return NULL;
+ }
+}
+
+#define FAIL_SETTING_ARRAY \
+ while (val-- > valstart) \
+ zsfree(*val); \
+ zfree(valstart, arrlen); \
+ return NULL
+
+#define IS_PY_STRING(s) (PyString_Check(s) || PyUnicode_Check(s))
+
+static char **
+get_chars_array(PyObject *seq, Allocator alloc)
+{
+ char **val, **valstart;
+ Py_ssize_t len = PySequence_Size(seq);
+ Py_ssize_t arrlen;
+ Py_ssize_t i = 0;
+
+ if (len == -1) {
+ PyErr_SetString(PyExc_ValueError, "Failed to get sequence size");
+ return NULL;
+ }
+
+ arrlen = (len + 1) * sizeof(char *);
+ val = (char **) alloc(arrlen);
+ valstart = val;
+
+ while (i < len) {
+ PyObject *item = PySequence_GetItem(seq, i);
+
+ if (!IS_PY_STRING(item)) {
+ PyErr_SetString(PyExc_TypeError, "Sequence item is not a string");
+ FAIL_SETTING_ARRAY;
+ }
+
+ if (!(*val++ = get_chars(item, alloc))) {
+ FAIL_SETTING_ARRAY;
+ }
+ i++;
+ }
+ *val = NULL;
+
+ return valstart;
+}
+
+static PyObject *
+ZshSetValue(UNUSED(PyObject *self), PyObject *args)
+{
+ char *name;
+ PyObject *value;
+
+ if (!PyArg_ParseTuple(args, "sO", &name, &value))
+ return NULL;
+
+ if (!isident(name)) {
+ PyErr_SetString(PyExc_KeyError, "Parameter name is not an identifier");
+ return NULL;
+ }
+
+ if (IS_PY_STRING(value)) {
+ char *s;
+
+ if (!(s = get_chars(value, zalloc)))
+ return NULL;
+
+ if (!setsparam(name, s)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Failed to assign string to the parameter");
+ zsfree(s);
+ return NULL;
+ }
+ }
+#if PY_MAJOR_VERSION < 3
+ else if (PyInt_Check(value)) {
+ if (!setiparam(name, (zlong) PyInt_AsLong(value))) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Failed to assign integer parameter");
+ return NULL;
+ }
+ }
+#endif
+ else if (PyLong_Check(value)) {
+ if (!setiparam(name, (zlong) PyLong_AsLong(value))) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Failed to assign long parameter");
+ return NULL;
+ }
+ }
+ else if (PyFloat_Check(value)) {
+ mnumber mnval;
+ mnval.type = MN_FLOAT;
+ mnval.u.d = PyFloat_AsDouble(value);
+ if (!setnparam(name, mnval)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Failed to assign float parameter");
+ return NULL;
+ }
+ }
+ else if (PyDict_Check(value)) {
+ char **val, **valstart;
+ PyObject *pkey, *pval;
+ Py_ssize_t arrlen, pos = 0;
+
+ arrlen = (2 * PyDict_Size(value) + 1) * sizeof(char *);
+ val = (char **) zalloc(arrlen);
+ valstart = val;
+
+ while (PyDict_Next(value, &pos, &pkey, &pval)) {
+ if (!IS_PY_STRING(pkey)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Only string keys are allowed");
+ FAIL_SETTING_ARRAY;
+ }
+ if (!IS_PY_STRING(pval)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Only string values are allowed");
+ FAIL_SETTING_ARRAY;
+ }
+
+ if (!(*val++ = get_chars(pkey, zalloc))) {
+ FAIL_SETTING_ARRAY;
+ }
+ if (!(*val++ = get_chars(pval, zalloc))) {
+ FAIL_SETTING_ARRAY;
+ }
+ }
+ *val = NULL;
+
+ if (!sethparam(name, valstart)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to set hash");
+ return NULL;
+ }
+ }
+ /* Python's list have no faster shortcut methods like PyDict_Next above
+ * thus using more abstract protocol */
+ else if (PySequence_Check(value)) {
+ char **ss = get_chars_array(value, zalloc);
+
+ if (!ss)
+ return NULL;
+
+ if (!setaparam(name, ss)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to set array");
+ return NULL;
+ }
+ }
+ else if (value == Py_None) {
+ unsetparam(name);
+ if (errflag) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to delete parameter");
+ return NULL;
+ }
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "Cannot assign value of the given type");
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+ZshExitCode(UNUSED(PyObject *self), UNUSED(PyObject *args))
+{
+ return PyLong_FromLong((long) lastval);
+}
+
+static PyObject *
+ZshColumns(UNUSED(PyObject *self), UNUSED(PyObject *args))
+{
+ return PyLong_FromLong((long) zterm_columns);
+}
+
+static PyObject *
+ZshLines(UNUSED(PyObject *self), UNUSED(PyObject *args))
+{
+ return PyLong_FromLong((long) zterm_lines);
+}
+
+static PyObject *
+ZshSubshell(UNUSED(PyObject *self), UNUSED(PyObject *args))
+{
+ return PyLong_FromLong((long) zsh_subshell);
+}
+
+static PyObject *
+ZshPipeStatus(UNUSED(PyObject *self), UNUSED(PyObject *args))
+{
+ size_t i = 0;
+ PyObject *r = PyList_New(numpipestats);
+ PyObject *num;
+
+ while (i < numpipestats) {
+ if (!(num = PyLong_FromLong(pipestats[i]))) {
+ Py_DECREF(r);
+ return NULL;
+ }
+ if (PyList_SetItem(r, i, num) == -1) {
+ Py_DECREF(r);
+ return NULL;
+ }
+ i++;
+ }
+ return r;
+}
+
+static void
+free_sp(struct specialparam *sp)
+{
+ if (sp->prev)
+ sp->prev->next = sp->next;
+ else
+ first_assigned_param = sp->next;
+
+ if (sp->next)
+ sp->next->prev = sp->prev;
+ else
+ last_assigned_param = sp->prev;
+
+ zsfree(sp->name);
+ PyMem_Free(sp);
+}
+
+static void
+unset_special_parameter(struct special_data *data)
+{
+ Py_DECREF(data->obj);
+ free_sp(data->sp);
+ PyMem_Free(data);
+}
+
+
+#define ZFAIL(errargs, failval) \
+ PyErr_PrintEx(0); \
+ PYTHON_FINISH; \
+ zerr errargs; \
+ return failval
+
+#define ZFAIL_NOFINISH(errargs, failval) \
+ PyErr_PrintEx(0); \
+ flush_io(); \
+ zerr errargs; \
+ return failval
+
+struct sh_keyobj_data {
+ PyObject *obj;
+ PyObject *keyobj;
+};
+
+struct sh_key_data {
+ PyObject *obj;
+ char *key;
+};
+
+static char *
+get_sh_item_value(PyObject *obj, PyObject *keyobj)
+{
+ PyObject *valobj, *string;
+ char *str;
+
+ if (!(valobj = PyObject_GetItem(obj, keyobj))) {
+ if (PyErr_Occurred()) {
+ /* Expected result: key not found */
+ if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+ PyErr_Clear();
+ return dupstring("");
+ }
+ /* Unexpected result: unknown exception */
+ else
+ ZFAIL_NOFINISH(("Failed to get value object"), dupstring(""));
+ }
+ else
+ return dupstring("");
+ }
+
+ if (IS_PY_STRING(valobj))
+ string = valobj;
+ else {
+ if (!(string = PyObject_Str(valobj))) {
+ ZFAIL_NOFINISH(("Failed to get value string object"), dupstring(""));
+ }
+ Py_DECREF(valobj);
+ }
+
+ if (!(str = get_chars(string, zhalloc))) {
+ Py_DECREF(string);
+ ZFAIL_NOFINISH(("Failed to get string from value string object"),
+ dupstring(""));
+ }
+ Py_DECREF(string);
+ return str;
+}
+
+static char *
+get_sh_keyobj_value(Param pm)
+{
+ struct sh_keyobj_data *sh_kodata = (struct sh_keyobj_data *) pm->u.data;
+ return get_sh_item_value(sh_kodata->obj, sh_kodata->keyobj);
+}
+
+static char *
+get_sh_key_value(Param pm)
+{
+ char *r, *key;
+ PyObject *keyobj, *obj;
+ struct sh_key_data *sh_kdata = (struct sh_key_data *) pm->u.data;
+
+ PYTHON_INIT(dupstring(""));
+
+ obj = sh_kdata->obj;
+ key = sh_kdata->key;
+
+ if (!(keyobj = get_string(key))) {
+ ZFAIL(("Failed to create key string object for key \"%s\"", key),
+ dupstring(""));
+ }
+ r = get_sh_item_value(obj, keyobj);
+ Py_DECREF(keyobj);
+
+ PYTHON_FINISH;
+ return r;
+}
+
+static void
+set_sh_item_value(PyObject *obj, PyObject *keyobj, char *val)
+{
+ PyObject *valobj;
+
+ if (!(valobj = get_string(val))) {
+ ZFAIL_NOFINISH(("Failed to create value string object"), );
+ }
+
+ if (PyObject_SetItem(obj, keyobj, valobj) == -1) {
+ Py_DECREF(valobj);
+ ZFAIL_NOFINISH(("Failed to set object to \"%s\"", val), );
+ }
+ Py_DECREF(valobj);
+}
+
+static void
+set_sh_key_value(Param pm, char *val)
+{
+ PyObject *obj, *keyobj;
+ char *key;
+ struct sh_key_data *sh_kdata = (struct sh_key_data *) pm->u.data;
+
+ PYTHON_INIT();
+
+ obj = sh_kdata->obj;
+ key = sh_kdata->key;
+
+ if (!(keyobj = get_string(key))) {
+ ZFAIL(("Failed to create key string object for key \"%s\"", key), );
+ }
+ set_sh_item_value(obj, keyobj, val);
+ Py_DECREF(keyobj);
+
+ PYTHON_FINISH;
+}
+
+static void
+set_sh_keyobj_value(Param pm, char *val)
+{
+ struct sh_keyobj_data *sh_kodata = (struct sh_keyobj_data *) pm->u.data;
+ set_sh_item_value(sh_kodata->obj, sh_kodata->keyobj, val);
+}
+
+static struct gsu_scalar sh_keyobj_gsu =
+{get_sh_keyobj_value, set_sh_keyobj_value, nullunsetfn};
+static struct gsu_scalar sh_key_gsu =
+{get_sh_key_value, set_sh_key_value, nullunsetfn};
+
+static HashNode
+get_sh_item(HashTable ht, const char *key)
+{
+ PyObject *obj = ((struct obj_hash_node *) (*ht->nodes))->obj;
+ Param pm;
+ struct sh_key_data *sh_kdata;
+
+ PYTHON_INIT(NULL);
+
+ pm = (Param) hcalloc(sizeof(struct param));
+ pm->node.nam = dupstring(key);
+ pm->node.flags = PM_SCALAR;
+ pm->gsu.s = &sh_key_gsu;
+
+ sh_kdata = (struct sh_key_data *) hcalloc(sizeof(struct sh_key_data) * 1);
+ sh_kdata->obj = obj;
+ sh_kdata->key = dupstring(key);
+
+ pm->u.data = (void *) sh_kdata;
+
+ PYTHON_FINISH;
+
+ return &pm->node;
+}
+
+static void
+scan_special_hash(HashTable ht, ScanFunc func, int flags)
+{
+ PyObject *obj = ((struct obj_hash_node *) (*ht->nodes))->obj;
+ PyObject *iter, *keyobj;
+ struct param pm;
+
+ memset((void *) &pm, 0, sizeof(struct param));
+ pm.node.flags = PM_SCALAR;
+ pm.gsu.s = &sh_keyobj_gsu;
+
+ PYTHON_INIT();
+
+ if (!(iter = PyObject_GetIter(obj))) {
+ ZFAIL(("Failed to get iterator"), );
+ }
+
+ while ((keyobj = PyIter_Next(iter))) {
+ char *str;
+ struct sh_keyobj_data sh_kodata;
+
+ if (!IS_PY_STRING(keyobj)) {
+ Py_DECREF(iter);
+ Py_DECREF(keyobj);
+ ZFAIL(("Key is not a string"), );
+ }
+
+ if (!(str = get_chars(keyobj, zhalloc))) {
+ Py_DECREF(iter);
+ Py_DECREF(keyobj);
+ ZFAIL(("Failed to get string from key string object"), );
+ }
+ pm.node.nam = str;
+
+ sh_kodata.obj = obj;
+ sh_kodata.keyobj = keyobj;
+ pm.u.data = (void *) &sh_kodata;
+
+ func(&pm.node, flags);
+
+ Py_DECREF(keyobj);
+ }
+ Py_DECREF(iter);
+
+ PYTHON_FINISH;
+}
+
+static char *
+get_special_string(Param pm)
+{
+ PyObject *robj;
+ char *r;
+
+ PYTHON_INIT(dupstring(""));
+
+ if (!(robj = PyObject_Str(((struct special_data *) pm->u.data)->obj))) {
+ ZFAIL(("Failed to create string object for parameter %s",
+ pm->node.nam), dupstring(""));
+ }
+
+ if (!(r = get_chars(robj, zhalloc))) {
+ ZFAIL(("Failed to transform value for parameter %s", pm->node.nam),
+ dupstring(""));
+ }
+
+ Py_DECREF(robj);
+
+ PYTHON_FINISH;
+
+ return r;
+}
+
+static zlong
+get_special_integer(Param pm)
+{
+ PyObject *robj;
+ zlong r;
+
+ PYTHON_INIT(0);
+
+ if (!(robj = PyNumber_Long(((struct special_data *) pm->u.data)->obj))) {
+ ZFAIL(("Failed to create int object for parameter %s", pm->node.nam),
+ 0);
+ }
+
+ r = PyLong_AsLong(robj);
+
+ Py_DECREF(robj);
+
+ PYTHON_FINISH;
+
+ return r;
+}
+
+static double
+get_special_float(Param pm)
+{
+ PyObject *robj;
+ float r;
+
+ PYTHON_INIT(0.0);
+
+ if (!(robj = PyNumber_Float(((struct special_data *) pm->u.data)->obj))) {
+ ZFAIL(("Failed to create float object for parameter %s", pm->node.nam),
+ 0);
+ }
+
+ r = PyFloat_AsDouble(robj);
+
+ Py_DECREF(robj);
+
+ PYTHON_FINISH;
+
+ return r;
+}
+
+static char **
+get_special_array(Param pm)
+{
+ char **r;
+
+ PYTHON_INIT(hcalloc(sizeof(char **)));
+
+ if (!(r = get_chars_array(((struct special_data *) pm->u.data)->obj,
+ zhalloc))) {
+ ZFAIL(("Failed to create array object for parameter %s", pm->node.nam),
+ hcalloc(sizeof(char **)));
+ }
+
+ PYTHON_FINISH;
+
+ return r;
+}
+
+static void
+set_special_string(Param pm, char *val)
+{
+ PyObject *r, *args;
+
+ PYTHON_INIT();
+
+ if (!val) {
+ unset_special_parameter((struct special_data *) pm->u.data);
+
+ PYTHON_FINISH;
+ return;
+ }
+
+ args = Py_BuildValue("(O&)", get_string, val);
+ r = PyObject_CallObject(((struct special_data *) pm->u.data)->obj, args);
+ Py_DECREF(args);
+ if (!r) {
+ PyErr_PrintEx(0);
+ zerr("Failed to assign value for string parameter %s", pm->node.nam);
+ PYTHON_FINISH;
+ return;
+ }
+ Py_DECREF(r);
+
+ PYTHON_FINISH;
+}
+
+static void
+set_special_integer(Param pm, zlong val)
+{
+ PyObject *r, *args;
+
+ PYTHON_INIT();
+
+ args = Py_BuildValue("(L)", (long long) val);
+ r = PyObject_CallObject(((struct special_data *) pm->u.data)->obj, args);
+ Py_DECREF(args);
+ if (!r) {
+ PyErr_PrintEx(0);
+ zerr("Failed to assign value for integer parameter %s", pm->node.nam);
+ PYTHON_FINISH;
+ return;
+ }
+ Py_DECREF(r);
+
+ PYTHON_FINISH;
+}
+
+static void
+set_special_float(Param pm, double val)
+{
+ PyObject *r, *args;
+
+ PYTHON_INIT();
+
+ args = Py_BuildValue("(d)", val);
+ r = PyObject_CallObject(((struct special_data *) pm->u.data)->obj, args);
+ Py_DECREF(args);
+ if (!r) {
+ PyErr_PrintEx(0);
+ zerr("Failed to assign value for float parameter %s", pm->node.nam);
+ PYTHON_FINISH;
+ return;
+ }
+ Py_DECREF(r);
+
+ PYTHON_FINISH;
+}
+
+static void
+set_special_array(Param pm, char **val)
+{
+ PyObject *r, *args;
+
+ PYTHON_INIT();
+
+ if (!val) {
+ unset_special_parameter((struct special_data *) pm->u.data);
+
+ PYTHON_FINISH;
+ return;
+ }
+
+ args = Py_BuildValue("(O&)", get_array, val);
+ r = PyObject_CallObject(((struct special_data *) pm->u.data)->obj, args);
+ Py_DECREF(args);
+ if (!r) {
+ PyErr_PrintEx(0);
+ zerr("Failed to assign value for array parameter %s", pm->node.nam);
+ PYTHON_FINISH;
+ return;
+ }
+ Py_DECREF(r);
+
+ PYTHON_FINISH;
+}
+
+static void
+set_special_hash(Param pm, HashTable ht)
+{
+ int i;
+ HashNode hn;
+ PyObject *obj = ((struct obj_hash_node *) (*pm->u.hash->nodes))->obj;
+ PyObject *keys, *iter, *keyobj;
+
+ if (pm->u.hash == ht)
+ return;
+
+ PYTHON_INIT();
+
+ if (!ht) {
+ struct specialparam *sp =
+ ((struct obj_hash_node *) (*pm->u.hash->nodes))->sp;
+ free_sp(sp);
+ Py_DECREF(obj);
+ PYTHON_FINISH;
+ return;
+ }
+
+ /* Can't use PyObject_GetIter on the object itself: it fails if object is
+ * being modified */
+ if (!(keys = PyMapping_Keys(obj))) {
+ ZFAIL(("Failed to get object keys"), );
+ }
+ if (!(iter = PyObject_GetIter(keys))) {
+ ZFAIL(("Failed to get keys iterator"), );
+ }
+ while ((keyobj = PyIter_Next(iter))) {
+ if (PyMapping_DelItem(obj, keyobj) == -1) {
+ ZFAIL(("Failed to delete some key"), );
+ }
+ Py_DECREF(keyobj);
+ }
+ Py_DECREF(iter);
+ Py_DECREF(keys);
+
+ for (i = 0; i < ht->hsize; i++)
+ for (hn = ht->nodes[i]; hn; hn = hn->next) {
+ struct value v;
+ char *val;
+ PyObject *valobj, *keyobj;
+
+ v.isarr = v.flags = v.start = 0;
+ v.end = -1;
+ v.arr = NULL;
+ v.pm = (Param) hn;
+
+ val = getstrvalue(&v);
+ if (!val) {
+ ZFAIL(("Failed to get string value"), );
+ }
+
+ if (!(valobj = get_string(val))) {
+ ZFAIL(("Failed to convert value \"%s\" to string object "
+ "while processing key", val, hn->nam), );
+ }
+ if (!(keyobj = get_string(hn->nam))) {
+ Py_DECREF(valobj);
+ ZFAIL(("Failed to convert key \"%s\" to string object",
+ hn->nam), );
+ }
+
+ if (PyObject_SetItem(obj, keyobj, valobj) == -1) {
+ Py_DECREF(valobj);
+ Py_DECREF(keyobj);
+ ZFAIL(("Failed to set key %s", hn->nam), );
+ }
+
+ Py_DECREF(valobj);
+ Py_DECREF(keyobj);
+ }
+ PYTHON_FINISH;
+}
+
+static void
+unsetfn(Param pm, int exp)
+{
+ unset_special_parameter((struct special_data *) pm->u.data);
+ stdunsetfn(pm, exp);
+}
+
+static void
+free_sh_node(HashNode nodeptr)
+{
+ /* Param is obtained from get_sh_item */
+ Param pm = (Param) nodeptr;
+ struct sh_key_data *sh_kdata = (struct sh_key_data *) pm->u.data;
+ PyObject *keyobj;
+
+ PYTHON_INIT();
+
+ if (!(keyobj = get_string(sh_kdata->key))) {
+ ZFAIL(("While unsetting key %s of parameter %s failed to get "
+ "key string object", sh_kdata->key, pm->node.nam), );
+ }
+ if (PyMapping_DelItem(sh_kdata->obj, keyobj) == -1) {
+ Py_DECREF(keyobj);
+ ZFAIL(("Failed to delete key %s of parameter %s",
+ sh_kdata->key, pm->node.nam), );
+ }
+ Py_DECREF(keyobj);
+
+ PYTHON_FINISH;
+}
+
+static const struct gsu_scalar special_string_gsu =
+{get_special_string, set_special_string, stdunsetfn};
+static const struct gsu_integer special_integer_gsu =
+{get_special_integer, set_special_integer, unsetfn};
+static const struct gsu_float special_float_gsu =
+{get_special_float, set_special_float, unsetfn};
+static const struct gsu_array special_array_gsu =
+{get_special_array, set_special_array, stdunsetfn};
+static const struct gsu_hash special_hash_gsu =
+{hashgetfn, set_special_hash, stdunsetfn};
+
+static int
+check_special_name(char *name)
+{
+ /* Needing strncasecmp, but the one that ignores locale */
+ if (!( (name[0] == 'z' || name[0] == 'Z')
+ && (name[1] == 'p' || name[1] == 'P')
+ && (name[2] == 'y' || name[2] == 'Y')
+ && (name[3] == 't' || name[3] == 'T')
+ && (name[4] == 'h' || name[4] == 'H')
+ && (name[5] == 'o' || name[5] == 'O')
+ && (name[6] == 'n' || name[6] == 'N')
+ && (name[7] != '\0')
+ ) || !isident(name))
+ {
+ PyErr_SetString(PyExc_KeyError, "Invalid special identifier: "
+ "it must be a valid variable name starting with "
+ "\"zpython\" (ignoring case) and containing at least one more "
+ "character");
+ return 1;
+ }
+ return 0;
+}
+
+static PyObject *
+set_special_parameter(PyObject *args, int type)
+{
+ char *name;
+ PyObject *obj;
+ Param pm;
+ int flags = type;
+ struct special_data *data;
+ struct specialparam *sp;
+
+ if (!PyArg_ParseTuple(args, "sO", &name, &obj))
+ return NULL;
+
+ if (check_special_name(name))
+ return NULL;
+
+ switch (type) {
+ case PM_SCALAR:
+ break;
+ case PM_INTEGER:
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ if (!PyNumber_Check(obj)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Object must implement numeric protocol");
+ return NULL;
+ }
+ break;
+ case PM_ARRAY:
+ if (!PySequence_Check(obj)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Object must implement sequence protocol");
+ return NULL;
+ }
+ break;
+ case PM_HASHED:
+ if (!PyMapping_Check(obj)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Object must implement mapping protocol");
+ return NULL;
+ }
+ break;
+ }
+
+ if (type == PM_HASHED) {
+ if (!(pm = createspecialhash(name, get_sh_item,
+ scan_special_hash, flags))) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to create parameter");
+ return NULL;
+ }
+ }
+ else {
+ if (!(pm = createparam(name, flags))) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to create parameter");
+ return NULL;
+ }
+ }
+
+ sp = PyMem_New(struct specialparam, 1);
+ sp->prev = last_assigned_param;
+ if (last_assigned_param)
+ last_assigned_param->next = sp;
+ else
+ first_assigned_param = sp;
+ last_assigned_param = sp;
+ sp->next = NULL;
+ sp->name = ztrdup(name);
+ sp->pm = pm;
+
+ if (type != PM_HASHED) {
+ data = PyMem_New(struct special_data, 1);
+ data->sp = sp;
+ data->obj = obj;
+ Py_INCREF(obj);
+ pm->u.data = data;
+ }
+
+ pm->level = 0;
+
+ switch (type) {
+ case PM_SCALAR:
+ pm->gsu.s = &special_string_gsu;
+ break;
+ case PM_INTEGER:
+ pm->gsu.i = &special_integer_gsu;
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ pm->gsu.f = &special_float_gsu;
+ break;
+ case PM_ARRAY:
+ pm->gsu.a = &special_array_gsu;
+ break;
+ case PM_HASHED:
+ {
+ struct obj_hash_node *ohn;
+ HashTable ht = pm->u.hash;
+ ohn = PyMem_New(struct obj_hash_node, 1);
+ ohn->nam = ztrdup("obj");
+ ohn->obj = obj;
+ Py_INCREF(obj);
+ ohn->sp = sp;
+ zfree(ht->nodes, ht->hsize * sizeof(HashNode));
+ ht->nodes = (HashNode *) zshcalloc(1 * sizeof(HashNode));
+ ht->hsize = 1;
+ *ht->nodes = (struct hashnode *) ohn;
+ pm->gsu.h = &special_hash_gsu;
+ ht->freenode = free_sh_node;
+ break;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+ZshSetMagicString(UNUSED(PyObject *self), PyObject *args)
+{
+ return set_special_parameter(args, PM_SCALAR);
+}
+
+static PyObject *
+ZshSetMagicInteger(UNUSED(PyObject *self), PyObject *args)
+{
+ return set_special_parameter(args, PM_INTEGER);
+}
+
+static PyObject *
+ZshSetMagicFloat(UNUSED(PyObject *self), PyObject *args)
+{
+ return set_special_parameter(args, PM_EFLOAT);
+}
+
+static PyObject *
+ZshSetMagicArray(UNUSED(PyObject *self), PyObject *args)
+{
+ return set_special_parameter(args, PM_ARRAY);
+}
+
+static PyObject *
+ZshSetMagicHash(UNUSED(PyObject *self), PyObject *args)
+{
+ return set_special_parameter(args, PM_HASHED);
+}
+
+static struct PyMethodDef ZshMethods[] = {
+ {"eval", ZshEval, METH_O,
+ "Evaluate command in current shell context",},
+ {"last_exit_code", ZshExitCode, METH_NOARGS,
+ "Get last exit code. Returns an int"},
+ {"pipestatus", ZshPipeStatus, METH_NOARGS,
+ "Get last pipe status. Returns a list of int"},
+ {"columns", ZshColumns, METH_NOARGS,
+ "Get number of columns. Returns an int"},
+ {"lines", ZshLines, METH_NOARGS,
+ "Get number of lines. Returns an int"},
+ {"subshell", ZshSubshell, METH_NOARGS,
+ "Get subshell recursion depth. Returns an int"},
+ {"getvalue", ZshGetValue, METH_VARARGS,
+ "Get parameter value. Return types:\n"
+ " str for scalars\n"
+ " long for integer numbers\n"
+ " float for floating-point numbers\n"
+ " list [str] for array parameters\n"
+ " dict {str : str} for associative arrays\n"
+ "Throws KeyError if identifier is invalid,\n"
+ " IndexError if parameter was not found\n"
+ },
+ {"setvalue", ZshSetValue, METH_VARARGS,
+ "Set parameter value. Use None to unset. Supported objects and corresponding\n"
+ "zsh parameter types:\n"
+ " str sets string scalars\n"
+ " long or int sets integer numbers\n"
+ " float sets floating-point numbers. Output is in scientific notation\n"
+ " sequence of str sets array parameters (sequence = anything implementing\n"
+ " sequence protocol)\n"
+ " dict {str : str} sets hashes\n"
+ "Throws KeyError if identifier is invalid,\n"
+ " RuntimeError if zsh set?param/unsetparam function failed,\n"
+ " ValueError if sequence item or dictionary key or value are not str\n"
+ " or sequence size is not known."},
+ {"set_special_string", ZshSetMagicString, METH_VARARGS,
+ "Define scalar (string) parameter.\n"
+ "First argument is parameter name, it must start with zpython (case is ignored).\n"
+ " Parameter with given name must not exist.\n"
+ "Second argument is value object. Its __str__ method will be used to get\n"
+ " resulting string when parameter is accessed in zsh, __call__ method will be used\n"
+ " to set value. If object is not callable then parameter will be considered readonly"},
+ {"set_special_integer", ZshSetMagicInteger, METH_VARARGS,
+ "Define integer parameter.\n"
+ "First argument is parameter name, it must start with zpython (case is ignored).\n"
+ " Parameter with given name must not exist.\n"
+ "Second argument is value object. It will be coerced to long integer,\n"
+ " __call__ method will be used to set value. If object is not callable\n"
+ " then parameter will be considered readonly"},
+ {"set_special_float", ZshSetMagicFloat, METH_VARARGS,
+ "Define floating point parameter.\n"
+ "First argument is parameter name, it must start with zpython (case is ignored).\n"
+ " Parameter with given name must not exist.\n"
+ "Second argument is value object. It will be coerced to float,\n"
+ " __call__ method will be used to set value. If object is not callable\n"
+ " then parameter will be considered readonly"},
+ {"set_special_array", ZshSetMagicArray, METH_VARARGS,
+ "Define array parameter.\n"
+ "First argument is parameter name, it must start with zpython (case is ignored).\n"
+ " Parameter with given name must not exist.\n"
+ "Second argument is value object. It must implement sequence protocol,\n"
+ " each item in sequence must have str type, __call__ method will be used\n"
+ " to set value. If object is not callable then parameter will be\n"
+ " considered readonly"},
+ {"set_special_hash", ZshSetMagicHash, METH_VARARGS,
+ "Define hash parameter.\n"
+ "First argument is parameter name, it must start with zpython (case is ignored).\n"
+ " Parameter with given name must not exist.\n"
+ "Second argument is value object. It must implement mapping protocol,\n"
+ " it may be iterable (in this case iterator must return keys),\n"
+ " __getitem__ must be able to work with string objects,\n"
+ " each item must have str type.\n"
+ " __setitem__ will be used to set hash items"},
+ {NULL, NULL, 0, NULL},
+};
+
+static PyTypeObject EnvironGeneratorType;
+
+typedef PyObject *(*SingleEnvItemGenerator) (char *);
+
+typedef struct {
+ PyObject_HEAD
+ char **environ;
+ SingleEnvItemGenerator getobject;
+} EnvironGeneratorObject;
+
+static PyObject *
+EnvironGeneratorNext(PyObject *self)
+{
+ EnvironGeneratorObject *this = (EnvironGeneratorObject *) self;
+
+ if (*this->environ == NULL)
+ return NULL;
+
+ return this->getobject(*(this->environ++));
+}
+
+static PyObject *
+EnvironGeneratorIter(PyObject *self)
+{
+ Py_INCREF(self);
+ return self;
+}
+
+static PyObject *
+EnvironGeneratorNew(SingleEnvItemGenerator getobject)
+{
+ EnvironGeneratorObject *self = PyObject_NEW(EnvironGeneratorObject, &EnvironGeneratorType);
+ self->environ = environ;
+ self->getobject = getobject;
+ return (PyObject *) self;
+}
+
+static PyObject *
+EnvironGetKey(char *eitem)
+{
+ char *p = strchr(eitem, '=');
+ if (p == NULL) {
+ PyErr_SetString(PyExc_SystemError, "No = in environ");
+ return NULL;
+ }
+ return PyString_FromStringAndSize(eitem, (Py_ssize_t) (p - eitem));
+}
+
+static PyObject *
+EnvironGetValue(char *eitem)
+{
+ char *p = strchr(eitem, '=');
+ if (p == NULL) {
+ PyErr_SetString(PyExc_SystemError, "No = in environ");
+ return NULL;
+ }
+ return PyString_FromString(p + 1);
+}
+
+static PyObject *
+EnvironGetItem(char *eitem)
+{
+ char *p = strchr(eitem, '=');
+ if (p == NULL) {
+ PyErr_SetString(PyExc_SystemError, "No = in environ");
+ return NULL;
+ }
+ return Py_BuildValue(
+#if PY_MAJOR_VERSION < 3
+ "s#s",
+#else
+ "y#y",
+#endif
+ eitem, (int) (p - eitem), p + 1);
+}
+
+static PyTypeObject EnvironType;
+
+static PyObject *
+EnvironKeys(UNUSED(PyObject *self))
+{
+ return EnvironGeneratorNew(EnvironGetKey);
+}
+
+static PyObject *
+EnvironValues(UNUSED(PyObject *self))
+{
+ return EnvironGeneratorNew(EnvironGetValue);
+}
+
+static PyObject *
+EnvironItems(UNUSED(PyObject *self))
+{
+ return EnvironGeneratorNew(EnvironGetItem);
+}
+
+static PyObject *
+EnvironCopy(UNUSED(PyObject *self))
+{
+ char **e;
+ PyObject *d = PyDict_New();
+
+ for (e = environ; *e != NULL; e++) {
+ PyObject *k;
+ PyObject *v;
+ char *p = strchr(*e, '=');
+ if (p == NULL) {
+ PyErr_SetString(PyExc_SystemError, "No = in environ");
+ Py_DECREF(d);
+ return NULL;
+ }
+ if (!(k = PyString_FromStringAndSize(*e, (Py_ssize_t) (p - *e)))) {
+ Py_DECREF(d);
+ return NULL;
+ }
+ if (!(v = PyString_FromString(p + 1))) {
+ Py_DECREF(k);
+ Py_DECREF(d);
+ return NULL;
+ }
+ if (PyDict_SetItem(d, k, v) != 0) {
+ Py_DECREF(k);
+ Py_DECREF(v);
+ Py_DECREF(d);
+ return NULL;
+ }
+ }
+
+ return d;
+}
+
+static PyObject *
+EnvironPop(UNUSED(PyObject *self), PyObject *args)
+{
+ char *var;
+ char *val;
+ Param pm;
+ PyObject *def = NULL;
+
+ if (!PyArg_ParseTuple(args, "s|O", &var, &def))
+ return NULL;
+
+ if (!(val = zgetenv(var))) {
+ if (def) {
+ Py_INCREF(def);
+ return def;
+ }
+ else {
+ PyErr_SetNone(PyExc_KeyError);
+ return NULL;
+ }
+ }
+
+ unsetparam(var);
+ if (errflag) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to delete parameter");
+ return NULL;
+ }
+
+ return PyString_FromString(val);
+}
+
+static PyObject *
+EnvironPopItem(PyObject *self, PyObject *args)
+{
+ PyObject *r;
+ char *var;
+
+ if (!PyArg_ParseTuple(args, "s", &var))
+ return NULL;
+
+ if (!(r = EnvironPop(self, args)))
+ return NULL;
+
+ return Py_BuildValue("sO", var, r);
+}
+
+static PyObject *
+EnvironGet(UNUSED(PyObject *self), PyObject *args)
+{
+ char *var;
+ char *val;
+ Param pm;
+ PyObject *def = NULL;
+
+ if (!PyArg_ParseTuple(args, "s|O", &var, &def))
+ return NULL;
+
+ if (!(val = zgetenv(var))) {
+ if (def) {
+ Py_INCREF(def);
+ return def;
+ }
+ else {
+ Py_RETURN_NONE;
+ }
+ }
+
+ return PyString_FromString(val);
+}
+
+static char *
+get_no_null_chars(PyObject *string)
+{
+ char *str;
+
+ if (PyString_Check(string)) {
+ if (PyString_AsStringAndSize(string, &str, NULL) == -1)
+ return NULL;
+ }
+ else {
+#if defined(PY_VERSION_HEX) && PY_VERSION_HEX >= 0x03030000
+ if (!(str = PyUnicode_AsUTF8AndSize(string, NULL)))
+ return NULL;
+#else
+ PyObject *bytes;
+ if (!(bytes = PyUnicode_AsUTF8String(string)))
+ return NULL;
+
+ if (PyString_AsStringAndSize(bytes, &str, NULL) == -1) {
+ Py_DECREF(bytes);
+ return NULL;
+ }
+ Py_DECREF(bytes);
+#endif
+ }
+
+ return str;
+}
+
+static PyMethodDef EnvironMethods[] = {
+ {"keys", (PyCFunction) EnvironKeys, METH_NOARGS,
+ "Generator of environment variable names, in order they are present in **environ"},
+ {"values", (PyCFunction) EnvironValues, METH_NOARGS,
+ "Generator of environment variable values, in order they are present in **environ"},
+ {"items", (PyCFunction) EnvironItems, METH_NOARGS,
+ "Generator of environment variable (name, value) tuples, in order they are present in **environ"},
+ {"copy", (PyCFunction) EnvironCopy, METH_NOARGS,
+ "Returns a dictionary with the snapshot of current exported environment"},
+ {"pop", EnvironPop, METH_VARARGS,
+ "Removes and returns exported variable, raising KeyError if it is not available.\n"
+ "If second argument is given returns it instead of raising KeyError."},
+ {"popitem", EnvironPopItem, METH_VARARGS,
+ "Removes exported variable and returns tuple (varname, value), raising KeyError if it is not available"},
+ {"get", EnvironGet, METH_VARARGS,
+ "Return environment variable value or second argument (defaults to None) if it is not found"},
+ {NULL, NULL, 0, NULL},
+};
+
+static PyObject *
+EnvironItem(UNUSED(PyObject *self), PyObject *keyObject)
+{
+ char *var;
+ char *val;
+
+ if (!(var = get_no_null_chars(keyObject)))
+ return NULL;
+
+ if (!(val = zgetenv(var))) {
+ PyErr_SetNone(PyExc_KeyError);
+ return NULL;
+ }
+
+ return PyString_FromString(val);
+}
+
+static PyObject *
+EnvironAssItem(PyObject *self, PyObject *keyObject, PyObject *valObject)
+{
+ if (valObject == NULL) {
+ PyObject *args;
+ PyObject *item;
+
+ if (!(args = PyTuple_Pack(1, keyObject)))
+ return NULL;
+ if (!(item = EnvironPop(self, args))) {
+ Py_DECREF(args);
+ return NULL;
+ }
+ Py_DECREF(args);
+ Py_DECREF(item);
+ Py_RETURN_NONE;
+ }
+ else {
+ char *var;
+ char *val;
+ if (!(var = get_no_null_chars(keyObject)))
+ return NULL;
+
+ if (!(val = get_no_null_chars(valObject)))
+ return NULL;
+
+ assignsparam(var, val, PM_EXPORTED);
+ Py_RETURN_NONE;
+ }
+}
+
+static Py_ssize_t
+EnvironLength(UNUSED(PyObject *self))
+{
+ char **e;
+ Py_ssize_t r = 0;
+
+ for(e = environ; *e != NULL; e++)
+ r++;
+
+ return r;
+}
+
+static PyMappingMethods EnvironAsMapping = {
+ (lenfunc) EnvironLength,
+ (binaryfunc) EnvironItem,
+ (objobjargproc) EnvironAssItem,
+};
+
+typedef struct {
+ PyObject_HEAD
+} EnvironObject;
+
+static int
+init_types(void)
+{
+ memset(&EnvironGeneratorType, 0, sizeof(EnvironGeneratorType));
+ EnvironGeneratorType.tp_name = "zsh.environ_generator";
+ EnvironGeneratorType.tp_basicsize = sizeof(EnvironGeneratorObject);
+ EnvironGeneratorType.tp_getattro = PyObject_GenericGetAttr;
+ EnvironGeneratorType.tp_iter = EnvironGeneratorIter;
+ EnvironGeneratorType.tp_iternext = EnvironGeneratorNext;
+ EnvironGeneratorType.tp_flags = Py_TPFLAGS_DEFAULT;
+
+ memset(&EnvironType, 0, sizeof(EnvironType));
+ EnvironType.tp_name = "zsh.environ";
+ EnvironType.tp_basicsize = sizeof(EnvironObject);
+ EnvironType.tp_getattro = PyObject_GenericGetAttr;
+ EnvironType.tp_methods = EnvironMethods;
+ EnvironType.tp_as_mapping = &EnvironAsMapping;
+ EnvironType.tp_flags = Py_TPFLAGS_DEFAULT;
+
+ if (PyType_Ready(&EnvironGeneratorType) == -1)
+ return 1;
+ if (PyType_Ready(&EnvironType) == -1)
+ return 1;
+ return 0;
+}
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef zshmodule = {
+ PyModuleDef_HEAD_INIT,
+ "zsh", /* Module name */
+ NULL, /* Module documentation */
+ -1, /* Size of additional memory needed (no memory needed) */
+ ZshMethods, /* Module methods */
+ NULL, /* Unused, should be null. Name: m_reload, type: inquiry */
+ NULL, /* A traversal function to call during GC traversal */
+ NULL, /* A clear function to call during GC clearing */
+ NULL, /* A function to call during deallocation */
+};
+#endif
+
+static struct builtin bintab[] = {
+ BUILTIN("zpython", 0, do_zpython, 1, 1, 0, NULL, NULL),
+};
+
+static struct features module_features = {
+ bintab, sizeof(bintab)/sizeof(*bintab),
+ NULL, 0,
+ NULL, 0,
+ NULL, 0,
+ 0
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+ return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+ *features = featuresarray(m, &module_features);
+ return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+ return handlefeatures(m, &module_features, enables);
+}
+
+static int
+zsh_init_globals(PyObject *zsh_globals)
+{
+ EnvironObject *environ;
+
+ if (init_types())
+ return 1;
+
+ if (!(environ = PyObject_NEW(EnvironObject, &EnvironType)))
+ return 1;
+
+ if (PyDict_SetItemString(zsh_globals, "environ", (PyObject *)environ) == -1)
+ return 1;
+ return 0;
+}
+
+#if PY_MAJOR_VERSION >= 3
+static PyObject *
+PyInit_zsh()
+{
+ PyObject *module_globals;
+ PyObject *module = PyModule_Create(&zshmodule);
+
+ if (!(module_globals = PyModule_GetDict(module)))
+ return NULL;
+ if (zsh_init_globals(module_globals))
+ return NULL;
+
+ return module;
+}
+#endif
+
+/**/
+int
+boot_(UNUSED(Module m))
+{
+ PyObject *module;
+ PyObject *module_globals;
+ zpython_subshell = zsh_subshell;
+ char *(argv[2]) = {argzero, NULL};
+#if PY_MAJOR_VERSION >= 3
+ if (PyImport_AppendInittab("zsh", PyInit_zsh) == -1)
+ return 1;
+ Py_Initialize();
+ PyEval_InitThreads();
+ PySys_SetArgvEx(1, argv, 0);
+#else
+ Py_Initialize();
+ PyEval_InitThreads();
+ PySys_SetArgvEx(1, argv, 0);
+ if (!(module = Py_InitModule3("zsh", ZshMethods, (char *) NULL)))
+ return 1;
+ if (!(module_globals = PyModule_GetDict(module)))
+ return 1;
+ if (zsh_init_globals(module_globals))
+ return 1;
+#endif
+ if (!(globals = PyModule_GetDict(PyImport_AddModule("__main__"))))
+ return 1;
+ PyEval_SaveThread();
+ return 0;
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+ if (Py_IsInitialized()) {
+ struct specialparam *cur_sp = first_assigned_param;
+
+ while (cur_sp) {
+ char *name = cur_sp->name;
+ Param pm = (Param) paramtab->getnode(paramtab, name);
+ struct specialparam *next_sp = cur_sp->next;
+
+ if (pm && pm != cur_sp->pm) {
+ Param prevpm, searchpm;
+ prevpm = pm;
+ searchpm = pm->old;
+ while (searchpm && searchpm != cur_sp->pm) {
+ prevpm = searchpm;
+ searchpm = searchpm->old;
+ }
+ if (searchpm) {
+ paramtab->removenode(paramtab, pm->node.nam);
+ prevpm->old = searchpm->old;
+ searchpm->old = pm;
+ paramtab->addnode(paramtab, searchpm->node.nam, searchpm);
+
+ pm = searchpm;
+ }
+ else {
+ pm = NULL;
+ }
+ }
+ if (pm) {
+ pm->node.flags = (pm->node.flags & ~PM_READONLY) | PM_REMOVABLE;
+ unsetparam_pm(pm, 0, 1);
+ }
+ /* Memory was freed while unsetting parameter, thus need to save
+ * sp->next */
+ cur_sp = next_sp;
+ }
+ PYTHON_RESTORE_THREAD;
+ Py_Finalize();
+ PYTHON_SAVE_THREAD;
+ }
+ return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+ return 0;
+}
diff --git a/Src/Modules/zpython.mdd b/Src/Modules/zpython.mdd
new file mode 100644
index 0000000..e64cd4d
--- /dev/null
+++ b/Src/Modules/zpython.mdd
@@ -0,0 +1,7 @@
+name=zsh/zpython
+link=dynamic
+load=no
+
+autofeatures="b:zpython"
+
+objects="zpython.o"
diff --git a/Test/V08zpython.ztst b/Test/V08zpython.ztst
new file mode 100644
index 0000000..eadb50c
--- /dev/null
+++ b/Test/V08zpython.ztst
@@ -0,0 +1,486 @@
+%prep
+ if ( zmodload -i zsh/zpython ) >/dev/null 2>&1; then
+ zmodload -i zsh/zpython
+ zpython 'import zsh, sys, os'
+ zpython 'sys.path.append(os.getcwd())'
+ zpython 'from ztstutil import *'
+ functio exe() { { eval "$@" } always { TRY_BLOCK_ERROR=0 } }
+ else
+ ZTST_unimplemented="The module zsh/zpython is not available"
+ fi
+%test
+ zpython 'print("Test stdout")'
+0:Stdout test
+>Test stdout
+
+ zpython 'import sys; sys.stderr.write("Test stderr\n")'
+0:Stderr test
+?Test stderr
+
+ zpython 'raise NotImplementedError()'
+1:Exception test
+*?*
+?*
+?NotImplementedError
+
+ (exit 5)
+ zpython 'print(zsh.last_exit_code())'
+0:Last exit code
+>5
+
+ function e() { return $1 }
+ e 5 | e 6 | e 7
+ zpython 'print(" ".join((str(i) for i in zsh.pipestatus())))'
+0:Pipe status
+>5 6 7
+
+ echo ABC-$ABC
+ zpython 'zsh.eval("ABC=2")'
+ echo ABC-$ABC
+0:zsh.eval
+>ABC-
+>ABC-2
+
+ zpython 'print(zsh.subshell())'
+0:Subshell test
+>0
+
+ STRING=abc
+ integer INT=5
+ float FLOAT=10.5
+ typeset -a ARRAY
+ ARRAY=(a b c d)
+ typeset -A HASH
+ HASH=(a b c d)
+ zpython 'print(repr(zsh.getvalue("STRING")))'
+ zpython 'print(repr(zsh.getvalue("INT")))'
+ zpython 'print(repr(zsh.getvalue("FLOAT")))'
+ zpython 'print(repr(zsh.getvalue("ARRAY")))'
+ zpython 'print(repr(sorted(zsh.getvalue("HASH").items())))'
+0:getvalue test
+*>(|b)'abc'
+>5(L|)
+>10.5
+>\[(|b)'a', (|b)'b', (|b)'c', (|b)'d'\]
+>\[\((|b)'a', (|b)'b'\), \((|b)'c', (|b)'d'\)\]
+
+ zpython 'import sys'
+ zpython 'zsh.setvalue("STRING", "def")'
+ zpython 'zsh.setvalue("INT", 3)'
+ zpython 'zsh.setvalue("INT2", long(4) if sys.version_info < (3,) else 4)'
+ zpython 'zsh.setvalue("FLOAT", 5.0)'
+ zpython 'zsh.setvalue("ARRAY", [str(i) for i in range(5)])'
+ zpython 'zsh.setvalue("DICT", {"a": "b", "c": "d"})'
+ echo $STRING
+ echo $INT
+ echo $INT2
+ printf "%.3f\\n" $FLOAT
+ echo $ARRAY
+ echo ${(kv)DICT}
+0:setvalue test
+>def
+>3
+>4
+>5.000
+>0 1 2 3 4
+>a b c d
+
+ zpython 'zsh.set_special_string("ZPYTHON_STRING", Str())'
+ zpython 'zsh.set_special_string("ZPYTHON_STRING2", CStr())'
+ echo $ZPYTHON_STRING
+ echo $ZPYTHON_STRING
+ echo $ZPYTHON_STRING2
+ ZPYTHON_STRING2=20
+ echo $ZPYTHON_STRING2
+ ZPYTHON_STRING2+=2
+ echo $ZPYTHON_STRING2
+0:set_special_string test
+>1
+>2
+>1
+>-18
+>156
+
+ zpython 'zsh.set_special_integer("ZPYTHON_INT", Int())'
+ zpython 'zsh.set_special_integer("ZPYTHON_INT2", CInt())'
+ echo $ZPYTHON_INT
+ echo $ZPYTHON_INT2
+ echo $ZPYTHON_INT2
+ ZPYTHON_INT2=8
+ echo $ZPYTHON_INT2
+ ZPYTHON_INT2+=0
+ echo $ZPYTHON_INT2
+0:set_special_integer test
+>4
+>4
+>16
+>8
+>4
+
+ zpython 'zsh.set_special_float("ZPYTHON_FLOAT", Float())'
+ zpython 'zsh.set_special_float("ZPYTHON_FLOAT2", CFloat())'
+ printf "%.3f\\n" $ZPYTHON_FLOAT
+ printf "%.3f\\n" $ZPYTHON_FLOAT2
+ printf "%.3f\\n" $ZPYTHON_FLOAT2
+ ZPYTHON_FLOAT2=3
+ printf "%.3f\\n" $ZPYTHON_FLOAT2
+ ZPYTHON_FLOAT2+=-4
+ printf "%.3f\\n" $ZPYTHON_FLOAT2
+0:set_special_integer test
+>2.000
+>2.000
+>4.000
+>2.000
+>8.000
+
+ zpython 'zsh.set_special_array("ZPYTHON_ARRAY", Array())'
+ zpython 'zsh.set_special_array("ZPYTHON_ARRAY2", CArray())'
+ zpython 'zsh.set_special_array("ZPYTHON_ARRAY3", CArray())'
+ echo $ZPYTHON_ARRAY
+ echo $ZPYTHON_ARRAY
+ ZPYTHON_ARRAY2+=(a b)
+ echo $ZPYTHON_ARRAY2
+ ZPYTHON_ARRAY3=(c d)
+ echo $ZPYTHON_ARRAY3
+0:set_special_array
+>len:1
+>len:1 get:0 len:3
+>len:1 get:0 len:3 get:0 get:1 get:2 set:len:1|a|b|len:3 len:8
+>set:c|d len:2
+
+ zpython 'zsh.set_special_hash("ZPYTHON_HASH", {"a": "b"} if sys.version_info < (3,) else {b"a": b"b"})'
+ echo ${(kv)ZPYTHON_HASH}
+ echo ${(k)ZPYTHON_HASH}
+ echo ${(v)ZPYTHON_HASH}
+ echo $ZPYTHON_HASH[a]
+ echo $ZPYTHON_HASH[b]
+ echo ${ZPYTHON_HASH}
+ ZPYTHON_HASH=(def abc)
+ echo ${(kv)ZPYTHON_HASH}
+ ZPYTHON_HASH+=(abc def)
+ echo ${(kv)ZPYTHON_HASH}
+0:set_special_hash: dictionary
+>a b
+>a
+>b
+>b
+>
+>b
+>def abc
+>abc def def abc
+
+ zpython 'zsh.set_special_hash("ZPYTHON_HASH2", Hash())'
+ echo $ZPYTHON_HASH2
+ echo $ZPYTHON_HASH2[a]
+ echo $ZPYTHON_HASH2[c]
+ ZPYTHON_HASH2[b]=d
+ echo $ZPYTHON_HASH2[acc]
+ ZPYTHON_HASH2=(b d)
+ echo $ZPYTHON_HASH2
+ ZPYTHON_HASH2+=(b def)
+ echo $ZPYTHON_HASH2[b]
+0:set_special_hash: Hash
+>i*2;[acc] b
+>b
+>None
+>i*2;[acc];[a]*3;[c]*2;[b];[b]=d;[acc]*2
+>i*2;[acc];[a]*3;[c]*2;[b];[b]=d;[acc]*2;k;![a];![b];[b]=d;i*2;[acc] d
+>def
+
+ zpython 'zsh.set_special_hash("zpython_hsh", EHash())'
+ zpython 'zsh.getvalue("NOT AN IDENTIFIER")'
+ zpython 'zsh.getvalue("XXX_UNKNOWN_PARAMETER")'
+ zpython 'zsh.setvalue("NOT AN IDENTIFIER", "")'
+ zpython 'zsh.setvalue("NOT_A_STRING_ITEM", [1])'
+ zpython 'zsh.setvalue("UNKNOWN_SIZE", EArray())'
+ zpython 'zsh.setvalue("NOT_A_STRING_KEY", {1 : "abc"})'
+ zpython 'zsh.setvalue("NOT_A_STRING_VALUE", {"abc" : 1})'
+ zpython 'zsh.setvalue("UNKNOWN_TYPE", str)'
+1:Various errors: getvalue and setvalue
+# NOT AN IDENTIFIER
+*?Traceback*
+?*
+?KeyError:*
+# XXX_UNKNOWN_PARAMETER
+?Traceback*
+?*
+?IndexError:*
+# NOT AN IDENTIFIER
+?Traceback*
+?*
+?KeyError:*
+# NOT_A_STRING_ITEM
+?Traceback*
+?*
+?TypeError:*
+# UNKNOWN_SIZE
+?Traceback*
+?*
+?ValueError:*
+# NOT_A_STRING_KEY
+?Traceback*
+?*
+?TypeError:*
+# NOT_A_STRING_VALUE
+?Traceback*
+?*
+?TypeError:*
+# UNKNOWN_TYPE
+?Traceback*
+?*
+?TypeError:*
+
+ zpython 'zsh.set_special_string("NO_ZPYTHON_STRING", "")'
+ zpython 'zsh.set_special_integer("NO_ZPYTHON_INT", 0)'
+ zpython 'zsh.set_special_float("NO_ZPYTHON_FLOAT", 0.0)'
+ zpython 'zsh.set_special_array("NO_ZPYTHON_ARRAY", [])'
+ zpython 'zsh.set_special_hash("NO_ZPYTHON_HASH", {})'
+ zpython 'zsh.set_special_string("ZPYTHON", "")'
+1:Key errors: set_special_*
+# NO_ZPYTHON_STRING
+*?Traceback*
+?*
+?KeyError:*
+# NO_ZPYTHON_INT
+?Traceback*
+?*
+?KeyError:*
+# NO_ZPYTHON_FLOAT
+?Traceback*
+?*
+?KeyError:*
+# NO_ZPYTHON_ARRAY
+?Traceback*
+?*
+?KeyError:*
+# NO_ZPYTHON_HASH
+?Traceback*
+?*
+?KeyError:*
+# ZPYTHON
+?Traceback*
+?*
+?KeyError:*
+
+ zpython 'zsh.set_special_integer("zpython_v", 0)'
+ zpython 'zsh.set_special_string("zpython_v", "")'
+ zpython 'zsh.set_special_integer("zpython_v", 0)'
+ zpython 'zsh.set_special_float("zpython_v", 0.0)'
+ zpython 'zsh.set_special_array("zpython_v", [])'
+ zpython 'zsh.set_special_hash("zpython_v", {})'
+1:Runtime errors (occupied variable name): set_special_*
+# string
+*?Traceback*
+?*
+?RuntimeError:*
+# integer
+?Traceback*
+?*
+?RuntimeError:*
+# float
+?Traceback*
+?*
+?RuntimeError:*
+# array
+?Traceback*
+?*
+?RuntimeError:*
+# hash
+?Traceback*
+?*
+?RuntimeError:*
+
+ zpython 'zsh.set_special_integer("zpython_int", [])'
+ zpython 'zsh.set_special_float("zpython_flt", [])'
+ zpython 'zsh.set_special_array("zpython_arr", 0)'
+ zpython 'zsh.set_special_hash("zpython_hsh", 0)'
+1:Type errors (unimplemented protocols): set_special_*
+# integer
+*?Traceback*
+?*
+?TypeError:*
+# float
+?Traceback*
+?*
+?TypeError:*
+# array
+?Traceback*
+?*
+?TypeError:*
+# hash
+?Traceback*
+?*
+?TypeError:*
+
+ exe 'echo $zpython_hsh'
+ exe 'echo $zpython_hsh[2]'
+ exe 'zpython_hsh=(a b)'
+ exe 'zpython_hsh+=(a b)'
+ exe 'zpython_hsh[1]=""'
+1:
+# echo $zpython_hsh
+*?Traceback*
+?*
+?*
+?NotImplementedError
+?*:1:*iterator*
+?Traceback*
+?*
+?*
+?NotImplementedError
+# echo $zpython_hsh[2]
+?Traceback*
+?*
+?*
+?SystemError
+?*:1:*value object*
+?Traceback*
+?*
+?*
+?SystemError
+# zpython_hsh=(a b)
+?Traceback*
+?*
+?*
+?ValueError
+?*:1:*object keys*
+# zpython_hsh+=(a b)
+?TypeError:*
+?*:1:*set object*
+# zpython_hsh[1]=""
+?Traceback*
+?*
+?*
+?SystemError
+?*:1:*value object*
+?TypeError:*
+
+ zpython 'zsh.set_special_hash("zpython_hsh2", EHash2())'
+ zpython 'zsh.set_special_hash("zpython_hsh3", EHash3())'
+ zpython 'zsh.set_special_string("zpython_str", EStr())'
+ zpython 'zsh.set_special_integer("zpython_int", ENum())'
+ zpython 'zsh.set_special_float("zpython_flt", ENum())'
+ exe 'echo $zpython_hsh2'
+ exe 'echo $zpython_hsh3'
+ exe 'echo $zpython_str'
+ exe 'echo $zpython_int'
+ exe 'echo $zpython_flt'
+ exe 'zpython_str=""'
+ exe 'zpython_int=2'
+ exe 'zpython_flt=2'
+1:
+# echo $zpython_hsh2
+*?Traceback*
+?*
+?*
+?NotImplementedError
+?*:1:*get value string*
+# echo $zpython_hsh3
+?*:1:*Key is not a string*
+# echo $zpython_str
+?Traceback*
+?*
+?*
+?NotImplementedError
+?*:1:*create string object*
+# echo $zpython_int
+?Traceback*
+?*
+?*
+?IndexError
+?*:1:*create int object*
+# echo $zpython_flt
+?Traceback*
+?*
+?*
+?KeyError
+?*:1:*create float object*
+# zpython_str=""
+?*:1:*assign value for string*
+?Traceback*
+?*
+?*
+?KeyError
+# zpython_int=2
+?(Traceback*|*:1:*assign value for integer*)
+?(*|Traceback*)
+?*
+?(ValueError|*)
+?(*:1:*assign value for integer*|ValueError)
+# zpython_flt=2
+?(Traceback*|*:1:*assign value for float*)
+?(*|Traceback*)
+?*
+?(ValueError|*)
+?(*:1:*assign value for float*|ValueError)
+
+ zpython 'zsh.set_special_array("zpython_arr", EArray())'
+ exe 'echo $zpython_arr'
+ exe 'zpython_arr=(a b c)'
+1:
+# echo $zpython_arr
+*?ValueError:*
+?*:1:*create array*
+# zpython_arr=(a b c)
+?(Traceback*|*:1:*assign value for array*)
+?(*|Traceback*)
+?*
+?(IndexError|*)
+?(*:1:*assign value for array*|IndexError)
+
+ zpython 'zsh.setvalue("NULL_STRING", "\0")'
+ echo $(( #NULL_STRING ))
+ zpython 'print(repr(zsh.getvalue("NULL_STRING")))'
+ zpython 'd={};zsh.set_special_hash("ZPYTHON_NHSH", d)'
+ ZPYTHON_NHSH[$NULL_STRING]=$'\0\0'
+ zpython 'print(repr(d))'
+ VALUE="${ZPYTHON_NHSH[$NULL_STRING]}"
+ echo $(( #VALUE ))
+ KEYS="${(k)ZPYTHON_NHSH}"
+ echo $(( #KEYS ))
+ set -A NULLS_ARRAY $'\0' $'a\0b'
+ zpython 'print(repr(zsh.getvalue("NULLS_ARRAY")))'
+ typeset -A NULLS_HASH
+ NULLS_HASH=( $'\0a\0' $'b\0a' )
+ zpython 'print(repr(zsh.getvalue("NULLS_HASH")))'
+0:Null in various strings
+*>0
+>(|b)'\\x00'
+>{(|b)'\\x00': (|b)'\\x00\\x00'}
+>0
+>0
+>\[(|b)'\\x00', (|b)'a\\x00b'\]
+>{(|b)'\\x00a\\x00': (|b)'b\\x00a'}
+
+ zpython 'zsh.set_special_hash("ZPYTHON_DICT", {"a": "b", "c": "d"} if sys.version_info < (3,) else {b"a": b"b", b"c": b"d"})'
+ zpython 'zsh.set_special_string("ZPYTHON_USTR", UStr())'
+ unset 'ZPYTHON_DICT[a]'
+ echo "${(kv)ZPYTHON_DICT}"
+ unset ZPYTHON_USTR
+0:unsets
+>c d
+?abc
+
+
+ zmodload -u zsh/zpython
+ for v in ZPYTHON_{{STRING,INT,FLOAT,ARRAY,HASH}{,2},ARRAY3} ; do
+ echo ${v}:${(P)v}
+ done
+# Ignoring stderr is needed for python with memory debugging enabled
+0D:Module unloading
+>ZPYTHON_STRING:
+>ZPYTHON_STRING2:
+>ZPYTHON_INT:
+>ZPYTHON_INT2:
+>ZPYTHON_FLOAT:
+>ZPYTHON_FLOAT2:
+>ZPYTHON_ARRAY:
+>ZPYTHON_ARRAY2:
+>ZPYTHON_HASH:
+>ZPYTHON_HASH2:
+>ZPYTHON_ARRAY3:
+
+ zmodload -i zsh/zpython
+0: Module was loaded
+
+%clean
diff --git a/Test/ztstutil.py b/Test/ztstutil.py
new file mode 100644
index 0000000..8b50ef1
--- /dev/null
+++ b/Test/ztstutil.py
@@ -0,0 +1,159 @@
+import sys
+
+try:
+ from __builtin__ import unicode
+ s = unicode
+except ImportError:
+ s = lambda string: str(string, 'utf-8') if type(string) is bytes else str(string)
+
+class Str(object):
+ def __init__(self):
+ self.i=0
+ def __str__(self):
+ self.i+=1
+ return str(self.i)
+
+class CStr(Str):
+ def __call__(self, s):
+ self.i-=int(s)
+
+class NBase(float):
+ __slots__=("i",)
+ def __init__(self):
+ self.i=float(1)
+
+class Int(NBase):
+ def __long__(self):
+ self.i*=4
+ return int(self.i)
+
+ __int__ = __long__
+
+class CInt(Int):
+ def __call__(self, i):
+ self.i/=i
+
+class Float(NBase):
+ def __float__(self):
+ self.i*=2
+ return float(self.i)
+
+class CFloat(Float):
+ def __call__(self, i):
+ self.i/=(i+1)
+
+class Array(object):
+ def __init__(self):
+ self.accesses=[]
+
+ def __len__(self):
+ self.accesses+=['len:'+s(len(self.accesses)+1)]
+ return len(self.accesses)
+
+ def __getitem__(self, i):
+ self.accesses+=['get:'+s(i)]
+ return self.accesses[i]
+
+class CArray(Array):
+ def __call__(self, a):
+ self.accesses+=['set:'+'|'.join((s(i) for i in a))]
+
+class Hash(object):
+ def accappend(self, a):
+ if self.acc and self.acc[-1][0] == a:
+ self.acc[-1][1]+=1
+ else:
+ self.acc.append([a, 1])
+
+ def __init__(self):
+ self.d = {'a': 'b'} if sys.version_info < (3,) else {b'a': b'b'}
+ self.acc = []
+
+ def keys(self):
+ self.accappend('k')
+ return self.d.keys()
+
+ def __getitem__(self, key):
+ self.accappend('['+s(key)+']')
+ if s(key) == 'acc':
+ return ';'.join([k[0]+('*'+s(k[1]) if k[1]>1 else '') for k in self.acc])
+ return self.d.get(key)
+
+ def __delitem__(self, key):
+ self.accappend('!['+s(key)+']')
+ self.d.pop(key)
+
+ def __contains__(self, key):
+ # Will be used only if I switch from PyMapping_HasKey to
+ # PySequence_Contains
+ self.accappend('?['+s(key)+']')
+ return s(key) == 'acc' or key in self.d
+
+ def __setitem__(self, key, val):
+ self.accappend('['+s(key)+']='+s(val))
+ self.d[key] = val
+
+ def __iter__(self):
+ self.accappend('i')
+ return iter(['acc' if sys.version_info < (3,) else b'acc']+list(self.d.keys()))
+
+class EHash(object):
+ def __getitem__(self, i):
+ raise SystemError()
+
+ # Invalid number of arguments
+ def __setitem__(self, i):
+ pass
+
+ def __iter__(self):
+ raise NotImplementedError()
+
+ def keys(self):
+ raise ValueError()
+
+class EStr(object):
+ def __str__(self):
+ raise NotImplementedError()
+
+ def __call__(self, s):
+ raise KeyError()
+
+class EHash2(object):
+ def __getitem__(self, i):
+ return EStr()
+
+ def __iter__(self):
+ return (str(i) for i in range(1))
+
+class EHash3(object):
+ def __getitem__(self, i):
+ return None
+
+ def __iter__(self):
+ return iter([EStr()])
+
+class ENum(float):
+ def __long__(self):
+ raise IndexError()
+
+ __int__ = __long__
+
+ def __float__(self):
+ raise KeyError()
+
+ def __call__(self, i):
+ raise ValueError()
+
+class EArray(Array):
+ def __len__(self):
+ raise NotImplementedError()
+
+ def __call__(self, a):
+ raise IndexError()
+
+class UStr(object):
+ def __str__(self):
+ return 'abc'
+
+ def __del__(self):
+ sys.stderr.write('abc\n')
diff --git a/configure.ac b/configure.ac
index 5528597..54a16f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2552,6 +2552,134 @@ AC_HELP_STRING([--enable-libc-musl], [compile with musl as the C library]),
fi])
dnl
+dnl zsh/zpython module
+dnl
+ifdef([zpython],[undefine([zpython])])dnl
+ifdef([zpython_confdir],[undefine([zpython_confdir])])dnl
+AC_ARG_ENABLE(zpython,
+AC_HELP_STRING([--enable-zpython], [compile zsh/zpython module]),
+[zpython="$enableval"], [zpython=yes])
+AC_ARG_WITH(python-config-dir,
+AC_HELP_STRING([--with-python-config-dir=FILE], [Python config directory]),
+[zsh_cv_path_python_conf="$withval"])dnl
+AC_ARG_WITH(python-executable,
+AC_HELP_STRING([--with-python-executable=FILE], [Path to python executable]),
+[zsh_cv_path_python="$withval"])dnl
+AC_ARG_WITH(python-version,
+AC_HELP_STRING([--with-python-version=FILE], [Python version (use MAJ.MINd for debugging versions)]),
+[zsh_cv_python_version="$withval"])dnl
+if test x$zpython = xyes; then
+ dnl Copied from vim's src/configure.in
+ if test x$zsh_cv_path_python == x; then
+ AC_PATH_PROGS(zsh_cv_path_python, python2 python)
+ fi
+ AC_CACHE_CHECK(Python version, zsh_cv_python_version,
+ [[zsh_cv_python_version=`${zsh_cv_path_python} -c 'import sys; print(sys.version[:3])'`]])
+ AC_CACHE_CHECK(Python install prefix, zsh_cv_path_python_pfx,
+ [zsh_cv_path_python_pfx=`${zsh_cv_path_python} -c 'import sys; print(sys.prefix)'`])
+ AC_CACHE_CHECK(Python execution prefix, zsh_cv_path_python_epfx,
+ [zsh_cv_path_python_epfx=`${zsh_cv_path_python} -c 'import sys; print(sys.exec_prefix)'`])
+ AC_CACHE_CHECK(Python configuration directory, zsh_cv_path_python_conf,
+ [zsh_cv_path_python_conf=
+ for path in "${zsh_cv_path_python_pfx}" "${zsh_cv_path_python_epfx}" ; do
+ for subdir in lib lib64 share ; do
+ d="${path}/${subdir}/python${zsh_cv_python_version}/config"
+ for dir in "${d}" "${d}-${zsh_cv_python_version}" ; do
+ if test -d "${dir}" && test -f "${dir}/config.c"; then
+ zsh_cv_path_python_conf="${dir}"
+ fi
+ done
+ done
+ done])
+ PYTHON_CONFDIR="${zsh_cv_path_python_conf}"
+ if test "x$PYTHON_CONFDIR" = "x"; then
+ AC_MSG_RESULT([not found])
+ else
+ AC_CACHE_VAL(zsh_cv_path_python_plibs,
+ [
+ pwd=`pwd`
+ tmp_mkf="$pwd/config-PyMake$$"
+ cat -- "${PYTHON_CONFDIR}/Makefile" - <<'eof' >"${tmp_mkf}"
+__:
+ @echo "python_BASEMODLIBS='$(BASEMODLIBS)'"
+ @echo "python_LIBS='$(LIBS)'"
+ @echo "python_SYSLIBS='$(SYSLIBS)'"
+ @echo "python_LINKFORSHARED='$(LINKFORSHARED)'"
+ @echo "python_DLLLIBRARY='$(DLLLIBRARY)'"
+ @echo "python_INSTSONAME='$(INSTSONAME)'"
+eof
+ dnl -- delete the lines from make about Entering/Leaving directory
+ eval "`cd ${PYTHON_CONFDIR} && make -f "${tmp_mkf}" __ | sed '/ directory /d'`"
+ rm -f -- "${tmp_mkf}"
+ if test "${zsh_cv_python_version}" = "1.4"; then
+ zsh_cv_path_python_plibs="${PYTHON_CONFDIR}/libModules.a ${PYTHON_CONFDIR}/libPython.a ${PYTHON_CONFDIR}/libObjects.a ${PYTHON_CONFDIR}/libParser.a"
+ else
+ zsh_cv_path_python_plibs="-L${PYTHON_CONFDIR} -lpython${zsh_cv_python_version}"
+ fi
+ zsh_cv_path_python_plibs="${zsh_cv_path_python_plibs} ${python_BASEMODLIBS} ${python_LIBS} ${python_SYSLIBS} ${python_LINKFORSHARED}"
+ dnl remove -ltermcap, it can conflict with an earlier -lncurses
+ zsh_cv_path_python_plibs=`echo $zsh_cv_path_python_plibs | sed s/-ltermcap//`
+ ])
+ if test "X$python_DLLLIBRARY" != "X"; then
+ python_INSTSONAME="$python_DLLLIBRARY"
+ fi
+ PYTHON_LIBS="${zsh_cv_path_python_plibs}"
+ if test "${zsh_cv_path_python_pfx}" = "${zsh_cv_path_python_epfx}"; then
+ PYTHON_CFLAGS="-I${zsh_cv_path_python_pfx}/include/python${zsh_cv_python_version} -DPYTHON_HOME=\\\"${zsh_cv_path_python_pfx}\\\""
+ else
+ PYTHON_CFLAGS="-I${zsh_cv_path_python_pfx}/include/python${zsh_cv_python_version} -I${zsh_cv_path_python_epfx}/include/python${zsh_cv_python_version} -DPYTHON_HOME=\\\"${zsh_cv_path_python_pfx}\\\""
+ fi
+ dnl On FreeBSD linking with "-pthread" is required to use threads.
+ dnl _THREAD_SAFE must be used for compiling then.
+ dnl The "-pthread" is added to $LIBS, so that the following check for
+ dnl sigaltstack() will look in libc_r (it's there in libc!).
+ dnl Otherwise, when using GCC, try adding -pthread to $CFLAGS. GCC
+ dnl will then define target-specific defines, e.g., -D_REENTRANT.
+ dnl Don't do this for Mac OSX, -pthread will generate a warning.
+ AC_MSG_CHECKING([if -pthread should be used])
+ threadsafe_flag=
+ thread_lib=
+ dnl if test "x$MACOSX" != "xyes"; then
+ if test "`(uname) 2>/dev/null`" != Darwin; then
+ test "$GCC" = yes && threadsafe_flag="-pthread"
+ if test "`(uname) 2>/dev/null`" = FreeBSD; then
+ threadsafe_flag="-D_THREAD_SAFE"
+ thread_lib="-pthread"
+ fi
+ fi
+ libs_save=$LIBS
+ if test -n "$threadsafe_flag"; then
+ cflags_save=$CFLAGS
+ CFLAGS="$CFLAGS $threadsafe_flag"
+ LIBS="$LIBS $thread_lib"
+ AC_TRY_LINK(,[ ],
+ AC_MSG_RESULT(yes); PYTHON_CFLAGS="$PYTHON_CFLAGS $threadsafe_flag",
+ AC_MSG_RESULT(no); LIBS=$libs_save
+ )
+ CFLAGS=$cflags_save
+ else
+ AC_MSG_RESULT(no)
+ fi
+
+ dnl Check that compiling a simple program still works with the flags
+ dnl added for Python.
+ AC_MSG_CHECKING([if compile and link flags for Python are sane])
+ cflags_save=$CFLAGS
+ CFLAGS="$CFLAGS $PYTHON_CFLAGS"
+ LIBS="$LIBS $PYTHON_LIBS"
+ AC_TRY_LINK(,[ ],
+ AC_MSG_RESULT(yes); python_ok=yes,
+ AC_MSG_RESULT(no: PYTHON DISABLED); python_ok=no)
+ if test x$python_ok != xyes; then
+ CFLAGS=$cflags_save
+ LIBS=$libs_save
+ PYTHON_LIBS=
+ PYTHON_CFLAGS=
+ fi
+ fi
+fi
+
+dnl
dnl static user lookup
dnl
AC_ARG_ENABLE(dynamic-nss,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment