--- /dev/null
+from StringIO import StringIO
+
+from elementtree.ElementTree import XML
+from libbe.restconvert import rest_xml
+
+def to_unix(text):
+ skip_newline = False
+ for ch in text:
+ if ch not in ('\r', '\n'):
+ yield ch
+ else:
+ if ch == '\n':
+ if skip_newline:
+ continue
+ else:
+ skip_newline = True
+ yield '\n'
+
+
+def soft_text(text):
+ first_space = False
+ translations = {'\n': '<br />\n', '&': '&', '\x3c': '<',
+ '\x3e': '>'}
+ for ch in to_unix(text):
+ if ch == ' ' and first_space is True:
+ yield ' '
+ first_space = ch in (' ')
+ try:
+ yield translations[ch]
+ except KeyError:
+ yield ch
+
+
+def soft_pre(text):
+ return XML('<div style="font-family: monospace">'+
+ ''.join(soft_text(text))+'</div>')
+
+
+def get_rest_body(rest):
+ xml, warnings = rest_xml(StringIO(rest))
+ return xml.find('{http://www.w3.org/1999/xhtml}body'), warnings
+
+
+def comment_body_xhtml(comment):
+ if comment.content_type == "text/restructured":
+ return get_rest_body(comment.body)[0]
+ else:
+ return soft_pre(comment.body)
+
+
+def select_among(name, options, default, display_names=None):
+ output = ['<select name="%s">' % name]
+ for option in options:
+ if option == default:
+ selected = ' selected="selected"'
+ else:
+ selected = ""
+ if display_names is None:
+ display_name = None
+ else:
+ display_name = display_names.get(option)
+
+ if option is None:
+ option = ""
+ if display_name is None:
+ display_name = option
+ value = ""
+ else:
+ value = ' value="%s"' % option
+ output.append("<option%s%s>%s</option>" % (selected, value,
+ display_name))
+ output.append("</select>")
+ return XML("".join(output))
from libbe.utility import time_to_str
from beweb.controllers import bug_list_url, comment_url
from beweb.config import people
-def select_among(name, options, default, display_names=None):
- output = ['<select name="%s">' % name]
- for option in options:
- if option == default:
- selected = ' selected="selected"'
- else:
- selected = ""
- if display_names is None:
- display_name = None
- else:
- display_name = display_names.get(option)
-
- if option is None:
- option = ""
- if display_name is None:
- display_name = option
- value = ""
- else:
- value = ' value="%s"' % option
- output.append("<option%s%s>%s</option>" % (selected, value,
- display_name))
- output.append("</select>")
- return XML("".join(output))
-
-def to_unix(text):
- skip_newline = False
- for ch in text:
- if ch not in ('\r', '\n'):
- yield ch
- else:
- if ch == '\n':
- if skip_newline:
- continue
- else:
- skip_newline = True
- yield '\n'
-
-def soft_text(text):
- first_space = False
- translations = {'\n': '<br />\n', '&': '&', '\x3c': '<',
- '\x3e': '>'}
- for ch in to_unix(text):
- if ch == ' ' and first_space is True:
- yield ' '
- first_space = ch in (' ')
- try:
- yield translations[ch]
- except KeyError:
- yield ch
-def soft_pre(text):
- return XML('<div style="font-family: monospace">'+
- ''.join(soft_text(text))+'</div>')
+from beweb.formatting import comment_body_xhtml, select_among
?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<tr><td>From</td><td>${comment.From}</td></tr>
<tr><td>Date</td><td>${time_to_str(comment.date)}</td></tr>
</table>
- <div py:content="soft_pre(comment.body)" py:strip="True"></div>
+ <div py:content="comment_body_xhtml(comment)" py:strip="True"></div>
<a href="${comment_url(project_id, bug.uuid, comment.uuid)}">Edit</a>
<a href="${comment_url(project_id, bug.uuid, comment.uuid,
action='Reply')}">Reply</a>
--- /dev/null
+import re
+from StringIO import StringIO
+from docutils import nodes
+from docutils.statemachine import StringList
+from docutils.core import publish_file
+from docutils.parsers import rst
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.states import Inliner, MarkupMismatch, unescape
+from elementtree import ElementTree
+
+
+def rest_xml(rest):
+ warnings = StringIO()
+ parser = rst.Parser(inliner=HelpLinkInliner())
+ xmltext = publish_file(rest, writer_name="html", parser=parser,
+ settings_overrides={"warning_stream": warnings,
+ "halt_level": 5})
+ warnings.seek(0)
+ return ElementTree.parse(StringIO(xmltext)).getroot(), warnings.read()
+
+class HelpLinkInliner(Inliner):
+ def __init__(self, roles=None):
+ Inliner.__init__(self, roles)
+ regex = re.compile('\[([^|]*)\|([^]]*)\]')
+ self.implicit_dispatch.append((regex, self.help_reference))
+
+ def parse(self, *args, **kwargs):
+ self.more_messages = []
+ nodes, messages = Inliner.parse(self, *args, **kwargs)
+ return nodes, (messages + self.more_messages)
+
+ def help_reference(self, match, lineno):
+ from wizardhelp.controllers import iter_help_pages
+ text,link = match.groups()
+ rawtext = match.group(0)
+ text, link, rawtext = [unescape(f, 1) for f in (text, link, rawtext)]
+ if link not in list(iter_help_pages()):
+ msg = self.reporter.warning('Broken link to "%s".' % link,
+ line=lineno)
+ self.more_messages.append(msg)
+ ref = "/help/%s/" % link
+ unescaped = text
+ node = nodes.reference(rawtext, text, refuri=ref)
+ node.set_class("helplink")
+ return [node]
+
+
+def rst_directive(name=None, required_args=0, optional_args=0,
+ final_arg_ws=False, options=None, content='forbidden'):
+ """Decorator that simplifies creating ReST directives
+
+ All arguments are optional. Name is, by default, determined from the
+ function name.
+
+ The possible values for content are 'forbidden', 'allowed' (but not
+ required), and 'required' (a warning will be generated if not present).
+ """
+ content_rules = {'forbidden': (False, False), 'allowed': (True, False),
+ 'required': (True, True)}
+ content_allowed, content_required = content_rules[content]
+
+ def decorator_factory(func):
+ my_name = name
+ if my_name is None:
+ my_name = func.__name__
+
+ def decorator(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ warn = state_machine.reporter.warning
+ if not content and content_required:
+ warn = state_machine.reporter.warning
+ warning = warn('%s is empty' % my_name,
+ nodes.literal_block(block_text, block_text),
+ line=lineno)
+ return [warning]
+ return func(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine)
+
+ decorator.arguments = (required_args, optional_args, final_arg_ws)
+ decorator.options = options
+ decorator.content = content_allowed
+ directives.register_directive(my_name, decorator)
+ return decorator
+ return decorator_factory
+
+
+@rst_directive(required_args=1, final_arg_ws=True, content='required')
+def foldout(name, arguments, options, content, lineno, content_offset,
+ block_text, state, state_machine):
+ """\
+ Generate a foldout section.
+
+ On the ReST side, this merely involves marking the items with suitable
+ classes. A Kid match rule will be used to insert the appropriate
+ Javascript magic.
+ """
+ text = '\n'.join(content)
+ foldout_title = nodes.paragraph([arguments[0]])
+ foldout_title.set_class('foldout-title')
+ state.nested_parse(StringList([arguments[0]]), 0, foldout_title)
+ foldout_body = nodes.compound(text)
+ foldout_body.set_class('foldout-body')
+ state.nested_parse(content, content_offset, foldout_body)
+ foldout = nodes.compound(text)
+ foldout += foldout_title
+ foldout += foldout_body
+ foldout.set_class('foldout')
+ return [foldout]