Skip to content

Commit 202d657

Browse files
committed
Upgrade to libsass==3.1.0. Resolves #36. Resolves #38.
1 parent 0cb44c3 commit 202d657

File tree

6 files changed

+161
-88
lines changed

6 files changed

+161
-88
lines changed

libsass

Submodule libsass updated from 030e267 to 31521ef

pysass.cpp

Lines changed: 50 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include <stdlib.h>
33
#include <string.h>
44
#include <Python.h>
5-
#include "sass_interface.h"
5+
#include "sass_context.h"
66

77
#if PY_MAJOR_VERSION >= 3
88
#define PySass_IF_PY3(three, two) (three)
@@ -37,9 +37,13 @@ static struct PySass_Pair PySass_output_style_enum[] = {
3737

3838
static PyObject *
3939
PySass_compile_string(PyObject *self, PyObject *args) {
40-
struct sass_context *context;
40+
struct Sass_Context *ctx;
41+
struct Sass_Data_Context *context;
42+
struct Sass_Options *options;
4143
char *string, *include_paths, *image_path;
42-
int output_style, source_comments, precision;
44+
const char *error_message, *output_string;
45+
Sass_Output_Style output_style;
46+
int source_comments, error_status, precision;
4347
PyObject *result;
4448

4549
if (!PyArg_ParseTuple(args,
@@ -49,30 +53,38 @@ PySass_compile_string(PyObject *self, PyObject *args) {
4953
return NULL;
5054
}
5155

52-
context = sass_new_context();
53-
context->source_string = string;
54-
context->options.output_style = output_style;
55-
context->options.source_comments = source_comments;
56-
context->options.include_paths = include_paths;
57-
context->options.image_path = image_path;
58-
context->options.precision = precision;
56+
context = sass_make_data_context(string);
57+
options = sass_data_context_get_options(context);
58+
sass_option_set_output_style(options, output_style);
59+
sass_option_set_source_comments(options, source_comments);
60+
sass_option_set_include_path(options, include_paths);
61+
sass_option_set_image_path(options, image_path);
62+
sass_option_set_precision(options, precision);
5963

60-
sass_compile(context);
64+
sass_compile_data_context(context);
6165

66+
ctx = sass_data_context_get_context(context);
67+
error_status = sass_context_get_error_status(ctx);
68+
error_message = sass_context_get_error_message(ctx);
69+
output_string = sass_context_get_output_string(ctx);
6270
result = Py_BuildValue(
6371
PySass_IF_PY3("hy", "hs"),
64-
(short int) !context->error_status,
65-
context->error_status ? context->error_message : context->output_string
72+
(short int) !error_status,
73+
error_status ? error_message : output_string
6674
);
67-
sass_free_context(context);
75+
sass_delete_data_context(context);
6876
return result;
6977
}
7078

7179
static PyObject *
7280
PySass_compile_filename(PyObject *self, PyObject *args) {
73-
struct sass_file_context *context;
81+
struct Sass_Context *ctx;
82+
struct Sass_File_Context *context;
83+
struct Sass_Options *options;
7484
char *filename, *include_paths, *image_path;
75-
int output_style, source_comments, error_status, precision;
85+
const char *error_message, *output_string, *source_map_string;
86+
Sass_Output_Style output_style;
87+
int source_comments, error_status, precision;
7688
PyObject *source_map_filename, *result;
7789

7890
if (!PyArg_ParseTuple(args,
@@ -82,73 +94,41 @@ PySass_compile_filename(PyObject *self, PyObject *args) {
8294
return NULL;
8395
}
8496

85-
context = sass_new_file_context();
86-
context->input_path = filename;
97+
context = sass_make_file_context(filename);
98+
options = sass_file_context_get_options(context);
99+
87100
if (source_comments && PySass_Bytes_Check(source_map_filename)) {
88101
size_t source_map_file_len = PySass_Bytes_GET_SIZE(source_map_filename);
89102
if (source_map_file_len) {
90103
char *source_map_file = (char *) malloc(source_map_file_len + 1);
91104
strncpy(
92-
source_map_file,
105+
source_map_file,
93106
PySass_Bytes_AS_STRING(source_map_filename),
94107
source_map_file_len + 1
95108
);
96-
context->options.source_map_file = source_map_file;
109+
sass_option_set_source_map_file(options, source_map_file);
97110
}
98111
}
99-
context->options.output_style = output_style;
100-
context->options.source_comments = source_comments;
101-
context->options.include_paths = include_paths;
102-
context->options.image_path = image_path;
103-
context->options.precision = precision;
104-
105-
sass_compile_file(context);
106-
107-
error_status = context->error_status;
112+
sass_option_set_output_style(options, output_style);
113+
sass_option_set_source_comments(options, source_comments);
114+
sass_option_set_include_path(options, include_paths);
115+
sass_option_set_image_path(options, image_path);
116+
sass_option_set_precision(options, precision);
117+
118+
sass_compile_file_context(context);
119+
120+
ctx = sass_file_context_get_context(context);
121+
error_status = sass_context_get_error_status(ctx);
122+
error_message = sass_context_get_error_message(ctx);
123+
output_string = sass_context_get_output_string(ctx);
124+
source_map_string = sass_context_get_source_map_string(ctx);
108125
result = Py_BuildValue(
109126
PySass_IF_PY3("hyy", "hss"),
110-
(short int) !context->error_status,
111-
error_status ? context->error_message : context->output_string,
112-
error_status || context->source_map_string == NULL
113-
? ""
114-
: context->source_map_string
115-
);
116-
sass_free_file_context(context);
117-
return result;
118-
}
119-
120-
static PyObject *
121-
PySass_compile_dirname(PyObject *self, PyObject *args) {
122-
struct sass_folder_context *context;
123-
char *search_path, *output_path, *include_paths, *image_path;
124-
int output_style, source_comments, precision;
125-
PyObject *result;
126-
127-
if (!PyArg_ParseTuple(args,
128-
PySass_IF_PY3("yyiiyyi", "ssiissi"),
129-
&search_path, &output_path,
130-
&output_style, &source_comments,
131-
&include_paths, &image_path, precision)) {
132-
return NULL;
133-
}
134-
135-
context = sass_new_folder_context();
136-
context->search_path = search_path;
137-
context->output_path = output_path;
138-
context->options.output_style = output_style;
139-
context->options.source_comments = source_comments;
140-
context->options.include_paths = include_paths;
141-
context->options.image_path = image_path;
142-
context->options.precision = precision;
143-
144-
sass_compile_folder(context);
145-
146-
result = Py_BuildValue(
147-
PySass_IF_PY3("hy", "hs"),
148-
(short int) !context->error_status,
149-
context->error_status ? context->error_message : NULL
127+
(short int) !error_status,
128+
error_status ? error_message : output_string,
129+
error_status || source_map_string == NULL ? "" : source_map_string
150130
);
151-
sass_free_folder_context(context);
131+
sass_delete_file_context(context);
152132
return result;
153133
}
154134

@@ -157,8 +137,6 @@ static PyMethodDef PySass_methods[] = {
157137
"Compile a SASS string."},
158138
{"compile_filename", PySass_compile_filename, METH_VARARGS,
159139
"Compile a SASS file."},
160-
{"compile_dirname", PySass_compile_dirname, METH_VARARGS,
161-
"Compile several SASS files."},
162140
{NULL, NULL, 0, NULL}
163141
};
164142

sass.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
from six import string_types, text_type
2121

22-
from _sass import (OUTPUT_STYLES, compile_dirname,
23-
compile_filename, compile_string)
22+
from _sass import OUTPUT_STYLES, compile_filename, compile_string
2423

2524
__all__ = ('MODES', 'OUTPUT_STYLES', 'SOURCE_COMMENTS', 'CompileError',
2625
'and_join', 'compile')
@@ -46,10 +45,46 @@
4645
class CompileError(ValueError):
4746
"""The exception type that is raised by :func:`compile()`.
4847
It is a subtype of :exc:`exceptions.ValueError`.
49-
5048
"""
5149

5250

51+
def mkdirp(path):
52+
try:
53+
os.makedirs(path)
54+
except OSError:
55+
if os.path.isdir(path):
56+
return
57+
raise
58+
59+
60+
def compile_dirname(
61+
search_path, output_path, output_style, source_comments, include_paths,
62+
image_path, precision,
63+
):
64+
fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
65+
for dirpath, _, filenames in os.walk(search_path):
66+
filenames = [
67+
filename for filename in filenames if filename.endswith('.scss')
68+
]
69+
for filename in filenames:
70+
input_filename = os.path.join(dirpath, filename)
71+
relpath_to_file = os.path.relpath(input_filename, search_path)
72+
output_filename = os.path.join(output_path, relpath_to_file)
73+
output_filename = re.sub('.scss$', '.css', output_filename)
74+
input_filename = input_filename.encode(fs_encoding)
75+
s, v, _ = compile_filename(
76+
input_filename, output_style, source_comments, include_paths,
77+
image_path, precision, None,
78+
)
79+
if s:
80+
v = v.decode('UTF-8')
81+
mkdirp(os.path.dirname(output_filename))
82+
with open(output_filename, 'w') as output_file:
83+
output_file.write(v)
84+
else:
85+
return False, v
86+
return True, None
87+
5388
def compile(**kwargs):
5489
"""There are three modes of parameters :func:`compile()` can take:
5590
``string``, ``filename``, and ``dirname``.
@@ -302,11 +337,6 @@ def compile(**kwargs):
302337
except ValueError:
303338
raise ValueError('dirname must be a pair of (source_dir, '
304339
'output_dir)')
305-
else:
306-
if isinstance(search_path, text_type):
307-
search_path = search_path.encode(fs_encoding)
308-
if isinstance(output_path, text_type):
309-
output_path = output_path.encode(fs_encoding)
310340
s, v = compile_dirname(search_path, output_path,
311341
output_style, source_comments,
312342
include_paths, image_path, precision)

sasstests.py

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import with_statement
33

44
import collections
5+
import contextlib
56
import glob
67
import json
78
import os
@@ -56,7 +57,7 @@ def normalize_path(path):
5657
'sources': ['test/a.scss'],
5758
'sourcesContent': [],
5859
'names': [],
59-
'mappings': ';AAKA;EAHE,kBAAkB;;EAIpB,KAAK;IAED,OAAO'
60+
'mappings': ';AAKA;EAHE,AAAkB;;EAKpB,AAAK;IACD,AAAO',
6061
}
6162

6263
B_EXPECTED_CSS = '''\
@@ -82,13 +83,15 @@ def normalize_path(path):
8283
'''
8384

8485
D_EXPECTED_CSS = '''\
86+
@charset "UTF-8";
8587
body {
8688
background-color: green; }
8789
body a {
8890
font: '나눔고딕', sans-serif; }
8991
'''
9092

9193
D_EXPECTED_CSS_WITH_MAP = '''\
94+
@charset "UTF-8";
9295
/* line 6, SOURCE */
9396
body {
9497
background-color: green; }
@@ -240,7 +243,8 @@ def test_compile_string(self):
240243
'''
241244
actual = sass.compile(string=u'a { color: blue; } /* 유니코드 */')
242245
self.assertEqual(
243-
u'''a {
246+
u'''@charset "UTF-8";
247+
a {
244248
color: blue; }
245249
246250
/* 유니코드 */''',
@@ -458,7 +462,7 @@ def test_build_one(self):
458462
'sources': ['../test/b.scss'],
459463
'sourcesContent': [],
460464
'names': [],
461-
'mappings': ';AAAA,EAAE;EAEE,WAAW'
465+
'mappings': ';AACA,AAAE;EACE,AAAW',
462466
},
463467
os.path.join(d, 'css', 'b.scss.css.map')
464468
)
@@ -476,7 +480,7 @@ def test_build_one(self):
476480
'sources': ['../test/d.scss'],
477481
'sourcesContent': [],
478482
'names': [],
479-
'mappings': ';AAKA;EAHE,kBAAkB;;EAIpB,KAAK;IAED,MAAM'
483+
'mappings': ';AAKA;EAHE,AAAkB;;EAKpB,AAAK;IACD,AAAM',
480484
},
481485
os.path.join(d, 'css', 'd.scss.css.map')
482486
)
@@ -673,14 +677,73 @@ def test_sassc_sourcemap(self):
673677
shutil.rmtree(tmp_dir)
674678

675679

680+
@contextlib.contextmanager
681+
def tempdir():
682+
tmpdir = tempfile.mkdtemp()
683+
try:
684+
yield tmpdir
685+
finally:
686+
shutil.rmtree(tmpdir)
687+
688+
689+
def write_file(filename, contents):
690+
with open(filename, 'w') as f:
691+
f.write(contents)
692+
693+
694+
class CompileDirectoriesTest(unittest.TestCase):
695+
696+
def test_successful(self):
697+
with tempdir() as tmpdir:
698+
input_dir = os.path.join(tmpdir, 'input')
699+
output_dir = os.path.join(tmpdir, 'output')
700+
os.makedirs(os.path.join(input_dir, 'foo'))
701+
write_file(os.path.join(input_dir, 'f1.scss'), 'a { b { width: 100%; } }')
702+
write_file(os.path.join(input_dir, 'foo/f2.scss'), 'foo { width: 100%; }')
703+
# Make sure we don't compile non-scss files
704+
write_file(os.path.join(input_dir, 'baz.txt'), 'Hello der')
705+
706+
# the api for this is weird, why does it need source?
707+
sass.compile(dirname=(input_dir, output_dir))
708+
assert os.path.exists(output_dir)
709+
assert os.path.exists(os.path.join(output_dir, 'foo'))
710+
assert os.path.exists(os.path.join(output_dir, 'f1.css'))
711+
assert os.path.exists(os.path.join(output_dir, 'foo/f2.css'))
712+
assert not os.path.exists(os.path.join(output_dir, 'baz.txt'))
713+
714+
contentsf1 = open(os.path.join(output_dir, 'f1.css')).read()
715+
contentsf2 = open(os.path.join(output_dir, 'foo/f2.css')).read()
716+
self.assertEqual(contentsf1, 'a b {\n width: 100%; }\n')
717+
self.assertEqual(contentsf2, 'foo {\n width: 100%; }\n')
718+
719+
def test_error(self):
720+
with tempdir() as tmpdir:
721+
input_dir = os.path.join(tmpdir, 'input')
722+
os.makedirs(input_dir)
723+
write_file(os.path.join(input_dir, 'bad.scss'), 'a {')
724+
725+
try:
726+
sass.compile(dirname=(input_dir, os.path.join(tmpdir, 'output')))
727+
assert False, 'Expected to raise'
728+
except sass.CompileError as e:
729+
msg, = e.args
730+
assert msg.decode('UTF-8').endswith(
731+
'bad.scss:1: invalid property name\n'
732+
), msg
733+
return
734+
except Exception as e:
735+
assert False, 'Expected to raise CompileError but got {0!r}'.format(e)
736+
737+
676738
test_cases = [
677739
SassTestCase,
678740
CompileTestCase,
679741
BuilderTestCase,
680742
ManifestTestCase,
681743
WsgiTestCase,
682744
DistutilsTestCase,
683-
SasscTestCase
745+
SasscTestCase,
746+
CompileDirectoriesTest,
684747
]
685748
loader = unittest.defaultTestLoader
686749
suite = unittest.TestSuite()

0 commit comments

Comments
 (0)