Files
notifications-api/notifications_utils/markdown.py
Carlo Costino 99edc88197 Localize notification_utils to the API
This changeset pulls in all of the notification_utils code directly into the API and removes it as an external dependency.  We are doing this to cut down on operational maintenance of the project and will begin removing parts of it no longer needed for the API.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-05-16 10:17:45 -04:00

309 lines
8.5 KiB
Python

import re
from itertools import count
import mistune
from ordered_set import OrderedSet
from notifications_utils import MAGIC_SEQUENCE, magic_sequence_regex
from notifications_utils.formatters import create_sanitised_html_for_url
LINK_STYLE = "word-wrap: break-word; color: #1D70B8;"
mistune._block_quote_leading_pattern = re.compile(r"^ *\^ ?", flags=re.M)
mistune.BlockGrammar.block_quote = re.compile(r"^( *\^[^\n]+(\n[^\n]+)*\n*)+")
mistune.BlockGrammar.list_block = re.compile(
r"^( *)([•*-]|\d+\.)[\s\S]+?"
r"(?:"
r"\n+(?=\1?(?:[-*_] *){3,}(?:\n+|$))" # hrule
r"|\n+(?=%s)" # def links
r"|\n+(?=%s)" # def footnotes
r"|\n{2,}"
r"(?! )"
r"(?!\1(?:[•*-]|\d+\.) )\n*"
r"|"
r"\s*$)"
% (
mistune._pure_pattern(mistune.BlockGrammar.def_links),
mistune._pure_pattern(mistune.BlockGrammar.def_footnotes),
)
)
mistune.BlockGrammar.list_item = re.compile(
r"^(( *)(?:[•*-]|\d+\.)[^\n]*" r"(?:\n(?!\2(?:[•*-]|\d+\.))[^\n]*)*)", flags=re.M
)
mistune.BlockGrammar.list_bullet = re.compile(r"^ *(?:[•*-]|\d+\.)")
mistune.InlineGrammar.url = re.compile(r"""^(https?:\/\/[^\s<]+[^<.,:"')\]\s])""")
mistune.InlineLexer.default_rules = list(
OrderedSet(mistune.InlineLexer.default_rules)
- set(
(
"emphasis",
"double_emphasis",
"strikethrough",
"code",
)
)
)
mistune.InlineLexer.inline_html_rules = list(
set(mistune.InlineLexer.inline_html_rules)
- set(
(
"emphasis",
"double_emphasis",
"strikethrough",
"code",
)
)
)
class NotifyLetterMarkdownPreviewRenderer(mistune.Renderer):
# TODO if we start removing the dead code detected by
# the vulture tool (such as the parameter 'language' here)
# it will break all the tests. Need to do some massive
# cleanup apparently, although it's not clear why vulture
# only recently started detecting this.
def block_code(self, code, language=None): # noqa
return code
def block_quote(self, text):
return text
def header(self, text, level, raw=None): # noqa
if level == 1:
return super().header(text, 2)
return self.paragraph(text)
def hrule(self):
return '<div class="page-break">&nbsp;</div>'
def paragraph(self, text):
if text.strip():
return "<p>{}</p>".format(text)
return ""
def table(self, header, body):
return ""
def autolink(self, link, is_email=False):
return "<strong>{}</strong>".format(
link.replace("http://", "").replace("https://", "")
)
def image(self, src, title, alt_text): # noqa
return ""
def linebreak(self):
return "<br>"
def newline(self):
return self.linebreak()
def list_item(self, text):
return "<li>{}</li>\n".format(text.strip())
def link(self, link, title, content):
return "{}: {}".format(content, self.autolink(link))
def footnote_ref(self, key, index):
return ""
def footnote_item(self, key, text):
return text
def footnotes(self, text):
return text
class NotifyEmailMarkdownRenderer(NotifyLetterMarkdownPreviewRenderer):
def header(self, text, level, raw=None): # noqa
if level == 1:
return (
'<h2 style="Margin: 0 0 20px 0; padding: 0; '
'font-size: 27px; line-height: 35px; font-weight: bold; color: #0B0C0C;">'
"{}"
"</h2>"
).format(text)
return self.paragraph(text)
def hrule(self):
return '<hr style="border: 0; height: 1px; background: #B1B4B6; Margin: 30px 0 30px 0;">'
def linebreak(self):
return "<br />"
def list(self, body, ordered=True):
return (
(
'<table role="presentation" style="padding: 0 0 20px 0;">'
"<tr>"
'<td style="font-family: Helvetica, Arial, sans-serif;">'
'<ol style="Margin: 0 0 0 20px; padding: 0; list-style-type: decimal;">'
"{}"
"</ol>"
"</td>"
"</tr>"
"</table>"
).format(body)
if ordered
else (
'<table role="presentation" style="padding: 0 0 20px 0;">'
"<tr>"
'<td style="font-family: Helvetica, Arial, sans-serif;">'
'<ul style="Margin: 0 0 0 20px; padding: 0; list-style-type: disc;">'
"{}"
"</ul>"
"</td>"
"</tr>"
"</table>"
).format(body)
)
def list_item(self, text):
return (
'<li style="Margin: 5px 0 5px; padding: 0 0 0 5px; font-size: 19px;'
'line-height: 25px; color: #0B0C0C;">'
"{}"
"</li>"
).format(text.strip())
def paragraph(self, text):
if text.strip():
return (
'<p style="Margin: 0 0 20px 0; font-size: 19px; line-height: 25px; color: #0B0C0C;">{}</p>'
).format(text)
return ""
def block_quote(self, text):
return (
"<blockquote "
'style="Margin: 0 0 20px 0; border-left: 10px solid #B1B4B6;'
'padding: 15px 0 0.1px 15px; font-size: 19px; line-height: 25px;"'
">"
"{}"
"</blockquote>"
).format(text)
def link(self, link, title, content):
return ('<a style="{}"{}{}>{}</a>').format(
LINK_STYLE,
' href="{}"'.format(link),
' title="{}"'.format(title) if title else "",
content,
)
def autolink(self, link, is_email=False):
if is_email:
return link
return create_sanitised_html_for_url(link, style=LINK_STYLE)
class NotifyPlainTextEmailMarkdownRenderer(NotifyEmailMarkdownRenderer):
COLUMN_WIDTH = 65
def header(self, text, level, raw=None): # noqa
if level == 1:
return "".join(
(
self.linebreak() * 3,
text,
self.linebreak(),
"-" * self.COLUMN_WIDTH,
)
)
return self.paragraph(text)
def hrule(self):
return self.paragraph("=" * self.COLUMN_WIDTH)
def linebreak(self):
return "\n"
def list(self, body, ordered=True):
def _get_list_marker():
decimal = count(1)
return lambda _: "{}.".format(next(decimal)) if ordered else ""
return "".join(
(
self.linebreak(),
re.sub(
magic_sequence_regex,
_get_list_marker(),
body,
),
)
)
def list_item(self, text):
return "".join(
(
self.linebreak(),
MAGIC_SEQUENCE,
" ",
text.strip(),
)
)
def paragraph(self, text):
if text.strip():
return "".join(
(
self.linebreak() * 2,
text,
)
)
return ""
def block_quote(self, text):
return text
def link(self, link, title, content):
return "".join(
(
content,
" ({})".format(title) if title else "",
": ",
link,
)
)
def autolink(self, link, is_email=False): # noqa
return link
class NotifyEmailPreheaderMarkdownRenderer(NotifyPlainTextEmailMarkdownRenderer):
def header(self, text, level, raw=None): # noqa
return self.paragraph(text)
def hrule(self):
return ""
def link(self, link, title, content):
return "".join(
(
content,
" ({})".format(title) if title else "",
)
)
notify_email_markdown = mistune.Markdown(
renderer=NotifyEmailMarkdownRenderer(),
hard_wrap=True,
use_xhtml=False,
)
notify_plain_text_email_markdown = mistune.Markdown(
renderer=NotifyPlainTextEmailMarkdownRenderer(),
hard_wrap=True,
)
notify_email_preheader_markdown = mistune.Markdown(
renderer=NotifyEmailPreheaderMarkdownRenderer(),
hard_wrap=True,
)
notify_letter_preview_markdown = mistune.Markdown(
renderer=NotifyLetterMarkdownPreviewRenderer(),
hard_wrap=True,
use_xhtml=False,
)