""" Embed HTML assets. It creates an HTML file that has three script tags: 1. A virtual file tree containing all assets in zipped form 2. The pako JS library to unzip the assets 3. Some boostrap code that fixes the HTML so it loads all assets from the virtual file tree instead of the file system Also, two scripts are injected into all HTML files in the file tree. One as the first child of
, one as the last child of . The first does some monkeypatching, the last sets up all magic. Author: Adrian Vollmer """ import base64 from fnmatch import fnmatch import json import logging import mimetypes import os from pathlib import Path import re import zlib try: import magic except ImportError as e: logger = logging.getLogger(__name__) logger.error(str(e)) logger.warning("Using `mimetypes` instead of `python-magic` for mime type guessing") import mimetypes from zundler.args import __version__ SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) logger = logging.getLogger(__name__) def embed_assets(index_file, output_path=None, append_pre="", append_post=""): init_files = {} for filename in [ "init.css", "init.html", "zundler_bootstrap.js", "zundler_common.js", "zundler_main.js", "inject_pre.js", "inject_post.js", "pako.min.js", "LICENSE", ]: path = os.path.join(SCRIPT_PATH, "assets", filename) init_files[filename] = open(path, "r").read() if filename == "inject_pre.js": init_files[filename] = append_pre + init_files[filename] if filename == "inject_post.js": init_files[filename] += append_post if filename.lower().endswith(".js"): init_files[filename] += "\n\n//# sourceURL=%s" % filename if not os.path.exists(index_file): raise FileNotFoundError("no such file: %s" % index_file) base_dir = os.path.dirname(index_file) base_name = os.path.basename(index_file) new_base_name = "SELF_CONTAINED_" + base_name if not output_path: output_path = os.path.join(base_dir, new_base_name) file_tree = load_filetree( base_dir, exclude_pattern=new_base_name, ) global_context = { "current_path": base_name, "fileTree": file_tree, "utils": { "zundler_main": init_files["zundler_main.js"], "zundler_common": init_files["zundler_common.js"], "inject_pre": init_files["inject_pre.js"], "inject_post": init_files["inject_post.js"], }, } global_context = json.dumps(global_context) logger.debug("total asset size: %d" % len(global_context)) global_context = deflate(global_context) logger.debug("total asset size (compressed): %d" % len(global_context)) result = """ {body} """.format( style=init_files["init.css"], body=init_files["init.html"], pako=init_files["pako.min.js"], bootstrap=init_files["zundler_bootstrap.js"], global_context=global_context, license=init_files["LICENSE"], version=__version__, ) with open(output_path, "w") as fp: fp.write(result) logger.info("Result written to: %s" % output_path) return output_path def prepare_file(filename): """Prepare a file for the file tree Referenced assets in CSS files will be embedded. HTML files will be injected with two scripts. `filename`: The name of the file """ _, ext = os.path.splitext(filename) ext = ext.lower()[1:] data = open(filename, "rb").read() mime_type, _ = mimetypes.guess_type(filename) if not mime_type: mime_type = mime_type_from_bytes(filename, data) base64encoded = False if ext == "css": # assuming all CSS files have names ending in '.css' data = embed_css_resources(data, filename) elif ext in [ "png", "jpg", "jpeg", "woff", "woff2", "eot", "ttf", "gif", "ico", "wasm", ]: # JSON doesn't allow binary data data = base64.b64encode(data).decode() base64encoded = True if not isinstance(data, str): try: data = data.decode() except UnicodeError: data = base64.b64encode(data).decode() base64encoded = True logger.debug("loaded file: %s [%s, %d bytes]" % (filename, mime_type, len(data))) result = { "data": data, "mime_type": mime_type, "base64encoded": base64encoded, } return result def deflate(data): data = zlib.compress(data.encode()) data = base64.b64encode(data).decode() return data def to_data_uri(filename, mime_type=None): """Create a data URI from the contents of a file""" try: data = open(filename, "br").read() except FileNotFoundError as e: logger.error(str(e)) data = base64.b64encode(data) if not mime_type: mime_type = "application/octet-stream" return "data:%s;charset=utf-8;base64, %s" % ( mime_type, data.decode(), ) def embed_css_resources(css, filename): """Replace `url(