Skip to content

Commit 4227f4f

Browse files
committed
Implement custom functions. Resolves #13.
1 parent b043a03 commit 4227f4f

File tree

5 files changed

+858
-12
lines changed

5 files changed

+858
-12
lines changed

Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This is to speed up development time.
2+
# Usage:
3+
# Needed once:
4+
# $ virtualenv venv
5+
# $ . venv/bin/activate
6+
# $ pip install -e .`
7+
# $ pip install werkzeug
8+
# Once that is done, to rebuild simply:
9+
# $ make -j 4 && python -m unittest sasstests
10+
11+
PY_HEADERS := -I/usr/include/python2.7
12+
C_SOURCES := $(wildcard libsass/*.c)
13+
C_OBJECTS = $(patsubst libsass/%.c,build2/libsass/c/%.o,$(C_SOURCES))
14+
CPP_SOURCES := $(wildcard libsass/*.cpp)
15+
CPP_OBJECTS = $(patsubst libsass/%.cpp,build2/libsass/cpp/%.o,$(CPP_SOURCES))
16+
17+
all: _sass.so
18+
19+
build2/libsass/c/%.o: libsass/%.c
20+
@mkdir -p build2/libsass/c/
21+
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I./libsass $(PY_HEADERS) -c $^ -o $@ -c -O2 -fPIC -std=c++0x -Wall -Wno-parentheses
22+
23+
build2/libsass/cpp/%.o: libsass/%.cpp
24+
@mkdir -p build2/libsass/cpp/
25+
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I./libsass $(PY_HEADERS) -c $^ -o $@ -c -O2 -fPIC -std=c++0x -Wall -Wno-parentheses
26+
27+
build2/pysass.o: pysass.cpp
28+
@mkdir -p build2
29+
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I./libsass $(PY_HEADERS) -c $^ -o $@ -c -O2 -fPIC -std=c++0x -Wall -Wno-parentheses
30+
31+
_sass.so: $(C_OBJECTS) $(CPP_OBJECTS) build2/pysass.o
32+
g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro $^ -L./libsass -o $@ -fPIC -lstdc++
33+
34+
.PHONY: clean
35+
clean:
36+
rm -rf build2 _sass.so
37+

pysass.cpp

Lines changed: 309 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,305 @@ static struct PySass_Pair PySass_output_style_enum[] = {
3535
{NULL}
3636
};
3737

38+
static PyObject* _to_py_value(const union Sass_Value* value) {
39+
PyObject* retv = NULL;
40+
PyObject* types_mod = PyImport_ImportModule("sassutils.sass_types");
41+
PyObject* sass_comma = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_COMMA");
42+
PyObject* sass_space = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_SPACE");
43+
44+
switch (sass_value_get_tag(value)) {
45+
case SASS_NULL:
46+
retv = Py_None;
47+
Py_INCREF(retv);
48+
break;
49+
case SASS_BOOLEAN:
50+
retv = PyBool_FromLong(sass_boolean_get_value(value));
51+
break;
52+
case SASS_STRING:
53+
retv = PyUnicode_FromString(sass_string_get_value(value));
54+
break;
55+
case SASS_NUMBER:
56+
retv = PyObject_CallMethod(
57+
types_mod,
58+
"SassNumber",
59+
PySass_IF_PY3("dy", "ds"),
60+
sass_number_get_value(value),
61+
sass_number_get_unit(value)
62+
);
63+
break;
64+
case SASS_COLOR:
65+
retv = PyObject_CallMethod(
66+
types_mod,
67+
"SassColor",
68+
"dddd",
69+
sass_color_get_r(value),
70+
sass_color_get_g(value),
71+
sass_color_get_b(value),
72+
sass_color_get_a(value)
73+
);
74+
break;
75+
case SASS_LIST: {
76+
size_t i = 0;
77+
PyObject* items = PyTuple_New(sass_list_get_length(value));
78+
PyObject* separator = sass_comma;
79+
switch (sass_list_get_separator(value)) {
80+
case SASS_COMMA:
81+
separator = sass_comma;
82+
break;
83+
case SASS_SPACE:
84+
separator = sass_space;
85+
break;
86+
}
87+
for (i = 0; i < sass_list_get_length(value); i += 1) {
88+
PyTuple_SetItem(
89+
items,
90+
i,
91+
_to_py_value(sass_list_get_value(value, i))
92+
);
93+
}
94+
retv = PyObject_CallMethod(
95+
types_mod, "SassList", "OO", items, separator
96+
);
97+
break;
98+
}
99+
case SASS_MAP: {
100+
size_t i = 0;
101+
PyObject* items = PyTuple_New(sass_map_get_length(value));
102+
for (i = 0; i < sass_map_get_length(value); i += 1) {
103+
PyObject* kvp = PyTuple_New(2);
104+
PyTuple_SetItem(
105+
kvp, 0, _to_py_value(sass_map_get_key(value, i))
106+
);
107+
PyTuple_SetItem(
108+
kvp, 1, _to_py_value(sass_map_get_value(value, i))
109+
);
110+
PyTuple_SetItem(items, i, kvp);
111+
}
112+
retv = PyObject_CallMethod(types_mod, "SassMap", "(O)", items);
113+
Py_DECREF(items);
114+
break;
115+
}
116+
case SASS_ERROR:
117+
case SASS_WARNING:
118+
/* @warning and @error cannot be passed */
119+
break;
120+
}
121+
122+
if (retv == NULL) {
123+
PyErr_SetString(PyExc_TypeError, "Unexpected sass type");
124+
}
125+
126+
Py_DECREF(types_mod);
127+
Py_DECREF(sass_comma);
128+
Py_DECREF(sass_space);
129+
return retv;
130+
}
131+
132+
static union Sass_Value* _to_sass_value(PyObject* value) {
133+
union Sass_Value* retv = NULL;
134+
PyObject* types_mod = PyImport_ImportModule("sassutils.sass_types");
135+
PyObject* sass_number_t = PyObject_GetAttrString(types_mod, "SassNumber");
136+
PyObject* sass_color_t = PyObject_GetAttrString(types_mod, "SassColor");
137+
PyObject* sass_list_t = PyObject_GetAttrString(types_mod, "SassList");
138+
PyObject* sass_warning_t = PyObject_GetAttrString(types_mod, "SassWarning");
139+
PyObject* sass_error_t = PyObject_GetAttrString(types_mod, "SassError");
140+
PyObject* sass_comma = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_COMMA");
141+
PyObject* sass_space = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_SPACE");
142+
143+
if (value == Py_None) {
144+
retv = sass_make_null();
145+
} else if (PyBool_Check(value)) {
146+
retv = sass_make_boolean(value == Py_True);
147+
} else if (PyUnicode_Check(value)) {
148+
PyObject* bytes = PyUnicode_AsEncodedString(value, "UTF-8", "strict");
149+
retv = sass_make_string(PySass_Bytes_AS_STRING(bytes));
150+
Py_DECREF(bytes);
151+
} else if (PySass_Bytes_Check(value)) {
152+
retv = sass_make_string(PySass_Bytes_AS_STRING(value));
153+
} else if (PyDict_Check(value)) {
154+
size_t i = 0;
155+
Py_ssize_t pos = 0;
156+
PyObject* d_key = NULL;
157+
PyObject* d_value = NULL;
158+
retv = sass_make_map(PyDict_Size(value));
159+
while (PyDict_Next(value, &pos, &d_key, &d_value)) {
160+
sass_map_set_key(retv, i, _to_sass_value(d_key));
161+
sass_map_set_value(retv, i, _to_sass_value(d_value));
162+
i += 1;
163+
}
164+
} else if (PyObject_IsInstance(value, sass_number_t)) {
165+
PyObject* d_value = PyObject_GetAttrString(value, "value");
166+
PyObject* unit = PyObject_GetAttrString(value, "unit");
167+
PyObject* bytes = PyUnicode_AsEncodedString(unit, "UTF-8", "strict");
168+
retv = sass_make_number(
169+
PyFloat_AsDouble(d_value), PySass_Bytes_AS_STRING(bytes)
170+
);
171+
Py_DECREF(d_value);
172+
Py_DECREF(unit);
173+
Py_DECREF(bytes);
174+
} else if (PyObject_IsInstance(value, sass_color_t)) {
175+
PyObject* r_value = PyObject_GetAttrString(value, "r");
176+
PyObject* g_value = PyObject_GetAttrString(value, "g");
177+
PyObject* b_value = PyObject_GetAttrString(value, "b");
178+
PyObject* a_value = PyObject_GetAttrString(value, "a");
179+
retv = sass_make_color(
180+
PyFloat_AsDouble(r_value),
181+
PyFloat_AsDouble(g_value),
182+
PyFloat_AsDouble(b_value),
183+
PyFloat_AsDouble(a_value)
184+
);
185+
Py_DECREF(r_value);
186+
Py_DECREF(g_value);
187+
Py_DECREF(b_value);
188+
Py_DECREF(a_value);
189+
} else if (PyObject_IsInstance(value, sass_list_t)) {
190+
Py_ssize_t i = 0;
191+
PyObject* items = PyObject_GetAttrString(value, "items");
192+
PyObject* separator = PyObject_GetAttrString(value, "separator");
193+
/* TODO: I don't really like this, maybe move types to C */
194+
Sass_Separator sep = SASS_COMMA;
195+
if (separator == sass_comma) {
196+
sep = SASS_COMMA;
197+
} else if (separator == sass_space) {
198+
sep = SASS_SPACE;
199+
} else {
200+
assert(0);
201+
}
202+
retv = sass_make_list(PyTuple_Size(items), sep);
203+
for (i = 0; i < PyTuple_Size(items); i += 1) {
204+
sass_list_set_value(
205+
retv, i, _to_sass_value(PyTuple_GET_ITEM(items, i))
206+
);
207+
}
208+
Py_DECREF(items);
209+
Py_DECREF(separator);
210+
} else if (PyObject_IsInstance(value, sass_warning_t)) {
211+
PyObject* msg = PyObject_GetAttrString(value, "msg");
212+
PyObject* bytes = PyUnicode_AsEncodedString(msg, "UTF-8", "strict");
213+
retv = sass_make_warning(PySass_Bytes_AS_STRING(bytes));
214+
Py_DECREF(msg);
215+
Py_DECREF(bytes);
216+
} else if (PyObject_IsInstance(value, sass_error_t)) {
217+
PyObject* msg = PyObject_GetAttrString(value, "msg");
218+
PyObject* bytes = PyUnicode_AsEncodedString(msg, "UTF-8", "strict");
219+
retv = sass_make_error(PySass_Bytes_AS_STRING(bytes));
220+
Py_DECREF(msg);
221+
Py_DECREF(bytes);
222+
}
223+
224+
if (retv == NULL) {
225+
PyObject* type = PyObject_Type(value);
226+
PyObject* type_name = PyObject_GetAttrString(type, "__name__");
227+
PyObject* fmt = PyUnicode_FromString(
228+
"Unexpected type: `{0}`.\n"
229+
"Expected one of:\n"
230+
"- None\n"
231+
"- bool\n"
232+
"- str\n"
233+
"- SassNumber\n"
234+
"- SassColor\n"
235+
"- SassList\n"
236+
"- dict\n"
237+
"- SassMap\n"
238+
"- SassWarning\n"
239+
"- SassError\n"
240+
);
241+
PyObject* format_meth = PyObject_GetAttrString(fmt, "format");
242+
PyObject* result = PyObject_CallFunctionObjArgs(
243+
format_meth, type_name, NULL
244+
);
245+
PyObject* bytes = PyUnicode_AsEncodedString(result, "UTF-8", "strict");
246+
retv = sass_make_error(PySass_Bytes_AS_STRING(bytes));
247+
Py_DECREF(type);
248+
Py_DECREF(type_name);
249+
Py_DECREF(fmt);
250+
Py_DECREF(format_meth);
251+
Py_DECREF(result);
252+
Py_DECREF(bytes);
253+
}
254+
255+
Py_DECREF(types_mod);
256+
Py_DECREF(sass_number_t);
257+
Py_DECREF(sass_color_t);
258+
Py_DECREF(sass_list_t);
259+
Py_DECREF(sass_warning_t);
260+
Py_DECREF(sass_error_t);
261+
Py_DECREF(sass_comma);
262+
Py_DECREF(sass_space);
263+
return retv;
264+
}
265+
266+
static union Sass_Value* _call_py_f(
267+
const union Sass_Value* sass_args, void* cookie
268+
) {
269+
size_t i;
270+
PyObject* pyfunc = (PyObject*)cookie;
271+
PyObject* py_args = PyTuple_New(sass_list_get_length(sass_args));
272+
PyObject* py_result = NULL;
273+
union Sass_Value* sass_result = NULL;
274+
275+
for (i = 0; i < sass_list_get_length(sass_args); i += 1) {
276+
union Sass_Value* sass_arg = sass_list_get_value(sass_args, i);
277+
PyObject* py_arg = NULL;
278+
if (!(py_arg = _to_py_value(sass_arg))) goto done;
279+
PyTuple_SetItem(py_args, i, py_arg);
280+
}
281+
282+
if (!(py_result = PyObject_CallObject(pyfunc, py_args))) goto done;
283+
sass_result = _to_sass_value(py_result);
284+
285+
done:
286+
if (sass_result == NULL) {
287+
PyObject* etype = NULL;
288+
PyObject* evalue = NULL;
289+
PyObject* etb = NULL;
290+
{
291+
PyErr_Fetch(&etype, &evalue, &etb);
292+
PyObject* traceback_mod = PyImport_ImportModule("traceback");
293+
PyObject* traceback_parts = PyObject_CallMethod(
294+
traceback_mod, "format_exception", "OOO", etype, evalue, etb
295+
);
296+
PyList_Insert(traceback_parts, 0, PyUnicode_FromString("\n"));
297+
PyObject* joinstr = PyUnicode_FromString("");
298+
PyObject* result = PyUnicode_Join(joinstr, traceback_parts);
299+
PyObject* bytes = PyUnicode_AsEncodedString(
300+
result, "UTF-8", "strict"
301+
);
302+
sass_result = sass_make_error(PySass_Bytes_AS_STRING(bytes));
303+
Py_DECREF(traceback_mod);
304+
Py_DECREF(traceback_parts);
305+
Py_DECREF(joinstr);
306+
Py_DECREF(result);
307+
Py_DECREF(bytes);
308+
}
309+
}
310+
Py_XDECREF(py_args);
311+
Py_XDECREF(py_result);
312+
return sass_result;
313+
}
314+
315+
316+
static void _add_custom_functions(
317+
struct Sass_Options* options, PyObject* custom_functions
318+
) {
319+
Py_ssize_t i;
320+
Sass_C_Function_List fn_list = sass_make_function_list(
321+
PyList_Size(custom_functions)
322+
);
323+
for (i = 0; i < PyList_GET_SIZE(custom_functions); i += 1) {
324+
PyObject* signature_and_func = PyList_GET_ITEM(custom_functions, i);
325+
PyObject* signature = PyTuple_GET_ITEM(signature_and_func, 0);
326+
PyObject* func = PyTuple_GET_ITEM(signature_and_func, 1);
327+
Sass_C_Function_Callback fn = sass_make_function(
328+
PySass_Bytes_AS_STRING(signature),
329+
_call_py_f,
330+
func
331+
);
332+
sass_function_set_list_entry(fn_list, i, fn);
333+
}
334+
sass_option_set_c_functions(options, fn_list);
335+
}
336+
38337
static PyObject *
39338
PySass_compile_string(PyObject *self, PyObject *args) {
40339
struct Sass_Context *ctx;
@@ -44,12 +343,14 @@ PySass_compile_string(PyObject *self, PyObject *args) {
44343
const char *error_message, *output_string;
45344
Sass_Output_Style output_style;
46345
int source_comments, error_status, precision;
346+
PyObject *custom_functions;
47347
PyObject *result;
48348

49349
if (!PyArg_ParseTuple(args,
50-
PySass_IF_PY3("yiiyyi", "siissi"),
350+
PySass_IF_PY3("yiiyyiO", "siissiO"),
51351
&string, &output_style, &source_comments,
52-
&include_paths, &image_path, &precision)) {
352+
&include_paths, &image_path, &precision,
353+
&custom_functions)) {
53354
return NULL;
54355
}
55356

@@ -60,6 +361,7 @@ PySass_compile_string(PyObject *self, PyObject *args) {
60361
sass_option_set_include_path(options, include_paths);
61362
sass_option_set_image_path(options, image_path);
62363
sass_option_set_precision(options, precision);
364+
_add_custom_functions(options, custom_functions);
63365

64366
sass_compile_data_context(context);
65367

@@ -85,12 +387,13 @@ PySass_compile_filename(PyObject *self, PyObject *args) {
85387
const char *error_message, *output_string, *source_map_string;
86388
Sass_Output_Style output_style;
87389
int source_comments, error_status, precision;
88-
PyObject *source_map_filename, *result;
390+
PyObject *source_map_filename, *custom_functions, *result;
89391

90392
if (!PyArg_ParseTuple(args,
91-
PySass_IF_PY3("yiiyyiO", "siissiO"),
393+
PySass_IF_PY3("yiiyyiOO", "siissiOO"),
92394
&filename, &output_style, &source_comments,
93-
&include_paths, &image_path, &precision, &source_map_filename)) {
395+
&include_paths, &image_path, &precision,
396+
&source_map_filename, &custom_functions)) {
94397
return NULL;
95398
}
96399

@@ -114,6 +417,7 @@ PySass_compile_filename(PyObject *self, PyObject *args) {
114417
sass_option_set_include_path(options, include_paths);
115418
sass_option_set_image_path(options, image_path);
116419
sass_option_set_precision(options, precision);
420+
_add_custom_functions(options, custom_functions);
117421

118422
sass_compile_file_context(context);
119423

0 commit comments

Comments
 (0)