2020-06-11 00:54:34 +02:00
import subprocess
2021-12-02 17:10:42 +01:00
from typing import List , Optional
2017-02-15 05:39:42 +01:00
2021-12-02 17:38:40 +01:00
from zulint . printer import BOLDRED , CYAN , ENDC , GREEN
2019-07-18 02:41:47 +02:00
2021-12-02 17:10:42 +01:00
from . template_parser import Token
2019-07-18 02:41:47 +02:00
2017-02-15 05:39:42 +01:00
2021-12-02 17:10:42 +01:00
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 = " "
2021-11-20 13:25:41 +01:00
2021-12-02 17:10:42 +01:00
for i , token in enumerate ( tokens [ : - 1 ] ) :
next_token = tokens [ i + 1 ]
2021-11-20 13:25:41 +01:00
2021-12-02 17:10:42 +01:00
if token . kind == " indent " :
next_token . indent = token . s
token . new_s = " "
2021-11-20 13:25:41 +01:00
2021-12-02 17:10:42 +01:00
if token . kind == " newline " and next_token . kind != " indent " :
next_token . indent = " "
2021-11-20 13:25:41 +01:00
2021-12-02 17:10:42 +01:00
def token_allows_children_to_skip_indents ( token : Token ) - > bool :
2021-12-07 00:00:54 +01:00
# 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.
2021-12-04 15:39:38 +01:00
if token . tag == " each " :
return False
2021-12-02 17:10:42 +01:00
return token . kind in ( " django_start " , " handlebars_start " ) or token . tag == " a "
2021-11-20 13:25:41 +01:00
2021-12-02 17:10:42 +01:00
def adjust_block_indentation ( tokens : List [ Token ] , fn : str ) - > None :
start_token : Optional [ Token ] = 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 :
2023-01-18 02:59:37 +01:00
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
2021-12-02 17:10:42 +01:00
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
2017-02-23 18:12:52 +01:00
else :
2021-12-02 17:10:42 +01:00
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 :
for token in tokens :
if token . kind == " code " :
continue
if token . line_span == 1 or token . indent is None :
continue
2022-09-09 08:58:17 +02:00
if token . kind in ( " django_comment " , " handlebars_comment " , " html_comment " , " text " ) :
2021-12-02 17:10:42 +01:00
continue_indent = token . indent
else :
continue_indent = token . indent + " "
frags = token . new_s . split ( " \n " )
def fix ( frag : str ) - > str :
frag = frag . strip ( )
return continue_indent + frag if frag else " "
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 )
2017-03-12 22:24:26 +01:00
2021-12-02 17:38:40 +01:00
def numbered_lines ( s : str ) - > str :
return " " . join ( f " { i + 1 : >5 } { line } \n " for i , line in enumerate ( s . split ( " \n " ) ) )
2021-12-02 13:19:19 +01:00
def validate_indent_html ( fn : str , tokens : List [ Token ] , fix : bool ) - > bool :
2020-04-09 21:51:58 +02:00
with open ( fn ) as f :
2019-07-14 21:37:08 +02:00
html = f . read ( )
2021-12-02 17:10:42 +01:00
phtml = pretty_print_html ( tokens , fn )
2023-01-18 03:30:35 +01:00
if html . split ( " \n " ) != phtml . split ( " \n " ) :
2019-07-18 02:41:47 +02:00
if fix :
2021-11-24 15:28:06 +01:00
print ( GREEN + f " Automatically fixing indentation for { fn } " + ENDC )
2021-02-12 08:20:45 +01:00
with open ( fn , " w " ) as f :
2019-07-18 02:41:47 +02:00
f . write ( phtml )
2021-11-24 15:15:01 +01:00
# Since we successfully fixed the issues, we return True.
return True
2021-02-12 08:19:30 +01:00
print (
2021-12-02 17:38:40 +01:00
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 } :
""" ,
2021-02-12 08:19:30 +01:00
flush = True ,
)
2023-08-18 00:50:03 +02:00
subprocess . run ( [ " diff " , fn , " - " ] , input = phtml , text = True , check = False )
2021-12-02 17:38:40 +01:00
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 } .
"""
)
2021-11-24 15:15:01 +01:00
return False
return True