mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			198 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import subprocess
 | 
						|
 | 
						|
from zulint.printer import BOLDRED, CYAN, ENDC, GREEN
 | 
						|
 | 
						|
from .template_parser import Token
 | 
						|
 | 
						|
 | 
						|
def shift_indents_to_the_next_tokens(tokens: list[Token]) -> None:
 | 
						|
    """
 | 
						|
    During the parsing/validation phase, it's useful to have separate
 | 
						|
    tokens for "indent" chunks, but during pretty printing, we like
 | 
						|
    to attach an `.indent` field to the substantive node, whether
 | 
						|
    it's an HTML tag or template directive or whatever.
 | 
						|
    """
 | 
						|
    tokens[0].indent = ""
 | 
						|
 | 
						|
    for i, token in enumerate(tokens[:-1]):
 | 
						|
        next_token = tokens[i + 1]
 | 
						|
 | 
						|
        if token.kind == "indent":
 | 
						|
            next_token.indent = token.s
 | 
						|
            token.new_s = ""
 | 
						|
 | 
						|
        if token.kind == "newline" and next_token.kind != "indent":
 | 
						|
            next_token.indent = ""
 | 
						|
 | 
						|
 | 
						|
def token_allows_children_to_skip_indents(token: Token) -> bool:
 | 
						|
    # To avoid excessive indentation in templates with other
 | 
						|
    # conditionals, we don't require extra indentation for template
 | 
						|
    # logic blocks don't contain further logic as direct children.
 | 
						|
 | 
						|
    # Each blocks are excluded from this rule, since we want loops to
 | 
						|
    # stand out.
 | 
						|
    if token.tag == "each":
 | 
						|
        return False
 | 
						|
 | 
						|
    return token.kind in ("django_start", "handlebars_start") or token.tag == "a"
 | 
						|
 | 
						|
 | 
						|
def adjust_block_indentation(tokens: list[Token], fn: str) -> None:
 | 
						|
    start_token: Token | None = None
 | 
						|
 | 
						|
    for token in tokens:
 | 
						|
        if token.kind in ("indent", "whitespace", "newline"):
 | 
						|
            continue
 | 
						|
 | 
						|
        if token.tag in ("code", "pre"):
 | 
						|
            continue
 | 
						|
 | 
						|
        # print(token.line, repr(start_token.indent) if start_token else "?", repr(token.indent), token.s, token.end_token and "start", token.start_token and "end")
 | 
						|
 | 
						|
        if token.tag == "else":
 | 
						|
            assert token.start_token
 | 
						|
            if token.indent is not None:
 | 
						|
                token.indent = token.start_token.indent
 | 
						|
            continue
 | 
						|
 | 
						|
        if start_token and token.indent is not None:
 | 
						|
            if (
 | 
						|
                not start_token.indent_is_final
 | 
						|
                and token.indent == start_token.orig_indent
 | 
						|
                and token_allows_children_to_skip_indents(start_token)
 | 
						|
            ):
 | 
						|
                start_token.child_indent = start_token.indent
 | 
						|
            start_token.indent_is_final = True
 | 
						|
 | 
						|
        # Detect start token by its having a end token
 | 
						|
        if token.end_token:
 | 
						|
            if token.indent is not None:
 | 
						|
                token.orig_indent = token.indent
 | 
						|
                if start_token:
 | 
						|
                    assert start_token.child_indent is not None
 | 
						|
                    token.indent = start_token.child_indent
 | 
						|
                else:
 | 
						|
                    token.indent = ""
 | 
						|
                token.child_indent = token.indent + "    "
 | 
						|
            token.parent_token = start_token
 | 
						|
            start_token = token
 | 
						|
            continue
 | 
						|
 | 
						|
        # Detect end token by its having a start token
 | 
						|
        if token.start_token:
 | 
						|
            if start_token != token.start_token:
 | 
						|
                raise AssertionError(
 | 
						|
                    f"""
 | 
						|
                    {token.kind} was unexpected in {token.s}
 | 
						|
                    in row {token.line} of {fn}
 | 
						|
                    """
 | 
						|
                )
 | 
						|
 | 
						|
            if token.indent is not None:
 | 
						|
                token.indent = start_token.indent
 | 
						|
            start_token = start_token.parent_token
 | 
						|
            continue
 | 
						|
 | 
						|
        if token.indent is None:
 | 
						|
            continue
 | 
						|
 | 
						|
        if start_token is None:
 | 
						|
            token.indent = ""
 | 
						|
            continue
 | 
						|
 | 
						|
        if start_token.child_indent is not None:
 | 
						|
            token.indent = start_token.child_indent
 | 
						|
 | 
						|
 | 
						|
