-base32.pyx
-base64.pyx
-hex.pyx
-build
-MANIFEST
-dist
-mLib.c
-COPYING
-mdwsetup.py
*.pyc
-auto-version
-pysetup.mk
+
+/COPYING
+/MANIFEST
+/TODO.pdf
+/TODO.tex
+/auto-version
+/build/
+/mdwsetup.py
+/pysetup.mk
--- /dev/null
+#+TITLE: mLib-python progress
+
+* [0/2] =buf=
+ + [ ] =buf/lbuf.h= ::
+ + [ ] =buf/pkbuf.h= ::
+
+* [0/5] =codec=
+ + [ ] =codec/base32.h= ::
+ + [ ] =codec/base64.h= ::
+ + [ ] =codec/codec.h= ::
+ + [ ] =codec/hex.h= ::
+ + [ ] =codec/url.h= ::
+
+* [0/2] =hash=
+ + [ ] =hash/crc32.h= ::
+ + [ ] =hash/unihash.h= ::
+
+* [4/4] =mem=
+ + [X] =mem/alloc.h= :: pointless
+ + [X] =mem/arena.h= :: pointless
+ + [X] =mem/pool.h= :: pointless
+ + [X] =mem/sub.h= :: pointless
+
+* [0/7] =sel=
+ + [ ] =sel/bres.h= ::
+ + [ ] =sel/conn.h= ::
+ + [ ] =sel/ident.h= ::
+ + [ ] =sel/sel.h= ::
+ + [ ] =sel/selbuf.h= ::
+ + [ ] =sel/selpk.h= ::
+ + [ ] =sel/sig.h= ::
+
+* [5/8] =struct=
+ + [X] =struct/assoc.h= ::
+ + [X] =struct/atom.h= ::
+ + [ ] =struct/buf.h= ::
+ + [ ] =struct/darray.h= ::
+ + [X] =struct/dspool.h= :: pointless
+ + [X] =struct/dstr.h= :: pointless
+ + [X] =struct/hash.h= :: pointless
+ + [ ] =struct/sym.h= ::
+
+* [5/8] =sys=
+ + [X] =sys/daemonize.h= ::
+ + [X] =sys/env.h= :: pointless
+ + [X] =sys/fdflags.h= ::
+ + [X] =sys/fdpass.h= ::
+ + [ ] =sys/fwatch.h= ::
+ + [ ] =sys/lock.h= ::
+ + [X] =sys/mdup.h= ::
+ + [ ] =sys/tv.h= ::
+
+* [0/1] =test=
+ + [ ] =test/testrig.h= ::
+
+* [0/1] =trace=
+ + [ ] =trace/trace.h= ::
+
+* [2/3] =ui=
+ + [ ] =ui/mdwopt.h= :: interface needs thinking about
+ + [X] =ui/quis.h= ::
+ + [X] =ui/report.h= ::
+
+* [5/7] =utils=
+ + [X] =utils/align.h= :: not applicable
+ + [X] =utils/bits.h= :: pointless
+ + [X] =utils/compiler.h= :: pointless
+ + [X] =utils/exc.h= :: pointless
+ + [X] =utils/macros.h= :: pointless
+ + [ ] =utils/str.h= ::
+ + [ ] =utils/versioncmp.h= ::
+
+* COMMENT Emacs cruft
+
+#+LaTeX_CLASS: strayman
--- /dev/null
+/* -*-c-*-
+ *
+ * Atoms and obarrays
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Python interface to mLib.
+ *
+ * mLib/Python is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * mLib/Python is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mLib/Python. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "mLib-python.h"
+
+/*----- Atoms -------------------------------------------------------------*/
+
+typedef struct {
+ PyObject_HEAD
+ atom *a;
+ PyObject *oawk;
+} atom_pyobj;
+static PyTypeObject *atom_pytype;
+#define ATOM_PYCHECK(o) PyObject_TypeCheck((o), atom_pytype)
+#define ATOM_A(o) (((atom_pyobj *)(o))->a)
+#define ATOM_OAWK(o) (((atom_pyobj *)(o))->oawk)
+#define ATOM_OAOBJ(o) (PyWeakref_GET_OBJECT(ATOM_OAWK(o)))
+
+struct obentry {
+ assoc_base _b;
+ PyObject *aobj;
+};
+
+typedef struct {
+ GMAP_PYOBJ_HEAD
+ atom_table tab;
+ assoc_table map;
+ PyObject *wkls;
+} obarray_pyobj;
+static PyTypeObject *obarray_pytype;
+#define OBARRAY_PYCHECK(o) PyObject_TypeCheck((o), obarray_pytype)
+#define OBARRAY_TAB(o) (&((obarray_pyobj *)(o))->tab)
+#define OBARRAY_MAP(o) (&((obarray_pyobj *)(o))->map)
+
+static PyObject *default_obarray(void)
+{
+ PyObject *oa = 0, *rc = 0;
+
+ if (!home_module) SYSERR("home module not set");
+ oa = PyObject_GetAttrString(home_module, "DEFAULT_ATOMTABLE");
+ if (!oa) goto end;
+ if (!OBARRAY_PYCHECK(oa)) TYERR("DEFAULT_ATOMTABLE isn't an AtomTable");
+ rc = oa; oa = 0;
+end:
+ Py_XDECREF(oa);
+ return (rc);
+}
+
+static PyObject *atom_pywrap(PyObject *oaobj, atom *a)
+{
+ struct obentry *e;
+ PyObject *oawk = 0, *rc = 0;
+ atom_pyobj *aobj;
+ unsigned f;
+
+ e = assoc_find(OBARRAY_MAP(oaobj), a, sizeof(*e), &f);
+ if (f)
+ rc = e->aobj;
+ else {
+ oawk = PyWeakref_NewRef(oaobj, 0); if (!oawk) goto end;
+ aobj = PyObject_NEW(atom_pyobj, atom_pytype);
+ aobj->a = a;
+ aobj->oawk = oawk; oawk = 0;
+ e->aobj = (PyObject *)aobj;
+ rc = (PyObject *)aobj;
+ }
+ Py_INCREF(rc);
+end:
+ Py_XDECREF(oawk);
+ if (e && !rc) assoc_remove(OBARRAY_MAP(oaobj), e);
+ return (rc);
+}
+
+static PyObject *atom_pyintern(PyObject *oaobj, PyObject *x)
+{
+ atom *a;
+ const char *p;
+ size_t sz;
+ PyObject *rc = 0;
+
+ if (ATOM_PYCHECK(x)) {
+ if (ATOM_OAOBJ(x) != oaobj) VALERR("wrong table for existing atom");
+ RETURN_OBJ(x);
+ }
+ if (x == Py_None)
+ a = atom_gensym(OBARRAY_TAB(oaobj));
+ else if (TEXT_CHECK(x))
+ { TEXT_PTRLEN(x, p, sz); a = atom_nintern(OBARRAY_TAB(oaobj), p, sz); }
+ else
+ TYERR("expected string or `None'");
+ rc = atom_pywrap(oaobj, a);
+end:
+ return (rc);
+}
+
+static PyObject *atom_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
+{
+ static const char *const kwlist[] = { "name", "table", 0 };
+ PyObject *name = Py_None, *oaobj = 0, *rc = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO!:new", KWLIST,
+ &name, obarray_pytype, &oaobj))
+ { oaobj = 0; goto end; }
+ if (oaobj) Py_INCREF(oaobj);
+ else { oaobj = default_obarray(); if (!oaobj) goto end; }
+ rc = atom_pyintern(oaobj, name);
+end:
+ Py_XDECREF(oaobj);
+ return (rc);
+}
+
+static void atom_pydealloc(PyObject *me)
+ { assert(!ATOM_OAWK(me)); FREEOBJ(me); }
+
+static int atom_check(PyObject *me)
+{
+ if (!ATOM_A(me)) VALERR("atom is stale");
+ return (0);
+end:
+ return (-1);
+}
+
+static PyObject *atomget_name(PyObject *me, void *hunoz)
+{
+ atom *a;
+
+ if (atom_check(me)) return (0);
+ a = ATOM_A(me); return (TEXT_FROMSTRLEN(ATOM_NAME(a), ATOM_LEN(a)));
+}
+
+static PyObject *atomget_home(PyObject *me, void *hunoz)
+{
+ PyObject *rc;
+
+ if (atom_check(me)) return (0);
+ rc = ATOM_OAOBJ(me); assert(rc != Py_None); RETURN_OBJ(rc);
+}
+
+static PyObject *atomget_internedp(PyObject *me, void *hunoz)
+{
+ if (atom_check(me)) return (0);
+ return (getbool(!(ATOM_A(me)->f&ATOMF_GENSYM)));
+}
+
+static PyObject *atomget_livep(PyObject *me, void *hunoz)
+ { return (getbool(!!ATOM_A(me))); }
+
+static PyObject *atom_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+ int r;
+
+ switch (op) {
+ case Py_EQ: r = (x == y); break;
+ case Py_NE: r = (x != y); break;
+ default: TYERR("atoms are unordered");
+ }
+ return (getbool(r));
+end:
+ return (0);
+}
+
+static Py_hash_t atom_pyhash(PyObject *me)
+ { return (atom_check(me) ? -1 : ATOM_HASH(ATOM_A(me))); }
+
+static const PyGetSetDef atom_pygetset[] = {
+#define GETSETNAME(op, name) atom##op##_##name
+ GET (name, "A.name -> STR: atom name")
+ GET (home, "A.home -> ATAB: atom home table")
+ GET (internedp, "A.internedp -> BOOL: atom interned (not gensym)?")
+ GET (livep, "A.livep -> BOOL: atom table still alive?")
+#undef GETSETNAME
+ { 0 }
+};
+
+static const PyTypeObject atom_pytype_skel = {
+ PyVarObject_HEAD_INIT(0, 0) /* Header */
+ "Atom", /* @tp_name@ */
+ sizeof(atom_pyobj), /* @tp_basicsize@ */
+ 0, /* @tp_itemsize@ */
+
+ atom_pydealloc, /* @tp_dealloc@ */
+ 0, /* @tp_print@ */
+ 0, /* @tp_getattr@ */
+ 0, /* @tp_setattr@ */
+ 0, /* @tp_compare@ */
+ 0, /* @tp_repr@ */
+ 0, /* @tp_as_number@ */
+ 0, /* @tp_as_sequence@ */
+ 0, /* @tp_as_mapping@ */
+ atom_pyhash, /* @tp_hash@ */
+ 0, /* @tp_call@ */
+ 0, /* @tp_str@ */
+ 0, /* @tp_getattro@ */
+ 0, /* @tp_setattro@ */
+ 0, /* @tp_as_buffer@ */
+ Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
+ Py_TPFLAGS_BASETYPE,
+
+ /* @tp_doc@ */
+ "Atom([name = None], [table = DEFAULT_ATOMTABLE])",
+
+ 0, /* @tp_traverse@ */
+ 0, /* @tp_clear@ */
+ atom_pyrichcompare, /* @tp_richcompare@ */
+ 0, /* @tp_weaklistoffset@ */
+ 0, /* @tp_iter@ */
+ 0, /* @tp_iternext@ */
+ 0, /* @tp_methods@ */
+ 0, /* @tp_members@ */
+ PYGETSET(atom), /* @tp_getset@ */
+ 0, /* @tp_base@ */
+ 0, /* @tp_dict@ */
+ 0, /* @tp_descr_get@ */
+ 0, /* @tp_descr_set@ */
+ 0, /* @tp_dictoffset@ */
+ 0, /* @tp_init@ */
+ PyType_GenericAlloc, /* @tp_alloc@ */
+ atom_pynew, /* @tp_new@ */
+ 0, /* @tp_free@ */
+ 0 /* @tp_is_gc@ */
+};
+
+static void *obarray_gmlookup(PyObject *me, PyObject *key, unsigned *f)
+{
+ atom *a = 0;
+ const char *p;
+ size_t sz;
+
+ if (!TEXT_CHECK(key)) TYERR("expected string");
+ TEXT_PTRLEN(key, p, sz); a = sym_find(&OBARRAY_TAB(me)->t, p, sz, 0, f);
+end:
+ return (a);
+}
+
+static void obarray_gmiterinit(PyObject *me, void *i)
+ { atom_mkiter(i, OBARRAY_TAB(me)); }
+
+static void *obarray_gmiternext(PyObject *me, void *i)
+ { return (atom_next(i)); }
+
+static PyObject *obarray_gmentrykey(PyObject *me, void *e)
+ { return (TEXT_FROMSTRLEN(ATOM_NAME(e), ATOM_LEN(e))); }
+
+static PyObject *obarray_gmentryvalue(PyObject *me, void *e)
+ { return (atom_pywrap(me, e)); }
+
+static const gmap_ops obarray_gmops = {
+ sizeof(atom_iter),
+ obarray_gmlookup,
+ obarray_gmiterinit,
+ obarray_gmiternext,
+ obarray_gmentrykey,
+ obarray_gmentryvalue
+};
+
+static PyObject *obarray_pynew(PyTypeObject *cls,
+ PyObject *arg, PyObject *kw)
+{
+ obarray_pyobj *rc = 0;
+ static const char *const kwlist[] = { 0 };
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
+ rc = PyObject_NEW(obarray_pyobj, cls); if (!rc) goto end;
+ rc->gmops = &obarray_gmops; rc->wkls = 0;
+ atom_createtable(&rc->tab); assoc_create(&rc->map);
+end:
+ return ((PyObject *)rc);
+}
+
+static void obarray_pydealloc(PyObject *me)
+{
+ assoc_iter it;
+ struct obentry *e;
+
+ ASSOC_MKITER(&it, OBARRAY_MAP(me));
+ for (;;) {
+ ASSOC_NEXT(&it, e); if (!e) break;
+ ATOM_A(e->aobj) = 0;
+ Py_DECREF(ATOM_OAWK(e->aobj)); ATOM_OAWK(e->aobj) = 0;
+ Py_DECREF(e->aobj);
+ }
+ assoc_destroy(OBARRAY_MAP(me));
+ atom_destroytable(OBARRAY_TAB(me));
+ FREEOBJ(me);
+}
+
+static PyObject *oameth_intern(PyObject *me, PyObject *arg)
+{
+ char *p;
+ Py_ssize_t sz;
+ atom *a;
+ PyObject *rc = 0;
+
+ if (!PyArg_ParseTuple(arg, "s#:intern", &p, &sz)) goto end;
+ a = atom_nintern(OBARRAY_TAB(me), p, sz);
+ rc = atom_pywrap(me, a);
+end:
+ return (rc);
+}
+
+static PyObject *oameth_gensym(PyObject *me)
+ { return (atom_pywrap(me, atom_gensym(OBARRAY_TAB(me)))); }
+
+static const PyMappingMethods obarray_pymapping = {
+ gmap_pysize,
+ atom_pyintern,
+ 0
+};
+
+static const PyMethodDef obarray_pymethods[] = {
+ GMAP_ROMETHODS
+#define METHNAME(name) oameth_##name
+ METH (intern, "ATAB.intern(STR) -> A: atom with given name")
+ NAMETH(gensym, "ATAB.gensym() -> A: fresh uninterned atom")
+#undef METHNAME
+ { 0 }
+};
+
+static const PyTypeObject obarray_pytype_skel = {
+ PyVarObject_HEAD_INIT(0, 0) /* Header */
+ "AtomTable", /* @tp_name@ */
+ sizeof(obarray_pyobj), /* @tp_basicsize@ */
+ 0, /* @tp_itemsize@ */
+
+ obarray_pydealloc, /* @tp_dealloc@ */
+ 0, /* @tp_print@ */
+ 0, /* @tp_getattr@ */
+ 0, /* @tp_setattr@ */
+ 0, /* @tp_compare@ */
+ 0, /* @tp_repr@ */
+ 0, /* @tp_as_number@ */
+ PYSEQUENCE(gmap), /* @tp_as_sequence@ */
+ PYMAPPING(obarray), /* @tp_as_mapping@ */
+ 0, /* @tp_hash@ */
+ 0, /* @tp_call@ */
+ 0, /* @tp_str@ */
+ 0, /* @tp_getattro@ */
+ 0, /* @tp_setattro@ */
+ 0, /* @tp_as_buffer@ */
+ Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
+ Py_TPFLAGS_BASETYPE,
+
+ /* @tp_doc@ */
+ "AtomTable()",
+
+ 0, /* @tp_traverse@ */
+ 0, /* @tp_clear@ */
+ 0, /* @tp_richcompare@ */
+ offsetof(obarray_pyobj, wkls), /* @tp_weaklistoffset@ */
+ gmap_pyiter, /* @tp_iter@ */
+ 0, /* @tp_iternext@ */
+ PYMETHODS(obarray), /* @tp_methods@ */
+ 0, /* @tp_members@ */
+ 0, /* @tp_getset@ */
+ 0, /* @tp_base@ */
+ 0, /* @tp_dict@ */
+ 0, /* @tp_descr_get@ */
+ 0, /* @tp_descr_set@ */
+ 0, /* @tp_dictoffset@ */
+ 0, /* @tp_init@ */
+ PyType_GenericAlloc, /* @tp_alloc@ */
+ obarray_pynew, /* @tp_new@ */
+ 0, /* @tp_free@ */
+ 0 /* @tp_is_gc@ */
+};
+
+/*----- Association tables ------------------------------------------------*/
+
+typedef struct {
+ GMAP_PYOBJ_HEAD
+ assoc_table t;
+ PyObject *oaobj;
+} assoc_pyobj;
+static PyTypeObject *assoc_pytype;
+#define ASSOC_PYCHECK(o) PyObject_TypeCheck((o), assoc_pytype)
+#define ASSOC_T(o) (&((assoc_pyobj *)(o))->t)
+#define ASSOC_OAOBJ(o) (((assoc_pyobj *)(o))->oaobj)
+
+struct aentry {
+ assoc_base _b;
+ PyObject *obj;
+};
+
+static void *assoc_gmlookup(PyObject *me, PyObject *key, unsigned *f)
+{
+ struct aentry *e = 0;
+ char *p;
+ size_t sz;
+ atom *a;
+
+ if (TEXT_CHECK(key)) {
+ TEXT_PTRLEN(key, p, sz);
+ a = atom_nintern(OBARRAY_TAB(ASSOC_OAOBJ(me)), p, sz);
+ } else if (ATOM_PYCHECK(key)) {
+ if (atom_check(key)) goto end;
+ if (ATOM_OAOBJ(key) != ASSOC_OAOBJ(me))
+ VALERR("wrong atom table for assoc");
+ a = ATOM_A(key);
+ } else
+ TYERR("expected atom or string");
+ e = assoc_find(ASSOC_T(me), a, f ? sizeof(*e) : 0, f);
+ if (!e) goto end;
+ if (f && !*f) e->obj = 0;
+end:
+ return (e);
+}
+
+static void assoc_gmiterinit(PyObject *me, void *i)
+ { assoc_iter *it = i; ASSOC_MKITER(it, ASSOC_T(me)); }
+
+static void *assoc_gmiternext(PyObject *me, void *i)
+ { assoc_iter *it = i; void *e; ASSOC_NEXT(it, e); return (e); }
+
+static PyObject *assoc_gmentrykey(PyObject *me, void *e)
+ { return (atom_pywrap(ASSOC_OAOBJ(me), ASSOC_ATOM(e))); }
+
+static PyObject *assoc_gmentryvalue(PyObject *me, void *e)
+ { struct aentry *ae = e; RETURN_OBJ(ae->obj); }
+
+static int assoc_gmsetentry(PyObject *me, void *e, PyObject *val)
+{
+ struct aentry *ae = e;
+
+ Py_XDECREF(ae->obj); Py_INCREF(val); ae->obj = val;
+ return (0);
+}
+
+static int assoc_gmdelentry(PyObject *me, void *e)
+ { assoc_remove(ASSOC_T(me), e); return (0); }
+
+static const gmap_ops assoc_gmops = {
+ sizeof(atom_iter),
+ assoc_gmlookup,
+ assoc_gmiterinit,
+ assoc_gmiternext,
+ assoc_gmentrykey,
+ assoc_gmentryvalue,
+ assoc_gmsetentry,
+ assoc_gmdelentry
+};
+
+static PyObject *assoc_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
+{
+ PyObject *oaobj = 0, *map = Py_None;
+ assoc_pyobj *me = 0;
+
+ if (!PyArg_ParseTuple(arg, "|OO!:new", &map, obarray_pytype, &oaobj))
+ { oaobj = 0; goto end; }
+ if (oaobj) Py_INCREF(oaobj);
+ else { oaobj = default_obarray(); if (!oaobj) goto end; }
+ me = PyObject_NEW(assoc_pyobj, cls);
+ me->gmops = &assoc_gmops;
+ assoc_create(&me->t);
+ me->oaobj = oaobj; oaobj = 0;
+ if ((map != Py_None && gmap_pyupdate((PyObject *)me, map)) ||
+ gmap_pyupdate((PyObject *)me, kw))
+ { Py_DECREF(me); me = 0; goto end; }
+end:
+ Py_XDECREF(oaobj);
+ return ((PyObject *)me);
+}
+
+static void assoc_pydealloc(PyObject *me)
+{
+ assoc_iter it;
+ struct aentry *ae;
+
+ ASSOC_MKITER(&it, ASSOC_T(me));
+ for (;;) {
+ ASSOC_NEXT(&it, ae); if (!ae) break;
+ Py_DECREF(ae->obj);
+ }
+ Py_DECREF(ASSOC_OAOBJ(me));
+ FREEOBJ(me);
+}
+
+static Py_ssize_t assoc_pysize(PyObject *me)
+{
+ assoc_table *t = ASSOC_T(me);
+ return (SYM_LIMIT(t->t.mask + 1) - t->load);
+}
+
+static const PyMemberDef assoc_pymembers[] = {
+#define MEMBERSTRUCT assoc_pyobj
+ MEMRNM(table, T_OBJECT, oaobj, READONLY,
+ "AS.table -> ATAB: home atom table")
+#undef MEMBERSTRUCT
+ { 0 }
+};
+
+static const PyMappingMethods assoc_pymapping = {
+ assoc_pysize,
+ gmap_pylookup,
+ gmap_pystore
+};
+
+static const PyTypeObject assoc_pytype_skel = {
+ PyVarObject_HEAD_INIT(0, 0) /* Header */
+ "AssocTable", /* @tp_name@ */
+ sizeof(assoc_pyobj), /* @tp_basicsize@ */
+ 0, /* @tp_itemsize@ */
+
+ assoc_pydealloc, /* @tp_dealloc@ */
+ 0, /* @tp_print@ */
+ 0, /* @tp_getattr@ */
+ 0, /* @tp_setattr@ */
+ 0, /* @tp_compare@ */
+ 0, /* @tp_repr@ */
+ 0, /* @tp_as_number@ */
+ PYSEQUENCE(gmap), /* @tp_as_sequence@ */
+ PYMAPPING(assoc), /* @tp_as_mapping@ */
+ 0, /* @tp_hash@ */
+ 0, /* @tp_call@ */
+ 0, /* @tp_str@ */
+ 0, /* @tp_getattro@ */
+ 0, /* @tp_setattro@ */
+ 0, /* @tp_as_buffer@ */
+ Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
+ Py_TPFLAGS_BASETYPE,
+
+ /* @tp_doc@ */
+ "AssocTable([MAP], [ATAB], [ATOM = VALUE, ...])",
+
+ 0, /* @tp_traverse@ */
+ 0, /* @tp_clear@ */
+ 0, /* @tp_richcompare@ */
+ 0, /* @tp_weaklistoffset@ */
+ gmap_pyiter, /* @tp_iter@ */
+ 0, /* @tp_iternext@ */
+ PYMETHODS(gmap), /* @tp_methods@ */
+ PYMEMBERS(assoc), /* @tp_members@ */
+ 0, /* @tp_getset@ */
+ 0, /* @tp_base@ */
+ 0, /* @tp_dict@ */
+ 0, /* @tp_descr_get@ */
+ 0, /* @tp_descr_set@ */
+ 0, /* @tp_dictoffset@ */
+ 0, /* @tp_init@ */
+ PyType_GenericAlloc, /* @tp_alloc@ */
+ assoc_pynew, /* @tp_new@ */
+ 0, /* @tp_free@ */
+ 0 /* @tp_is_gc@ */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+void atom_pyinit(void)
+{
+ INITTYPE(atom, root);
+ INITTYPE(obarray, root);
+ INITTYPE(assoc, root);
+}
+
+void atom_pyinsert(PyObject *mod)
+{
+ INSERT("Atom", atom_pytype);
+ INSERT("AtomTable", obarray_pytype);
+ INSERT("AssocTable", assoc_pytype);
+};
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Definitions for mLib bindings
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Python interface to mLib.
+ *
+ * mLib/Python is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * mLib/Python is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mLib/Python. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef MLIB_PYTHON_H
+#define MLIB_PYTHON_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "pyke/pyke-mLib.h"
+
+PUBLIC_SYMBOLS;
+#include <mLib/alloc.h>
+#include <mLib/assoc.h>
+#include <mLib/atom.h>
+#include <mLib/daemonize.h>
+#include <mLib/fdflags.h>
+#include <mLib/fdpass.h>
+#include <mLib/mdup.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+PRIVATE_SYMBOLS;
+
+/*----- Miscellaneous preliminaries ---------------------------------------*/
+
+/* Submodules. */
+#define MODULES(_) \
+ _(atom) _(sys) _(ui) \
+ _(pyke_gmap)
+MODULES(DECLARE_MODINIT)
+
+/*----- User interface functions ------------------------------------------*/
+
+extern int ui_pyready(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
--- /dev/null
+/* -*-c-*-
+ *
+ * Where the fun begins
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Python interface to mLib.
+ *
+ * mLib/Python is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * mLib/Python is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mLib/Python. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "mLib-python.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static PyObject *meth__ready(PyObject *me, PyObject *arg)
+{
+ PyObject *mod;
+
+ if (!PyArg_ParseTuple(arg, "O!:_ready", &PyModule_Type, &mod))
+ goto end;
+ Py_XDECREF(home_module); home_module = mod; Py_INCREF(home_module);
+ if (ui_pyready()) goto end;
+ RETURN_NONE;
+end:
+ return (0);
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(name) meth_##name
+ METH (_ready, 0)
+#undef METHNAME
+ { 0 }
+};
+
+#ifdef PY3
+static PyModuleDef moddef = {
+ PyModuleDef_HEAD_INIT,
+ "mLib._base", /* @m_name@ */
+ "Low-level module for mLib bindings. Use `mLib' instead.",
+ /* @m_doc@ */
+ 0, /* @m_size@ */
+ 0, /* @m_methods@ */
+ 0, /* @m_slots@ */
+ 0, /* @m_traverse@ */
+ 0, /* @m_clear@ */
+ 0 /* @m_free@ */
+};
+#endif
+
+EXPORT PyMODINIT_FUNC PY23(init_base, PyInit__base)(void)
+{
+ PyObject *mod;
+
+ modname = TEXT_FROMSTR("mLib");
+ addmethods(methods);
+ INIT_MODULES;
+#ifdef PY3
+ moddef.m_methods = donemethods();
+ mod = PyModule_Create(&moddef);
+#else
+ mod = Py_InitModule("mLib._base", donemethods());
+#endif
+ INSERT_MODULES;
+#ifdef PY3
+ return (mod);
+#endif
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+### -*-python-*-
+###
+### Setup for mLib bindings
+###
+### (c) 2019 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 2 of the License, or (at your
+### option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python. If not, write to the Free Software
+### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+### USA.
+
+import sys as _sys
+import types as _types
+
+###--------------------------------------------------------------------------
+### Import the main C extension module.
+
+if _sys.version_info >= (3,): from . import _base
+else: import _base
+
+###--------------------------------------------------------------------------
+### Basic stuff.
+
+## Register our module.
+_base._ready(_sys.modules[__name__])
+def default_lostexchook(why, ty, val, tb):
+ """`mLib.lostexchook(WHY, TY, VAL, TB)' reports lost exceptions."""
+ _sys.stderr.write("\n\n!!! LOST EXCEPTION: %s\n" % why)
+ _sys.excepthook(ty, val, tb)
+ _sys.stderr.write("\n")
+lostexchook = default_lostexchook
+
+## For the benefit of the reporter functions, we need the program name.
+_base.ego(_sys.argv[0])
+
+## Initialize the module.
+def _init():
+ d = globals()
+ b = _base.__dict__;
+ for i in b:
+ if i[0] != '_': d[i] = b[i];
+_init()
+
+## A handy function for our work: add the methods of a named class to an
+## existing class. This is how we write the Python-implemented parts of our
+## mostly-C types.
+def _augment(c, cc):
+ for i in cc.__dict__:
+ a = cc.__dict__[i]
+ if type(a) is _types.MethodType:
+ a = a.im_func
+ elif type(a) not in (_types.FunctionType, staticmethod, classmethod):
+ continue
+ setattr(c, i, a)
+
+###--------------------------------------------------------------------------
+### Atoms.
+
+DEFAULT_ATOMTABLE = AtomTable()
+
+if _sys.version_info >= (3,):
+ def atoms(): return iter(DEFAULT_ATOMTABLE.values())
+else:
+ def atoms(): return DEFAULT_ATOMTABLE.itervalues()
+
+class _tmp:
+ def __repr__(me): return "Atom(%r)" % me.name
+ __str__ = __repr__
+_augment(Atom, _tmp)
+
+###--------------------------------------------------------------------------
+### User-interface funtions.
+
+def pquis(msg, file = _sys.stdout):
+ "pquis(MSG, [file = sys.stdout]): write MSG, replacing `$' by program name"
+ file.write(msg.replace("$", quis))
+
+###----- That's all, folks --------------------------------------------------
#! /usr/bin/python
import distutils.core as DC
-import Pyrex.Distutils as PXD
import mdwsetup as MS
-MS.pkg_config('mLib', '2.1.0')
+MS.pkg_config('mLib', '2.2.2.1')
-mLib = DC.Extension('mLib', ['mLib.pyx', 'atom-base.c', 'array.c'],
+mLib = DC.Extension('mLib._base',
+ ['mLib.c', 'atom.c', 'sys.c', 'ui.c',
+ 'pyke/pyke.c', 'pyke/mapping.c'],
##extra_compile_args = ['-O0'],
include_dirs = MS.uniquify(MS.INCLUDEDIRS),
library_dirs = MS.uniquify(MS.LIBDIRS),
MS.setup(name = 'mLib-python',
description = 'Python interface to mLib utilities library',
+ url = 'https://git.distorted.org.uk/~mdw/mLib-python/',
author = 'Straylight/Edgeware',
author_email = 'mdw@distorted.org.uk',
license = 'GNU General Public License',
- ext_modules = [mLib],
- genfiles = [MS.Derive('base64.pyx', 'codec.pyx.in',
- {'CLASS': 'Base64', 'PREFIX': 'base64'}),
- MS.Derive('base32.pyx', 'codec.pyx.in',
- {'CLASS': 'Base32', 'PREFIX': 'base32'}),
- MS.Derive('hex.pyx', 'codec.pyx.in',
- {'CLASS': 'Hex', 'PREFIX': 'hex'})],
- cleanfiles = ['mLib.c'],
- cmdclass = { 'build_ext': PXD.build_ext })
+ packages = ['mLib'],
+ unittest_dir = "t",
+ unittests = ["t-misc", "t-atom", "t-ui"],
+ ext_modules = [mLib])
--- /dev/null
+/* -*-c-*-
+ *
+ * System-specific functionality
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Python interface to mLib.
+ *
+ * mLib/Python is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * mLib/Python is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mLib/Python. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "mLib-python.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static int getint(PyObject *obj, int *i_out)
+{
+ PyObject *t = 0;
+ long i;
+ int rc = -1;
+
+ t = PyNumber_Index(obj); if (!t) goto end;
+ i = PyInt_AsLong(t); if (i == -1 && PyErr_Occurred()) goto end;
+ if (INT_MIN > i || i > INT_MAX) OVFERR("out of range");
+ *i_out = i;
+ rc = 0;
+end:
+ Py_XDECREF(t);
+ return (rc);
+}
+
+static int convfd(PyObject *obj, void *p)
+{
+ int *fd_out = p;
+ PyObject *t = 0;
+ int rc = 0;
+
+ if (getint(obj, fd_out)) {
+ PyErr_Clear();
+ t = PyObject_CallMethod(obj, "fileno", 0); if (!t) goto end;
+ if (getint(t, fd_out)) goto end;
+ }
+ rc = 1;
+end:
+ Py_XDECREF(t);
+ return (rc);
+}
+
+static PyObject *meth_detachtty(PyObject *me)
+ { detachtty(); RETURN_NONE; }
+
+static PyObject *meth_daemonize(PyObject *me)
+{
+ if (daemonize()) OSERR(0);
+ RETURN_NONE;
+end:
+ return (0);
+}
+
+static PyObject *meth_fdflags(PyObject *me, PyObject *arg, PyObject *kw)
+{
+ int fd;
+ unsigned fbic = 0, fxor = 0, fdbic = 0, fdxor = 0;
+ static const char *const kwlist[] =
+ { "file", "fbic", "fxor", "fdbic", "fdxor", 0 };
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&O&O&O&:fdflags", KWLIST,
+ convfd, &fd,
+ convuint, &fbic, convuint, &fxor,
+ convuint, &fdbic, convuint, &fdxor))
+ goto end;
+ if (fdflags(fd, fbic, fxor, fdbic, fdxor)) OSERR(0);
+ RETURN_NONE;
+end:
+ return (0);
+}
+
+static PyObject *meth_fdsend(PyObject *me, PyObject *arg, PyObject *kw)
+{
+ int sock, fd;
+ struct bin buf;
+ ssize_t n;
+ PyObject *rc = 0;
+ static const char *const kwlist[] = { "sock", "file", "buffer", 0 };
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&O&:fdsend", KWLIST,
+ convfd, &sock, convfd, &fd,
+ convbin, &buf))
+ goto end;
+ n = fdpass_send(sock, fd, buf.p, buf.sz); if (n < 0) OSERR(0);
+ rc = PyInt_FromLong(n);
+end:
+ return (rc);
+}
+
+static PyObject *meth_fdrecv(PyObject *me, PyObject *arg, PyObject *kw)
+{
+ int sock, fd;
+ size_t sz;
+ ssize_t n;
+ void *p;
+ PyObject *buf = 0, *rc = 0;
+ static const char *const kwlist[] = { "sock", "size", 0 };
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:fdrecv", KWLIST,
+ convfd, &sock, convszt, &sz))
+ goto end;
+ BIN_PREPAREWRITE(buf, p, sz);
+ n = fdpass_recv(sock, &fd, p, sz); if (n < 0) OSERR(0);
+ BIN_DONEWRITE(buf, n);
+ rc = Py_BuildValue("(iN)", fd, buf); buf = 0;
+end:
+ Py_XDECREF(buf);
+ return (rc);
+}
+
+static PyObject *meth_mdup(PyObject *me, PyObject *arg)
+{
+ PyObject *v;
+ PyObject *t = 0, *u = 0, *rc = 0;
+ Py_ssize_t n, m;
+ mdup_fd *vv;
+ size_t i;
+ int err;
+
+ if (!PyArg_ParseTuple(arg, "O:mdup", &v)) goto end;
+ n = PySequence_Size(v); if (n < 0) goto end;
+ vv = xmalloc(n*sizeof(*vv));
+ for (i = 0; i < n; i++) {
+ t = PySequence_GetItem(v, i); if (!t) goto end;
+ m = PySequence_Size(t); if (m < 0) goto end;
+ if (m != 2) VALERR("expected a list of pairs");
+
+ u = PySequence_GetItem(t, 0);
+ if (getint(u, &vv[i].cur)) goto end;
+ Py_DECREF(u); u = 0;
+
+ u = PySequence_GetItem(t, 1);
+ if (getint(u, &vv[i].want)) goto end;
+ Py_DECREF(u); u = 0;
+
+ Py_DECREF(t); t = 0;
+ }
+
+ err = mdup(vv, n);
+
+ for (i = 0; i < n; i++) {
+ t = Py_BuildValue("(ii)", vv[i].cur, vv[i].want);
+ if (PySequence_SetItem(v, i, t)) goto end;
+ Py_DECREF(t); t = 0;
+ }
+
+ if (err) OSERR(0);
+ rc = v; Py_INCREF(rc);
+
+end:
+ Py_XDECREF(t); Py_XDECREF(u);
+ return (rc);
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(name) meth_##name
+ NAMETH(detachtty, "detachtty(): fork, detatch controlling terminal")
+ NAMETH(daemonize, "daemonize(): fork and become a daemon")
+ KWMETH(fdflags, "fdflags(FILE, [fbic = 0], [fxor = 0], "
+ "[fdbic = 0], [fdxor = 0])")
+ KWMETH(fdsend, "fdsend(FILE, FD, BUFFER) -> N")
+ KWMETH(fdrecv, "fdrecv(FILE, SIZE) -> FD, BUFFER")
+ METH (mdup, "mdup(LIST) -> LIST:\n"
+ " LIST is a list (mutable sequence) of pairs (CUR, WANT). Duplicate\n"
+ " each CUR file descriptor as WANT (may be -1 to mean `don't care'),\n"
+ " closing original CUR. Works even if there are cycles. LIST is\n"
+ " updated in place with CUR reflecting the new file descriptors even\n"
+ " on error. Returns the same LIST on success.")
+#undef METHNAME
+ { 0 }
+};
+
+void sys_pyinit(void)
+{
+ addmethods(methods);
+}
+
+void sys_pyinsert(PyObject *mod)
+{
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+### -*-python-*-
+###
+### Test atoms and related functionality
+###
+### (c) 2019 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 2 of the License, or (at your
+### option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python. If not, write to the Free Software
+### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+### USA.
+
+import mLib as M
+import testutils as T
+import unittest as U
+
+###--------------------------------------------------------------------------
+class TestAtoms (U.TestCase):
+
+ def test_simple(me):
+ foo = M.Atom("foo")
+ bar = M.Atom("bar")
+ me.assertTrue(foo is M.Atom("foo"))
+ me.assertTrue(foo is not bar)
+ me.assertEqual(foo, foo)
+ me.assertNotEqual(foo, bar)
+ me.assertEqual(set(M.atoms()), set([foo, bar]))
+ me.assertEqual(foo.name, "foo")
+ me.assertTrue(foo.internedp)
+ me.assertEqual(foo.home, M.DEFAULT_ATOMTABLE)
+
+ def test_obarray(me):
+ tab = M.AtomTable()
+ foo = tab.intern("foo")
+ bar = M.Atom("bar", tab)
+ g0 = tab.gensym()
+ g1 = M.Atom(table = tab)
+ me.assertEqual(bar, tab.intern("bar"))
+ me.assertNotEqual(foo, M.Atom("foo"))
+ for sym in [foo, bar, g0, g1]:
+ me.assertEqual(sym.home, tab)
+ me.assertTrue(sym.livep)
+ for sym in [foo, bar]: me.assertTrue(sym.internedp)
+ for sym in [g0, g1]: me.assertFalse(sym.internedp)
+ me.assertEqual(tab["foo"], foo)
+ me.assertEqual(tab.get("spong"), None)
+ me.assertFalse("spong" in tab)
+ spong = tab["spong"]
+ me.assertTrue("spong" in tab)
+ me.assertEqual(set(T.itervalues(tab)), set([foo, bar, spong, g0, g1]))
+ del tab
+ for sym in [foo, bar, spong, g0, g1]: me.assertFalse(sym.livep)
+ me.assertRaises(ValueError, getattr, foo, "name")
+
+ tab = M.AtomTable()
+ a = tab["a"]
+ me.assertRaises(TypeError, M.Atom, None, tab, foo = "extra!")
+ me.assertRaises(TypeError, M.Atom, table = tab, bar = "extra!")
+ me.assertRaises(TypeError, M.Atom, None, tab, "extra!")
+ me.assertTrue(a.livep)
+ del tab
+ me.assertFalse(a.livep)
+
+
+###--------------------------------------------------------------------------
+class TestAssoc (T.MutableMappingTestMixin):
+
+ def setUp(me):
+ me._obarray = M.AtomTable()
+
+ def _mkkey(me, i): return me._obarray["k#%d" % i]
+ def _getkey(me, k): return int(k.name[2:])
+
+ def test_mapping(me):
+ me.check_mapping(lambda: M.AssocTable(None, me._obarray))
+
+ def test_assoc(me):
+ obarray = me._obarray
+ foo = obarray["foo"]
+ bar = obarray["bar"]
+ baz = obarray["baz"]
+ tab = M.AssocTable({ foo: 1 }, obarray, bar = 2)
+ me.assertEqual(tab.table, obarray)
+ me.assertEqual(tab["foo"], 1)
+ me.assertEqual(tab[bar], 2)
+ me.assertRaises(ValueError, tab.get, M.Atom("foo"))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
--- /dev/null
+### -*- mode: python, coding: utf-8 -*-
+###
+### Miscellaneous tests
+###
+### (c) 2019 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software: you can redistribute it and/or
+### modify it under the terms of the GNU General Public License as
+### published by the Free Software Foundation; either version 2 of the
+### License, or (at your option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python. If not, write to the Free Software
+### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+### USA.
+
+###--------------------------------------------------------------------------
+### Imported modules.
+
+import mLib as M
+import unittest as U
+
+###--------------------------------------------------------------------------
+class Tests (U.TestCase):
+
+ def test_path(me):
+ me.assertTrue(M.__file__.startswith("build"))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
--- /dev/null
+### -*-python-*-
+###
+### Test system-specific functionality
+###
+### (c) 2019 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 2 of the License, or (at your
+### option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python. If not, write to the Free Software
+### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+### USA.
+
+import mLib as M
+import fcntl as FC
+import os as OS
+import socket as SK
+import sys as SYS
+import testutils as T
+import unittest as U
+
+## Cygwin leaves cruft in the top bits of inode numbers for some reason.
+if SYS.platform.startswith('cygwin'):
+ def _hack_inode(ino): return ino&0x0000ffffffffffff
+else:
+ def _hack_inode(ino): return ino
+
+###--------------------------------------------------------------------------
+class TestSys (U.TestCase):
+
+ def _fdid(me, fd):
+ st = OS.fstat(fd)
+ return st.st_dev, _hack_inode(st.st_ino)
+
+ def _check_same_file(me, fd0, fd1):
+ me.assertEqual(me._fdid(fd0), me._fdid(fd1))
+
+ def _make_fd(me):
+ fd, other = OS.pipe()
+ OS.close(other)
+ return fd
+
+ def test_fdflags(me):
+ fd = me._make_fd()
+ f = OS.fdopen(fd, "r")
+ try:
+ of, ofd = FC.fcntl(fd, FC.F_GETFL), FC.fcntl(fd, FC.F_GETFD)
+ M.fdflags(fd, fbic = OS.O_NONBLOCK, fxor = OS.O_NONBLOCK)
+ me.assertEqual(FC.fcntl(fd, FC.F_GETFL), of | OS.O_NONBLOCK)
+ M.fdflags(f, fbic = OS.O_NONBLOCK)
+ me.assertEqual(FC.fcntl(fd, FC.F_GETFL), of&~OS.O_NONBLOCK)
+ M.fdflags(fd, fdbic = FC.FD_CLOEXEC, fdxor = FC.FD_CLOEXEC)
+ me.assertEqual(FC.fcntl(fd, FC.F_GETFD), ofd | FC.FD_CLOEXEC)
+ M.fdflags(f, fdbic = FC.FD_CLOEXEC)
+ me.assertEqual(FC.fcntl(fd, FC.F_GETFD), ofd&~FC.FD_CLOEXEC)
+ me.assertRaises(AttributeError, M.fdflags, "bzzt")
+ me.assertRaises(OSError, M.fdflags, -1, 1, 1)
+ finally:
+ f.close()
+
+ def test_fdpass(me):
+ sk0, sk1 = SK.socketpair(SK.AF_UNIX, SK.SOCK_STREAM)
+ fd = me._make_fd()
+ nfd = -1
+ try:
+ msg = T.bin("some unnecessary message")
+ n = M.fdsend(sk0, fd, msg)
+ me.assertEqual(n, len(msg))
+ nfd, buf = M.fdrecv(sk1, 32)
+ me.assertEqual(buf, msg)
+ me._check_same_file(fd, nfd)
+ OS.close(nfd); nfd = -1
+ finally:
+ sk0.close(); sk1.close()
+ OS.close(fd)
+ if nfd != -1: OS.close(nfd)
+
+ def test_mdup(me):
+ NFD = 5
+ fds = [me._make_fd() for i in T.range(NFD)]
+ v = [(fds[i], fds[(i + 1)%5]) for i in T.range(NFD)]
+ try:
+ id = [me._fdid(fds[i]) for i in T.range(NFD)]
+ vv = M.mdup(v)
+ me.assertTrue(vv is v)
+ for i in T.range(NFD):
+ cur, want = v[i]
+ me.assertEqual(cur, want)
+ me.assertEqual(cur, fds[(i + 1)%NFD])
+ me.assertEqual(me._fdid(cur), id[i])
+ finally:
+ for fd, _ in v: OS.close(fd)
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
--- /dev/null
+### -*-python-*-
+###
+### Test ui functionality
+###
+### (c) 2019 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 2 of the License, or (at your
+### option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python. If not, write to the Free Software
+### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+### USA.
+
+import mLib as M
+import os as OS
+import sys as SYS
+import testutils as T
+import unittest as U
+
+###--------------------------------------------------------------------------
+class TestUI (U.TestCase):
+
+ def test_quis(me):
+ old = M.quis
+ M.ego(OS.path.join("test", "thing")); me.assertEqual(M.quis, "thing")
+ buf = T.StringIO(); M.pquis("a simple $ test", file = buf)
+ me.assertEqual(buf.getvalue(), "a simple thing test")
+ M.ego(OS.path.join("other-thing")); me.assertEqual(M.quis, "other-thing")
+ M.ego(old)
+
+ def _capture_stderr(me):
+ pin, pout = OS.pipe(); OS.dup2(pout, 2); OS.close(pout)
+ return pin
+
+ @T.subprocess
+ def test_report(me):
+ pin = me._capture_stderr()
+ ref = T.bin("%s: all ok really\n" % M.quis)
+ M.moan("all ok really")
+ stuff = OS.read(pin, 32)
+ assert stuff == ref
+
+ @T.subprocess
+ def test_die(me):
+ pin = me._capture_stderr()
+ ref = T.bin("%s: so this is it\n" % M.quis)
+ try: M.die("so this is it", 123)
+ except SystemExit:
+ stuff = OS.read(pin, 32)
+ assert stuff == ref
+ assert SYS.exc_info()[1].code == 123
+ else:
+ raise AssertionError("die didn't exit")
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
--- /dev/null
+### -*-python-*-
+###
+### Test utilities
+###
+### (c) 2019 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to mLib.
+###
+### mLib/Python is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 2 of the License, or (at your
+### option) any later version.
+###
+### mLib/Python is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with mLib/Python. If not, write to the Free Software
+### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+### USA.
+
+###--------------------------------------------------------------------------
+### Imported modules.
+
+import mLib as M
+import contextlib as CTX
+import os as OS
+import sys as SYS
+if SYS.version_info >= (3,): import builtins as B
+else: import __builtin__ as B
+import unittest as U
+
+###--------------------------------------------------------------------------
+### Main code.
+
+## Some compatibility hacks.
+if SYS.version_info >= (3,):
+ PY2, PY3 = False, True
+ def bin(x): return x.encode('iso8859-1')
+ def py23(x, y): return y
+ range = range
+ byteseq = bytes
+ long = int
+ imap = map
+ def iterkeys(m): return m.keys()
+ def itervalues(m): return m.values()
+ def iteritems(m): return m.items()
+ from io import StringIO
+ MAXFIXNUM = SYS.maxsize
+else:
+ import itertools as I
+ PY2, PY3 = True, False
+ def bin(x): return x
+ def py23(x, y): return x
+ range = xrange
+ long = long
+ imap = I.imap
+ def byteseq(seq): return "".join(map(chr, seq))
+ def iterkeys(m): return m.iterkeys()
+ def itervalues(m): return m.itervalues()
+ def iteritems(m): return m.iteritems()
+ from cStringIO import StringIO
+ MAXFIXNUM = SYS.maxint
+
+def subprocess(fn):
+ def func(test):
+ for f in [SYS.stdout, SYS.stderr]: f.flush()
+ kid = OS.fork()
+ if kid == 0:
+ old_stderr_fd = OS.dup(2)
+ try: fn(test)
+ except:
+ for f in [SYS.stdout, SYS.stderr]: f.flush()
+ OS.dup2(old_stderr_fd, 2)
+ SYS.excepthook(*SYS.exc_info())
+ SYS.stderr.flush()
+ OS._exit(111)
+ else:
+ for f in [SYS.stdout, SYS.stderr]: f.flush()
+ OS._exit(0)
+ _, st = OS.waitpid(kid, 0)
+ if OS.WIFSIGNALED(st):
+ test.fail("child killed by signal %d" % OS.WTERMSIG(st))
+ elif not OS.WIFEXITED(st):
+ test.fail("child terminated with unknown status %x" % st)
+ else:
+ rc = OS.WEXITSTATUS(st)
+ if rc != 0: test.fail("child exited with status %d" % rc)
+ return func
+
+
+class ImmutableMappingTextMixin (U.TestCase):
+
+ ## Subclass stubs.
+ def _mkkey(me, i): return "k#%d" % i
+ def _getkey(me, k): return int(k[2:])
+ def _getvalue(me, v): return int(v[2:])
+ def _getitem(me, it): k, v = it; return me._getkey(k), me._getvalue(v)
+
+ def check_immutable_mapping(me, map, model):
+
+ ## Lookup.
+ limk = 0
+ any = False
+ me.assertEqual(len(map), len(model))
+ for k, v in iteritems(model):
+ any = True
+ if k >= limk: limk = k + 1
+ me.assertTrue(me._mkkey(k) in map)
+ if PY2: me.assertTrue(map.has_key(me._mkkey(k)))
+ me.assertEqual(me._getvalue(map[me._mkkey(k)]), v)
+ me.assertEqual(me._getvalue(map.get(me._mkkey(k))), v)
+ if any: me.assertTrue(me._mkkey(k) in map)
+ if PY2: me.assertFalse(map.has_key(me._mkkey(limk)))
+ me.assertRaises(KeyError, lambda: map[me._mkkey(limk)])
+ me.assertEqual(map.get(me._mkkey(limk)), None)
+
+ if PY3:
+ empty = set()
+
+ for k, v in iteritems(map):
+ me.assertTrue(k in map.keys())
+ me.assertTrue((k, v) in map.items())
+ me.assertFalse(me._mkkey(limk) in map.keys())
+
+ for viewfn, getfn in [(lambda x: x.keys(), me._getkey),
+ (lambda x: x.items(), me._getitem)]:
+ rview, rview2, mview = viewfn(map), viewfn(map), viewfn(model)
+ me.assertEqual(set(imap(getfn, rview)), set(mview))
+ me.assertEqual(rview, rview2)
+ me.assertEqual(rview, set(rview2))
+ me.assertEqual(rview | empty, set(rview))
+ me.assertEqual(rview | rview2, set(rview))
+ me.assertEqual(rview ^ empty, set(rview))
+ me.assertEqual(rview ^ rview, empty)
+ me.assertEqual(rview & empty, empty)
+ me.assertEqual(len(rview), len(model))
+
+ if any: subset = set(rview2); subset.pop()
+ superset = set(rview2); superset.add(object())
+
+ me.assertFalse(rview < rview2)
+ me.assertTrue(rview < superset)
+ me.assertFalse(superset < rview)
+ me.assertFalse(rview < empty)
+ if any:
+ me.assertTrue(empty < rview)
+ me.assertTrue(subset < rview)
+ me.assertFalse(rview < subset)
+
+ me.assertTrue(rview <= rview2)
+ me.assertTrue(rview <= superset)
+ me.assertFalse(superset <= rview)
+ if any:
+ me.assertTrue(empty <= rview)
+ me.assertFalse(rview <= empty)
+ me.assertTrue(subset <= rview)
+ me.assertFalse(rview <= subset)
+
+ me.assertTrue(rview >= rview2)
+ me.assertTrue(superset >= rview)
+ me.assertFalse(rview >= superset)
+ if any:
+ me.assertTrue(rview >= empty)
+ me.assertFalse(empty >= rview)
+ me.assertTrue(rview >= subset)
+ me.assertFalse(subset >= rview)
+
+ me.assertFalse(rview > rview2)
+ me.assertTrue(superset > rview)
+ me.assertFalse(rview > superset)
+ me.assertFalse(empty > rview)
+ if any:
+ me.assertTrue(rview > empty)
+ me.assertTrue(rview > subset)
+ me.assertFalse(subset > rview)
+
+ else:
+ for listfn, getfn in [(lambda x: x.keys(), me._getkey),
+ (lambda x: x.values(), me._getvalue),
+ (lambda x: x.items(), me._getitem)]:
+ rlist, mlist = listfn(map), listfn(model)
+ me.assertEqual(type(rlist), list)
+ rlist = B.map(getfn, rlist)
+ rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist)
+ for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey),
+ (lambda x: x.itervalues(), me._getvalue),
+ (lambda x: x.iteritems(), me._getitem)]:
+ me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model)))
+
+class MutableMappingTestMixin (ImmutableMappingTextMixin):
+
+ ## Subclass stubs.
+ def _mkvalue(me, i): return "v#%d" % i
+
+ def check_mapping(me, emptymapfn):
+
+ map = emptymapfn()
+ me.assertEqual(len(map), 0)
+
+ if not PY3:
+ def check_views():
+ me.check_immutable_mapping(map, model)
+ else:
+ kview, iview, vview = map.keys(), map.items(), map.values()
+ def check_views():
+ me.check_immutable_mapping(map, model)
+ me.assertEqual(set(imap(me._getkey, kview)), model.keys())
+ me.assertEqual(set(imap(me._getitem, iview)), model.items())
+ me.assertEqual(set(imap(me._getvalue, vview)), set(model.values()))
+
+ model = { 1: 101, 2: 202, 4: 404 }
+ for k, v in iteritems(model): map[me._mkkey(k)] = me._mkvalue(v)
+ check_views()
+
+ model.update({ 2: 212, 6: 606, 7: 707 })
+ map.update({ me._mkkey(2): me._mkvalue(212),
+ me._mkkey(6): me._mkvalue(606) },
+ **{ me._mkkey(7): me._mkvalue(707) })
+ check_views()
+
+ model[9] = 909
+ map[me._mkkey(9)] = me._mkvalue(909)
+ check_views()
+
+ model[9] = 919
+ map[me._mkkey(9)] = me._mkvalue(919)
+ check_views()
+
+ map.setdefault(me._mkkey(9), me._mkvalue(929))
+ check_views()
+
+ model[8] = 808
+ map.setdefault(me._mkkey(8), me._mkvalue(808))
+ check_views()
+
+ me.assertRaises(KeyError, map.pop, me._mkkey(5))
+ obj = object()
+ me.assertEqual(map.pop(me._mkkey(5), obj), obj)
+ me.assertEqual(me._getvalue(map.pop(me._mkkey(8))), 808)
+ del model[8]
+ check_views()
+
+ del model[9]
+ del map[me._mkkey(9)]
+ check_views()
+
+ k, v = map.popitem()
+ mk, mv = me._getkey(k), me._getvalue(v)
+ me.assertEqual(model[mk], mv)
+ del model[mk]
+ check_views()
+
+ map.clear()
+ model = {}
+ check_views()
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+/* -*-c-*-
+ *
+ * mLib user interface
+ *
+ * (c) 2019 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Python interface to mLib.
+ *
+ * mLib/Python is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * mLib/Python is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with mLib/Python. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "mLib-python.h"
+
+/*----- Program name ------------------------------------------------------*/
+
+static int set_program_name(void)
+{
+ PyObject *p = TEXT_FROMSTR(pn__name);
+ int rc = -1;
+
+ if (!home_module) SYSERR("home module not set");
+ if (PyObject_SetAttrString(home_module, "quis", p)) goto end;
+ pn__name = TEXT_PTR(p); p = 0; rc = 0;
+end:
+ Py_XDECREF(p);
+ return (rc);
+}
+
+static PyObject *meth_ego(PyObject *me, PyObject *arg)
+{
+ char *p;
+ const char *old;
+
+ if (!PyArg_ParseTuple(arg, "s:ego", &p)) goto end;
+ old = pn__name; ego(p);
+ if (set_program_name()) { pn__name = old; goto end; }
+ RETURN_NONE;
+end:
+ return (0);
+}
+
+/*----- Error reporting ---------------------------------------------------*/
+
+static PyObject *meth_moan(PyObject *me, PyObject *arg)
+{
+ char *p;
+
+ if (!PyArg_ParseTuple(arg, "s:moan", &p)) goto end;
+ moan("%s", p);
+ RETURN_NONE;
+end:
+ return (0);
+}
+
+static PyObject *meth_die(PyObject *me, PyObject *arg, PyObject *kw)
+{
+ const char *const kwlist[] = { "msg", "rc", 0 };
+ char *p;
+ int rc = 126;
+ PyObject *rcobj = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|i:moan", KWLIST, &p, &rc))
+ goto end;
+ rcobj = PyInt_FromLong(rc); if (!rcobj) goto end;
+ moan("%s", p);
+ PyErr_SetObject(PyExc_SystemExit, rcobj);
+end:
+ Py_XDECREF(rcobj);
+ return (0);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+static const PyMethodDef methods[] = {
+#define METHNAME(name) meth_##name
+ METH (ego, "ego(PROG): set program name")
+ METH (moan, "moan(MSG): report a warning")
+ KWMETH(die, "die(MSG, [rc = 126]): report a fatal error and exit")
+#undef METHNAME
+ { 0 }
+};
+
+void ui_pyinit(void)
+{
+ addmethods(methods);
+}
+
+void ui_pyinsert(PyObject *mod)
+{
+}
+
+int ui_pyready(void) { return (set_program_name()); }
+
+/*----- That's all, folks -------------------------------------------------*/