Show More
Commit Description:
merge with algo and add brython files that were missing
Commit Description:
merge with algo and add brython files that were missing
References:
File last commit:
Show/Diff file:
Action:
lib/assets/Lib/formatter.py
| 445 lines
| 15.0 KiB
| text/x-python
| PythonLexer
|
r584 | """Generic output formatting. | |||
Formatter objects transform an abstract flow of formatting events into | ||||
specific output events on writer objects. Formatters manage several stack | ||||
structures to allow various properties of a writer object to be changed and | ||||
restored; writers need not be able to handle relative changes nor any sort | ||||
of ``change back'' operation. Specific writer properties which may be | ||||
controlled via formatter objects are horizontal alignment, font, and left | ||||
margin indentations. A mechanism is provided which supports providing | ||||
arbitrary, non-exclusive style settings to a writer as well. Additional | ||||
interfaces facilitate formatting events which are not reversible, such as | ||||
paragraph separation. | ||||
Writer objects encapsulate device interfaces. Abstract devices, such as | ||||
file formats, are supported as well as physical devices. The provided | ||||
implementations all work with abstract devices. The interface makes | ||||
available mechanisms for setting the properties which formatter objects | ||||
manage and inserting data into the output. | ||||
""" | ||||
import sys | ||||
AS_IS = None | ||||
class NullFormatter: | ||||
"""A formatter which does nothing. | ||||
If the writer parameter is omitted, a NullWriter instance is created. | ||||
No methods of the writer are called by NullFormatter instances. | ||||
Implementations should inherit from this class if implementing a writer | ||||
interface but don't need to inherit any implementation. | ||||
""" | ||||
def __init__(self, writer=None): | ||||
if writer is None: | ||||
writer = NullWriter() | ||||
self.writer = writer | ||||
def end_paragraph(self, blankline): pass | ||||
def add_line_break(self): pass | ||||
def add_hor_rule(self, *args, **kw): pass | ||||
def add_label_data(self, format, counter, blankline=None): pass | ||||
def add_flowing_data(self, data): pass | ||||
def add_literal_data(self, data): pass | ||||
def flush_softspace(self): pass | ||||
def push_alignment(self, align): pass | ||||
def pop_alignment(self): pass | ||||
def push_font(self, x): pass | ||||
def pop_font(self): pass | ||||
def push_margin(self, margin): pass | ||||
def pop_margin(self): pass | ||||
def set_spacing(self, spacing): pass | ||||
def push_style(self, *styles): pass | ||||
def pop_style(self, n=1): pass | ||||
def assert_line_data(self, flag=1): pass | ||||
class AbstractFormatter: | ||||
"""The standard formatter. | ||||
This implementation has demonstrated wide applicability to many writers, | ||||
and may be used directly in most circumstances. It has been used to | ||||
implement a full-featured World Wide Web browser. | ||||
""" | ||||
# Space handling policy: blank spaces at the boundary between elements | ||||
# are handled by the outermost context. "Literal" data is not checked | ||||
# to determine context, so spaces in literal data are handled directly | ||||
# in all circumstances. | ||||
def __init__(self, writer): | ||||
self.writer = writer # Output device | ||||
self.align = None # Current alignment | ||||
self.align_stack = [] # Alignment stack | ||||
self.font_stack = [] # Font state | ||||
self.margin_stack = [] # Margin state | ||||
self.spacing = None # Vertical spacing state | ||||
self.style_stack = [] # Other state, e.g. color | ||||
self.nospace = 1 # Should leading space be suppressed | ||||
self.softspace = 0 # Should a space be inserted | ||||
self.para_end = 1 # Just ended a paragraph | ||||
self.parskip = 0 # Skipped space between paragraphs? | ||||
self.hard_break = 1 # Have a hard break | ||||
self.have_label = 0 | ||||
def end_paragraph(self, blankline): | ||||
if not self.hard_break: | ||||
self.writer.send_line_break() | ||||
self.have_label = 0 | ||||
if self.parskip < blankline and not self.have_label: | ||||
self.writer.send_paragraph(blankline - self.parskip) | ||||
self.parskip = blankline | ||||
self.have_label = 0 | ||||
self.hard_break = self.nospace = self.para_end = 1 | ||||
self.softspace = 0 | ||||
def add_line_break(self): | ||||
if not (self.hard_break or self.para_end): | ||||
self.writer.send_line_break() | ||||
self.have_label = self.parskip = 0 | ||||
self.hard_break = self.nospace = 1 | ||||
self.softspace = 0 | ||||
def add_hor_rule(self, *args, **kw): | ||||
if not self.hard_break: | ||||
self.writer.send_line_break() | ||||
self.writer.send_hor_rule(*args, **kw) | ||||
self.hard_break = self.nospace = 1 | ||||
self.have_label = self.para_end = self.softspace = self.parskip = 0 | ||||
def add_label_data(self, format, counter, blankline = None): | ||||
if self.have_label or not self.hard_break: | ||||
self.writer.send_line_break() | ||||
if not self.para_end: | ||||
self.writer.send_paragraph((blankline and 1) or 0) | ||||
if isinstance(format, str): | ||||
self.writer.send_label_data(self.format_counter(format, counter)) | ||||
else: | ||||
self.writer.send_label_data(format) | ||||
self.nospace = self.have_label = self.hard_break = self.para_end = 1 | ||||
self.softspace = self.parskip = 0 | ||||
def format_counter(self, format, counter): | ||||
label = '' | ||||
for c in format: | ||||
if c == '1': | ||||
label = label + ('%d' % counter) | ||||
elif c in 'aA': | ||||
if counter > 0: | ||||
label = label + self.format_letter(c, counter) | ||||
elif c in 'iI': | ||||
if counter > 0: | ||||
label = label + self.format_roman(c, counter) | ||||
else: | ||||
label = label + c | ||||
return label | ||||
def format_letter(self, case, counter): | ||||
label = '' | ||||
while counter > 0: | ||||
counter, x = divmod(counter-1, 26) | ||||
# This makes a strong assumption that lowercase letters | ||||
# and uppercase letters form two contiguous blocks, with | ||||
# letters in order! | ||||
s = chr(ord(case) + x) | ||||
label = s + label | ||||
return label | ||||
def format_roman(self, case, counter): | ||||
ones = ['i', 'x', 'c', 'm'] | ||||
fives = ['v', 'l', 'd'] | ||||
label, index = '', 0 | ||||
# This will die of IndexError when counter is too big | ||||
while counter > 0: | ||||
counter, x = divmod(counter, 10) | ||||
if x == 9: | ||||
label = ones[index] + ones[index+1] + label | ||||
elif x == 4: | ||||
label = ones[index] + fives[index] + label | ||||
else: | ||||
if x >= 5: | ||||
s = fives[index] | ||||
x = x-5 | ||||
else: | ||||
s = '' | ||||
s = s + ones[index]*x | ||||
label = s + label | ||||
index = index + 1 | ||||
if case == 'I': | ||||
return label.upper() | ||||
return label | ||||
def add_flowing_data(self, data): | ||||
if not data: return | ||||
prespace = data[:1].isspace() | ||||
postspace = data[-1:].isspace() | ||||
data = " ".join(data.split()) | ||||
if self.nospace and not data: | ||||
return | ||||
elif prespace or self.softspace: | ||||
if not data: | ||||
if not self.nospace: | ||||
self.softspace = 1 | ||||
self.parskip = 0 | ||||
return | ||||
if not self.nospace: | ||||
data = ' ' + data | ||||
self.hard_break = self.nospace = self.para_end = \ | ||||
self.parskip = self.have_label = 0 | ||||
self.softspace = postspace | ||||
self.writer.send_flowing_data(data) | ||||
def add_literal_data(self, data): | ||||
if not data: return | ||||
if self.softspace: | ||||
self.writer.send_flowing_data(" ") | ||||
self.hard_break = data[-1:] == '\n' | ||||
self.nospace = self.para_end = self.softspace = \ | ||||
self.parskip = self.have_label = 0 | ||||
self.writer.send_literal_data(data) | ||||
def flush_softspace(self): | ||||
if self.softspace: | ||||
self.hard_break = self.para_end = self.parskip = \ | ||||
self.have_label = self.softspace = 0 | ||||
self.nospace = 1 | ||||
self.writer.send_flowing_data(' ') | ||||
def push_alignment(self, align): | ||||
if align and align != self.align: | ||||
self.writer.new_alignment(align) | ||||
self.align = align | ||||
self.align_stack.append(align) | ||||
else: | ||||
self.align_stack.append(self.align) | ||||
def pop_alignment(self): | ||||
if self.align_stack: | ||||
del self.align_stack[-1] | ||||
if self.align_stack: | ||||
self.align = align = self.align_stack[-1] | ||||
self.writer.new_alignment(align) | ||||
else: | ||||
self.align = None | ||||
self.writer.new_alignment(None) | ||||
def push_font(self, font): | ||||
size, i, b, tt = font | ||||
if self.softspace: | ||||
self.hard_break = self.para_end = self.softspace = 0 | ||||
self.nospace = 1 | ||||
self.writer.send_flowing_data(' ') | ||||
if self.font_stack: | ||||
csize, ci, cb, ctt = self.font_stack[-1] | ||||
if size is AS_IS: size = csize | ||||
if i is AS_IS: i = ci | ||||
if b is AS_IS: b = cb | ||||
if tt is AS_IS: tt = ctt | ||||
font = (size, i, b, tt) | ||||
self.font_stack.append(font) | ||||
self.writer.new_font(font) | ||||
def pop_font(self): | ||||
if self.font_stack: | ||||
del self.font_stack[-1] | ||||
if self.font_stack: | ||||
font = self.font_stack[-1] | ||||
else: | ||||
font = None | ||||
self.writer.new_font(font) | ||||
def push_margin(self, margin): | ||||
self.margin_stack.append(margin) | ||||
fstack = [m for m in self.margin_stack if m] | ||||
if not margin and fstack: | ||||
margin = fstack[-1] | ||||
self.writer.new_margin(margin, len(fstack)) | ||||
def pop_margin(self): | ||||
if self.margin_stack: | ||||
del self.margin_stack[-1] | ||||
fstack = [m for m in self.margin_stack if m] | ||||
if fstack: | ||||
margin = fstack[-1] | ||||
else: | ||||
margin = None | ||||
self.writer.new_margin(margin, len(fstack)) | ||||
def set_spacing(self, spacing): | ||||
self.spacing = spacing | ||||
self.writer.new_spacing(spacing) | ||||
def push_style(self, *styles): | ||||
if self.softspace: | ||||
self.hard_break = self.para_end = self.softspace = 0 | ||||
self.nospace = 1 | ||||
self.writer.send_flowing_data(' ') | ||||
for style in styles: | ||||
self.style_stack.append(style) | ||||
self.writer.new_styles(tuple(self.style_stack)) | ||||
def pop_style(self, n=1): | ||||
del self.style_stack[-n:] | ||||
self.writer.new_styles(tuple(self.style_stack)) | ||||
def assert_line_data(self, flag=1): | ||||
self.nospace = self.hard_break = not flag | ||||
self.para_end = self.parskip = self.have_label = 0 | ||||
class NullWriter: | ||||
"""Minimal writer interface to use in testing & inheritance. | ||||
A writer which only provides the interface definition; no actions are | ||||
taken on any methods. This should be the base class for all writers | ||||
which do not need to inherit any implementation methods. | ||||
""" | ||||
def __init__(self): pass | ||||
def flush(self): pass | ||||
def new_alignment(self, align): pass | ||||
def new_font(self, font): pass | ||||
def new_margin(self, margin, level): pass | ||||
def new_spacing(self, spacing): pass | ||||
def new_styles(self, styles): pass | ||||
def send_paragraph(self, blankline): pass | ||||
def send_line_break(self): pass | ||||
def send_hor_rule(self, *args, **kw): pass | ||||
def send_label_data(self, data): pass | ||||
def send_flowing_data(self, data): pass | ||||
def send_literal_data(self, data): pass | ||||
class AbstractWriter(NullWriter): | ||||
"""A writer which can be used in debugging formatters, but not much else. | ||||
Each method simply announces itself by printing its name and | ||||
arguments on standard output. | ||||
""" | ||||
def new_alignment(self, align): | ||||
print("new_alignment(%r)" % (align,)) | ||||
def new_font(self, font): | ||||
print("new_font(%r)" % (font,)) | ||||
def new_margin(self, margin, level): | ||||
print("new_margin(%r, %d)" % (margin, level)) | ||||
def new_spacing(self, spacing): | ||||
print("new_spacing(%r)" % (spacing,)) | ||||
def new_styles(self, styles): | ||||
print("new_styles(%r)" % (styles,)) | ||||
def send_paragraph(self, blankline): | ||||
print("send_paragraph(%r)" % (blankline,)) | ||||
def send_line_break(self): | ||||
print("send_line_break()") | ||||
def send_hor_rule(self, *args, **kw): | ||||
print("send_hor_rule()") | ||||
def send_label_data(self, data): | ||||
print("send_label_data(%r)" % (data,)) | ||||
def send_flowing_data(self, data): | ||||
print("send_flowing_data(%r)" % (data,)) | ||||
def send_literal_data(self, data): | ||||
print("send_literal_data(%r)" % (data,)) | ||||
class DumbWriter(NullWriter): | ||||
"""Simple writer class which writes output on the file object passed in | ||||
as the file parameter or, if file is omitted, on standard output. The | ||||
output is simply word-wrapped to the number of columns specified by | ||||
the maxcol parameter. This class is suitable for reflowing a sequence | ||||
of paragraphs. | ||||
""" | ||||
def __init__(self, file=None, maxcol=72): | ||||
self.file = file or sys.stdout | ||||
self.maxcol = maxcol | ||||
NullWriter.__init__(self) | ||||
self.reset() | ||||
def reset(self): | ||||
self.col = 0 | ||||
self.atbreak = 0 | ||||
def send_paragraph(self, blankline): | ||||
self.file.write('\n'*blankline) | ||||
self.col = 0 | ||||
self.atbreak = 0 | ||||
def send_line_break(self): | ||||
self.file.write('\n') | ||||
self.col = 0 | ||||
self.atbreak = 0 | ||||
def send_hor_rule(self, *args, **kw): | ||||
self.file.write('\n') | ||||
self.file.write('-'*self.maxcol) | ||||
self.file.write('\n') | ||||
self.col = 0 | ||||
self.atbreak = 0 | ||||
def send_literal_data(self, data): | ||||
self.file.write(data) | ||||
i = data.rfind('\n') | ||||
if i >= 0: | ||||
self.col = 0 | ||||
data = data[i+1:] | ||||
data = data.expandtabs() | ||||
self.col = self.col + len(data) | ||||
self.atbreak = 0 | ||||
def send_flowing_data(self, data): | ||||
if not data: return | ||||
atbreak = self.atbreak or data[0].isspace() | ||||
col = self.col | ||||
maxcol = self.maxcol | ||||
write = self.file.write | ||||
for word in data.split(): | ||||
if atbreak: | ||||
if col + len(word) >= maxcol: | ||||
write('\n') | ||||
col = 0 | ||||
else: | ||||
write(' ') | ||||
col = col + 1 | ||||
write(word) | ||||
col = col + len(word) | ||||
atbreak = 1 | ||||
self.col = col | ||||
self.atbreak = data[-1].isspace() | ||||
def test(file = None): | ||||
w = DumbWriter() | ||||
f = AbstractFormatter(w) | ||||
if file is not None: | ||||
fp = open(file) | ||||
elif sys.argv[1:]: | ||||
fp = open(sys.argv[1]) | ||||
else: | ||||
fp = sys.stdin | ||||
for line in fp: | ||||
if line == '\n': | ||||
f.end_paragraph(1) | ||||
else: | ||||
f.add_flowing_data(line) | ||||
f.end_paragraph(0) | ||||
if __name__ == '__main__': | ||||
test() | ||||