Blame pagure/pfmarkdown.py

Ralph Bean ad48e3
# This program is free software; you can redistribute it and/or
Ralph Bean ad48e3
# modify it under the terms of the GNU General Public License
Ralph Bean ad48e3
# as published by the Free Software Foundation; either version 2
Ralph Bean ad48e3
# of the License, or (at your option) any later version.
Ralph Bean ad48e3
#
Ralph Bean ad48e3
# This program is distributed in the hope that it will be useful,
Ralph Bean ad48e3
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Ralph Bean ad48e3
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Ralph Bean ad48e3
# GNU General Public License for more details.
Ralph Bean ad48e3
#
Ralph Bean ad48e3
# You should have received a copy of the GNU General Public License
Ralph Bean ad48e3
# along with this program; if not, write to the Free Software
Ralph Bean ad48e3
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
Ralph Bean ad48e3
# USA.
Ralph Bean ad48e3
Ralph Bean ad48e3
""" Pagure-flavored Markdown
Ralph Bean ad48e3
Ralph Bean ad48e3
Author: Ralph Bean <rbean@redhat.com></rbean@redhat.com>
Pierre-Yves Chibon 721198
        Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Ralph Bean ad48e3
"""
Ralph Bean ad48e3
Pierre-Yves Chibon 721198
import re
Pierre-Yves Chibon 721198
Ralph Bean ad48e3
import flask
Ralph Bean ad48e3
Ralph Bean ad48e3
import markdown.inlinepatterns
Ralph Bean ad48e3
import markdown.util
Ralph Bean ad48e3
Ralph Bean 6ac06e
import pagure
Ralph Bean 6ac06e
import pagure.lib
Ralph Bean 6ac06e
Ralph Bean ad48e3
Pierre-Yves Chibon 98e061
MENTION_RE = r'@(\w+)'
Pierre-Yves Chibon 55850f
EXPLICIT_LINK_RE = r'(?
Pierre-Yves Chibon 55850f
'(fork[s]?/)?'\  # See if there is a `forks/` at the start
Pierre-Yves Chibon 55850f
'([a-zA-Z0-9_-]*?/)?'\ # See if we have a `user/`
Pierre-Yves Chibon 55850f
'([a-zA-Z0-9_-]*?/)?'\  # See if we have a `namespace/`
Pierre-Yves Chibon 55850f
'([a-zA-Z0-9_-]+)'\  # Get the last part `project`
Pierre-Yves Chibon 55850f
'#(?P<id>[0-9]+)'  # Get the identifier `#<id>`</id></id>
Pierre-Yves Chibon b8c7ae
IMPLICIT_ISSUE_RE = r'[^|\w](?
Pierre-Yves Chibon 1a226f
IMPLICIT_PR_RE = r'[^|\w](?
Pierre-Yves Chibon e7a276
STRIKE_THROUGH_RE = r'~~(\w+)~~'
Pierre-Yves Chibon b8c7ae
Pierre-Yves Chibon b8c7ae
Pierre-Yves Chibon 721198
class MentionPattern(markdown.inlinepatterns.Pattern):
Pierre-Yves Chibon 721198
    """ @user pattern class. """
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
    def handleMatch(self, m):
Pierre-Yves Chibon 721198
        """ When the pattern matches, update the text. """
Pierre-Yves Chibon 98e061
        name = markdown.util.AtomicString(m.group(2))
Pierre-Yves Chibon 721198
        text = ' @%s' % name
Pierre-Yves Chibon 721198
        user = pagure.lib.search_user(pagure.SESSION, username=name)
Pierre-Yves Chibon 721198
        if not user:
Pierre-Yves Chibon 721198
            return text
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
        element = markdown.util.etree.Element("a")
Pierre-Yves Chibon 477d8f
        base_url = pagure.APP.config['APP_URL']
Pierre-Yves Chibon 477d8f
        if base_url.endswith('/'):
Pierre-Yves Chibon 477d8f
            base_url = base_url[:-1]
Pierre-Yves Chibon 477d8f
        url = '%s/user/%s' % (base_url, user.username)
Pierre-Yves Chibon 721198
        element.set('href', url)
Pierre-Yves Chibon 721198
        element.text = text
Pierre-Yves Chibon 721198
        return element
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 10eda2
class ExplicitLinkPattern(markdown.inlinepatterns.Pattern):
Pierre-Yves Chibon 10eda2
    """ Explicit link pattern. """
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
    def handleMatch(self, m):
Pierre-Yves Chibon 721198
        """ When the pattern matches, update the text. """
