1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
/*
Copyright (C) 2016 Barry Warsaw
This project is licensed under the terms of the Apache 2.0 License. See
LICENSE.txt for details.
*/
#include <Python.h>
static PyObject *
public(PyObject *self, PyObject *args, PyObject *kwds)
{
PyObject *arg = NULL;
PyObject *globals = NULL;
PyObject *all = NULL;
PyObject *rtn = NULL;
if (!PyArg_UnpackTuple(args, "public", 0, 1, &arg))
return NULL;
/* kwds can be empty, but the keys must be strings. */
if (kwds != NULL && !PyArg_ValidateKeywordArguments(kwds))
return NULL;
if (!(globals = PyEval_GetGlobals())) {
PyErr_Format(PyExc_TypeError,
"@public called with no active globals");
return NULL;
}
if (!(all = PyDict_GetItemString(globals, "__all__"))) {
if (!(all = PyList_New(0)))
return NULL;
if (PyDict_SetItemString(globals, "__all__", all) < 0)
goto byebye;
}
else {
/* Make sure __all__ is a list, raising an exception if not. Bump
all's reference count since it's currently borrowed, and this way
we guarantee we own a reference for common exit cleanup.
*/
if (!PyList_Check(all)) {
PyErr_Format(PyExc_ValueError,
"__all__ must be a list not: %S",
all->ob_type);
goto byebye;
}
Py_INCREF(all);
}
if (arg != NULL) {
PyObject *name = NULL;
/* There is a single positional argument. This must have a "__name__"
attribute, which we will put in __all__. The keywords dictionary
must be empty.
*/
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_Format(PyExc_TypeError,
"Positional and keywords are mutually exclusive");
goto byebye;
}
if (!PyObject_HasAttrString(arg, "__name__")) {
PyErr_Format(PyExc_TypeError,
"Positional argument has no __name__");
goto byebye;
}
if (!(name = PyObject_GetAttrString(arg, "__name__")) ||
!PyUnicode_Check(name))
{
PyErr_Format(PyExc_TypeError, "Bad __name__ value");
Py_XDECREF(name);
goto byebye;
}
if (PyList_Append(all, name) < 0) {
Py_DECREF(name);
goto byebye;
}
Py_DECREF(name);
rtn = arg;
}
else if (kwds == NULL || PyDict_Size(kwds) == 0) {
PyErr_Format(
PyExc_TypeError,
"Either a single positional or keyword arguments required");
goto byebye;
}
else {
/* There are only keyword arguments, so for each of these, insert the
key in __all__ *and* bind the name/key to the value in globals. We
force the use of globals here because we're modifying the global
__all__ so it doesn't make sense to touch locals.
*/
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(kwds, &pos, &key, &value)) {
if (PyList_Append(all, key) < 0)
goto byebye;
if (PyDict_SetItem(globals, key, value) < 0)
goto byebye;
}
rtn = Py_None;
}
byebye:
Py_DECREF(all);
Py_XINCREF(rtn);
return rtn;
}
PyDoc_STRVAR(public_doc,
"public(named)\n\
public(**kws)\n\
\n\
May be used as a decorator or called directly. When used as a decorator\n\
the thing being decorated must have an __name__ attribute. The value of\n\
__name__ will be inserted in the global __all__ list. In this use case\n\
it is an error to also include keyword arguments.\n\
\n\
When called directly, it is an error to pass a positional argument. The\n\
keys must be strings, and are inserted into the global __all__ list. The\n\
mapping of keys to values is also inserted into the globals, thus creating\n\
name bindings for each key/value pair.");
static PyMethodDef public_methods[] = {
{"public", (PyCFunction)public, METH_VARARGS | METH_KEYWORDS, public_doc},
{NULL, NULL}
};
static struct PyModuleDef public_def = {
PyModuleDef_HEAD_INIT,
"public",
NULL,
0, /* size of module state */
public_methods,
NULL
};
PyObject *
PyInit__public(void)
{
return PyModule_Create(&public_def);
}
/*
* Local Variables:
* c-file-style: "python3"
* End:
*/
|