-
Notifications
You must be signed in to change notification settings - Fork 66
/
processor.py
131 lines (113 loc) · 4.94 KB
/
processor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import json
from django.apps import apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile
from django.template import Context
from django.templatetags.static import PrefixNode
from django.utils.encoding import force_bytes
from django.utils.six.moves.urllib.parse import quote, urljoin
from sass_processor.utils import get_setting
from .storage import SassFileStorage, find_file
from .apps import APPS_INCLUDE_DIRS
try:
import sass
except ImportError:
sass = None
try:
FileNotFoundError
except NameError:
FileNotFoundError = IOError
class SassProcessor(object):
storage = SassFileStorage()
include_paths = list(getattr(settings, 'SASS_PROCESSOR_INCLUDE_DIRS', []))
try:
sass_precision = int(settings.SASS_PRECISION)
except (AttributeError, TypeError, ValueError):
sass_precision = None
sass_output_style = getattr(
settings,
'SASS_OUTPUT_STYLE',
'nested' if settings.DEBUG else 'compressed')
processor_enabled = getattr(settings, 'SASS_PROCESSOR_ENABLED', settings.DEBUG)
sass_extensions = ('.scss', '.sass')
def __init__(self, path=None):
self._path = path
def __call__(self, path):
basename, ext = os.path.splitext(path)
filename = find_file(path)
if filename is None:
raise FileNotFoundError("Unable to locate file {path}".format(path=path))
if ext not in self.sass_extensions:
# return the given path, since it ends neither in `.scss` nor in `.sass`
return path
# compare timestamp of sourcemap file with all its dependencies, and check if we must recompile
css_filename = basename + '.css'
if not self.processor_enabled:
return css_filename
sourcemap_filename = css_filename + '.map'
if find_file(css_filename) and self.is_latest(sourcemap_filename):
return css_filename
# with offline compilation, raise an error, if css file could not be found.
if sass is None:
msg = "Offline compiled file `{}` is missing and libsass has not been installed."
raise ImproperlyConfigured(msg.format(css_filename))
# add a function to be used from inside SASS
custom_functions = {'get-setting': get_setting}
# otherwise compile the SASS/SCSS file into .css and store it
sourcemap_url = self.storage.url(sourcemap_filename)
compile_kwargs = {
'filename': filename,
'source_map_filename': sourcemap_url,
'include_paths': self.include_paths + APPS_INCLUDE_DIRS,
'custom_functions': custom_functions,
}
if self.sass_precision:
compile_kwargs['precision'] = self.sass_precision
if self.sass_output_style:
compile_kwargs['output_style'] = self.sass_output_style
content, sourcemap = sass.compile(**compile_kwargs)
content = force_bytes(content)
sourcemap = force_bytes(sourcemap)
if self.storage.exists(css_filename):
self.storage.delete(css_filename)
self.storage.save(css_filename, ContentFile(content))
if self.storage.exists(sourcemap_filename):
self.storage.delete(sourcemap_filename)
self.storage.save(sourcemap_filename, ContentFile(sourcemap))
return css_filename
def resolve_path(self, context=None):
if context is None:
context = Context()
return self._path.resolve(context)
def is_sass(self):
_, ext = os.path.splitext(self.resolve_path())
return ext in self.sass_extensions
def is_latest(self, sourcemap_filename):
sourcemap_file = find_file(sourcemap_filename)
if not sourcemap_file or not os.path.isfile(sourcemap_file):
return False
sourcemap_mtime = os.stat(sourcemap_file).st_mtime
with open(sourcemap_file, 'r') as fp:
sourcemap = json.load(fp)
for srcfilename in sourcemap.get('sources'):
components = os.path.normpath(srcfilename).split('/')
srcfilename = ''.join([os.path.sep + c for c in components if c != os.path.pardir])
if not os.path.isfile(srcfilename) or os.stat(srcfilename).st_mtime > sourcemap_mtime:
# at least one of the source is younger that the sourcemap referring it
return False
return True
@classmethod
def handle_simple(cls, path):
if apps.is_installed('django.contrib.staticfiles'):
from django.contrib.staticfiles.storage import staticfiles_storage
return staticfiles_storage.url(path)
else:
return urljoin(PrefixNode.handle_simple('STATIC_URL'), quote(path))
_sass_processor = SassProcessor()
def sass_processor(filename):
path = _sass_processor(filename)
return SassProcessor.handle_simple(path)