|  | 
|  | 1 | +import email.message | 
|  | 2 | +import email.policy | 
| 1 | 3 | import re | 
| 2 | 4 | import textwrap | 
| 3 |  | -import email.message | 
| 4 | 5 | 
 | 
| 5 | 6 | from ._text import FoldedCase | 
| 6 | 7 | 
 | 
| 7 | 8 | 
 | 
|  | 9 | +class RawPolicy(email.policy.EmailPolicy): | 
|  | 10 | +    def fold(self, name, value): | 
|  | 11 | +        folded = self.linesep.join( | 
|  | 12 | +            textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True) | 
|  | 13 | +            .lstrip() | 
|  | 14 | +            .splitlines() | 
|  | 15 | +        ) | 
|  | 16 | +        return f'{name}: {folded}{self.linesep}' | 
|  | 17 | + | 
|  | 18 | + | 
| 8 | 19 | class Message(email.message.Message): | 
|  | 20 | +    r""" | 
|  | 21 | +    Specialized Message subclass to handle metadata naturally. | 
|  | 22 | +
 | 
|  | 23 | +    Reads values that may have newlines in them and converts the | 
|  | 24 | +    payload to the Description. | 
|  | 25 | +
 | 
|  | 26 | +    >>> msg_text = textwrap.dedent(''' | 
|  | 27 | +    ...     Name: Foo | 
|  | 28 | +    ...     Version: 3.0 | 
|  | 29 | +    ...     License: blah | 
|  | 30 | +    ...             de-blah | 
|  | 31 | +    ...     <BLANKLINE> | 
|  | 32 | +    ...     First line of description. | 
|  | 33 | +    ...     Second line of description. | 
|  | 34 | +    ...     <BLANKLINE> | 
|  | 35 | +    ...     Fourth line! | 
|  | 36 | +    ...     ''').lstrip().replace('<BLANKLINE>', '') | 
|  | 37 | +    >>> msg = Message(email.message_from_string(msg_text)) | 
|  | 38 | +    >>> msg['Description'] | 
|  | 39 | +    'First line of description.\nSecond line of description.\n\nFourth line!\n' | 
|  | 40 | +
 | 
|  | 41 | +    Message should render even if values contain newlines. | 
|  | 42 | +
 | 
|  | 43 | +    >>> print(msg) | 
|  | 44 | +    Name: Foo | 
|  | 45 | +    Version: 3.0 | 
|  | 46 | +    License: blah | 
|  | 47 | +            de-blah | 
|  | 48 | +    Description: First line of description. | 
|  | 49 | +            Second line of description. | 
|  | 50 | +    <BLANKLINE> | 
|  | 51 | +            Fourth line! | 
|  | 52 | +    <BLANKLINE> | 
|  | 53 | +    <BLANKLINE> | 
|  | 54 | +    """ | 
|  | 55 | + | 
| 9 | 56 |     multiple_use_keys = set( | 
| 10 | 57 |         map( | 
| 11 | 58 |             FoldedCase, | 
| @@ -57,15 +104,20 @@ def __getitem__(self, item): | 
| 57 | 104 |     def _repair_headers(self): | 
| 58 | 105 |         def redent(value): | 
| 59 | 106 |             "Correct for RFC822 indentation" | 
| 60 |  | -            if not value or '\n' not in value: | 
|  | 107 | +            indent = ' ' * 8 | 
|  | 108 | +            if not value or '\n' + indent not in value: | 
| 61 | 109 |                 return value | 
| 62 |  | -            return textwrap.dedent(' ' * 8 + value) | 
|  | 110 | +            return textwrap.dedent(indent + value) | 
| 63 | 111 | 
 | 
| 64 | 112 |         headers = [(key, redent(value)) for key, value in vars(self)['_headers']] | 
| 65 | 113 |         if self._payload: | 
| 66 | 114 |             headers.append(('Description', self.get_payload())) | 
|  | 115 | +            self.set_payload('') | 
| 67 | 116 |         return headers | 
| 68 | 117 | 
 | 
|  | 118 | +    def as_string(self): | 
|  | 119 | +        return super().as_string(policy=RawPolicy()) | 
|  | 120 | + | 
| 69 | 121 |     @property | 
| 70 | 122 |     def json(self): | 
| 71 | 123 |         """ | 
|  | 
0 commit comments