dirs.c
305 lines
| 6.3 KiB
| text/x-c
|
CLexer
/ mercurial / dirs.c
Bryan O'Sullivan
|
r18900 | /* | ||
dirs.c - dynamic directory diddling for dirstates | ||||
Copyright 2013 Facebook | ||||
This software may be used and distributed according to the terms of | ||||
the GNU General Public License, incorporated herein by reference. | ||||
*/ | ||||
#define PY_SSIZE_T_CLEAN | ||||
#include <Python.h> | ||||
#include "util.h" | ||||
/* | ||||
* This is a multiset of directory names, built from the files that | ||||
* appear in a dirstate or manifest. | ||||
Bryan O'Sullivan
|
r18901 | * | ||
* A few implementation notes: | ||||
* | ||||
* We modify Python integers for refcounting, but those integers are | ||||
* never visible to Python code. | ||||
Bryan O'Sullivan
|
r18902 | * | ||
* We mutate strings in-place, but leave them immutable once they can | ||||
* be seen by Python code. | ||||
Bryan O'Sullivan
|
r18900 | */ | ||
typedef struct { | ||||
PyObject_HEAD | ||||
PyObject *dict; | ||||
} dirsObject; | ||||
static inline Py_ssize_t _finddir(PyObject *path, Py_ssize_t pos) | ||||
{ | ||||
const char *s = PyString_AS_STRING(path); | ||||
while (pos != -1) { | ||||
if (s[pos] == '/') | ||||
break; | ||||
pos -= 1; | ||||
} | ||||
return pos; | ||||
} | ||||
static int _addpath(PyObject *dirs, PyObject *path) | ||||
{ | ||||
Bryan O'Sullivan
|
r18902 | const char *cpath = PyString_AS_STRING(path); | ||
Bryan O'Sullivan
|
r18900 | Py_ssize_t pos = PyString_GET_SIZE(path); | ||
Bryan O'Sullivan
|
r18901 | PyObject *key = NULL; | ||
Bryan O'Sullivan
|
r18900 | int ret = -1; | ||
while ((pos = _finddir(path, pos - 1)) != -1) { | ||||
PyObject *val; | ||||
Bryan O'Sullivan
|
r18902 | /* It's likely that every prefix already has an entry | ||
in our dict. Try to avoid allocating and | ||||
deallocating a string for each prefix we check. */ | ||||
if (key != NULL) | ||||
((PyStringObject *)key)->ob_shash = -1; | ||||
else { | ||||
/* Force Python to not reuse a small shared string. */ | ||||
key = PyString_FromStringAndSize(cpath, | ||||
pos < 2 ? 2 : pos); | ||||
if (key == NULL) | ||||
goto bail; | ||||
} | ||||
PyString_GET_SIZE(key) = pos; | ||||
PyString_AS_STRING(key)[pos] = '\0'; | ||||
Bryan O'Sullivan
|
r18900 | |||
val = PyDict_GetItem(dirs, key); | ||||
Bryan O'Sullivan
|
r18901 | if (val != NULL) { | ||
PyInt_AS_LONG(val) += 1; | ||||
continue; | ||||
} | ||||
Bryan O'Sullivan
|
r18900 | |||
Bryan O'Sullivan
|
r18901 | /* Force Python to not reuse a small shared int. */ | ||
val = PyInt_FromLong(0x1eadbeef); | ||||
Bryan O'Sullivan
|
r18900 | |||
Bryan O'Sullivan
|
r18901 | if (val == NULL) | ||
Bryan O'Sullivan
|
r18900 | goto bail; | ||
Bryan O'Sullivan
|
r18901 | PyInt_AS_LONG(val) = 1; | ||
ret = PyDict_SetItem(dirs, key, val); | ||||
Py_DECREF(val); | ||||
Bryan O'Sullivan
|
r18900 | if (ret == -1) | ||
goto bail; | ||||
Py_CLEAR(key); | ||||
} | ||||
ret = 0; | ||||
bail: | ||||
Py_XDECREF(key); | ||||
return ret; | ||||
} | ||||
static int _delpath(PyObject *dirs, PyObject *path) | ||||
{ | ||||
Py_ssize_t pos = PyString_GET_SIZE(path); | ||||
Bryan O'Sullivan
|
r18901 | PyObject *key = NULL; | ||
Bryan O'Sullivan
|
r18900 | int ret = -1; | ||
while ((pos = _finddir(path, pos - 1)) != -1) { | ||||
PyObject *val; | ||||
key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos); | ||||
if (key == NULL) | ||||
goto bail; | ||||
val = PyDict_GetItem(dirs, key); | ||||
if (val == NULL) { | ||||
PyErr_SetString(PyExc_ValueError, | ||||
"expected a value, found none"); | ||||
goto bail; | ||||
} | ||||
Bryan O'Sullivan
|
r18901 | if (--PyInt_AS_LONG(val) <= 0 && | ||
PyDict_DelItem(dirs, key) == -1) | ||||
Bryan O'Sullivan
|
r18900 | goto bail; | ||
Py_CLEAR(key); | ||||
} | ||||
ret = 0; | ||||
bail: | ||||
Py_XDECREF(key); | ||||
return ret; | ||||
} | ||||
static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar) | ||||
{ | ||||
PyObject *key, *value; | ||||
Py_ssize_t pos = 0; | ||||
while (PyDict_Next(source, &pos, &key, &value)) { | ||||
if (!PyString_Check(key)) { | ||||
PyErr_SetString(PyExc_TypeError, "expected string key"); | ||||
return -1; | ||||
} | ||||
if (skipchar) { | ||||
PyObject *st; | ||||
if (!PyTuple_Check(value) || | ||||
PyTuple_GET_SIZE(value) == 0) { | ||||
PyErr_SetString(PyExc_TypeError, | ||||
"expected non-empty tuple"); | ||||
return -1; | ||||
} | ||||
st = PyTuple_GET_ITEM(value, 0); | ||||
if (!PyString_Check(st) || PyString_GET_SIZE(st) == 0) { | ||||
PyErr_SetString(PyExc_TypeError, | ||||
"expected non-empty string " | ||||
"at tuple index 0"); | ||||
return -1; | ||||
} | ||||
if (PyString_AS_STRING(st)[0] == skipchar) | ||||
continue; | ||||
} | ||||
if (_addpath(dirs, key) == -1) | ||||
return -1; | ||||
} | ||||
return 0; | ||||
} | ||||
static int dirs_fromiter(PyObject *dirs, PyObject *source) | ||||
{ | ||||
PyObject *iter, *item = NULL; | ||||
int ret; | ||||
iter = PyObject_GetIter(source); | ||||
if (iter == NULL) | ||||
return -1; | ||||
while ((item = PyIter_Next(iter)) != NULL) { | ||||
if (!PyString_Check(item)) { | ||||
PyErr_SetString(PyExc_TypeError, "expected string"); | ||||
break; | ||||
} | ||||
if (_addpath(dirs, item) == -1) | ||||
break; | ||||
Py_CLEAR(item); | ||||
} | ||||
ret = PyErr_Occurred() ? -1 : 0; | ||||
Py_XDECREF(item); | ||||
return ret; | ||||
} | ||||
/* | ||||
* Calculate a refcounted set of directory names for the files in a | ||||
* dirstate. | ||||
*/ | ||||
static int dirs_init(dirsObject *self, PyObject *args) | ||||
{ | ||||
PyObject *dirs = NULL, *source = NULL; | ||||
char skipchar = 0; | ||||
int ret = -1; | ||||
self->dict = NULL; | ||||
if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar)) | ||||
return -1; | ||||
dirs = PyDict_New(); | ||||
if (dirs == NULL) | ||||
return -1; | ||||
if (source == NULL) | ||||
ret = 0; | ||||
else if (PyDict_Check(source)) | ||||
ret = dirs_fromdict(dirs, source, skipchar); | ||||
else if (skipchar) | ||||
PyErr_SetString(PyExc_ValueError, | ||||
"skip character is only supported " | ||||
"with a dict source"); | ||||
else | ||||
ret = dirs_fromiter(dirs, source); | ||||
if (ret == -1) | ||||
Py_XDECREF(dirs); | ||||
else | ||||
self->dict = dirs; | ||||
return ret; | ||||
} | ||||
PyObject *dirs_addpath(dirsObject *self, PyObject *args) | ||||
{ | ||||
PyObject *path; | ||||
if (!PyArg_ParseTuple(args, "O!:addpath", &PyString_Type, &path)) | ||||
return NULL; | ||||
if (_addpath(self->dict, path) == -1) | ||||
return NULL; | ||||
Py_RETURN_NONE; | ||||
} | ||||
static PyObject *dirs_delpath(dirsObject *self, PyObject *args) | ||||
{ | ||||
PyObject *path; | ||||
if (!PyArg_ParseTuple(args, "O!:delpath", &PyString_Type, &path)) | ||||
return NULL; | ||||
if (_delpath(self->dict, path) == -1) | ||||
return NULL; | ||||
Py_RETURN_NONE; | ||||
} | ||||
static int dirs_contains(dirsObject *self, PyObject *value) | ||||
{ | ||||
return PyString_Check(value) ? PyDict_Contains(self->dict, value) : 0; | ||||
} | ||||
static void dirs_dealloc(dirsObject *self) | ||||
{ | ||||
Py_XDECREF(self->dict); | ||||
PyObject_Del(self); | ||||
} | ||||
static PyObject *dirs_iter(dirsObject *self) | ||||
{ | ||||
return PyObject_GetIter(self->dict); | ||||
} | ||||
static PySequenceMethods dirs_sequence_methods; | ||||
static PyMethodDef dirs_methods[] = { | ||||
{"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"}, | ||||
{"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"}, | ||||
{NULL} /* Sentinel */ | ||||
}; | ||||
static PyTypeObject dirsType = { PyObject_HEAD_INIT(NULL) }; | ||||
void dirs_module_init(PyObject *mod) | ||||
{ | ||||
dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains; | ||||
dirsType.tp_name = "parsers.dirs"; | ||||
dirsType.tp_new = PyType_GenericNew; | ||||
dirsType.tp_basicsize = sizeof(dirsObject); | ||||
dirsType.tp_dealloc = (destructor)dirs_dealloc; | ||||
dirsType.tp_as_sequence = &dirs_sequence_methods; | ||||
dirsType.tp_flags = Py_TPFLAGS_DEFAULT; | ||||
dirsType.tp_doc = "dirs"; | ||||
dirsType.tp_iter = (getiterfunc)dirs_iter; | ||||
dirsType.tp_methods = dirs_methods; | ||||
dirsType.tp_init = (initproc)dirs_init; | ||||
if (PyType_Ready(&dirsType) < 0) | ||||
return; | ||||
Py_INCREF(&dirsType); | ||||
PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType); | ||||
} | ||||