2017-09-16 14:30:57 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import os
|
|
|
|
|
|
|
|
from premailer import Premailer
|
|
|
|
from cssutils import profile
|
|
|
|
from cssutils.profiles import Profiles, properties, macros
|
|
|
|
|
2019-11-16 02:38:42 +01:00
|
|
|
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
|
2020-04-09 12:22:23 +02:00
|
|
|
EMAIL_TEMPLATES_PATH = os.path.join(ZULIP_PATH, 'templates', 'zerver', 'emails')
|
|
|
|
COMPILED_EMAIL_TEMPLATES_PATH = os.path.join(EMAIL_TEMPLATES_PATH, 'compiled')
|
|
|
|
CSS_SOURCE_PATH = os.path.join(EMAIL_TEMPLATES_PATH, "email.css")
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2020-04-06 02:48:43 +02:00
|
|
|
def configure_cssutils() -> None:
|
2017-09-16 14:30:57 +02:00
|
|
|
# These properties are not supported by cssutils by default and will
|
|
|
|
# result in warnings when premailer package is run.
|
|
|
|
properties[Profiles.CSS_LEVEL_2]['-ms-interpolation-mode'] = r'none|bicubic|nearest-neighbor'
|
|
|
|
properties[Profiles.CSS_LEVEL_2]['-ms-text-size-adjust'] = r'none|auto|{percentage}'
|
|
|
|
properties[Profiles.CSS_LEVEL_2]['mso-table-lspace'] = r'0|{num}(pt)'
|
|
|
|
properties[Profiles.CSS_LEVEL_2]['mso-table-rspace'] = r'0|{num}(pt)'
|
|
|
|
properties[Profiles.CSS_LEVEL_2]['-webkit-text-size-adjust'] = r'none|auto|{percentage}'
|
|
|
|
properties[Profiles.CSS_LEVEL_2]['mso-hide'] = 'all'
|
2017-11-08 03:47:28 +01:00
|
|
|
properties[Profiles.CSS_LEVEL_2]['pointer-events'] = (r'auto|none|visiblePainted|'
|
|
|
|
r'visibleFill|visibleStroke|'
|
2017-09-16 14:30:57 +02:00
|
|
|
r'visible|painted|fill|stroke|all|inherit')
|
|
|
|
|
2017-11-10 04:33:28 +01:00
|
|
|
profile.addProfiles([(Profiles.CSS_LEVEL_2, properties[Profiles.CSS_LEVEL_2],
|
|
|
|
macros[Profiles.CSS_LEVEL_2])])
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2020-04-09 12:12:09 +02:00
|
|
|
def escape_jinja2_characters(text: str) -> str:
|
2020-04-06 02:48:43 +02:00
|
|
|
escaped_jinja2_characters = [('%7B%7B%20', '{{ '), ('%20%7D%7D', ' }}'), ('>', '>')]
|
2020-04-09 12:12:09 +02:00
|
|
|
for escaped, original in escaped_jinja2_characters:
|
|
|
|
text = text.replace(escaped, original)
|
|
|
|
return text
|
2020-04-06 02:48:43 +02:00
|
|
|
|
2020-04-09 12:16:53 +02:00
|
|
|
def strip_unnecesary_tags(text: str) -> str:
|
|
|
|
end_block = '</body>\n</html>'
|
|
|
|
start_block = '{% extends'
|
|
|
|
start = text.find(start_block)
|
|
|
|
end = text.rfind(end_block)
|
|
|
|
if start != -1 and end != -1:
|
|
|
|
text = text[start:end]
|
|
|
|
return text
|
|
|
|
else:
|
|
|
|
raise ValueError("Template does not have %s or %s" % (start_block, end_block))
|
|
|
|
|
2020-04-09 12:12:09 +02:00
|
|
|
if __name__ == "__main__":
|
2020-04-06 02:48:43 +02:00
|
|
|
templates_to_inline = set()
|
2020-04-09 12:22:23 +02:00
|
|
|
for f in os.listdir(EMAIL_TEMPLATES_PATH):
|
2020-04-06 02:48:43 +02:00
|
|
|
if f.endswith('.source.html'):
|
|
|
|
templates_to_inline.add(f.split('.source.html')[0])
|
|
|
|
|
|
|
|
configure_cssutils()
|
|
|
|
|
2020-04-09 12:22:23 +02:00
|
|
|
os.makedirs(COMPILED_EMAIL_TEMPLATES_PATH, exist_ok=True)
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2020-04-09 12:22:23 +02:00
|
|
|
for template_name in templates_to_inline:
|
|
|
|
template_html_source = template_name + ".source.html"
|
|
|
|
compiled_template_path = os.path.join(COMPILED_EMAIL_TEMPLATES_PATH,
|
|
|
|
template_name + ".html")
|
|
|
|
template_path = os.path.join(EMAIL_TEMPLATES_PATH, template_html_source)
|
2017-09-26 01:41:15 +02:00
|
|
|
|
2020-04-09 12:22:23 +02:00
|
|
|
with open(template_path) as template_source_file:
|
2017-09-16 14:30:57 +02:00
|
|
|
template_str = template_source_file.read()
|
|
|
|
output = Premailer(template_str,
|
2020-04-09 12:22:23 +02:00
|
|
|
external_styles=[CSS_SOURCE_PATH]).transform()
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2020-04-09 12:12:09 +02:00
|
|
|
output = escape_jinja2_characters(output)
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2017-09-28 01:01:12 +02:00
|
|
|
# Premailer.transform will try to complete the DOM tree,
|
|
|
|
# adding html, head, and body tags if they aren't there.
|
|
|
|
# While this is correct for the email_base_default template,
|
|
|
|
# it is wrong for the other templates that extend this
|
|
|
|
# template, since we'll end up with 2 copipes of those tags.
|
|
|
|
# Thus, we strip this stuff out if the template extends
|
|
|
|
# another template.
|
2020-04-09 12:22:23 +02:00
|
|
|
if template_name != 'email_base_default':
|
2020-04-09 12:16:53 +02:00
|
|
|
output = strip_unnecesary_tags(output)
|
2019-01-11 23:12:47 +01:00
|
|
|
|
|
|
|
if ('zerver/emails/compiled/email_base_default.html' in output or
|
|
|
|
'zerver/emails/email_base_messages.html' in output):
|
|
|
|
assert output.count('<html>') == 0
|
|
|
|
assert output.count('<body>') == 0
|
|
|
|
assert output.count('</html>') == 0
|
|
|
|
assert output.count('</body>') == 0
|
2017-09-28 01:01:12 +02:00
|
|
|
|
2017-09-26 01:41:15 +02:00
|
|
|
with open(compiled_template_path, 'w') as final_template_file:
|
2017-09-16 14:30:57 +02:00
|
|
|
final_template_file.write(output)
|