minirst: add basic HTML formatting support
authorMatt Mackall <mpm@selenic.com>
Sat, 15 Oct 2011 00:39:01 -0500
changeset 15261 e2df5b866d22
parent 15260 be2c67a8bf88
child 15262 e8076af14498
minirst: add basic HTML formatting support
mercurial/minirst.py
tests/test-minirst.py
tests/test-minirst.py.out
--- a/mercurial/minirst.py	Fri Oct 14 15:40:50 2011 -0500
+++ b/mercurial/minirst.py	Sat Oct 15 00:39:01 2011 -0500
@@ -497,6 +497,93 @@
                      initindent=indent,
                      hangindent=subindent) + '\n'
 
+def formathtml(blocks):
+    """Format RST blocks as HTML"""
+
+    out = []
+    headernest = ''
+    listnest = []
+
+    def openlist(start, level):
+        if not listnest or listnest[-1][0] != start:
+            listnest.append((start, level))
+            out.append('<%s>\n' % start)
+
+    blocks = [b for b in blocks if b['type'] != 'margin']
+
+    for pos, b in enumerate(blocks):
+        btype = b['type']
+        level = b['indent']
+        lines = b['lines']
+
+        if btype == 'admonition':
+            admonition = _admonitiontitles[b['admonitiontitle']]
+            text = ' '.join(map(str.strip, lines))
+            out.append('<p>\n<b>%s</b> %s\n</p>\n' % (admonition, text))
+        elif btype == 'paragraph':
+            out.append('<p>\n%s\n</p>\n' % '\n'.join(lines))
+        elif btype == 'margin':
+            pass
+        elif btype == 'literal':
+            out.append('<pre>\n%s\n</pre>\n' % '\n'.join(lines))
+        elif btype == 'section':
+            i = b['underline']
+            if i not in headernest:
+                headernest += i
+            level = headernest.index(i) + 1
+            out.append('<h%d>%s</h%d>\n' % (level, lines[0], level))
+        elif btype == 'table':
+            table = b['table']
+            t = []
+            for row in table:
+                l = []
+                for v in zip(row):
+                    if not t:
+                        l.append('<th>%s</th>' % v)
+                    else:
+                        l.append('<td>%s</td>' % v)
+                t.append(' <tr>%s</tr>\n' % ''.join(l))
+            out.append('<table>\n%s</table>\n' % ''.join(t))
+        elif btype == 'definition':
+            openlist('dl', level)
+            term = lines[0]
+            text = ' '.join(map(str.strip, lines[1:]))
+            out.append(' <dt>%s\n <dd>%s\n' % (term, text))
+        elif btype == 'bullet':
+            bullet, head = lines[0].split(' ', 1)
+            if bullet == '-':
+                openlist('ul', level)
+            else:
+                openlist('ol', level)
+            out.append(' <li> %s\n' % ' '.join([head] + lines[1:]))
+        elif btype == 'field':
+            openlist('dl', level)
+            key = b['key']
+            text = ' '.join(map(str.strip, lines))
+            out.append(' <dt>%s\n <dd>%s\n' % (key, text))
+        elif btype == 'option':
+            openlist('dl', level)
+            opt = b['optstr']
+            desc = ' '.join(map(str.strip, lines))
+            out.append(' <dt>%s\n <dd>%s\n' % (opt, desc))
+
+        # close lists if indent level of next block is lower
+        if listnest:
+            start, level = listnest[-1]
+            if pos == len(blocks) - 1:
+                out.append('</%s>\n' % start)
+                listnest.pop()
+            else:
+                nb = blocks[pos + 1]
+                ni = nb['indent']
+                if (ni < level or
+                    (ni == level and
+                     nb['type'] not in 'definition bullet field option')):
+                    out.append('</%s>\n' % start)
+                    listnest.pop()
+
+    return ''.join(out)
+
 def parse(text, indent=0, keep=None):
     """Parse text into a list of blocks"""
     pruned = []
