|
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
|