aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Bentley <abentley@panoramicfeedback.com>2006-04-06 14:05:47 -0400
committerAaron Bentley <abentley@panoramicfeedback.com>2006-04-06 14:05:47 -0400
commit64ea14acca6f6420ea17c0c2126a71de2c3ef91d (patch)
treea1fdc94063e7d3b5f64c3428a9313dcc33a55f43
parente762576b97dc1c7ccbb7b0d07b94d9d42ec36b9d (diff)
downloadbugseverywhere-64ea14acca6f6420ea17c0c2126a71de2c3ef91d.tar.gz
Add ReST support to web front end
-rw-r--r--.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body1
-rw-r--r--.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values21
-rw-r--r--.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values35
-rw-r--r--beweb/beweb/controllers.py1
-rw-r--r--beweb/beweb/formatting.py73
-rw-r--r--beweb/beweb/templates/edit_bug.kid55
-rw-r--r--libbe/restconvert.py108
7 files changed, 241 insertions, 53 deletions
diff --git a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body
new file mode 100644
index 0000000..21fb43d
--- /dev/null
+++ b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/body
@@ -0,0 +1 @@
+Add *support*, damnit!
diff --git a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values
new file mode 100644
index 0000000..58e8ffa
--- /dev/null
+++ b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/comments/2bb9163c-a2c4-4301-aff5-385f58a14301/values
@@ -0,0 +1,21 @@
+
+
+
+Content-type=text/rst
+
+
+
+
+
+
+Date=Thu, 06 Apr 2006 16:47:25 +0000
+
+
+
+
+
+
+From=abentley
+
+
+
diff --git a/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values
new file mode 100644
index 0000000..9f3c7ac
--- /dev/null
+++ b/.be/bugs/09f84059-fc8e-4954-b24d-a2b33ef21bf4/values
@@ -0,0 +1,35 @@
+
+
+
+creator=abentley
+
+
+
+
+
+
+severity=minor
+
+
+
+
+
+
+status=closed
+
+
+
+
+
+
+summary=Support RST
+
+
+
+
+
+
+time=Thu, 06 Apr 2006 16:45:52 +0000
+
+
+
diff --git a/beweb/beweb/controllers.py b/beweb/beweb/controllers.py
index 74c6a7d..fc505ed 100644
--- a/beweb/beweb/controllers.py
+++ b/beweb/beweb/controllers.py
@@ -23,6 +23,7 @@ class Comment(PrestHandler):
bug_tree = project_tree(comment_data['project'])
bug = bug_tree.get_bug(comment_data['bug'])
comment = new_comment(bug, "")
+ comment.content_type = "text/restructured"
comment.save()
raise cherrypy.HTTPRedirect(comment_url(comment=comment.uuid,
**comment_data))
diff --git a/beweb/beweb/formatting.py b/beweb/beweb/formatting.py
new file mode 100644
index 0000000..44ed849
--- /dev/null
+++ b/beweb/beweb/formatting.py
@@ -0,0 +1,73 @@
+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', '&': '&amp;', '\x3c': '&lt;',
+ '\x3e': '&gt;'}
+ for ch in to_unix(text):
+ if ch == ' ' and first_space is True:
+ yield '&#160;'
+ 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))
diff --git a/beweb/beweb/templates/edit_bug.kid b/beweb/beweb/templates/edit_bug.kid
index 960866d..c31d660 100644
--- a/beweb/beweb/templates/edit_bug.kid
+++ b/beweb/beweb/templates/edit_bug.kid
@@ -4,58 +4,7 @@ from libbe.bugdir import severity_levels, active_status, inactive_status, thread
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', '&': '&amp;', '\x3c': '&lt;',
- '\x3e': '&gt;'}
- for ch in to_unix(text):
- if ch == ' ' and first_space is True:
- yield '&#160;'
- 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'">
@@ -79,7 +28,7 @@ def soft_pre(text):
<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>
diff --git a/libbe/restconvert.py b/libbe/restconvert.py
new file mode 100644
index 0000000..f93fcf6
--- /dev/null
+++ b/libbe/restconvert.py
@@ -0,0 +1,108 @@
+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]