pagure/pagure/pfmarkdown.py

236 lines
7.2 KiB
Python

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
""" Pagure-flavored Markdown
Author: Ralph Bean <rbean@redhat.com>
Pierre-Yves Chibon <pingou@pingoured.fr>
"""
import re
import flask
import markdown.inlinepatterns
import markdown.util
import pagure
import pagure.lib
MENTION_RE = r'@(\w+)'
EXPLICIT_FORK_ISSUE_RE = r'(\w+)/(\w+)#([0-9]+)'
EXPLICIT_MAIN_ISSUE_RE = r'[^|\w](?<!\/)(\w+)#([0-9]+)'
IMPLICIT_ISSUE_RE = r'[^|\w](?<!\w)#([0-9]+)'
IMPLICIT_PR_RE = r'[^|\w](?<!\w)PR#([0-9]+)'
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)
issue = _issue_exists(user, repo, idx)
if not issue:
return text
return _obj_anchor_tag(user, repo, issue, 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)
issue = _issue_exists(None, repo, idx)
if not issue:
return text
return _obj_anchor_tag(None, repo, issue, 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
try:
root = flask.request.url_root
url = flask.request.url
except RuntimeError:
return text
repo = user = None
if flask.request.args.get('user'):
user = flask.request.args.get('user')
if flask.request.args.get('repo'):
repo = flask.request.args.get('repo')
if not user and not repo:
if 'fork/' in url:
user, repo = url.split('fork/')[1].split('/', 2)[:2]
else:
repo = url.split(root)[1].split('/', 1)[0]
issue = _issue_exists(user, repo, idx)
if issue:
return _obj_anchor_tag(user, repo, issue, text)
request = _pr_exists(user, repo, idx)
if request:
return _obj_anchor_tag(user, repo, request, text)
return text
class ImplicitPRPattern(markdown.inlinepatterns.Pattern):
""" Implicit pull-request pattern. """
def handleMatch(self, m):
""" When the pattern matches, update the text. """
idx = markdown.util.AtomicString(m.group(2))
text = ' PR#%s' % idx
try:
root = flask.request.url_root
url = flask.request.url
except RuntimeError:
return text
repo = user = None
if flask.request.args.get('user'):
user = flask.request.args.get('user')
if flask.request.args.get('repo'):
repo = flask.request.args.get('repo')
if not user and not repo:
if 'fork/' in url:
user, repo = url.split('fork/')[1].split('/', 2)[:2]
else:
repo = url.split(root)[1].split('/', 1)[0]
issue = _issue_exists(user, repo, idx)
if issue:
return _obj_anchor_tag(user, repo, issue, text)
request = _pr_exists(user, repo, idx)
if request:
return _obj_anchor_tag(user, repo, request, text)
return 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]',
])
md.inlinePatterns['mention'] = MentionPattern(MENTION_RE)
if pagure.APP.config.get('ENABLE_TICKETS', True):
md.inlinePatterns['implicit_pr'] = \
ImplicitPRPattern(IMPLICIT_PR_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):
""" Utility method checking if a given issue exists. """
repo_obj = pagure.lib.get_project(
pagure.SESSION, name=repo, user=user)
if not repo_obj:
return False
issue_obj = pagure.lib.search_issues(
pagure.SESSION, repo=repo_obj, issueid=idx)
if not issue_obj:
return False
return issue_obj
def _pr_exists(user, repo, idx):
""" Utility method checking if a given PR exists. """
repo_obj = pagure.lib.get_project(
pagure.SESSION, name=repo, user=user)
if not repo_obj:
return False
pr_obj = pagure.lib.search_pull_requests(
pagure.SESSION, project_id=repo_obj.id, requestid=idx)
if not pr_obj:
return False
return pr_obj
def _obj_anchor_tag(user, repo, obj, text):
""" Utility method generating the link to an issue or a PR. """
if obj.isa == 'issue':
url = flask.url_for(
'view_issue', username=user, repo=repo, issueid=obj.id)
else:
url = flask.url_for(
'request_pull', username=user, repo=repo, requestid=obj.id)
element = markdown.util.etree.Element("a")
element.set('href', url)
element.set('title', obj.title)
element.text = text
return element