blob: 58a9b94879fe79875adf6cadc473653112d0e6ee [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 Tanous1ccd57c2017-03-21 13:15:58 -07008import re
Ed Tanous904063f2017-03-02 16:48:24 -08009
10THIS_DIR = os.path.dirname(os.path.realpath(__file__))
11
Ed Tanous1ccd57c2017-03-21 13:15:58 -070012ENABLE_CACHING = True
Ed Tanous904063f2017-03-02 16:48:24 -080013
Ed Tanous1ccd57c2017-03-21 13:15:58 -070014# TODO(ed) this needs to be better
Ed Tanousc4771fb2017-03-13 13:39:49 -070015CONTENT_TYPES = {
16 '.css': "text/css;charset=UTF-8",
17 '.html': "text/html;charset=UTF-8",
18 '.js': "text/html;charset=UTF-8",
Ed Tanous1ccd57c2017-03-21 13:15:58 -070019 '.png': "image/png;charset=UTF-8",
20 '.woff': "application/x-font-woff",
Ed Tanousc4771fb2017-03-13 13:39:49 -070021}
Ed Tanous904063f2017-03-02 16:48:24 -080022
Ed Tanousc4771fb2017-03-13 13:39:49 -070023CPP_BEGIN_BUFFER = """
Ed Tanous904063f2017-03-02 16:48:24 -080024#include <webassets.hpp>
25
Ed Tanous904063f2017-03-02 16:48:24 -080026"""
27
Ed Tanous1ccd57c2017-03-21 13:15:58 -070028ROUTE_DECLARATION = """
29
30void crow::webassets::request_routes(BmcAppType& app){
Ed Tanousc4771fb2017-03-13 13:39:49 -070031"""
32
Ed Tanous1ccd57c2017-03-21 13:15:58 -070033CPP_MIDDLE_CACHING_HANDLER = """
34 res.add_header("Cache-Control", "public, max-age=31556926");
35 res.add_header("ETag", "{sha1}");
36 if (req.headers.count("If-None-Match") == 1) {{
37 if (req.get_header_value("If-None-Match") == "{sha1}"){{
38 res.code = 304;
39 res.end();
40 return;
41 }}
42 }}
43"""
44
45
Ed Tanousc4771fb2017-03-13 13:39:49 -070046CPP_MIDDLE_BUFFER = """
47 CROW_ROUTE(app, "{relative_path_sha1}")([](const crow::request& req, crow::response& res) {{
Ed Tanous1ccd57c2017-03-21 13:15:58 -070048 {CPP_MIDDLE_CACHING_HANDLER}
Ed Tanous904063f2017-03-02 16:48:24 -080049 res.code = 200;
50 // TODO, if you have a browser from the dark ages that doesn't support gzip,
51 // unzip it before sending based on Accept-Encoding header
Ed Tanous1ccd57c2017-03-21 13:15:58 -070052 res.add_header("Content-Encoding", "{content_encoding}");
Ed Tanousc4771fb2017-03-13 13:39:49 -070053 res.add_header("Content-Type", "{content_type}");
Ed Tanous904063f2017-03-02 16:48:24 -080054
Ed Tanousc4771fb2017-03-13 13:39:49 -070055 res.write({relative_path_escaped});
Ed Tanous904063f2017-03-02 16:48:24 -080056
Ed Tanous1ccd57c2017-03-21 13:15:58 -070057 res.end();
Ed Tanous904063f2017-03-02 16:48:24 -080058 }});
59"""
60
61
Ed Tanous1ccd57c2017-03-21 13:15:58 -070062def twos_comp(val, bits):
63 """compute the 2's compliment of int value val"""
64 if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
65 val = val - (1 << bits) # compute negative value
66 return val # return positive value as is
67
Ed Tanousc4771fb2017-03-13 13:39:49 -070068CPP_END_BUFFER = """
69}
Ed Tanous904063f2017-03-02 16:48:24 -080070"""
71
Ed Tanousc4771fb2017-03-13 13:39:49 -070072CPP_END_BUFFER2 = """const static std::string {relative_path_escaped}{{{file_bytes}}};
73"""
74
75def get_relative_path(full_filepath):
76 pathsplit = full_filepath.split(os.path.sep)
77 relative_path = os.path.sep.join(pathsplit[pathsplit.index("static") + 1:])
Ed Tanous1ccd57c2017-03-21 13:15:58 -070078 relative_path_escaped = relative_path
79 for character in ['/', '.', '-']:
80 relative_path_escaped = relative_path_escaped.replace(character, "_")
Ed Tanousc4771fb2017-03-13 13:39:49 -070081
82 relative_path = "/static/" + relative_path
83
Ed Tanousc4771fb2017-03-13 13:39:49 -070084 return relative_path, relative_path_escaped
85
86def get_sha1_path_from_relative(relative_path, sha1):
87 if sha1 != "":
88 path, extension = os.path.splitext(relative_path)
Ed Tanous1ccd57c2017-03-21 13:15:58 -070089 return path + "-" + sha1[:10] + extension
Ed Tanousc4771fb2017-03-13 13:39:49 -070090 else:
91 return relative_path
92
Ed Tanousc4771fb2017-03-13 13:39:49 -070093def filter_html(sha1_list, file_content):
94 string_content = file_content.decode()
95 for key, value in sha1_list.items():
Ed Tanous1ccd57c2017-03-21 13:15:58 -070096 key = key.lstrip("/")
97 replace_name = get_sha1_path_from_relative(key, value)
98 key = re.escape(key)
99 string_content = re.sub("((src|href)=[\"'])(" + key + ")([\"'])", "\\1" + replace_name + "\\4", string_content)
Ed Tanousc4771fb2017-03-13 13:39:49 -0700100 return string_content.encode()
101
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700102
Ed Tanous904063f2017-03-02 16:48:24 -0800103def main():
104 """ Main Function """
Ed Tanous904063f2017-03-02 16:48:24 -0800105
106 parser = argparse.ArgumentParser()
107 parser.add_argument('-i', '--input', nargs='+', type=str)
108 parser.add_argument('-o', '--output', type=str)
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700109 parser.add_argument('-d', '--debug', action='store_true')
Ed Tanous904063f2017-03-02 16:48:24 -0800110 args = parser.parse_args()
111
112 file_list = args.input
113
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700114 file_list = [os.path.realpath(f) for f in file_list]
115
Ed Tanousc4771fb2017-03-13 13:39:49 -0700116 sha1_list = {}
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700117 if not args.debug:
118 # TODO(ed) most html and woff cacheable
119 excluded_types = [".html", ".woff"]
120 # sha1 hash everthing
Ed Tanousc4771fb2017-03-13 13:39:49 -0700121 for full_filepath in file_list:
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700122 if os.path.splitext(full_filepath)[1] not in excluded_types:
Ed Tanousc4771fb2017-03-13 13:39:49 -0700123 with open(full_filepath, 'rb') as input_file:
124 file_content = input_file.read()
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700125 sha = hashlib.sha1()
Ed Tanousc4771fb2017-03-13 13:39:49 -0700126 sha.update(file_content)
Ed Tanous904063f2017-03-02 16:48:24 -0800127
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700128 sha_text = "".join("{:02x}".format(x) for x in sha.digest())
Ed Tanousc4771fb2017-03-13 13:39:49 -0700129 relative_path, relative_path_escaped = get_relative_path(full_filepath)
130 sha1_list[relative_path] = sha_text
131
132 with open(args.output, 'w') as cpp_output:
133 cpp_output.write(CPP_BEGIN_BUFFER)
Ed Tanous904063f2017-03-02 16:48:24 -0800134
135 for full_filepath in file_list:
Ed Tanous904063f2017-03-02 16:48:24 -0800136 # make sure none of the files are hidden
137 with open(full_filepath, 'rb') as input_file:
138 file_content = input_file.read()
Ed Tanousc4771fb2017-03-13 13:39:49 -0700139 relative_path, relative_path_escaped = get_relative_path(full_filepath)
Ed Tanous904063f2017-03-02 16:48:24 -0800140
141 print("Including {:<40} size {:>7}".format(relative_path, len(file_content)))
142
Ed Tanousc4771fb2017-03-13 13:39:49 -0700143 if relative_path.endswith(".html") or relative_path == "/":
144 print("Fixing {}".format(relative_path))
145 file_content = filter_html(sha1_list, file_content)
Ed Tanous904063f2017-03-02 16:48:24 -0800146
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700147 if not args.debug:
148 file_content = gzip.compress(file_content)
149 #file_content = file_content[:10]
150 # compute the 2s complement. If you don't, you get narrowing warnings from gcc/clang
151
152 array_binary_text = ', '.join(str(twos_comp(x, 8)) for x in file_content)
Ed Tanous904063f2017-03-02 16:48:24 -0800153
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700154 cpp_output.write(
155 CPP_END_BUFFER2.format(
156 relative_path=relative_path,
157 file_bytes=array_binary_text,
158 relative_path_escaped=relative_path_escaped
159 )
160 )
Ed Tanous904063f2017-03-02 16:48:24 -0800161
Ed Tanousc4771fb2017-03-13 13:39:49 -0700162 cpp_output.write(ROUTE_DECLARATION)
163
164
165 for full_filepath in file_list:
166 relative_path, relative_path_escaped = get_relative_path(full_filepath)
167 sha1 = sha1_list.get(relative_path, '')
168
Ed Tanousc4771fb2017-03-13 13:39:49 -0700169 content_type = CONTENT_TYPES.get(os.path.splitext(relative_path)[1], "")
170 if content_type == "":
171 print("unknown content type for {}".format(relative_path))
172
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700173 # handle the default routes
174 if relative_path == "/static/index.html":
175 relative_path = "/"
176
177 relative_path_sha1 = get_sha1_path_from_relative(relative_path, sha1)
178
179 content_encoding = 'none' if args.debug else 'gzip'
180
181 environment = {
182 'relative_path':relative_path,
183 'relative_path_escaped': relative_path_escaped,
184 'relative_path_sha1': relative_path_sha1,
185 'sha1': sha1,
186 'sha1_short': sha1[:20],
187 'content_type': content_type,
188 'ENABLE_CACHING': str(ENABLE_CACHING).lower(),
189 'content_encoding': ''
190 }
191 if ENABLE_CACHING and sha1 != "":
192 environment["CPP_MIDDLE_CACHING_HANDLER"] = CPP_MIDDLE_CACHING_HANDLER.format(
193 **environment
194 )
Ed Tanousc4771fb2017-03-13 13:39:49 -0700195 else:
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700196 environment["CPP_MIDDLE_CACHING_HANDLER"] = ""
Ed Tanousc4771fb2017-03-13 13:39:49 -0700197
198 content = CPP_MIDDLE_BUFFER.format(
Ed Tanous1ccd57c2017-03-21 13:15:58 -0700199 **environment
Ed Tanousc4771fb2017-03-13 13:39:49 -0700200 )
201 cpp_output.write(content)
202
203 cpp_output.write(CPP_END_BUFFER)
204
205
Ed Tanous904063f2017-03-02 16:48:24 -0800206
207if __name__ == "__main__":
208 main()