Skip to content
This repository was archived by the owner on Oct 24, 2025. It is now read-only.

Commit f3acec5

Browse files
committed
Finish implementation of custom importers
1 parent ea9a7c9 commit f3acec5

File tree

4 files changed

+252
-125
lines changed

4 files changed

+252
-125
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ build2/libsass/cpp/%.o: libsass/src/%.cpp
2626

2727
build2/pysass.o: pysass.cpp
2828
@mkdir -p build2
29-
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I./libsass/include $(PY_HEADERS) -c $^ -o $@ -c -O2 -fPIC -std=c++0x -Wall -Wno-parentheses
29+
gcc -pthread -fno-strict-aliasing -Wno-write-strings -DNDEBUG -g -fwrapv -O2 -Wall -fPIC -I./libsass/include $(PY_HEADERS) -c $^ -o $@ -c -O2 -fPIC -std=c++0x -Wall -Wno-parentheses
3030

3131
_sass.so: $(C_OBJECTS) $(CPP_OBJECTS) build2/pysass.o
3232
g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro $^ -L./libsass -o $@ -fPIC -lstdc++

pysass.cpp

Lines changed: 68 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -272,42 +272,7 @@ static union Sass_Value* _unknown_type_to_sass_error(PyObject* value) {
272272
return retv;
273273
}
274274