Pierre-Yves Chibon 10eda2
        is_fork = m.group(2)
Pierre-Yves Chibon 10eda2
        user = m.group(3)
Pierre-Yves Chibon 10eda2
        namespace = m.group(4)
Pierre-Yves Chibon 10eda2
        repo = m.group(5)
Pierre-Yves Chibon 10eda2
        idx = m.group(6)
Pierre-Yves Chibon 10eda2
        text = '%s#%s' % (repo, idx)
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 10eda2
        if not is_fork and user:
Pierre-Yves Chibon 10eda2
            namespace = user
Pierre-Yves Chibon 10eda2
            user = None
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 10eda2
        if namespace:
Pierre-Yves Chibon 10eda2
            namespace = namespace.rstrip('/')
Pierre-Yves Chibon 10eda2
            text = '%s/%s' % (namespace, text)
Pierre-Yves Chibon 10eda2
        if user:
Pierre-Yves Chibon 10eda2
            user = user.rstrip('/')
Pierre-Yves Chibon 10eda2
            text = '%s/%s' % (user.rstrip('/'), text)
Pierre-Yves Chibon 721198
Pierre-Yves Chibon ac8d80
        try:
Pierre-Yves Chibon ac8d80
            idx = int(idx)
Pierre-Yves Chibon ac8d80
        except:
Pierre-Yves Chibon ac8d80
            return text
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 10eda2
        issue = _issue_exists(user, namespace, repo, idx)
Pierre-Yves Chibon 6c7986
        if issue:
Pierre-Yves Chibon 10eda2
            return _obj_anchor_tag(user, namespace, repo, issue, text)
Pierre-Yves Chibon 6c7986
Pierre-Yves Chibon 6c7986
Pierre-Yves Chibon 10eda2
        request = _pr_exists(user, namespace, repo, idx)
Pierre-Yves Chibon 6c7986
        if request:
Pierre-Yves Chibon 10eda2
            return _obj_anchor_tag(user, namespace, repo, request, text)
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 6c7986
        return text
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
class ImplicitIssuePattern(markdown.inlinepatterns.Pattern):
Pierre-Yves Chibon 721198
    """ Implicit issue pattern. """
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
    def handleMatch(self, m):
Pierre-Yves Chibon 721198
        """ When the pattern matches, update the text. """
Pierre-Yves Chibon 721198
        idx = markdown.util.AtomicString(m.group(2))
Pierre-Yves Chibon 721198
        text = ' #%s' % idx
Pierre-Yves Chibon ac8d80
        try:
Pierre-Yves Chibon ac8d80
            idx = int(idx)
Pierre-Yves Chibon ac8d80
        except:
Pierre-Yves Chibon ac8d80
            return text
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 89dcc4
        try:
Pierre-Yves Chibon a9d793
            root = flask.request.url_root
Pierre-Yves Chibon 89dcc4
            url = flask.request.url
Pierre-Yves Chibon 89dcc4
        except RuntimeError:
Pierre-Yves Chibon 89dcc4
            return text
Pierre-Yves Chibon 10eda2
        repo = namespace = user = None
Pierre-Yves Chibon aa74ce
Pierre-Yves Chibon aa74ce
        if flask.request.args.get('user'):
Pierre-Yves Chibon aa74ce
            user = flask.request.args.get('user')
Pierre-Yves Chibon 10eda2
        if flask.request.args.get('namespace'):
Pierre-Yves Chibon 10eda2
            namespace = flask.request.args.get('namespace')
Pierre-Yves Chibon aa74ce
        if flask.request.args.get('repo'):
Pierre-Yves Chibon aa74ce
            repo = flask.request.args.get('repo')
Pierre-Yves Chibon aa74ce
Pierre-Yves Chibon aa74ce
        if not user and not repo:
Pierre-Yves Chibon 89dcc4
            if 'fork/' in url:
Pierre-Yves Chibon aa74ce
                user, repo = url.split('fork/')[1].split('/', 2)[:2]
Pierre-Yves Chibon aa74ce
            else:
Pierre-Yves Chibon aa74ce
                repo = url.split(root)[1].split('/', 1)[0]
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 10eda2
        issue = _issue_exists(user, namespace, repo, idx)
Pierre-Yves Chibon 3549f5
        if issue:
Pierre-Yves Chibon 10eda2
            return _obj_anchor_tag(user, namespace, repo, issue, text)
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 10eda2
        request = _pr_exists(user, namespace, repo, idx)
