Source code for pywebpack.manifests

# -*- coding: utf-8 -*-
#
# This file is part of PyWebpack
# Copyright (C) 2017 CERN
# Copyright (C) 2017 Anders Kaseorg
#
# PyWebpack is free software; you can redistribute it and/or modify
# it under the terms of the Revised BSD License; see LICENSE file for
# more details.

"""Webpack manifests API."""

from __future__ import absolute_import, print_function

import json
import sys
from os.path import splitext

_string_types = (str,) if sys.version_info[0] == 3 else (basestring,)


#
# Errors
#
[docs]class ManifestError(Exception): """Manifest error."""
[docs]class InvalidManifestError(ManifestError): """Invalid manifest."""
[docs]class UnfinishedManifestError(ManifestError): """Manifest is currently being built."""
[docs]class UnsupportedManifestError(ManifestError): """Could not parse the manifest."""
[docs]class UnsupportedExtensionError(ManifestError): """Manifest contains a file with an extension that is not supported."""
# # Manifest #
[docs]class Manifest(object): """Assets manifest.""" def __init__(self): """Initialize manifest.""" self._entries = {}
[docs] def add(self, entry): """Add an entry to the manifest.""" if entry.name in self._entries: raise KeyError("Entry {} already present".format(entry.name)) self._entries[entry.name] = entry
def __getitem__(self, key): """Get a manifest entry.""" return self._entries[key] def __getattr__(self, name): """Get a manifest entry.""" try: return self._entries[name] except KeyError: raise AttributeError("Attribute {} does not exists.".format(name)) def __iter__(self): """Iterate over entries in the manifest.""" return iter(self._entries.values())
[docs]class ManifestEntry(object): """Represents a manifest entry.""" templates = { ".js": '<script src="{}"></script>', ".css": '<link rel="stylesheet" href="{}" />', } def __init__(self, name, paths): """Initialize manifest entry.""" self.name = name self._paths = paths
[docs] def render(self): """Render entry.""" out = [] for p in self._paths: _dummy_name, ext = splitext(p) tpl = self.templates.get(ext.lower()) if tpl is None: raise UnsupportedExtensionError(p) out.append(tpl.format(p)) return "".join(out)
def __iter__(self): """Iterate over files in the manifest entry.""" for path in self._paths: yield path def __str__(self): """Render entry.""" return self.render()
# # Factories #
[docs]class ManifestFactory(object): """Manifest factory base class.""" def __init__(self, manifest_cls=Manifest, entry_cls=ManifestEntry): """Initialize factory.""" self.manifest_cls = manifest_cls self.entry_cls = entry_cls
[docs] def load(self, filepath): """Load a manifest file.""" with open(filepath) as fp: return self.create(json.load(fp))
[docs] def create_entry(self, entry, paths): """Create a manifest entry instance.""" return self.entry_cls(entry, paths)
[docs] def create_manifest(self): """Create a manifest instance.""" return self.manifest_cls()
[docs]class WebpackManifestFactory(ManifestFactory): """Manifest factory for webpack-manifest-plugin."""
[docs] def create(self, data): """Create manifest from parsed data.""" manifest = self.create_manifest() for entry_name, path in data.items(): if not isinstance(path, _string_types): raise InvalidManifestError("webpack-manifest-plugin") manifest.add(self.create_entry(entry_name, [path])) return manifest
[docs]class WebpackYamFactory(ManifestFactory): """Manifest factory for webpack-yam-plugin."""
[docs] def create(self, data): """Create manifest from parsed data.""" # Is manifest of correct type? try: status = data["status"] files = data["files"] except KeyError: raise InvalidManifestError("webpack-yam-plugin") # Is manifest finished? if files is None or status != "built": raise UnfinishedManifestError(data) manifest = self.create_manifest() for entry_name, paths in files.items(): manifest.add(self.create_entry(entry_name, paths)) return manifest
[docs]class WebpackBundleTrackerFactory(ManifestFactory): """Manifest factory for webpack-bundle-tracker."""
[docs] def create(self, data): """Create manifest from parsed data.""" # Is manifest of correct type? try: status = data["status"] except KeyError: raise InvalidManifestError("webpack-bundle-tracker") if "chunks" not in data: raise InvalidManifestError("webpack-bundle-tracker") # Is manifest finished? if status != "done": raise UnfinishedManifestError(data) manifest = self.create_manifest() chunks = data["chunks"] assets = data["assets"] for entry_name, paths in chunks.items(): js_paths = [p for p in paths if p.endswith(".js")] if js_paths: manifest.add( self.create_entry( entry_name + ".js", [assets[p]["publicPath"] for p in js_paths] ) ) css_paths = [p for p in paths if p.endswith(".css")] if css_paths: manifest.add( self.create_entry( entry_name + ".css", [assets[p]["publicPath"] for p in css_paths], ) ) return manifest
[docs]class ManifestLoader(object): """Loads a Webpack manifest (multiple types supported).""" types = [ WebpackBundleTrackerFactory, WebpackYamFactory, WebpackManifestFactory, ] def __init__(self, manifest_cls=Manifest, entry_cls=ManifestEntry): """Initialize loader.""" self.manifest_cls = manifest_cls self.entry_cls = entry_cls
[docs] def load(self, filepath): """Load a manifest from a file.""" with open(filepath) as fp: data = json.load(fp) for t in self.types: try: return t( manifest_cls=self.manifest_cls, entry_cls=self.entry_cls ).create(data) except InvalidManifestError: pass raise UnsupportedManifestError(filepath)