Vega strike Python Modules doc  0.5.1
Documentation of the " Modules " folder of Vega strike
 All Data Structures Namespaces Files Functions Variables
formatter.py
Go to the documentation of this file.
1 """Generic output formatting.
2 
3 Formatter objects transform an abstract flow of formatting events into
4 specific output events on writer objects. Formatters manage several stack
5 structures to allow various properties of a writer object to be changed and
6 restored; writers need not be able to handle relative changes nor any sort
7 of ``change back'' operation. Specific writer properties which may be
8 controlled via formatter objects are horizontal alignment, font, and left
9 margin indentations. A mechanism is provided which supports providing
10 arbitrary, non-exclusive style settings to a writer as well. Additional
11 interfaces facilitate formatting events which are not reversible, such as
12 paragraph separation.
13 
14 Writer objects encapsulate device interfaces. Abstract devices, such as
15 file formats, are supported as well as physical devices. The provided
16 implementations all work with abstract devices. The interface makes
17 available mechanisms for setting the properties which formatter objects
18 manage and inserting data into the output.
19 """
20 
21 import string
22 import sys
23 from types import StringType
24 
25 
26 AS_IS = None
27 
28 
30 
31  def __init__(self, writer=None):
32  if not writer:
33  writer = NullWriter()
34  self.writer = writer
35  def end_paragraph(self, blankline): pass
36  def add_line_break(self): pass
37  def add_hor_rule(self, *args, **kw): pass
38  def add_label_data(self, format, counter, blankline=None): pass
39  def add_flowing_data(self, data): pass
40  def add_literal_data(self, data): pass
41  def flush_softspace(self): pass
42  def push_alignment(self, align): pass
43  def pop_alignment(self): pass
44  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
49  def push_style(self, *styles): pass
50  def pop_style(self, n=1): pass
51  def assert_line_data(self, flag=1): pass
52 
53 
55 
56  # 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 
61  def __init__(self, writer):
62  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
75 
76  def end_paragraph(self, blankline):
77  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
86 
87  def add_line_break(self):
88  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
93 
94  def add_hor_rule(self, *args, **kw):
95  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
100 
101  def add_label_data(self, format, counter, blankline = None):
102  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
112 
113  def format_counter(self, format, counter):
114  label = ''
115  for c in format:
116  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:
125  label = label + c
126  return label
127 
128  def format_letter(self, case, counter):
129  label = ''
130  while counter > 0:
131  counter, x = divmod(counter-1, 26)
132  # This makes a strong assumption that lowercase letters
133  # and uppercase letters form two contiguous blocks, with
134  # letters in order!
135  s = chr(ord(case) + x)
136  label = s + label
137  return label
138 
139  def format_roman(self, case, counter):
140  ones = ['i', 'x', 'c', 'm']
141  fives = ['v', 'l', 'd']
142  label, index = '', 0
143  # This will die of IndexError when counter is too big
144  while counter > 0:
145  counter, x = divmod(counter, 10)
146  if x == 9:
147  label = ones[index] + ones[index+1] + label
148  elif x == 4:
149  label = ones[index] + fives[index] + label
150  else:
151  if x >= 5:
152  s = fives[index]
153  x = x-5
154  else:
155  s = ''
156  s = s + ones[index]*x
157  label = s + label
158  index = index + 1
159  if case == 'I':
160  return label.upper()
161  return label
162 
163  def add_flowing_data(self, data,
164  # 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)
187 
188  def add_literal_data(self, data):
189  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)
196 
197  def flush_softspace(self):
198  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(' ')
203 
204  def push_alignment(self, align):
205  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)
211 
212  def pop_alignment(self):
213  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)
221 
222  def push_font(self, (size, i, b, tt)):
223  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)
236 
237  def pop_font(self):
238  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)
245 
246  def push_margin(self, margin):
247  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))
252 
253  def pop_margin(self):
254  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))
262 
263  def set_spacing(self, spacing):
264  self.spacing = spacing
265  self.writer.new_spacing(spacing)
266 
267  def push_style(self, *styles):
268  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))
275 
276  def pop_style(self, n=1):
277  del self.style_stack[-n:]
278  self.writer.new_styles(tuple(self.style_stack))
279 
280  def assert_line_data(self, flag=1):
281  self.nospace = self.hard_break = not flag
282  self.para_end = self.parskip = self.have_label = 0
283 
284 
286  """Minimal writer interface to use in testing & inheritance."""
287  def __init__(self): pass
288  def flush(self): pass
289  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 
301 
303 
304  def new_alignment(self, align):
305  print "new_alignment(%s)" % `align`
306 
307  def new_font(self, font):
308  print "new_font(%s)" % `font`
309 
310  def new_margin(self, margin, level):
311  print "new_margin(%s, %d)" % (`margin`, level)
312 
313  def new_spacing(self, spacing):
314  print "new_spacing(%s)" % `spacing`
315 
316  def new_styles(self, styles):
317  print "new_styles(%s)" % `styles`
318 
319  def send_paragraph(self, blankline):
320  print "send_paragraph(%s)" % `blankline`
321 
322  def send_line_break(self):
323  print "send_line_break()"
324 
325  def send_hor_rule(self, *args, **kw):
326  print "send_hor_rule()"
327 
328  def send_label_data(self, data):
329  print "send_label_data(%s)" % `data`
330 
331  def send_flowing_data(self, data):
332  print "send_flowing_data(%s)" % `data`
333 
334  def send_literal_data(self, data):
335  print "send_literal_data(%s)" % `data`
336 
337 
339 
340  def __init__(self, file=None, maxcol=72):
341  self.file = file or sys.stdout
342  self.maxcol = maxcol
343  NullWriter.__init__(self)
344  self.reset()
345 
346  def reset(self):
347  self.col = 0
348  self.atbreak = 0
349 
350  def send_paragraph(self, blankline):
351  self.file.write('\n'*blankline)
352  self.col = 0
353  self.atbreak = 0
354 
355  def send_line_break(self):
356  self.file.write('\n')
357  self.col = 0
358  self.atbreak = 0
359 
360  def send_hor_rule(self, *args, **kw):
361  self.file.write('\n')
362  self.file.write('-'*self.maxcol)
363  self.file.write('\n')
364  self.col = 0
365  self.atbreak = 0
366 
367  def send_literal_data(self, data):
368  self.file.write(data)
369  i = data.rfind('\n')
370  if i >= 0:
371  self.col = 0
372  data = data[i+1:]
373  data = data.expandtabs()
374  self.col = self.col + len(data)
375  self.atbreak = 0
376 
377  def send_flowing_data(self, data):
378  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
383  for word in data.split():
384  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
396 
397 
398 def test(file = None):
399  w = DumbWriter()
400  f = AbstractFormatter(w)
401  if file:
402  fp = open(file)
403  elif sys.argv[1:]:
404  fp = open(sys.argv[1])
405  else:
406  fp = sys.stdin
407  while 1:
408  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)
415  f.end_paragraph(0)
416 
417 
418 if __name__ == '__main__':
419  test()