def fix_indents_for_multi_line_tags(tokens: list[Token]) -> None:
 | 
						|
    def fix(frag: str) -> str:
 | 
						|
        frag = frag.strip()
 | 
						|
        return continue_indent + frag if frag else ""
 | 
						|
 | 
						|
    for token in tokens:
 | 
						|
        if token.kind == "code":
 | 
						|
            continue
 | 
						|
 | 
						|
        if token.line_span == 1 or token.indent is None:
 | 
						|
            continue
 | 
						|
 | 
						|
        if token.kind in ("django_comment", "handlebars_comment", "html_comment", "text"):
 | 
						|
            continue_indent = token.indent
 | 
						|
        else:
 | 
						|
            continue_indent = token.indent + "  "
 | 
						|
 | 
						|
        frags = token.new_s.split("\n")
 | 
						|
 | 
						|
        token.new_s = frags[0] + "\n" + "\n".join(fix(frag) for frag in frags[1:])
 | 
						|
 | 
						|
 | 
						|
def apply_token_indents(tokens: list[Token]) -> None:
 | 
						|
    for token in tokens:
 | 
						|
        if token.indent:
 | 
						|
            token.new_s = token.indent + token.new_s
 | 
						|
 | 
						|
 | 
						|
def pretty_print_html(tokens: list[Token], fn: str) -> str:
 | 
						|
    for token in tokens:
 | 
						|
        token.new_s = token.s
 | 
						|
 | 
						|
    shift_indents_to_the_next_tokens(tokens)
 | 
						|
    adjust_block_indentation(tokens, fn)
 | 
						|
    fix_indents_for_multi_line_tags(tokens)
 | 
						|
    apply_token_indents(tokens)
 | 
						|
 | 
						|
    return "".join(token.new_s for token in tokens)
 | 
						|
 | 
						|
 | 
						|
def numbered_lines(s: str) -> str:
 | 
						|
    return "".join(f"{i + 1: >5} {line}\n" for i, line in enumerate(s.split("\n")))
 | 
						|
 | 
						|
 | 
						|
def validate_indent_html(fn: str, tokens: list[Token], fix: bool) -> bool:
 | 
						|
    with open(fn) as f:
 | 
						|
        html = f.read()
 | 
						|
    phtml = pretty_print_html(tokens, fn)
 | 
						|
    if html.split("\n") != phtml.split("\n"):
 | 
						|
        if fix:
 | 
						|
            print(GREEN + f"Automatically fixing indentation for {fn}" + ENDC)
 | 
						|
            with open(fn, "w") as f:
 | 
						|
                f.write(phtml)
 | 
						|
            # Since we successfully fixed the issues, we return True.
 | 
						|
            return True
 | 
						|
        print(
 | 
						|
            f"""
 | 
						|
{BOLDRED}PROBLEM{ENDC}: formatting errors in {fn}
 | 
						|
 | 
						|
Here is how we would like you to format
 | 
						|
{CYAN}{fn}{ENDC}:
 | 
						|
---
 | 
						|
{numbered_lines(phtml)}
 | 
						|
---
 | 
						|
 | 
						|
Here is the diff that you should either execute in your editor
 | 
						|
or apply automatically with the --fix option.
 | 
						|
 | 
						|
({CYAN}Scroll up{ENDC} to see how we would like the file formatted.)
 | 
						|
 | 
						|
Proposed {BOLDRED}diff{ENDC} for {CYAN}{fn}{ENDC}:
 | 
						|
            """,
 | 
						|
            flush=True,
 | 
						|
        )
 | 
						|
        subprocess.run(["diff", fn, "-"], input=phtml, text=True, check=False)
 | 
						|
        print(
 | 
						|
            f"""
 | 
						|
---
 | 
						|
 | 
						|
{BOLDRED}PROBLEM!!!{ENDC}
 | 
						|
 | 
						|
    You have formatting errors in {CYAN}{fn}{ENDC}
 | 
						|
    (Usually these messages are related to indentation.)
 | 
						|
 | 
						|
This problem can be fixed with the {CYAN}`--fix`{ENDC} option.
 | 
						|
Scroll up for more details about {BOLDRED}what you need to fix ^^^{ENDC}.
 | 
						|
            """
 | 
						|
        )
 | 
						|
        return False
 | 
						|
    return True
 |