chiark / gitweb /
@@@ lbuf needs test
[mLib-python] / lbuf.pyx
1 ### -*-pyrex-*-
2 ###
3 ### Line buffering
4 ###
5 ### (c) 2005 Straylight/Edgeware
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This file is part of the Python interface to mLib.
11 ###
12 ### mLib/Python is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
16 ###
17 ### mLib/Python is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ### GNU General Public License for more details.
21 ###
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 Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 LBUF_CRLF = _LBUF_CRLF
27 LBUF_STRICTCRLF = _LBUF_STRICTCRLF
28
29 cdef class LineBuffer:
30   """
31   LineBuffer([lineproc = None], [eofproc = None])
32
33   Split an incoming stream into lines.
34   """
35
36   cdef lbuf b
37   cdef object _line
38   cdef object _eof
39
40   def __cinit__(me):
41     lbuf_init(&me.b, _lbfunc, <void *>me)
42     me._line = None
43     me._eof = None
44   def __dealloc__(me):
45     lbuf_destroy(&me.b)
46
47   def __init__(me, object lineproc = None, object eofproc = None):
48     me._line = _checkcallable(lineproc, 'line proc')
49     me._eof = _checkcallable(eofproc, 'eof proc')
50
51   @property
52   def activep(me):
53     """LB.activep -> BOOL: is the buffer still active?"""
54     return <bint>(me.b.f & LBUF_ENABLE)
55
56   @property
57   def delim(me):
58     """LB.delim -> CHAR | LBUF_...: line-end delimiter"""
59     if me.b.delim == _LBUF_CRLF or me.b.delim == _LBUF_STRICTCRLF:
60       return me.b.delim
61     else:
62       return chr(me.b.delim)
63   @delim.setter
64   def delim(me, d):
65     cdef ch
66     if d == _LBUF_CRLF or d == _LBUF_STRICTCRLF:
67       me.b.delim = d
68     else:
69       me.b.delim = ord(d)
70
71   @property
72   def size(me):
73     """LB.size -> INT: buffer size limit"""
74     return me.b.sz
75   @size.setter
76   def size(me, size_t sz):
77     lbuf_setsize(&me.b, sz)
78
79   @property
80   def lineproc(me):
81     """LB.lineproc -> FUNC: call FUNC(LINE) on each line"""
82     return me._line
83   @lineproc.setter
84   def lineproc(me, object proc):
85     me._line = _checkcallable(proc, 'line proc')
86   @lineproc.deleter
87   def lineproc(me):
88     me._line = None
89
90   @property
91   def eofproc(me):
92     """LB.eofproc -> FUNC: call FUNC() at end-of-file"""
93     return me._eof
94   @eofproc.setter
95   def eofproc(me, object proc):
96     me._eof = _checkcallable(proc, 'eof proc')
97   @eofproc.deleter
98   def eofproc(me):
99     me._eof = None
100
101   def enable(me):
102     """LB.enable(): enable the buffer, allowing lines to be emitted"""
103     if me.b.f & LBUF_ENABLE:
104       raise ValueError('already enabled')
105     me.b.f = me.b.f | LBUF_ENABLE
106     me.enabled()
107     return me
108
109   def disable(me):
110     """LB.disable(): disable the buffer, suspending line emission"""
111     if not (me.b.f & LBUF_ENABLE):
112       raise ValueError('already disabled')
113     me.b.f = me.b.f & ~LBUF_ENABLE
114     me.disabled()
115     return me
116
117   def close(me):
118     """LB.close(): report the end of the input stream"""
119     if not (me.b.f & LBUF_ENABLE):
120       raise ValueError('buffer disabled')
121     lbuf_close(&me.b)
122     return me
123
124   @property
125   def free(me):
126     """LB.free -> INT: amount of space remaining in buffer"""
127     cdef char *p
128     return lbuf_free(&me.b, &p)
129
130   def flush(me, str):
131     """LB.flush(STR) -> insert STR into the buffer and emit lines"""
132     cdef Py_ssize_t len
133     cdef char *p
134     cdef char *q
135     cdef size_t n
136
137     ## Get the input string as bytes.
138     TEXT_PTRLEN(str, &p, &len)
139
140     ## Feed the input string into the buffer.
141     while len > 0:
142       n = lbuf_free(&me.b, &q)
143       if n > len:
144         n = len
145       memcpy(q, p, n); p += n; len -= n
146       if not (me.b.f & LBUF_ENABLE):
147         break
148       lbuf_flush(&me.b, q, n)
149
150     IF PYVERSION >= (3,):
151       ## And here we have a problem.  The line buffer may have been disabled
152       ## while we still have text to push through, and the split may be
153       ## within a UTF-8-encoded scalar.  Let's see if there's anything to do
154       ## before we start worrying too much.
155
156       if len == 0:
157         ## We pushed all of our data into the buffer, so there's nothing left
158         ## over.
159
160         pass
161
162       elif me.b.len == me.b.sz:
163         ## We filled the buffer up, and there was no newline.  We already
164         ## sent the truncated line to the output function, but we still have
165         ## the remaining piece.  Trim any remaining pieces of the UTF-8
166         ## scalar from the start of the leftover string.
167
168         while len > 0 and 128 <= <unsigned char>p[0] < 192:
169           p += 1; len -= 1
170
171       else:
172         ## The remaining possibility is the tricky one.  After accepting a
173         ## full line, the line function has disabled further input.  We've
174         ## just filled the buffer up and we have stuff left over.  If the
175         ## leftover portion starts midway through a UTF-8-encoded scalar then
176         ## Python won't let us stuff it back into a string.  So work
177         ## backwards through the buffer until we reach the start of a scalar.
178         ##
179         ## This must work, because the only way the tail end of a scalar
180         ## could be left over is if the start of that scalar came from our
181         ## original input string.
182
183         while 128 <= <unsigned char>p[0] < 192:
184           p -= 1; len += 1; me.b.len -= 1
185
186       ## Everything is OK now.
187       return TEXT_FROMSTRLEN(p, len)
188
189   def enabled(me):
190     """LB.enabled(): called when buffer is enabled"""
191     pass
192   def disabled(me):
193     """LB.disabled(): called when buffer is disabled"""
194     pass
195   def line(me, line):
196     """LB.line(LINE): called for each completed line"""
197     return _maybecall(me._line, (line,))
198   def eof(me):
199     """LB.eof(): called at end-of-file"""
200     return _maybecall(me._eof, ())
201
202 cdef void _lbfunc(char *s, size_t n, void *arg):
203   cdef LineBuffer sb = <LineBuffer>arg
204   if s is NULL:
205     sb.eof()
206   else:
207     IF PYVERSION >= (3,):
208       ## If the input line was too long and has been truncated then there
209       ## might be an incomplete Unicode scalar at the end.  Strip this away.
210
211       while n > 0 and 128 <= <unsigned char>s[n - 1] < 192:
212         n -= 1
213
214     sb.line(TEXT_FROMSTRLEN(s, n))
215
216 ###----- That's all, folks --------------------------------------------------