ยปCore Development>Code coverage>Lib/formatter.py

Python code coverage for Lib/formatter.py

#countcontent
1n/a"""Generic output formatting.
2n/a
3n/aFormatter objects transform an abstract flow of formatting events into
4n/aspecific output events on writer objects. Formatters manage several stack
5n/astructures to allow various properties of a writer object to be changed and
6n/arestored; writers need not be able to handle relative changes nor any sort
7n/aof ``change back'' operation. Specific writer properties which may be
8n/acontrolled via formatter objects are horizontal alignment, font, and left
9n/amargin indentations. A mechanism is provided which supports providing
10n/aarbitrary, non-exclusive style settings to a writer as well. Additional
11n/ainterfaces facilitate formatting events which are not reversible, such as
12n/aparagraph separation.
13n/a
14n/aWriter objects encapsulate device interfaces. Abstract devices, such as
15n/afile formats, are supported as well as physical devices. The provided
16n/aimplementations all work with abstract devices. The interface makes
17n/aavailable mechanisms for setting the properties which formatter objects
18n/amanage and inserting data into the output.
19n/a"""
20n/a
21n/aimport sys
22n/aimport warnings
23n/awarnings.warn('the formatter module is deprecated', DeprecationWarning,
24n/a stacklevel=2)
25n/a
26n/a
27n/aAS_IS = None
28n/a
29n/a
30n/aclass NullFormatter:
31n/a """A formatter which does nothing.
32n/a
33n/a If the writer parameter is omitted, a NullWriter instance is created.
34n/a No methods of the writer are called by NullFormatter instances.
35n/a
36n/a Implementations should inherit from this class if implementing a writer
37n/a interface but don't need to inherit any implementation.
38n/a
39n/a """
40n/a
41n/a def __init__(self, writer=None):
42n/a if writer is None:
43n/a writer = NullWriter()
44n/a self.writer = writer
45n/a def end_paragraph(self, blankline): pass
46n/a def add_line_break(self): pass
47n/a def add_hor_rule(self, *args, **kw): pass
48n/a def add_label_data(self, format, counter, blankline=None): pass
49n/a def add_flowing_data(self, data): pass
50n/a def add_literal_data(self, data): pass
51n/a def flush_softspace(self): pass
52n/a def push_alignment(self, align): pass
53n/a def pop_alignment(self): pass
54n/a def push_font(self, x): pass
55n/a def pop_font(self): pass
56n/a def push_margin(self, margin): pass
57n/a def pop_margin(self): pass
58n/a def set_spacing(self, spacing): pass
59n/a def push_style(self, *styles): pass
60n/a def pop_style(self, n=1): pass
61n/a def assert_line_data(self, flag=1): pass
62n/a
63n/a
64n/aclass AbstractFormatter:
65n/a """The standard formatter.
66n/a
67n/a This implementation has demonstrated wide applicability to many writers,
68n/a and may be used directly in most circumstances. It has been used to
69n/a implement a full-featured World Wide Web browser.
70n/a
71n/a """
72n/a
73n/a # Space handling policy: blank spaces at the boundary between elements
74n/a # are handled by the outermost context. "Literal" data is not checked
75n/a # to determine context, so spaces in literal data are handled directly
76n/a # in all circumstances.
77n/a
78n/a def __init__(self, writer):
79n/a self.writer = writer # Output device
80n/a self.align = None # Current alignment
81n/a self.align_stack = [] # Alignment stack
82n/a self.font_stack = [] # Font state
83n/a self.margin_stack = [] # Margin state
84n/a self.spacing = None # Vertical spacing state
85n/a self.style_stack = [] # Other state, e.g. color
86n/a self.nospace = 1 # Should leading space be suppressed
87n/a self.softspace = 0 # Should a space be inserted
88n/a self.para_end = 1 # Just ended a paragraph
89n/a self.parskip = 0 # Skipped space between paragraphs?
90n/a self.hard_break = 1 # Have a hard break
91n/a self.have_label = 0
92n/a
93n/a def end_paragraph(self, blankline):
94n/a if not self.hard_break:
95n/a self.writer.send_line_break()
96n/a self.have_label = 0
97n/a if self.parskip < blankline and not self.have_label:
98n/a self.writer.send_paragraph(blankline - self.parskip)
99n/a self.parskip = blankline
100n/a self.have_label = 0
101n/a self.hard_break = self.nospace = self.para_end = 1
102n/a self.softspace = 0
103n/a
104n/a def add_line_break(self):
105n/a if not (self.hard_break or self.para_end):
106n/a self.writer.send_line_break()
107n/a self.have_label = self.parskip = 0
108n/a self.hard_break = self.nospace = 1
109n/a self.softspace = 0
110n/a
111n/a def add_hor_rule(self, *args, **kw):
112n/a if not self.hard_break:
113n/a self.writer.send_line_break()
114n/a self.writer.send_hor_rule(*args, **kw)
115n/a self.hard_break = self.nospace = 1
116n/a self.have_label = self.para_end = self.softspace = self.parskip = 0
117n/a
118n/a def add_label_data(self, format, counter, blankline = None):
119n/a if self.have_label or not self.hard_break:
120n/a self.writer.send_line_break()
121n/a if not self.para_end:
122n/a self.writer.send_paragraph((blankline and 1) or 0)
123n/a if isinstance(format, str):
124n/a self.writer.send_label_data(self.format_counter(format, counter))
125n/a else:
126n/a self.writer.send_label_data(format)
127n/a self.nospace = self.have_label = self.hard_break = self.para_end = 1
128n/a self.softspace = self.parskip = 0
129n/a
130n/a def format_counter(self, format, counter):
131n/a label = ''
132n/a for c in format:
133n/a if c == '1':
134n/a label = label + ('%d' % counter)
135n/a elif c in 'aA':
136n/a if counter > 0:
137n/a label = label + self.format_letter(c, counter)
138n/a elif c in 'iI':
139n/a if counter > 0:
140n/a label = label + self.format_roman(c, counter)
141n/a else:
142n/a label = label + c
143n/a return label
144n/a
145n/a def format_letter(self, case, counter):
146n/a label = ''
147n/a while counter > 0:
148n/a counter, x = divmod(counter-1, 26)
149n/a # This makes a strong assumption that lowercase letters
150n/a # and uppercase letters form two contiguous blocks, with
151n/a # letters in order!
152n/a s = chr(ord(case) + x)
153n/a label = s + label
154n/a return label
155n/a
156n/a def format_roman(self, case, counter):
157n/a ones = ['i', 'x', 'c', 'm']
158n/a fives = ['v', 'l', 'd']
159n/a label, index = '', 0
160n/a # This will die of IndexError when counter is too big
161n/a while counter > 0:
162n/a counter, x = divmod(counter, 10)
163n/a if x == 9:
164n/a label = ones[index] + ones[index+1] + label
165n/a elif x == 4:
166n/a label = ones[index] + fives[index] + label
167n/a else:
168n/a if x >= 5:
169n/a s = fives[index]
170n/a x = x-5
171n/a else:
172n/a s = ''
173n/a s = s + ones[index]*x
174n/a label = s + label
175n/a index = index + 1
176n/a if case == 'I':
177n/a return label.upper()
178n/a return label
179n/a
180n/a def add_flowing_data(self, data):
181n/a if not data: return
182n/a prespace = data[:1].isspace()
183n/a postspace = data[-1:].isspace()
184n/a data = " ".join(data.split())
185n/a if self.nospace and not data:
186n/a return
187n/a elif prespace or self.softspace:
188n/a if not data:
189n/a if not self.nospace:
190n/a self.softspace = 1
191n/a self.parskip = 0
192n/a return
193n/a if not self.nospace:
194n/a data = ' ' + data
195n/a self.hard_break = self.nospace = self.para_end = \
196n/a self.parskip = self.have_label = 0
197n/a self.softspace = postspace
198n/a self.writer.send_flowing_data(data)
199n/a
200n/a def add_literal_data(self, data):
201n/a if not data: return
202n/a if self.softspace:
203n/a self.writer.send_flowing_data(" ")
204n/a self.hard_break = data[-1:] == '\n'
205n/a self.nospace = self.para_end = self.softspace = \
206n/a self.parskip = self.have_label = 0
207n/a self.writer.send_literal_data(data)
208n/a
209n/a def flush_softspace(self):
210n/a if self.softspace:
211n/a self.hard_break = self.para_end = self.parskip = \
212n/a self.have_label = self.softspace = 0
213n/a self.nospace = 1
214n/a self.writer.send_flowing_data(' ')
215n/a
216n/a def push_alignment(self, align):
217n/a if align and align != self.align:
218n/a self.writer.new_alignment(align)
219n/a self.align = align
220n/a self.align_stack.append(align)
221n/a else:
222n/a self.align_stack.append(self.align)
223n/a
224n/a def pop_alignment(self):
225n/a if self.align_stack:
226n/a del self.align_stack[-1]
227n/a if self.align_stack:
228n/a self.align = align = self.align_stack[-1]
229n/a self.writer.new_alignment(align)
230n/a else:
231n/a self.align = None
232n/a self.writer.new_alignment(None)
233n/a
234n/a def push_font(self, font):
235n/a size, i, b, tt = font
236n/a if self.softspace:
237n/a self.hard_break = self.para_end = self.softspace = 0
238n/a self.nospace = 1
239n/a self.writer.send_flowing_data(' ')
240n/a if self.font_stack:
241n/a csize, ci, cb, ctt = self.font_stack[-1]
242n/a if size is AS_IS: size = csize
243n/a if i is AS_IS: i = ci
244n/a if b is AS_IS: b = cb
245n/a if tt is AS_IS: tt = ctt
246n/a font = (size, i, b, tt)
247n/a self.font_stack.append(font)
248n/a self.writer.new_font(font)
249n/a
250n/a def pop_font(self):
251n/a if self.font_stack:
252n/a del self.font_stack[-1]
253n/a if self.font_stack:
254n/a font = self.font_stack[-1]
255n/a else:
256n/a font = None
257n/a self.writer.new_font(font)
258n/a
259n/a def push_margin(self, margin):
260n/a self.margin_stack.append(margin)
261n/a fstack = [m for m in self.margin_stack if m]
262n/a if not margin and fstack:
263n/a margin = fstack[-1]
264n/a self.writer.new_margin(margin, len(fstack))
265n/a
266n/a def pop_margin(self):
267n/a if self.margin_stack:
268n/a del self.margin_stack[-1]
269n/a fstack = [m for m in self.margin_stack if m]
270n/a if fstack:
271n/a margin = fstack[-1]
272n/a else:
273n/a margin = None
274n/a self.writer.new_margin(margin, len(fstack))
275n/a
276n/a def set_spacing(self, spacing):
277n/a self.spacing = spacing
278n/a self.writer.new_spacing(spacing)
279n/a
280n/a def push_style(self, *styles):
281n/a if self.softspace:
282n/a self.hard_break = self.para_end = self.softspace = 0
283n/a self.nospace = 1
284n/a self.writer.send_flowing_data(' ')
285n/a for style in styles:
286n/a self.style_stack.append(style)
287n/a self.writer.new_styles(tuple(self.style_stack))
288n/a
289n/a def pop_style(self, n=1):
290n/a del self.style_stack[-n:]
291n/a self.writer.new_styles(tuple(self.style_stack))
292n/a
293n/a def assert_line_data(self, flag=1):
294n/a self.nospace = self.hard_break = not flag
295n/a self.para_end = self.parskip = self.have_label = 0
296n/a
297n/a
298n/aclass NullWriter:
299n/a """Minimal writer interface to use in testing & inheritance.
300n/a
301n/a A writer which only provides the interface definition; no actions are
302n/a taken on any methods. This should be the base class for all writers
303n/a which do not need to inherit any implementation methods.
304n/a
305n/a """
306n/a def __init__(self): pass
307n/a def flush(self): pass
308n/a def new_alignment(self, align): pass
309n/a def new_font(self, font): pass
310n/a def new_margin(self, margin, level): pass
311n/a def new_spacing(self, spacing): pass
312n/a def new_styles(self, styles): pass
313n/a def send_paragraph(self, blankline): pass
314n/a def send_line_break(self): pass
315n/a def send_hor_rule(self, *args, **kw): pass
316n/a def send_label_data(self, data): pass
317n/a def send_flowing_data(self, data): pass
318n/a def send_literal_data(self, data): pass
319n/a
320n/a
321n/aclass AbstractWriter(NullWriter):
322n/a """A writer which can be used in debugging formatters, but not much else.
323n/a
324n/a Each method simply announces itself by printing its name and
325n/a arguments on standard output.
326n/a
327n/a """
328n/a
329n/a def new_alignment(self, align):
330n/a print("new_alignment(%r)" % (align,))
331n/a
332n/a def new_font(self, font):
333n/a print("new_font(%r)" % (font,))
334n/a
335n/a def new_margin(self, margin, level):
336n/a print("new_margin(%r, %d)" % (margin, level))
337n/a
338n/a def new_spacing(self, spacing):
339n/a print("new_spacing(%r)" % (spacing,))
340n/a
341n/a def new_styles(self, styles):
342n/a print("new_styles(%r)" % (styles,))
343n/a
344n/a def send_paragraph(self, blankline):
345n/a print("send_paragraph(%r)" % (blankline,))
346n/a
347n/a def send_line_break(self):
348n/a print("send_line_break()")
349n/a
350n/a def send_hor_rule(self, *args, **kw):
351n/a print("send_hor_rule()")
352n/a
353n/a def send_label_data(self, data):
354n/a print("send_label_data(%r)" % (data,))
355n/a
356n/a def send_flowing_data(self, data):
357n/a print("send_flowing_data(%r)" % (data,))
358n/a
359n/a def send_literal_data(self, data):
360n/a print("send_literal_data(%r)" % (data,))
361n/a
362n/a
363n/aclass DumbWriter(NullWriter):
364n/a """Simple writer class which writes output on the file object passed in
365n/a as the file parameter or, if file is omitted, on standard output. The
366n/a output is simply word-wrapped to the number of columns specified by
367n/a the maxcol parameter. This class is suitable for reflowing a sequence
368n/a of paragraphs.
369n/a
370n/a """
371n/a
372n/a def __init__(self, file=None, maxcol=72):
373n/a self.file = file or sys.stdout
374n/a self.maxcol = maxcol
375n/a NullWriter.__init__(self)
376n/a self.reset()
377n/a
378n/a def reset(self):
379n/a self.col = 0
380n/a self.atbreak = 0
381n/a
382n/a def send_paragraph(self, blankline):
383n/a self.file.write('\n'*blankline)
384n/a self.col = 0
385n/a self.atbreak = 0
386n/a
387n/a def send_line_break(self):
388n/a self.file.write('\n')
389n/a self.col = 0
390n/a self.atbreak = 0
391n/a
392n/a def send_hor_rule(self, *args, **kw):
393n/a self.file.write('\n')
394n/a self.file.write('-'*self.maxcol)
395n/a self.file.write('\n')
396n/a self.col = 0
397n/a self.atbreak = 0
398n/a
399n/a def send_literal_data(self, data):
400n/a self.file.write(data)
401n/a i = data.rfind('\n')
402n/a if i >= 0:
403n/a self.col = 0
404n/a data = data[i+1:]
405n/a data = data.expandtabs()
406n/a self.col = self.col + len(data)
407n/a self.atbreak = 0
408n/a
409n/a def send_flowing_data(self, data):
410n/a if not data: return
411n/a atbreak = self.atbreak or data[0].isspace()
412n/a col = self.col
413n/a maxcol = self.maxcol
414n/a write = self.file.write
415n/a for word in data.split():
416n/a if atbreak:
417n/a if col + len(word) >= maxcol:
418n/a write('\n')
419n/a col = 0
420n/a else:
421n/a write(' ')
422n/a col = col + 1
423n/a write(word)
424n/a col = col + len(word)
425n/a atbreak = 1
426n/a self.col = col
427n/a self.atbreak = data[-1].isspace()
428n/a
429n/a
430n/adef test(file = None):
431n/a w = DumbWriter()
432n/a f = AbstractFormatter(w)
433n/a if file is not None:
434n/a fp = open(file)
435n/a elif sys.argv[1:]:
436n/a fp = open(sys.argv[1])
437n/a else:
438n/a fp = sys.stdin
439n/a try:
440n/a for line in fp:
441n/a if line == '\n':
442n/a f.end_paragraph(1)
443n/a else:
444n/a f.add_flowing_data(line)
445n/a finally:
446n/a if fp is not sys.stdin:
447n/a fp.close()
448n/a f.end_paragraph(0)
449n/a
450n/a
451n/aif __name__ == '__main__':
452n/a test()