~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# Copyright 2009 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""The MailWrapper class."""

__metaclass__ = type
__all__ = [
    'MailWrapper',
    ]


import textwrap


class MailWrapper:
    """Wraps text that should be included in an email.

        :width: how long should the lines be
        :indent: specifies how much indentation the lines should have
        :indent_first_line: indicates whether the first line should be
                            indented or not.

    Note that MailWrapper doesn't guarantee that all lines will be less
    than :width:, sometimes it's better not to break long lines in
    emails. See textformatting.txt for more information.
    """

    def __init__(self, width=72, indent='', indent_first_line=True):
        self.indent = indent
        self.indent_first_line = indent_first_line
        self._text_wrapper = textwrap.TextWrapper(
            width=width, subsequent_indent=indent,
            replace_whitespace=False, break_long_words=False)

    def format(self, text, force_wrap=False, wrap_func=None):
        """Format the text to be included in an email.

        :param force_wrap: When False (the default), only paragraphs
            containing a single line will be wrapped.  Otherwise paragraphs in
            text will be re-wrapped.
        :type force_wrap: bool
        :param wrap_func: A function to call at the beginning of each
            paragraph to be wrapped.  If the function returns False, the
            paragraph is not wrapped.
        :type wrap_func: callable or None
        """
        wrapped_lines = []

        if self.indent_first_line:
            indentation = self.indent
        else:
            indentation = ''

        # We don't care about trailing whitespace.
        text = text.rstrip()

        # Normalize dos-style line endings to unix-style.
        text = text.replace('\r\n', '\n')

        for paragraph in text.split('\n\n'):
            lines = paragraph.split('\n')

            if wrap_func is not None and not wrap_func(paragraph):
                # The user's callback function has indicated that the
                # paragraph should not be wrapped.
                wrapped_lines += (
                    [indentation + lines[0]] +
                    [self.indent + line for line in lines[1:]])
            elif len(lines) == 1:
                # We use TextWrapper only if the paragraph consists of a
                # single line, like in the case where a person enters a
                # comment via the web ui, without breaking the lines
                # manually.
                self._text_wrapper.initial_indent = indentation
                wrapped_lines += self._text_wrapper.wrap(paragraph)
            elif force_wrap:
                self._text_wrapper.initial_indent = indentation
                for line in lines:
                    wrapped_lines += self._text_wrapper.wrap(line)
            else:
                # If the user has gone through the trouble of wrapping
                # the lines, we shouldn't re-wrap them for him.
                wrapped_lines += (
                    [indentation + lines[0]] +
                    [self.indent + line for line in lines[1:]])

            if not self.indent_first_line:
                # 'indentation' was temporarily set to '' in order to
                # prevent the first line from being indented. Set it
                # back to self.indent so that the rest of the lines get
                # indented.
                indentation = self.indent

            # Add an empty line so that the paragraphs get separated by
            # a blank line when they are joined together again.
            wrapped_lines.append('')

        # We added one line too much, remove it.
        wrapped_lines = wrapped_lines[:-1]
        return '\n'.join(wrapped_lines)