Reworked the relative/absolute import system.
Now, it works more like HTML URL resolution, where any URL is relative unless it starts with a /.
This commit is contained in:
parent
ae411e0b97
commit
53d023e453
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
def get_possible_import_paths(self, filename, relative_to=None):
|
||||
"""
|
||||
Returns an iterable of possible filenames for an import.
|
||||
|
||||
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]
|
||||
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)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
@import "css/foo.scss";
|
||||
@import "css/app1.scss";
|
||||
@import "foo.scss";
|
||||
@import "app1.scss";
|
||||
|
|
|
@ -1 +1 @@
|
|||
@import "./foo.scss";
|
||||
@import "foo.scss";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue