← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~mbp/launchpad/391780-markdown into lp:launchpad

 

Martin Pool has proposed merging lp:~mbp/launchpad/391780-markdown into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~mbp/launchpad/391780-markdown/+merge/82832

Support Markdown markup in project and user home pages, per bug 391780.
-- 
https://code.launchpad.net/~mbp/launchpad/391780-markdown/+merge/82832
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~mbp/launchpad/391780-markdown into lp:launchpad.
=== modified file 'lib/lp/app/browser/stringformatter.py'
--- lib/lp/app/browser/stringformatter.py	2011-11-03 03:24:57 +0000
+++ lib/lp/app/browser/stringformatter.py	2011-11-20 23:02:25 +0000
@@ -22,6 +22,7 @@
 import re
 from lxml import html
 from xml.sax.saxutils import unescape as xml_unescape
+import markdown
 
 from zope.component import getUtility
 from zope.interface import implements
@@ -39,6 +40,9 @@
     re_email_address,
     obfuscate_email,
     )
+from lp.services.features import (
+    getFeatureFlag,
+    )
 
 
 def escape(text, quote=True):
@@ -972,6 +976,12 @@
         url = root_url + self._stringtoformat
         return '<a href="%s">%s</a>' % (url, self._stringtoformat)
 
+    def markdown(self):
+        if getFeatureFlag('markdown.enabled'):
+            return format_markdown(self._stringtoformat)
+        else:
+            return self.text_to_html()
+
     def traverse(self, name, furtherPath):
         if name == 'nl_to_br':
             return self.nl_to_br()
@@ -981,6 +991,8 @@
             return self.lower()
         elif name == 'break-long-words':
             return self.break_long_words()
+        elif name == 'markdown':
+            return self.markdown()
         elif name == 'text-to-html':
             return self.text_to_html()
         elif name == 'text-to-html-with-target':
@@ -1021,3 +1033,14 @@
             return self.oops_id()
         else:
             raise TraversalError(name)
+
+
+def format_markdown(text):
+    """Return html form of marked-up text."""
+    # This returns whole paragraphs (in p tags), similarly to text_to_html.
+    md = markdown.Markdown(
+        safe_mode='escape',
+        extensions=[
+            'tables',
+            ])
+    return md.convert(text)  # How easy was that?

=== modified file 'lib/lp/app/browser/tests/test_stringformatter.py'
--- lib/lp/app/browser/tests/test_stringformatter.py	2011-08-28 07:29:11 +0000
+++ lib/lp/app/browser/tests/test_stringformatter.py	2011-11-20 23:02:25 +0000
@@ -9,6 +9,11 @@
 from textwrap import dedent
 import unittest
 
+from testtools.matchers import (
+    Equals,
+    Matcher,
+    )
+
 from zope.component import getUtility
 
 from canonical.config import config
@@ -20,6 +25,7 @@
     linkify_bug_numbers,
     )
 from lp.testing import TestCase
+from lp.services.features.testing import FeatureFixture
 
 
 def test_split_paragraphs():
@@ -401,6 +407,50 @@
                 expected_string, formatted_string))
 
 
+class MarksDownAs(Matcher):
+
+    def __init__(self, expected_html):
+        self.expected_html = expected_html
+
+    def match(self, input_string):
+        return Equals(self.expected_html).match(
+            FormattersAPI(input_string).markdown())
+
+
+class TestMarkdownDisabled(TestCase):
+    """Feature flag can turn Markdown stuff off.
+    """
+
+    layer = DatabaseFunctionalLayer  # Fixtures need the database for now
+
+    def setUp(self):
+        super(TestMarkdownDisabled, self).setUp()
+        self.useFixture(FeatureFixture({'markdown.enabled': None}))
+
+    def test_plain_text(self):
+        self.assertThat(
+            'hello **simple** world',
+            MarksDownAs('<p>hello **simple** world</p>'))
+
+
+class TestMarkdown(TestCase):
+    """Test for Markdown integration within Launchpad.
+
+    Not an exhaustive test, more of a check for our integration and configuration.
+    """
+
+    layer = DatabaseFunctionalLayer  # Fixtures need the database for now
+
+    def setUp(self):
+        super(TestMarkdown, self).setUp()
+        self.useFixture(FeatureFixture({'markdown.enabled': 'on'}))
+
+    def test_plain_text(self):
+        self.assertThat(
+            'hello world',
+            MarksDownAs('<p>hello world</p>'))
+
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTests(DocTestSuite())

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2011-10-19 12:44:55 +0000
+++ lib/lp/registry/browser/person.py	2011-11-20 23:02:25 +0000
@@ -2381,10 +2381,12 @@
         if content is None:
             return None
         elif self.is_probationary_or_invalid_user:
+            # XXX: Is this really useful?  They can post links in many other
+            # places. -- mbp 2011-11-20.
             return cgi.escape(content)
         else:
             formatter = FormattersAPI
-            return formatter(content).text_to_html()
+            return formatter(content).markdown()
 
     @cachedproperty
     def recently_approved_members(self):

=== modified file 'lib/lp/registry/templates/product-index.pt'
--- lib/lp/registry/templates/product-index.pt	2011-06-16 13:50:58 +0000
+++ lib/lp/registry/templates/product-index.pt	2011-11-20 23:02:25 +0000
@@ -55,14 +55,15 @@
           <a tal:replace="structure overview_menu/review_license/fmt:icon"/>
         </p>
 
-        <div class="summary" tal:content="context/summary">
+	<div class="summary" 
+	  tal:content="structure context/summary/fmt:markdown">
           $Product.summary goes here. This should be quite short,
           just a single paragraph of text really, giving the project
           highlights.
         </div>
 
         <div class="description"
-          tal:content="structure context/description/fmt:text-to-html"
+          tal:content="structure context/description/fmt:markdown"
           tal:condition="context/description">
           $Product.description goes here. This should be a longer piece of
           text, up to three paragraphs in length, which gives much more

=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py	2011-11-16 23:46:01 +0000
+++ lib/lp/services/features/flags.py	2011-11-20 23:02:25 +0000
@@ -107,6 +107,12 @@
      '',
      '',
      ''),
+    ('markdown.enabled',
+     'boolean',
+     'Interpret selected user content as Markdown.',
+     'disabled',
+     'Markdown',
+     'https://launchpad.net/bugs/391780'),
     ('memcache',
      'boolean',
      'Enables use of memcached where it is supported.',

=== modified file 'setup.py'
--- setup.py	2011-11-07 12:47:36 +0000
+++ setup.py	2011-11-20 23:02:25 +0000
@@ -52,6 +52,7 @@
         # Required for launchpadlib
         'keyring',
         'manuel',
+        'Markdown',
         'mechanize',
         'meliae',
         'mercurial',

=== modified file 'versions.cfg'
--- versions.cfg	2011-11-17 23:52:26 +0000
+++ versions.cfg	2011-11-20 23:02:25 +0000
@@ -43,6 +43,7 @@
 lazr.testing = 0.1.1
 lazr.uri = 1.0.2
 manuel = 1.1.1
+Markdown = 2.0.3
 martian = 0.11
 mechanize = 0.1.11
 meliae = 0.2.0.final.0


Follow ups