#!/usr/bin/env python import os import sys import subprocess class Record: pass def validate(fn, check_indent=True): text = open(fn).read() state = Record() def NoStartTag(end_tag): raise Exception(''' No start tag fn: %s end tag: %s line %d, col %d ''' % (fn, end_tag, state.line, state.col)) def start_tag_matcher(s): start_line = state.line start_col = state.col state.depth += 1 if s[0] == '<': tag = s[1:-1] elif s[0] == '{': tag = s[3:-2] start_tag = tag.split()[0] old_matcher = state.matcher def f(end_tag): problem = None if not matching_tags(start_tag, end_tag): problem = 'Mismatched tag.' elif check_indent and state.line > start_line + 1 and state.col != start_col: problem = 'Bad indentation.' if problem: raise Exception(''' fn: %s %s start: %s line %d, col %d end tag: %s line %d, col %d ''' % (fn, problem, s, start_line, start_col, end_tag, state.line, state.col)) state.matcher = old_matcher state.depth -= 1 state.matcher = f state.depth = 0 state.i = 0 state.line = 1 state.col = 1 state.matcher = NoStartTag def advance(n): for _ in range(n): state.i += 1 if state.i >= 0 and text[state.i - 1] == '\n': state.line += 1 state.col = 1 else: state.col += 1 def looking_at(s): return text[state.i:state.i+len(s)] == s while True: if state.i >= len(text): break c = text[state.i] if c == '<': s = get_html_tag(text, state.i) if s.startswith('') or tag in ['meta', '!DOCTYPE'] if not ignore: start_tag_matcher(s) advance(len(s)) continue if looking_at("{{#") or looking_at("{{^"): s = get_handlebars_tag(text, state.i) start_tag_matcher(s) advance(len(s)) continue if looking_at("{{/"): s = get_handlebars_tag(text, state.i) state.matcher(s) advance(len(s)) continue advance(1) if state.depth != 0: return state.matcher("(NO TAG)") def get_handlebars_tag(text, i): end = i + 2 while end < len(text) -1 and text[end] != '}': end += 1 if text[end] != '}' or text[end+1] != '}': raise Exception('Tag missing }}') s = text[i:end+2] return s def get_html_tag(text, i): end = i + 1 while end < len(text) and text[end] != '>': end += 1 if text[end] != '>': raise Exception('Tag missing >') s = text[i:end+1] return s def matching_tags(start_tag, end_tag): if end_tag[0] == '<': return start_tag == end_tag[2:-1] if end_tag[0] == '{': return start_tag == end_tag[3:-2] def check_our_files(): git_files = map(str.strip, subprocess.check_output(['git', 'ls-files']).split('\n')) # Check all our handlebars templates. templates = [fn for fn in git_files if fn.endswith('.handlebars')] assert len(templates) >= 10 # sanity check that we are actually doing work for fn in templates: validate(fn) # Django templates are pretty messy now, so we do minimal checking. templates = sorted([fn for fn in git_files if fn.endswith('.html') and 'templates' in fn]) def ok(fn): if 'api.html' in fn: return False if 'base.html' in fn: return False return True templates = filter(ok, templates) assert len(templates) >= 10 # sanity check that we are actually doing work for fn in templates: validate(fn, check_indent=False) if __name__ == '__main__': check_our_files()