libsass-python/_sass.c

702 lines
24 KiB
C

#include <Python.h>
#include <sass/context.h>
#if PY_MAJOR_VERSION >= 3
#define PySass_IF_PY3(three, two) (three)
#define PySass_Object_Bytes(o) PyUnicode_AsUTF8String(PyObject_Str(o))
#define COLLECTIONS_ABC_MOD "collections.abc"
#else
#define PySass_IF_PY3(three, two) (two)
#define PySass_Object_Bytes(o) PyObject_Str(o)
#define COLLECTIONS_ABC_MOD "collections"
#endif
static PyObject* _to_py_value(const union Sass_Value* value);
static union Sass_Value* _to_sass_value(PyObject* value);
static union Sass_Value* _color_to_sass_value(PyObject* value);
static union Sass_Value* _number_to_sass_value(PyObject* value);
static union Sass_Value* _list_to_sass_value(PyObject* value);
static union Sass_Value* _mapping_to_sass_value(PyObject* value);
static union Sass_Value* _unicode_to_sass_value(PyObject* value);
static union Sass_Value* _warning_to_sass_value(PyObject* value);
static union Sass_Value* _error_to_sass_value(PyObject* value);
static union Sass_Value* _unknown_type_to_sass_error(PyObject* value);
static union Sass_Value* _exception_to_sass_error();
static PyObject* _to_py_value(const union Sass_Value* value) {
PyObject* retv = NULL;
PyObject* types_mod = PyImport_ImportModule("sass");
PyObject* sass_comma = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_COMMA");
PyObject* sass_space = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_SPACE");
switch (sass_value_get_tag(value)) {
case SASS_NULL:
retv = Py_None;
Py_INCREF(retv);
break;
case SASS_BOOLEAN:
retv = PyBool_FromLong(sass_boolean_get_value(value));
break;
case SASS_STRING:
retv = PyUnicode_FromString(sass_string_get_value(value));
break;
case SASS_NUMBER:
retv = PyObject_CallMethod(
types_mod,
"SassNumber",
PySass_IF_PY3("dy", "ds"),
sass_number_get_value(value),
sass_number_get_unit(value)
);
break;
case SASS_COLOR:
retv = PyObject_CallMethod(
types_mod,
"SassColor",
"dddd",
sass_color_get_r(value),
sass_color_get_g(value),
sass_color_get_b(value),
sass_color_get_a(value)
);
break;
case SASS_LIST: {
size_t i = 0;
PyObject* items = PyTuple_New(sass_list_get_length(value));
PyObject* separator = sass_comma;
int is_bracketed = sass_list_get_is_bracketed(value);
PyObject* bracketed = PyBool_FromLong(is_bracketed);
switch (sass_list_get_separator(value)) {
case SASS_COMMA:
separator = sass_comma;
break;
case SASS_SPACE:
separator = sass_space;
break;
case SASS_HASH:
assert(0);
break;
}
for (i = 0; i < sass_list_get_length(value); i += 1) {
PyTuple_SetItem(
items,
i,
_to_py_value(sass_list_get_value(value, i))
);
}
retv = PyObject_CallMethod(
types_mod, "SassList", "OOO", items, separator, bracketed
);
break;
}
case SASS_MAP: {
size_t i = 0;
PyObject* items = PyTuple_New(sass_map_get_length(value));
for (i = 0; i < sass_map_get_length(value); i += 1) {
PyObject* kvp = PyTuple_New(2);
PyTuple_SetItem(
kvp, 0, _to_py_value(sass_map_get_key(value, i))
);
PyTuple_SetItem(
kvp, 1, _to_py_value(sass_map_get_value(value, i))
);
PyTuple_SetItem(items, i, kvp);
}
retv = PyObject_CallMethod(types_mod, "SassMap", "(O)", items);
Py_DECREF(items);
break;
}
case SASS_ERROR:
case SASS_WARNING:
/* @warning and @error cannot be passed */
break;
}
if (retv == NULL) {
PyErr_SetString(PyExc_TypeError, "Unexpected sass type");
}
Py_DECREF(types_mod);
Py_DECREF(sass_comma);
Py_DECREF(sass_space);
return retv;
}
static union Sass_Value* _color_to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* r_value = PyObject_GetAttrString(value, "r");
PyObject* g_value = PyObject_GetAttrString(value, "g");
PyObject* b_value = PyObject_GetAttrString(value, "b");
PyObject* a_value = PyObject_GetAttrString(value, "a");
retv = sass_make_color(
PyFloat_AsDouble(r_value),
PyFloat_AsDouble(g_value),
PyFloat_AsDouble(b_value),
PyFloat_AsDouble(a_value)
);
Py_DECREF(r_value);
Py_DECREF(g_value);
Py_DECREF(b_value);
Py_DECREF(a_value);
return retv;
}
static union Sass_Value* _list_to_sass_value(PyObject* value) {
PyObject* types_mod = PyImport_ImportModule("sass");
PyObject* sass_comma = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_COMMA");
PyObject* sass_space = PyObject_GetAttrString(types_mod, "SASS_SEPARATOR_SPACE");
union Sass_Value* retv = NULL;
Py_ssize_t i = 0;
PyObject* items = PyObject_GetAttrString(value, "items");
PyObject* separator = PyObject_GetAttrString(value, "separator");
PyObject* bracketed = PyObject_GetAttrString(value, "bracketed");
enum Sass_Separator sep = SASS_COMMA;
if (separator == sass_comma) {
sep = SASS_COMMA;
} else if (separator == sass_space) {
sep = SASS_SPACE;
} else {
assert(0);
}
int is_bracketed = bracketed == Py_True;
retv = sass_make_list(PyTuple_Size(items), sep, is_bracketed);
for (i = 0; i < PyTuple_Size(items); i += 1) {
sass_list_set_value(
retv, i, _to_sass_value(PyTuple_GetItem(items, i))
);
}
Py_DECREF(types_mod);
Py_DECREF(sass_comma);
Py_DECREF(sass_space);
Py_DECREF(items);
Py_DECREF(separator);
Py_DECREF(bracketed);
return retv;
}
static union Sass_Value* _mapping_to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
size_t i = 0;
Py_ssize_t pos = 0;
PyObject* d_key = NULL;
PyObject* d_value = NULL;
PyObject* dct = PyDict_New();
PyDict_Update(dct, value);
retv = sass_make_map(PyDict_Size(dct));
while (PyDict_Next(dct, &pos, &d_key, &d_value)) {
sass_map_set_key(retv, i, _to_sass_value(d_key));
sass_map_set_value(retv, i, _to_sass_value(d_value));
i += 1;
}
Py_DECREF(dct);
return retv;
}
static union Sass_Value* _number_to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* d_value = PyObject_GetAttrString(value, "value");
PyObject* unit = PyObject_GetAttrString(value, "unit");
PyObject* bytes = PyUnicode_AsEncodedString(unit, "UTF-8", "strict");
retv = sass_make_number(
PyFloat_AsDouble(d_value), PyBytes_AsString(bytes)
);
Py_DECREF(d_value);
Py_DECREF(unit);
Py_DECREF(bytes);
return retv;
}
static union Sass_Value* _unicode_to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* bytes = PyUnicode_AsEncodedString(value, "UTF-8", "strict");
retv = sass_make_string(PyBytes_AsString(bytes));
Py_DECREF(bytes);
return retv;
}
static union Sass_Value* _warning_to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* msg = PyObject_GetAttrString(value, "msg");
PyObject* bytes = PyUnicode_AsEncodedString(msg, "UTF-8", "strict");
retv = sass_make_warning(PyBytes_AsString(bytes));
Py_DECREF(msg);
Py_DECREF(bytes);
return retv;
}
static union Sass_Value* _error_to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* msg = PyObject_GetAttrString(value, "msg");
PyObject* bytes = PyUnicode_AsEncodedString(msg, "UTF-8", "strict");
retv = sass_make_error(PyBytes_AsString(bytes));
Py_DECREF(msg);
Py_DECREF(bytes);
return retv;
}
static union Sass_Value* _unknown_type_to_sass_error(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* type = PyObject_Type(value);
PyObject* type_name = PyObject_GetAttrString(type, "__name__");
PyObject* fmt = PyUnicode_FromString(
"Unexpected type: `{0}`.\n"
"Expected one of:\n"
"- None\n"
"- bool\n"
"- str\n"
"- SassNumber\n"
"- SassColor\n"
"- SassList\n"
"- dict\n"
"- SassMap\n"
"- SassWarning\n"
"- SassError\n"
);
PyObject* format_meth = PyObject_GetAttrString(fmt, "format");
PyObject* result = PyObject_CallFunctionObjArgs(
format_meth, type_name, NULL
);
PyObject* bytes = PyUnicode_AsEncodedString(result, "UTF-8", "strict");
retv = sass_make_error(PyBytes_AsString(bytes));
Py_DECREF(type);
Py_DECREF(type_name);
Py_DECREF(fmt);
Py_DECREF(format_meth);
Py_DECREF(result);
Py_DECREF(bytes);
return retv;
}
static PyObject* _exception_to_bytes() {
PyObject* retv = NULL;
PyObject* etype = NULL;
PyObject* evalue = NULL;
PyObject* etb = NULL;
PyErr_Fetch(&etype, &evalue, &etb);
PyErr_NormalizeException(&etype, &evalue, &etb);
{
PyObject* traceback_mod = PyImport_ImportModule("traceback");
PyObject* traceback_parts = PyObject_CallMethod(
traceback_mod, "format_exception", "OOO", etype, evalue, etb
);
PyList_Insert(traceback_parts, 0, PyUnicode_FromString("\n"));
PyObject* joinstr = PyUnicode_FromString("");
PyObject* result = PyUnicode_Join(joinstr, traceback_parts);
retv = PyUnicode_AsEncodedString(result, "UTF-8", "strict");
Py_DECREF(traceback_mod);
Py_DECREF(traceback_parts);
Py_DECREF(joinstr);
Py_DECREF(result);
}
Py_DECREF(etype);
Py_DECREF(evalue);
Py_DECREF(etb);
return retv;
}
static union Sass_Value* _exception_to_sass_error() {
PyObject* bytes = _exception_to_bytes();
union Sass_Value* retv = sass_make_error(PyBytes_AsString(bytes));
Py_DECREF(bytes);
return retv;
}
static Sass_Import_List _exception_to_sass_import_error(const char* path) {
PyObject* bytes = _exception_to_bytes();
Sass_Import_List import_list = sass_make_import_list(1);
import_list[0] = sass_make_import_entry(path, 0, 0);
sass_import_set_error(import_list[0], PyBytes_AsString(bytes), 0, 0);
Py_DECREF(bytes);
return import_list;
}
static union Sass_Value* _to_sass_value(PyObject* value) {
union Sass_Value* retv = NULL;
PyObject* types_mod = PyImport_ImportModule("sass");
PyObject* sass_number_t = PyObject_GetAttrString(types_mod, "SassNumber");
PyObject* sass_color_t = PyObject_GetAttrString(types_mod, "SassColor");
PyObject* sass_list_t = PyObject_GetAttrString(types_mod, "SassList");
PyObject* sass_warning_t = PyObject_GetAttrString(types_mod, "SassWarning");
PyObject* sass_error_t = PyObject_GetAttrString(types_mod, "SassError");
PyObject* collections_mod = PyImport_ImportModule(COLLECTIONS_ABC_MOD);
PyObject* mapping_t = PyObject_GetAttrString(collections_mod, "Mapping");
if (value == Py_None) {
retv = sass_make_null();
} else if (PyBool_Check(value)) {
retv = sass_make_boolean(value == Py_True);
} else if (PyUnicode_Check(value)) {
retv = _unicode_to_sass_value(value);
} else if (PyBytes_Check(value)) {
retv = sass_make_string(PyBytes_AsString(value));
/* XXX: PyMapping_Check returns true for lists and tuples in python3 :( */
/* XXX: pypy derps on dicts: https://bitbucket.org/pypy/pypy/issue/1970 */
} else if (PyDict_Check(value) || PyObject_IsInstance(value, mapping_t)) {
retv = _mapping_to_sass_value(value);
} else if (PyObject_IsInstance(value, sass_number_t)) {
retv = _number_to_sass_value(value);
} else if (PyObject_IsInstance(value, sass_color_t)) {
retv = _color_to_sass_value(value);
} else if (PyObject_IsInstance(value, sass_list_t)) {
retv = _list_to_sass_value(value);
} else if (PyObject_IsInstance(value, sass_warning_t)) {
retv = _warning_to_sass_value(value);
} else if (PyObject_IsInstance(value, sass_error_t)) {
retv = _error_to_sass_value(value);
}
if (retv == NULL) {
retv = _unknown_type_to_sass_error(value);
}
Py_DECREF(types_mod);
Py_DECREF(sass_number_t);
Py_DECREF(sass_color_t);
Py_DECREF(sass_list_t);
Py_DECREF(sass_warning_t);
Py_DECREF(sass_error_t);
Py_DECREF(collections_mod);
Py_DECREF(mapping_t);
return retv;
}
static union Sass_Value* _call_py_f(
const union Sass_Value* sass_args,
Sass_Function_Entry cb,
struct Sass_Compiler* compiler
) {
size_t i;
PyObject* pyfunc = (PyObject*)sass_function_get_cookie(cb);
PyObject* py_args = PyTuple_New(sass_list_get_length(sass_args));
PyObject* py_result = NULL;
union Sass_Value* sass_result = NULL;
for (i = 0; i < sass_list_get_length(sass_args); i += 1) {
const union Sass_Value* sass_arg = sass_list_get_value(sass_args, i);
PyObject* py_arg = NULL;
if (!(py_arg = _to_py_value(sass_arg))) goto done;
PyTuple_SetItem(py_args, i, py_arg);
}
if (!(py_result = PyObject_CallObject(pyfunc, py_args))) goto done;
sass_result = _to_sass_value(py_result);
done:
if (sass_result == NULL) {
sass_result = _exception_to_sass_error();
}
Py_XDECREF(py_args);
Py_XDECREF(py_result);
return sass_result;
}
static void _add_custom_functions(
struct Sass_Options* options, PyObject* custom_functions
) {
Py_ssize_t i;
Sass_Function_List fn_list = sass_make_function_list(
PyList_Size(custom_functions)
);
for (i = 0; i < PyList_Size(custom_functions); i += 1) {
PyObject* sass_function = PyList_GetItem(custom_functions, i);
PyObject* signature = PySass_Object_Bytes(sass_function);
Sass_Function_Entry fn = sass_make_function(
PyBytes_AsString(signature),
_call_py_f,
sass_function
);
sass_function_set_list_entry(fn_list, i, fn);
}
sass_option_set_c_functions(options, fn_list);
}
static Sass_Import_List _call_py_importer_f(
const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp
) {
PyObject* pyfunc = (PyObject*)sass_importer_get_cookie(cb);
PyObject* py_result = NULL;
Sass_Import_List sass_imports = NULL;
struct Sass_Import* previous;
const char* prev_path;
Py_ssize_t i;
previous = sass_compiler_get_last_import(comp);
prev_path = sass_import_get_abs_path(previous);
py_result = PyObject_CallFunction(pyfunc, PySass_IF_PY3("yy", "ss"), path, prev_path);
/* Handle importer throwing an exception */
if (!py_result) goto done;
/* Could return None indicating it could not handle the import */
if (py_result == Py_None) {
Py_XDECREF(py_result);
return NULL;
}
/* Otherwise, we know our importer is well formed (because we wrap it)
* The return value will be a tuple of 1, 2, or 3 tuples */
sass_imports = sass_make_import_list(PyTuple_Size(py_result));
for (i = 0; i < PyTuple_Size(py_result); i += 1) {
char* path_str = NULL; /* XXX: Memory leak? */
char* source_str = NULL;
char* sourcemap_str = NULL;
PyObject* tup = PyTuple_GetItem(py_result, i);
Py_ssize_t size = PyTuple_Size(tup);
if (size == 1) {
PyArg_ParseTuple(tup, PySass_IF_PY3("y", "s"), &path_str);
} else if (size == 2) {
PyArg_ParseTuple(
tup, PySass_IF_PY3("yy", "ss"), &path_str, &source_str
);
} else if (size == 3) {
PyArg_ParseTuple(
tup, PySass_IF_PY3("yyy", "sss"),
&path_str, &source_str, &sourcemap_str
);
}
/* We need to give copies of these arguments; libsass handles
* deallocation of them later, whereas path_str is left flapping
* in the breeze -- it's treated const, so that's okay. */
if (source_str) source_str = sass_copy_c_string(source_str);
if (sourcemap_str) sourcemap_str = sass_copy_c_string(sourcemap_str);
sass_imports[i] = sass_make_import_entry(
path_str, source_str, sourcemap_str
);
}
done:
if (sass_imports == NULL) {
sass_imports = _exception_to_sass_import_error(path);
}
Py_XDECREF(py_result);
return sass_imports;
}
static void _add_custom_importers(
struct Sass_Options* options, PyObject* custom_importers
) {
Py_ssize_t i;
Sass_Importer_List importer_list;
if (custom_importers == Py_None) {
return;
}
importer_list = sass_make_importer_list(PyTuple_Size(custom_importers));
for (i = 0; i < PyTuple_Size(custom_importers); i += 1) {
PyObject* item = PyTuple_GetItem(custom_importers, i);
int priority = 0;
PyObject* import_function = NULL;
PyArg_ParseTuple(item, "iO", &priority, &import_function);
importer_list[i] = sass_make_importer(
_call_py_importer_f, priority, import_function
);
}
sass_option_set_c_importers(options, importer_list);
}
static PyObject *
PySass_compile_string(PyObject *self, PyObject *args) {
struct Sass_Context *ctx;
struct Sass_Data_Context *context;
struct Sass_Options *options;
char *string, *include_paths;
const char *error_message, *output_string;
enum Sass_Output_Style output_style;
int source_comments, error_status, precision, indented,
source_map_embed, source_map_contents,
omit_source_map_url;
PyObject *custom_functions;
PyObject *custom_importers;
PyObject *source_map_root;
PyObject *result;
if (!PyArg_ParseTuple(args,
PySass_IF_PY3("yiiyiOiOiiiO", "siisiOiOiiiO"),
&string, &output_style, &source_comments,
&include_paths, &precision,
&custom_functions, &indented, &custom_importers,
&source_map_contents, &source_map_embed,
&omit_source_map_url, &source_map_root)) {
return NULL;
}
context = sass_make_data_context(sass_copy_c_string(string));
options = sass_data_context_get_options(context);
sass_option_set_output_style(options, output_style);
sass_option_set_source_comments(options, source_comments);
sass_option_set_include_path(options, include_paths);
sass_option_set_precision(options, precision);
sass_option_set_is_indented_syntax_src(options, indented);
sass_option_set_source_map_contents(options, source_map_contents);
sass_option_set_source_map_embed(options, source_map_embed);
sass_option_set_omit_source_map_url(options, omit_source_map_url);
if (PyBytes_Check(source_map_root) && PyBytes_Size(source_map_root)) {
sass_option_set_source_map_root(
options, PyBytes_AsString(source_map_root)
);
}
_add_custom_functions(options, custom_functions);
_add_custom_importers(options, custom_importers);
sass_compile_data_context(context);
ctx = sass_data_context_get_context(context);
error_status = sass_context_get_error_status(ctx);
error_message = sass_context_get_error_message(ctx);
output_string = sass_context_get_output_string(ctx);
result = Py_BuildValue(
PySass_IF_PY3("hy", "hs"),
(short int) !error_status,
error_status ? error_message : output_string
);
sass_delete_data_context(context);
return result;
}
static PyObject *
PySass_compile_filename(PyObject *self, PyObject *args) {
struct Sass_Context *ctx;
struct Sass_File_Context *context;
struct Sass_Options *options;
char *filename, *include_paths;
const char *error_message, *output_string, *source_map_string;
enum Sass_Output_Style output_style;
int source_comments, error_status, precision, source_map_embed,
source_map_contents, omit_source_map_url;
PyObject *source_map_filename, *custom_functions, *custom_importers,
*result, *output_filename_hint, *source_map_root;
if (!PyArg_ParseTuple(args,
PySass_IF_PY3("yiiyiOOOOiiiO", "siisiOOOOiiiO"),
&filename, &output_style, &source_comments,
&include_paths, &precision,
&source_map_filename, &custom_functions,
&custom_importers, &output_filename_hint,
&source_map_contents, &source_map_embed,
&omit_source_map_url, &source_map_root)) {
return NULL;
}
context = sass_make_file_context(filename);
options = sass_file_context_get_options(context);
if (PyBytes_Check(source_map_filename)) {
if (PyBytes_Size(source_map_filename)) {
sass_option_set_source_map_file(
options, PyBytes_AsString(source_map_filename)
);
}
}
if (PyBytes_Check(output_filename_hint)) {
if (PyBytes_Size(output_filename_hint)) {
sass_option_set_output_path(
options, PyBytes_AsString(output_filename_hint)
);
}
}
if (PyBytes_Check(source_map_root) && PyBytes_Size(source_map_root)) {
sass_option_set_source_map_root(
options, PyBytes_AsString(source_map_root)
);
}
sass_option_set_output_style(options, output_style);
sass_option_set_source_comments(options, source_comments);
sass_option_set_include_path(options, include_paths);
sass_option_set_precision(options, precision);
sass_option_set_source_map_contents(options, source_map_contents);
sass_option_set_source_map_embed(options, source_map_embed);
sass_option_set_omit_source_map_url(options, omit_source_map_url);
_add_custom_functions(options, custom_functions);
_add_custom_importers(options, custom_importers);
sass_compile_file_context(context);
ctx = sass_file_context_get_context(context);
error_status = sass_context_get_error_status(ctx);
error_message = sass_context_get_error_message(ctx);
output_string = sass_context_get_output_string(ctx);
source_map_string = sass_context_get_source_map_string(ctx);
result = Py_BuildValue(
PySass_IF_PY3("hyy", "hss"),
(short int) !error_status,
error_status ? error_message : output_string,
error_status || source_map_string == NULL ? "" : source_map_string
);
sass_delete_file_context(context);
return result;
}
static PyMethodDef PySass_methods[] = {
{"compile_string", PySass_compile_string, METH_VARARGS,
"Compile a Sass string."},
{"compile_filename", PySass_compile_filename, METH_VARARGS,
"Compile a Sass file."},
{NULL, NULL, 0, NULL}
};
static char PySass_doc[] = "The thin binding of libsass for Python.";
PyObject* PySass_make_enum_dict() {
PyObject* dct = PyDict_New();
PyDict_SetItemString(dct, "nested", PyLong_FromLong(SASS_STYLE_NESTED));
PyDict_SetItemString(dct, "expanded", PyLong_FromLong(SASS_STYLE_EXPANDED));
PyDict_SetItemString(dct, "compact", PyLong_FromLong(SASS_STYLE_COMPACT));
PyDict_SetItemString(dct, "compressed", PyLong_FromLong(SASS_STYLE_COMPRESSED));
return dct;
}
void PySass_init_module(PyObject *module) {
PyModule_AddObject(module, "OUTPUT_STYLES", PySass_make_enum_dict());
PyModule_AddObject(module, "libsass_version", PyUnicode_FromString(libsass_version()));
}
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef sassmodule = {
PyModuleDef_HEAD_INIT,
"_sass",
PySass_doc,
-1,
PySass_methods
};
PyMODINIT_FUNC
PyInit__sass()
{
PyObject *module = PyModule_Create(&sassmodule);
if (module != NULL) {
PySass_init_module(module);
}
return module;
}
#else
PyMODINIT_FUNC
init_sass()
{
PyObject *module;
module = Py_InitModule3("_sass", PySass_methods, PySass_doc);
if (module != NULL) {
PySass_init_module(module);
}
}
#endif