blob: 0607526404ced4d19a05a7ec56068d91e29d3466 [file] [log] [blame]
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +00001"""Generic output formatting.
2
3Formatter objects transform an abstract flow of formatting events into
4specific output events on writer objects. Formatters manage several stack
5structures to allow various properties of a writer object to be changed and
6restored; writers need not be able to handle relative changes nor any sort
7of ``change back'' operation. Specific writer properties which may be
8controlled via formatter objects are horizontal alignment, font, and left
9margin indentations. A mechanism is provided which supports providing
10arbitrary, non-exclusive style settings to a writer as well. Additional
11interfaces facilitate formatting events which are not reversible, such as
Tim Peters88869f92001-01-14 23:36:06 +000012paragraph separation.
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +000013
14Writer objects encapsulate device interfaces. Abstract devices, such as
15file formats, are supported as well as physical devices. The provided
16implementations all work with abstract devices. The interface makes
17available mechanisms for setting the properties which formatter objects
Tim Peters88869f92001-01-14 23:36:06 +000018manage and inserting data into the output.
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +000019"""
20
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000021import string
22import sys
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000023from types import StringType
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000024
25
26AS_IS = None
27
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000028
Guido van Rossum909507d1995-10-06 15:31:30 +000029class NullFormatter:
30
Guido van Rossumccd8b191996-10-07 21:29:49 +000031 def __init__(self, writer=None):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000032 if not writer:
33 writer = NullWriter()
34 self.writer = writer
Guido van Rossum909507d1995-10-06 15:31:30 +000035 def end_paragraph(self, blankline): pass
36 def add_line_break(self): pass
Fred Drakeca8cdc61996-10-08 21:57:47 +000037 def add_hor_rule(self, *args, **kw): pass
38 def add_label_data(self, format, counter, blankline=None): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000039 def add_flowing_data(self, data): pass
40 def add_literal_data(self, data): pass
41 def flush_softspace(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000042 def push_alignment(self, align): pass
43 def pop_alignment(self): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000044 def push_font(self, x): pass
45 def pop_font(self): pass
46 def push_margin(self, margin): pass
47 def pop_margin(self): pass
48 def set_spacing(self, spacing): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000049 def push_style(self, *styles): pass
50 def pop_style(self, n=1): pass
51 def assert_line_data(self, flag=1): pass
Guido van Rossum909507d1995-10-06 15:31:30 +000052
53
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000054class AbstractFormatter:
55
Guido van Rossum8e449911996-08-26 16:19:23 +000056 # Space handling policy: blank spaces at the boundary between elements
57 # are handled by the outermost context. "Literal" data is not checked
58 # to determine context, so spaces in literal data are handled directly
59 # in all circumstances.
60
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000061 def __init__(self, writer):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000062 self.writer = writer # Output device
63 self.align = None # Current alignment
64 self.align_stack = [] # Alignment stack
65 self.font_stack = [] # Font state
66 self.margin_stack = [] # Margin state
67 self.spacing = None # Vertical spacing state
68 self.style_stack = [] # Other state, e.g. color
69 self.nospace = 1 # Should leading space be suppressed
70 self.softspace = 0 # Should a space be inserted
71 self.para_end = 1 # Just ended a paragraph
72 self.parskip = 0 # Skipped space between paragraphs?
73 self.hard_break = 1 # Have a hard break
74 self.have_label = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000075
76 def end_paragraph(self, blankline):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000077 if not self.hard_break:
78 self.writer.send_line_break()
79 self.have_label = 0
80 if self.parskip < blankline and not self.have_label:
81 self.writer.send_paragraph(blankline - self.parskip)
82 self.parskip = blankline
83 self.have_label = 0
84 self.hard_break = self.nospace = self.para_end = 1
85 self.softspace = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000086
87 def add_line_break(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000088 if not (self.hard_break or self.para_end):
89 self.writer.send_line_break()
90 self.have_label = self.parskip = 0
91 self.hard_break = self.nospace = 1
92 self.softspace = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +000093
Guido van Rossum9787bdaf1996-05-28 23:50:49 +000094 def add_hor_rule(self, *args, **kw):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000095 if not self.hard_break:
96 self.writer.send_line_break()
97 apply(self.writer.send_hor_rule, args, kw)
98 self.hard_break = self.nospace = 1
99 self.have_label = self.para_end = self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000100
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000101 def add_label_data(self, format, counter, blankline = None):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000102 if self.have_label or not self.hard_break:
103 self.writer.send_line_break()
104 if not self.para_end:
105 self.writer.send_paragraph((blankline and 1) or 0)
106 if type(format) is StringType:
107 self.writer.send_label_data(self.format_counter(format, counter))
108 else:
109 self.writer.send_label_data(format)
110 self.nospace = self.have_label = self.hard_break = self.para_end = 1
111 self.softspace = self.parskip = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000112
113 def format_counter(self, format, counter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000114 label = ''
115 for c in format:
Fred Drake6f6a14f2001-05-11 19:25:08 +0000116 if c == '1':
117 label = label + ('%d' % counter)
118 elif c in 'aA':
119 if counter > 0:
120 label = label + self.format_letter(c, counter)
121 elif c in 'iI':
122 if counter > 0:
123 label = label + self.format_roman(c, counter)
124 else:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000125 label = label + c
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000126 return label
127
128 def format_letter(self, case, counter):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000129 label = ''
130 while counter > 0:
131 counter, x = divmod(counter-1, 26)
Fred Drake6f6a14f2001-05-11 19:25:08 +0000132 # This makes a strong assumption that lowercase letters
133 # and uppercase letters form two contiguous blocks, with
134 # letters in order!
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000135 s = chr(ord(case) + x)
136 label = s + label
137 return label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000138
139 def format_roman(self, case, counter):
140 ones = ['i', 'x', 'c', 'm']
141 fives = ['v', 'l', 'd']
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000142 label, index = '', 0
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 # This will die of IndexError when counter is too big
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000144 while counter > 0:
145 counter, x = divmod(counter, 10)
146 if x == 9:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000147 label = ones[index] + ones[index+1] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000148 elif x == 4:
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000149 label = ones[index] + fives[index] + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000150 else:
151 if x >= 5:
152 s = fives[index]
153 x = x-5
154 else:
155 s = ''
156 s = s + ones[index]*x
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000157 label = s + label
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000158 index = index + 1
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000159 if case == 'I':
Eric S. Raymondb08b2d32001-02-09 11:10:16 +0000160 return label.upper()
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000161 return label
162
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000163 def add_flowing_data(self, data,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000164 # These are only here to load them into locals:
165 whitespace = string.whitespace,
166 join = string.join, split = string.split):
167 if not data: return
168 # The following looks a bit convoluted but is a great improvement over
169 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
170 prespace = data[:1] in whitespace
171 postspace = data[-1:] in whitespace
172 data = join(split(data))
173 if self.nospace and not data:
174 return
175 elif prespace or self.softspace:
176 if not data:
177 if not self.nospace:
178 self.softspace = 1
179 self.parskip = 0
180 return
181 if not self.nospace:
182 data = ' ' + data
183 self.hard_break = self.nospace = self.para_end = \
184 self.parskip = self.have_label = 0
185 self.softspace = postspace
186 self.writer.send_flowing_data(data)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000187
188 def add_literal_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000189 if not data: return
190 if self.softspace:
191 self.writer.send_flowing_data(" ")
192 self.hard_break = data[-1:] == '\n'
193 self.nospace = self.para_end = self.softspace = \
194 self.parskip = self.have_label = 0
195 self.writer.send_literal_data(data)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000196
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000197 def flush_softspace(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000198 if self.softspace:
199 self.hard_break = self.para_end = self.parskip = \
200 self.have_label = self.softspace = 0
201 self.nospace = 1
202 self.writer.send_flowing_data(' ')
Guido van Rossumc7ae9201995-09-30 16:49:58 +0000203
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000204 def push_alignment(self, align):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000205 if align and align != self.align:
206 self.writer.new_alignment(align)
207 self.align = align
208 self.align_stack.append(align)
209 else:
210 self.align_stack.append(self.align)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000211
212 def pop_alignment(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000213 if self.align_stack:
214 del self.align_stack[-1]
215 if self.align_stack:
216 self.align = align = self.align_stack[-1]
217 self.writer.new_alignment(align)
218 else:
219 self.align = None
220 self.writer.new_alignment(None)
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000221
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000222 def push_font(self, (size, i, b, tt)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000223 if self.softspace:
224 self.hard_break = self.para_end = self.softspace = 0
225 self.nospace = 1
226 self.writer.send_flowing_data(' ')
227 if self.font_stack:
228 csize, ci, cb, ctt = self.font_stack[-1]
229 if size is AS_IS: size = csize
230 if i is AS_IS: i = ci
231 if b is AS_IS: b = cb
232 if tt is AS_IS: tt = ctt
233 font = (size, i, b, tt)
234 self.font_stack.append(font)
235 self.writer.new_font(font)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000236
237 def pop_font(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000238 if self.font_stack:
239 del self.font_stack[-1]
240 if self.font_stack:
241 font = self.font_stack[-1]
242 else:
243 font = None
244 self.writer.new_font(font)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000245
246 def push_margin(self, margin):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000247 self.margin_stack.append(margin)
248 fstack = filter(None, self.margin_stack)
249 if not margin and fstack:
250 margin = fstack[-1]
251 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000252
253 def pop_margin(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000254 if self.margin_stack:
255 del self.margin_stack[-1]
256 fstack = filter(None, self.margin_stack)
257 if fstack:
258 margin = fstack[-1]
259 else:
260 margin = None
261 self.writer.new_margin(margin, len(fstack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000262
263 def set_spacing(self, spacing):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000264 self.spacing = spacing
265 self.writer.new_spacing(spacing)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000266
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000267 def push_style(self, *styles):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000268 if self.softspace:
269 self.hard_break = self.para_end = self.softspace = 0
270 self.nospace = 1
271 self.writer.send_flowing_data(' ')
272 for style in styles:
273 self.style_stack.append(style)
274 self.writer.new_styles(tuple(self.style_stack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000275
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000276 def pop_style(self, n=1):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000277 del self.style_stack[-n:]
278 self.writer.new_styles(tuple(self.style_stack))
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000279
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000280 def assert_line_data(self, flag=1):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000281 self.nospace = self.hard_break = not flag
282 self.para_end = self.parskip = self.have_label = 0
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000283
284
285class NullWriter:
Fred Drake28231681996-12-31 20:50:51 +0000286 """Minimal writer interface to use in testing & inheritance."""
Guido van Rossum3672aa21996-05-29 00:02:30 +0000287 def __init__(self): pass
Fred Drake28231681996-12-31 20:50:51 +0000288 def flush(self): pass
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000289 def new_alignment(self, align): pass
290 def new_font(self, font): pass
291 def new_margin(self, margin, level): pass
292 def new_spacing(self, spacing): pass
293 def new_styles(self, styles): pass
294 def send_paragraph(self, blankline): pass
295 def send_line_break(self): pass
296 def send_hor_rule(self, *args, **kw): pass
297 def send_label_data(self, data): pass
298 def send_flowing_data(self, data): pass
299 def send_literal_data(self, data): pass
300
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000301
Guido van Rossum3672aa21996-05-29 00:02:30 +0000302class AbstractWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000303
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000304 def new_alignment(self, align):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000305 print "new_alignment(%s)" % `align`
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000306
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000307 def new_font(self, font):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000308 print "new_font(%s)" % `font`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000309
310 def new_margin(self, margin, level):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000311 print "new_margin(%s, %d)" % (`margin`, level)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000312
313 def new_spacing(self, spacing):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000314 print "new_spacing(%s)" % `spacing`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000315
316 def new_styles(self, styles):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000317 print "new_styles(%s)" % `styles`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000318
319 def send_paragraph(self, blankline):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000320 print "send_paragraph(%s)" % `blankline`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000321
322 def send_line_break(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000323 print "send_line_break()"
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000324
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000325 def send_hor_rule(self, *args, **kw):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000326 print "send_hor_rule()"
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000327
328 def send_label_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000329 print "send_label_data(%s)" % `data`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000330
331 def send_flowing_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000332 print "send_flowing_data(%s)" % `data`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000333
334 def send_literal_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000335 print "send_literal_data(%s)" % `data`
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000336
337
Guido van Rossum3672aa21996-05-29 00:02:30 +0000338class DumbWriter(NullWriter):
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000339
340 def __init__(self, file=None, maxcol=72):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000341 self.file = file or sys.stdout
342 self.maxcol = maxcol
343 NullWriter.__init__(self)
344 self.reset()
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000345
346 def reset(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000347 self.col = 0
348 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000349
350 def send_paragraph(self, blankline):
Fred Drakef4bb6561999-01-12 18:13:27 +0000351 self.file.write('\n'*blankline)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000352 self.col = 0
353 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000354
355 def send_line_break(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000356 self.file.write('\n')
357 self.col = 0
358 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000359
Guido van Rossum9787bdaf1996-05-28 23:50:49 +0000360 def send_hor_rule(self, *args, **kw):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000361 self.file.write('\n')
362 self.file.write('-'*self.maxcol)
363 self.file.write('\n')
364 self.col = 0
365 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000366
367 def send_literal_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000368 self.file.write(data)
Eric S. Raymondb08b2d32001-02-09 11:10:16 +0000369 i = data.rfind('\n')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000370 if i >= 0:
371 self.col = 0
372 data = data[i+1:]
Eric S. Raymondb08b2d32001-02-09 11:10:16 +0000373 data = data.expandtabs()
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000374 self.col = self.col + len(data)
375 self.atbreak = 0
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000376
377 def send_flowing_data(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000378 if not data: return
379 atbreak = self.atbreak or data[0] in string.whitespace
380 col = self.col
381 maxcol = self.maxcol
382 write = self.file.write
Eric S. Raymondb08b2d32001-02-09 11:10:16 +0000383 for word in data.split():
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000384 if atbreak:
385 if col + len(word) >= maxcol:
386 write('\n')
387 col = 0
388 else:
389 write(' ')
390 col = col + 1
391 write(word)
392 col = col + len(word)
393 atbreak = 1
394 self.col = col
395 self.atbreak = data[-1] in string.whitespace
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000396
397
398def test(file = None):
399 w = DumbWriter()
400 f = AbstractFormatter(w)
401 if file:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000402 fp = open(file)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000403 elif sys.argv[1:]:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000404 fp = open(sys.argv[1])
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000405 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000406 fp = sys.stdin
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000407 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000408 line = fp.readline()
409 if not line:
410 break
411 if line == '\n':
412 f.end_paragraph(1)
413 else:
414 f.add_flowing_data(line)
Guido van Rossuma0eab1d1995-08-07 20:07:36 +0000415 f.end_paragraph(0)
416
417
418if __name__ == '__main__':
419 test()