vextoly/gitman

Fork of patx/gitman.

"""Recognize profile mentions using Markdown's code/link/HTML boundaries."""
from xml.etree.ElementTree import Element

import markdown
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor


MENTION_PATTERN = r'(?<![\w@/\\])@([A-Za-z0-9][A-Za-z0-9._-]+)(?![\[email protected]])'


class MentionProcessor(InlineProcessor):
    ANCESTOR_EXCLUDES = ('a', 'code', 'pre')

    def __init__(self, md, resolve):
        super().__init__(MENTION_PATTERN, md)
        self.resolve = resolve

    def handleMatch(self, match, data):
        # A sentence's trailing full stop is punctuation, not part of a handle.
        name = match.group(1).rstrip('.')
        username = self.resolve(name.lower()) if 2 <= len(name) <= 63 else None
        if not username:
            return None, None, None
        end = match.end(0) - (len(match.group(1)) - len(name))
        link = Element('a', {'href': '/' + username})
        link.text = data[match.start(0):end]
        return link, match.start(0), end


class MentionExtension(Extension):
    def __init__(self, resolve):
        self.resolve = resolve
        super().__init__()

    def extendMarkdown(self, md):
        # Run after code, escaping, raw HTML and ordinary Markdown links.
        md.inlinePatterns.register(MentionProcessor(md, self.resolve), 'mentions', 75)


def text_mentions_user(text, username):
    if not text or not username or '@' + username.lower() not in text.lower():
        return False
    found = False

    def resolve(name):
        nonlocal found
        if name == username.lower():
            found = True
            return name
        return None

    markdown.markdown(text, extensions=['extra', 'sane_lists', MentionExtension(resolve)])
    return found