root/masterdriverz/snakeoil-formatters/src/formatters.c @ masterdriverz%2540gentoo.org-20070520172217-uub8yeoav6bdzfr8

Revision masterdriverz%2540gentoo.org-20070520172217-uub8yeoav6bdzfr8, 21.9 kB (checked in by Charlie Shepherd <masterdriverz@…>, 20 months ago)

Move around convert_list and fix its description, add a known bugs list

Line 
1#include <errno.h>
2#include "Python.h"
3
4/*
5 * Known bugs:
6 *      - Setting first_prefix to [None] causes a segfault.
7 *      - Passing encoding to __init__ causes a segfault.
8 *      - Passing Unicode objects that cannot be
9 *        decoded by the encoding to write causes an
10 *        EncodingError to be raised, even though we
11 *        use "replace". :-\
12 */
13
14
15
16/* Duplicating this is annoying, but we need to
17   access it from the C level, so we do. */
18static PyObject *StreamClosed;
19
20/* PlainTextFormatter is abbreviated to PTF */
21
22typedef struct {
23    PyObject_HEAD
24
25    /* Public attrs */
26    PyObject *stream; /* This is actually the write method of stream if it's not a file */
27    PyObject *first_prefix;
28    PyObject *later_prefix;
29
30    /* need these for TermInfoFormatter */
31    PyObject *reset;
32    PyObject *bold;
33    PyObject *underline;
34    char *encoding;
35    int autoline;
36    int wrap;
37    long width;
38
39    /* Private */
40    PyObject *stored_stream;
41    PyObject *stored_autoline;
42    int pos;
43    int in_first_line;
44    int wrote_something;
45
46    int stream_is_file;
47} PTF_object;
48
49static PyObject *convert_list(PyObject *s) {
50    /* Convenience function, returns *s as list if it isn't one already */
51    if (PyList_Check(s))
52        return s;
53
54    /* If it's NULL we return it as we would return NULL anyway... */
55    return PySequence_List(s);
56}
57
58/*
59#define IMMUTABLE_ATTR(name)                                            \
60static int                                                              \
61PTF_set_##name (PTF_object *self, PyObject *v, void *closure)           \
62{                                                                       \
63    PyErr_SetString(PyExc_AttributeError, #name" is immutable");        \
64    return -1;                                                          \
65}
66
67#define EMPTY_IMMUTABLE_CONST(name)                                     \
68    {#name,                                                             \
69     (getter)PTF_getemptystring,                                        \
70     (setter)PTF_set_##name,                                            \
71     #name,                                                             \
72     NULL}
73
74IMMUTABLE_ATTR(bold)
75IMMUTABLE_ATTR(reset)
76IMMUTABLE_ATTR(underline)
77*/
78
79#define annoying_pyobj_func(name, attr) pyobj_get_func(name, attr) \
80    pyobj_set_func(name, attr)
81
82#define pyobj_get_func(name, attr) static PyObject *               \
83PTF_getobj_##name(PTF_object *self, void *closure)                 \
84{                                                                  \
85    Py_INCREF(self->attr);                                         \
86    return self->attr;                                             \
87}                                                                  \
88
89#define pyobj_set_func(name, attr) static int                      \
90PTF_setobj_##name(PTF_object *self, PyObject *value, void *closure)\
91{                                                                  \
92    if (value == NULL) {                                           \
93        PyErr_SetString(PyExc_TypeError,                           \
94                "Cannot delete the "#name" attribute");            \
95        return -1;                                                 \
96    }                                                              \
97    Py_DECREF(self->attr);                                         \
98    Py_INCREF(value);                                              \
99    self->attr = value;                                            \
100    return 0;                                                      \
101}
102
103#define pyobj_func(name) annoying_pyobj_func(name, name)
104
105pyobj_func(first_prefix)
106pyobj_func(later_prefix)
107pyobj_func(bold)
108pyobj_func(underline)
109pyobj_func(reset)
110
111pyobj_get_func(autoline, stored_autoline)
112
113static PyObject *
114PTF_getwidth(PTF_object *self, void *closure)
115{
116    return PyInt_FromLong(self->width);
117}
118
119static int
120PTF_setwidth(PTF_object *self, PyObject *value, void *closure)
121{
122    long tmp = PyInt_AsLong(value);
123    if (tmp == -1 && PyErr_Occurred())
124        return -1;
125    self->width = tmp;
126    return 0;
127}
128
129static int
130PTF_setautoline(PTF_object *self, PyObject *value, void *closure)
131{
132    int tmp;
133
134    if (!value) {
135        PyErr_SetString(PyExc_TypeError, "Cannot delete the autoline attribute");
136        return -1;
137    }
138
139     tmp = PyObject_IsTrue(value);
140     if (tmp == -1)
141         return -1;
142
143     self->autoline = tmp;
144     self->stored_autoline = value;
145     return 0;
146}
147
148static int
149PTF_setprefix(PTF_object *self, PyObject *value, char closure)
150{
151    PyObject *tmp = convert_list(value);
152
153    if (!tmp)
154        return -1;
155
156    if (closure == 'f')
157        self->first_prefix = tmp;
158    else if (closure == 'l')
159        self->later_prefix = tmp;
160
161    return 0;
162}
163
164static PyObject *
165PTF_returnemptystring(PTF_object *self, PyObject *args)
166{
167    char *s;
168    if (!PyArg_ParseTuple(args, "|s", s))
169        return NULL;
170
171    PyObject *ret = PyString_FromString("");
172    if (!ret)
173        return NULL;
174    return ret;
175}
176
177
178static PyObject *
179PTF_getstream(PTF_object *self, void *closure)
180{
181    Py_INCREF(self->stored_stream);
182    return self->stored_stream;
183}
184
185
186static int
187PTF_setstream(PTF_object *self, PyObject *value, void *closure)
188{
189    PyObject *tmp;
190    if (value == NULL) {
191        PyErr_SetString(PyExc_TypeError, "Cannot delete the stream attribute");
192        return -1;
193    }
194
195    if (PyFile_Check(value)) {
196        self->stream_is_file = 1;
197        self->stream = value;
198    } else {
199        tmp = PyObject_GetAttrString(value, "write");
200        if (!tmp) {
201            if (!PyErr_Occurred())
202                PyErr_SetString(PyExc_TypeError, "stream has no write method");
203            return -1;
204        }
205        self->stream_is_file = 0;
206        self->stream = tmp;
207    }
208
209    Py_XDECREF(self->stored_stream);
210    Py_INCREF(value);
211    self->stored_stream = value;
212
213    return 0;
214}
215
216
217static int
218PTF_traverse(PTF_object *self, visitproc visit, void *arg)
219{
220    Py_VISIT(self->stored_stream);
221    Py_VISIT(self->first_prefix);
222    Py_VISIT(self->later_prefix);
223    Py_VISIT(self->reset);
224    Py_VISIT(self->bold);
225    Py_VISIT(self->underline);
226    return 0;
227}
228
229static int
230PTF_clear(PTF_object *self)
231{
232    Py_CLEAR(self->stored_stream);
233    Py_CLEAR(self->first_prefix);
234    Py_CLEAR(self->later_prefix);
235    Py_CLEAR(self->reset);
236    Py_CLEAR(self->bold);
237    Py_CLEAR(self->underline);
238    return 0;
239}
240
241static void
242PTF_dealloc(PTF_object *self) {
243    PTF_clear(self);
244    self->ob_type->tp_free((PyObject*)self);
245}
246
247#if 0
248    /* Public attrs */
249    PyObject *stream; /* This is actually the write method of stream if it's not a file */
250    PyObject *first_prefix;
251    PyObject *later_prefix;
252
253    /* need these for TermInfoFormatter */
254    PyObject *reset;
255    PyObject *bold;
256    PyObject *underline;
257    char *encoding;
258    int autoline;
259    int wrap;
260    int width;
261
262    /* Private */
263    PyObject *stored_stream;
264    int pos;
265    int in_first_line;
266    int wrote_something;
267
268    int stream_is_file;
269#endif
270
271#define blank_string(what) self->what = PyString_FromString(""); \
272        if (!self->what)                                         \
273            goto error
274
275static PyObject *
276PTF_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
277{
278    PTF_object *self;
279    self = (PTF_object *)type->tp_alloc(type, 0);
280
281    /* Private */
282    self->autoline = 1;
283    self->wrap = 0;
284    self->pos = 0;
285    self->in_first_line = 1;
286    self->wrote_something = 0;
287
288    /* Defaults */
289    self->width = 79;
290    self->encoding = "ascii"; /* this should pick up on the system default but i'm lazy.
291                               * So sue me. */
292    self->first_prefix = PyList_New(0);
293    self->later_prefix = PyList_New(0);
294
295    Py_INCREF(Py_True);
296    self->stored_autoline = Py_True;
297
298    blank_string(bold);
299    blank_string(reset);
300    blank_string(underline);
301    return (PyObject *)self;
302error:
303    return NULL;
304}
305
306static int
307PTF_init(PTF_object *self, PyObject *args, PyObject *kwds)
308{
309    PyObject *encoding=NULL;
310    char *s;
311    static char *kwlist[] = {"stream", "width", "encoding", NULL};
312
313    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iz", kwlist,
314        &self->stream, &self->width, &encoding))
315        return -1;
316
317    if (encoding != NULL && encoding != Py_None) {
318        s = PyString_AsString(encoding);
319        if (!s)
320             return -1;
321        printf("setting encoding to %s\n", s);
322        self->encoding = s;
323    }
324
325    /* Seems kinda stupid to do this, but it needs to be done. */
326    PTF_setstream(self, self->stream, NULL);
327
328    return 0;
329}
330
331
332static int
333_write_prefix(PTF_object *self, int wrap) {
334    PyObject *prefix, *iter, *arg=NULL, *tmp, *stream;
335
336    if (self->in_first_line)
337        prefix = self->first_prefix;
338    else
339        prefix = self->later_prefix;
340
341    iter = PyObject_GetIter(prefix);
342    if (!iter)
343        return -1;
344    while ((arg = PyIter_Next(iter))) {
345        while ((PyCallable_Check(arg))) {
346            tmp = PyObject_CallFunctionObjArgs(arg, (PyObject*)self, NULL);
347            if (!tmp)
348                goto error;
349            Py_DECREF(arg);
350            arg = tmp;
351        }
352
353        if (arg == Py_None)
354            continue;
355
356        if (PyUnicode_Check(arg)) {
357            tmp = PyUnicode_AsEncodedString(arg, self->encoding, "replace");
358            if (!tmp)
359                goto error;
360            Py_DECREF(arg);
361            arg = tmp;
362        }
363
364        if (!(PyString_Check(arg))) {
365            tmp = PyObject_Str(arg);
366            if (!tmp)
367                goto error;
368            Py_DECREF(arg);
369            arg = tmp;
370        }
371
372        if (self->stream_is_file) {
373            if (PyFile_WriteObject(arg, stream, Py_PRINT_RAW))
374                goto error;
375        } else {
376            if (!PyObject_CallFunctionObjArgs(self->stream, arg, NULL))
377                goto error;
378        }
379
380        if (wrap && (self->pos >= self->width))
381            self->pos = self->width-10;
382    }
383    return 0;
384error:
385    return -1;
386}
387
388#define getitem(arg) arg = PyDict_GetItemString(kwargs, #arg);     \
389        if (!arg && PyErr_Occurred())                              \
390            goto error
391
392static PyObject *
393PTF_write(PTF_object *self, PyObject *args, PyObject *kwargs) {
394    PyObject *wrap=NULL, *autoline=NULL, *prefixes=NULL, *prefix=NULL;
395    PyObject *first_prefixes=NULL, *later_prefixes=NULL;
396    PyObject *first_prefix=NULL, *later_prefix=NULL;
397    PyObject *tmp=NULL, *arg=NULL;
398    PyObject *iterator=NULL, *bit=NULL, *e=NULL;
399
400    int i, maxlen, space, i_autoline;
401    char *p;
402
403    wrap = self->wrap;
404    if ((kwargs)) {
405        getitem(prefixes);
406        getitem(prefix);
407        getitem(first_prefixes);
408        getitem(later_prefixes);
409        getitem(later_prefix);
410        getitem(wrap);
411        getitem(autoline);
412    }
413
414
415
416   /* "args", "prefixes", "prefix", "first_prefixes", "first_prefix", "later_prefixes", "later_prefix", "wrap", "autoline", NULL};
417
418    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOOOOOOO", kwlist,
419        &args_t, &prefixes, &prefix, &first_prefixes, &first_prefix, &later_prefixes, &later_prefix, &wrap, &autoline)) {
420        return NULL;
421    }*/
422
423
424    if (autoline) {
425        i_autoline = PyObject_IsTrue(autoline);
426        if (i_autoline == -1)
427            goto error;
428    } else {
429        i_autoline = self->autoline;
430    }
431
432    if ((prefixes)) {
433        if ((first_prefixes) || (later_prefixes)) {
434            PyErr_SetString(PyExc_TypeError,
435                "do not pass first_prefixes or later_prefixes "
436                "if prefixes is passed");
437            goto error;
438        }
439        first_prefixes = later_prefixes = prefixes;
440    }
441
442
443    if ((prefix)) {
444        if ((first_prefix) || (later_prefix)) {
445            PyErr_SetString(PyExc_TypeError,
446                "do not pass first_prefix or later_prefix with prefix");
447            goto error;
448        }
449        first_prefix = later_prefix = prefix;
450    }
451
452    if ((first_prefix)) {
453        if ((first_prefixes)) {
454            PyErr_SetString(PyExc_TypeError,
455                "do not pass both first_prefix and first_prefixes");
456            goto error;
457        }
458        first_prefixes = PyTuple_Pack(1, first_prefix);
459    }
460
461    if ((later_prefix)) {
462        if (later_prefixes) {
463            PyErr_SetString(PyExc_TypeError,
464                "do not pass both later_prefix and later_prefixes");
465            goto error;
466        }
467        later_prefixes = PyTuple_Pack(1, later_prefix);
468    }
469
470    if ((first_prefixes))
471        if (!_PyList_Extend(self->first_prefix, first_prefixes))
472            goto error;
473
474    if ((later_prefixes))
475        if (!_PyList_Extend(self->later_prefix, later_prefixes))
476            goto error;
477
478    iterator = PyObject_GetIter(args);
479    while (arg = PyIter_Next(iterator)) {
480        /* If we're at the start of the line, write our prefix.
481         * There is a deficiency here: if neither our arg nor our
482         * prefix affect _pos (both are escape sequences or empty)
483         * we will write prefix more than once. This should not
484         * matter.
485         */
486        if (self->pos == 0)
487            if (_write_prefix(self, wrap))
488                goto finally;
489
490
491        while (PyCallable_Check(arg)) {
492            arg = PyObject_CallFunctionObjArgs(arg, (PyObject*)self, NULL);
493            if (!arg)
494                goto finally;
495        }
496
497        if (arg == Py_None)
498            continue;
499
500        if (!PyString_Check(arg)) {
501            tmp = PyObject_Str(arg);
502            if (!tmp)
503                goto finally;
504            Py_DECREF(arg);
505            arg = tmp;
506        }
507
508        if (PyUnicode_Check(arg)) {
509            tmp = PyUnicode_AsEncodedString(arg, self->encoding, "replace");
510            if (!tmp)
511                goto finally;
512            Py_DECREF(arg);
513            arg = tmp;
514        }
515
516        if (!PyString_GET_SIZE(arg))
517            /* There's nothing to write, so skip this bit... */
518            continue;
519
520        while (wrap && ((self->pos + PyString_GET_SIZE(arg)) > self->width)) {
521            /* We have to split. */
522            maxlen = self->width - self->pos;
523            p = PyString_AS_STRING(arg);
524            for (space = -1, i = 0; *p++, i++;) {
525                if (i == maxlen)
526                    break;
527                if (*p == ' ') {
528                    space = i;
529                    break;
530                }
531            }
532
533            if (space == -1) {
534                /* No space to split on.
535
536                * If we are on the first line we can simply go to
537                * the next (this helps if the "later" prefix is
538                * shorter and should not really matter if not).
539
540                * If we are on the second line and have already
541                * written something we can also go to the next
542                * line.
543                */
544
545                if (self->in_first_line || self->wrote_something) {
546                    bit = PyString_FromString("");
547                    if (!bit)
548                        goto finally;
549                }
550                else {
551                    /* Forcibly split this as far to the right as
552                     * possible.
553                     */
554                    bit = PySequence_GetSlice(arg, 0, maxlen);
555                    tmp = PySequence_GetSlice(arg, maxlen, 0);
556                    if (!bit || !tmp)
557                        goto finally;
558                    Py_DECREF(arg);
559                    arg = tmp;
560                }
561            } else {
562                /* Omit the space we split on.*/
563                bit = PySequence_GetSlice(arg, NULL, space);
564                tmp = PySequence_GetSlice(arg, space+1, NULL);
565                if (!bit || !tmp)
566                    goto finally;
567                Py_DECREF(arg);
568                arg = tmp;
569            }
570
571            if (self->stream_is_file) {
572                if (PyFile_WriteObject(bit, self->stream, Py_PRINT_RAW))
573                    goto finally;
574            } else {
575                if (!PyObject_CallFunctionObjArgs(self->stream, bit))
576                    goto finally;
577            }
578
579            self->pos = 0;
580            self->in_first_line = 0;
581            self->wrote_something = 0;
582            if (_write_prefix(self, wrap))
583                goto finally;
584
585        }
586
587        if (self->stream_is_file) {
588            if (PyFile_WriteObject(arg, self->stream, Py_PRINT_RAW))
589                goto finally;
590        } else {
591            if (!PyObject_CallFunctionObjArgs(self->stream, arg, NULL))
592                goto finally;
593        }
594        if (!i_autoline) {
595            self->wrote_something = 1;
596            self->pos += PyString_GET_SIZE(arg);
597        }
598    }
599
600    if (i_autoline) {
601        if (self->stream_is_file) {
602            if (PyFile_WriteString("\n", self->stream))
603                goto finally;
604        } else {
605            if (!PyObject_CallFunction(self->stream, "(s)", "\n"))
606                goto finally;
607        }
608        self->in_first_line = 1;
609        self->wrote_something = 0;
610        self->pos = 0;
611    }
612
613finally:
614
615    if (first_prefixes)
616        PyList_SetSlice(self->first_prefix, -PyList_GET_SIZE(first_prefixes), NULL, NULL);
617    if (later_prefixes)
618        PyList_SetSlice(self->later_prefix, -PyList_GET_SIZE(later_prefixes), NULL, NULL);
619
620    e = PyErr_Occurred();
621    if (e) {
622        if (PyErr_ExceptionMatches(PyExc_IOError) &&
623            PyInt_AS_LONG(PyObject_GetAttrString(e, "errno")) == EPIPE)
624                PyErr_SetObject(e, StreamClosed);
625        goto error;
626    }
627
628   Py_RETURN_NONE;
629
630error:
631    Py_XDECREF(wrap);
632    Py_XDECREF(autoline);
633    Py_XDECREF(prefixes);
634    Py_XDECREF(first_prefixes);
635    Py_XDECREF(later_prefixes);
636    Py_XDECREF(tmp);
637    return NULL;
638}
639
640static PyMethodDef PTF_methods[] = {
641    {"write", (PyCFunction)PTF_write, METH_VARARGS | METH_KEYWORDS,
642     "Return the name, combining the first and last name"
643    },
644    {"fg", (PyCFunction)PTF_returnemptystring, METH_VARARGS,
645     ""
646    },
647    {"bg", (PyCFunction)PTF_returnemptystring, METH_VARARGS,
648     ""
649    },
650    {"title", (PyCFunction)PTF_returnemptystring, METH_VARARGS,
651     ""
652    },
653    {NULL}  /* Sentinel */
654};
655
656#define pyobj_struct(name) {#name,                         \
657     (getter)PTF_getobj_##name, (setter)PTF_setobj_##name, \
658     #name,                                                \
659     NULL}
660
661
662static PyGetSetDef PTF_getseters[] = {
663    {"stream",
664     (getter)PTF_getstream, (setter)PTF_setstream,
665     "stream to write to",
666     's'},
667
668    {"first_prefix",
669     (getter)PTF_getobj_first_prefix, (setter)PTF_setprefix,
670     "the first prefix",
671     'f'},
672
673    {"later_prefix",
674     (getter)PTF_getobj_later_prefix, (setter)PTF_setprefix,
675     "later prefixes",
676     'l'},
677
678    {"autoline",
679     (getter)PTF_getobj_autoline, (setter)PTF_setautoline,
680     "autoline",
681     NULL},
682
683    {"width",
684     (getter)PTF_getwidth, (setter)PTF_setwidth,
685     "width",
686     NULL},
687
688    pyobj_struct(bold),
689    pyobj_struct(underline),
690    pyobj_struct(reset),
691
692    {NULL} /* Sentinel */
693};
694
695PyDoc_STRVAR(PTF_doc,
696"PTF(iter1 [,iter2 [...]]) --> izip object\n\
697\n\
698Return a PTF object whose .next() method returns a tuple where\n\
699the i-th element comes from the i-th iterable argument.  The .next()\n\
700method continues until the shortest iterable in the argument sequence\n\
701is exhausted and then it raises StopIteration.  Works like the zip()\n\
702function but consumes less memory by returning an iterator instead of\n\
703a list.");
704
705
706static PyTypeObject PTF_type = {
707        PyObject_HEAD_INIT(NULL)
708        0,                              /* ob_size */
709        "formatters.PlainTextFormatter",/* tp_name */
710        sizeof(PTF_object),             /* tp_basicsize */
711        0,                              /* tp_itemsize */
712        (destructor)PTF_dealloc,        /* tp_dealloc */
713        0,                              /* tp_print */
714        0,                              /* tp_getattr */
715        0,                              /* tp_setattr */
716        0,                              /* tp_compare */
717        0,                              /* tp_repr */
718        0,                              /* tp_as_number */
719        0,                              /* tp_as_sequence */
720        0,                              /* tp_as_mapping */
721        0,                              /* tp_hash */
722        0,                              /* tp_call */
723        0,                              /* tp_str */
724        0,                              /* tp_getattro */
725        0,                              /* tp_setattro */
726        0,                              /* tp_as_buffer */
727        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_CLASS | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,    /* tp_flags */
728        PTF_doc,                        /* tp_doc */
729        (traverseproc)PTF_traverse,                              /* tp_traverse */
730        (inquiry)PTF_clear,                              /* tp_clear */
731        0,                              /* tp_richcompare */
732        0,                              /* tp_weaklistoffset */
733        0,                              /* tp_iter */
734        0,                              /* tp_iternext */
735        PTF_methods,                    /* tp_methods */
736        0,                              /* tp_members */
737        PTF_getseters,                  /* tp_getset */
738        0,                              /* tp_base */
739        0,                              /* tp_dict */
740        0,                              /* tp_descr_get */
741        0,                              /* tp_descr_set */
742        0,                              /* tp_dictoffset */
743        (initproc)PTF_init,             /* tp_init */
744        0,                              /* tp_alloc */
745        PTF_new,                        /* tp_new */
746};
747
748PyDoc_STRVAR(formatters_module_doc, "my funky module\n");
749
750PyMODINIT_FUNC
751init_formatters()
752{
753    PyObject *m = Py_InitModule3("_formatters", NULL, formatters_module_doc);
754    if (!m)
755        return;
756
757    PyObject *stream_closed = PyErr_NewException("snakeoil._formatters.StreamClosed", PyExc_KeyboardInterrupt, NULL);
758    Py_INCREF(stream_closed);
759    if (PyModule_AddObject(m, "StreamClosed", stream_closed))
760        return;
761
762    if (PyType_Ready(&PTF_type) < 0)
763        return;
764    Py_INCREF(&PTF_type);
765    if (PyModule_AddObject(m, "PlainTextFormatter", (PyObject *)&PTF_type) == -1)
766        return;
767}
Note: See TracBrowser for help on using the browser.