Source

web2py-template / new_template.py

Full commit
# coding: utf-8

quotes = ("'", '"')
string_on_html = 'response.write("%s", escape=False)'
indentation = '    '
unindentation_words_before = ('else', 'elif', 'except')
unindentation_words_after = ('pass', 'break', 'continue', 'return')

    
def parse_string(text):
    return text.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')


def parse_line(line):
    if not line:
        return ''
    if line[0] == '=':
        return 'response.write(%s)' % line[1:]
    else:
        return line


def should_unindent_before(parsed_line):
    if parsed_line.strip():
        return parsed_line.split()[0].replace(':', '').strip() in unindentation_words_before
    else:
        return ''


def should_unindent_after(parsed_line):
    if parsed_line.strip():
        return parsed_line.split()[0].replace(':', '').strip() in unindentation_words_after
    else:
        return ''


def parse_python_code(python_code, indentation_level=0):
    response = []

    in_string = False
    last_quote = ''
    last_character = ''
    line = []
    open_something = '([{'
    close_something = ')]}'
    opened = 0

    for character in python_code:
        line.append(character)
        if not in_string:
            if character in open_something:
                opened += 1
            elif character in close_something:
                opened -= 1
        if character in quotes and (not last_quote or character == last_quote):
            last_quote = character
            in_string = not in_string
            if not in_string:
                last_quote = ''
        if character == '\n' and not in_string and last_character != '\\' and not opened:
            parsed_line = parse_line(''.join(line[:-1])).strip()
            if parsed_line:
                if should_unindent_before(parsed_line):
                    indentation_level -= 1
                if parsed_line and parsed_line != 'pass':
                    response.append(indentation_level * indentation + parsed_line)
                if should_unindent_after(parsed_line):
                    indentation_level -= 1
            line = []
            if last_character == ':':
                indentation_level += 1
        last_character = character

    if line:
        parsed_line = parse_line(''.join(line)).strip()
        if parsed_line:
            if should_unindent_before(parsed_line):
                indentation_level -= 1
            if parsed_line and parsed_line != 'pass':
                response.append(indentation_level * indentation + parsed_line)
            if should_unindent_after(parsed_line):
                indentation_level -= 1
            if last_character == ':' and not in_string:
                indentation_level += 1

    return indentation_level, response


def parse_template(template_code):
    indentation_level = 0
    response = []
    line = []
    open_tag_count = 0
    close_tag_count = 0
    in_python_code = False
    in_string = False
    last_quote = ''
    in_comment = False
    dict_count = 0
    backslash = False
    last_character = ''

    for character in template_code:

        if in_python_code and not in_string:
            if (character == '\n' or (character == '}' and last_character == '}')) and in_comment:
                in_comment = False
                if line:
                    indentation_level, parsed_code = parse_python_code(''.join(line), indentation_level)
                    response.extend(parsed_code)
                    line = []
                if character == '}' and last_character == '}':
                    in_python_code = False
                last_character = character
                continue
            elif character == '#' or in_comment:
                in_comment = True
                last_character = character
                continue
        
        if not in_python_code and character != '{':
            open_tag_count = 0
        elif in_python_code and character != '}':
            close_tag_count = 0

        line.append(character)

        if in_python_code and character in quotes and not backslash \
           and (not last_quote or character == last_quote):
            last_quote = character
            in_string = not in_string
            if not in_string:
                last_quote = ''
        elif character == '{':
            if in_python_code and not open_tag_count and not in_string:
                dict_count += 1
                last_character = character
                continue
            if not in_string:
                open_tag_count += 1
            if open_tag_count == 2 and not in_python_code and not in_string:
                open_tag_count = 0
                in_python_code = True
                line = line[:-2]
                if line:
                    response.append(indentation_level * indentation + \
                                    string_on_html % parse_string(''.join(line)))
                    line = []
        elif character == '}':
            if in_python_code and dict_count > 0 and not in_string:
                dict_count -= 1
                last_character = character
                continue
            if not in_string:
                close_tag_count += 1
            if close_tag_count == 2 and in_python_code and not in_string:
                in_python_code = False
                close_tag_count = 0
                python_code = ''.join(line[:-2]).strip()
                indentation_level, parsed_code = parse_python_code(python_code,
                        indentation_level)
                response.extend(parsed_code)
                line = []
        if character == '\\':
            backslash = True
        else:
            backslash = False

    if line:
        if in_python_code:
            indentation_level, parsed_code = parse_python_code(''.join(line),
                    indentation_level)
            response.extend(parsed_code)
        else:
            response.append(indentation_level * indentation + \
                            string_on_html % parse_string(''.join(line)))
    return '\n'.join(response)
    

code_to_parse = '''{{if something:
some_code
other_code
elif other_condition:
elif_code
other_elif_code
else:
code_on_else
other_code_on_else
pass
code_outside
for a in b:
code_on_for
other_code
pass
some_code_outside
for c in d:
code_on_second_for
other_code_on_second_for
else:
code_on_else
other_code_on_else
pass
other_code_outside
try:
something
code_to_try
except:
code_on_the_exception
blablabla
pass
code_outside
try:
other_try_code
more_one_line
except SomeException, e:
lets_solve_this_exception
more_code
except:
code_to_general_exception
goes_here
pass
more_code_outside_all_blocks}}'''