Pierre-Yves Chibon 3549f5
        if request:
Pierre-Yves Chibon 10eda2
            return _obj_anchor_tag(user, namespace, repo, request, text)
Pierre-Yves Chibon 3549f5
Pierre-Yves Chibon 3549f5
        return text
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 1a226f
class ImplicitPRPattern(markdown.inlinepatterns.Pattern):
Pierre-Yves Chibon 1a226f
    """ Implicit pull-request pattern. """
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 1a226f
    def handleMatch(self, m):
Pierre-Yves Chibon 1a226f
        """ When the pattern matches, update the text. """
Pierre-Yves Chibon 1a226f
        idx = markdown.util.AtomicString(m.group(2))
Pierre-Yves Chibon 1a226f
        text = ' PR#%s' % idx
Pierre-Yves Chibon ac8d80
        try:
Pierre-Yves Chibon ac8d80
            idx = int(idx)
Pierre-Yves Chibon ac8d80
        except:
Pierre-Yves Chibon ac8d80
            return text
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 89dcc4
        try:
Pierre-Yves Chibon a9d793
            root = flask.request.url_root
Pierre-Yves Chibon 89dcc4
            url = flask.request.url
Pierre-Yves Chibon 89dcc4
        except RuntimeError:
Pierre-Yves Chibon 89dcc4
            return text
Pierre-Yves Chibon 10eda2
        repo = namespace = user = None
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 1a226f
        if flask.request.args.get('user'):
Pierre-Yves Chibon 1a226f
            user = flask.request.args.get('user')
Pierre-Yves Chibon 10eda2
        if flask.request.args.get('namespace'):
Pierre-Yves Chibon 10eda2
            namespace = flask.request.args.get('namespace')
Pierre-Yves Chibon 1a226f
        if flask.request.args.get('repo'):
Pierre-Yves Chibon 1a226f
            repo = flask.request.args.get('repo')
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 1a226f
        if not user and not repo:
Pierre-Yves Chibon 89dcc4
            if 'fork/' in url:
Pierre-Yves Chibon 1a226f
                user, repo = url.split('fork/')[1].split('/', 2)[:2]
Pierre-Yves Chibon 1a226f
            else:
Pierre-Yves Chibon 1a226f
                repo = url.split(root)[1].split('/', 1)[0]
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 10eda2
        issue = _issue_exists(user, namespace, repo, idx)
Pierre-Yves Chibon 1a226f
        if issue:
Pierre-Yves Chibon 10eda2
            return _obj_anchor_tag(user, namespace, repo, issue, text)
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 10eda2
        request = _pr_exists(user, namespace, repo, idx)
Pierre-Yves Chibon 1a226f
        if request:
Pierre-Yves Chibon 10eda2
            return _obj_anchor_tag(user, namespace, repo, request, text)
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 1a226f
        return text
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon 1a226f
Pierre-Yves Chibon e7a276
class StrikeThroughPattern(markdown.inlinepatterns.Pattern):
Pierre-Yves Chibon e7a276
    """ ~~striked~~ pattern class. """
Pierre-Yves Chibon e7a276
Pierre-Yves Chibon e7a276
    def handleMatch(self, m):
Pierre-Yves Chibon e7a276
        """ When the pattern matches, update the text. """
Pierre-Yves Chibon e7a276
        text = markdown.util.AtomicString(m.group(2))
Pierre-Yves Chibon e7a276
Pierre-Yves Chibon e7a276
        element = markdown.util.etree.Element("del")
Pierre-Yves Chibon e7a276
        element.text = text
Pierre-Yves Chibon e7a276
        return element
Pierre-Yves Chibon e7a276
Pierre-Yves Chibon e7a276
Pierre-Yves Chibon 721198
class PagureExtension(markdown.extensions.Extension):
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
    def extendMarkdown(self, md, md_globals):
Pierre-Yves Chibon 721198
        # First, make it so that bare links get automatically linkified.
Pierre-Yves Chibon 721198
        markdown.inlinepatterns.AUTOLINK_RE = '(%s)' % '|'.join([
Pierre-Yves Chibon 721198
            r'<(?:f|ht)tps?://[^>]*>',
Pierre-Yves Chibon 721198
            r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
Pierre-Yves Chibon 721198
        ])
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
        md.inlinePatterns['mention'] = MentionPattern(MENTION_RE)
Pierre-Yves Chibon a4bd4d
        if pagure.APP.config.get('ENABLE_TICKETS', True):
Pierre-Yves Chibon 1a226f
            md.inlinePatterns['implicit_pr'] = \
