Files
zulip/scripts/setup/inline_email_css.py
Anders Kaseorg 365fe0b3d5 python: Sort imports with isort.
Fixes #2665.

Regenerated by tabbott with `lint --fix` after a rebase and change in
parameters.

Note from tabbott: In a few cases, this converts technical debt in the
form of unsorted imports into different technical debt in the form of
our largest files having very long, ugly import sequences at the
start.  I expect this change will increase pressure for us to split
those files, which isn't a bad thing.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-11 16:45:32 -07:00

95 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
import os
from typing import Set
from cssutils import profile
from cssutils.profiles import Profiles, macros, properties
from premailer import Premailer
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
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")
def configure_cssutils() -> None:
# 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'
properties[Profiles.CSS_LEVEL_2]['pointer-events'] = (r'auto|none|visiblePainted|'
r'visibleFill|visibleStroke|'
r'visible|painted|fill|stroke|all|inherit')
profile.addProfiles([(Profiles.CSS_LEVEL_2, properties[Profiles.CSS_LEVEL_2],
macros[Profiles.CSS_LEVEL_2])])
configure_cssutils()
def inline_template(template_source_name: str) -> None:
template_name = template_source_name.split('.source.html')[0]
template_path = os.path.join(EMAIL_TEMPLATES_PATH, template_source_name)
compiled_template_path = os.path.join(os.path.dirname(template_path), "compiled",
os.path.basename(template_name) + ".html")
os.makedirs(os.path.dirname(compiled_template_path), exist_ok=True)
with open(template_path) as template_source_file:
template_str = template_source_file.read()
output = Premailer(template_str,
external_styles=[CSS_SOURCE_PATH]).transform()
output = escape_jinja2_characters(output)
# 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.
if template_name != 'email_base_default':
output = strip_unnecesary_tags(output)
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
with open(compiled_template_path, 'w') as final_template_file:
final_template_file.write(output)
def escape_jinja2_characters(text: str) -> str:
escaped_jinja2_characters = [('%7B%7B%20', '{{ '), ('%20%7D%7D', ' }}'), ('&gt;', '>')]
for escaped, original in escaped_jinja2_characters:
text = text.replace(escaped, original)
return text
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(f"Template does not have {start_block} or {end_block}")
def get_all_templates_from_directory(directory: str) -> Set[str]:
result = set()
for f in os.listdir(directory):
if f.endswith('.source.html'):
result.add(f)
return result
if __name__ == "__main__":
templates_to_inline = get_all_templates_from_directory(EMAIL_TEMPLATES_PATH)
for template_source_name in templates_to_inline:
inline_template(template_source_name)