2017-09-16 14:30:57 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import os
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing import Set
|
2017-09-16 14:30:57 +02:00
|
|
|
|
|
|
|
from cssutils import profile
|
2020-06-11 00:54:34 +02:00
|
|
|
from cssutils.profiles import Profiles, macros, properties
|
|
|
|
from premailer import Premailer
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
|
|
|
|
EMAIL_TEMPLATES_PATH = os.path.join(ZULIP_PATH, "templates", "zerver", "emails")
|
2020-04-09 12:22:23 +02:00
|
|
|
CSS_SOURCE_PATH = os.path.join(EMAIL_TEMPLATES_PATH, "email.css")
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2021-02-12 08:19:30 +01: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.
|
2021-02-12 08:20:45 +01:00
|
|
|
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"
|
2021-02-12 08:19:30 +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-11 00:49:28 +02:00
|
|
|
configure_cssutils()
|
2017-09-16 14:30:57 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-04-11 00:52:52 +02:00
|
|
|
def inline_template(template_source_name: str) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
template_name = template_source_name.split(".source.html")[0]
|
2020-04-11 00:52:52 +02:00
|
|
|
template_path = os.path.join(EMAIL_TEMPLATES_PATH, template_source_name)
|
2021-02-12 08:19:30 +01:00
|
|
|
compiled_template_path = os.path.join(
|
|
|
|
os.path.dirname(template_path), "compiled", os.path.basename(template_name) + ".html"
|
|
|
|
)
|
2020-04-22 01:48:28 +02:00
|
|
|
|
|
|
|
os.makedirs(os.path.dirname(compiled_template_path), exist_ok=True)
|
2020-04-09 12:39:01 +02:00
|
|
|
|
|
|
|
with open(template_path) as template_source_file:
|
|
|
|
template_str = template_source_file.read()
|
2021-06-04 20:11:00 +02:00
|
|
|
output = Premailer(
|
|
|
|
template_str, external_styles=[CSS_SOURCE_PATH], allow_loading_external_files=True
|
|
|
|
).transform()
|
2020-04-09 12:39:01 +02:00
|
|
|
|
|
|
|
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.
|
2021-08-02 06:11:36 +02:00
|
|
|
if template_name not in ["email_base_default", "email_base_marketing", "macros"]:
|
2020-04-09 12:39:01 +02:00
|
|
|
output = strip_unnecesary_tags(output)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
if (
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver/emails/compiled/email_base_default.html" in output
|
2021-08-02 06:11:36 +02:00
|
|
|
or "zerver/emails/compiled/email_base_marketing.html" in output
|
2021-02-12 08:20:45 +01:00
|
|
|
or "zerver/emails/email_base_messages.html" in output
|
2021-02-12 08:19:30 +01:00
|
|
|
):
|
2021-02-12 08:20:45 +01:00
|
|
|
assert output.count("<html>") == 0
|
|
|
|
assert output.count("<body>") == 0
|
|
|
|
assert output.count("</html>") == 0
|
|
|
|
assert output.count("</body>") == 0
|
2020-04-09 12:39:01 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
with open(compiled_template_path, "w") as final_template_file:
|
2020-04-09 12:39:01 +02:00
|
|
|
final_template_file.write(output)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-04-09 12:12:09 +02:00
|
|
|
def escape_jinja2_characters(text: str) -> str:
|
2021-02-12 08:20:45 +01: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
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-04-09 12:16:53 +02:00
|
|
|
def strip_unnecesary_tags(text: str) -> str:
|
2021-02-12 08:20:45 +01:00
|
|
|
end_block = "</body>\n</html>"
|
|
|
|
start_block = "{% extends"
|
2020-04-09 12:16:53 +02:00
|
|
|
start = text.find(start_block)
|
|
|
|
end = text.rfind(end_block)
|
|
|
|
if start != -1 and end != -1:
|
|
|
|
text = text[start:end]
|
|
|
|
return text
|
|
|
|
else:
|
2020-06-10 06:41:04 +02:00
|
|
|
raise ValueError(f"Template does not have {start_block} or {end_block}")
|
2020-04-09 12:16:53 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-04-09 12:27:24 +02:00
|
|
|
def get_all_templates_from_directory(directory: str) -> Set[str]:
|
|
|
|
result = set()
|
|
|
|
for f in os.listdir(directory):
|
2021-02-12 08:20:45 +01:00
|
|
|
if f.endswith(".source.html"):
|
2020-04-11 00:52:52 +02:00
|
|
|
result.add(f)
|
2020-04-09 12:27:24 +02:00
|
|
|
return result
|
2020-04-06 02:48:43 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-04-09 12:27:24 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
templates_to_inline = get_all_templates_from_directory(EMAIL_TEMPLATES_PATH)
|
2020-04-06 02:48:43 +02:00
|
|
|
|
2020-04-11 00:52:52 +02:00
|
|
|
for template_source_name in templates_to_inline:
|
|
|
|
inline_template(template_source_name)
|