Pierre-Yves Chibon 1a226f
                ImplicitPRPattern(IMPLICIT_PR_RE)
Pierre-Yves Chibon dbf319
            md.inlinePatterns['explicit_fork_issue'] = \
Pierre-Yves Chibon 10eda2
                ExplicitLinkPattern(EXPLICIT_LINK_RE)
Pierre-Yves Chibon dbf319
            md.inlinePatterns['implicit_issue'] = \
Pierre-Yves Chibon dbf319
                ImplicitIssuePattern(IMPLICIT_ISSUE_RE)
Pierre-Yves Chibon 721198
Pierre-Yves Chibon e7a276
        md.inlinePatterns['striked'] = StrikeThroughPattern(
Pierre-Yves Chibon e7a276
            STRIKE_THROUGH_RE)
Pierre-Yves Chibon e7a276
Pierre-Yves Chibon 721198
        md.registerExtension(self)
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
Pierre-Yves Chibon 721198
def makeExtension(*arg, **kwargs):
Pierre-Yves Chibon 721198
    return PagureExtension(**kwargs)
Ralph Bean ad48e3
Ralph Bean ad48e3
Pierre-Yves Chibon 10eda2
def _issue_exists(user, namespace, repo, idx):
Pierre-Yves Chibon b8c7ae
    """ Utility method checking if a given issue exists. """
Ralph Bean debf63
    repo_obj = pagure.lib.get_project(
Pierre-Yves Chibon 10eda2
        pagure.SESSION, name=repo, user=user, namespace=namespace)
Ralph Bean debf63
    if not repo_obj:
Ralph Bean debf63
        return False
Ralph Bean debf63
Ralph Bean debf63
    issue_obj = pagure.lib.search_issues(
Ralph Bean debf63
        pagure.SESSION, repo=repo_obj, issueid=idx)
Ralph Bean debf63
    if not issue_obj:
Ralph Bean debf63
        return False
Ralph Bean debf63
Pierre-Yves Chibon 3549f5
    return issue_obj
Ralph Bean debf63
Ralph Bean debf63
Pierre-Yves Chibon 10eda2
def _pr_exists(user, namespace, repo, idx):
Pierre-Yves Chibon f4a3af
    """ Utility method checking if a given PR exists. """
Pierre-Yves Chibon 3549f5
    repo_obj = pagure.lib.get_project(
Pierre-Yves Chibon 10eda2
        pagure.SESSION, name=repo, user=user, namespace=namespace)
Pierre-Yves Chibon 3549f5
    if not repo_obj:
Pierre-Yves Chibon 3549f5
        return False
Pierre-Yves Chibon 3549f5
Pierre-Yves Chibon 3549f5
    pr_obj = pagure.lib.search_pull_requests(
Pierre-Yves Chibon 3549f5
        pagure.SESSION, project_id=repo_obj.id, requestid=idx)
Pierre-Yves Chibon 3549f5
    if not pr_obj:
Pierre-Yves Chibon 3549f5
        return False
Pierre-Yves Chibon 3549f5
Pierre-Yves Chibon 3549f5
    return pr_obj
Pierre-Yves Chibon 3549f5
Pierre-Yves Chibon 3549f5
Pierre-Yves Chibon 10eda2
def _obj_anchor_tag(user, namespace, repo, obj, text):
Pierre-Yves Chibon 4a091c
    """ Utility method generating the link to an issue or a PR. """
Pierre-Yves Chibon 3549f5
    if obj.isa == 'issue':
Pierre-Yves Chibon 3549f5
        url = flask.url_for(
Pierre-Yves Chibon 10eda2
            'view_issue', username=user, namespace=namespace, repo=repo,
Pierre-Yves Chibon 10eda2
             issueid=obj.id)
Pierre-Yves Chibon 3549f5
    else:
Pierre-Yves Chibon 3549f5
        url = flask.url_for(
Pierre-Yves Chibon 10eda2
            'request_pull', username=user, namespace=namespace, repo=repo,
Pierre-Yves Chibon 10eda2
            requestid=obj.id)
Pierre-Yves Chibon 3549f5
Pierre-Yves Chibon b8c7ae
    element = markdown.util.etree.Element("a")
Pierre-Yves Chibon b8c7ae
    element.set('href', url)
Pierre-Yves Chibon 3549f5
    element.set('title', obj.title)
Pierre-Yves Chibon b8c7ae
    element.text = text
Pierre-Yves Chibon b8c7ae
    return element