Skip to content

Commit

Permalink
Merge branch 'hotfix/0.7.1' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
miracle2k committed May 31, 2012
2 parents ddaa680 + 80c48e1 commit 8143c5f
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 19 deletions.
4 changes: 2 additions & 2 deletions docs/upgrading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ When upgrading from an older version, you might encounter some backwards
incompatibility. The ``webassets`` API is not stable yet.


In Development version
~~~~~~~~~~~~~~~~~~~~~~
In 0.7
~~~~~~

There are some significant backwards incompatible changes in this release.

Expand Down
21 changes: 11 additions & 10 deletions src/webassets/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
def is_url(s):
if not isinstance(s, str):
return False
scheme = urlparse.urlsplit(s).scheme
return bool(scheme) and len(scheme) > 1
parsed = urlparse.urlsplit(s)
return bool(parsed.scheme and parsed.netloc) and len(parsed.scheme) > 1


def has_placeholder(s):
Expand Down Expand Up @@ -376,16 +376,13 @@ def _merge_and_apply(self, env, output, force, parent_debug=None,
combined_filters, disable_cache=disable_cache)
hunks.append(hunk)
else:
# Pass along the original relative path, as specified by the
# user. This may differ from the actual filesystem path, if
# extensions provide a virtualized filesystem (e.g. Flask
# blueprints, Django staticfiles).
kwargs = {'source': rel_name}

# Give a filter the chance to open his file.
try:
hunk = filtertool.apply_func(
combined_filters, 'open', [item], kwargs=kwargs)
combined_filters, 'open', [item],
# Also pass along the original relative path, as
# specified by the user, before resolving.
kwargs={'source': rel_name})
except MoreThanOneFilterError, e:
raise BuildError(e)

Expand All @@ -399,7 +396,11 @@ def _merge_and_apply(self, env, output, force, parent_debug=None,
hunks.append(hunk)
else:
hunks.append(filtertool.apply(
hunk, combined_filters, 'input', kwargs=kwargs))
hunk, combined_filters, 'input',
# Pass along both the original relative path, as
# specified by the user, and the one that has been
# resolved to a filesystem location.
kwargs={'source': rel_name, 'source_path': item}))

# Merge the individual files together.
try:
Expand Down
2 changes: 0 additions & 2 deletions src/webassets/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,6 @@ def apply(self, hunk, filters, type, kwargs=None):
def func():
kwargs_final = self.kwargs.copy()
kwargs_final.update(kwargs or {})
if hasattr(hunk, 'filename'):
kwargs_final.setdefault('source_path', hunk.filename)

