""" Script to embed JavaScript dependencies in mpl.js. """ from collections import namedtuple from pathlib import Path import re import shutil import subprocess import sys Package = namedtuple('Package', [ # The package to embed, in some form that `npm install` can use. 'name', # The path to the source file within the package to embed. 'source', # The path to the license file within the package to embed. 'license']) # The list of packages to embed, in some form that `npm install` can use. JAVASCRIPT_PACKAGES = [ # Polyfill/ponyfill for ResizeObserver. Package('@jsxtools/resize-observer', 'index.js', 'LICENSE.md'), ] # This is the magic line that must exist in mpl.js, after which the embedded # JavaScript will be appended. MPLJS_MAGIC_HEADER = ( "///////////////// REMAINING CONTENT GENERATED BY embed_js.py " "/////////////////\n") def safe_name(name): """ Make *name* safe to use as a JavaScript variable name. """ return '_'.join(re.split(r'[@/-]', name)).upper() def prep_package(web_backend_path, pkg): source = web_backend_path / 'node_modules' / pkg.name / pkg.source license = web_backend_path / 'node_modules' / pkg.name / pkg.license if not source.exists(): # Exact version should already be saved in package.json, so we use # --no-save here. try: subprocess.run(['npm', 'install', '--no-save', pkg.name], cwd=web_backend_path) except FileNotFoundError as err: raise ValueError( f'npm must be installed to fetch {pkg.name}') from err if not source.exists(): raise ValueError( f'{pkg.name} package is missing source in {pkg.source}') elif not license.exists(): raise ValueError( f'{pkg.name} package is missing license in {pkg.license}') return source, license def gen_embedded_lines(pkg, source): name = safe_name(pkg.name) print('Embedding', source, 'as', name) yield '// prettier-ignore\n' for line in source.read_text().splitlines(): yield (line.replace('module.exports=function', f'var {name}=function') + ' // eslint-disable-line\n') def build_mpljs(web_backend_path, license_path): mpljs_path = web_backend_path / "js/mpl.js" mpljs_orig = mpljs_path.read_text().splitlines(keepends=True) try: mpljs_orig = mpljs_orig[:mpljs_orig.index(MPLJS_MAGIC_HEADER) + 1] except IndexError as err: raise ValueError( f'The mpl.js file *must* have the exact line: {MPLJS_MAGIC_HEADER}' ) from err with mpljs_path.open('w') as mpljs: mpljs.writelines(mpljs_orig) for pkg in JAVASCRIPT_PACKAGES: source, license = prep_package(web_backend_path, pkg) mpljs.writelines(gen_embedded_lines(pkg, source)) shutil.copy(license, license_path / f'LICENSE{safe_name(pkg.name)}') if __name__ == '__main__': # Write the mpl.js file. if len(sys.argv) > 1: web_backend_path = Path(sys.argv[1]) else: web_backend_path = (Path(__file__).parent.parent / "lib/matplotlib/backends/web_backend") if len(sys.argv) > 2: license_path = Path(sys.argv[2]) else: license_path = Path(__file__).parent.parent / "LICENSE" build_mpljs(web_backend_path, license_path)