275-
static union Sass_Value* _exception_to_sass_error() {
276-
union Sass_Value* retv = NULL;
277-
PyObject* etype = NULL;
278-
PyObject* evalue = NULL;
279-
PyObject* etb = NULL;
280-
PyErr_Fetch(&etype, &evalue, &etb);
281-
PyErr_NormalizeException(&etype, &evalue, &etb);
282-
{
283-
PyObject* traceback_mod = PyImport_ImportModule("traceback");
284-
PyObject* traceback_parts = PyObject_CallMethod(
285-
traceback_mod, "format_exception", "OOO", etype, evalue, etb
286-
);
287-
PyList_Insert(traceback_parts, 0, PyUnicode_FromString("\n"));
288-
PyObject* joinstr = PyUnicode_FromString("");
289-
PyObject* result = PyUnicode_Join(joinstr, traceback_parts);
290-
PyObject* bytes = PyUnicode_AsEncodedString(
291-
result, "UTF-8", "strict"
292-
);
293-
retv = sass_make_error(PySass_Bytes_AS_STRING(bytes));
294-
Py_DECREF(traceback_mod);
295-
Py_DECREF(traceback_parts);
296-
Py_DECREF(joinstr);
297-
Py_DECREF(result);
298-
Py_DECREF(bytes);
299-
}
300-
Py_DECREF(etype);
301-
Py_DECREF(evalue);
302-
Py_DECREF(etb);
303-
return retv;
304-
}
305-
306275
static PyObject* _exception_to_bytes() {
307-
/* Grabs a Bytes instance for you to PySass_Bytes_AS_STRING.
308-
Remember to Py_DECREF the object later!
309-
TODO: This is a terrible violation of DRY, see above.
310-
*/
311276
PyObject* retv = NULL;
312277
PyObject* etype = NULL;
313278
PyObject* evalue = NULL;
@@ -334,6 +299,22 @@ static PyObject* _exception_to_bytes() {
334299
return retv;
335300
}
336301

302+
static union Sass_Value* _exception_to_sass_error() {
303+
PyObject* bytes = _exception_to_bytes();
304+
union Sass_Value* retv = sass_make_error(PySass_Bytes_AS_STRING(bytes));
305+
Py_DECREF(bytes);
306+
return retv;
307+
}
308+
309+
static Sass_Import_List _exception_to_sass_import_error(const char* path) {
310+
PyObject* bytes = _exception_to_bytes();
311+
Sass_Import_List import_list = sass_make_import_list(1);
312+
import_list[0] = sass_make_import_entry(path, 0, 0);
313+
sass_import_set_error(import_list[0], PySass_Bytes_AS_STRING(bytes), 0, 0);
314+
Py_DECREF(bytes);
315+
return import_list;
316+
}
317+
337318
static union Sass_Value* _to_sass_value(PyObject* value) {
338319
union Sass_Value* retv = NULL;
339320
PyObject* types_mod = PyImport_ImportModule("sass");
@@ -435,74 +416,64 @@ static void _add_custom_functions(
435416
sass_option_set_c_functions(options, fn_list);
436417
}
437418

438-
Sass_Import_List _call_py_importer_f(
439-
const char* path,
440-
Sass_Importer_Entry cb,
441-
struct Sass_Compiler* comp
419+
static Sass_Import_List _call_py_importer_f(
420+
const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp
442421
) {
443422
PyObject* pyfunc = (PyObject*)sass_importer_get_cookie(cb);
444-
PyObject* py_path = PyUnicode_FromString(path);
445423
PyObject* py_result = NULL;
446-
PyObject *iterator;
447-
PyObject *import_item;
448424
Sass_Import_List sass_imports = NULL;
449425
Py_ssize_t i;
450426

451-
py_result = PyObject_CallObject(pyfunc, PySass_IF_PY3("y", "s"), py_path);
452-
453-
if (!py_result) {
454-
sass_imports = sass_make_import_list(1);
455-
sass_imports[0] = sass_make_import_entry(path, 0, 0);
456-
457-
PyObject* exc = _exception_to_bytes();
458-
char* err = PySass_Bytes_AS_STRING(exc);
427+
py_result = PyObject_CallFunction(pyfunc, PySass_IF_PY3("y", "s"), path);
459428

460-
sass_import_set_error(sass_imports[0],
461-
err,
462-
0, 0);
463-
464-
Py_XDECREF(exc);
465-
Py_XDECREF(py_result);
466-
return sass_imports;
467-
}
429+
/* Handle importer throwing an exception */
430+
if (!py_result) goto done;
468431

432+
/* Could return None indicating it could not handle the import */
469433
if (py_result == Py_None) {
470434
Py_XDECREF(py_result);
471-
return 0;
435+
return NULL;
472436
}
473437

474-
sass_imports = sass_make_import_list(PyList_Size(py_result));
475-
476-
iterator = PyObject_GetIter(py_result);
477-
while (import_item = PyIter_Next(iterator)) {
438+
/* Otherwise, we know our importer is well formed (because we wrap it)
439+
* The return value will be a tuple of 1, 2, or 3 tuples */
440+
sass_imports = sass_make_import_list(PyTuple_GET_SIZE(py_result));
441+
for (i = 0; i < PyTuple_GET_SIZE(py_result); i += 1) {
478442
char* path_str = NULL; /* XXX: Memory leak? */
479443
char* source_str = NULL;
480444
char* sourcemap_str = NULL;
481-
482-
/* TODO: Switch statement and error handling for default case. Better way? */
483-
if ( PyTuple_GET_SIZE(import_item) == 1 ) {
484-
PyArg_ParseTuple(import_item, "es",
485-
0, &path_str);
486-
} else if ( PyTuple_GET_SIZE(import_item) == 2 ) {
487-
PyArg_ParseTuple(import_item, "eses",
488-
0, &path_str, 0, &source_str);
489-
} else if ( PyTuple_GET_SIZE(import_item) == 3 ) {
490-
PyArg_ParseTuple(import_item, "eseses",
491-
0, &path_str, 0, &source_str, 0, &sourcemap_str);
445+
PyObject* tup = PyTuple_GET_ITEM(py_result, i);
446+
Py_ssize_t size = PyTuple_GET_SIZE(tup);
447+
448+
if (size == 1) {
449+
PyArg_ParseTuple(tup, PySass_IF_PY3("y", "s"), &path_str);
450+
} else if (size == 2) {
451+
PyArg_ParseTuple(
452+
tup, PySass_IF_PY3("yy", "ss"), &path_str, &source_str
453+
);
454+
} else if (size == 3) {
455+
PyArg_ParseTuple(
456+
tup, PySass_IF_PY3("yyy", "sss"),
457+
&path_str, &source_str, &sourcemap_str
458+
);
492459
}
493460

494461
/* We need to give copies of these arguments; libsass handles
495-
deallocation of them later, whereas path_str is left flapping
496-
in the breeze -- it's treated const, so that's okay. */
497-
if ( source_str ) source_str = strdup(source_str);
498-
if ( sourcemap_str ) sourcemap_str = strdup(sourcemap_str);
462+
* deallocation of them later, whereas path_str is left flapping
463+
* in the breeze -- it's treated const, so that's okay. */
464+
if (source_str) source_str = strdup(source_str);
465+
if (sourcemap_str) sourcemap_str = strdup(sourcemap_str);
499466

500-
sass_imports[i] = sass_make_import_entry(path_str, source_str, sourcemap_str);
467+
sass_imports[i] = sass_make_import_entry(
468+
path_str, source_str, sourcemap_str
469+
);
470+
}
501471

502-
Py_XDECREF(import_item);
472+
done:
473+
if (sass_imports == NULL) {
474+
sass_imports = _exception_to_sass_import_error(path);
503475
}
504476

505-
Py_XDECREF(iterator);
506477
Py_XDECREF(py_result);
507478

508479
return sass_imports;
@@ -513,26 +484,25 @@ static void _add_custom_importers(
513484
) {
514485
Py_ssize_t i;
515486
Sass_Importer_List importer_list;
516-
517-
if ( custom_importers == Py_None ) {
487+
488+
if (custom_importers == Py_None) {
518489
return;
519490
}
520-
521-
importer_list = sass_make_importer_list(PyList_Size(custom_importers));
522-
523-
for (i = 0; i < PyList_GET_SIZE(custom_importers); i += 1) {
524-
PyObject* item = PyList_GET_ITEM(custom_importers, i);
491+
492+
importer_list = sass_make_importer_list(PyTuple_GET_SIZE(custom_importers));
493+
494+
for (i = 0; i < PyTuple_GET_SIZE(custom_importers); i += 1) {
495+
PyObject* item = PyTuple_GET_ITEM(custom_importers, i);
525496
int priority = 0;
526497
PyObject* import_function = NULL;
527-
528-
PyArg_ParseTuple(item, "iO",
529-
&priority, &import_function);
530-
531-
importer_list[i] = sass_make_importer(_call_py_importer_f,
532-
priority,
533-
import_function);
498+
499+
PyArg_ParseTuple(item, "iO", &priority, &import_function);
500+
501+
importer_list[i] = sass_make_importer(
502+
_call_py_importer_f, priority, import_function
503+
);
534504
}
535-
505+
536506
sass_option_set_c_importers(options, importer_list);
537507
}
538508

@@ -548,7 +518,7 @@ PySass_compile_string(PyObject *self, PyObject *args) {
548518
PyObject *custom_functions;
549519
PyObject *custom_importers;
550520
PyObject *result;
551-
521+
552522
if (!PyArg_ParseTuple(args,
553523
PySass_IF_PY3("yiiyiOiO", "siisiOiO"),
554524
&string, &output_style, &source_comments,
@@ -566,7 +536,6 @@ PySass_compile_string(PyObject *self, PyObject *args) {
566536
sass_option_set_is_indented_syntax_src(options, indented);
567537
_add_custom_functions(options, custom_functions);
568538
_add_custom_importers(options, custom_importers);
569-
570539
sass_compile_data_context(context);
571540

572541
ctx = sass_data_context_get_context(context);
@@ -578,7 +547,6 @@ PySass_compile_string(PyObject *self, PyObject *args) {
578547
(short int) !error_status,
579548
error_status ? error_message : output_string
580549
);
581-
582550
sass_delete_data_context(context);
583551
return result;
584552
}
@@ -594,7 +562,7 @@ PySass_compile_filename(PyObject *self, PyObject *args) {
594562
int source_comments, error_status, precision;
595563
PyObject *source_map_filename, *custom_functions, *custom_importers,
596564
*result;
597-
565+
598566
if (!PyArg_ParseTuple(args,
599567
PySass_IF_PY3("yiiyiOOO", "siisiOOO"),
600568
&filename, &output_style, &source_comments,
@@ -625,7 +593,6 @@ PySass_compile_filename(PyObject *self, PyObject *args) {
625593
sass_option_set_precision(options, precision);
626594
_add_custom_functions(options, custom_functions);
627595
_add_custom_importers(options, custom_importers);
628-
629596
sass_compile_file_context(context);
630597

631598
ctx = sass_file_context_get_context(context);

sass.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from __future__ import absolute_import
1414

1515
import collections
16+
import functools
1617
import inspect
1718
from io import open
1819
import os
@@ -144,6 +145,56 @@ def __str__(self):
144145
return self.signature
145146

146147

148+
def _normalize_importer_return_value(result):
149+
# An importer must return an iterable of iterables of 1-3 stringlike
150+
# objects
151+
if result is None:
152+
return result
153+
154+
def _to_importer_result(single_result):
155+
single_result = tuple(single_result)
156+
if len(single_result) not in (1, 2, 3):
157+
raise ValueError(
158+
'Expected importer result to be a tuple of length (1, 2, 3) '
159+
'but got {0}: {1!r}'.format(len(single_result), single_result)
160+
)
161+
162+
def _to_bytes(obj):
163+
if not isinstance(obj, bytes):
164+
return obj.encode('UTF-8')
165+
else:
166+
return obj
167+
168+
return tuple(_to_bytes(s) for s in single_result)
169+
170+
return tuple(_to_importer_result(x) for x in result)
171+
172+
173+
def _importer_callback_wrapper(func):
174+
@functools.wraps(func)
175+
def inner(path):
176+
ret = func(path.decode('UTF-8'))
177+
return _normalize_importer_return_value(ret)
178+
return inner
179+
180+
181+
def _validate_importers(importers):
182+
"""Validates the importers and decorates the callables with our output
183+
formatter.
184+
"""
185+
# They could have no importers, that's chill
186+
if importers is None:
187+
return None
188+
189+
def _to_importer(priority, func):
190+
assert isinstance(priority, int), priority
191+
assert callable(func), func
192+
return (priority, _importer_callback_wrapper(func))
193+
194+
# Our code assumes tuple of tuples
195+
return tuple(_to_importer(priority, func) for priority, func in importers)
196+
197+
147198
def compile_dirname(
148199
search_path, output_path, output_style, source_comments, include_paths,
149200
precision, custom_functions, importers
@@ -509,7 +560,7 @@ def my_importer(path):
509560
'not {1!r}'.format(SassFunction, custom_functions)
510561
)
511562

512-
importers = kwargs.pop('importers', None)
563+
importers = _validate_importers(kwargs.pop('importers', None))
513564

514565
if 'string' in modes:
515566
string = kwargs.pop('string')
@@ -537,7 +588,7 @@ def my_importer(path):
537588
_check_no_remaining_kwargs(compile, kwargs)
538589
s, v, source_map = compile_filename(
539590
filename, output_style, source_comments, include_paths, precision,
540-
source_map_filename, custom_functions, importers
591+
source_map_filename, custom_functions, importers,
541592
)
542593
if s:
543594
v = v.decode('utf-8')
@@ -583,7 +634,7 @@ def my_importer(path):
583634
_check_no_remaining_kwargs(compile, kwargs)
584635
s, v = compile_dirname(
585636
search_path, output_path, output_style, source_comments,
586-
include_paths, precision, custom_functions, importers
637+
include_paths, precision, custom_functions, importers,
587638
)
588639
if s:
589640
return

0 commit comments

Comments
 (0)