data = StringIO.StringIO(hunk.data())
for filter in filters:
Expand Down
22 changes: 17 additions & 5 deletions src/webassets/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,22 @@ def build(self, bundles=None, output=None, directory=None, no_cache=None,
if len(built):
self.event_handlers['post_build']()

def watch(self):
def watch(self, loop=None):
"""Watch assets for changes.
TODO: This should probably also restart when the code changes.
``loop``
A callback, taking no arguments, to be called once every loop
iteration. Can be useful to integrate the command with other code.
If not specified, the loop wil call ``time.sleep()``.
"""
# TODO: This should probably also restart when the code changes.
_mtimes = {}
_win = (sys.platform == "win32")
def check_for_changes():
# Do not update original mtimes dict right away, so that we detect
# all bundle changes if a file is in multiple bundles.
_new_mtimes = _mtimes.copy()

changed_bundles = []
for bundle in self.environment:
for filename in get_all_bundle_files(bundle):
Expand All @@ -217,9 +225,11 @@ def check_for_changes():

if _mtimes.get(filename, mtime) != mtime:
changed_bundles.append(bundle)
_mtimes[filename] = mtime
_new_mtimes[filename] = mtime
break
_mtimes[filename] = mtime
_new_mtimes[filename] = mtime

_mtimes.update(_new_mtimes)
return changed_bundles

try:
Expand All @@ -236,7 +246,9 @@ def check_for_changes():
print "Failed: %s" % e
if len(built):
self.event_handlers['post_build']()
time.sleep(0.1)
do_end = loop() if loop else time.sleep(0.1)
if do_end:
break
except KeyboardInterrupt:
pass

Expand Down
24 changes: 24 additions & 0 deletions tests/test_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,23 @@ def concat(self, out, hunks, **kw):
self.mkbundle('a', 'b', filters=ConcatFilter, output='out').build()
assert self.get('out') == '1%%%2'

def test_open_before_input(self):
"""[Regression] Test that if an open filter is used, input filters
still receive the ``source_path`` kwargs.
"""
captured_kw = {}
class TestFilter(Filter):
def open(self, out, *a, **kw): out.write('foo')
def input(self, *a, **kw):
assert not captured_kw
captured_kw.update(kw)
self.create_files({'a': '1'})
self.mkbundle('a', filters=TestFilter, output='out').build()
# TODO: Could be generalized to test all the other values that
# each filter method expects to receive. This is currently not
# done anywhere )though it likely still wouldn't have caught this).
assert 'source_path' in captured_kw


class TestAutoUpdate(TempEnvironmentHelper):
"""Test bundle auto rebuild, and generally everything involving
Expand Down Expand Up @@ -1261,6 +1278,13 @@ def test_autorebuild_updaters(self):
bundle = self.mkbundle('http://foo', output='out')
TimestampUpdater().needs_rebuild(bundle, bundle.env)

def test_pyramid_asset_specs(self):
"""Make sure that pyramid asset specs (in the form of
package:path) do not pass the url check."""
self.create_files({'foo:bar/qux': 'test'})
self.mkbundle('foo:bar/qux', output='out').build()
assert self.get('out') == 'test'


class TestNormalizeSourcePath(TempEnvironmentHelper):
"""The Environment class allows overriding a
Expand Down
88 changes: 88 additions & 0 deletions tests/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@

from __future__ import with_statement

from os import path
import logging
from threading import Thread, Event
from nose.tools import assert_raises
import time
from webassets import Bundle
from webassets.script import main, CommandLineEnvironment, CommandError
from webassets.test import TempEnvironmentHelper
Expand Down Expand Up @@ -137,3 +140,88 @@ def test_manifest(self):
# Use prefix syntax
self.cmd_env.build(manifest='file:miau')
assert self.exists('media/sub/miau')


class TestWatchCommand(TestCLI):
"""This is a hard one to test.
We run the watch command in a thread, and rely on it's ``loop`` argument
to stop the thread again.
"""

default_files = {'in': 'foo', 'out': 'bar'}

def watch_loop(self):
# Hooked into the loop of the ``watch`` command.
# Allows stopping the thread.
self.has_looped.set()
time.sleep(0.01)
if getattr(self, 'stopped', False):
return True

def start_watching(self):
"""Run the watch command in a thread."""
self.has_looped = Event()
t = Thread(target=self.cmd_env.watch, kwargs={'loop': self.watch_loop})
t.daemon = True # In case something goes wrong with stopping, this
# will allow the test process to be end nonetheless.
t.start()
self.t = t
# Wait for first iteration, which will initialize the mtimes. Only
# after this will ``watch`` be able to detect changes.
self.has_looped.wait(1)

def stop_watching(self):
"""Stop the watch command thread."""
self.stopped = True
self.t.join(1)

def __enter__(self):
self.start_watching()

def __exit__(self, exc_type, exc_val, exc_tb):
self.stop_watching()

def test(self):
# Register a bundle to watch
bundle = self.mkbundle('in', output='out')
self.env.register('test', bundle)
now = self.setmtime('in', 'out')

# Assert initial state
assert self.get('out') == 'bar'

# While watch is running, change input mtime
with self:
self.setmtime('in', mtime=now+10)
# Allow watch to pick up the change
time.sleep(0.2)

# output file has been updated.
assert self.get('out') == 'foo'


def test_same_file_multiple_bundles(self):
"""[Bug] Test watch command can deal with the same file being part
of multiple bundles. This was not always the case (github-127).
"""
self.create_files({'out2': 'bar'})
bundle1 = self.mkbundle('in', output='out')
bundle2 = self.mkbundle('in', output='out2')
self.env.register('test1', bundle1)
self.env.register('test2', bundle2)
now = self.setmtime('in', 'out', 'out2')

# Assert initial state
assert self.get('out') == 'bar'
assert self.get('out2') == 'bar'

# While watch is running, change input mtime
with self:
self.setmtime('in', mtime=now+10)
# Allow watch to pick up the change
time.sleep(0.2)

# Both output files have been updated.
assert self.get('out') == 'foo'
assert self.get('out2') == 'foo'

0 comments on commit 8143c5f

Please sign in to comment.