diff options
author | Rocky Meza <rocky@fusionbox.com> | 2015-03-03 03:41:33 -0700 |
---|---|---|
committer | Rocky Meza <rocky@fusionbox.com> | 2015-04-22 16:07:00 -0600 |
commit | 518098530e4ab9c2f33303ba00f34a1575c7ba3c (patch) | |
tree | 855b44da4f5504cb2840e1785d621beb62c10611 | |
parent | 313c0e02b8d7b43ab534dd2491e07e37deeb17d3 (diff) | |
download | django-pyscss-518098530e4ab9c2f33303ba00f34a1575c7ba3c.tar.gz |
Add pyScss 1.3 and Python 3 support
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 37 | ||||
-rw-r--r-- | README.rst | 95 | ||||
-rw-r--r-- | django_pyscss/__init__.py | 1 | ||||
-rw-r--r-- | django_pyscss/compiler.py | 58 | ||||
-rw-r--r-- | django_pyscss/compressor.py | 13 | ||||
-rw-r--r-- | django_pyscss/extension/__init__.py | 0 | ||||
-rw-r--r-- | django_pyscss/extension/django.py | 44 | ||||
-rw-r--r-- | django_pyscss/scss.py | 223 | ||||
-rw-r--r-- | django_pyscss/utils.py | 28 | ||||
-rw-r--r-- | setup.py | 5 | ||||
-rw-r--r-- | testproject/runtests.py | 1 | ||||
-rw-r--r-- | tests/test_compressor.py | 2 | ||||
-rw-r--r-- | tests/test_scss.py | 55 | ||||
-rw-r--r-- | tests/utils.py | 17 | ||||
-rw-r--r-- | tox.ini | 21 |
16 files changed, 298 insertions, 304 deletions
@@ -4,3 +4,5 @@ tmp/ .coverage htmlcov/ /dist/ +.eggs +.tox diff --git a/.travis.yml b/.travis.yml index 8e31eda..9bfe446 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,23 @@ +sudo: false language: python python: - "2.7" - - "2.6" - # - "3.3" +cache: + directories: + - $HOME/.pip-cache/ env: - - DJANGO_PACKAGE='Django>=1.5,<1.6' - - DJANGO_PACKAGE='Django>=1.6,<1.7' - - DJANGO_PACKAGE='Django>=1.7,<1.8' -matrix: - include: - - python: "2.6" - env: DJANGO_PACKAGE='Django>=1.4,<1.5' - - python: "2.7" - env: DJANGO_PACKAGE='Django>=1.4,<1.5' - exclude: - - python: "2.6" - env: DJANGO_PACKAGE='Django>=1.7,<1.8' + - TOX_ENV=py26-dj14 + - TOX_ENV=py27-dj14 + - TOX_ENV=py27-dj17 + - TOX_ENV=py27-dj18 + - TOX_ENV=py33-dj17 + - TOX_ENV=py33-dj18 + - TOX_ENV=py34-dj17 + - TOX_ENV=py34-dj18 install: - - pip install -q $DJANGO_PACKAGE --use-mirrors - - pip install --use-mirrors . - - pip install coveralls + - pip install --upgrade pip + - pip install tox==1.8.0 script: - - coverage run --source=django_pyscss setup.py test -after_success: - coveralls + - tox -e $TOX_ENV +after_script: + - cat .tox/$TOX_ENV/log/*.log @@ -11,6 +11,23 @@ A collection of tools for making it easier to use pyScss within Django. :target: https://coveralls.io/r/fusionbox/django-pyscss :alt: Coverage Status + +.. note:: + + This version only supports pyScss 1.3.4 and greater. For pyScss 1.2 support, + you can use the 1.x series of django-pyscss. + + +Installation +============ + +django-pyscss supports Django 1.4+, and Pythons 2 and 3. + +You may install django-pyscss off of PyPI:: + + pip install django-pyscss + + Why do we need this? ==================== @@ -20,7 +37,7 @@ This app smooths over a lot of things when dealing with pyScss in Django. It can import SCSS files from any app (or any file that's findable by the STATICFILES_FINDERS) with no hassle. -- Configures pyScss to work with the staticfiles app for it's image functions +- Configures pyScss to work with the staticfiles app for its image functions (e.g. inline-image and sprite-map). - It provides a django-compressor precompile filter class so that you can @@ -34,41 +51,52 @@ This app smooths over a lot of things when dealing with pyScss in Django. It Rendering SCSS manually ======================= -You can render SCSS manually from a string like this:: +You can render SCSS manually from a string like this: - from django_pyscss.scss import DjangoScss +.. code-block:: python - compiler = DjangoScss() - compiler.compile(scss_string=".foo { color: green; }") + from django_pyscss import DjangoScssCompiler -You can render SCSS from a file like this:: + compiler = DjangoScssCompiler() + compiler.compile_string(".foo { color: green; }") - from django_pyscss.scss import DjangoScss +You can render SCSS from a file like this: - compiler = DjangoScss() - compiler.compile(scss_file='css/styles.scss') +.. code-block:: python + + from django_pyscss import DjangoScssCompiler + + compiler = DjangoScssCompiler() + compiler.compile('css/styles.scss') The file needs to be able to be located by staticfiles finders in order to be used. +The ``DjangoScssCompiler`` class is a subclass of ``scss.Compiler`` that +injects the ``DjangoExtension``. ``DjangoExtension`` is what overrides the +import mechanism. + +``DjangoScssCompiler`` also turns on the CompassExtension by default, if you +wish to turn this off you do so: -.. class:: django_pyscss.scss.DjangoScss +.. code-block:: python - A subclass of :class:`scss.Scss` that uses the Django staticfiles storage - and finders instead of the filesystem. This obsoletes the load_paths - option that was present previously by searching instead in your staticfiles - directories. + from django_pyscss import DjangoScssCompiler + from django_pyscss.extensions.django import DjangoExtension - In DEBUG mode, DjangoScss will search using all of the finders to find the - file. If you are not in DEBUG mode, it assumes you have run collectstatic - and will only use staticfiles_storage to find the file. + compiler = DjangoScssCompiler(extensions=[DjangoExtension]) +For a list of options that ``DjangoScssCompiler`` accepts, please see the +pyScss `API documentation <http://pyscss.readthedocs.org/en/latest/python-api.html#new-api>`_. -Using in conjunction with django-compressor. -============================================ + +Using in conjunction with django-compressor +=========================================== django-pyscss comes with support for django-compressor. All you have to do is -add it to your ``COMPRESS_PRECOMPILERS`` setting. :: +add it to your ``COMPRESS_PRECOMPILERS`` setting. : + +.. code-block:: python COMPRESS_PRECOMPILERS = ( # ... @@ -76,12 +104,37 @@ add it to your ``COMPRESS_PRECOMPILERS`` setting. :: # ... ) -Then you can just use SCSS like you would use CSS normally. :: +Then you can just use SCSS like you would use CSS normally. : + +.. code-block:: html+django {% compress css %} <link rel="stylesheet" type="text/x-scss" href="{% static 'css/styles.css' %}"> {% endcompress %} +If you wish to provide your own compiler instance (for example if you wanted to +change some settings on the ``DjangoScssCompiler``), you can subclass +``DjangoScssFilter``. : + +.. code-block:: python + + # myproject/scss_filter.py + from django_pyscss import DjangoScssCompiler + from django_pyscss.compressor import DjangoScssFilter + + class MyDjangoScssFilter(DjangoScssFilter): + compiler = DjangoScssCompiler( + # Example configuration + output_style='compressed', + ) + + # settings.py + COMPRESS_PRECOMPILERS = ( + # ... + ('text/x-scss', 'myproject.scss_filter.MyDjangoScssFilter'), + # ... + ) + Running the tests ================= diff --git a/django_pyscss/__init__.py b/django_pyscss/__init__.py index e69de29..a31ff85 100644 --- a/django_pyscss/__init__.py +++ b/django_pyscss/__init__.py @@ -0,0 +1 @@ +from .compiler import DjangoScssCompiler # NOQA diff --git a/django_pyscss/compiler.py b/django_pyscss/compiler.py new file mode 100644 index 0000000..cf20595 --- /dev/null +++ b/django_pyscss/compiler.py @@ -0,0 +1,58 @@ +from __future__ import absolute_import + +import os +from pathlib import PurePath + +from django.utils.six.moves import StringIO + +from django.conf import settings +from django.contrib.staticfiles.storage import staticfiles_storage + +from scss import Compiler, config +from scss.extension.compass import CompassExtension +from scss.source import SourceFile + +from .extension.django import DjangoExtension +from .utils import find_all_files, get_file_and_storage + + +# TODO: It's really gross to modify this global settings variable. +# This is where PyScss is supposed to find the image files for making sprites. +config.STATIC_ROOT = find_all_files +config.STATIC_URL = staticfiles_storage.url('scss/') + +# This is where PyScss places the sprite files. +config.ASSETS_ROOT = os.path.join(settings.STATIC_ROOT, 'scss', 'assets') +# PyScss expects a trailing slash. +config.ASSETS_URL = staticfiles_storage.url('scss/assets/') + + +class DjangoScssCompiler(Compiler): + def __init__(self, **kwargs): + kwargs.setdefault('extensions', (DjangoExtension, CompassExtension)) + if not os.path.exists(config.ASSETS_ROOT): + os.makedirs(config.ASSETS_ROOT) + super(DjangoScssCompiler, self).__init__(**kwargs) + + def compile(self, *paths): + compilation = self.make_compilation() + for path in paths: + path = PurePath(path) + if path.is_absolute(): + path = path.relative_to('/') + filename, storage = get_file_and_storage(str(path)) + with storage.open(filename) as f: + source = SourceFile.from_file(f, origin=path.parent, relpath=PurePath(path.name)) + compilation.add_source(source) + return self.call_and_catch_errors(compilation.run) + + def compile_string(self, string, filename=None): + compilation = self.make_compilation() + if filename is not None: + f = StringIO(string) + filename = PurePath(filename) + source = SourceFile.from_file(f, origin=filename.parent, relpath=PurePath(filename.name)) + else: + source = SourceFile.from_string(string) + compilation.add_source(source) + return self.call_and_catch_errors(compilation.run) diff --git a/django_pyscss/compressor.py b/django_pyscss/compressor.py index 78d5fd0..68aec24 100644 --- a/django_pyscss/compressor.py +++ b/django_pyscss/compressor.py @@ -1,15 +1,13 @@ from __future__ import absolute_import -import os - from compressor.filters import FilterBase from compressor.conf import settings -from django_pyscss.scss import DjangoScss +from django_pyscss import DjangoScssCompiler class DjangoScssFilter(FilterBase): - compiler = DjangoScss() + compiler = DjangoScssCompiler() def __init__(self, content, attrs=None, filter_type=None, filename=None, **kwargs): # It looks like there is a bug in django-compressor because it expects @@ -21,10 +19,9 @@ class DjangoScssFilter(FilterBase): href = attrs['href'] except KeyError: # this is a style tag which means this is inline SCSS. - self.relative_to = None + self.filename = None else: - self.relative_to = os.path.dirname(href.replace(settings.STATIC_URL, '')) + self.filename = href.replace(settings.STATIC_URL, '') def input(self, **kwargs): - return self.compiler.compile(scss_string=self.content, - relative_to=self.relative_to) + return self.compiler.compile_string(self.content, filename=self.filename) diff --git a/django_pyscss/extension/__init__.py b/django_pyscss/extension/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/django_pyscss/extension/__init__.py diff --git a/django_pyscss/extension/django.py b/django_pyscss/extension/django.py new file mode 100644 index 0000000..c127270 --- /dev/null +++ b/django_pyscss/extension/django.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, unicode_literals + +from itertools import product +from pathlib import PurePath + +from scss.extension.core import CoreExtension +from scss.source import SourceFile + +from ..utils import get_file_and_storage + + +class DjangoExtension(CoreExtension): + name = 'django' + + def handle_import(self, name, compilation, rule): + """ + Re-implementation of the core Sass import mechanism, which looks for + files using the staticfiles storage and staticfiles finders. + """ + original_path = PurePath(name) + + if original_path.suffix: + search_exts = [original_path.suffix] + else: + search_exts = compilation.compiler.dynamic_extensions + + if original_path.is_absolute(): + # Remove the beginning slash + search_path = original_path.relative_to('/').parent + elif rule.source_file.origin: + search_path = rule.source_file.origin + else: + search_path = original_path.parent + + basename = original_path.stem + + for prefix, suffix in product(('_', ''), search_exts): + filename = PurePath(prefix + basename + suffix) + + full_filename, storage = get_file_and_storage(str(search_path / filename)) + + if full_filename: + with storage.open(full_filename) as f: + return SourceFile.from_file(f, origin=search_path, relpath=filename) diff --git a/django_pyscss/scss.py b/django_pyscss/scss.py deleted file mode 100644 index b4c5981..0000000 --- a/django_pyscss/scss.py +++ /dev/null @@ -1,223 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import os -from itertools import product - -from django.contrib.staticfiles.storage import staticfiles_storage -from django.conf import settings - -from scss import ( - Scss, dequote, log, SourceFile, SassRule, config, -) - -from django_pyscss.utils import find_all_files - - -# TODO: It's really gross to modify this global settings variable. -# This is where PyScss is supposed to find the image files for making sprites. -config.STATIC_ROOT = find_all_files -config.STATIC_URL = staticfiles_storage.url('scss/') - -# This is where PyScss places the sprite files. -config.ASSETS_ROOT = os.path.join(settings.STATIC_ROOT, 'scss', 'assets') -# PyScss expects a trailing slash. -config.ASSETS_URL = staticfiles_storage.url('scss/assets/') - - -class DjangoScss(Scss): - """ - A subclass of the Scss compiler that uses the storages API for accessing - files. - """ - supported_extensions = ['.scss', '.sass', '.css'] - - def get_file_from_storage(self, filename): - try: - filename = staticfiles_storage.path(filename) - except NotImplementedError: - # remote storages don't implement path - pass - if staticfiles_storage.exists(filename): - return filename, staticfiles_storage - else: - return None, None - - def get_file_from_finders(self, filename): - for file_and_storage in find_all_files(filename): - return file_and_storage - return None, None - - def get_file_and_storage(self, filename): - # TODO: the switch probably shouldn't be on DEBUG - if settings.DEBUG: - return self.get_file_from_finders(filename) - else: - return self.get_file_from_storage(filename) - - def get_possible_import_paths(self, path, relative_to=None): - """ - Returns an iterable of possible paths for an import. - - relative_to is None in the case that the SCSS is being rendered from a - string or if it is the first file. - """ - paths = [] - - if path.startswith('/'): # absolute import - path = path[1:] - elif relative_to: # relative import - path = os.path.join(relative_to, path) - - dirname, filename = os.path.split(path) - name, ext = os.path.splitext(filename) - if ext: - search_exts = [ext] - else: - search_exts = self.supported_extensions - for prefix, suffix in product(('_', ''), search_exts): - paths.append(os.path.join(dirname, prefix + name + suffix)) - paths.append(path) - return paths - - def _find_source_file(self, filename, relative_to=None): - paths = self.get_possible_import_paths(filename, relative_to) - log.debug('Searching for %s in %s', filename, paths) - for name in paths: - full_filename, storage = self.get_file_and_storage(name) - if full_filename: - if full_filename not in self.source_file_index: - with storage.open(full_filename) as f: - source = f.read() - - source_file = SourceFile( - full_filename, - source, - ) - # SourceFile.__init__ calls os.path.realpath on this, we don't want - # that, we want them to remain relative. - source_file.parent_dir = os.path.dirname(name) - self.source_files.append(source_file) - self.source_file_index[full_filename] = source_file - return self.source_file_index[full_filename] - - def _do_import(self, rule, scope, block): - """ - Implements @import using the django storages API. - """ - # Protect against going to prohibited places... - if any(scary_token in block.argument for scary_token in ('..', '://', 'url(')): - rule.properties.append((block.prop, None)) - return - - full_filename = None - names = block.argument.split(',') - for name in names: - name = dequote(name.strip()) - - relative_to = rule.source_file.parent_dir - source_file = self._find_source_file(name, relative_to) - - if source_file is None: - i_codestr = self._do_magic_import(rule, scope, block) - - if i_codestr is not None: - source_file = SourceFile.from_string(i_codestr) - self.source_files.append(source_file) - self.source_file_index[full_filename] = source_file - - if source_file is None: - log.warn("File to import not found or unreadable: '%s' (%s)", name, rule.file_and_line) - continue - - import_key = (name, source_file.parent_dir) - if rule.namespace.has_import(import_key): - # If already imported in this scope, skip - continue - - _rule = SassRule( - source_file=source_file, - lineno=block.lineno, - import_key=import_key, - unparsed_contents=source_file.contents, - - # rule - options=rule.options, - properties=rule.properties, - extends_selectors=rule.extends_selectors, - ancestry=rule.ancestry, - namespace=rule.namespace, - ) - rule.namespace.add_import(import_key, rule.import_key, rule.file_and_line) - self.manage_children(_rule, scope) - - def Compilation(self, scss_string=None, scss_file=None, super_selector=None, - filename=None, is_sass=None, line_numbers=True, - relative_to=None): - """ - Overwritten to call _find_source_file instead of - SourceFile.from_filename. Also added the relative_to option. - """ - if not os.path.exists(config.ASSETS_ROOT): - os.makedirs(config.ASSETS_ROOT) - if super_selector: - self.super_selector = super_selector + ' ' - self.reset() - - source_file = None - if scss_string is not None: - source_file = SourceFile.from_string(scss_string, filename, is_sass, line_numbers) - # Set the parent_dir to be something meaningful instead of the - # current working directory, which is never correct for DjangoScss. - source_file.parent_dir = relative_to - elif scss_file is not None: - # Call _find_source_file instead of SourceFile.from_filename - source_file = self._find_source_file(scss_file) - - if source_file is not None: - # Clear the existing list of files - self.source_files = [] - self.source_file_index = dict() - - self.source_files.append(source_file) - self.source_file_index[source_file.filename] = source_file - - # this will compile and manage rule: child objects inside of a node - self.parse_children() - - # this will manage @extends - self.apply_extends() - - rules_by_file, css_files = self.parse_properties() - - all_rules = 0 - all_selectors = 0 - exceeded = '' - final_cont = '' - files = len(css_files) - for source_file in css_files: - rules = rules_by_file[source_file] - fcont, total_rules, total_selectors = self.create_css(rules) - all_rules += total_rules - all_selectors += total_selectors - if not exceeded and all_selectors > 4095: - exceeded = " (IE exceeded!)" - log.error("Maximum number of supported selectors in Internet Explorer (4095) exceeded!") - if files > 1 and self.scss_opts.get('debug_info', False): - if source_file.is_string: - final_cont += "/* %s %s generated add up to a total of %s %s accumulated%s */\n" % ( - total_selectors, - 'selector' if total_selectors == 1 else 'selectors', - all_selectors, - 'selector' if all_selectors == 1 else 'selectors', - exceeded) - else: - final_cont += "/* %s %s generated from '%s' add up to a total of %s %s accumulated%s */\n" % ( - total_selectors, - 'selector' if total_selectors == 1 else 'selectors', - source_file.filename, - all_selectors, - 'selector' if all_selectors == 1 else 'selectors', - exceeded) - final_cont += fcont - - return final_cont diff --git a/django_pyscss/utils.py b/django_pyscss/utils.py index d668280..1f9e4b4 100644 --- a/django_pyscss/utils.py +++ b/django_pyscss/utils.py @@ -1,7 +1,9 @@ import fnmatch import os +from django.conf import settings from django.contrib.staticfiles import finders +from django.contrib.staticfiles.storage import staticfiles_storage def find_all_files(glob): @@ -17,3 +19,29 @@ def find_all_files(glob): or '', path), glob): yield path, storage + + +def get_file_from_storage(filename): + try: + filename = staticfiles_storage.path(filename) + except NotImplementedError: + # remote storages don't implement path + pass + if staticfiles_storage.exists(filename): + return filename, staticfiles_storage + else: + return None, None + + +def get_file_from_finders(filename): + for file_and_storage in find_all_files(filename): + return file_and_storage + return None, None + + +def get_file_and_storage(filename): + # TODO: the switch probably shouldn't be on DEBUG + if settings.DEBUG: + return get_file_from_finders(filename) + else: + return get_file_from_storage(filename) @@ -11,7 +11,7 @@ def read(fname): install_requires = [ 'Django>=1.4', - 'pyScss>=1.2.0,<1.3.0', + 'pyScss>=1.3.4', ] tests_require = [ 'Pillow', @@ -47,6 +47,7 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - #'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', ], ) diff --git a/testproject/runtests.py b/testproject/runtests.py index 09930f0..dc3fbc9 100644 --- a/testproject/runtests.py +++ b/testproject/runtests.py @@ -15,5 +15,6 @@ def runtests(): # Stolen from django/core/management/commands/test.py TestRunner = get_runner(settings) test_runner = TestRunner(verbosity=1, interactive=True) + # failures = test_runner.run_tests(['tests.test_scss.FindersImportTest.test_relative_import_with_filename']) failures = test_runner.run_tests(['tests']) sys.exit(bool(failures)) diff --git a/tests/test_compressor.py b/tests/test_compressor.py index cd8827d..aa554fe 100644 --- a/tests/test_compressor.py +++ b/tests/test_compressor.py @@ -1,4 +1,4 @@ -from django.template.loader import Template, Context +from django.template import Template, Context from tests.utils import CollectStaticTestCase diff --git a/tests/test_scss.py b/tests/test_scss.py index 78cd46e..e8095f7 100644 --- a/tests/test_scss.py +++ b/tests/test_scss.py @@ -1,12 +1,15 @@ import os +import re from django.test import TestCase from django.test.utils import override_settings from django.conf import settings -from django_pyscss.scss import DjangoScss +from scss.errors import SassImportError -from tests.utils import clean_css, CollectStaticTestCase +from django_pyscss import DjangoScssCompiler + +from tests.utils import clean_css, CollectStaticTestCase, NoCollectStaticTestCase with open(os.path.join(settings.BASE_DIR, 'testproject', 'static', 'css', 'foo.scss')) as f: @@ -35,69 +38,69 @@ with open(os.path.join(settings.BASE_DIR, 'testproject', 'static', 'css', 'path_ class CompilerTestMixin(object): def setUp(self): - self.compiler = DjangoScss(scss_opts={ - # No compress so that I can compare more easily - 'compress': 0, - }) + self.compiler = DjangoScssCompiler() super(CompilerTestMixin, self).setUp() class ImportTestMixin(CompilerTestMixin): def test_import_from_staticfiles_dirs(self): - actual = self.compiler.compile(scss_string='@import "/css/foo.scss";') + actual = self.compiler.compile_string('@import "/css/foo.scss";') self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS)) def test_import_from_staticfiles_dirs_prefixed(self): - actual = self.compiler.compile(scss_string='@import "/css_prefix/baz.scss";') + actual = self.compiler.compile_string('@import "/css_prefix/baz.scss";') self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS)) def test_import_from_staticfiles_dirs_relative(self): - actual = self.compiler.compile(scss_string='@import "css/foo.scss";') + actual = self.compiler.compile_string('@import "css/foo.scss";') self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS)) def test_import_from_app(self): - actual = self.compiler.compile(scss_string='@import "/css/app1.scss";') + actual = self.compiler.compile_string('@import "/css/app1.scss";') self.assertEqual(clean_css(actual), clean_css(APP1_CONTENTS)) def test_import_from_app_relative(self): - actual = self.compiler.compile(scss_string='@import "css/app1.scss";') + actual = self.compiler.compile_string('@import "css/app1.scss";') self.assertEqual(clean_css(actual), clean_css(APP1_CONTENTS)) def test_imports_within_file(self): - actual = self.compiler.compile(scss_string='@import "/css/app2.scss";') + actual = self.compiler.compile_string('@import "/css/app2.scss";') self.assertEqual(clean_css(actual), clean_css(APP2_CONTENTS)) def test_relative_import(self): - actual = self.compiler.compile(scss_file='/css/bar.scss') + actual = self.compiler.compile('/css/bar.scss') + self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS)) + + def test_relative_import_with_filename(self): + actual = self.compiler.compile_string('@import "foo.scss";', 'css/bar.scss') self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS)) def test_bad_import(self): - actual = self.compiler.compile(scss_string='@import "this-file-does-not-and-should-never-exist.scss";') - self.assertEqual(clean_css(actual), '') + self.assertRaises(SassImportError, self.compiler.compile_string, '@import "this-file-does-not-and-should-never-exist.scss";') def test_no_extension_import(self): - actual = self.compiler.compile(scss_string='@import "/css/foo";') + actual = self.compiler.compile_string('@import "/css/foo";') self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS)) def test_no_extension_import_sass(self): - actual = self.compiler.compile(scss_string='@import "/css/sass_file";') + actual = self.compiler.compile_string('@import "/css/sass_file";') self.assertEqual(clean_css(actual), clean_css(SASS_CONTENTS)) - def test_no_extension_import_css(self): - actual = self.compiler.compile(scss_string='@import "/css/css_file";') - self.assertEqual(clean_css(actual), clean_css(CSS_CONTENTS)) + # def test_no_extension_import_css(self): + # actual = self.compiler.compile_string('@import "/css/css_file";') + # self.assertEqual(clean_css(actual), clean_css(CSS_CONTENTS)) def test_import_underscore_file(self): - actual = self.compiler.compile(scss_string='@import "/css/baz";') + actual = self.compiler.compile_string('@import "/css/baz";') self.assertEqual(clean_css(actual), clean_css(BAZ_CONTENTS)) def test_import_conflict(self): - actual = self.compiler.compile(scss_string='@import "/css/path_conflict";') + actual = self.compiler.compile_string('@import "/css/path_conflict";') self.assertEqual(clean_css(actual), clean_css(PATH_CONFLICT_CONTENTS)) @override_settings(DEBUG=True) -class FindersImportTest(ImportTestMixin, TestCase): +class FindersImportTest(ImportTestMixin, NoCollectStaticTestCase): pass @@ -133,11 +136,11 @@ $widgets: sprite-map('images/icons/widget-*.png'); class AssetsTest(CompilerTestMixin, TestCase): def test_inline_image(self): - actual = self.compiler.compile(scss_string=INLINE_IMAGE) + actual = self.compiler.compile_string(INLINE_IMAGE) self.assertEqual(clean_css(actual), clean_css(INLINED_IMAGE_EXPECTED)) def test_sprite_images(self): - actual = self.compiler.compile(scss_string=SPRITE_MAP) + actual = self.compiler.compile_string(SPRITE_MAP) # pyScss puts a cachebuster query string on the end of the URLs, lets # just check that it made the file that we expected. - self.assertIn('KUZdBAnPCdlG5qfocw9GYw.png', actual) + self.assertTrue(re.search(r'url\(/static/scss/assets/images_icons-.+\.png\?_=\d+', actual)) diff --git a/tests/utils.py b/tests/utils.py index 1936481..beba759 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,11 +1,22 @@ +import shutil + from django.test import TestCase from django.core.management import call_command +from django.conf import settings class CollectStaticTestCase(TestCase): - def setUp(self): - call_command('collectstatic', interactive=False) - super(CollectStaticTestCase, self).setUp() + @classmethod + def setUpClass(cls): + super(CollectStaticTestCase, cls).setUpClass() + call_command('collectstatic', interactive=False, verbosity=0) + + +class NoCollectStaticTestCase(TestCase): + @classmethod + def setUpClass(cls): + super(NoCollectStaticTestCase, cls).setUpClass() + shutil.rmtree(settings.STATIC_ROOT, ignore_errors=True) def clean_css(string): @@ -0,0 +1,21 @@ +[tox] +envlist= + py{26,27}-dj{14}, + py{27,33,34}-dj{17,18} + +[testenv] +basepython= + py26: python2.6 + py27: python2.7 + py33: python3.3 + py34: python3.4 +commands= + /usr/bin/env + python setup.py test +deps= + dj14: Django>=1.4,<1.5 + dj17: Django>=1.7,<1.8 + dj18: Django>=1.8,<1.9 +whitelist_externals= + env + make |