Skip to content

Commit b043a03

Browse files
committed
Merge pull request #43 from asottile/upgrade_to_cpp_api
Upgrade to libsass==3.1.0. Resolves #36. Resolves #38.
2 parents 0cb44c3 + 15df8fc commit b043a03

File tree

7 files changed

+162
-89
lines changed

7 files changed

+162
-89
lines changed

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[submodule "libsass"]
22
path = libsass
3-
url = git://github.com/dahlia/libsass-python.git
3+
url = git://github.com/sass/libsass.git

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy