| 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() |