@@@ mdwopt test and fix
authorMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 21:47:55 +0000 (22:47 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 21:49:48 +0000 (22:49 +0100)
TODO.org
t/t-ui.py
ui.c

index 1f1402f..8c43295 100644 (file)
--- a/TODO.org
+++ b/TODO.org
@@ -56,8 +56,8 @@
 * [0/1] =trace=
   + [ ] =trace/trace.h= ::
 
-* [2/3] =ui=
-  + [ ] =ui/mdwopt.h= :: interface needs thinking about
+* [3/3] =ui=
+  + [X] =ui/mdwopt.h= ::
   + [X] =ui/quis.h= ::
   + [X] =ui/report.h= ::
 
index 4d3a6e7..7b2dc89 100644 (file)
--- a/t/t-ui.py
+++ b/t/t-ui.py
@@ -65,9 +65,50 @@ class TestUI (U.TestCase):
     else:
       raise AssertionError("die didn't exit")
 
-  def test_mdwopt(me):
-    mo = M.MdwOpt()
-    print(list(mo))
+###--------------------------------------------------------------------------
+class TestMdwOpt (U.TestCase):
+
+  def test_argv_default(me):
+    old_argv = SYS.argv
+    try:
+      SYS.argv = ["just", "another", "Python", "hacker"]
+      mo = M.MdwOpt()
+      me.assertEqual(mo.argv, SYS.argv)
+    finally:
+      SYS.argv = old_argv
+
+  def test_basic(me):
+    mo = M.MdwOpt(argv = ["example",
+                          "one",
+                          "-abcarg",
+                          "--opt",
+                          "+de",
+                          "two",
+                          "--no-thing",
+                          "-f", "alpha",
+                          "--foo", "beta",
+                          "--switch", "--frob", "--toggle", "--no-frob",
+                          "three"],
+                  shortopt = "abc:d+e+f:",
+                  longopt = [("opt", "o"),
+                             ("thing", "t", M.OPTF_NEGATE),
+                             ("foo", None, M.OPTF_ARGREQ, "foo"),
+                             ("switch", 1, M.OPTF_SWITCH, "flags"),
+                             ("frob", 2, M.OPTF_SWITCH | M.OPTF_NEGATE,
+                                "flags"),
+                             ("toggle", 4, M.OPTF_SWITCH, "flags")],
+                  flags = M.OPTF_NEGATION)
+    me.assertEqual(list(mo), [("a", None, 0),
+                              ("b", None, 0),
+                              ("c", "arg", 0),
+                              ("o", None, 0),
+                              ("d", None, M.OPTF_NEGATED),
+                              ("e", None, M.OPTF_NEGATED),
+                              ("t", None, M.OPTF_NEGATED),
+                              ("f", "alpha", 0)])
+    me.assertEqual(mo.argv[mo.ind:], ["one", "two", "three"])
+    me.assertEqual(mo.state.foo, "beta")
+    me.assertEqual(mo.state.flags, 5)
 
 ###----- That's all, folks --------------------------------------------------
 
diff --git a/ui.c b/ui.c
index 2912723..e50637e 100644 (file)
--- a/ui.c
+++ b/ui.c
@@ -89,6 +89,97 @@ end:
 
 /*----- Option parser -----------------------------------------------------*/
 
+typedef struct {
+  PyObject_HEAD
+  PyObject *attrs;
+} optstate_pyobj;
+static PyTypeObject *optstate_pytype;
+
+static PyObject *optstate_pywrap(PyTypeObject *ty)
+{
+  optstate_pyobj *me = 0;
+
+  me = (optstate_pyobj *)ty->tp_alloc(ty, 0); if (!me) goto fail;
+  me->attrs = PyDict_New(); if (!me->attrs) goto fail;
+  return ((PyObject *)me);
+
+fail:
+  if (me) {
+    Py_XDECREF(me->attrs);
+    Py_DECREF(me);
+  }
+  return (0);
+}
+
+static PyObject *optstate_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { 0 };
+  PyObject *me = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
+  me = optstate_pywrap(ty);
+end:
+  return (me);
+}
+
+static void optstate_pydealloc(PyObject *me)
+{
+  optstate_pyobj *st = (optstate_pyobj *)me;
+  Py_DECREF(st->attrs);
+  FREEOBJ(me);
+}
+
+static const PyTypeObject optstate_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "OptState",                          /* @tp_name@ */
+  sizeof(optstate_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  optstate_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@ */
+  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@ */
+  "OptState()\n"
+  "\n"
+  "A passive and generic container for arbitrary object attributes.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  offsetof(optstate_pyobj, attrs),     /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  optstate_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
 struct optextra {
   PyObject *tag;
   PyObject *attr;
@@ -111,7 +202,7 @@ static PyTypeObject *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))->stringdata)
-#define MDWOPT_LONG(o) (((mdwopt_pyobj *)(o))->longopt)
+#define MDWOPT_LONG(o) stativ(((mdwopt_pyobj *)(o))->longopt)
 
 #define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 2) | 0x200)
 #define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x3ff) >> 2))
