blob: f9b3f74e66ce038af9ba14140570103d5e38e995 [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
Ed Tanousb4a7bfa2017-04-04 17:23:00 -07008from collections import defaultdict
Ed Tanous1ccd57c2017-03-21 13:15:58 -07009import re
Ed Tanous904063f2017-03-02 16:48:24 -080010
11THIS_DIR = os.path.dirname(os.path.realpath(__file__))
12
Ed Tanous911ac312017-08-15 09:37:42 -070013ENABLE_CACHING = False
Ed Tanous904063f2017-03-02 16:48:24 -080014
Ed Tanous911ac312017-08-15 09:37:42 -070015# TODO(ed) THis should really pull type and file information from webpack
Ed Tanousc4771fb2017-03-13 13:39:49 -070016CONTENT_TYPES = {
Ed Tanous911ac312017-08-15 09:37:42 -070017 'css': "text/css;charset=UTF-8",
18 'html': "text/html;charset=UTF-8",
19 'js': "text/html;charset=UTF-8",
20 'png': "image/png;charset=UTF-8",
21 'woff': "application/x-font-woff",
22 'woff2': "application/x-font-woff2",
23 'ttf': "application/x-font-ttf",
24 "svg": "image/svg+xml",
25 "eot": "application/vnd.ms-fontobject",
26 # dev tools don't care, causes browser to show as text
27 # https://stackoverflow.com/questions/19911929/what-mime-type-should-i-use-for-javascript-source-map-files
28 "map": "application/json"
Ed Tanousc4771fb2017-03-13 13:39:49 -070029}
Ed Tanous904063f2017-03-02 16:48:24 -080030
Ed Tanous911ac312017-08-15 09:37:42 -070031CPP_MIDDLE_BUFFER = """ CROW_ROUTE(app, "{pretty_name}")
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070032 ([](const crow::request& req, crow::response& res) {{
33 {CACHE_FOREVER_HEADER}
Ed Tanous4758d5b2017-06-06 15:28:13 -070034 std::string sha1("{sha1}");
35 res.add_header(etag_string, sha1);
36
37 if (req.get_header_value(if_none_match_string) == sha1) {{
38 res.code = 304;
39 }} else {{
40 res.code = 200;
41 // TODO, if you have a browser from the dark ages that doesn't support gzip,
42 // unzip it before sending based on Accept-Encoding header
43 res.add_header(content_encoding_string, {content_encoding});
44 res.add_header(content_type_string, "{content_type}");
45
46 res.write(staticassets::{relative_path_escaped});
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070047 }}
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070048 res.end();
49 }});
Ed Tanous4758d5b2017-06-06 15:28:13 -070050
Ed Tanous904063f2017-03-02 16:48:24 -080051"""
52
Ed Tanous4758d5b2017-06-06 15:28:13 -070053HPP_START_BUFFER = ("#pragma once\n"
54 "\n"
55 "#include <string>\n"
56 "\n"
57 "#include <crow/app.h>\n"
58 "#include <crow/http_request.h>\n"
59 "#include <crow/http_response.h>\n"
60 "\n"
61 "#include <crow/routing.h>\n"
62 "\n"
63 "namespace crow {\n"
64 "namespace webassets {\n"
Ed Tanous911ac312017-08-15 09:37:42 -070065 "static const char* gzip_string = \"gzip\";\n"
66 "static const char* none_string = \"none\";\n"
67 "static const char* if_none_match_string = \"If-None-Match\";\n"
68 "static const char* content_encoding_string = \"Content-Encoding\";\n"
69 "static const char* content_type_string = \"Content-Type\";\n"
70 "static const char* etag_string = \"ETag\";\n"
71 )
Ed Tanous4758d5b2017-06-06 15:28:13 -070072
Ed Tanous904063f2017-03-02 16:48:24 -080073
Ed Tanous1ccd57c2017-03-21 13:15:58 -070074def twos_comp(val, bits):
75 """compute the 2's compliment of int value val"""
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070076 if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
Ed Tanous1ccd57c2017-03-21 13:15:58 -070077 val = val - (1 << bits) # compute negative value
78 return val # return positive value as is
79
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070080
Ed Tanous904063f2017-03-02 16:48:24 -080081def main():
82 """ Main Function """
Ed Tanous904063f2017-03-02 16:48:24 -080083
84 parser = argparse.ArgumentParser()
Ed Tanous911ac312017-08-15 09:37:42 -070085 parser.add_argument('-i', '--input', type=str)
Ed Tanous904063f2017-03-02 16:48:24 -080086 parser.add_argument('-o', '--output', type=str)
Ed Tanous1ccd57c2017-03-21 13:15:58 -070087 parser.add_argument('-d', '--debug', action='store_true')
Ed Tanous904063f2017-03-02 16:48:24 -080088 args = parser.parse_args()
89
Ed Tanous911ac312017-08-15 09:37:42 -070090 dist_dir = args.input
Ed Tanous1ff48782017-04-18 12:45:08 -070091
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070092 with open(args.output.replace("cpp", "hpp"), 'w') as hpp_output:
Ed Tanous4758d5b2017-06-06 15:28:13 -070093 hpp_output.write(HPP_START_BUFFER)
Ed Tanousb4a7bfa2017-04-04 17:23:00 -070094 hpp_output.write("struct staticassets {\n")
Ed Tanous911ac312017-08-15 09:37:42 -070095
96 asset_filenames = []
97
98 for root, dirnames, filenames in os.walk(dist_dir):
99 for filename in filenames:
100 root_file = os.path.join(root, filename)
101 pretty_name = "/" + os.path.relpath(root_file, dist_dir)
102 cpp_name = "file" + pretty_name
103 for character in ['/', '.', '-']:
104 cpp_name = cpp_name.replace(character, "_")
105
106 if pretty_name.endswith(".gz"):
107 pretty_name = pretty_name[:-3]
108 gzip = True
109 else:
110 gzip = False
111
112 if pretty_name.endswith("/index.html"):
113 pretty_name = pretty_name[:-10]
114
115 asset_filenames.append(
116 (root_file, pretty_name, cpp_name, gzip))
117
118 for root_file, pretty_name, cpp_name, gzip in asset_filenames:
119
120 with open(root_file, 'rb') as file_handle:
121 file_content = file_handle.read()
122
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700123 hpp_output.write(
Ed Tanous911ac312017-08-15 09:37:42 -0700124 " static const std::array<char, {}> {};\n".format(len(file_content), cpp_name))
125 hpp_output.write(
126 " static const std::array<const char*, {}> routes;\n".format(len(asset_filenames)))
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700127 hpp_output.write("};\n\n")
128 hpp_output.write("template <typename... Middlewares>\n")
Ed Tanous911ac312017-08-15 09:37:42 -0700129 hpp_output.write(
130 "void request_routes(Crow<Middlewares...>& app) {\n")
Ed Tanous904063f2017-03-02 16:48:24 -0800131
Ed Tanous911ac312017-08-15 09:37:42 -0700132 for root_file, pretty_name, cpp_name, gzip in asset_filenames:
133 os.path.basename(root_file)
134 with open(root_file, 'rb') as file_handle:
135 file_content = file_handle.read()
136 sha = hashlib.sha1()
137 sha.update(file_content)
138 sha1 = sha.hexdigest()
Ed Tanousc4771fb2017-03-13 13:39:49 -0700139
Ed Tanous911ac312017-08-15 09:37:42 -0700140 ext = os.path.split(root_file)[-1].split(".")[-1]
141 if ext == "gz":
142 ext = os.path.split(root_file)[-1].split(".")[-2]
143
144 content_type = CONTENT_TYPES.get(ext, "")
Ed Tanousc4771fb2017-03-13 13:39:49 -0700145 if content_type == "":
Ed Tanous911ac312017-08-15 09:37:42 -0700146 print("unknown content type for {}".format(pretty_name))
Ed Tanousc4771fb2017-03-13 13:39:49 -0700147
Ed Tanous911ac312017-08-15 09:37:42 -0700148 content_encoding = 'gzip_string' if gzip else 'none_string'
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700149
150 environment = {
Ed Tanous911ac312017-08-15 09:37:42 -0700151 'relative_path': pretty_name,
152 'relative_path_escaped': cpp_name,
153 'pretty_name': pretty_name,
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700154 'sha1': sha1,
155 'sha1_short': sha1[:20],
156 'content_type': content_type,
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700157 'content_encoding': content_encoding,
158 "CACHE_FOREVER_HEADER": ""
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700159 }
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700160
Ed Tanousb4d29f42017-03-24 16:39:25 -0700161 if ENABLE_CACHING:
162 # if we have a valid sha1, and we have a unique path to the resource
163 # it can be safely cached forever
164 if sha1 != "" and relative_path != relative_path_sha1:
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700165 environment["CACHE_FOREVER_HEADER"] = "res.add_header(\"Cache-Control\", \"public, max-age=31556926\");\n"
Ed Tanousc4771fb2017-03-13 13:39:49 -0700166
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700167 content = CPP_MIDDLE_BUFFER.format(**environment)
168 hpp_output.write(content)
169
Ed Tanous911ac312017-08-15 09:37:42 -0700170 hpp_output.write(
171 "} // namespace staticassets\n} // namespace webassets\n} // namespace crow")
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700172
Ed Tanous911ac312017-08-15 09:37:42 -0700173 with open(args.output, 'w') as cpp_output:
174 cpp_output.write("#include <webassets.hpp>\n"
175 "namespace crow{\n"
176 "namespace webassets{\n")
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700177
Ed Tanous911ac312017-08-15 09:37:42 -0700178 for root_file, pretty_name, cpp_name, gzip in asset_filenames:
179 with open(root_file, 'rb') as file_handle:
180 file_content = file_handle.read()
181 # compute the 2s complement for negative numbers.
182 # If you don't, you get narrowing warnings from gcc/clang
183 array_binary_text = ', '.join(str(twos_comp(x, 8))
184 for x in file_content)
185 cpp_end_buffer = " const std::array<char, {byte_length}> staticassets::{relative_path_escaped} = {{{file_bytes}}};\n"
186 cpp_output.write(
187 cpp_end_buffer.format(
188 relative_path_escaped=cpp_name,
189 byte_length=len(file_content),
190 relative_path=pretty_name,
191 file_bytes=array_binary_text
192 )
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700193 )
Ed Tanous911ac312017-08-15 09:37:42 -0700194 print("{:<40} took {:>6} KB".format(
195 pretty_name, int(len(array_binary_text) / 1024)))
196 static_routes = ",\n".join(
197 [' "' + x[1] + '"' for x in asset_filenames])
198 cpp_output.write(
199 "\n const std::array<const char*, {}> staticassets::routes{{\n{}}};\n".format(len(asset_filenames), static_routes))
200 cpp_output.write(
201 "} // namespace webassets\n} // namespace crow\n")
Ed Tanous904063f2017-03-02 16:48:24 -0800202
Ed Tanous1ff48782017-04-18 12:45:08 -0700203
Ed Tanous904063f2017-03-02 16:48:24 -0800204if __name__ == "__main__":
205 main()