Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 1 | #! /usr/bin/python3 |
| 2 | |
| 3 | import argparse |
| 4 | import os |
| 5 | import gzip |
| 6 | import hashlib |
| 7 | from subprocess import Popen, PIPE |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 8 | from collections import defaultdict |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 9 | import re |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 10 | |
| 11 | THIS_DIR = os.path.dirname(os.path.realpath(__file__)) |
| 12 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 13 | ENABLE_CACHING = False |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 14 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 15 | # TODO(ed) THis should really pull type and file information from webpack |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 16 | CONTENT_TYPES = { |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 17 | '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 Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 29 | } |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 30 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 31 | CPP_MIDDLE_BUFFER = """ CROW_ROUTE(app, "{pretty_name}") |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 32 | ([](const crow::request& req, crow::response& res) {{ |
| 33 | {CACHE_FOREVER_HEADER} |
Ed Tanous | 4758d5b | 2017-06-06 15:28:13 -0700 | [diff] [blame] | 34 | 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 Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 47 | }} |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 48 | res.end(); |
| 49 | }}); |
Ed Tanous | 4758d5b | 2017-06-06 15:28:13 -0700 | [diff] [blame] | 50 | |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 51 | """ |
| 52 | |
Ed Tanous | 4758d5b | 2017-06-06 15:28:13 -0700 | [diff] [blame] | 53 | HPP_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 Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 65 | "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 Tanous | 4758d5b | 2017-06-06 15:28:13 -0700 | [diff] [blame] | 72 | |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 73 | |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 74 | def twos_comp(val, bits): |
| 75 | """compute the 2's compliment of int value val""" |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 76 | if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 77 | val = val - (1 << bits) # compute negative value |
| 78 | return val # return positive value as is |
| 79 | |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 80 | |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 81 | def main(): |
| 82 | """ Main Function """ |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 83 | |
| 84 | parser = argparse.ArgumentParser() |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 85 | parser.add_argument('-i', '--input', type=str) |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 86 | parser.add_argument('-o', '--output', type=str) |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 87 | parser.add_argument('-d', '--debug', action='store_true') |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 88 | args = parser.parse_args() |
| 89 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 90 | dist_dir = args.input |
Ed Tanous | 1ff4878 | 2017-04-18 12:45:08 -0700 | [diff] [blame] | 91 | |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 92 | with open(args.output.replace("cpp", "hpp"), 'w') as hpp_output: |
Ed Tanous | 4758d5b | 2017-06-06 15:28:13 -0700 | [diff] [blame] | 93 | hpp_output.write(HPP_START_BUFFER) |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 94 | hpp_output.write("struct staticassets {\n") |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 95 | |
| 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 Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 123 | hpp_output.write( |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 124 | " 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 Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 127 | hpp_output.write("};\n\n") |
| 128 | hpp_output.write("template <typename... Middlewares>\n") |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 129 | hpp_output.write( |
| 130 | "void request_routes(Crow<Middlewares...>& app) {\n") |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 131 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 132 | 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 Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 139 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 140 | 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 Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 145 | if content_type == "": |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 146 | print("unknown content type for {}".format(pretty_name)) |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 147 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 148 | content_encoding = 'gzip_string' if gzip else 'none_string' |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 149 | |
| 150 | environment = { |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 151 | 'relative_path': pretty_name, |
| 152 | 'relative_path_escaped': cpp_name, |
| 153 | 'pretty_name': pretty_name, |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 154 | 'sha1': sha1, |
| 155 | 'sha1_short': sha1[:20], |
| 156 | 'content_type': content_type, |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 157 | 'content_encoding': content_encoding, |
| 158 | "CACHE_FOREVER_HEADER": "" |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 159 | } |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 160 | |
Ed Tanous | b4d29f4 | 2017-03-24 16:39:25 -0700 | [diff] [blame] | 161 | 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 Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 165 | environment["CACHE_FOREVER_HEADER"] = "res.add_header(\"Cache-Control\", \"public, max-age=31556926\");\n" |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 166 | |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 167 | content = CPP_MIDDLE_BUFFER.format(**environment) |
| 168 | hpp_output.write(content) |
| 169 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 170 | hpp_output.write( |
| 171 | "} // namespace staticassets\n} // namespace webassets\n} // namespace crow") |
Ed Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 172 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 173 | 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 Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 177 | |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 178 | 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 Tanous | b4a7bfa | 2017-04-04 17:23:00 -0700 | [diff] [blame] | 193 | ) |
Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 194 | 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 Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 202 | |
Ed Tanous | 1ff4878 | 2017-04-18 12:45:08 -0700 | [diff] [blame] | 203 | |
Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 204 | if __name__ == "__main__": |
| 205 | main() |