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 -----------------------------------------------------*/
100 struct option
*longopt
;
101 struct optextra
*extra
;
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)
116 #define IXTAG(ix) (((ix)&0xff) | (((ix)&~0xff) << 2) | 0x200)
117 #define TAGIX(tag) (((tag)&0xff) | (((tag)&~0x3ff) >> 2))
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);
124 /* Ordering of strings within `stringdata'.
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')
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 */
139 #define OPTBUILD_INIT { DSTR_INIT, 0, DA_INIT, DA_INIT }
141 static PyObject
*mdwopt_pynew(PyTypeObject
*cls
, PyObject
*arg
, PyObject
*kw
)
143 PyObject
*argvobj
= 0, *longoptobj
= 0;
144 PyObject
*it
= 0, *t
= 0, *u
= 0;
145 const char *p
; size_t sz
;
148 mdwopt_pyobj
*me
= 0;
149 const char *shortopt
= "";
151 struct optbuild build
= OPTBUILD_INIT
;
153 struct optextra
*extra
;
154 static const char *const kwlist
[] =
155 { "argv", "shortopt", "longopt", "flags", 0 };
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); \
163 #define COPYTAB(slot, base, len) do { \
164 me->slot = xmalloc((len)*sizeof(*me->slot)); \
165 memcpy(me->slot, base, (len)*sizeof(*me->slot)); \
168 /* Collect the arguments. */
169 if (!PyArg_ParseTupleAndKeywords(arg
, kw
, "|OsOO&:new", KWLIST
,
176 argvobj
= PySys_GetObject("argv");
177 if (!argvobj
) SYSERR("sys.argv missing");
180 /* Commit the short-options string to the buffer.
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
187 DPUTM(&build
.strbuf
, shortopt
, strlen(shortopt
) + 1);
189 /* Collect the arguments to be parsed. */
190 it
= PyObject_GetIter(argvobj
); if (!it
) goto end
;
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);
198 if (PyErr_Occurred()) goto end
;
199 build
.narg
= DA_LEN(&build
.off
);
200 Py_DECREF(it
); it
= 0;
202 /* Collect the long-option specifications. */
204 it
= PyObject_GetIter(longoptobj
); if (!it
) goto end
;
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
;
211 VALERR("long-options entry should be "
212 "(NAME, VAL, [FLAG = 0, [ATTR = None]])");
214 /* Allocate new entries in the options and extra-data tables. */
216 EXTEND(extra
, extra
);
218 extra
->tag
= 0; extra
->attr
= 0;
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);
227 /* Get the option tag and store it in the extra data.
228 * `PySequence_GetItem' bumps the refcount for us.
230 extra
->tag
= PySequence_GetItem(t
, 1); if (!extra
->tag
) goto end
;
232 /* Get the flags for this option. */
236 u
= PySequence_GetItem(t
, 0); if (!u
) goto end
;
237 if (convint(u
, &opt
->has_arg
)) goto end
;
241 /* Finally, get the attribute name. */
243 { extra
->attr
= Py_None
; Py_INCREF(Py_None
); }
245 extra
->attr
= PySequence_GetItem(t
, 3);
246 if (!extra
->attr
) goto end
;
249 /* Done. Let's go round again. */
252 if (PyErr_Occurred()) goto end
;
253 Py_DECREF(it
); it
= 0;
256 /* Allocate the state value. */
257 t
= PyBaseObject_Type
.tp_alloc(&PyBaseObject_Type
, 0);
260 /* Allocate our return value. */
261 me
= (mdwopt_pyobj
*)cls
->tp_alloc(cls
, 0);
262 me
->state
= t
; t
= 0;
264 me
->narg
= build
.narg
;
265 me
->nlong
= DA_LEN(&build
.opt
);
267 /* Add a final terminating entry to the long-options table. */
268 EXTEND(opt
, opt
); opt
->name
= 0;
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
));
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;
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
);
288 /* Initialize the parser state. Set up everything because Python might
289 * ask awkward questions before we're ready.
297 /* And other random things. */
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
);
308 DA_DESTROY(&build
.off
);
309 DA_DESTROY(&build
.opt
);
310 DA_DESTROY(&build
.extra
);
311 return ((PyObject
*)me
);
317 static void mdwopt_pydealloc(PyObject
*me
)
319 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
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
);
328 static PyObject
*mdwopt_pynext(PyObject
*me
)
330 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
331 PyObject
*val
= 0, *arg
= 0, *t
= 0, *u
= 0, *v
= 0;
337 i
= mdwopt(m
->narg
, m
->argv
, m
->stringdata
, m
->longopt
, &ix
,
340 if (i
== -1) goto end
;
344 if (m
->opt
.arg
) arg
= TEXT_FROMSTR(m
->opt
.arg
);
345 else { arg
= Py_None
; Py_INCREF(Py_None
); }
349 val
= TEXT_FROMSTRLEN((char *)&ch
, 1); if (!val
) goto end
;
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
)) {
357 t
= PyInt_FromLong(0); if (!t
) goto end
;
360 { v
= PyNumber_Or(t
, m
->extra
[ix
].tag
); if (!v
) goto end
; }
362 u
= PyNumber_Invert(m
->extra
[ix
].tag
); if (!u
) goto end
;
363 v
= PyNumber_And(t
, u
); if (!v
) goto end
;
365 if (PyObject_SetAttr(m
->state
, m
->extra
[ix
].attr
, v
)) goto end
;
366 Py_DECREF(t
); Py_XDECREF(u
); Py_DECREF(v
);
368 if (PyObject_SetAttr(m
->state
, m
->extra
[ix
].attr
,
371 m
->longopt
[ix
].has_arg
&OPTF_ARG ?
372 arg
: m
->extra
[ix
].tag
))
379 rc
= Py_BuildValue("(OOi)", val
, arg
, f
);
381 Py_XDECREF(val
); Py_XDECREF(arg
);
382 Py_XDECREF(t
); Py_XDECREF(u
); Py_XDECREF(v
);
386 static PyObject
*moget_argv(PyObject
*me
, void *hunoz
)
388 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
389 PyObject
*rc
= 0, *t
= 0;
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;
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
)
409 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
412 if (!v
) NIERR("__del__");
413 if (convbool(v
, &m
->opt
.err
)) goto end
;
419 static PyObject
*moget_prog(PyObject
*me
, void *hunoz
)
421 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
423 if (!m
->opt
.prog
) RETURN_NONE
;
425 { m
->prog
= TEXT_FROMSTR(m
->opt
.prog
); if (!m
->prog
) return (0); }
428 static int moset_prog(PyObject
*me
, PyObject
*v
, void *hunoz
)
430 mdwopt_pyobj
*m
= (mdwopt_pyobj
*)me
;
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
);
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")
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")
463 static const PyMethodDef mdwopt_pymethods
[] = {
464 #define METHNAME(name) mometh_##name
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@ */
475 mdwopt_pydealloc
, /* @tp_dealloc@ */
477 0, /* @tp_getattr@ */
478 0, /* @tp_setattr@ */
479 0, /* @tp_compare@ */
481 0, /* @tp_as_number@ */
482 0, /* @tp_as_sequence@ */
483 0, /* @tp_as_mapping@ */
487 0, /* @tp_getattro@ */
488 0, /* @tp_setattro@ */
489 0, /* @tp_as_buffer@ */
490 Py_TPFLAGS_DEFAULT
| /* @tp_flags@ */
494 "MdwOpt([argv = SEQ], [shortopt = STR], [longopt = SEQ], [flags = 0])\n"
496 "ARGV is the sequence of arguments to be parsed. If omitted, it\n"
497 "defaults to `sys.argv'.\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"
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"
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"
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"
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."
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"
533 " * `OPTF_NEGATED' -- set if the option was negated.\n"
535 "Special values of VAL are:\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"
541 "Useful attributes:\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.",
550 0, /* @tp_traverse@ */
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@ */
561 0, /* @tp_descr_get@ */
562 0, /* @tp_descr_set@ */
563 0, /* @tp_dictoffset@ */
565 PyType_GenericAlloc
, /* @tp_alloc@ */
566 mdwopt_pynew
, /* @tp_new@ */
571 /*----- Main code ---------------------------------------------------------*/
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")
584 INITTYPE(mdwopt
, root
);
588 void ui_pyinsert(PyObject
*mod
)
590 INSERT("MdwOpt", mdwopt_pytype
);
593 int ui_pyready(void) { return (set_program_name()); }
595 /*----- That's all, folks -------------------------------------------------*/