b8499851e252b3785d1c4548474b5ac2c7d1b8f3
5 ### (c) 2013 Mark Wooding
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of Chopwood: a password-changing service.
12 ### Chopwood is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU Affero General Public License as
14 ### published by the Free Software Foundation; either version 3 of the
15 ### License, or (at your option) any later version.
17 ### Chopwood 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 Affero General Public License for more details.
22 ### You should have received a copy of the GNU Affero General Public
23 ### License along with Chopwood; if not, see
24 ### <http://www.gnu.org/licenses/>.
26 from __future__
import with_statement
28 import contextlib
as CTX
29 from cStringIO
import StringIO
34 ### There are a number of interesting things to do with output.
36 ### The remote-service interface needs to prefix its output lines with `INFO'
37 ### tokens so that they get parsed properly.
39 ### The CGI interface needs to prefix its output with at least a
40 ### `Content-Type' header.
42 ###--------------------------------------------------------------------------
45 def http_headers(**kw
):
47 Generate mostly-formatted HTTP headers.
49 KW is a dictionary mapping HTTP header names to values. Each name is
50 converted to external form by changing underscores `_' to hyphens `-', and
51 capitalizing each constituent word. The values are converted to strings.
52 If a value is a list, a header is produced for each element. Subsequent
53 lines in values containing internal line breaks have a tab character
56 def hack_header(k
, v
):
57 return '%s: %s' %
('-'.join(i
.title() for i
in k
.split('_')),
58 str(v
).replace('\n', '\n\t'))
59 for k
, v
in kw
.iteritems():
60 if isinstance(v
, list):
61 for i
in v
: yield hack_header(k
, i
)
63 yield hack_header(k
, v
)
65 ###--------------------------------------------------------------------------
68 class BasicOutputDriver (object):
70 A base class for output drivers, providing trivial implementations of most
73 The main missing piece is the `_write' method, which should write its
74 argument to the output with as little ceremony as possible. Any fancy
75 formatting should be applied by overriding `write'.
79 """Trivial constructor."""
83 """Write MSG, as a complete line."""
84 me
.write(str(msg
) + '\n')
87 """Write MSG to the output, with any necessary decoration."""
91 """Wrap up when everything that needs saying has been said."""
95 """Emit HTTP-style headers in a distinctive way."""
96 for h
in http_headers(**kw
):
99 class BasicLineOutputDriver (BasicOutputDriver
):
101 Mixin class for line-oriented output formatting.
103 We override `write' to buffer partial lines; complete lines are passed to
104 `_writeln' to be written, presumably through the low-level `_write' method.
107 def __init__(me
, *args
, **kw
):
109 super(BasicLineOutputDriver
, me
).__init__(*args
, **kw
)
113 """Write any incomplete line accumulated so far, and clear the buffer."""
115 me
._writeln(me
._buf
.getvalue())
119 """Write MSG, sending any complete lines to the `_writeln' method."""
122 ## If there's not a complete line here then we just accumulate the
123 ## message into our buffer.
125 if not me
._buf
: me
._buf
= StringIO()
129 ## There's at least one complete line here. We take the final
130 ## incomplete line off the end.
132 lines
= msg
.split('\n')
135 ## If there's a partial line already buffered then add whatever new
136 ## stuff we have and flush it out.
138 me
._buf
.write(lines
[0])
141 ## Write out any other complete lines.
145 ## If there's a proper partial line, then start a new buffer.
151 """If there's any partial line buffered, flush it out."""
154 ###--------------------------------------------------------------------------
157 class FileOutput (BasicOutputDriver
):
158 """Output driver for writing stuff to a file."""
159 def __init__(me
, file = SYS
.stdout
, *args
, **kw
):
160 """Constructor: send output to FILE (default is stdout)."""
161 super(FileOutput
, me
).__init__(*args
, **kw
)
163 def _write(me
, text
):
164 """Output protocol: write TEXT to the ouptut file."""
167 class RemoteOutput (FileOutput
, BasicLineOutputDriver
):
168 """Output driver for decorating lines with `INFO' tags."""
169 def _writeln(me
, line
):
170 """Line output protocol: write a complete line with an `INFO' tag."""
171 me
._write('INFO %s\n' % line
)
173 ###--------------------------------------------------------------------------
176 class DelegatingOutput (BasicOutputDriver
):
177 """Fake output driver which delegates to some other driver."""
179 def __init__(me
, default
= None):
180 """Constructor: send output to DEFAULT."""
181 me
._fluid
= U
.Fluid(target
= default
)
184 def redirect_to(me
, target
):
185 """Temporarily redirect output to TARGET, closing it when finished."""
187 with me
._fluid
.bind(target
= target
):
192 ## Delegating methods.
193 def write(me
, msg
): me
._fluid
.target
.write(msg
)
194 def writeln(me
, msg
): me
._fluid
.target
.writeln(msg
)
195 def close(me
): me
._fluid
.target
.close()
196 def header(me
, **kw
): me
._fluid
.target
.header(**kw
)
198 ## Delegating properties.
200 def headerp(me
): return me
._fluid
.target
.headerp
202 ## The selected output driver. Set this with `output_to'.
203 OUT
= DelegatingOutput()
206 """Write the MSG as a line to the current output."""
209 ###----- That's all, folks --------------------------------------------------