Add pyScss 1.3 and Python 3 support
This commit is contained in:
parent
313c0e02b8
commit
518098530e
|
@ -4,3 +4,5 @@ tmp/
|
|||
.coverage
|
||||
htmlcov/
|
||||
/dist/
|
||||
.eggs
|
||||
.tox
|
||||
|
|
37
.travis.yml
37
.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
|
||||
|
|
95
README.rst
95
README.rst
|
@ -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.
|
||||
|
||||
.. class:: django_pyscss.scss.DjangoScss
|
||||
``DjangoScssCompiler`` also turns on the CompassExtension by default, if you
|
||||
wish to turn this off you do so:
|
||||
|
||||
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.
|
||||
.. code-block:: python
|
||||
|
||||
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.
|
||||
from django_pyscss import DjangoScssCompiler
|
||||
from django_pyscss.extensions.django import DjangoExtension
|
||||
|
||||
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
|
||||
=================
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .compiler import DjangoScssCompiler # NOQA
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
|
5
setup.py
5
setup.py
|
@ -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',
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.template.loader import Template, Context
|
||||
from django.template import Template, Context
|
||||
|
||||
from tests.utils import CollectStaticTestCase
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue