summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRocky Meza <rocky@fusionbox.com>2014-02-03 17:54:27 -0700
committerRocky Meza <rocky@fusionbox.com>2014-02-03 17:54:27 -0700
commit53d023e45357516840ff28fa30df98ce14639111 (patch)
treeef1b194c8dd1a3b15db2af71abacf6349a169ed6
parentae411e0b9723c1d63d8326c492bfdd5833c14094 (diff)
downloaddjango-pyscss-53d023e45357516840ff28fa30df98ce14639111.tar.gz
Reworked the relative/absolute import system.
Now, it works more like HTML URL resolution, where any URL is relative unless it starts with a /.
-rw-r--r--README.rst4
-rw-r--r--django_pyscss/compressor.py13
-rw-r--r--django_pyscss/scss.py69
-rw-r--r--testproject/testapp2/static/css/app2.scss4
-rw-r--r--testproject/testproject/static/css/bar.scss2
-rw-r--r--tests/test_compressor.py15
-rw-r--r--tests/test_scss.py31
7 files changed, 85 insertions, 53 deletions
diff --git a/README.rst b/README.rst
index d8c9639..6c8391d 100644
--- a/README.rst
+++ b/README.rst
@@ -41,9 +41,7 @@ You can render SCSS from a file like this::
compiler.compile(scss_file='css/styles.scss')
The file needs to be able to be located by staticfiles finders in order to be
-used. All imports are relative to the ``STATIC_ROOT``, but you can also have
-relative imports from a file. If you prefix an import with ``./``, you can
-import a sibling file without having to write out the whole import path.
+used.
.. class:: django_pyscss.scss.DjangoScss
diff --git a/django_pyscss/compressor.py b/django_pyscss/compressor.py
index ab13f77..6ec9e31 100644
--- a/django_pyscss/compressor.py
+++ b/django_pyscss/compressor.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
import os
from compressor.filters import FilterBase
+from compressor.conf import settings
from django_pyscss.scss import DjangoScss, config
@@ -14,8 +15,18 @@ class DjangoScssFilter(FilterBase):
# It looks like there is a bug in django-compressor because it expects
# us to accept attrs.
super(DjangoScssFilter, self).__init__(content, filter_type, filename)
+ try:
+ # this is a link tag which means there is an SCSS file being
+ # referenced.
+ href = attrs['href']
+ except KeyError:
+ # this is a style tag which means this is inline SCSS.
+ self.relative_to = None
+ else:
+ self.relative_to = os.path.dirname(href.replace(settings.STATIC_URL, ''))
def input(self, **kwargs):
if not os.path.exists(config.ASSETS_ROOT):
os.makedirs(config.ASSETS_ROOT)
- return self.compiler.compile(self.content)
+ return self.compiler.compile(scss_string=self.content,
+ relative_to=self.relative_to)
diff --git a/django_pyscss/scss.py b/django_pyscss/scss.py
index 03e6c57..3423b2c 100644
--- a/django_pyscss/scss.py
+++ b/django_pyscss/scss.py
@@ -4,7 +4,6 @@ import os
from django.contrib.staticfiles.storage import staticfiles_storage
from django.conf import settings
-from django.core.exceptions import SuspiciousFileOperation
from scss import (
Scss, dequote, log, SourceFile, SassRule, config,
@@ -48,26 +47,39 @@ class DjangoScss(Scss):
else:
return self.get_file_from_storage(filename)
- def _find_source_file(self, name):
- file_and_storage = self.get_file_and_storage(name)
- if file_and_storage is None:
- return None
- else:
- full_filename, storage = file_and_storage
- if name not in self.source_files:
- 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.
- 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 get_possible_import_paths(self, filename, relative_to=None):
+ """
+ Returns an iterable of possible filenames 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.
+ """
+ if filename.startswith('/'): # absolute import
+ filename = filename[1:]
+ elif relative_to: # relative import
+ filename = os.path.join(relative_to, filename)
+
+ return [filename]
+
+ def _find_source_file(self, filename, relative_to=None):
+ for name in self.get_possible_import_paths(filename, relative_to):
+ file_and_storage = self.get_file_and_storage(name)
+ if file_and_storage:
+ full_filename, storage = file_and_storage
+ if name not in self.source_files:
+ 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):
"""
@@ -83,10 +95,8 @@ class DjangoScss(Scss):
for name in names:
name = dequote(name.strip())
- if name.startswith('./'):
- name = rule.source_file.parent_dir + name[1:]
-
- source_file = self._find_source_file(name)
+ 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)
@@ -121,10 +131,12 @@ class DjangoScss(Scss):
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):
+ 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.
+ SourceFile.from_filename. Also added the relative_to option.
"""
if super_selector:
self.super_selector = super_selector + ' '
@@ -133,6 +145,9 @@ class DjangoScss(Scss):
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)
diff --git a/testproject/testapp2/static/css/app2.scss b/testproject/testapp2/static/css/app2.scss
index d19b910..a096082 100644
--- a/testproject/testapp2/static/css/app2.scss
+++ b/testproject/testapp2/static/css/app2.scss
@@ -1,2 +1,2 @@
-@import "css/foo.scss";
-@import "css/app1.scss";
+@import "foo.scss";
+@import "app1.scss";
diff --git a/testproject/testproject/static/css/bar.scss b/testproject/testproject/static/css/bar.scss
index 448776a..08819e4 100644
--- a/testproject/testproject/static/css/bar.scss
+++ b/testproject/testproject/static/css/bar.scss
@@ -1 +1 @@
-@import "./foo.scss";
+@import "foo.scss";
diff --git a/tests/test_compressor.py b/tests/test_compressor.py
index 4f8f64e..4ff6630 100644
--- a/tests/test_compressor.py
+++ b/tests/test_compressor.py
@@ -9,8 +9,23 @@ APP2_LINK_TAG = """
{% endcompress %}
"""
+IMPORT_APP2_STYLE_TAG = """
+{% load staticfiles compress %}
+{% compress css %}
+<style type="text/x-scss">
+@import "css/app2.scss";
+</style>
+{% endcompress %}
+"""
+
class CompressorTest(TestCase):
def test_compressor_can_compile_scss(self):
actual = Template(APP2_LINK_TAG).render(Context())
+ # 4b368862ec8c is the cache key that compressor gives to the compiled
+ # version of app2.scss.
+ self.assertIn('4b368862ec8c.css', actual)
+
+ def test_compressor_can_compile_scss_from_style_tag(self):
+ actual = Template(IMPORT_APP2_STYLE_TAG).render(Context())
self.assertIn('4b368862ec8c.css', actual)
diff --git a/tests/test_scss.py b/tests/test_scss.py
index 3178f6b..d538068 100644
--- a/tests/test_scss.py
+++ b/tests/test_scss.py
@@ -9,26 +9,12 @@ from django_pyscss.scss import DjangoScss
from tests.utils import clean_css, CollectStaticTestCase
-IMPORT_FOO = """
-@import "css/foo.scss";
-"""
-
with open(os.path.join(settings.BASE_DIR, 'testproject', 'static', 'css', 'foo.scss')) as f:
FOO_CONTENTS = f.read()
-
-IMPORT_APP1 = """
-@import "css/app1.scss";
-"""
-
with open(os.path.join(settings.BASE_DIR, 'testapp1', 'static', 'css', 'app1.scss')) as f:
APP1_CONTENTS = f.read()
-
-IMPORT_APP2 = """
-@import "css/app2.scss";
-"""
-
APP2_CONTENTS = FOO_CONTENTS + APP1_CONTENTS
@@ -43,20 +29,27 @@ class CompilerTestMixin(object):
class ImportTestMixin(CompilerTestMixin):
def test_import_from_staticfiles_dirs(self):
- actual = self.compiler.compile(scss_string=IMPORT_FOO)
+ actual = self.compiler.compile(scss_string='@import "/css/foo.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";')
self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS))
def test_import_from_app(self):
- actual = self.compiler.compile(scss_string=IMPORT_APP1)
+ actual = self.compiler.compile(scss_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";')
self.assertEqual(clean_css(actual), clean_css(APP1_CONTENTS))
def test_imports_within_file(self):
- actual = self.compiler.compile(scss_string=IMPORT_APP2)
+ actual = self.compiler.compile(scss_string='@import "/css/app2.scss";')
self.assertEqual(clean_css(actual), clean_css(APP2_CONTENTS))
def test_relative_import(self):
- bar_scss = 'css/bar.scss'
- actual = self.compiler.compile(scss_file=bar_scss)
+ actual = self.compiler.compile(scss_file='/css/bar.scss')
self.assertEqual(clean_css(actual), clean_css(FOO_CONTENTS))
def test_bad_import(self):