5 * (c) 2019 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the Python interface to mLib.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
30 #include "mLib-python.h"
32 /*----- Program name ------------------------------------------------------*/
34 static int set_program_name(void)
36 PyObject
*p
= TEXT_FROMSTR(pn__name
);
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;
47 static PyObject
*meth_ego(PyObject
*me
, PyObject
*arg
)
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
; }
60 /*----- Error reporting ---------------------------------------------------*/
62 static PyObject
*meth_moan(PyObject
*me
, PyObject
*arg
)
66 if (!PyArg_ParseTuple(arg
, "s:moan", &p
)) goto end
;
73 static PyObject
*meth_die(PyObject
*me
, PyObject
*arg
, PyObject
*kw
)
75 const char *const kwlist
[] = { "msg", "rc", 0 };
80 if (!PyArg_ParseTupleAndKeywords(arg
, kw
, "s|i:moan", KWLIST
, &p
, &rc
))
82 rcobj
= PyInt_FromLong(rc
); if (!rcobj
) goto end
;
84 PyErr_SetObject(PyExc_SystemExit
, rcobj
);
90 /*----- Option parser -----------------------------------------------------*/
96 static PyTypeObject
*optstate_pytype
;
98 static PyObject
*optstate_pywrap(PyTypeObject
*ty
)
100 optstate_pyobj
*me
= 0;
102 me
= (optstate_pyobj
*)ty
->tp_alloc(ty
, 0); if (!me
) goto fail
;
103 me
->attrs
= PyDict_New(); if (!me
->attrs
) goto fail
;
104 return ((PyObject
*)me
);
108 Py_XDECREF(me
->attrs
);
114 static PyObject
*optstate_pynew(PyTypeObject
*ty
,
115 PyObject
*arg
, PyObject
*kw
)
117 static const char *const kwlist
[] = { 0 };
120 if (!PyArg_ParseTupleAndKeywords(arg
, kw
, ":new", KWLIST
)) goto end
;
121 me
= optstate_pywrap(ty
);
126 static void optstate_pydealloc(PyObject
*me
)
128 optstate_pyobj
*st
= (optstate_pyobj
*)me
;
129 Py_DECREF(st
->attrs
);
133 static const PyTypeObject optstate_pytype_skel
= {
134 PyVarObject_HEAD_INIT(0, 0) /* Header */
135 "OptState", /* @tp_name@ */
136 sizeof(optstate_pyobj
), /* @tp_basicsize@ */
137 0, /* @tp_itemsize@ */
139 optstate_pydealloc
, /* @tp_dealloc@ */
141 0, /* @tp_getattr@ */
142 0, /* @tp_setattr@ */
143 0, /* @tp_compare@ */
145 0, /* @tp_as_number@ */
146 0, /* @tp_as_sequence@ */
147 0, /* @tp_as_mapping@ */
151 0, /* @tp_getattro@ */
152 0, /* @tp_setattro@ */
153 0, /* @tp_as_buffer@ */
154 Py_TPFLAGS_DEFAULT
| /* @tp_flags@ */
160 "A passive and generic container for arbitrary object attributes.",
162 0, /* @tp_traverse@ */
164 0, /* @tp_richcompare@ */
165 0, /* @tp_weaklistoffset@ */
167 0, /* @tp_iternext@ */
168 0, /* @tp_methods@ */
169 0, /* @tp_members@ */
173 0, /* @tp_descr_get@ */
174 0, /* @tp_descr_set@ */
175 offsetof(optstate_pyobj
, attrs
), /* @tp_dictoffset@ */
177 PyType_GenericAlloc
, /* @tp_alloc@ */
178 optstate_pynew
, /* @tp_new@ */
191 struct option
*longopt
;
192 struct optextra
*extra
;
200 static PyTypeObject
*mdwopt_pytype
;
201 #define MDWOPT_PYCHECK(o) PyObject_TypeCheck((o), mdwopt_pytype)
202 #define MDWOPT_OPT(o) (&((mdwopt_pyobj *)(o))->opt)
203 #define MDWOPT_ARGV(o) (((mdwopt_pyobj *)(o))->argv)
204 #define MDWOPT_SHORT(o) (((mdwopt_pyobj *)(o))->stringdata)
205 #define MDWOPT_LONG(o) stativ(((mdwopt_pyobj *)(o))->longopt)
207 #define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 2) | 0x200)
208 #define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x3ff) >> 2))
210 DA_DECL(obj_v
, PyObject
*);
211 DA_DECL(opt_v
, struct option
);
212 DA_DECL(extra_v
, struct optextra
);
213 DA_DECL(size_v
, size_t);
215 /* Ordering of strings within `stringdata'.
217 * * `shortopt' (at the start so we don't need an extra pointer)
218 * * `argv' (individual strings addressed by `argv')
219 * * `longopt' names (in order, addressed by `longopt[i].name')
223 dstr strbuf
; /* string buffer */
224 size_t narg
; /* number of arguments */
225 size_v off
; /* offsets of string starts;
226 * doesn't include `shortopt' */
227 opt_v opt
; /* options */
228 extra_v extra
; /* option extra data */
230 #define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT, DA_INIT }
232 static PyObject
*mdwopt_pynew(PyTypeObject
*ty
, PyObject
*arg
, PyObject
*kw
)
234 PyObject
*argvobj
= 0, *longoptobj
= 0;
235 PyObject
*it
= 0, *t
= 0, *u
= 0;
236 const char *p
; size_t sz
;
239 mdwopt_pyobj
*me
= 0;
240 const char *shortopt
= "";
242 struct optbuild build
= OPTBUILD_INIT
;
244 struct optextra
*extra
;
245 static const char *const kwlist
[] =
246 { "argv", "shortopt", "longopt", "flags", 0 };
248 #define EXTEND(var, vec) do { \
249 DA_ENSURE(&build.vec, 1); \
250 var = &DA(&build.vec)[DA_LEN(&build.vec)]; \
251 DA_EXTEND(&build.vec, 1); \
254 #define COPYTAB(slot, base, len) do { \
255 me->slot = xmalloc((len)*sizeof(*me->slot)); \
256 memcpy(me->slot, base, (len)*sizeof(*me->slot)); \
259 /* Collect the arguments. */
260 if (!PyArg_ParseTupleAndKeywords(arg
, kw
, "|OsOO&:new", KWLIST
,
267 argvobj
= PySys_GetObject("argv");
268 if (!argvobj
) SYSERR("sys.argv missing");
271 /* Commit the short-options string to the buffer.
273 * Putting this first means that we don't need a separate pointer. All of
274 * the other things are arrays, so avoiding maintaining a separate index or
275 * pointer for the first element will just make things unnecessarily
278 DPUTM(&build
.strbuf
, shortopt
, strlen(shortopt
) + 1);
280 /* Collect the arguments to be parsed. */
281 it
= PyObject_GetIter(argvobj
); if (!it
) goto end
;
283 t
= PyIter_Next(it
); if (!t
) break;
284 if (!TEXT_CHECK(t
)) TYERR("argv should be a sequence of strings");
285 DA_PUSH(&build
.off
, build
.strbuf
.len
);
286 TEXT_PTRLEN(t
, p
, sz
); DPUTM(&build
.strbuf
, p
, sz
+ 1);
289 if (PyErr_Occurred()) goto end
;
290 build
.narg
= DA_LEN(&build
.off
);
291 Py_DECREF(it
); it
= 0;
293 /* Collect the long-option specifications. */
295 it
= PyObject_GetIter(longoptobj
); if (!it
) goto end
;
298 /* Get the next item and check that it's basically sensible. */
299 t
= PyIter_Next(it
); if (!t
) break;
300 n
= PySequence_Size(t
); if (n
< 0) goto end
;
302 VALERR("long-options entry should be "
303 "(NAME, VAL, [FLAG = 0, [ATTR = None]])");
305 /* Allocate new entries in the options and extra-data tables. */
307 EXTEND(extra
, extra
);
309 extra
->tag
= 0; extra
->attr
= 0;
311 /* Get the option name and contribute it to the string buffer. */
312 u
= PySequence_GetItem(t
, 0); if (!u
) goto end
;
313 if (!TEXT_CHECK(u
)) TYERR("option name should be a string");
314 DA_PUSH(&build
.off
, build
.strbuf
.len
);
315 TEXT_PTRLEN(u
, p
, sz
); DPUTM(&build
.strbuf
, p
, sz
+ 1);
318 /* Get the option tag and store it in the extra data.
319 * `PySequence_GetItem' bumps the refcount for us.
321 extra
->tag
= PySequence_GetItem(t
, 1); if (!extra
->tag
) goto end
;
323 /* Get the flags for this option. */
327 u
= PySequence_GetItem(t
, 2); if (!u
) goto end
;
328 if (!convint(u
, &opt
->has_arg
)) goto end
;
332 /* Finally, get the attribute name. */
334 { extra
->attr
= Py_None
; Py_INCREF(Py_None
); }
336 extra
->attr
= PySequence_GetItem(t
, 3);
337 if (!extra
->attr
) goto end
;
340 /* Done. Let's go round again. */
343 if (PyErr_Occurred()) goto end
;
344 Py_DECREF(it
); it
= 0;
347 /* Allocate the state value. */
348 t
= optstate_pywrap(optstate_pytype
); if (!t
) goto end
;
350 /* Allocate our return value. */
351 me
= (mdwopt_pyobj
*)ty
->tp_alloc(ty
, 0);
352 me
->state
= t
; t
= 0;
354 me
->narg
= build
.narg
;
355 me
->nlong
= DA_LEN(&build
.opt
);
357 /* Add a final terminating entry to the long-options table. */
358 EXTEND(opt
, opt
); opt
->name
= 0;
360 /* Copy the main tables. */
361 COPYTAB(stringdata
, build
.strbuf
.buf
, build
.strbuf
.len
);
362 COPYTAB(longopt
, DA(&build
.opt
), DA_LEN(&build
.opt
));
363 COPYTAB(extra
, DA(&build
.extra
), DA_LEN(&build
.extra
));
365 /* Fill in the `argv' vector. */
366 me
->argv
= xmalloc((build
.narg
+ 1)*sizeof(*me
->argv
));
367 for (i
= 0; i
< build
.narg
; i
++)
368 me
->argv
[i
] = me
->stringdata
+ DA(&build
.off
)[i
];
369 me
->argv
[build
.narg
] = 0;
371 /* Fix up the string pointers and values in the long-options table. */
372 for (i
= 0; i
< me
->nlong
; i
++) {
373 me
->longopt
[i
].name
=
374 me
->stringdata
+ DA(&build
.off
)[i
+ build
.narg
];
375 me
->longopt
[i
].val
= IXTAG(i
);
378 /* Initialize the parser state. Set up everything because Python might
379 * ask awkward questions before we're ready.
387 /* And other random things. */
391 /* Clean up and go home. */
392 Py_XDECREF(it
); Py_XDECREF(t
); Py_XDECREF(u
);
393 DDESTROY(&build
.strbuf
);
394 if (!me
) for (i
= 0; i
< DA_LEN(&build
.extra
); i
++) {
395 extra
= &DA(&build
.extra
)[i
];
396 Py_XDECREF(extra
->tag
); Py_XDECREF(extra
->attr
);
398 DA_DESTROY(&build
.off
);
399 DA_DESTROY(&build
.opt
);
400 DA_DESTROY(&build
.extra
);
401 return ((PyObject
*)me
);
407 static void mdwopt_pydealloc(PyObject
*me
)
409 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
412 for (i
= 0; i
< m
->nlong
; i
++)
413 { Py_DECREF(m
->extra
[i
].tag
); Py_DECREF(m
->extra
[i
].attr
); }
414 xfree(m
->stringdata
); xfree(m
->longopt
); xfree(m
->extra
);
418 static PyObject
*mdwopt_pynext(PyObject
*me
)
420 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
421 PyObject
*val
= 0, *arg
= 0, *t
= 0, *u
= 0, *v
= 0;
427 i
= mdwopt(m
->narg
, m
->argv
, m
->stringdata
, m
->longopt
, &ix
,
430 if (i
== -1) goto end
;
434 if (m
->opt
.arg
) arg
= TEXT_FROMSTR(m
->opt
.arg
);
435 else { arg
= Py_None
; Py_INCREF(Py_None
); }
439 val
= TEXT_FROMSTRLEN((char *)&ch
, 1); if (!val
) goto end
;
441 if (m
->extra
[ix
].attr
== Py_None
)
442 { val
= m
->extra
[ix
].tag
; Py_INCREF(val
); }
444 if (m
->longopt
[ix
].has_arg
&OPTF_SWITCH
) {
445 t
= PyObject_GetAttr(m
->state
, m
->extra
[ix
].attr
);
446 if (!t
&& PyErr_ExceptionMatches(PyExc_AttributeError
)) {
448 t
= PyInt_FromLong(0); if (!t
) goto end
;
451 { v
= PyNumber_Or(t
, m
->extra
[ix
].tag
); if (!v
) goto end
; }
453 u
= PyNumber_Invert(m
->extra
[ix
].tag
); if (!u
) goto end
;
454 v
= PyNumber_And(t
, u
); if (!v
) goto end
;
456 if (PyObject_SetAttr(m
->state
, m
->extra
[ix
].attr
, v
)) goto end
;
458 Py_XDECREF(u
); u
= 0;
461 if (PyObject_SetAttr(m
->state
, m
->extra
[ix
].attr
,
464 m
->longopt
[ix
].has_arg
&OPTF_ARG ?
465 arg
: m
->extra
[ix
].tag
))
468 Py_DECREF(arg
); arg
= 0;
473 rc
= Py_BuildValue("(OOi)", val
, arg
, f
);
475 Py_XDECREF(val
); Py_XDECREF(arg
);
476 Py_XDECREF(t
); Py_XDECREF(u
); Py_XDECREF(v
);
480 static PyObject
*moget_argv(PyObject
*me
, void *hunoz
)
482 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
483 PyObject
*rc
= 0, *t
= 0;
486 rc
= PyList_New(m
->narg
); if (!rc
) goto fail
;
487 for (i
= 0; i
< m
->narg
; i
++) {
488 t
= TEXT_FROMSTR(m
->argv
[i
]); if (!t
) goto fail
;
489 PyList_SET_ITEM(rc
, i
, t
); t
= 0;
499 static PyObject
*moget_err(PyObject
*me
, void *hunoz
)
500 { mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
; return (getbool(m
->opt
.err
)); }
501 static int moset_err(PyObject
*me
, PyObject
*v
, void *hunoz
)
503 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
506 if (!v
) NIERR("__del__");
507 if (convbool(v
, &m
->opt
.err
)) goto end
;
513 static PyObject
*moget_prog(PyObject
*me
, void *hunoz
)
515 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
517 if (!m
->opt
.prog
) RETURN_NONE
;
519 { m
->prog
= TEXT_FROMSTR(m
->opt
.prog
); if (!m
->prog
) return (0); }
522 static int moset_prog(PyObject
*me
, PyObject
*v
, void *hunoz
)
524 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
528 if (!v
) NIERR("__del__");
529 p
= TEXT_STR(v
); if (!p
) goto end
;
530 m
->opt
.prog
= (/*unconst*/ char *)p
;
531 Py_XDECREF(m
->prog
); m
->prog
= v
; Py_INCREF(v
);
537 static const PyMemberDef mdwopt_pymembers
[] = {
538 #define MEMBERSTRUCT mdwopt_pyobj
539 MEMBER(state
, T_OBJECT_EX
, 0, "M.state = object on which flags are set")
540 MEMRNM(arg
, T_STRING
, opt
.arg
, READONLY
,
541 "M.arg -> argument of most recent option")
542 MEMRNM(ind
, T_INT
, opt
.ind
, READONLY
,
543 "M.ind -> index of first non-option argument")
548 static const PyGetSetDef mdwopt_pygetset
[] = {
549 #define GETSETNAME(op, name) mo##op##_##name
550 GET (argv
, "M.argv -> vector of arguments (permuted)")
551 GETSET(err
, "M.err = report errors to `stderr'?")
552 GETSET(prog
, "M.prog = program name (to report in errors")
557 static const PyMethodDef mdwopt_pymethods
[] = {
558 #define METHNAME(name) mometh_##name
563 static const PyTypeObject mdwopt_pytype_skel
= {
564 PyVarObject_HEAD_INIT(0, 0) /* Header */
565 "MdwOpt", /* @tp_name@ */
566 sizeof(mdwopt_pyobj
), /* @tp_basicsize@ */
567 0, /* @tp_itemsize@ */
569 mdwopt_pydealloc
, /* @tp_dealloc@ */
571 0, /* @tp_getattr@ */
572 0, /* @tp_setattr@ */
573 0, /* @tp_compare@ */
575 0, /* @tp_as_number@ */
576 0, /* @tp_as_sequence@ */
577 0, /* @tp_as_mapping@ */
581 0, /* @tp_getattro@ */
582 0, /* @tp_setattro@ */
583 0, /* @tp_as_buffer@ */
584 Py_TPFLAGS_DEFAULT
| /* @tp_flags@ */
588 "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])\n"
590 "ARGV is the sequence of arguments to be parsed. If omitted, it\n"
591 "defaults to `sys.argv'.\n"
593 "SHORTOPT has the form `[+|-|!][:]OPT...', where OPT is `CHAR[+][:[:]]'.\n"
594 "The CHAR names the option character; a `+' indicates that the option\n"
595 "may be negated; a `:' indicates that the option takes an argument, and\n"
596 " `::' means the argument is optional. Before the OPTs, the following\n"
599 " * `+' -- force POSIX option order: end iteration at first non-option;\n"
600 " * `-' -- treat non-options as arguments to option `None';\n"
601 " * `!' -- force default reordering behaviour: extract all options;\n"
602 " * `:' -- return `:' rather than `?' for missing argument.\n"
604 "LONGOPT is a sequence of tuples (NAME, VAL, [FLAG = 0, [ATTR = None]]):\n"
605 "the NAME is the long-option string; FLAG is a mask of flags listed\n"
606 "below; ATTR is `None' or an attribute name; VAL is the value to return\n"
607 "or, if ATTR is not `None', to store in `state.ATTR'. Flags are:\n"
609 " * `OPTF_ARGREQ' -- argument is mandatory (like `:');\n"
610 " * `OPTF_ARGOPT' -- argument is optional (like `::');\n"
611 " * `OPTF_SWITCH' -- set or clear VAL bits in ATTR;\n"
612 " * `OPTF_NEGATE' -- option may be negated\n"
614 "Flags to the function are:\n"
615 " * `OPTF_NOLONGS' -- don't accept long options at all;\n"
616 " * `OPTF_NOSHORTS' -- accept long options with single `-';\n"
617 " * `OPTF_NUMBERS' -- accept numeric options (value `#');\n"
618 " * `OPTF_NEGATION' -- allow options to be negated;\n"
619 " * `OPTF_ENVVAR' -- read options from environment variable;\n"
620 " * `OPTF_NOPROGNAME' -- don't assume program name is in `ARGV[0]'\n."
622 "The object is iterable, and yields triples of the form (VAL, ARG,\n"
623 "FLAGS): VAL is the option letter (for short options) or VAL slot (for a\n"
624 "long option); ARG is the argument, or `None'; and FLAG is a mask of the\n"
627 " * `OPTF_NEGATED' -- set if the option was negated.\n"
629 "Special values of VAL are:\n"
631 " * '?' -- an error was encountered;\n"
632 " * ':' -- a required argument was omitted (if `:' is in SHORTOPT);\n"
633 " * '#' -- a numeric option was found (if `OPTF_NUMBERS' is in FLAGS).\n"
635 "Useful attributes:\n"
637 " * `arg' (read-only) -- argument to most recent option, or `None';\n"
638 " * `argv' (read-only) -- vector of arguments to parse (permuted);\n"
639 " * `ind' (read-only) -- index of first non-option argument;\n"
640 " * `err' (read-write) -- boolean: report errors to `stderr'?;\n"
641 " * `prog' (read-write) -- program name (to report in errors);\n"
642 " * `state' (read-write) -- object to accumulate attribute settings\n.",
644 0, /* @tp_traverse@ */
646 0, /* @tp_richcompare@ */
647 0, /* @tp_weaklistoffset@ */
648 PyObject_SelfIter
, /* @tp_iter@ */
649 mdwopt_pynext
, /* @tp_iternext@ */
650 PYMETHODS(mdwopt
), /* @tp_methods@ */
651 PYMEMBERS(mdwopt
), /* @tp_members@ */
652 PYGETSET(mdwopt
), /* @tp_getset@ */
655 0, /* @tp_descr_get@ */
656 0, /* @tp_descr_set@ */
657 0, /* @tp_dictoffset@ */
659 PyType_GenericAlloc
, /* @tp_alloc@ */
660 mdwopt_pynew
, /* @tp_new@ */
665 /*----- Main code ---------------------------------------------------------*/
667 static const struct nameval consts
[] = {
668 CONST(OPTF_NOARG
), CONST(OPTF_ARGREQ
), CONST(OPTF_ARGOPT
), CONST(OPTF_ARG
),
669 CONST(OPTF_SWITCH
), CONST(OPTF_NEGATE
),
670 CONST(OPTF_NOLONGS
), CONST(OPTF_NOSHORTS
), CONST(OPTF_NUMBERS
),
671 CONST(OPTF_NEGATION
), CONST(OPTF_ENVVAR
), CONST(OPTF_NOPROGNAME
),
672 CONST(OPTF_NEGNUMBER
),
676 static const PyMethodDef methods
[] = {
677 #define METHNAME(name) meth_##name
678 METH (ego
, "ego(PROG): set program name")
679 METH (moan
, "moan(MSG): report a warning")
680 KWMETH(die
, "die(MSG, [rc = 126]): report a fatal error and exit")
687 INITTYPE(optstate
, root
);
688 INITTYPE(mdwopt
, root
);
692 void ui_pyinsert(PyObject
*mod
)
694 INSERT("OptState", optstate_pytype
);
695 INSERT("MdwOpt", mdwopt_pytype
);
696 setconstants(mod
, consts
);
699 int ui_pyready(void) { return (set_program_name()); }
701 /*----- That's all, folks -------------------------------------------------*/