2912723812ec1dcf5b4ffd981c7d44b02d5bd811
[mLib-python] / ui.c
1 /* -*-c-*-
2 *
3 * mLib user interface
4 *
5 * (c) 2019 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the Python interface to mLib.
11 *
12 * mLib/Python is free software: you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the
14 * Free Software Foundation; either version 2 of the License, or (at your
15 * option) any later version.
16 *
17 * mLib/Python is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with mLib/Python. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 * USA.
26 */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "mLib-python.h"
31
32 /*----- Program name ------------------------------------------------------*/
33
34 static int set_program_name(void)
35 {
36 PyObject *p = TEXT_FROMSTR(pn__name);
37 int rc = -1;
38
39 if (!home_module) SYSERR("home module not set");
40 if (PyObject_SetAttrString(home_module, "quis", p)) goto end;
41 pn__name = TEXT_PTR(p); p = 0; rc = 0;
42 end:
43 Py_XDECREF(p);
44 return (rc);
45 }
46
47 static PyObject *meth_ego(PyObject *me, PyObject *arg)
48 {
49 char *p;
50 const char *old;
51
52 if (!PyArg_ParseTuple(arg, "s:ego", &p)) goto end;
53 old = pn__name; ego(p);
54 if (set_program_name()) { pn__name = old; goto end; }
55 RETURN_NONE;
56 end:
57 return (0);
58 }
59
60 /*----- Error reporting ---------------------------------------------------*/
61
62 static PyObject *meth_moan(PyObject *me, PyObject *arg)
63 {
64 char *p;
65
66 if (!PyArg_ParseTuple(arg, "s:moan", &p)) goto end;
67 moan("%s", p);
68 RETURN_NONE;
69 end:
70 return (0);
71 }
72
73 static PyObject *meth_die(PyObject *me, PyObject *arg, PyObject *kw)
74 {
75 const char *const kwlist[] = { "msg", "rc", 0 };
76 char *p;
77 int rc = 126;
78 PyObject *rcobj = 0;
79
80 if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|i:moan", KWLIST, &p, &rc))
81 goto end;
82 rcobj = PyInt_FromLong(rc); if (!rcobj) goto end;
83 moan("%s", p);
84 PyErr_SetObject(PyExc_SystemExit, rcobj);
85 end:
86 Py_XDECREF(rcobj);
87 return (0);
88 }
89
90 /*----- Option parser -----------------------------------------------------*/
91
92 struct optextra {
93 PyObject *tag;
94 PyObject *attr;
95 };
96
97 typedef struct {
98 PyObject_HEAD
99 char *stringdata;
100 struct option *longopt;
101 struct optextra *extra;
102 char **argv;
103 size_t nlong, narg;
104 int flags;
105 mdwopt_data opt;
106 PyObject *prog;
107 PyObject *state;
108 } mdwopt_pyobj;
109 static PyTypeObject *mdwopt_pytype;
110 #define MDWOPT_PYCHECK(o) PyObject_TypeCheck((o), mdwopt_pytype)
111 #define MDWOPT_OPT(o) (&((mdwopt_pyobj *)(o))->opt)
112 #define MDWOPT_ARGV(o) (((mdwopt_pyobj *)(o))->argv)
113 #define MDWOPT_SHORT(o) (((mdwopt_pyobj *)(o))->stringdata)
114 #define MDWOPT_LONG(o) (((mdwopt_pyobj *)(o))->longopt)
115
116 #define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 2) | 0x200)
117 #define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x3ff) >> 2))
118
119 DA_DECL(obj_v, PyObject *);
120 DA_DECL(opt_v, struct option);
121 DA_DECL(extra_v, struct optextra);
122 DA_DECL(size_v, size_t);
123
124 /* Ordering of strings within `stringdata'.
125 *
126 * * `shortopt' (at the start so we don't need an extra pointer)
127 * * `argv' (individual strings addressed by `argv')
128 * * `longopt' names (in order, addressed by `longopt[i].name')
129 */
130
131 struct optbuild {
132 dstr strbuf; /* string buffer */
133 size_t narg; /* number of arguments */
134 size_v off; /* offsets of string starts;
135 * doesn't include `shortopt' */
136 opt_v opt; /* options */
137 extra_v extra; /* option extra data */
138 };
139 #define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT }
140
141 static PyObject *mdwopt_pynew(PyTypeObject *cls, PyObject *arg, PyObject *kw)
142 {
143 PyObject *argvobj = 0, *longoptobj = 0;
144 PyObject *it = 0, *t = 0, *u = 0;
145 const char *p; size_t sz;
146 size_t i;
147 Py_ssize_t n;
148 mdwopt_pyobj *me = 0;
149 const char *shortopt = "";
150 unsigned flags = 0;
151 struct optbuild build = OPTBUILD_INIT;
152 struct option *opt;
153 struct optextra *extra;
154 static const char *const kwlist[] =
155 { "argv", "shortopt", "longopt", "flags", 0 };
156
157 #define EXTEND(var, vec) do { \
158 DA_ENSURE(&build.vec, 1); \
159 var = &DA(&build.vec)[DA_LEN(&build.vec)]; \
160 DA_EXTEND(&build.vec, 1); \
161 } while (0)
162
163 #define COPYTAB(slot, base, len) do { \
164 me->slot = xmalloc((len)*sizeof(*me->slot)); \
165 memcpy(me->slot, base, (len)*sizeof(*me->slot)); \
166 } while (0)
167
168 /* Collect the arguments. */
169 if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OsOO&:new", KWLIST,
170 &argvobj,
171 &shortopt,
172 &longoptobj,
173 convuint, &flags))
174 goto end;
175 if (!argvobj) {
176 argvobj = PySys_GetObject("argv");
177 if (!argvobj) SYSERR("sys.argv missing");
178 }
179
180 /* Commit the short-options string to the buffer.
181 *
182 * Putting this first means that we don't need a separate pointer. All of
183 * the other things are arrays, so avoiding maintaining a separate index or
184 * pointer for the first element will just make things unnecessarily
185 * complicated.
186 */
187 DPUTM(&build.strbuf, shortopt, strlen(shortopt) + 1);
188
189 /* Collect the arguments to be parsed. */
190 it = PyObject_GetIter(argvobj); if (!it) goto end;
191 for (;;) {
192 t = PyIter_Next(it); if (!t) break;
193 if (!TEXT_CHECK(t)) TYERR("argv should be a sequence of strings");
194 DA_PUSH(&build.off, build.strbuf.len);
195 TEXT_PTRLEN(t, p, sz); DPUTM(&build.strbuf, p, sz + 1);
196 Py_DECREF(t); t = 0;
197 }
198 if (PyErr_Occurred()) goto end;
199 build.narg = DA_LEN(&build.off);
200 Py_DECREF(it); it = 0;
201
202 /* Collect the long-option specifications. */
203 if (longoptobj) {
204 it = PyObject_GetIter(longoptobj); if (!it) goto end;
205 for (;;) {
206
207 /* Get the next item and check that it's basically sensible. */
208 t = PyIter_Next(it); if (!t) break;
209 n = PySequence_Size(t); if (n < 0) goto end;
210 if (n < 2 || n > 4)
211 VALERR("long-options entry should be "
212 "(NAME, VAL, [FLAG = 0, [ATTR = None]])");
213
214 /* Allocate new entries in the options and extra-data tables. */
215 EXTEND(opt, opt);
216 EXTEND(extra, extra);
217 opt->flag = 0;
218 extra->tag = 0; extra->attr = 0;
219
220 /* Get the option name and contribute it to the string buffer. */
221 u = PySequence_GetItem(t, 0); if (!u) goto end;
222 if (!TEXT_CHECK(u)) TYERR("option name should be a string");
223 DA_PUSH(&build.off, build.strbuf.len);
224 TEXT_PTRLEN(u, p, sz); DPUTM(&build.strbuf, p, sz + 1);
225 Py_DECREF(u); u = 0;
226
227 /* Get the option tag and store it in the extra data.
228 * `PySequence_GetItem' bumps the refcount for us.
229 */
230 extra->tag = PySequence_GetItem(t, 1); if (!extra->tag) goto end;
231
232 /* Get the flags for this option. */
233 if (n < 3)
234 opt->has_arg = 0;
235 else {
236 u = PySequence_GetItem(t, 0); if (!u) goto end;
237 if (convint(u, &opt->has_arg)) goto end;
238 Py_DECREF(u); u = 0;
239 }
240
241 /* Finally, get the attribute name. */
242 if (n < 4)
243 { extra->attr = Py_None; Py_INCREF(Py_None); }
244 else {
245 extra->attr = PySequence_GetItem(t, 3);
246 if (!extra->attr) goto end;
247 }
248
249 /* Done. Let's go round again. */
250 Py_DECREF(t); t = 0;
251 }
252 if (PyErr_Occurred()) goto end;
253 Py_DECREF(it); it = 0;
254 }
255
256 /* Allocate the state value. */
257 t = PyBaseObject_Type.tp_alloc(&PyBaseObject_Type, 0);
258 if (!t) goto end;
259
260 /* Allocate our return value. */
261 me = (mdwopt_pyobj *)cls->tp_alloc(cls, 0);
262 me->state = t; t = 0;
263 me->flags = flags;
264 me->narg = build.narg;
265 me->nlong = DA_LEN(&build.opt);
266
267 /* Add a final terminating entry to the long-options table. */
268 EXTEND(opt, opt); opt->name = 0;
269
270 /* Copy the main tables. */
271 COPYTAB(stringdata, build.strbuf.buf, build.strbuf.len);
272 COPYTAB(longopt, DA(&build.opt), DA_LEN(&build.opt));
273 COPYTAB(extra, DA(&build.extra), DA_LEN(&build.extra));
274
275 /* Fill in the `argv' vector. */
276 me->argv = xmalloc(build.narg*sizeof(*me->argv));
277 for (i = 0; i < build.narg; i++)
278 me->argv[i] = me->stringdata + DA(&build.off)[i + 1];
279 me->argv[build.narg] = 0;
280
281 /* Fix up the string pointers and values in the long-options table. */
282 for (i = 0; i < me->nlong; i++) {
283 me->longopt[i].name =
284 me->stringdata + DA(&build.off)[i + build.narg + 1];
285 me->longopt[i].val = IXTAG(i);
286 }
287
288 /* Initialize the parser state. Set up everything because Python might
289 * ask awkward questions before we're ready.
290 */
291 me->opt.arg = 0;
292 me->opt.opt = -1;
293 me->opt.ind = 0;
294 me->opt.err = 1;
295 me->opt.prog = 0;
296
297 /* And other random things. */
298 me->prog = 0;
299
300 end:
301 /* Clean up and go home. */
302 Py_XDECREF(it); Py_XDECREF(t); Py_XDECREF(u);
303 DDESTROY(&build.strbuf);
304 if (!me) for (i = 0; i < DA_LEN(&build.extra); i++) {
305 extra = &DA(&build.extra)[i];
306 Py_XDECREF(extra->tag); Py_XDECREF(extra->attr);
307 }
308 DA_DESTROY(&build.off);
309 DA_DESTROY(&build.opt);
310 DA_DESTROY(&build.extra);
311 return ((PyObject *)me);
312
313 #undef EXTEND
314 #undef COPYTAB
315 }
316
317 static void mdwopt_pydealloc(PyObject *me)
318 {
319 mdwopt_pyobj *m = (mdwopt_pyobj *)me;
320 size_t i;
321
322 for (i = 0; i < m->nlong; i++)
323 { Py_DECREF(m->extra[i].tag); Py_DECREF(m->extra[i].attr); }
324 xfree(m->stringdata); xfree(m->longopt); xfree(m->extra);
325 FREEOBJ(me);
326 }
327
328 static PyObject *mdwopt_pynext(PyObject *me)
329 {
330 mdwopt_pyobj *m = (mdwopt_pyobj *)me;
331 PyObject *val = 0, *arg = 0, *t = 0, *u = 0, *v = 0;
332 int f, ix, i;
333 unsigned char ch;
334 PyObject *rc = 0;
335
336 again:
337 i = mdwopt(m->narg, m->argv, m->stringdata, m->longopt, &ix,
338 &m->opt, m->flags);
339
340 if (i == -1) goto end;
341
342 f = i&OPTF_NEGATED;
343
344 if (m->opt.arg) arg = TEXT_FROMSTR(m->opt.arg);
345 else { arg = Py_None; Py_INCREF(Py_None); }
346
347 if (ix < 0) {
348 ch = i&0xff;
349 val = TEXT_FROMSTRLEN((char *)&ch, 1); if (!val) goto end;
350 } else {
351 if (m->extra[ix].attr == Py_None)
352 { val = m->extra[ix].tag; Py_INCREF(val); }
353 else if (m->longopt[ix].has_arg&OPTF_SWITCH) {
354 t = PyObject_GetAttr(m->state, m->extra[ix].attr);
355 if (!t && PyErr_ExceptionMatches(PyExc_AttributeError)) {
356 PyErr_Clear();
357 t = PyInt_FromLong(0); if (!t) goto end;
358 }
359 if (!f)
360 { v = PyNumber_Or(t, m->extra[ix].tag); if (!v) goto end; }
361 else {
362 u = PyNumber_Invert(m->extra[ix].tag); if (!u) goto end;
363 v = PyNumber_And(t, u); if (!v) goto end;
364 }
365 if (PyObject_SetAttr(m->state, m->extra[ix].attr, v)) goto end;
366 Py_DECREF(t); Py_XDECREF(u); Py_DECREF(v);
367 } else {
368 if (PyObject_SetAttr(m->state, m->extra[ix].attr,
369 f ?
370 Py_None :
371 m->longopt[ix].has_arg&OPTF_ARG ?
372 arg : m->extra[ix].tag))
373 goto end;
374 }
375 Py_DECREF(arg);
376 goto again;
377 }
378
379 rc = Py_BuildValue("(OOi)", val, arg, f);
380 end:
381 Py_XDECREF(val); Py_XDECREF(arg);
382 Py_XDECREF(t); Py_XDECREF(u); Py_XDECREF(v);
383 return (rc);
384 }
385
386 static PyObject *moget_argv(PyObject *me, void *hunoz)
387 {
388 mdwopt_pyobj *m = (mdwopt_pyobj *)me;
389 PyObject *rc = 0, *t = 0;
390 size_t i = 0;
391
392 rc = PyList_New(m->narg); if (!rc) goto fail;
393 for (i = 0; i < m->narg; i++) {
394 t = TEXT_FROMSTR(m->argv[i]); if (!t) goto fail;
395 PyList_SET_ITEM(rc, i, t); t = 0;
396 }
397 return (rc);
398
399 fail:
400 Py_XDECREF(t);
401 Py_XDECREF(rc);
402 return (0);
403 }
404
405 static PyObject *moget_err(PyObject *me, void *hunoz)
406 { mdwopt_pyobj *m = (mdwopt_pyobj *)me; return (getbool(m->opt.err)); }
407 static int moset_err(PyObject *me, PyObject *v, void *hunoz)
408 {
409 mdwopt_pyobj *m = (mdwopt_pyobj *)me;
410 int rc = -1;
411
412 if (!v) NIERR("__del__");
413 if (convbool(v, &m->opt.err)) goto end;
414 rc = 0;
415 end:
416 return (rc);
417 }
418
419 static PyObject *moget_prog(PyObject *me, void *hunoz)
420 {
421 mdwopt_pyobj *m = (mdwopt_pyobj *)me;
422
423 if (!m->opt.prog) RETURN_NONE;
424 if (!m->prog)
425 { m->prog = TEXT_FROMSTR(m->opt.prog); if (!m->prog) return (0); }
426 RETURN_OBJ(m->prog);
427 }
428 static int moset_prog(PyObject *me, PyObject *v, void *hunoz)
429 {
430 mdwopt_pyobj *m = (mdwopt_pyobj *)me;
431 const char *p;
432 int rc = -1;
433
434 if (!v) NIERR("__del__");
435 p = TEXT_STR(v); if (!p) goto end;
436 m->opt.prog = (/*unconst*/ char *)p;
437 Py_XDECREF(m->prog); m->prog = v; Py_INCREF(v);
438 rc = 0;
439 end:
440 return (rc);
441 }
442
443 static const PyMemberDef mdwopt_pymembers[] = {
444 #define MEMBERSTRUCT mdwopt_pyobj
445 MEMBER(state, T_OBJECT_EX, 0, "M.state = object on which flags are set")
446 MEMRNM(arg, T_STRING, opt.arg, READONLY,
447 "M.arg -> argument of most recent option")
448 MEMRNM(ind, T_INT, opt.ind, READONLY,
449 "M.ind -> index of first non-option argument")
450 #undef MEMBERSTRUCT
451 { 0 }
452 };
453
454 static const PyGetSetDef mdwopt_pygetset[] = {
455 #define GETSETNAME(op, name) mo##op##_##name
456 GET (argv, "M.argv -> vector of arguments (permuted)")
457 GETSET(err, "M.err = report errors to `stderr'?")
458 GETSET(prog, "M.prog = program name (to report in errors")
459 #undef GETSETNAME
460 { 0 }
461 };
462
463 static const PyMethodDef mdwopt_pymethods[] = {
464 #define METHNAME(name) mometh_##name
465 #undef METHNAME
466 { 0 }
467 };
468
469 static const PyTypeObject mdwopt_pytype_skel = {
470 PyVarObject_HEAD_INIT(0, 0) /* Header */
471 "MdwOpt", /* @tp_name@ */
472 sizeof(mdwopt_pyobj), /* @tp_basicsize@ */
473 0, /* @tp_itemsize@ */
474
475 mdwopt_pydealloc, /* @tp_dealloc@ */
476 0, /* @tp_print@ */
477 0, /* @tp_getattr@ */
478 0, /* @tp_setattr@ */
479 0, /* @tp_compare@ */
480 0, /* @tp_repr@ */
481 0, /* @tp_as_number@ */
482 0, /* @tp_as_sequence@ */
483 0, /* @tp_as_mapping@ */
484 0, /* @tp_hash@ */
485 0, /* @tp_call@ */
486 0, /* @tp_str@ */
487 0, /* @tp_getattro@ */
488 0, /* @tp_setattro@ */
489 0, /* @tp_as_buffer@ */
490 Py_TPFLAGS_DEFAULT | /* @tp_flags@ */
491 Py_TPFLAGS_BASETYPE,
492
493 /* @tp_doc@ */
494 "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])\n"
495 "\n"
496 "ARGV is the sequence of arguments to be parsed. If omitted, it\n"
497 "defaults to `sys.argv'.\n"
498 "\n"
499 "SHORTOPT has the form `[+|-|!][:]OPT...', where OPT is `CHAR[+][:[:]]'.\n"
500 "The CHAR names the option character; a `+' indicates that the option\n"
501 "may be negated; a `:' indicates that the option takes an argument, and\n"
502 " `::' means the argument is optional. Before the OPTs, the following\n"
503 "may appear:\n"
504 "\n"
505 " * `+' -- force POSIX option order: end iteration at first non-option;\n"
506 " * `-' -- treat non-options as arguments to option `None';\n"
507 " * `!' -- force default reordering behaviour: extract all options;\n"
508 " * `:' -- return `:' rather than `?' for missing argument.\n"
509 "\n"
510 "LONGOPT is a sequence of tuples (NAME, VAL, [FLAG = 0, [ATTR = None]]):\n"
511 "the NAME is the long-option string; FLAG is a mask of flags listed\n"
512 "below; ATTR is `None' or an attribute name; VAL is the value to return\n"
513 "or, if ATTR is not `None', to store in `state.ATTR'. Flags are:\n"
514 "\n"
515 " * `OPTF_ARGREQ' -- argument is mandatory (like `:');\n"
516 " * `OPTF_ARGOPT' -- argument is optional (like `::');\n"
517 " * `OPTF_SWITCH' -- set or clear VAL bits in ATTR;\n"
518 " * `OPTF_NEGATE' -- option may be negated\n"
519 "\n"
520 "Flags to the function are:\n"
521 " * `OPTF_NOLONGS' -- don't accept long options at all;\n"
522 " * `OPTF_NOSHORTS' -- accept long options with single `-';\n"
523 " * `OPTF_NUMBERS' -- accept numeric options (value `#');\n"
524 " * `OPTF_NEGATION' -- allow options to be negated;\n"
525 " * `OPTF_ENVVAR' -- read options from environment variable;\n"
526 " * `OPTF_NOPROGNAME' -- don't assume program name is in `ARGV[0]'\n."
527 "\n"
528 "The object is iterable, and yields triples of the form (VAL, ARG,\n"
529 "FLAGS): VAL is the option letter (for short options) or VAL slot (for a\n"
530 "long option); ARG is the argument, or `None'; and FLAG is a mask of the\n"
531 "following flags:\n"
532 "\n"
533 " * `OPTF_NEGATED' -- set if the option was negated.\n"
534 "\n"
535 "Special values of VAL are:\n"
536 "\n"
537 " * '?' -- an error was encountered;\n"
538 " * ':' -- a required argument was omitted (if `:' is in SHORTOPT);\n"
539 " * '#' -- a numeric option was found (if `OPTF_NUMBERS' is in FLAGS).\n"
540 "\n"
541 "Useful attributes:\n"
542 "\n"
543 " * `arg' (read-only) -- argument to most recent option, or `None';\n"
544 " * `argv' (read-only) -- vector of arguments to parse (permuted);\n"
545 " * `ind' (read-only) -- index of first non-option argument;\n"
546 " * `err' (read-write) -- boolean: report errors to `stderr'?;\n"
547 " * `prog' (read-write) -- program name (to report in errors);\n"
548 " * `state' (read-write) -- object to accumulate attribute settings\n.",
549
550 0, /* @tp_traverse@ */
551 0, /* @tp_clear@ */
552 0, /* @tp_richcompare@ */
553 0, /* @tp_weaklistoffset@ */
554 PyObject_SelfIter, /* @tp_iter@ */
555 mdwopt_pynext, /* @tp_iternext@ */
556 PYMETHODS(mdwopt), /* @tp_methods@ */
557 PYMEMBERS(mdwopt), /* @tp_members@ */
558 PYGETSET(mdwopt), /* @tp_getset@ */
559 0, /* @tp_base@ */
560 0, /* @tp_dict@ */
561 0, /* @tp_descr_get@ */
562 0, /* @tp_descr_set@ */
563 0, /* @tp_dictoffset@ */
564 0, /* @tp_init@ */
565 PyType_GenericAlloc, /* @tp_alloc@ */
566 mdwopt_pynew, /* @tp_new@ */
567 0, /* @tp_free@ */
568 0 /* @tp_is_gc@ */
569 };
570
571 /*----- Main code ---------------------------------------------------------*/
572
573 static const PyMethodDef methods[] = {
574 #define METHNAME(name) meth_##name
575 METH (ego, "ego(PROG): set program name")
576 METH (moan, "moan(MSG): report a warning")
577 KWMETH(die, "die(MSG, [rc = 126]): report a fatal error and exit")
578 #undef METHNAME
579 { 0 }
580 };
581
582 void ui_pyinit(void)
583 {
584 INITTYPE(mdwopt, root);
585 addmethods(methods);
586 }
587
588 void ui_pyinsert(PyObject *mod)
589 {
590 INSERT("MdwOpt", mdwopt_pytype);
591 }
592
593 int ui_pyready(void) { return (set_program_name()); }
594
595 /*----- That's all, folks -------------------------------------------------*/