// Copyright (c) 2013-2014 David Caldwell.
// Copyright (c) 2014-2017 Marcel Greter.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// dont hook libc calls
#define NO_XSLOCKS
#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#undef my_setlocale
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#ifdef __cplusplus
}
#endif
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <sass.h>
#define isSafeSv(sv) sv && SvOK(*sv)
#define Constant(c) newCONSTSUB(stash, #c, newSViv(c))
#undef free
// implement this logic here for now
// libsass has no auto quoting concept
bool sass_string_need_quotes(char* str)
{
char* it = str;
if (*it == 0) return false;
if (!(
(*it >= 'a' && *it <= 'z') ||
(*it >= 'A' && *it <= 'Z')
)) return true;
++it;
while (*it) {
if (!(
(*it >= 127) ||
(*it >= '0' && *it <= '9') ||
(*it >= 'a' && *it <= 'z') ||
(*it >= 'A' && *it <= 'Z') ||
(*it == '\\' && *(it+1) != 0)
)) return true;
++it;
}
return false;
}
char* safe_svpv(SV* sv, char* _default)
{
size_t length;
char* str = SvPV(sv, length);
// NULL Terminated "array"
if (memchr(str, 0, length + 1))
return str;
return _default;
}
union Sass_Value* sass_make_error_f(char* format,...)
{
va_list ap;
va_start(ap, format);
SV* res = vnewSVpvf(format, &ap);
va_end(ap);
return sass_make_error(SvPV_nolen(res));
}
// convert from perl to libsass
union Sass_Value* sv_to_sass_value(SV* sv)
{
// remember me
SV* org = sv;
// dereference if possible
if (SvROK(sv)) sv = SvRV(sv);
// have a scalar value
if (SvTYPE(sv) < SVt_PVAV) {
// if scalar is undef we return a null type
if (!SvOK(sv)) return sass_make_null();
// perl double
else if (SvNOK(sv)) { // i.e. 4.2
// perl doesn't know numbers with units
return sass_make_number(SvNV(sv), "");
}
// perl integer
else if (SvIOK(sv)) { // i.e. 42
// perl doesn't know numbers with units
return sass_make_number(SvIV(sv), "");
}
// perl string
else if (SvPOK(sv)) { // i.e. "foobar"
char* str = SvPV_nolen(sv);
// coerce all other scalars into a string
// IMO there should only be strings left!?
if (sv_derived_from(org, "CSS::Sass::Value::String::Quoted"))
{ return sass_make_qstring(str); }
else if (sv_derived_from(org, "CSS::Sass::Value::String::Constant"))
{ return sass_make_string(str); }
// perl-libsass autoquote behavior
if (sass_string_need_quotes(str))
{ return sass_make_qstring(str); }
else { return sass_make_string(str); }
}
// perl reference
else if (SvROK(sv)) {
// dereference
sv = SvRV(sv);
// check out scalar value
if (SvTYPE(sv) < SVt_PVAV) {
// if scalar is undef we return a null type
if (!SvOK(sv)) return sass_make_null();
// perl reference
if (SvROK(sv)) {
// dereference
sv = SvRV(sv);
// check if it's an error struct
if (SvTYPE(sv) == SVt_PVAV) {
bool has_msg = false;
if (av_len((AV*)sv) >= 0) {
SV** value_svp = av_fetch((AV*)sv, 0, false);
has_msg = value_svp && *value_svp && SvOK(*value_svp);
return sass_make_error(has_msg ? SvPV_nolen(*value_svp) : "error");
} else {
return sass_make_error("error");
}
}
// if we have a scalar
} else if (!SvROK(sv)) {
// then it is a boolean type
return sass_make_boolean(SvTRUE(sv));
}
}
// an array means we have a number
else if (SvTYPE(sv) == SVt_PVAV) {
AV* number = (AV*) sv;
int len = av_len(number);
if (len >= 0) {
SV* num = *av_fetch(number, 0, false);
if (SvIOK(num) || SvNOK(num)) {
double val = SvNV(num);
if (len > 0) {
SV** unit_svp = av_fetch(number, 1, false);
SV* unit_sv = unit_svp ? *unit_svp : newSVpv("", 0);
return sass_make_number(val, SvPV_nolen(unit_sv));
}
return sass_make_number(val, "");
}
}
}
// a hash means we have a color
else if (SvTYPE(sv) == SVt_PVHV) {
HV* color = (HV*) sv;
SV* sv_r = *hv_fetchs(color, "r", false);
SV* sv_g = *hv_fetchs(color, "g", false);
SV* sv_b = *hv_fetchs(color, "b", false);
SV* sv_a = *hv_fetchs(color, "a", false);
return sass_make_color(
SvOK(sv_r) ? SvNV(sv_r) : 0,
SvOK(sv_g) ? SvNV(sv_g) : 0,
SvOK(sv_b) ? SvNV(sv_b) : 0,
SvOK(sv_a) ? SvNV(sv_a) : 0
);
}
}
// EO SvROK
}
// perl array reference
else if (SvTYPE(sv) == SVt_PVAV) {
AV* av = (AV*) sv;
enum Sass_Separator sep = SASS_COMMA;
// special check for space separated lists
if (sv_derived_from(org, "CSS::Sass::Value::List::Space")) sep = SASS_SPACE;
union Sass_Value* list = sass_make_list(av_len(av) + 1, sep, false);
size_t i;
for (i = 0; i < sass_list_get_length(list); i++) {
SV** value_svp = av_fetch(av, i, false);
SV* value_sv = value_svp ? *value_svp : &PL_sv_undef;
sass_list_set_value(list, i, sv_to_sass_value(value_sv));
}
return list;
}
// perl hash reference
else if (SvTYPE(sv) == SVt_PVHV) {
HV* hv = (HV*) sv;
union Sass_Value* map = sass_make_map(HvUSEDKEYS(hv));
HE* key;
int i = 0;
hv_iterinit(hv);
while (NULL != (key = hv_iternext(hv))) {
void* key_ptr = HeKEY(key);
// using the HePV makros gave me strange gcc warnings here:
// dereferencing type-punned pointer will break strict-aliasing rules
union Sass_Value* key_val = (HeKLEN(key) < 0)
? sv_to_sass_value((SV*) key_ptr)
: sass_make_string((char*) key_ptr);
sass_map_set_key(map, i, key_val);
sass_map_set_value(map, i, sv_to_sass_value(HeVAL(key)));
i++;
}
return map;
}
// if scalar is undef we return a null type
if (!SvOK(sv)) return sass_make_null();
// stringify anything else
// can be usefull for soft-refs
return sass_make_string(SvPV_nolen(sv));
}
SV* new_sv_sass_null () {
SV* sv = newRV_noinc(newRV_noinc(newSV(0)));
sv_bless(sv, gv_stashpv("CSS::Sass::Value::Null", GV_ADD));
return sv;
}
SV* new_sv_sass_string (SV* string, bool quoted) {
SV* sv = newRV_noinc(string);
if (quoted) sv_bless(sv, gv_stashpv("CSS::Sass::Value::String::Quoted", GV_ADD));
else { sv_bless(sv, gv_stashpv("CSS::Sass::Value::String::Constant", GV_ADD)); }
return sv;
}
SV* new_sv_sass_boolean (SV* boolean) {
SV* sv = newRV_noinc(newRV_noinc(boolean));
sv_bless(sv, gv_stashpv("CSS::Sass::Value::Boolean", GV_ADD));
return sv;
}
SV* new_sv_sass_number (SV* number, SV* unit) {
AV* array = newAV();
av_push(array, number);
av_push(array, unit);
SV* sv = newRV_noinc(newRV_noinc((SV*) array));
sv_bless(sv, gv_stashpv("CSS::Sass::Value::Number", GV_ADD));
return sv;
}
SV* new_sv_sass_color (SV* r, SV* g, SV* b, SV* a) {
HV* hash = newHV();
(void)hv_store(hash, "r", 1, r, 0);
(void)hv_store(hash, "g", 1, g, 0);
(void)hv_store(hash, "b", 1, b, 0);
(void)hv_store(hash, "a", 1, a, 0);
SV* sv = newRV_noinc(newRV_noinc((SV*) hash));
sv_bless(sv, gv_stashpv("CSS::Sass::Value::Color", GV_ADD));
return sv;
}
SV* new_sv_sass_error (SV* msg) {
AV* error = newAV();
av_push(error, msg);
SV* sv = newRV_noinc(newRV_noinc(newRV_noinc((SV*) error)));
sv_bless(sv, gv_stashpv("CSS::Sass::Value::Error", GV_ADD));
return sv;
}
// convert from libsass to perl
SV* sass_value_to_sv(union Sass_Value* val)
{
SV* sv;
switch(sass_value_get_tag(val)) {
case SASS_NULL: {
sv = new_sv_sass_null();
} break;
case SASS_BOOLEAN: {
sv = new_sv_sass_boolean(
newSViv(sass_boolean_get_value(val))
);
} break;
case SASS_NUMBER: {
sv = new_sv_sass_number(
newSVnv(sass_number_get_value(val)),
newSVpv(sass_number_get_unit(val), 0)
);
} break;
case SASS_COLOR: {
sv = new_sv_sass_color(
newSVnv(sass_color_get_r(val)),
newSVnv(sass_color_get_g(val)),
newSVnv(sass_color_get_b(val)),
newSVnv(sass_color_get_a(val))
);
} break;
case SASS_STRING: {
sv = new_sv_sass_string(
newSVpv(sass_string_get_value(val), 0),
false
);
} break;
case SASS_LIST: {
size_t i;
AV* list = newAV();
sv = newRV_noinc((SV*) list);
if (sass_list_get_separator(val) == SASS_SPACE) {
sv_bless(sv, gv_stashpv("CSS::Sass::Value::List::Space", GV_ADD));
} else {
sv_bless(sv, gv_stashpv("CSS::Sass::Value::List::Comma", GV_ADD));
}
for (i=0; i<sass_list_get_length(val); i++)
av_push(list, sass_value_to_sv(sass_list_get_value(val, i)));
} break;
case SASS_MAP: {
size_t i;
HV* map = newHV();
sv = newRV_noinc((SV*) map);
sv_bless(sv, gv_stashpv("CSS::Sass::Value::Map", GV_ADD));
for (i=0; i<sass_map_get_length(val); i++) {
// this should return a scalar sv
union Sass_Value* key = sass_map_get_key(val, i);
SV* sv_key = sass_value_to_sv(key);
// call us recursive if needed to get sass values
union Sass_Value* value = sass_map_get_value(val, i);
SV* sv_value = sass_value_to_sv(value);
// store the key/value pair on the hash
(void)hv_store_ent(map, sv_key, sv_value, 0);
// make key sv mortal
sv_2mortal(sv_key);
}
} break;
case SASS_ERROR: {
sv = new_sv_sass_error(
newSVpv(sass_error_get_message(val), 0)
);
} break;
default:
sv = new_sv_sass_error(
newSVpvf("BUG: Sass_Value type is unknown (%d)", sass_value_get_tag(val))
);
break;
}
return sv;
}
Sass_Import_List sass_importer(const char* cur_path, Sass_Importer_Entry cb, struct Sass_Compiler* comp)
{
dSP;
// value from perl function
SV* perl_value = NULL;
// value to return to libsass
// union Sass_Value* sass_value = NULL;
ENTER;
SAVETMPS;
void* cookie = sass_importer_get_cookie(cb);
struct Sass_Import* previous = sass_compiler_get_last_import(comp);
const char* prev_abs_path = sass_import_get_abs_path(previous);
const char* prev_imp_path = sass_import_get_imp_path(previous);
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSVpv(cur_path, 0)));
XPUSHs(sv_2mortal(newSVpv(prev_abs_path, 0)));
XPUSHs(sv_2mortal(newSVpv(prev_imp_path, 0)));
PUTBACK;
// call the static function by soft name reference
// force array context since we want to check for errors
// in scalar context it would take the last value from list
// also enable eval context to catch any major problems
int count = call_sv(cookie, G_EVAL | G_ARRAY);
SPAGAIN;
if (!SvTRUE(ERRSV)) {
if (count == 0)
perl_value = &PL_sv_undef;
else if (count == 1)
perl_value = POPs;
}
// dereference if possible
if (perl_value && SvROK(perl_value)) {
perl_value = SvRV(perl_value);
}
size_t len = 0;
struct Sass_Import** incs = 0;
if (SvTRUE(ERRSV)) {
char* message = SvPV_nolen(ERRSV);
incs = sass_make_import_list(1);
incs[0] = sass_make_import_entry(0, 0, 0);
sass_import_set_error(incs[0], message, -1, -1);
}
// do nothing if we got undef retuned
else if (SvTYPE(perl_value) == SVt_NULL) { }
// we may have gotten a single path
else if (SvTYPE(perl_value) < SVt_PVAV) {
// try to load the filename
incs = sass_make_import_list(1);
char* path = SvPV_nolen(perl_value);
incs[0] = sass_make_import_entry(path, 0, 0);
}
// the expected type is an array
else if (SvTYPE(perl_value) == SVt_PVAV) {
int i;
AV* sass_imports_av = (AV*) perl_value;
size_t length = av_len(sass_imports_av);
incs = sass_make_import_list(length + 1);
// process all import statements returned by perl
for (i = 0; i <= av_len(sass_imports_av); i++) {
char* path = 0;
char* source = 0;
char* mapjson = 0;
char* error_msg = 0;
size_t error_line = -1;
size_t error_column = -1;
// get the entry from the array
// can either be another array or a path string
SV** import_svp = av_fetch(sass_imports_av, i, false);
// error fetching entry?
if (!import_svp) continue;
SV* import_sv = *import_svp;
// dereference if possible
if (SvROK(import_sv)) {
import_sv = SvRV(import_sv);
}
// we may have gotten a single path
if (SvTYPE(import_sv) < SVt_PVAV) {
path = SvPV_nolen(import_sv);
}
// the expected type is an array
else if (SvTYPE(import_sv) == SVt_PVAV) {
AV* import_av = (AV*) import_sv;
int len = av_len(import_av);
SV** path_sv = len < 0 ? 0 : av_fetch(import_av, 0, false);
SV** source_sv = len < 1 ? 0 : av_fetch(import_av, 1, false);
SV** mapjson_sv = len < 2 ? 0 : av_fetch(import_av, 2, false);
SV** error_msg_sv = len < 3 ? 0 : av_fetch(import_av, 3, false);
SV** error_line_sv = len < 4 ? 0 : av_fetch(import_av, 4, false);
SV** error_column_sv = len < 5 ? 0 : av_fetch(import_av, 5, false);
if (path_sv && SvOK(*path_sv)) path = SvPV_nolen(*path_sv);
if (source_sv && SvOK(*source_sv)) source = SvPV_nolen(*source_sv);
if (mapjson_sv && SvOK(*mapjson_sv)) mapjson = SvPV_nolen(*mapjson_sv);
if (error_msg_sv && SvOK(*error_msg_sv)) error_msg = SvPV_nolen(*error_msg_sv);
if (error_line_sv && SvOK(*error_line_sv)) error_line = SvNV(*error_line_sv);
if (error_column_sv && SvOK(*error_column_sv)) error_column = SvNV(*error_column_sv);
}
// error
else {
// output a warning to inform the implementer of his mischief
// vwarn seems to have a bug (expects char** but needs char***)
vwarn("Importer returned invalid data type", 0);
}
// check valid import statement
if (!path && !source) continue;
// push new import on to the importer list
// need to make copy of blobs handled by perl
char* cp_source = source ? strdup(source) : 0;
char* cp_mapjson = mapjson ? strdup(mapjson) : 0;
incs[len] = sass_make_import_entry(path, cp_source, cp_mapjson);
if (error_msg && strlen(error_msg) > 0) {
sass_import_set_error(incs[len], error_msg, error_line, error_column);
}
++len;
}
// EO each SV in AV
}
// error
else {
// output a warning to inform the implementer of his mischief
// vwarn seems to have a bug (expects char** but needs char***)
vwarn("Importer returned invalid data type", 0);
}
PUTBACK;
FREETMPS;
LEAVE;
return incs;
}
// we are called by libsass to dispatch to registered functions
union Sass_Value* call_sass_function(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp)
{
dSP;
// value from perl function
SV* perl_value = NULL;
// value to return to libsass
union Sass_Value* sass_value = NULL;
size_t i;
ENTER;
SAVETMPS;
void* cookie = sass_function_get_cookie(cb);
PUSHMARK(SP);
for (i=0; i<sass_list_get_length(s_args); i++) {
// get the Sass_Value from libsass
union Sass_Value* arg = sass_list_get_value(s_args, i);
// convert and add argument for perl
XPUSHs(sv_2mortal(sass_value_to_sv(arg)));
}
PUTBACK;
// free input values
// free_sass_value(s_args);
// call the static function by soft name reference
// force array context since we want to check for errors
// in scalar context it would take the last value from list
// also enable eval context to catch any major problems
int count = call_sv(cookie, G_EVAL | G_ARRAY);
SPAGAIN;
if (!SvTRUE(ERRSV)) {
if (count == 0)
perl_value = &PL_sv_undef;
else if (count == 1)
perl_value = POPs;
}
if (SvTRUE(ERRSV)) {
// perl function died or had some other major problem
sass_value = sass_make_error_f("%s:%d %s: Perl sub died with message: %s!\n", __FILE__, __LINE__, __func__, SvPV_nolen(ERRSV));
} else if (count > 1) {
// perl function returned a list of values (undefined behaviour)
sass_value = sass_make_error_f("%s:%d %s: Perl sub must not return a list of values!\n", __FILE__, __LINE__, __func__);
} else {
// convert returned sv to Sass_Value
sass_value = sv_to_sass_value(perl_value);
}
PUTBACK;
FREETMPS;
LEAVE;
// union Sass_Value
return sass_value;
}
SV* init_sass_options(struct Sass_Options* sass_options, HV* perl_options)
{
SV** input_path_sv = hv_fetchs(perl_options, "input_path", false);
SV** output_path_sv = hv_fetchs(perl_options, "output_path", false);
SV** output_style_sv = hv_fetchs(perl_options, "output_style", false);
SV** source_comments_sv = hv_fetchs(perl_options, "source_comments", false);
SV** omit_source_map_sv = hv_fetchs(perl_options, "omit_source_map", false);
SV** omit_source_map_url_sv = hv_fetchs(perl_options, "omit_source_map_url", false);
SV** source_map_file_urls_sv = hv_fetchs(perl_options, "source_map_file_urls", false);
SV** source_map_contents_sv = hv_fetchs(perl_options, "source_map_contents", false);
SV** source_map_embed_sv = hv_fetchs(perl_options, "source_map_embed", false);
SV** include_paths_sv = hv_fetchs(perl_options, "include_paths", false);
SV** plugin_paths_sv = hv_fetchs(perl_options, "plugin_paths", false);
SV** precision_sv = hv_fetchs(perl_options, "precision", false);
SV** linefeed_sv = hv_fetchs(perl_options, "linefeed", false);
SV** indent_sv = hv_fetchs(perl_options, "indent", false);
SV** source_map_root_sv = hv_fetchs(perl_options, "source_map_root", false);
SV** source_map_file_sv = hv_fetchs(perl_options, "source_map_file", false);
SV** sass_headers_sv = hv_fetchs(perl_options, "headers", false);
SV** sass_importers_sv = hv_fetchs(perl_options, "importers", false);
SV** sass_functions_sv = hv_fetchs(perl_options, "functions", false);
if (input_path_sv) sass_option_set_input_path (sass_options, safe_svpv(*input_path_sv, ""));
if (output_path_sv) sass_option_set_output_path (sass_options, safe_svpv(*output_path_sv, ""));
if (output_style_sv) sass_option_set_output_style (sass_options, SvUV(*output_style_sv));
if (source_comments_sv) sass_option_set_source_comments (sass_options, SvTRUE(*source_comments_sv));
if (omit_source_map_sv) sass_option_set_omit_source_map_url (sass_options, SvTRUE(*omit_source_map_sv));
if (omit_source_map_url_sv) sass_option_set_omit_source_map_url (sass_options, SvTRUE(*omit_source_map_url_sv));
if (source_map_file_urls_sv) sass_option_set_source_map_file_urls (sass_options, SvTRUE(*source_map_file_urls_sv));
if (source_map_contents_sv) sass_option_set_source_map_contents (sass_options, SvTRUE(*source_map_contents_sv));
if (source_map_embed_sv) sass_option_set_source_map_embed (sass_options, SvTRUE(*source_map_embed_sv));
if (include_paths_sv) sass_option_set_include_path (sass_options, safe_svpv(*include_paths_sv, ""));
if (plugin_paths_sv) sass_option_set_plugin_path (sass_options, safe_svpv(*plugin_paths_sv, ""));
if (source_map_root_sv) sass_option_set_source_map_root (sass_options, safe_svpv(*source_map_root_sv, ""));
if (source_map_file_sv) sass_option_set_source_map_file (sass_options, safe_svpv(*source_map_file_sv, ""));
// do not set anything if the option is set to undef
if (isSafeSv(indent_sv)) sass_option_set_indent (sass_options, SvPV_nolen(*indent_sv));
if (isSafeSv(linefeed_sv)) sass_option_set_linefeed (sass_options, SvPV_nolen(*linefeed_sv));
if (isSafeSv(precision_sv)) sass_option_set_precision (sass_options, SvUV(*precision_sv));
if (sass_importers_sv) {
int i;
AV* sass_importers_av;
if (!SvROK(*sass_importers_sv) || SvTYPE(SvRV(*sass_importers_sv)) != SVt_PVAV) {
return newSVpvf("sass_importers should be an arrayref (SvTYPE=%u)", (unsigned)SvTYPE(SvRV(*sass_importers_sv)));
}
sass_importers_av = (AV*)SvRV(*sass_importers_sv);
Sass_Importer_List c_importers = sass_make_importer_list(av_len(sass_importers_av) + 1);
if (!c_importers) {
return newSVpv("couldn't alloc memory for c_importers", 0);
}
for (i=0; i<=av_len(sass_importers_av); i++) {
SV** entry_sv = av_fetch(sass_importers_av, i, false);
AV* entry_av;
if (!SvROK(*entry_sv) || SvTYPE(SvRV(*entry_sv)) != SVt_PVAV) {
return newSVpvf("each sass_importer entry should be an arrayref (SvTYPE=%u)", (unsigned)SvTYPE(SvRV(*entry_sv)));
}
entry_av = (AV*)SvRV(*entry_sv);
SV** importer_sv = av_fetch(entry_av, 0, false);
SV** priority_sv = av_fetch(entry_av, 1, false);
double priority = priority_sv ? SvNV(*priority_sv) : 0;
if (!importer_sv) return newSVpv("custom importer without callback", 0);
c_importers[i] = sass_make_importer(sass_importer, priority, *importer_sv);
}
sass_option_set_c_importers(sass_options, c_importers);
}
if (sass_headers_sv) {
int i;
AV* sass_headers_av;
if (!SvROK(*sass_headers_sv) || SvTYPE(SvRV(*sass_headers_sv)) != SVt_PVAV) {
return newSVpvf("sass_headers should be an arrayref (SvTYPE=%u)", (unsigned)SvTYPE(SvRV(*sass_headers_sv)));
}
sass_headers_av = (AV*)SvRV(*sass_headers_sv);
Sass_Importer_List c_headers = sass_make_importer_list(av_len(sass_headers_av) + 1);
if (!c_headers) {
return newSVpv("couldn't alloc memory for c_headers", 0);
}
for (i=0; i<=av_len(sass_headers_av); i++) {
SV** entry_sv = av_fetch(sass_headers_av, i, false);
AV* entry_av;
if (!SvROK(*entry_sv) || SvTYPE(SvRV(*entry_sv)) != SVt_PVAV) {
return newSVpvf("each sass_header entry should be an arrayref (SvTYPE=%u)", (unsigned)SvTYPE(SvRV(*entry_sv)));
}
entry_av = (AV*)SvRV(*entry_sv);
SV** header_sv = av_fetch(entry_av, 0, false);
SV** priority_sv = av_fetch(entry_av, 1, false);
double priority = priority_sv ? SvNV(*priority_sv) : 0;
if (!header_sv) return newSVpv("custom header without callback", 0);
c_headers[i] = sass_make_importer(sass_importer, priority, *header_sv);
}
sass_option_set_c_headers(sass_options, c_headers);
}
if (sass_functions_sv) {
int i;
AV* sass_functions_av;
if (!SvROK(*sass_functions_sv) || SvTYPE(SvRV(*sass_functions_sv)) != SVt_PVAV) {
return newSVpvf("sass_functions should be an arrayref (SvTYPE=%u)", (unsigned)SvTYPE(SvRV(*sass_functions_sv)));
}
sass_functions_av = (AV*)SvRV(*sass_functions_sv);
Sass_Function_List c_functions = sass_make_function_list(av_len(sass_functions_av) + 1);
if (!c_functions) {
return newSVpv("couldn't alloc memory for c_functions", 0);
}
for (i=0; i<=av_len(sass_functions_av); i++) {
SV** entry_sv = av_fetch(sass_functions_av, i, false);
AV* entry_av;
if (!SvROK(*entry_sv) || SvTYPE(SvRV(*entry_sv)) != SVt_PVAV) {
return newSVpvf("each sass_function entry should be an arrayref (SvTYPE=%u)", (unsigned)SvTYPE(SvRV(*entry_sv)));
}
entry_av = (AV*)SvRV(*entry_sv);
SV** sig_sv = av_fetch(entry_av, 0, false);
SV** sub_sv = av_fetch(entry_av, 1, false);
if (!sig_sv) return newSVpv("custom function without prototype", 0);
if (!sub_sv) return newSVpv("custom function without callback", 0);
c_functions[i] = sass_make_function(safe_svpv(*sig_sv, ""), call_sass_function, *sub_sv);
}
sass_option_set_c_functions(sass_options, c_functions);
}
return &PL_sv_undef;
}
void finalize_sass_context(struct Sass_Context* ctx, HV* RETVAL, SV* err)
{
const int error_status = sass_context_get_error_status(ctx);
const char* error_json = sass_context_get_error_json(ctx);
const char* error_file = sass_context_get_error_file(ctx);
size_t error_line = sass_context_get_error_line(ctx);
size_t error_column = sass_context_get_error_column(ctx);
const char* error_text = sass_context_get_error_text(ctx);
const char* error_message = sass_context_get_error_message(ctx);
const char* error_src = 0; // sass_context_get_error_src(ctx);
const char* output_string = sass_context_get_output_string(ctx);
const char* source_map_string = sass_context_get_source_map_string(ctx);
char** included_files = sass_context_get_included_files(ctx);
AV* sv_included_files = newAV();
char** it = included_files;
while (it && (*it) != 0) {
av_push(sv_included_files, newSVpv(*it, 0));
++it;
}
SV* sv_error_status = newSViv(error_status || SvOK(err));
SV* sv_output_string = output_string ? newSVpv(output_string, 0) : newSV(0);
SV* sv_source_map_string = source_map_string ? newSVpv(source_map_string, 0) : newSV(0);
SV* sv_error_line = SvOK(err) ? err : error_line ? newSViv(error_line) : newSViv(0);
SV* sv_error_column = SvOK(err) ? err : error_column ? newSViv(error_column) : newSViv(0);
SV* sv_error_src = SvOK(err) ? err : error_src ? newSVpv(error_src, 0) : newSViv(0);
SV* sv_error_text = SvOK(err) ? err : error_text ? newSVpv(error_text, 0) : newSV(0);
SV* sv_error_json = SvOK(err) ? err : error_json ? newSVpv(error_json, 0) : newSV(0);
SV* sv_error_file = SvOK(err) ? err : error_file ? newSVpv(error_file, 0) : newSV(0);
SV* sv_error_message = SvOK(err) ? err : error_message ? newSVpv(error_message, 0) : newSV(0);
SvUTF8_on(sv_output_string);
SvUTF8_on(sv_source_map_string);
SvUTF8_on(sv_error_src);
SvUTF8_on(sv_error_text);
SvUTF8_on(sv_error_json);
SvUTF8_on(sv_error_file);
SvUTF8_on(sv_error_message);
(void)hv_stores(RETVAL, "error_status", sv_error_status);
(void)hv_stores(RETVAL, "output_string", sv_output_string);
(void)hv_stores(RETVAL, "source_map_string", sv_source_map_string);
(void)hv_stores(RETVAL, "error_line", sv_error_line);
(void)hv_stores(RETVAL, "error_column", sv_error_column);
(void)hv_stores(RETVAL, "error_message", sv_error_message);
(void)hv_stores(RETVAL, "error_src", sv_error_src);
(void)hv_stores(RETVAL, "error_text", sv_error_text);
(void)hv_stores(RETVAL, "error_json", sv_error_json);
(void)hv_stores(RETVAL, "error_file", sv_error_file);
(void)hv_stores(RETVAL, "included_files", newRV_noinc((SV*) sv_included_files));
}
MODULE = CSS::Sass PACKAGE = CSS::Sass
BOOT:
{
HV* stash = gv_stashpv("CSS::Sass", 0);
Constant(SASS_STYLE_NESTED);
Constant(SASS_STYLE_EXPANDED);
Constant(SASS_STYLE_COMPACT);
Constant(SASS_STYLE_COMPRESSED);
Constant(SASS_BOOLEAN);
Constant(SASS_NUMBER);
Constant(SASS_COLOR);
Constant(SASS_STRING);
Constant(SASS_LIST);
Constant(SASS_MAP);
Constant(SASS_NULL);
Constant(SASS_ERROR);
// sass list types
Constant(SASS_COMMA);
Constant(SASS_SPACE);
// sass2scss constants
Constant(SASS2SCSS_PRETTIFY_0);
Constant(SASS2SCSS_PRETTIFY_1);
Constant(SASS2SCSS_PRETTIFY_2);
Constant(SASS2SCSS_PRETTIFY_3);
// more options for sass2scss
Constant(SASS2SCSS_KEEP_COMMENT);
Constant(SASS2SCSS_STRIP_COMMENT);
Constant(SASS2SCSS_CONVERT_COMMENT);
// enum Sass_OP
Constant(AND);
Constant(OR);
Constant(EQ);
Constant(NEQ);
Constant(GT);
Constant(GTE);
Constant(LT);
Constant(LTE);
Constant(ADD);
Constant(SUB);
Constant(MUL);
Constant(DIV);
Constant(MOD);
}
HV*
compile_sass(input_string, options)
char* input_string
HV* options
CODE:
RETVAL = newHV();
sv_2mortal((SV*)RETVAL);
{
char* src = strdup(input_string);
// input_string will be freed by libsass (first "loaded" source)
struct Sass_Data_Context* data_ctx = sass_make_data_context(src);
struct Sass_Context* ctx = sass_data_context_get_context(data_ctx);
struct Sass_Options* ctx_opt = sass_context_get_options(ctx);
SV* err = init_sass_options(ctx_opt, options);
if (!SvTRUE(err)) {
struct Sass_Compiler* compiler = sass_make_data_compiler(data_ctx);
sass_compiler_parse(compiler);
sass_compiler_execute(compiler);
sass_delete_compiler(compiler);
}
// if (!SvTRUE(err)) sass_compile_data_context(data_ctx);
finalize_sass_context(ctx, RETVAL, err);
sass_delete_data_context(data_ctx);
}
OUTPUT:
RETVAL
HV*
compile_sass_file(input_path, options)
char* input_path
HV* options
CODE:
RETVAL = newHV();
sv_2mortal((SV*)RETVAL);
{
struct Sass_File_Context* file_ctx = sass_make_file_context(input_path);
struct Sass_Context* ctx = sass_file_context_get_context(file_ctx);
struct Sass_Options* ctx_opt = sass_context_get_options(ctx);
SV* err = init_sass_options(ctx_opt, options);
if (!SvTRUE(err)) {
struct Sass_Compiler* compiler = sass_make_file_compiler(file_ctx);
sass_compiler_parse(compiler);
sass_compiler_execute(compiler);
sass_delete_compiler(compiler);
}
finalize_sass_context(ctx, RETVAL, err);
sass_delete_file_context(file_ctx);
}
OUTPUT:
RETVAL
SV*
sass2scss(sass, options = SASS2SCSS_PRETTIFY_1)
const char* sass
int options
CODE:
{
char* css = sass2scss(sass, options);
RETVAL = newSVpv(css, 0);
sass_free_memory (css);
}
OUTPUT:
RETVAL
SV*
quote(str)
char* str
CODE:
{
char* quoted = sass_string_quote(str, '*');
RETVAL = newSVpv(quoted, 0);
sass_free_memory (quoted);
}
OUTPUT:
RETVAL
SV*
unquote(str)
char* str
CODE:
{
char* unquoted = sass_string_unquote(str);
RETVAL = newSVpv(unquoted, 0);
sass_free_memory (unquoted);
}
OUTPUT:
RETVAL
SV*
sass_operation(op, a, b)
SV* op
SV* a
SV* b
CODE:
{
union Sass_Value* lhs = sv_to_sass_value(a);
union Sass_Value* rhs = sv_to_sass_value(b);
union Sass_Value* rv = 0;
switch ((enum Sass_OP) SvNV(op)) {
case ADD: rv = sass_value_op(ADD, lhs, rhs); break;
case MUL: rv = sass_value_op(MUL, lhs, rhs); break;
case AND: rv = sass_value_op(AND, lhs, rhs); break;
case OR: rv = sass_value_op(OR, lhs, rhs); break;
case EQ: rv = sass_value_op(EQ, lhs, rhs); break;
case NEQ: rv = sass_value_op(NEQ, lhs, rhs); break;
case GT: rv = sass_value_op(GT, lhs, rhs); break;
case GTE: rv = sass_value_op(GTE, lhs, rhs); break;
case LT: rv = sass_value_op(LT, lhs, rhs); break;
case LTE: rv = sass_value_op(LTE, lhs, rhs); break;
case SUB: rv = sass_value_op(SUB, lhs, rhs); break;
case DIV: rv = sass_value_op(DIV, lhs, rhs); break;
case MOD: rv = sass_value_op(MOD, lhs, rhs); break;
default: rv = sass_make_error("invalid op"); break;
}
if (rv) RETVAL = sass_value_to_sv(rv);
else RETVAL = new_sv_sass_null();
sass_delete_value(rhs);
sass_delete_value(lhs);
sass_delete_value(rv);
}
OUTPUT:
RETVAL
SV*
sass_stringify(v)
SV* v
CODE:
{
union Sass_Value* val = sv_to_sass_value(v);
// ToDo: make compressed and precision option configurable
union Sass_Value* rv = sass_value_stringify(val, false, 5);
RETVAL = sass_value_to_sv(rv);
sass_delete_value(val);
sass_delete_value(rv);
}
OUTPUT:
RETVAL
SV*
auto_quote(str)
char* str
CODE:
{
if (sass_string_need_quotes(str)) {
char* string = sass_string_quote(str, '*');
RETVAL = newSVpv(string, 0);
sass_free_memory (string);
} else {
RETVAL = newSVpv(str, 0);
}
}
OUTPUT:
RETVAL
SV*
need_quotes(str)
char* str
CODE:
{
RETVAL = sass_string_need_quotes(str) ? &PL_sv_yes : &PL_sv_no;
}
OUTPUT:
RETVAL
SV*
import_sv(sv)
SV* sv
CODE:
{
union Sass_Value* value = sv_to_sass_value(sv);
RETVAL = sass_value_to_sv(value);
sass_delete_value(value);
}
OUTPUT:
RETVAL
SV*
libsass_version()
CODE:
{
RETVAL = newSVpv(libsass_version(), 0);
}
OUTPUT:
RETVAL
SV*
sass2scss_version()
CODE:
{
RETVAL = newSVpv(sass2scss_version(), 0);
}
OUTPUT:
RETVAL