#!/usr/bin/env python3 import argparse import logging import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) # check for the venv from tools.lib import sanity_check sanity_check.check_venv(__file__) from typing import Dict, Iterable, List from zulint import lister from tools.lib.html_branches import build_id_dict from tools.lib.pretty_print import validate_indent_html from tools.lib.template_parser import validate EXCLUDED_FILES = [ ## Test data Files for testing modules in tests "tools/tests/test_template_data", # Our parser doesn't handle the way its conditionals are layered "templates/zerver/emails/missed_message.source.html", # Previously unchecked and our parser doesn't like its indentation "static/assets/icons/template.hbs", # The parser does not like the indentation of custom ReadTheDocs templates "docs/_templates/layout.html", ] def check_our_files(modified_only: bool, all_dups: bool, fix: bool, targets: List[str]) -> None: by_lang = lister.list_files( targets=targets, modified_only=modified_only, ftypes=["hbs", "html"], group_by_ftype=True, exclude=EXCLUDED_FILES, ) check_handlebar_templates(by_lang["hbs"], fix) check_html_templates(by_lang["html"], all_dups, fix) def check_html_templates(templates: Iterable[str], all_dups: bool, fix: bool) -> None: # Our files with .html extensions are usually for Django, but we also # have a few static .html files. logging.basicConfig(format="%(levelname)s:%(message)s") templates = sorted(fn for fn in templates) # Use of lodash templates <%= %>. if "templates/zerver/team.html" in templates: templates.remove("templates/zerver/team.html") def check_for_duplicate_ids(templates: List[str]) -> Dict[str, List[str]]: template_id_dict = build_id_dict(templates) # TODO: Clean up these cases of duplicate ids in the code IGNORE_IDS = [ "api-example-tabs", "errors", "error-message-box", "email", "messages", "registration", "pw_strength", "id_password", "top_navbar", "id_email", "id_terms", "logout_form", "send_confirm", "register", "footer", "charged_amount", "change-plan-status", # Temporary while we have searchbox forked "search_exit", "search_query", "message_view_header", "search_arrows", "searchbox_form", "searchbox", ] bad_ids_dict = { ids: fns for ids, fns in template_id_dict.items() if (ids not in IGNORE_IDS) and len(fns) > 1 } if all_dups: ignorable_ids_dict = { ids: fns for ids, fns in template_id_dict.items() if ids in IGNORE_IDS and len(fns) > 1 } for ids, fns in ignorable_ids_dict.items(): logging.warning( "Duplicate ID(s) detected :Id '" + ids + "' present at following files:" ) for fn in fns: print(fn) for ids, fns in bad_ids_dict.items(): logging.error("Duplicate ID(s) detected :Id '" + ids + "' present at following files:") for fn in fns: print(fn) return bad_ids_dict bad_ids_list = list(check_for_duplicate_ids(templates).keys()) if bad_ids_list: print("Exiting--please clean up all duplicates before running this again.") sys.exit(1) for fn in templates: validate(fn) for fn in templates: if not validate_indent_html(fn, fix): sys.exit(1) def check_handlebar_templates(templates: Iterable[str], fix: bool) -> None: # Check all our handlebars templates. templates = [fn for fn in templates if fn.endswith(".hbs")] for fn in templates: validate(fn) for fn in templates: if not validate_indent_html(fn, fix): sys.exit(1) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-m", "--modified", action="store_true", help="only check modified files") parser.add_argument( "--all-dups", action="store_true", help="Run lint tool to detect duplicate ids on ignored files as well", ) parser.add_argument( "--fix", action="store_true", help="Automatically fix indentation problems." ) parser.add_argument("targets", nargs=argparse.REMAINDER) args = parser.parse_args() check_our_files(args.modified, args.all_dups, args.fix, args.targets)