From 7211983b752b1fe50de75253f4ba86c7444c9252 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Aug 06 2015 16:10:34 +0000 Subject: [PATCH 1/3] Rework the markdown integration to rely on our own extension calling our patterns --- diff --git a/pagure/pfmarkdown.py b/pagure/pfmarkdown.py index 5d1cc7e..ec79c67 100644 --- a/pagure/pfmarkdown.py +++ b/pagure/pfmarkdown.py @@ -16,8 +16,11 @@ """ Pagure-flavored Markdown Author: Ralph Bean + Pierre-Yves Chibon """ +import re + import flask import markdown.inlinepatterns @@ -33,100 +36,101 @@ EXPLICIT_MAIN_ISSUE_RE = r'[^|\w](?]*>', - r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]', - r'\bwww\.[^)<>\s]+[^.,)<>\s]', - r'[^(<\s]+\.(?:com|net|org)\b', - ]) - - # Second, build some Pattern objects for @mentions, #bugs, etc... - class MentionPattern(markdown.inlinepatterns.Pattern): - """ @user pattern class. """ - - def handleMatch(self, m): - """ When the pattern matches, update the text. """ - name = markdown.util.AtomicString(m.group(2)) - text = ' @%s' % name - user = pagure.lib.search_user(pagure.SESSION, username=name) - if not user: - return text - - element = markdown.util.etree.Element("a") - url = flask.url_for('view_user', username=name) - element.set('href', url) - element.text = text - return element - - class ExplicitForkIssuePattern(markdown.inlinepatterns.Pattern): - """ Explicit fork issue pattern. """ - - def handleMatch(self, m): - """ When the pattern matches, update the text. """ - user = markdown.util.AtomicString(m.group(2)) - repo = markdown.util.AtomicString(m.group(3)) - idx = markdown.util.AtomicString(m.group(4)) - text = '%s/%s#%s' % (user, repo, idx) - - if not _issue_exists(user, repo, idx): - return text - - return _issue_anchor_tag(user, repo, idx, text) - - class ExplicitMainIssuePattern(markdown.inlinepatterns.Pattern): - """ Explicit issue pattern (for non-fork project). """ - def handleMatch(self, m): - """ When the pattern matches, update the text. """ - repo = markdown.util.AtomicString(m.group(2)) - idx = markdown.util.AtomicString(m.group(3)) - text = ' %s#%s' % (repo, idx) - - if not _issue_exists(None, repo, idx): - return text - - return _issue_anchor_tag(None, repo, idx, text) - - class ImplicitIssuePattern(markdown.inlinepatterns.Pattern): - """ Implicit issue pattern. """ - def handleMatch(self, m): - """ When the pattern matches, update the text. """ - idx = markdown.util.AtomicString(m.group(2)) - text = ' #%s' % idx - - root = flask.request.url_root - url = flask.request.url - user = None - if 'fork/' in flask.request.url: - user, repo = url.split('fork/')[1].split('/', 2)[:2] - else: - repo = url.split(root)[1].split('/', 1)[0] - - if not _issue_exists(user, repo, idx): - return text - - return _issue_anchor_tag(user, repo, idx, text) - - # Lastly, monkey-patch the build_inlinepatterns func to insert our patterns - original_builder = markdown.build_inlinepatterns - - def extended_builder(func, **kwargs): - """ Extends the original builder with our owns. """ - patterns = original_builder(func, **kwargs) - patterns['mention'] = MentionPattern( - MENTION_RE, func) - patterns['explicit_fork_issue'] = ExplicitForkIssuePattern( - EXPLICIT_FORK_ISSUE_RE, func) - patterns['explicit_main_issue'] = ExplicitMainIssuePattern( - EXPLICIT_MAIN_ISSUE_RE, func) - patterns['implicit_issue'] = ImplicitIssuePattern( - IMPLICIT_ISSUE_RE, func) - return patterns - - markdown.build_inlinepatterns = extended_builder +class MentionPattern(markdown.inlinepatterns.Pattern): + """ @user pattern class. """ + + def handleMatch(self, m): + """ When the pattern matches, update the text. """ + name = markdown.util.AtomicString(m.group(2)) + text = ' @%s' % name + user = pagure.lib.search_user(pagure.SESSION, username=name) + if not user: + return text + + element = markdown.util.etree.Element("a") + url = flask.url_for('view_user', username=name) + element.set('href', url) + element.text = text + return element + + +class ExplicitForkIssuePattern(markdown.inlinepatterns.Pattern): + """ Explicit fork issue pattern. """ + + def handleMatch(self, m): + """ When the pattern matches, update the text. """ + user = markdown.util.AtomicString(m.group(2)) + repo = markdown.util.AtomicString(m.group(3)) + idx = markdown.util.AtomicString(m.group(4)) + text = '%s/%s#%s' % (user, repo, idx) + + if not _issue_exists(user, repo, idx): + return text + + return _issue_anchor_tag(user, repo, idx, text) + + +class ExplicitMainIssuePattern(markdown.inlinepatterns.Pattern): + """ Explicit issue pattern (for non-fork project). """ + + def handleMatch(self, m): + """ When the pattern matches, update the text. """ + repo = markdown.util.AtomicString(m.group(2)) + idx = markdown.util.AtomicString(m.group(3)) + text = ' %s#%s' % (repo, idx) + + if not _issue_exists(None, repo, idx): + return text + + return _issue_anchor_tag(None, repo, idx, text) + + +class ImplicitIssuePattern(markdown.inlinepatterns.Pattern): + """ Implicit issue pattern. """ + + def handleMatch(self, m): + """ When the pattern matches, update the text. """ + idx = markdown.util.AtomicString(m.group(2)) + text = ' #%s' % idx + + root = flask.request.url_root + url = flask.request.url + user = None + if 'fork/' in flask.request.url: + user, repo = url.split('fork/')[1].split('/', 2)[:2] + else: + repo = url.split(root)[1].split('/', 1)[0] + + if not _issue_exists(user, repo, idx): + return text + + return _issue_anchor_tag(user, repo, idx, text) + + +class PagureExtension(markdown.extensions.Extension): + + def extendMarkdown(self, md, md_globals): + # First, make it so that bare links get automatically linkified. + markdown.inlinepatterns.AUTOLINK_RE = '(%s)' % '|'.join([ + r'<(?:f|ht)tps?://[^>]*>', + r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]', + r'\bwww\.[^)<>\s]+[^.,)<>\s]', + r'[^(<\s]+\.(?:com|net|org)\b', + ]) + + md.inlinePatterns['mention'] = MentionPattern(MENTION_RE) + md.inlinePatterns['explicit_fork_issue'] = \ + ExplicitForkIssuePattern(EXPLICIT_FORK_ISSUE_RE) + md.inlinePatterns['explicit_main_issue'] = \ + ExplicitMainIssuePattern(EXPLICIT_MAIN_ISSUE_RE) + md.inlinePatterns['implicit_issue'] = \ + ImplicitIssuePattern(IMPLICIT_ISSUE_RE) + + md.registerExtension(self) + + +def makeExtension(*arg, **kwargs): + return PagureExtension(**kwargs) def _issue_exists(user, repo, idx): From 88bce5fad334d682aa8621eead68eb924ed3c4c3 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Aug 06 2015 16:11:21 +0000 Subject: [PATCH 2/3] Instanciate the markdown converter with our extension, on demand --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 64d229c..c528997 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -2332,9 +2332,10 @@ def add_token_to_user(session, project, acls, username): def text2markdown(text, extended=True): """ Simple text to html converter using the markdown library. """ + md = markdown.Markdown() if extended: # Install our markdown modifications - pagure.pfmarkdown.inject() + md = markdown.Markdown(extensions=['pagure.pfmarkdown']) if text: # Hack to allow blockquotes to be marked by ~~~ @@ -2347,7 +2348,7 @@ def text2markdown(text, extended=True): if indent: line = ' %s' % line ntext.append(line) - return clean_input(markdown.markdown('\n'.join(ntext))) + return clean_input(md.convert('\n'.join(ntext))) return '' From 774fbae45b5f8d4f0f67a37e546285350e74eb35 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Aug 06 2015 16:16:35 +0000 Subject: [PATCH 3/3] Adjust the @user regex to work when @user is the first text in a comment --- diff --git a/pagure/pfmarkdown.py b/pagure/pfmarkdown.py index ec79c67..eaaca3d 100644 --- a/pagure/pfmarkdown.py +++ b/pagure/pfmarkdown.py @@ -30,7 +30,7 @@ import pagure import pagure.lib -MENTION_RE = r'[^|\w]@(\w+)' +MENTION_RE = r'^(.*?)@(\w+)' EXPLICIT_FORK_ISSUE_RE = r'(\w+)/(\w+)#([0-9]+)' EXPLICIT_MAIN_ISSUE_RE = r'[^|\w](?