@@ -136,9 +227,9 @@ struct optbuild {
   opt_v opt;                           /* options */
   extra_v extra;                       /* option extra data */
 };
-#define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT }
+#define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT, DA_INIT }
 
-static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
+static PyObject *mdwopt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
 {
   PyObject *argvobj = 0, *longoptobj = 0;
   PyObject *it = 0, *t = 0, *u = 0;
@@ -233,8 +324,8 @@ static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
       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;
+       u = PySequence_GetItem(t, 2); if (!u) goto end;
+       if (!convint(u, &opt->has_arg)) goto end;
        Py_DECREF(u); u = 0;
       }
 
@@ -254,11 +345,10 @@ static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
   }
 
   /* Allocate the state value. */
-  t = PyBaseObject_Type.tp_alloc(&PyBaseObject_Type, 0);
-  if (!t) goto end;
+  t = optstate_pywrap(optstate_pytype); if (!t) goto end;
 
   /* Allocate our return value. */
-  me = (mdwopt_pyobj *)cls->tp_alloc(cls, 0);
+  me = (mdwopt_pyobj *)ty->tp_alloc(ty, 0);
   me->state = t; t = 0;
   me->flags = flags;
   me->narg = build.narg;
@@ -273,15 +363,15 @@ static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
   COPYTAB(extra, DA(&build.extra), DA_LEN(&build.extra));
 
   /* Fill in the `argv' vector. */
-  me->argv = xmalloc(build.narg*sizeof(*me->argv));
+  me->argv = xmalloc((build.narg + 1)*sizeof(*me->argv));
   for (i = 0; i < build.narg; i++)
-    me->argv[i] = me->stringdata + DA(&build.off)[i + 1];
+    me->argv[i] = me->stringdata + DA(&build.off)[i];
   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->stringdata + DA(&build.off)[i + build.narg];
     me->longopt[i].val = IXTAG(i);
   }
 
@@ -350,30 +440,34 @@ again:
   } 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;
+    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); t = 0;
+       Py_XDECREF(u); u = 0;
+       Py_DECREF(v); v = 0;
+      } 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;
       }
-      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); arg = 0;
+      goto again;
     }
-    Py_DECREF(arg);
-    goto again;
   }
 
   rc = Py_BuildValue("(OOi)", val, arg, f);
@@ -570,6 +664,15 @@ static const PyTypeObject mdwopt_pytype_skel = {
 
 /*----- Main code ---------------------------------------------------------*/
 
+static const struct nameval consts[] = {
+  CONST(OPTF_NOARG), CONST(OPTF_ARGREQ), CONST(OPTF_ARGOPT), CONST(OPTF_ARG),
+    CONST(OPTF_SWITCH), CONST(OPTF_NEGATE),
+  CONST(OPTF_NOLONGS), CONST(OPTF_NOSHORTS), CONST(OPTF_NUMBERS),
+    CONST(OPTF_NEGATION), CONST(OPTF_ENVVAR), CONST(OPTF_NOPROGNAME),
+    CONST(OPTF_NEGNUMBER),
+  CONST(OPTF_NEGATED)
+};
+
 static const PyMethodDef methods[] = {
 #define METHNAME(name) meth_##name
   METH (ego,           "ego(PROG): set program name")
@@ -581,13 +684,16 @@ static const PyMethodDef methods[] = {
 
 void ui_pyinit(void)
 {
+  INITTYPE(optstate, root);
   INITTYPE(mdwopt, root);
   addmethods(methods);
 }
 
 void ui_pyinsert(PyObject *mod)
 {
+  INSERT("OptState", optstate_pytype);
   INSERT("MdwOpt", mdwopt_pytype);
+  setconstants(mod, consts);
 }
 
 int ui_pyready(void) { return (set_program_name()); }