--- a/tests/test-minirst.py	Fri Oct 14 15:40:50 2011 -0500
+++ b/tests/test-minirst.py	Sat Oct 15 00:39:01 2011 -0500
@@ -3,8 +3,9 @@
 
 def debugformat(title, text, width, **kwargs):
     print "%s formatted to fit within %d characters:" % (title, width)
+    formatted = minirst.format(text, width, **kwargs)
+    html = minirst.formathtml(minirst.parse(text, **kwargs)[0])
     print "-" * 70
-    formatted = minirst.format(text, width, **kwargs)
     if type(formatted) == tuple:
         print formatted[0]
         print "-" * 70
@@ -12,6 +13,8 @@
     else:
         print formatted
     print "-" * 70
+    print html
+    print "-" * 70
     print
 
 paragraphs = """
--- a/tests/test-minirst.py.out	Fri Oct 14 15:40:50 2011 -0500
+++ b/tests/test-minirst.py.out	Sat Oct 15 00:39:01 2011 -0500
@@ -8,6 +8,19 @@
 The third and final paragraph.
 
 ----------------------------------------------------------------------
+<p>
+This is some text in the first paragraph.
+</p>
+<p>
+A small indented paragraph.
+It is followed by some lines
+containing random whitespace.
+</p>
+<p>
+The third and final paragraph.
+</p>
+
+----------------------------------------------------------------------
 
 paragraphs formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -22,6 +35,19 @@
 The third and final paragraph.
 
 ----------------------------------------------------------------------
+<p>
+This is some text in the first paragraph.
+</p>
+<p>
+A small indented paragraph.
+It is followed by some lines
+containing random whitespace.
+</p>
+<p>
+The third and final paragraph.
+</p>
+
+----------------------------------------------------------------------
 
 definitions formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -37,6 +63,16 @@
     Definition.
 
 ----------------------------------------------------------------------
+<dl>
+ <dt>A Term
+ <dd>Definition. The indented lines make up the definition.
+ <dt>Another Term
+ <dd>Another definition. The final line in the definition determines the indentation, so this will be indented with four spaces.
+ <dt>A Nested/Indented Term
+ <dd>Definition.
+</dl>
+
+----------------------------------------------------------------------
 
 definitions formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -57,6 +93,16 @@
     Definition.
 
 ----------------------------------------------------------------------
+<dl>
+ <dt>A Term
+ <dd>Definition. The indented lines make up the definition.
+ <dt>Another Term
+ <dd>Another definition. The final line in the definition determines the indentation, so this will be indented with four spaces.
+ <dt>A Nested/Indented Term
+ <dd>Definition.
+</dl>
+
+----------------------------------------------------------------------
 
 literals formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -78,6 +124,31 @@
       with '::' disappears in the final output.
 
 ----------------------------------------------------------------------
+<p>
+The fully minimized form is the most
+convenient form:
+</p>
+<pre>
+Hello
+  literal
+    world
+</pre>
+<p>
+In the partially minimized form a paragraph
+simply ends with space-double-colon.
+</p>
+<pre>
+////////////////////////////////////////
+long un-wrapped line in a literal block
+\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+</pre>
+<pre>
+This literal block is started with '::',
+  the so-called expanded form. The paragraph
+    with '::' disappears in the final output.
+</pre>
+
+----------------------------------------------------------------------
 
 literals formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -101,6 +172,31 @@
       with '::' disappears in the final output.
 
 ----------------------------------------------------------------------
+<p>
+The fully minimized form is the most
+convenient form:
+</p>
+<pre>
+Hello
+  literal
+    world
+</pre>
+<p>
+In the partially minimized form a paragraph
+simply ends with space-double-colon.
+</p>
+<pre>
+////////////////////////////////////////
+long un-wrapped line in a literal block
+\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+</pre>
+<pre>
+This literal block is started with '::',
+  the so-called expanded form. The paragraph
+    with '::' disappears in the final output.
+</pre>
+
+----------------------------------------------------------------------
 
 lists formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -137,6 +233,49 @@
 This is the second line.
 
 ----------------------------------------------------------------------
+<ul>
+ <li> This is the first list item.
+<p>
+Second paragraph in the first list item.
+</p>
+ <li> List items need not be separated   by a blank line.
+ <li> And will be rendered without   one in any case.
+</ul>
+<p>
+We can have indented lists:
+</p>
+<ul>
+ <li> This is an indented list item
+ <li> Another indented list item:
+<pre>
+- A literal block in the middle
+      of an indented list.
+</pre>
+<pre>
+(The above is not a list item since we are in the literal block.)
+</pre>
+</ul>
+<pre>
+Literal block with no indentation (apart from
+the two spaces added to all literal blocks).
+</pre>
+<ol>
+ <li> This is an enumerated list (first item).
+ <li> Continuing with the second item.
+ <li> foo
+ <li> bar
+ <li> Another
+ <li> List
+</ol>
+<p>
+Line blocks are also a form of list:
+</p>
+<ol>
+ <li> This is the first line.   The line continues here.
+ <li> This is the second line.
+</ol>
+
+----------------------------------------------------------------------
 
 lists formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -182,6 +321,49 @@
 This is the second line.
 
 ----------------------------------------------------------------------
+<ul>
+ <li> This is the first list item.
+<p>
+Second paragraph in the first list item.
+</p>
+ <li> List items need not be separated   by a blank line.
+ <li> And will be rendered without   one in any case.
+</ul>
+<p>
+We can have indented lists:
+</p>
+<ul>
+ <li> This is an indented list item
+ <li> Another indented list item:
+<pre>
+- A literal block in the middle
+      of an indented list.
+</pre>
+<pre>
+(The above is not a list item since we are in the literal block.)
+</pre>
+</ul>
+<pre>
+Literal block with no indentation (apart from
+the two spaces added to all literal blocks).
+</pre>
+<ol>
+ <li> This is an enumerated list (first item).
+ <li> Continuing with the second item.
+ <li> foo
+ <li> bar
+ <li> Another
+ <li> List
+</ol>
+<p>
+Line blocks are also a form of list:
+</p>
+<ol>
+ <li> This is the first line.   The line continues here.
+ <li> This is the second line.
+</ol>
+
+----------------------------------------------------------------------
 
 options formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -210,6 +392,37 @@
 --foo bar baz
 
 ----------------------------------------------------------------------
+<p>
+There is support for simple option lists,
+but only with long options:
+</p>
+<dl>
+ <dt>-X --exclude filter
+ <dd>an option with a short and long option with an argument
+ <dt>-I --include
+ <dd>an option with both a short option and a long option
+ <dt>   --all
+ <dd>Output all.
+ <dt>   --both
+ <dd>Output both (this description is quite long).
+ <dt>   --long
+ <dd>Output all day long.
+ <dt>   --par
+ <dd>This option has two paragraphs in its description. This is the first.
+<p>
+This is the second.  Blank lines may be omitted between
+options (as above) or left in (as here).
+</p>
+</dl>
+<p>
+The next paragraph looks like an option list, but lacks the two-space
+marker after the option. It is treated as a normal paragraph:
+</p>
+<p>
+--foo bar baz
+</p>
+
+----------------------------------------------------------------------
 
 options formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -283,6 +496,37 @@
 --foo bar baz
 
 ----------------------------------------------------------------------
+<p>
+There is support for simple option lists,
+but only with long options:
+</p>
+<dl>
+ <dt>-X --exclude filter
+ <dd>an option with a short and long option with an argument
+ <dt>-I --include
+ <dd>an option with both a short option and a long option
+ <dt>   --all
+ <dd>Output all.
+ <dt>   --both
+ <dd>Output both (this description is quite long).
+ <dt>   --long
+ <dd>Output all day long.
+ <dt>   --par
+ <dd>This option has two paragraphs in its description. This is the first.
+<p>
+This is the second.  Blank lines may be omitted between
+options (as above) or left in (as here).
+</p>
+</dl>
+<p>
+The next paragraph looks like an option list, but lacks the two-space
+marker after the option. It is treated as a normal paragraph:
+</p>
+<p>
+--foo bar baz
+</p>
+
+----------------------------------------------------------------------
 
 fields formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -298,6 +542,23 @@
             This key is big enough to get its own line.
 
 ----------------------------------------------------------------------
+<dl>
+ <dt>a
+ <dd>First item.
+ <dt>ab
+ <dd>Second item. Indentation and wrapping is handled automatically.
+</dl>
+<p>
+Next list:
+</p>
+<dl>
+ <dt>small
+ <dd>The larger key below triggers full indentation here.
+ <dt>much too large
+ <dd>This key is big enough to get its own line.
+</dl>
+
+----------------------------------------------------------------------
 
 fields formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -318,12 +579,34 @@
             own line.
 
 ----------------------------------------------------------------------
+<dl>
+ <dt>a
+ <dd>First item.
+ <dt>ab
+ <dd>Second item. Indentation and wrapping is handled automatically.
+</dl>
+<p>
+Next list:
+</p>
+<dl>
+ <dt>small
+ <dd>The larger key below triggers full indentation here.
+ <dt>much too large
+ <dd>This key is big enough to get its own line.
+</dl>
+
+----------------------------------------------------------------------
 
 containers (normal) formatted to fit within 60 characters:
 ----------------------------------------------------------------------
 Normal output.
 
 ----------------------------------------------------------------------
+<p>
+Normal output.
+</p>
+
+----------------------------------------------------------------------
 
 containers (verbose) formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -334,6 +617,14 @@
 ----------------------------------------------------------------------
 ['debug', 'debug']
 ----------------------------------------------------------------------
+<p>
+Normal output.
+</p>
+<p>
+Verbose output.
+</p>
+
+----------------------------------------------------------------------
 
 containers (debug) formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -344,6 +635,14 @@
 ----------------------------------------------------------------------
 ['verbose']
 ----------------------------------------------------------------------
+<p>
+Normal output.
+</p>
+<p>
+Initial debug output.
+</p>
+
+----------------------------------------------------------------------
 
 containers (verbose debug) formatted to fit within 60 characters:
 ----------------------------------------------------------------------
@@ -358,12 +657,31 @@
 ----------------------------------------------------------------------
 []
 ----------------------------------------------------------------------
+<p>
+Normal output.
+</p>
+<p>
+Initial debug output.
+</p>
+<p>
+Verbose output.
+</p>
+<p>
+Debug output.
+</p>
+
+----------------------------------------------------------------------
 
 roles formatted to fit within 60 characters:
 ----------------------------------------------------------------------
 Please see "hg add".
 
 ----------------------------------------------------------------------
+<p>
+Please see "hg add".
+</p>
+
+----------------------------------------------------------------------
 
 sections formatted to fit within 20 characters:
 ----------------------------------------------------------------------
@@ -380,6 +698,12 @@
 ---------------------------
 
 ----------------------------------------------------------------------
+<h1>Title</h1>
+<h2>Section</h2>
+<h3>Subsection</h3>
+<h2>Markup: "foo" and "hg help"</h2>
+
+----------------------------------------------------------------------
 
 admonitions formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -397,6 +721,21 @@
    This is danger
 
 ----------------------------------------------------------------------
+<p>
+<b>Note:</b> This is a note
+</p>
+<ul>
+ <li> Bullet 1
+ <li> Bullet 2
+</ul>
+<p>
+<b>Warning!</b> This is a warning Second input line of warning
+</p>
+<p>
+<b>!Danger!</b> This is danger
+</p>
+
+----------------------------------------------------------------------
 
 comments formatted to fit within 30 characters:
 ----------------------------------------------------------------------
@@ -407,6 +746,17 @@
 Empty comment above
 
 ----------------------------------------------------------------------
+<p>
+Some text.
+</p>
+<p>
+Some indented text.
+</p>
+<p>
+Empty comment above
+</p>
+
+----------------------------------------------------------------------
 
   === === ========================================
   a   b   c                                       
@@ -425,4 +775,11 @@
           man
 
 ----------------------------------------------------------------------
+<table>
+ <tr><th>a</th><th>b</th><th>c</th></tr>
+ <tr><td>1</td><td>2</td><td>3</td></tr>
+ <tr><td>foo</td><td>bar</td><td>baz this list is very very very long man</td></tr>
+</table>
 
+----------------------------------------------------------------------
+