= TALES email formatting =
There are many edge-cases when marking up email text for presentation.
There is subtle differences in how people quote text that must be
handled properly. There are also cases were content may look like
quoted text, but it is not.
See 'The fmt: namespace to get strings (hiding)' in tales.txt for
the common use cases.
First, let's bring in a small helper function:
>>> from lp.testing import test_tales
== Quoting styles ==
Paragraphs that mix quoted and reply text fold only the quoted lines.
>>> mixed_quoted_text = ('Mister X wrote:\n'
... '> This is a quoted line\n'
... 'This is a reply to the line above.\n'
... 'This is a continuation line.'
... '\n')
>>> print test_tales('foo/fmt:email-to-html', foo=mixed_quoted_text)
Mister X wrote:
> This is a quoted line
This is a reply to the line above.
This is a continuation line.
A quoted section is folded without affecting the display of the
surrounding paragraph, even if there are no blank lines to separate
the quoted section from the paragraph.
>>> quoted_remark_text = ('Attribution line\n'
... '> quoted_line\n'
... 'Remark line.\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html', foo=quoted_remark_text)
Attribution line
> quoted_line
Remark line.
Multiple quoted paragraphs are treated as a single continuous folded
span.
>>> quoted_paragraphs = ('Attribution line\n'
... '> First line in the first paragraph.\n'
... '> Second line in the first paragraph.\n'
... '> \n'
... '> First line in the second paragraph.\n'
... '> Second line in the second paragraph.\n'
... '> \n'
... '> First line in the third paragraph.\n'
... '> Second line in the third paragraph.\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html', foo=quoted_paragraphs)
Attribution line
> First line in the first paragraph.
> Second line in the first paragraph.
>
> First line in the second paragraph.
> Second line in the second paragraph.
>
> First line in the third paragraph.
> Second line in the third paragraph.
Paragraphs with nested quoting fold all the quoted lines. There
is no distinction between the nested levels of quoting.
>>> nested_quoting = ('>>>> four\n'
... '>>> three\n'
... '>> two\n'
... '> one\n')
>>> print test_tales('foo/fmt:email-to-html', foo=nested_quoting)
>>>> four
>>> three
>> two
> one
Quoting styles vary between email clients, and how the user starts the
quote. Starting runs like '>> ' are as valid as '> ', so they are
wrapped in a foldable-quoted span.
>>> weird_quoted_text = ('Ms. Y wrote:\n'
... '>> This is a double quoted line\n'
... '>> > This is a triple quoted line.\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html', foo=weird_quoted_text)
Ms. Y wrote:
>> This is a double quoted line
>> > This is a triple quoted line.
== Python interpreter and dpkg handling ==
The output from the Python interpreter is not quoted text. Passages
of text that start with '>>> ' are exempted from the 'foldable-quoted'
rules. Note that when '>>> ' occurs inside an existing quoted passage
it will be folded because they are a continuation of a quote (see
the preceding nested quoting test).
# XXX sinzui 2007-08-13 bug=132263:
# Passages may be wrongly be interpreted as Python because they start
# with '>>> '. The formatter does not check that next and previous
# lines of text consistently uses '>>> ' as Python would.
>>> python = ('>>> tz = pytz.timezone("Asia/Calcutta")\n'
... '>>> mydate = datetime.datetime(2007, 2, 18, 15, 35)\n'
... '>>> print tz.localize(mydate)\n'
... '2007-02-18 15:35:00+05:30\n'
... '\n')
>>> not_python = ('> This line really is a quoted passage.\n'
... '>>> This does not invoke an exception rule.\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html',
... foo='\n'.join([python, not_python]))
>>> tz = pytz.timezone("Asia/Calcutta"...
>>> mydate = datetime.datetime(2007, 2, ...
2007-02-18 15:35:00+05:30
> This line really is a quoted ...
>>> This does not invoke an exception rule.
Dpkg generates lines that start with a '|' that will be confused with
quoted text. Dpkg is common in messages, and when it is, we do not
fold lines that start with a '|'. We sometimes receive bad dpkg output
where the lines are broken, and we must take care to identify that
output and not fold it.
>>> bar_quoted_text = ('Someone said sometime ago:\n'
... '| Quote passages are folded.\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html', foo=bar_quoted_text)
Someone said sometime ago:
| Quote passages are folded.
>>> dpkg = ('dpkg -l libdvdread3\n'
... 'Desired=Unknown/Install/Remove/Purge/Hold\n'
... '| Status=Not/Installed/Config-files/Unpacked/Failed-co\n'
... '|/ Err?=(none)/Hold/Reinst-required/X=both-problems\n'
... '||/ Name Version Description\n'
... '+++-==============-==============-====================\n'
... 'ii libdvdread3 0.9.7-2ubuntu1 library for reading DVDs\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html', foo=dpkg)
dpkg -l libdvdread3
Desired=Unknown/Install/...
| Status=Not/Installed/Config-...
|/ Err?=(none)/Hold/Reinst-required/...
||/ Name Version Description
+++-==============-=========...
ii libdvdread3 0.9.7-2ubuntu1 library for reading DVDs
>>> bad_dpkg = ('When dpkg output is in text, possibly tampered with,\n'
... "we must take care to identify '|' quoted passages.\n"
... '$ Desired=Unknown/Install/Remove/Purge/Hold\n'
... '|\n'
... ' Status=Not/Installed/Config-files/Unpacked/Failed-co\n'
... '|/ Err?=(none)/Hold/Reinst-required/X=both-problems\n'
... '||/ Name Version Description\n'
... '+++-==============-==============-==================\n'
... 'ii libdvdread3 0.9.7-2ubuntu1 library for reading DVDs\n'
... '\n')
>>> print test_tales('foo/fmt:email-to-html',
... foo='\n'.join([bad_dpkg]))
When dpkg output is in text, possibly tampered with,
we must take care to identify '|' quoted passages.
$ Desired=Unknown/Install/Remove/...
|
Status=Not/Installed/Config-...
|/ Err?=(none)/Hold/Reinst-required/...
||/ Name Version Description
+++-==============-=========...
ii libdvdread3 0.9.7-2ubuntu1 library for reading DVDs