blob: 9cad070f2726ede0bb05b0a36914254400ce1b88 [file] [log] [blame]
Ed Tanous904063f2017-03-02 16:48:24 -08001#! /usr/bin/python3
2
3import argparse
4import os
5import gzip
6import hashlib
7from subprocess import Popen, PIPE
8
9THIS_DIR = os.path.dirname(os.path.realpath(__file__))
10
Ed Tanousc4771fb2017-03-13 13:39:49 -070011ENABLE_CACHING = False
Ed Tanous904063f2017-03-02 16:48:24 -080012
Ed Tanousc4771fb2017-03-13 13:39:49 -070013# TODO this needs to be better
14CONTENT_TYPES = {
15 '.css': "text/css;charset=UTF-8",
16 '.html': "text/html;charset=UTF-8",
17 '.js': "text/html;charset=UTF-8",
18 '.png': "image/png;charset=UTF-8"
19}
Ed Tanous904063f2017-03-02 16:48:24 -080020
Ed Tanousc4771fb2017-03-13 13:39:49 -070021CPP_BEGIN_BUFFER = """
Ed Tanous904063f2017-03-02 16:48:24 -080022#include <webassets.hpp>
23
Ed Tanous904063f2017-03-02 16:48:24 -080024"""
25
Ed Tanousc4771fb2017-03-13 13:39:49 -070026ROUTE_DECLARATION = """void crow::webassets::request_routes(crow::App<crow::TokenAuthorizationMiddleware>& app){
27"""
28
29CPP_MIDDLE_BUFFER = """
30 CROW_ROUTE(app, "{relative_path_sha1}")([](const crow::request& req, crow::response& res) {{
Ed Tanous904063f2017-03-02 16:48:24 -080031 res.code = 200;
32 // TODO, if you have a browser from the dark ages that doesn't support gzip,
33 // unzip it before sending based on Accept-Encoding header
34 res.add_header("Content-Encoding", "gzip");
Ed Tanousc4771fb2017-03-13 13:39:49 -070035 res.add_header("Cache-Control", "{cache_control_value}");
36 res.add_header("Content-Type", "{content_type}");
Ed Tanous904063f2017-03-02 16:48:24 -080037
Ed Tanousc4771fb2017-03-13 13:39:49 -070038 res.write({relative_path_escaped});
Ed Tanous904063f2017-03-02 16:48:24 -080039 res.end();
40
41 }});
42"""
43
44
Ed Tanousc4771fb2017-03-13 13:39:49 -070045CPP_END_BUFFER = """
46}
Ed Tanous904063f2017-03-02 16:48:24 -080047"""
48
Ed Tanousc4771fb2017-03-13 13:39:49 -070049CPP_END_BUFFER2 = """const static std::string {relative_path_escaped}{{{file_bytes}}};
50"""
51
52def get_relative_path(full_filepath):
53 pathsplit = full_filepath.split(os.path.sep)
54 relative_path = os.path.sep.join(pathsplit[pathsplit.index("static") + 1:])
55
56 relative_path_escaped = relative_path.replace("/", "_").replace(".", "_").replace("-", "_")
57
58 relative_path = "/static/" + relative_path
59
60 # handle the default routes
61 if relative_path == "/static/index.html":
62 relative_path = "/"
63
64 return relative_path, relative_path_escaped
65
66def get_sha1_path_from_relative(relative_path, sha1):
67 if sha1 != "":
68 path, extension = os.path.splitext(relative_path)
69 return path + "_" + sha1 + extension
70 else:
71 return relative_path
72
73
74def filter_html(sha1_list, file_content):
75 string_content = file_content.decode()
76 for key, value in sha1_list.items():
77 if key != "/":
78 # todo, this is very naive, do it better (parse the html)
79 start = "src=\"" + key.lstrip("/")
80 replace = "src=\"" + get_sha1_path_from_relative(key, value)
81 #print("REplacing {} with {}".format(start, replace))
82 string_content = string_content.replace(start, replace)
83
84 start = "href=\"" + key.lstrip("/")
85 replace = "href=\"" + get_sha1_path_from_relative(key, value)
86 #print("REplacing {} with {}".format(start, replace))
87 string_content = string_content.replace(start, replace)
88 return string_content.encode()
89
Ed Tanous904063f2017-03-02 16:48:24 -080090def main():
91 """ Main Function """
Ed Tanous904063f2017-03-02 16:48:24 -080092
93 parser = argparse.ArgumentParser()
94 parser.add_argument('-i', '--input', nargs='+', type=str)
95 parser.add_argument('-o', '--output', type=str)
96 args = parser.parse_args()
97
98 file_list = args.input
99
Ed Tanousc4771fb2017-03-13 13:39:49 -0700100 sha1_list = {}
101 if ENABLE_CACHING:
102 # Sha256 hash everthing
103 for full_filepath in file_list:
104 if not full_filepath.endswith(".html"):
105 with open(full_filepath, 'rb') as input_file:
106 file_content = input_file.read()
107 sha = hashlib.sha256()
108 sha.update(file_content)
Ed Tanous904063f2017-03-02 16:48:24 -0800109
Ed Tanousc4771fb2017-03-13 13:39:49 -0700110 sha_text = "".join("{:02x}".format(x) for x in sha.digest())[:10]
111 relative_path, relative_path_escaped = get_relative_path(full_filepath)
112 sha1_list[relative_path] = sha_text
113
114 with open(args.output, 'w') as cpp_output:
115 cpp_output.write(CPP_BEGIN_BUFFER)
Ed Tanous904063f2017-03-02 16:48:24 -0800116
117 for full_filepath in file_list:
Ed Tanous904063f2017-03-02 16:48:24 -0800118 # make sure none of the files are hidden
119 with open(full_filepath, 'rb') as input_file:
120 file_content = input_file.read()
Ed Tanousc4771fb2017-03-13 13:39:49 -0700121 relative_path, relative_path_escaped = get_relative_path(full_filepath)
Ed Tanous904063f2017-03-02 16:48:24 -0800122
123 print("Including {:<40} size {:>7}".format(relative_path, len(file_content)))
124
Ed Tanousc4771fb2017-03-13 13:39:49 -0700125 if relative_path.endswith(".html") or relative_path == "/":
126 print("Fixing {}".format(relative_path))
127 file_content = filter_html(sha1_list, file_content)
Ed Tanous904063f2017-03-02 16:48:24 -0800128
Ed Tanous904063f2017-03-02 16:48:24 -0800129
Ed Tanous904063f2017-03-02 16:48:24 -0800130 file_content = gzip.compress(file_content)
131 #file_content = file_content[:10]
132
133 array_binary_text = ', '.join('0x{:02x}'.format(x) for x in file_content)
134
Ed Tanousc4771fb2017-03-13 13:39:49 -0700135 cpp_output.write(CPP_END_BUFFER2.format(relative_path=relative_path, file_bytes=array_binary_text, relative_path_escaped=relative_path_escaped))
Ed Tanous904063f2017-03-02 16:48:24 -0800136
Ed Tanousc4771fb2017-03-13 13:39:49 -0700137 cpp_output.write(ROUTE_DECLARATION)
138
139
140 for full_filepath in file_list:
141 relative_path, relative_path_escaped = get_relative_path(full_filepath)
142 sha1 = sha1_list.get(relative_path, '')
143
144 relative_path_sha1 = get_sha1_path_from_relative(relative_path, sha1)
145
146 content_type = CONTENT_TYPES.get(os.path.splitext(relative_path)[1], "")
147 if content_type == "":
148 print("unknown content type for {}".format(relative_path))
149
150 if sha1 == "":
151 cache_control_value = "no-cache"
152 else:
153 cache_control_value = "max-age=31556926"
154
155 content = CPP_MIDDLE_BUFFER.format(
156 relative_path=relative_path,
157 relative_path_escaped=relative_path_escaped,
158 relative_path_sha1=relative_path_sha1,
159 sha1=sha1,
160 content_type=content_type,
161 cache_control_value=cache_control_value
162 )
163 cpp_output.write(content)
164
165 cpp_output.write(CPP_END_BUFFER)
166
167
Ed Tanous904063f2017-03-02 16:48:24 -0800168
169if __name__ == "__main__":
170 main()