From 952c15151b33a0af41e88b7782153d8cad469048 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Thu, 17 May 2012 17:32:45 +0100 Subject: [PATCH 01/15] highly experimental first stab at versioning image files etc --- .gitignore | 3 ++ src/webassets/__init__.py | 3 +- src/webassets/env.py | 5 ++ src/webassets/externalassets.py | 56 +++++++++++++++++++++ src/webassets/filter/cssrewrite/__init__.py | 15 +++++- src/webassets/merge.py | 7 +++ src/webassets/script.py | 6 +++ src/webassets/version.py | 27 ++++++++++ 8 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/webassets/externalassets.py diff --git a/.gitignore b/.gitignore index 13ea8079..d98a08b1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ *.~* *.pyc + +# OS X +.DS_Store \ No newline at end of file diff --git a/src/webassets/__init__.py b/src/webassets/__init__.py index 2cbd544c..c6ea0759 100644 --- a/src/webassets/__init__.py +++ b/src/webassets/__init__.py @@ -1,6 +1,7 @@ __version__ = (0, 7) -# Make a couple frequently used things available right here. +# Make a few frequently used things available right here. from bundle import Bundle +from externalassets import ExternalAssets from env import Environment \ No newline at end of file diff --git a/src/webassets/env.py b/src/webassets/env.py index 526f3c8c..fc15e1c8 100644 --- a/src/webassets/env.py +++ b/src/webassets/env.py @@ -127,6 +127,7 @@ class BaseEnvironment(object): def __init__(self, **config): self._named_bundles = {} self._anon_bundles = [] + self.external_assets = None self._config = self.config_storage_class(self) # directory, url currently do not have default values @@ -157,6 +158,10 @@ def __contains__(self, name): def __len__(self): return len(self._named_bundles) + len(self._anon_bundles) + def register_externals(self, externals): + externals.env = self + self.external_assets = externals + def register(self, name, *args, **kwargs): """Register a bundle with the given name. diff --git a/src/webassets/externalassets.py b/src/webassets/externalassets.py new file mode 100644 index 00000000..1ab8cccb --- /dev/null +++ b/src/webassets/externalassets.py @@ -0,0 +1,56 @@ +import os +from os import path +from merge import FileHunk + +try: + # Current version of glob2 does not let us access has_magic :/ + import glob2 as glob + from glob import has_magic +except ImportError: + import glob + from glob import has_magic + +__all__ = ('ExternalAssets',) + +class ExternalAssets(object): + + def __init__(self, folders): + + self.folders = folders + #self.version = options.pop('version', []) + + def get_versioned_file(self, file_name): + version = self.get_version(file_name) + bits = file_name.split('.') + bits.insert(len(bits)-1, version) + return '.'.join(bits) + + def get_output_path(self, file_name): + return self.env.abspath(self.get_versioned_file(file_name)) + + def write_file(self, file_name): + output_path = self.get_output_path(file_name) + hunk = FileHunk(self.env.abspath(file_name)) + output_path = output_path.replace('img/','genimg/') + output_dir = path.dirname(output_path) + if not path.exists(output_dir): + os.makedirs(output_dir) + hunk.save(output_path) + if self.env.manifest: + self.env.manifest.remember_file(file_name, self.env, self.get_version(file_name)) + + def write_files(self): + for folder in self.folders: + path = self.env.abspath(folder) + for file_name in glob.glob(path): + self.write_file(file_name.replace('%s/' % self.env.abspath(''),'')) + + def show_manifest(self): + if self.env.manifest: + print self.env.manifest.get_manifest() + + def url(self, file_name): + return self.env.absurl(self.get_versioned_file(file_name)) + + def get_version(self, file_name): + return self.env.versions.determine_file_version(file_name, self.env) \ No newline at end of file diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index 48ce2697..ee5e13d6 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -84,7 +84,18 @@ def replace_url(self, url): if not url.startswith('/') and not (url.startswith('http://') or url.startswith('https://')): # rewritten url: relative path from new location (output) # to location of referenced file (source + current url) - url = urlpath.relpath(self.output_url, - urlparse.urljoin(self.source_url, url)) + # let's look in the manifest to see if we have a versioned url for this + replacement = None + if self.env.manifest: + file_path = urlpath.pathjoin(self.source_path, url) + if self.env.external_assets: + file_name = self.env.external_assets.get_versioned_file(file_path) + replacement = urlpath.relpathto(self.env.directory, self.output_path, file_name) + + if replacement is None: + url = urlpath.relpath(self.output_url, + urlparse.urljoin(self.source_url, url)) + else: + url = replacement.replace('img/','genimg/') return url diff --git a/src/webassets/merge.py b/src/webassets/merge.py index 22862f4d..0d18f9ba 100644 --- a/src/webassets/merge.py +++ b/src/webassets/merge.py @@ -42,6 +42,13 @@ def __init__(self, filename): def mtime(self): pass + def save(self, filename): + f = open(filename, 'wb') + try: + f.write(self.data()) + finally: + f.close() + def data(self): f = open(self.filename, 'rb') try: diff --git a/src/webassets/script.py b/src/webassets/script.py index 9032315d..fd3c1866 100644 --- a/src/webassets/script.py +++ b/src/webassets/script.py @@ -196,6 +196,12 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None, built.append(bundle) except BuildError, e: self.log.error("Failed, error was: %s" % e) + + # build external assets if we have any + if self.environment.external_assets: + self.log.info("Building external assets") + self.environment.external_assets.write_files() + if len(built): self.event_handlers['post_build']() diff --git a/src/webassets/version.py b/src/webassets/version.py index f94d6aec..911aebf4 100644 --- a/src/webassets/version.py +++ b/src/webassets/version.py @@ -41,6 +41,9 @@ class Version(object): clazz=lambda: Version, attribute='determine_version', desc='a version implementation') + def determine_file_version(self, file_name, env=None): + raise NotImplementedError() + def determine_version(self, bundle, hunk=None, env=None): """Return a string that represents the current version of the given bundle. @@ -152,6 +155,12 @@ def __init__(self, length=8, hash=md5_constructor): self.length = length self.hasher = hash + def determine_file_version(self, file_name, env): + hunk = FileHunk(env.abspath(file_name)) + hasher = self.hasher() + hasher.update(hunk.data()) + return hasher.hexdigest()[:self.length] + def determine_version(self, bundle, env, hunk=None): if not hunk: if not has_placeholder(bundle.output): @@ -215,6 +224,11 @@ def remember(self, bundle, env, version): def query(self, bundle, env): raise NotImplementedError() + def remember_file(self, file_name, env, version): + raise NotImplementedError() + + def query_file(self, file_name, env): + raise NotImplementedError() get_manifest = Manifest.resolve @@ -252,6 +266,19 @@ def query(self, bundle, env): self._load_manifest() return self.manifest.get(bundle.output, None) + def remember_file(self, file_name, env, version): + self.manifest[file_name] = version + self._save_manifest() + + def query_file(self, file_name, env): + if env.auto_build: + self._load_manifest() + return self.manifest.get(file_name, None) + + def get_manifest(self): + self._load_manifest() + return self.manifest + def _load_manifest(self): if os.path.exists(self.filename): with open(self.filename, 'rb') as f: From 19b2e545e07f5065d3fbd9b94f813a29b4f1fba0 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Sat, 19 May 2012 13:21:11 +0100 Subject: [PATCH 02/15] added output folder for external assets --- src/webassets/exceptions.py | 5 ++++- src/webassets/externalassets.py | 25 +++++++++++++++++---- src/webassets/filter/cssrewrite/__init__.py | 12 ++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/webassets/exceptions.py b/src/webassets/exceptions.py index a642c966..dd107959 100644 --- a/src/webassets/exceptions.py +++ b/src/webassets/exceptions.py @@ -1,10 +1,13 @@ __all__ = ('BundleError', 'BuildError', 'FilterError', - 'EnvironmentError', 'ImminentDeprecationWarning') + 'EnvironmentError', 'ImminentDeprecationWarning', 'ExternalAssetsError') class EnvironmentError(Exception): pass +class ExternalAssetsError(Exception): + pass + class BundleError(Exception): pass diff --git a/src/webassets/externalassets.py b/src/webassets/externalassets.py index 1ab8cccb..5828ef11 100644 --- a/src/webassets/externalassets.py +++ b/src/webassets/externalassets.py @@ -2,6 +2,8 @@ from os import path from merge import FileHunk +from exceptions import ExternalAssetsError + try: # Current version of glob2 does not let us access has_magic :/ import glob2 as glob @@ -25,13 +27,19 @@ def get_versioned_file(self, file_name): bits.insert(len(bits)-1, version) return '.'.join(bits) + def versioned_folder(self, file_name): + output_folder = self.env.config.get('external_assets_output_folder', None) + if output_folder is None: + raise ExternalAssetsError('You must set the external_assets_output_folder config value') + versioned = self.get_versioned_file(file_name) + return path.join(output_folder, path.basename(versioned)) + def get_output_path(self, file_name): - return self.env.abspath(self.get_versioned_file(file_name)) + return self.env.abspath(self.versioned_folder(file_name)) def write_file(self, file_name): output_path = self.get_output_path(file_name) hunk = FileHunk(self.env.abspath(file_name)) - output_path = output_path.replace('img/','genimg/') output_dir = path.dirname(output_path) if not path.exists(output_dir): os.makedirs(output_dir) @@ -50,7 +58,16 @@ def show_manifest(self): print self.env.manifest.get_manifest() def url(self, file_name): - return self.env.absurl(self.get_versioned_file(file_name)) + versioned = self.versioned_folder(file_name) + url = self.env.absurl(versioned) + if not path.exists(self.env.abspath(versioned)): + self.write_file(file_name) + return url def get_version(self, file_name): - return self.env.versions.determine_file_version(file_name, self.env) \ No newline at end of file + version = None + if self.env.manifest: + version = self.env.manifest.query_file(file_name, self.env) + if version is None: + version = self.env.versions.determine_file_version(file_name, self.env) + return version \ No newline at end of file diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index ee5e13d6..e266ace5 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -89,13 +89,17 @@ def replace_url(self, url): if self.env.manifest: file_path = urlpath.pathjoin(self.source_path, url) if self.env.external_assets: - file_name = self.env.external_assets.get_versioned_file(file_path) - replacement = urlpath.relpathto(self.env.directory, self.output_path, file_name) - + versioned_folder = self.env.external_assets.versioned_folder(file_path) + self.env.external_assets.write_file(file_path) + if self.env.url: + replacement = urlparse.urljoin(self.env.url, versioned_folder) + else: + replacement = urlpath.relpathto(self.env.directory, self.output_path, versioned_folder) + if replacement is None: url = urlpath.relpath(self.output_url, urlparse.urljoin(self.source_url, url)) else: - url = replacement.replace('img/','genimg/') + url = replacement return url From 383609c9f92a320fbca4498e17e9dc9425e1c535 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Tue, 24 Jul 2012 14:23:03 +0100 Subject: [PATCH 03/15] changed webassets.externalassets to webassets.external --- src/webassets/__init__.py | 2 +- src/webassets/{externalassets.py => external.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/webassets/{externalassets.py => external.py} (100%) diff --git a/src/webassets/__init__.py b/src/webassets/__init__.py index c6ea0759..bd545416 100644 --- a/src/webassets/__init__.py +++ b/src/webassets/__init__.py @@ -3,5 +3,5 @@ # Make a few frequently used things available right here. from bundle import Bundle -from externalassets import ExternalAssets +from external import ExternalAssets from env import Environment \ No newline at end of file diff --git a/src/webassets/externalassets.py b/src/webassets/external.py similarity index 100% rename from src/webassets/externalassets.py rename to src/webassets/external.py From 848dc382401474c4991f962dd23442efe60e3e8f Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Wed, 25 Jul 2012 10:34:04 +0100 Subject: [PATCH 04/15] first pass at bringing API of ExternalAssets more into line with Bundle --- src/webassets/env.py | 7 ++----- src/webassets/external.py | 39 ++++++++++++++++++++++++++++++--------- src/webassets/script.py | 21 ++++++++++----------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/webassets/env.py b/src/webassets/env.py index fc15e1c8..02fa642d 100644 --- a/src/webassets/env.py +++ b/src/webassets/env.py @@ -3,6 +3,7 @@ from itertools import chain import warnings from bundle import Bundle +from external import ExternalAssets from cache import get_cache from version import get_versioner, get_manifest from updater import get_updater @@ -158,10 +159,6 @@ def __contains__(self, name): def __len__(self): return len(self._named_bundles) + len(self._anon_bundles) - def register_externals(self, externals): - externals.env = self - self.external_assets = externals - def register(self, name, *args, **kwargs): """Register a bundle with the given name. @@ -179,7 +176,7 @@ def register(self, name, *args, **kwargs): if len(args) == 0: raise TypeError('at least two arguments are required') else: - if len(args) == 1 and not kwargs and isinstance(args[0], Bundle): + if len(args) == 1 and not kwargs and (isinstance(args[0], Bundle) or isinstance(args[0], ExternalAssets)): bundle = args[0] else: bundle = Bundle(*args, **kwargs) diff --git a/src/webassets/external.py b/src/webassets/external.py index 5828ef11..42c97ccd 100644 --- a/src/webassets/external.py +++ b/src/webassets/external.py @@ -16,24 +16,39 @@ class ExternalAssets(object): - def __init__(self, folders): - + def __init__(self, *folders, **options): + self.env = None self.folders = folders - #self.version = options.pop('version', []) + self.output = options.pop('output', None) + if options: + raise TypeError("got unexpected keyword argument '%s'" % + options.keys()[0]) + self.extra_data = {} + + def __repr__(self): + return "<%s folders=%s>" % ( + self.__class__.__name__, + self.folders, + ) def get_versioned_file(self, file_name): version = self.get_version(file_name) bits = file_name.split('.') bits.insert(len(bits)-1, version) return '.'.join(bits) - + def versioned_folder(self, file_name): - output_folder = self.env.config.get('external_assets_output_folder', None) + if self.output: + output_folder = self.output + else: + output_folder = self.env.config.get('external_assets_output_folder', None) if output_folder is None: - raise ExternalAssetsError('You must set the external_assets_output_folder config value') + raise ExternalAssetsError( + 'You must set an output folder for these ExternalAssets ' + 'or the external_assets_output_folder config value') versioned = self.get_versioned_file(file_name) return path.join(output_folder, path.basename(versioned)) - + def get_output_path(self, file_name): return self.env.abspath(self.versioned_folder(file_name)) @@ -47,7 +62,7 @@ def write_file(self, file_name): if self.env.manifest: self.env.manifest.remember_file(file_name, self.env, self.get_version(file_name)) - def write_files(self): + def build(self, env=None, force=None, disable_cache=None): for folder in self.folders: path = self.env.abspath(folder) for file_name in glob.glob(path): @@ -70,4 +85,10 @@ def get_version(self, file_name): version = self.env.manifest.query_file(file_name, self.env) if version is None: version = self.env.versions.determine_file_version(file_name, self.env) - return version \ No newline at end of file + return version + + @property + def is_container(self): + """ExternalAssets cannot be containers + """ + return False diff --git a/src/webassets/script.py b/src/webassets/script.py index fd3c1866..95f9ff73 100644 --- a/src/webassets/script.py +++ b/src/webassets/script.py @@ -125,14 +125,18 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None, raise CommandError( 'I do not know a bundle name named "%s".' % name) - # Make a list of bundles to build, and the filename to write to. + # Make a list of all the named bundles to build, and the filename to write to. + # TODO: It's not ok to use an internal property here. + bundles = [(n,b) for n, b in self.environment._named_bundles.items()] + if bundle_names: - # TODO: It's not ok to use an internal property here. - bundles = [(n,b) for n, b in self.environment._named_bundles.items() + # if bundle names have been specified, filter by those + bundles = [(n,b) for n, b in bundles if n in bundle_names] else: - # Includes unnamed bundles as well. - bundles = [(None, b) for b in self.environment] + # Otherwise include unnamed bundles as well, if not already in + bundles = bundles + [(None, b) for b in self.environment + if b not in [b for (n, b) in bundles]] # Determine common prefix for use with ``directory`` option. if directory: @@ -166,7 +170,7 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None, built = [] for bundle, overwrite_filename, name in to_build: if name: - # A name is not necessary available of the bundle was + # A name is not necessarily available if the bundle was # registered without one. self.log.info("Building bundle: %s (to %s)" % ( name, overwrite_filename or bundle.output)) @@ -197,11 +201,6 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None, except BuildError, e: self.log.error("Failed, error was: %s" % e) - # build external assets if we have any - if self.environment.external_assets: - self.log.info("Building external assets") - self.environment.external_assets.write_files() - if len(built): self.event_handlers['post_build']() From 725bf1fe6538afeadbf4344591a9b86a279fe354 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Wed, 25 Jul 2012 13:04:06 +0100 Subject: [PATCH 05/15] changed cssrewrite filter to accomodate external keyword for external assets --- src/webassets/filter/cssrewrite/__init__.py | 69 +++++++++++++++------ src/webassets/script.py | 14 ++++- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index e266ace5..1a64c33c 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -30,12 +30,19 @@ class CSSRewriteFilter(CSSUrlRewriter): No configuration is necessary. - The filter also supports a manual mode:: + The filter also supports a manual mode, using either ``replace`` or ``external``:: get_filter('cssrewrite', replace={'old_directory', '/custom/path/'}) This will rewrite all urls that point to files within ``old_directory`` to use ``/custom/path`` as a prefix instead. + + get_filter('cssrewrite', external='external_assets') + + This will rewrite all urls with versioned file names determined by the + ``ExternalAssets`` object registered with the environment with the name + ``external_assets``. + """ # TODO: If we want to support inline assets, this needs to be @@ -44,9 +51,10 @@ class CSSRewriteFilter(CSSUrlRewriter): name = 'cssrewrite' - def __init__(self, replace=False): + def __init__(self, replace=False, external=False): super(CSSRewriteFilter, self).__init__() self.replace = replace + self.external = external def unique(self): # Allow mixing the standard version of this filter, and replace mode. @@ -66,8 +74,14 @@ def input(self, _in, out, **kw): replace_dict[replurl] = sub self.replace_dict = replace_dict + if self.external not in (False, None): + self.external_assets = self.env.__getitem__(self.external) + return super(CSSRewriteFilter, self).input(_in, out, **kw) + def _is_abs_url(self, url): + return url.startswith('/') and (url.startswith('http://') or url.startswith('https://')) + def replace_url(self, url): # Replace mode: manually adjust the location of files if self.replace is not False: @@ -78,28 +92,43 @@ def replace_url(self, url): # Only apply the first match break - # Default mode: auto correct relative urls else: - # If path is an absolute one, keep it - if not url.startswith('/') and not (url.startswith('http://') or url.startswith('https://')): - # rewritten url: relative path from new location (output) - # to location of referenced file (source + current url) - # let's look in the manifest to see if we have a versioned url for this - replacement = None - if self.env.manifest: + # External mode: use an ExternalAssets object + # to work out replacements + if self.external is not False: + # If path is an absolute one, keep it + if not self._is_abs_url(url): + + replacement = None + file_path = urlpath.pathjoin(self.source_path, url) - if self.env.external_assets: - versioned_folder = self.env.external_assets.versioned_folder(file_path) - self.env.external_assets.write_file(file_path) - if self.env.url: - replacement = urlparse.urljoin(self.env.url, versioned_folder) + asset_path = self.external_assets.versioned_folder(file_path) + + if self.env.url: + # see if it's a complete url (rather than a folder) + # otherwise we want a relative path in the CSS + if self.env.url.startswith('http://')\ + or self.env.url.startswith('https://')\ + or self.env.url.startswith('//'): + replacement = urlparse.urljoin(self.env.url, asset_path) else: - replacement = urlpath.relpathto(self.env.directory, self.output_path, versioned_folder) - - if replacement is None: + replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.absurl(asset_path)) + else: + replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path) + + if replacement is None: + url = urlpath.relpath(self.output_url, + urlparse.urljoin(self.source_url, url)) + else: + url = replacement + + else: + # Default mode: auto correct relative urls + # If path is an absolute one, keep it + if not self._is_abs_url(url): + # rewritten url: relative path from new location (output) + # to location of referenced file (source + current url) url = urlpath.relpath(self.output_url, urlparse.urljoin(self.source_url, url)) - else: - url = replacement return url diff --git a/src/webassets/script.py b/src/webassets/script.py index 95f9ff73..dca99242 100644 --- a/src/webassets/script.py +++ b/src/webassets/script.py @@ -13,6 +13,7 @@ from webassets.exceptions import BuildError, ImminentDeprecationWarning from webassets.updater import TimestampUpdater from webassets.merge import MemoryHunk +from webassets.external import ExternalAssets __all__ = ('CommandError', 'CommandLineEnvironment', 'main') @@ -169,13 +170,20 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None, # Build. built = [] for bundle, overwrite_filename, name in to_build: + if isinstance(bundle, ExternalAssets): + bundle_type = 'external assets' + output_destination = bundle.output or\ + self.environment.config.get('external_assets_output_folder', None) + else: + bundle_type = 'bundle' + output_destination = overwrite_filename or bundle.output if name: # A name is not necessarily available if the bundle was # registered without one. - self.log.info("Building bundle: %s (to %s)" % ( - name, overwrite_filename or bundle.output)) + self.log.info("Building %s: %s (to %s)" % ( + bundle_type, name, output_destination)) else: - self.log.info("Building bundle: %s" % bundle.output) + self.log.info("Building %s: %s" % (bundle_type, output_destination)) try: if not overwrite_filename: From b02d7dd982d56e4206f70b5be10b7b2e67d25c5c Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Thu, 26 Jul 2012 09:57:40 +0100 Subject: [PATCH 06/15] use multiple external assets in cssrewrite filter --- src/webassets/filter/cssrewrite/__init__.py | 49 +++++++++++++-------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index 1a64c33c..2c0f1da8 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -1,6 +1,7 @@ import os, urlparse from os.path import join from webassets.utils import common_path_prefix +from webassets.external import ExternalAssets import urlpath try: from collections import OrderedDict @@ -51,10 +52,10 @@ class CSSRewriteFilter(CSSUrlRewriter): name = 'cssrewrite' - def __init__(self, replace=False, external=False): + def __init__(self, replace=False): super(CSSRewriteFilter, self).__init__() self.replace = replace - self.external = external + self.external = [] def unique(self): # Allow mixing the standard version of this filter, and replace mode. @@ -74,8 +75,10 @@ def input(self, _in, out, **kw): replace_dict[replurl] = sub self.replace_dict = replace_dict - if self.external not in (False, None): - self.external_assets = self.env.__getitem__(self.external) + # see if we have external assets in the environment + for bundle in self.env: + if isinstance(bundle, ExternalAssets): + self.external.append(bundle) return super(CSSRewriteFilter, self).input(_in, out, **kw) @@ -93,28 +96,36 @@ def replace_url(self, url): break else: - # External mode: use an ExternalAssets object - # to work out replacements - if self.external is not False: + # External mode: use ExternalAssets objects + # to fetch replacements + if len(self.external): # If path is an absolute one, keep it if not self._is_abs_url(url): replacement = None file_path = urlpath.pathjoin(self.source_path, url) - asset_path = self.external_assets.versioned_folder(file_path) - - if self.env.url: - # see if it's a complete url (rather than a folder) - # otherwise we want a relative path in the CSS - if self.env.url.startswith('http://')\ - or self.env.url.startswith('https://')\ - or self.env.url.startswith('//'): - replacement = urlparse.urljoin(self.env.url, asset_path) + asset_path = None + for external_assets in self.external: + # see if our file has a versioned version available + try: + asset_path = external_assets.versioned_folder(file_path) + break + except IOError: + pass + + if asset_path is not None: + if self.env.url: + # see if it's a complete url (rather than a folder) + # otherwise we want a relative path in the CSS + if self.env.url.startswith('http://')\ + or self.env.url.startswith('https://')\ + or self.env.url.startswith('//'): + replacement = urlparse.urljoin(self.env.url, asset_path) + else: + replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.absurl(asset_path)) else: - replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.absurl(asset_path)) - else: - replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path) + replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path) if replacement is None: url = urlpath.relpath(self.output_url, From 9a7caccf9b4a5be764cd0c817f2e8deeeb3cfbb3 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Thu, 26 Jul 2012 13:06:24 +0100 Subject: [PATCH 07/15] first part of shared file specification between Bundle and ExternalAssets --- src/webassets/bundle.py | 84 +---------------- src/webassets/container.py | 99 +++++++++++++++++++++ src/webassets/exceptions.py | 9 +- src/webassets/external.py | 22 +++-- src/webassets/filter/cssrewrite/__init__.py | 1 + src/webassets/version.py | 3 +- 6 files changed, 125 insertions(+), 93 deletions(-) create mode 100644 src/webassets/container.py diff --git a/src/webassets/bundle.py b/src/webassets/bundle.py index ba4b27bd..8bad0448 100644 --- a/src/webassets/bundle.py +++ b/src/webassets/bundle.py @@ -1,7 +1,6 @@ import os from os import path import urlparse -import os try: # Current version of glob2 does not let us access has_magic :/ @@ -15,24 +14,16 @@ from merge import (FileHunk, UrlHunk, FilterTool, merge, merge_filters, MoreThanOneFilterError) from updater import SKIP_CACHE +from container import Container, is_url from exceptions import BundleError, BuildError __all__ = ('Bundle', 'get_all_bundle_files',) - -def is_url(s): - if not isinstance(s, str): - return False - scheme = urlparse.urlsplit(s).scheme - return bool(scheme) and len(scheme) > 1 - - def has_placeholder(s): return '%(version)s' in s - -class Bundle(object): +class Bundle(Container): """A bundle is the unit webassets uses to organize groups of media files, which filters to apply and where to store them. @@ -55,6 +46,7 @@ class takes an ``env`` parameter - so a parent bundle can provide """ def __init__(self, *contents, **options): + super(Container, self).__init__() self.env = None self.contents = contents self.output = options.pop('output', None) @@ -95,76 +87,6 @@ def _set_filters(self, value): self._filters = [get_filter(f) for f in filters] filters = property(_get_filters, _set_filters) - def _get_contents(self): - return self._contents - def _set_contents(self, value): - self._contents = value - self._resolved_contents = None - contents = property(_get_contents, _set_contents) - - def resolve_contents(self, env=None, force=False): - """Convert bundle contents into something that can be easily processed. - - - Glob patterns are resolved - - Validate all the source paths to complain about missing files early. - - Third party extensions get to hook into this to provide a basic - virtualized filesystem. - - The return value is a list of 2-tuples (relpath, abspath). The first - element is the path that is assumed to be relative to the - ``Environment.directory`` value. We need it to construct urls to the - source files. - The second element is the absolute path to the actual location of the - file. Depending on the magic a third party extension does, this may be - somewhere completely different. - - URLs and nested Bundles are returned as a 2-tuple where both items are - the same. - - Set ``force`` to ignore any cache, and always re-resolve glob patterns. - """ - env = self._get_env(env) - - # TODO: We cache the values, which in theory is problematic, since - # due to changes in the env object, the result of the globbing may - # change. Not to mention that a different env object may be passed - # in. We should find a fix for this. - if getattr(self, '_resolved_contents', None) is None or force: - l = [] - for item in self.contents: - if isinstance(item, Bundle): - l.append((item, item)) - else: - if is_url(item): - # Is a URL - l.append((item, item)) - elif isinstance(item, basestring) and has_magic(item): - # Is globbed pattern - path = env.abspath(item) - for f in glob.glob(path): - if os.path.isdir(f): - continue - if self.output and env.abspath(self.output) == f: - # Exclude the output file. Note this will - # not work if nested bundles do the - # including. TODO: Should be even have this - # test if it doesn't work properly? Should - # be throw an error during building instead? - # Or can be give this method access to the - # parent bundle, since allowing env settings - # overrides in bundles is planned anyway? - continue - l.append((f[len(path)-len(item):], f)) - else: - # Is just a normal path; Send it through - # _normalize_source_path(). - try: - l.append((item, env._normalize_source_path(item))) - except IOError, e: - raise BundleError(e) - self._resolved_contents = l - return self._resolved_contents - def _get_depends(self): return self._depends def _set_depends(self, value): diff --git a/src/webassets/container.py b/src/webassets/container.py new file mode 100644 index 00000000..15eb9804 --- /dev/null +++ b/src/webassets/container.py @@ -0,0 +1,99 @@ +import urlparse +import os +from exceptions import ContainerError + +try: + # Current version of glob2 does not let us access has_magic :/ + import glob2 as glob + from glob import has_magic +except ImportError: + import glob + from glob import has_magic + +def is_url(s): + if not isinstance(s, str): + return False + scheme = urlparse.urlsplit(s).scheme + return bool(scheme) and len(scheme) > 1 + +class Container(object): + + def __init__(self): + pass + + def _get_contents(self): + return self._contents + def _set_contents(self, value): + self._contents = value + self._resolved_contents = None + contents = property(_get_contents, _set_contents) + + def _get_env(self, env): + # Note how bool(env) can be False, due to __len__. + env = env if env is not None else self.env + if env is None: + raise ContainerError('Container not connected to an environment') + return env + + def resolve_contents(self, env=None, force=False): + """Convert bundle contents into something that can be easily processed. + + - Glob patterns are resolved + - Validate all the source paths to complain about missing files early. + - Third party extensions get to hook into this to provide a basic + virtualized filesystem. + + The return value is a list of 2-tuples (relpath, abspath). The first + element is the path that is assumed to be relative to the + ``Environment.directory`` value. We need it to construct urls to the + source files. + The second element is the absolute path to the actual location of the + file. Depending on the magic a third party extension does, this may be + somewhere completely different. + + URLs and nested Bundles are returned as a 2-tuple where both items are + the same. + + Set ``force`` to ignore any cache, and always re-resolve glob patterns. + """ + env = self._get_env(env) + + # TODO: We cache the values, which in theory is problematic, since + # due to changes in the env object, the result of the globbing may + # change. Not to mention that a different env object may be passed + # in. We should find a fix for this. + if getattr(self, '_resolved_contents', None) is None or force: + l = [] + for item in self.contents: + if isinstance(item, Container): + l.append((item, item)) + else: + if is_url(item): + # Is a URL + l.append((item, item)) + elif isinstance(item, basestring) and has_magic(item): + # Is globbed pattern + path = env.abspath(item) + for f in glob.glob(path): + if os.path.isdir(f): + continue + if self.output and env.abspath(self.output) == f: + # Exclude the output file. Note this will + # not work if nested bundles do the + # including. TODO: Should be even have this + # test if it doesn't work properly? Should + # be throw an error during building instead? + # Or can be give this method access to the + # parent bundle, since allowing env settings + # overrides in bundles is planned anyway? + continue + l.append((f[len(path)-len(item):], f)) + else: + # Is just a normal path; Send it through + # _normalize_source_path(). + try: + l.append((item, env._normalize_source_path(item))) + except IOError, e: + raise BundleError(e) + self._resolved_contents = l + return self._resolved_contents diff --git a/src/webassets/exceptions.py b/src/webassets/exceptions.py index dd107959..6c9d2229 100644 --- a/src/webassets/exceptions.py +++ b/src/webassets/exceptions.py @@ -1,14 +1,19 @@ -__all__ = ('BundleError', 'BuildError', 'FilterError', - 'EnvironmentError', 'ImminentDeprecationWarning', 'ExternalAssetsError') +__all__ = ('BundleError', 'BuildError', 'ContainerError', 'FilterError', + 'EnvironmentError', 'ExternalAssetsError', 'ImminentDeprecationWarning', ) class EnvironmentError(Exception): pass + class ExternalAssetsError(Exception): pass +class ContainerError(Exception): + pass + + class BundleError(Exception): pass diff --git a/src/webassets/external.py b/src/webassets/external.py index 42c97ccd..640ea498 100644 --- a/src/webassets/external.py +++ b/src/webassets/external.py @@ -2,7 +2,8 @@ from os import path from merge import FileHunk -from exceptions import ExternalAssetsError +from exceptions import ExternalAssetsError, BuildError +from container import Container try: # Current version of glob2 does not let us access has_magic :/ @@ -14,11 +15,12 @@ __all__ = ('ExternalAssets',) -class ExternalAssets(object): +class ExternalAssets(Container): - def __init__(self, *folders, **options): + def __init__(self, *contents, **options): + super(Container, self).__init__() self.env = None - self.folders = folders + self.contents = contents self.output = options.pop('output', None) if options: raise TypeError("got unexpected keyword argument '%s'" % @@ -28,7 +30,7 @@ def __init__(self, *folders, **options): def __repr__(self): return "<%s folders=%s>" % ( self.__class__.__name__, - self.folders, + self.contents, ) def get_versioned_file(self, file_name): @@ -63,10 +65,12 @@ def write_file(self, file_name): self.env.manifest.remember_file(file_name, self.env, self.get_version(file_name)) def build(self, env=None, force=None, disable_cache=None): - for folder in self.folders: - path = self.env.abspath(folder) - for file_name in glob.glob(path): - self.write_file(file_name.replace('%s/' % self.env.abspath(''),'')) + # Prepare contents + resolved_contents = self.resolve_contents(env, force=True) + if not resolved_contents: + raise BuildError('empty external assets cannot be built') + for relpath, abspath in resolved_contents: + self.write_file(relpath) def show_manifest(self): if self.env.manifest: diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index 2c0f1da8..1b94f33b 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -79,6 +79,7 @@ def input(self, _in, out, **kw): for bundle in self.env: if isinstance(bundle, ExternalAssets): self.external.append(bundle) + #pass return super(CSSRewriteFilter, self).input(_in, out, **kw) diff --git a/src/webassets/version.py b/src/webassets/version.py index 911aebf4..331f8a6c 100644 --- a/src/webassets/version.py +++ b/src/webassets/version.py @@ -7,7 +7,8 @@ import os import pickle -from webassets.bundle import has_placeholder, is_url, get_all_bundle_files +from webassets.container import is_url +from webassets.bundle import has_placeholder, get_all_bundle_files from webassets.merge import FileHunk from webassets.utils import md5_constructor, RegistryMetaclass From fd305e2e33b6dd72b2b7de5a53912df214f939ae Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Tue, 16 Oct 2012 12:10:12 +0100 Subject: [PATCH 08/15] standalone external assets build works again after merge --- src/webassets/external.py | 33 +++++++++++++++++---------------- src/webassets/version.py | 1 - 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/webassets/external.py b/src/webassets/external.py index bce2b229..9bbf3b4e 100644 --- a/src/webassets/external.py +++ b/src/webassets/external.py @@ -5,16 +5,9 @@ from exceptions import ExternalAssetsError, BuildError from container import Container -try: - # Current version of glob2 does not let us access has_magic :/ - import glob2 as glob - from glob import has_magic -except ImportError: - import glob - from glob import has_magic - __all__ = ('ExternalAssets',) + class ExternalAssets(Container): def __init__(self, *contents, **options): @@ -36,7 +29,7 @@ def __repr__(self): def get_versioned_file(self, file_name): version = self.get_version(file_name) bits = file_name.split('.') - bits.insert(len(bits)-1, version) + bits.insert(len(bits) - 1, version) return '.'.join(bits) def versioned_folder(self, file_name): @@ -51,12 +44,12 @@ def versioned_folder(self, file_name): versioned = self.get_versioned_file(file_name) return path.join(output_folder, path.basename(versioned)) - def get_output_path(self, file_name): - return self.env.resolver.resolve_source(self.versioned_folder(file_name)) + def get_resolved_path(self, file_name): + return self.env.resolver.resolve_source(file_name) def write_file(self, file_name): - output_path = self.get_output_path(file_name) - hunk = FileHunk(self.env.abspath(file_name)) + hunk = FileHunk(file_name) + output_path = path.join(self.env.directory, self.versioned_folder(file_name)) output_dir = path.dirname(output_path) if not path.exists(output_dir): os.makedirs(output_dir) @@ -64,13 +57,20 @@ def write_file(self, file_name): if self.env.manifest: self.env.manifest.remember_file(file_name, self.env, self.get_version(file_name)) + def write_files(self, external_assets_path): + resolved_paths = self.get_resolved_path(external_assets_path) + if type(resolved_paths) is not list: + resolved_paths = [resolved_paths] + for path in resolved_paths: + self.write_file(path) + def build(self, env=None, force=None, disable_cache=None): # Prepare contents resolved_contents = self.resolve_contents(env, force=True) if not resolved_contents: raise BuildError('empty external assets cannot be built') for relpath, abspath in resolved_contents: - self.write_file(relpath) + self.write_files(relpath) def show_manifest(self): if self.env.manifest: @@ -79,8 +79,9 @@ def show_manifest(self): def url(self, file_name): versioned = self.versioned_folder(file_name) url = self.env.resolver.resolve_output_to_url(versioned) - if not path.exists(self.env.resolver.resolve_source(versioned)): - self.write_file(file_name) + file_path = self.env.resolver.resolve_source(file_name) + if not path.exists(file_path): + self.write_file(file_path) return url def get_version(self, file_name): diff --git a/src/webassets/version.py b/src/webassets/version.py index ef5dbdeb..f1083f03 100644 --- a/src/webassets/version.py +++ b/src/webassets/version.py @@ -157,7 +157,6 @@ def __init__(self, length=8, hash=md5_constructor): self.hasher = hash def determine_file_version(self, file_name, env): - print env.resolver.resolve_source(file_name) hunk = FileHunk(env.resolver.resolve_source(file_name)) hasher = self.hasher() hasher.update(hunk.data()) From 66d63262a5b92ff2519bf773fadc3711b3244e86 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Tue, 23 Oct 2012 13:29:41 +0100 Subject: [PATCH 09/15] use resolver method in url rewriter --- src/webassets/filter/cssrewrite/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index d63714a8..021ab738 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -120,12 +120,10 @@ def replace_url(self, url): if self.env.url: # see if it's a complete url (rather than a folder) # otherwise we want a relative path in the CSS - if self.env.url.startswith('http://')\ - or self.env.url.startswith('https://')\ - or self.env.url.startswith('//'): + if self._is_abs_url(self.env.url): replacement = urlparse.urljoin(self.env.url, asset_path) else: - replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.absurl(asset_path)) + replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.resolver.resolve_source(asset_path)) else: replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path) From 1c63c996fb999a192b2967d993951e644de14259 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Tue, 23 Oct 2012 15:40:36 +0100 Subject: [PATCH 10/15] jinja webasset tag --- src/webassets/ext/jinja2.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/webassets/ext/jinja2.py b/src/webassets/ext/jinja2.py index d6a041fa..a8f71346 100644 --- a/src/webassets/ext/jinja2.py +++ b/src/webassets/ext/jinja2.py @@ -4,12 +4,26 @@ import jinja2 from jinja2.ext import Extension from jinja2 import nodes -from webassets import Bundle +from jinja2.utils import environmentfunction +from webassets import Bundle, ExternalAssets from webassets.loaders import GlobLoader, LoaderError from webassets.exceptions import ImminentDeprecationWarning -__all__ = ('assets', 'Jinja2Loader',) +__all__ = ('assets', 'Jinja2Loader', 'webasset_tag') + + +@environmentfunction +def webasset_tag(env, file_path): + for bundle in env.assets_environment: + if isinstance(bundle, ExternalAssets): + # see if our file has a versioned version available + try: + asset_path = bundle.versioned_folder(file_path) + return env.assets_environment.resolver.resolve_output_to_url(asset_path) + break + except IOError: + return file_path class AssetsExtension(Extension): From 2fee10d4aceb78ad11fcf5b14fe842e81329ef16 Mon Sep 17 00:00:00 2001 From: Edward Stone Date: Wed, 24 Oct 2012 16:54:18 +0100 Subject: [PATCH 11/15] don't resolve contents twice --- src/webassets/external.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webassets/external.py b/src/webassets/external.py index 9bbf3b4e..d2965bea 100644 --- a/src/webassets/external.py +++ b/src/webassets/external.py @@ -69,8 +69,8 @@ def build(self, env=None, force=None, disable_cache=None): resolved_contents = self.resolve_contents(env, force=True) if not resolved_contents: raise BuildError('empty external assets cannot be built') - for relpath, abspath in resolved_contents: - self.write_files(relpath) + for path in self.contents: + self.write_files(path) def show_manifest(self): if self.env.manifest: From 39ee62835e8d90ff42cb567d29aef5da5c983e18 Mon Sep 17 00:00:00 2001 From: Erik Simmler Date: Tue, 4 Dec 2012 21:52:09 -0500 Subject: [PATCH 12/15] We want to see if the versioned file exists, not the source --- src/webassets/external.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webassets/external.py b/src/webassets/external.py index d2965bea..5fbd0ee0 100644 --- a/src/webassets/external.py +++ b/src/webassets/external.py @@ -80,7 +80,8 @@ def url(self, file_name): versioned = self.versioned_folder(file_name) url = self.env.resolver.resolve_output_to_url(versioned) file_path = self.env.resolver.resolve_source(file_name) - if not path.exists(file_path): + versioned_path = self.env.resolver.resolve_output_to_path(versioned, self) + if not path.exists(versioned_path): self.write_file(file_path) return url From c49de0ce3c0a4f80f3c8aa455af84645f963efa4 Mon Sep 17 00:00:00 2001 From: Erik Simmler Date: Tue, 4 Dec 2012 21:53:29 -0500 Subject: [PATCH 13/15] Fix for issue with load_path settings causing a list of paths to be returned when a single was expected --- src/webassets/env.py | 11 +++++++++++ src/webassets/external.py | 2 +- src/webassets/filter/cssrewrite/__init__.py | 2 +- src/webassets/version.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/webassets/env.py b/src/webassets/env.py index d41a328b..0929a2de 100644 --- a/src/webassets/env.py +++ b/src/webassets/env.py @@ -270,6 +270,17 @@ def resolve_source(self, item): return self.search_for_source(item) + def resolve_source_to_path(self, file_name): + """Given ``item`` from a Bundle's contents, this has to + return the final value to use, usually an absolute + filesystem path. Unlike :meth:`search_for_source` this + will only return the first matching path it finds. + """ + source = self.resolve_source(file_name) + if isinstance(source, list): + return source[0] + return source + def resolve_output_to_path(self, target, bundle): """Given ``target``, this has to return the absolute filesystem path to which the output file of ``bundle`` diff --git a/src/webassets/external.py b/src/webassets/external.py index 5fbd0ee0..b17f594f 100644 --- a/src/webassets/external.py +++ b/src/webassets/external.py @@ -79,7 +79,7 @@ def show_manifest(self): def url(self, file_name): versioned = self.versioned_folder(file_name) url = self.env.resolver.resolve_output_to_url(versioned) - file_path = self.env.resolver.resolve_source(file_name) + file_path = self.env.resolver.resolve_source_to_path(file_name) versioned_path = self.env.resolver.resolve_output_to_path(versioned, self) if not path.exists(versioned_path): self.write_file(file_path) diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index 021ab738..696555d6 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -123,7 +123,7 @@ def replace_url(self, url): if self._is_abs_url(self.env.url): replacement = urlparse.urljoin(self.env.url, asset_path) else: - replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.resolver.resolve_source(asset_path)) + replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.resolver.resolve_source_to_path(asset_path)) else: replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path) diff --git a/src/webassets/version.py b/src/webassets/version.py index f1083f03..d09b8e39 100644 --- a/src/webassets/version.py +++ b/src/webassets/version.py @@ -157,7 +157,7 @@ def __init__(self, length=8, hash=md5_constructor): self.hasher = hash def determine_file_version(self, file_name, env): - hunk = FileHunk(env.resolver.resolve_source(file_name)) + hunk = FileHunk(env.resolver.resolve_source_to_path(file_name)) hasher = self.hasher() hasher.update(hunk.data()) return hasher.hexdigest()[:self.length] From 6fb0d3457af7c0247cd670a1150934e9a8ccbcad Mon Sep 17 00:00:00 2001 From: Erik Simmler Date: Tue, 4 Dec 2012 22:55:18 -0500 Subject: [PATCH 14/15] Added minimal starting docs for external assets --- docs/external_assets.rst | 46 ++++++++++++++++++++++++++++++++++++++++ docs/generic/index.rst | 1 + docs/index.rst | 1 + 3 files changed, 48 insertions(+) create mode 100644 docs/external_assets.rst diff --git a/docs/external_assets.rst b/docs/external_assets.rst new file mode 100644 index 00000000..426c5c14 --- /dev/null +++ b/docs/external_assets.rst @@ -0,0 +1,46 @@ +.. _external_assets: + +=============== +External Assets +=============== + +An external assets bundle is used to manage images, webfonts and other assets +that you wouldn't normally include in another bundle. Files will have a cache +buster applied (see :doc:`URL Expiry `), and the +:ref:`cssrewrite ` filter can modify css files to point to +the versioned filenames. + + +Registering external files +-------------------------- + +An external assets bundle takes any number of input patterns and one output +directory. + +.. code-block:: python + + ExternalAssets('images/*', 'more_images/*', output='versioned_images') + +The output directory is relative to the ``directory`` setting of your +:doc:`environment `. All files found matching the input patterns +will be copied (with rewritten filenames) to this directory. + + +Using rewritten files +--------------------- + +CSS files using the :ref:`cssrewrite ` filter will be +automatically adapted to use the versioned filenames. + +.. code-block:: python + + Bundle('style.css', filters=['cssrewrite']) + + +If you need to get the specific url for a file, you can request it from the +bundle directly using :meth:`ExternalAssets.url` directly. + +.. code-block:: python + + >>> env['images'].url('logo.png') + /static/logo.c49de0ce.png diff --git a/docs/generic/index.rst b/docs/generic/index.rst index 9279b1db..6decab63 100644 --- a/docs/generic/index.rst +++ b/docs/generic/index.rst @@ -89,6 +89,7 @@ Further Reading /environment /bundles + /external_assets /script /builtin_filters /custom_filters diff --git a/docs/index.rst b/docs/index.rst index 4a98e987..6e8ddc32 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ of framework used: environment bundles + external_assets script builtin_filters expiring From d97bf7e40cc4899d8bed78b7049af3ab9825a2a4 Mon Sep 17 00:00:00 2001 From: Erik Simmler Date: Sat, 8 Dec 2012 15:46:53 -0500 Subject: [PATCH 15/15] It appears that we're doing some uneeded duplicate coverting, and this causes it to often fail. --- src/webassets/filter/cssrewrite/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webassets/filter/cssrewrite/__init__.py b/src/webassets/filter/cssrewrite/__init__.py index 696555d6..0a16f3ee 100644 --- a/src/webassets/filter/cssrewrite/__init__.py +++ b/src/webassets/filter/cssrewrite/__init__.py @@ -123,7 +123,7 @@ def replace_url(self, url): if self._is_abs_url(self.env.url): replacement = urlparse.urljoin(self.env.url, asset_path) else: - replacement = urlpath.relpathto(self.env.directory, self.output_path, self.env.resolver.resolve_source_to_path(asset_path)) + replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path) else: replacement = urlpath.relpathto(self.env.directory, self.output_path, asset_path)