# 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 Pierre-Yves Chibon """ from __future__ import unicode_literals, absolute_import import flask import markdown.inlinepatterns import markdown.preprocessors import markdown.util import pygit2 import re import six import pagure.lib.query from pagure.config import config as pagure_config try: from markdown.inlinepatterns import ImagePattern as ImagePattern MK_VERSION = 2 except ImportError: from markdown.inlinepatterns import ImageInlineProcessor as ImagePattern MK_VERSION = 3 # the (?` EXPLICIT_LINK_RE = ( r"(?[0-9]+)" ) COMMIT_LINK_RE = ( r"(?[\w]{40})" ) # PREPROCIMPLLINK is used by ImplicitIssuePreprocessor to replace the # '#' when a line starts with an implicit issue link, to prevent # markdown parsing it as a header; we have to handle it here IMPLICIT_ISSUE_RE = r"(?`). """ def handleMatch(self, m): """ When the pattern matches, update the text. :arg m: the matched object """ url = m.group(2) if url.startswith("<"): url = url[1:] if url.endswith(">"): url = url[:-1] el = markdown.util.etree.Element("a") el.set("href", self.unescape(url)) el.text = markdown.util.AtomicString(url) return el class ImagePatternLazyLoad(ImagePattern): """ Customize the image element matched for lazyloading. """ def handleMatch(self, m, *args): out = super(ImagePatternLazyLoad, self).handleMatch(m, *args) if MK_VERSION == 3: el = out[0] else: el = out # Add a noscript tag with the untouched img tag noscript = markdown.util.etree.Element("noscript") noscript.append(el) # Modify the origina img tag img = markdown.util.etree.Element("img") img.set("data-src", el.get("src")) img.set("src", "") img.set("alt", el.get("alt")) img.set("class", "lazyload") # Create a global span in which we add both the new img tag and the # noscript one outel = markdown.util.etree.Element("span") outel.append(img) outel.append(noscript) output = outel if MK_VERSION == 3: output = (outel, out[1], out[2]) return output class PagureExtension(markdown.extensions.Extension): def extendMarkdown(self, md, md_globals): # First, make it so that bare links get automatically linkified. AUTOLINK_RE = "(%s)" % "|".join( [ r"<((?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^>]*)>", r"\b(?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^)<>\s]+[^.,)<>\s]", r"<(Ii][Rr][Cc][Ss]?://[^>]*)>", r"\b[Ii][Rr][Cc][Ss]?://[^)<>\s]+[^.,)<>\s]", ] ) markdown.inlinepatterns.AUTOLINK_RE = AUTOLINK_RE md.preprocessors["implicit_issue"] = ImplicitIssuePreprocessor() md.inlinePatterns["mention"] = MentionPattern(MENTION_RE) # Customize the image linking to support lazy loading md.inlinePatterns["image_link"] = ImagePatternLazyLoad( markdown.inlinepatterns.IMAGE_LINK_RE, md ) md.inlinePatterns["implicit_commit"] = ImplicitCommitPattern( IMPLICIT_COMMIT_RE ) md.inlinePatterns["commit_links"] = CommitLinkPattern(COMMIT_LINK_RE) md.inlinePatterns["autolink"] = AutolinkPattern2(AUTOLINK_RE, md) if pagure_config.get("ENABLE_TICKETS", True): md.inlinePatterns["implicit_pr"] = ImplicitPRPattern( IMPLICIT_PR_RE ) md.inlinePatterns["explicit_fork_issue"] = ExplicitLinkPattern( EXPLICIT_LINK_RE ) md.inlinePatterns["implicit_issue"] = ImplicitIssuePattern( IMPLICIT_ISSUE_RE ) md.inlinePatterns["striked"] = StrikeThroughPattern(STRIKE_THROUGH_RE) md.registerExtension(self) def makeExtension(*arg, **kwargs): return PagureExtension(**kwargs) def _issue_exists(user, namespace, repo, idx): """ Utility method checking if a given issue exists. """ repo_obj = pagure.lib.query.get_authorized_project( flask.g.session, project_name=repo, user=user, namespace=namespace ) if not repo_obj: return False issue_obj = pagure.lib.query.search_issues( flask.g.session, repo=repo_obj, issueid=idx ) if not issue_obj: return False return issue_obj def _pr_exists(user, namespace, repo, idx): """ Utility method checking if a given PR exists. """ repo_obj = pagure.lib.query.get_authorized_project( flask.g.session, project_name=repo, user=user, namespace=namespace ) if not repo_obj: return False pr_obj = pagure.lib.query.search_pull_requests( flask.g.session, project_id=repo_obj.id, requestid=idx ) if not pr_obj: return False return pr_obj def _commit_exists(user, namespace, repo, githash): """ Utility method checking if a given commit exists. """ repo_obj = pagure.lib.query.get_authorized_project( flask.g.session, project_name=repo, user=user, namespace=namespace ) if not repo_obj: return False reponame = pagure.utils.get_repo_path(repo_obj) git_repo = pygit2.Repository(reponame) return githash in git_repo def _obj_anchor_tag(user, namespace, repo, obj, text): """ Utility method generating the link to an issue or a PR. :return: An element tree containing the href to the issue or PR :rtype: xml.etree.ElementTree.Element """ if isinstance(obj, six.string_types): url = flask.url_for( "ui_ns.view_commit", username=user, namespace=namespace, repo=repo, commitid=obj, ) title = "Commit %s" % obj elif obj.isa == "issue": url = flask.url_for( "ui_ns.view_issue", username=user, namespace=namespace, repo=repo, issueid=obj.id, ) if obj.private: title = "Private issue" else: if obj.status: title = "[%s] %s" % (obj.status, obj.title) else: title = obj.title else: url = flask.url_for( "ui_ns.request_pull", username=user, namespace=namespace, repo=repo, requestid=obj.id, ) if obj.status: title = "[%s] %s" % (obj.status, obj.title) else: title = obj.title element = markdown.util.etree.Element("a") element.set("href", url) element.set("title", title) element.text = text return element def _get_ns_repo_user(): """ Return the namespace, repo, user corresponding to the given request :return: A tuple of three string corresponding to namespace, repo, user :rtype: tuple(str, str, str) """ root = flask.request.url_root url = flask.request.url user = flask.request.args.get("user") namespace = flask.request.args.get("namespace") repo = flask.request.args.get("repo") if not user and not repo: if "fork/" in url: user, ext = url.split("fork/")[1].split("/", 1) else: ext = url.split(root)[1] if ext.count("/") >= 3: namespace, repo = ext.split("/", 2)[:2] else: repo = ext.split("/", 1)[0] return (namespace, repo, user)