chiark / gitweb /
@@@ mdwopt buggy wip
authorMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 14:30:26 +0000 (15:30 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 14:32:41 +0000 (15:32 +0100)
mLib-python.h
pyke/pyke.c
pyke/pyke.h
t/t-misc.py
t/t-ui.py
ui.c

index 33000d4492be5217af8dfd8ae90ec66dd951c58a..6005b2458f7c939ef8179ffde942e5d139055968 100644 (file)
@@ -41,6 +41,8 @@ PUBLIC_SYMBOLS;
 #include <mLib/assoc.h>
 #include <mLib/atom.h>
 #include <mLib/daemonize.h>
+#include <mLib/darray.h>
+#include <mLib/dstr.h>
 #include <mLib/fdflags.h>
 #include <mLib/fdpass.h>
 #include <mLib/mdup.h>
index 330f15cab6e95d2f88221d647993ad84be54d3dc..2b65c9f8d4fec294756264249a5ec8dcd9b90599 100644 (file)
@@ -44,6 +44,41 @@ PyObject *getulong(unsigned long w)
 PyObject *getbool(int b)
   { if (b) RETURN_TRUE; else RETURN_FALSE; }
 
+int convlong(PyObject *o, void *pp)
+{
+  long *p = pp;
+  PyObject *t;
+
+  if (!o) VALERR("can't delete");
+#ifdef PY2
+  if (PyInt_Check(o))
+    *p = PyInt_AS_LONG(o);
+  else
+#endif
+  {
+    if ((t = PyNumber_Index(o)) == 0) goto end;
+    *p = PyLong_AsLong(t);
+    Py_DECREF(t);
+    if (PyErr_Occurred()) goto end;
+  }
+  return (1);
+end:
+  return (0);
+}
+
+int convint(PyObject *o, void *pp)
+{
+  long l;
+  int *p = pp;
+
+  if (!convlong(o, &l)) goto end;
+  if (INT_MIN > l || l > INT_MAX) VALERR("out of range");
+  *p = l;
+  return (1);
+end:
+  return (0);
+}
+
 int convulong(PyObject *o, void *pp)
 {
   unsigned long *p = pp;
@@ -58,7 +93,7 @@ int convulong(PyObject *o, void *pp)
   } else
 #endif
   {
-    if ((t = PyNumber_Long(o)) == 0) goto end;
+    if ((t = PyNumber_Index(o)) == 0) goto end;
     *p = PyLong_AsUnsignedLong(t);
     Py_DECREF(t);
     if (PyErr_Occurred()) goto end;
index ada47fc43867da317a9755389219974b7b75f558..477e9d0df8453a9e39138cb91b9acb777c410d07 100644 (file)
@@ -356,6 +356,8 @@ extern void restore_exception(struct excinfo *, const char *, ...);
  * checking where applicable.
  */
 struct bin { const void *p; Py_ssize_t sz; };
+extern int convlong(PyObject *, void *); /* long */
+extern int convint(PyObject *, void *); /* int */
 extern int convulong(PyObject *, void *); /* unsigned long */
 extern int convuint(PyObject *, void *); /* unsigned int */
 extern int convszt(PyObject *, void *);        /* size_t */
index 66ea0e16378737f017ae56c50c048da0c55df8c7..fb84091a7b2b2e3e0040d5fa7c1b7da97271e869 100644 (file)
@@ -34,6 +34,7 @@ import unittest as U
 class Tests (U.TestCase):
 
   def test_path(me):
+    print(M.__file__)
     me.assertTrue(M.__file__.startswith("build"))
 
 ###----- That's all, folks --------------------------------------------------
index 60e49b6efb1bc5ee522c2013a95af13ebf3944e6..4d3a6e793ac8c48ac2ee6f59ee5a05432fcbfdba 100644 (file)
--- a/t/t-ui.py
+++ b/t/t-ui.py
@@ -65,6 +65,10 @@ class TestUI (U.TestCase):
     else:
       raise AssertionError("die didn't exit")
 
+  def test_mdwopt(me):
+    mo = M.MdwOpt()
+    print(list(mo))
+
 ###----- That's all, folks --------------------------------------------------
 
 if __name__ == "__main__": U.main()
diff --git a/ui.c b/ui.c
index 4316d93a926b806bd02e8e6557d8f7d2d1345140..0ac0fa17a083b58df143d81a050702a9fb8160ed 100644 (file)
--- a/ui.c
+++ b/ui.c
@@ -96,43 +96,76 @@ struct optextra {
 
 typedef struct {
   PyObject_HEAD
-  mdwopt_data opt;
-  char **argv, *stringdata;
-  char *shortopt;
+  char *stringdata;
   struct option *longopt;
+  struct optextra *extra;
+  char **argv;
+  size_t nlong, narg;
+  int flags;
+  mdwopt_data opt;
+  PyObject *prog;
+  PyObject *state;
 } mdwopt_pyobj;
 static PyTypeObject *mdwopt_pytype;
 #define MDWOPT_PYCHECK(o) PyObject_TypeCheck((o), mdwopt_pytype)
 #define MDWOPT_OPT(o) (&((mdwopt_pyobj *)(o))->opt)
 #define MDWOPT_ARGV(o) (((mdwopt_pyobj *)(o))->argv)
-#define MDWOPT_SHORT(o) (((mdwopt_pyobj *)(o))->shortopt)
+#define MDWOPT_SHORT(o) (((mdwopt_pyobj *)(o))->stringdata)
 #define MDWOPT_LONG(o) (((mdwopt_pyobj *)(o))->longopt)
 
-#define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 1))
-#define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x1ff) >> 1))
+#define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 2) | 0x200)
+#define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x3ff) >> 2))
+
+DA_DECL(obj_v, PyObject *);
+DA_DECL(opt_v, struct option);
+DA_DECL(extra_v, struct optextra);
+DA_DECL(size_v, size_t);
+
+/* Ordering of strings within `stringdata'.
+ *
+ *   * `shortopt' (at the start so we don't need an extra pointer)
+ *   * `argv' (individual strings addressed by `argv')
+ *   * `longopt' names (in order, addressed by `longopt[i].name')
+ */
+
+struct optbuild {
+  dstr strbuf;                         /* string buffer */
+  size_t narg;                         /* number of arguments */
+  size_v off;                          /* offsets of string starts;
+                                        * doesn't include `shortopt' */
+  opt_v opt;                           /* options */
+  extra_v extra;                       /* option extra data */
+};
+#define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT }
 
 static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
 {
-  DA_DECL(obj_v, PyObject *);
-  DA_DECL(opt_v, struct option);
-  DA_DECL(size_v, size_t);
-
   PyObject *argvobj = 0, *longoptobj = 0;
   PyObject *it = 0, *t = 0, *u = 0;
   char *p; size_t sz;
+  size_t i;
   Py_ssize_t n;
   mdwopt_pyobj *me = 0;
-  char *shortopt;
+  char *shortopt = "";
   unsigned flags = 0;
-  int f;
-  opt_v opts = DA_INIT;
-  obj_v tags = DA_INIT;
-  size_v off = DA_INIT;
-  dstr strbuf = DSTR_INIT;
-  size_t narg;
+  struct optbuild build = OPTBUILD_INIT;
+  struct option *opt;
+  struct optextra *extra;
   static const char *const kwlist[] =
     { "argv", "shortopt", "longopt", "flags", 0 };
 
+#define EXTEND(var, vec) do {                                          \
+  DA_ENSURE(&build.vec, 1);                                            \
+  var = &DA(&build.vec)[DA_LEN(&build.vec)];                           \
+  DA_EXTEND(&build.vec, 1);                                            \
+} while (0)
+
+#define COPYTAB(slot, base, len) do {                                  \
+  me->slot = xmalloc((len)*sizeof(*me->slot));                         \
+  memcpy(me->slot, base, (len)*sizeof(*me->slot));                     \
+} while (0)
+
+  /* Collect the arguments. */
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OsOO&:new", KWLIST,
                                   &argvobj,
                                   &shortopt,
@@ -144,68 +177,285 @@ static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
     if (!argvobj) SYSERR("sys.argv missing");
   }
 
+  /* Commit the short-options string to the buffer.
+   *
+   * Putting this first means that we don't need a separate pointer.  All of
+   * the other things are arrays, so avoiding maintaining a separate index or
+   * pointer for the first element will just make things unnecessarily
+   * complicated.
+   */
+  DPUTM(&build.strbuf, shortopt, strlen(shortopt) + 1);
+
+  /* Collect the arguments to be parsed. */
   it = PyObject_GetIter(argvobj); if (!it) goto end;
   for (;;) {
     t = PyIter_Next(it); if (!t) break;
     if (!TEXT_CHECK(t)) TYERR("argv should be a sequence of strings");
-    TEXT_PTRLEN(t, p, sz);
-    DA_PUSH(&off, strbuf.len);
-    DPUTM(&strbuf, p, sz + 1);
+    DA_PUSH(&build.off, build.strbuf.len);
+    TEXT_PTRLEN(t, p, sz); DPUTM(&build.strbuf, p, sz + 1);
     Py_DECREF(t); t = 0;
   }
   if (PyErr_Occurred()) goto end;
-  narg = DA_LEN(&off);
+  build.narg = DA_LEN(&build.off);
   Py_DECREF(it); it = 0;
 
-  it = PyObject_GetIter(longoptobj); if (!it) goto end;
-  for (;;) {
-    t = PyIter_Next(it); if (!t) break;
-    n = PySequence_Size(t); if (n < 0) goto end;
-    if (n < 2 || n > 4)
-      VALERR("long-options entry should be "
-            "(OPT, TAG, [FLAGS = 0, [ATTR = None]])");
-
-    u = PySequence_GetItem(t, 0); if (!u) goto end;
-    if (!TEXT_CHECK(u)) TYERR("option name should be a string");
-    TEXT_PTRLEN(u, p, sz);
-    DA_PUSH(&off, strbuf.len);
-    DPUTM(&strbuf, p, sz + 1);
-    Py_DECREF(u); u = 0;
-
-    u = PySequence_GetItem(t, 1); if (!u) goto end;
-    DA_PUSH(&tags, u); u = 0;
-
-    if (n < 3)
-      f = 0;
-    else {
+  /* Collect the long-option specifications. */
+  if (longoptobj) {
+    it = PyObject_GetIter(longoptobj); if (!it) goto end;
+    for (;;) {
+
+      /* Get the next item and check that it's basically sensible. */
+      t = PyIter_Next(it); if (!t) break;
+      n = PySequence_Size(t); if (n < 0) goto end;
+      if (n < 2 || n > 4)
+       VALERR("long-options entry should be "
+              "(NAME, VAL, [FLAG = 0, [ATTR = None]])");
+
+      /* Allocate new entries in the options and extra-data tables. */
+      EXTEND(opt, opt);
+      EXTEND(extra, extra);
+      opt->flag = 0;
+      extra->tag = 0; extra->attr = 0;
+
+      /* Get the option name and contribute it to the string buffer. */
       u = PySequence_GetItem(t, 0); if (!u) goto end;
-      if (getint(u, &f)) goto end;
+      if (!TEXT_CHECK(u)) TYERR("option name should be a string");
+      DA_PUSH(&build.off, build.strbuf.len);
+      TEXT_PTRLEN(u, p, sz); DPUTM(&build.strbuf, p, sz + 1);
       Py_DECREF(u); u = 0;
+
+      /* Get the option tag and store it in the extra data.
+       * `PySequence_GetItem' bumps the refcount for us.
+       */
+      extra->tag = PySequence_GetItem(t, 1); if (!extra->tag) goto end;
+
+      /* Get the flags for this option. */
+      if (n < 3)
+       opt->has_arg = 0;
+      else {
+       u = PySequence_GetItem(t, 0); if (!u) goto end;
+       if (convint(u, &opt->has_arg)) goto end;
+       Py_DECREF(u); u = 0;
+      }
+
+      /* Finally, get the attribute name. */
+      if (n < 4)
+       { extra->attr = Py_None; Py_INCREF(Py_None); }
+      else {
+       extra->attr = PySequence_GetItem(t, 3);
+       if (!extra->attr) goto end;
+      }
+
+      /* Done.  Let's go round again. */
+      Py_DECREF(t); t = 0;
     }
+    if (PyErr_Occurred()) goto end;
+    Py_DECREF(it); it = 0;
+  }
+
+  /* Allocate the state value. */
+  t = PyBaseObject_Type.tp_alloc(&PyBaseObject_Type, 0);
+  if (!t) goto end;
+
+  /* Allocate our return value. */
+  me = (mdwopt_pyobj *)cls->tp_alloc(cls, 0);
+  me->state = t; t = 0;
+  me->flags = flags;
+  me->narg = build.narg;
+  me->nlong = DA_LEN(&build.opt);
+
+  /* Add a final terminating entry to the long-options table. */
+  EXTEND(opt, opt); opt->name = 0;
+
+  /* Copy the main tables. */
+  COPYTAB(stringdata, build.strbuf.buf, build.strbuf.len);
+  COPYTAB(longopt, DA(&build.opt), DA_LEN(&build.opt));
+  COPYTAB(extra, DA(&build.extra), DA_LEN(&build.extra));
+
+  /* Fill in the `argv' vector. */
+  me->argv = xmalloc(build.narg*sizeof(*me->argv));
+  for (i = 0; i < build.narg; i++)
+    me->argv[i] = me->stringdata + DA(&build.off)[i + 1];
+  me->argv[build.narg] = 0;
+
+  /* Fix up the string pointers and values in the long-options table. */
+  for (i = 0; i < me->nlong; i++) {
+    me->longopt[i].name =
+      me->stringdata + DA(&build.off)[i + build.narg + 1];
+    me->longopt[i].val = IXTAG(i);
+  }
+
+  /* Initialize the parser state.  Set up everything because Python might
+   * ask awkward questions before we're ready.
+   */
+  me->opt.arg = 0;
+  me->opt.opt = -1;
+  me->opt.ind = 0;
+  me->opt.err = 1;
+  me->opt.prog = 0;
 
-    
+  /* And other random things. */
+  me->prog = 0;
 
 end:
-  return (PyObject *)me;
+  /* Clean up and go home. */
+  Py_XDECREF(it); Py_XDECREF(t); Py_XDECREF(u);
+  DDESTROY(&build.strbuf);
+  if (!me) for (i = 0; i < DA_LEN(&build.extra); i++) {
+    extra = &DA(&build.extra)[i];
+    Py_XDECREF(extra->tag); Py_XDECREF(extra->attr);
+  }
+  DA_DESTROY(&build.off);
+  DA_DESTROY(&build.opt);
+  DA_DESTROY(&build.extra);
+  return ((PyObject *)me);
+
+#undef EXTEND
+#undef COPYTAB
 }
 
 static void mdwopt_pydealloc(PyObject *me)
 {
   mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  size_t i;
 
-  xfree(m->stringdata);
-  xfree(m->longopt);
+  for (i = 0; i < m->nlong; i++)
+    { Py_DECREF(m->extra[i].tag); Py_DECREF(m->extra[i].attr); }
+  xfree(m->stringdata); xfree(m->longopt); xfree(m->extra);
   FREEOBJ(me);
 }
 
+static PyObject *mdwopt_pynext(PyObject *me)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  PyObject *val = 0, *arg = 0, *t = 0, *u = 0, *v = 0;
+  int f, ix, i;
+  unsigned char ch;
+  PyObject *rc = 0;
+
+again:
+  i = mdwopt(m->narg, m->argv, m->stringdata, m->longopt, &ix,
+                &m->opt, m->flags);
+
+  if (i == -1) goto end;
+
+  f = i&OPTF_NEGATED;
+
+  if (m->opt.arg) arg = TEXT_FROMSTR(m->opt.arg);
+  else { arg = Py_None; Py_INCREF(Py_None); }
+
+  if (ix < 0) {
+    ch = i&0xff;
+    val = TEXT_FROMSTRLEN((char *)&ch, 1); if (!val) goto end;
+  } else {
+    if (m->extra[ix].attr == Py_None)
+      { val = m->extra[ix].tag; Py_INCREF(val); }
+    else if (m->longopt[ix].has_arg&OPTF_SWITCH) {
+      t = PyObject_GetAttr(m->state, m->extra[ix].attr);
+      if (!t && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+       PyErr_Clear();
+       t = PyInt_FromLong(0); if (!t) goto end;
+      }
+      if (!f)
+       { v = PyNumber_Or(t, m->extra[ix].tag); if (!v) goto end; }
+      else {
+       u = PyNumber_Invert(m->extra[ix].tag); if (!u) goto end;
+       v = PyNumber_And(t, u); if (!v) goto end;
+      }
+      if (PyObject_SetAttr(m->state, m->extra[ix].attr, v)) goto end;
+      Py_DECREF(t); Py_XDECREF(u); Py_DECREF(v);
+    } else {
+      if (PyObject_SetAttr(m->state, m->extra[ix].attr,
+                          f ?
+                          Py_None :
+                          m->longopt[ix].has_arg&OPTF_ARG ?
+                          arg : m->extra[ix].tag))
+       goto end;
+    }
+    Py_DECREF(arg);
+    goto again;
+  }
+
+  rc = Py_BuildValue("(OOi)", val, arg, f);
+end:
+  Py_XDECREF(val); Py_XDECREF(arg);
+  Py_XDECREF(t); Py_XDECREF(u); Py_XDECREF(v);
+  return (rc);
+}
+
+static PyObject *moget_argv(PyObject *me, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  PyObject *rc = 0, *t = 0;
+  size_t i = 0;
+
+  rc = PyList_New(m->narg); if (!rc) goto fail;
+  for (i = 0; i < m->narg; i++) {
+    t = TEXT_FROMSTR(m->argv[i]); if (!t) goto fail;
+    PyList_SET_ITEM(rc, i, t); t = 0;
+  }
+  return (rc);
+
+fail:
+  Py_XDECREF(t);
+  Py_XDECREF(rc);
+  return (0);
+}
+
+static PyObject *moget_err(PyObject *me, void *hunoz)
+  { mdwopt_pyobj *m = (mdwopt_pyobj *)me; return (getbool(m->opt.err)); }
+static int moset_err(PyObject *me, PyObject *v, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  int rc = -1;
+
+  if (!v) NIERR("__del__");
+  if (convbool(v, &m->opt.err)) goto end;
+  rc = 0;
+end:
+  return (rc);
+}
+
+static PyObject *moget_prog(PyObject *me, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+
+  if (!m->opt.prog) RETURN_NONE;
+  if (!m->prog)
+    { m->prog = TEXT_FROMSTR(m->opt.prog); if (!m->prog) return (0); }
+  RETURN_OBJ(m->prog);
+}
+static int moset_prog(PyObject *me, PyObject *v, void *hunoz)
+{
+  mdwopt_pyobj *m = (mdwopt_pyobj *)me;
+  char *p;
+  int rc = -1;
+
+  if (!v) NIERR("__del__");
+  p = TEXT_STR(v); if (!p) goto end;
+  m->opt.prog = p;
+  Py_XDECREF(m->prog); m->prog = v; Py_INCREF(v);
+  rc = 0;
+end:
+  return (rc);
+}
+
 static const PyMemberDef mdwopt_pymembers[] = {
 #define MEMBERSTRUCT mdwopt_pyobj
+  MEMBER(state, T_OBJECT_EX, 0, "M.state = object on which flags are set")
+  MEMRNM(arg, T_STRING, opt.arg, READONLY,
+                       "M.arg -> argument of most recent option")
+  MEMRNM(ind, T_INT, opt.ind, READONLY,
+                       "M.ind -> index of first non-option argument")
 #undef MEMBERSTRUCT
   { 0 }
 };
 
 static const PyGetSetDef mdwopt_pygetset[] = {
 #define GETSETNAME(op, name) mo##op##_##name
+  GET  (argv,          "M.argv -> vector of arguments (permuted)")
+  GETSET(err,          "M.err = report errors to `stderr'?")
+  GETSET(prog,         "M.prog = program name (to report in errors")
 #undef GETSETNAME
   { 0 }
 };
@@ -241,14 +491,68 @@ static const PyTypeObject mdwopt_pytype_skel = {
     Py_TPFLAGS_BASETYPE,
 
   /* @tp_doc@ */
-  "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])",
+  "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])\n"
+  "\n"
+  "ARGV is the sequence of arguments to be parsed.  If omitted, it\n"
+  "defaults to `sys.argv'.\n"
+  "\n"
+  "SHORTOPT has the form `[+|-|!][:]OPT...', where OPT is `CHAR[+][:[:]]'.\n"
+  "The CHAR names the option character; a `+' indicates that the option\n"
+  "may be negated; a `:' indicates that the option takes an argument, and\n"
+  " `::' means the argument is optional.  Before the OPTs, the following\n"
+  "may appear:\n"
+  "\n"
+  "  * `+' -- force POSIX option order: end iteration at first non-option;\n"
+  "  * `-' -- treat non-options as arguments to option `None';\n"
+  "  * `!' -- force default reordering behaviour: extract all options;\n"
+  "  * `:' -- return `:' rather than `?' for missing argument.\n"
+  "\n"
+  "LONGOPT is a sequence of tuples (NAME, VAL, [FLAG = 0, [ATTR = None]]):\n"
+  "the NAME is the long-option string; FLAG is a mask of flags listed\n"
+  "below; ATTR is `None' or an attribute name; VAL is the value to return\n"
+  "or, if ATTR is not `None', to store in `state.ATTR'.  Flags are:\n"
+  "\n"
+  "  * `OPTF_ARGREQ' -- argument is mandatory (like `:');\n"
+  "  * `OPTF_ARGOPT' -- argument is optional (like `::');\n"
+  "  * `OPTF_SWITCH' -- set or clear VAL bits in ATTR;\n"
+  "  * `OPTF_NEGATE' -- option may be negated\n"
+  "\n"
+  "Flags to the function are:\n"
+  "  * `OPTF_NOLONGS' -- don't accept long options at all;\n"
+  "  * `OPTF_NOSHORTS' -- accept long options with single `-';\n"
+  "  * `OPTF_NUMBERS' -- accept numeric options (value `#');\n"
+  "  * `OPTF_NEGATION' -- allow options to be negated;\n"
+  "  * `OPTF_ENVVAR' -- read options from environment variable;\n"
+  "  * `OPTF_NOPROGNAME' -- don't assume program name is in `ARGV[0]'\n."
+  "\n"
+  "The object is iterable, and yields triples of the form (VAL, ARG,\n"
+  "FLAGS): VAL is the option letter (for short options) or VAL slot (for a\n"
+  "long option); ARG is the argument, or `None'; and FLAG is a mask of the\n"
+  "following flags:\n"
+  "\n"
+  "  * `OPTF_NEGATED' -- set if the option was negated.\n"
+  "\n"
+  "Special values of VAL are:\n"
+  "\n"
+  "  * '?' -- an error was encountered;\n"
+  "  * ':' -- a required argument was omitted (if `:' is in SHORTOPT);\n"
+  "  * '#' -- a numeric option was found (if `OPTF_NUMBERS' is in FLAGS).\n"
+  "\n"
+  "Useful attributes:\n"
+  "\n"
+  "  * `arg' (read-only) -- argument to most recent option, or `None';\n"
+  "  * `argv' (read-only) -- vector of arguments to parse (permuted);\n"
+  "  * `ind' (read-only) -- index of first non-option argument;\n"
+  "  * `err' (read-write) -- boolean: report errors to `stderr'?;\n"
+  "  * `prog' (read-write) -- program name (to report in errors);\n"
+  "  * `state' (read-write) -- object to accumulate attribute settings\n.",
 
   0,                                   /* @tp_traverse@ */
   0,                                   /* @tp_clear@ */
   0,                                   /* @tp_richcompare@ */
   0,                                   /* @tp_weaklistoffset@ */
-  0,                                   /* @tp_iter@ */
-  0,                                   /* @tp_iternext@ */
+  PyObject_SelfIter,                   /* @tp_iter@ */
+  mdwopt_pynext,                       /* @tp_iternext@ */
   PYMETHODS(mdwopt),                   /* @tp_methods@ */
   PYMEMBERS(mdwopt),                   /* @tp_members@ */
   PYGETSET(mdwopt),                    /* @tp_getset@ */