check_routes.py 4.65 KB
Newer Older
1 2 3 4 5
#!/usr/bin/env python3
import regex as re
import os
import sys

6 7 8 9
ROUTE_PATTERN = (
    r'@(?:[[:alpha:]])+\.route\(\"(?<url>[^"]+)"[^)]*\)\s*'
    r'(?:@[[:alpha:]_()., ]+\s*)*def\s+(?<name>[[:alpha:]][[:alnum:]_]*)'
    r'\((?<params>[[:alnum:], ]*)\):')
10
quote_group = "[\"']"
11 12 13
URL_FOR_PATTERN = (
    r'url_for\({quotes}(?<name>[[:alpha:]][[:alnum:]_]*)'
    '{quotes}'.format(quotes=quote_group))
14 15 16 17 18

ROOT_DIR = "."
ENDINGS = [".py", ".html", ".txt"]
MAX_DEPTH = 2

19

20 21 22 23 24 25 26 27 28 29 30 31
def list_dir(dir, level=0):
    if level >= MAX_DEPTH:
        return
    for file in os.listdir(dir):
        path = os.path.join(dir, file)
        if os.path.isfile(path):
            if file == sys.argv[0]:
                continue
            for ending in ENDINGS:
                if file.endswith(ending):
                    yield path
        elif os.path.isdir(path):
32 33
            yield from list_dir(path, level + 1)

34 35 36 37 38 39 40 41 42 43 44 45 46 47

class Route:
    def __init__(self, file, name, parameters):
        self.file = file
        self.name = name
        self.parameters = parameters

    def __repr__(self):
        return "Route({file}, {name}, {parameters})".format(
            file=self.file, name=self.name, parameters=self.parameters)

    def get_parameter_set(self):
        return {parameter.name for parameter in self.parameters}

48

49 50 51 52 53 54
class Parameter:
    def __init__(self, name, type=None):
        self.name = name
        self.type = type

    def __repr__(self):
55 56
        return "Parameter({name}, {type})".format(
            name=self.name, type=self.type)
57 58 59 60 61 62 63 64

    @staticmethod
    def from_string(text):
        if ":" in text:
            type, name = text.split(":", 1)
            return Parameter(name, type)
        return Parameter(text)

65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
def split_url_parameters(url):
    params = []
    current_param = None
    for char in url:
        if current_param is None:
            if char == "<":
                current_param = ""
        else:
            if char == ">":
                params.append(Parameter.from_string(current_param))
                current_param = None
            else:
                current_param += char
    return params

81

82 83 84
def split_function_parameters(parameters):
    return list(map(str.strip, parameters.split(",")))

85

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
def read_url_for_parameters(content):
    params = []
    bracket_level = 1
    current_param = None
    for char in content:
        if bracket_level == 0:
            if current_param is not None:
                params.append(current_param.split("=")[0].strip())
            return params
        if char == ",":
            if current_param is not None:
                params.append(current_param.split("=")[0].strip())
            current_param = ""
        else:
            if current_param is not None:
                current_param += char
            if char == "(":
                bracket_level += 1
            elif char == ")":
                bracket_level -= 1

107

108 109 110 111 112 113 114
class UrlFor:
    def __init__(self, file, name, parameters):
        self.file = file
        self.name = name
        self.parameters = parameters

    def __repr__(self):
115 116 117 118
        return (
            "UrlFor(file={file}, name={name}, parameters={parameters})".format(
                file=self.file, name=self.name, parameters=self.parameters))

119 120 121 122 123 124 125 126

routes = {}
url_fors = []
for file in list_dir(ROOT_DIR):
    with open(file, "r") as infile:
        content = infile.read()
        for match in re.finditer(ROUTE_PATTERN, content):
            name = match.group("name")
127 128
            function_parameters = split_function_parameters(
                match.group("params"))
129 130 131 132 133 134
            url_parameters = split_url_parameters(match.group("url"))
            routes[name] = Route(file, name, url_parameters)
        for match in re.finditer(URL_FOR_PATTERN, content):
            name = match.group("name")
            begin, end = match.span()
            parameters = read_url_for_parameters(content[end:])
135 136 137
            url_fors.append(UrlFor(
                file=file, name=name, parameters=parameters))

138 139 140

for url_for in url_fors:
    if url_for.name not in routes:
141 142
        print("Missing route '{}' (for url_for in '{}')".format(
            url_for.name, url_for.file))
143 144 145 146 147
        continue
    route = routes[url_for.name]
    route_parameters = route.get_parameter_set()
    url_parameters = set(url_for.parameters)
    if len(route_parameters ^ url_parameters) > 0:
148 149
        print("Parameters not matching for '{}' in '{}:'".format(
            url_for.name, url_for.file))
150 151 152 153 154 155
        only_route = route_parameters - url_parameters
        only_url = url_parameters - route_parameters
        if len(only_route) > 0:
            print("Only in route: {}".format(only_route))
        if len(only_url) > 0:
            print("Only in url: {}".format(only_url))