Commit 07a9540a by Matthew Ryo

Support for custom functions

parent 8c641f4c
...@@ -17,9 +17,9 @@ matrix: ...@@ -17,9 +17,9 @@ matrix:
before_install: before_install:
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update - sudo apt-get update
- sudo apt-get install gcc-4.8 g++-4.8 - sudo apt-get install gcc-4.7 g++-4.7
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 20 - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.7 20
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 20 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.7 20
- g++ --version - g++ --version
- sudo apt-get update -qq - sudo apt-get update -qq
- git submodule update --init --recursive - git submodule update --init --recursive
......
...@@ -89,6 +89,68 @@ When returning or calling `done()` with `{ contents: "String" }`, the string val ...@@ -89,6 +89,68 @@ When returning or calling `done()` with `{ contents: "String" }`, the string val
`this` refers to a contextual scope for the immediate run of `sass.render` or `sass.renderSync` `this` refers to a contextual scope for the immediate run of `sass.render` or `sass.renderSync`
### functions
`functions` is an `Object` that holds a collection of custom functions that may be invoked by the sass files being compiled. They may take zero or more input parameters and must return a value either synchronously (`return ...;`) or asynchronously (`done();`). Those parameters will be instances of one of the constructors contained in the `require('node-sass').types` hash. The return value must be of one of these types as well. See the list of available types below:
#### types.Number(value [, unit = ""])
* `getValue()`/ `setValue(value)` : gets / sets the numerical portion of the number
* `getUnit()` / `setUnit(unit)` : gets / sets the unit portion of the number
#### types.String(value)
* `getValue()` / `setValue(value)` : gets / sets the enclosed string
#### types.Color(r, g, b [, a = 1.0]) or types.Color(argb)
* `getR()` / `setR(value)` : red component (integer from `0` to `255`)
* `getG()` / `setG(value)` : green component (integer from `0` to `255`)
* `getB()` / `setB(value)` : blue component (integer from `0` to `255`)
* `getA()` / `setA(value)` : alpha component (number from `0` to `1.0`)
Example:
```javascript
var Color = require('node-sass').types.Color,
c1 = new Color(255, 0, 0),
c2 = new Color(0xff0088cc);
```
#### types.Boolean(value)
* `getValue()` : gets the enclosed boolean
* `types.Boolean.TRUE` : Singleton instance of `types.Boolean` that holds "true"
* `types.Boolean.FALSE` : Singleton instance of `types.Boolean` that holds "false"
#### types.List(length [, commaSeparator = true])
* `getValue(index)` / `setValue(index, value)` : `value` must itself be an instance of one of the constructors in `sass.types`.
* `getSeparator()` / `setSeparator(isComma)` : whether to use commas as a separator
* `getLength()`
#### types.Map(length)
* `getKey(index)` / `setKey(index, value)`
* `getValue(index)` / `setValue(index, value)`
* `getLength()`
#### types.Null()
* `types.Null.NULL` : Singleton instance of `types.Null`.
#### Example
```javascript
sass.renderSync({
data: '#{headings(2,5)} { color: #08c; }',
functions: {
'headings($from: 0, $to: 6)': function(from, to) {
var i, f = from.getValue(), t = to.getValue(),
list = new sass.types.List(t - f + 1);
for (i = f; i <= t; i++) {
list.setValue(i - f, new sass.types.String('h' + i));
}
return list;
}
}
});
```
### includePaths ### includePaths
Type: `Array<String>` Type: `Array<String>`
Default: `[]` Default: `[]`
......
...@@ -4,7 +4,19 @@ ...@@ -4,7 +4,19 @@
'target_name': 'binding', 'target_name': 'binding',
'sources': [ 'sources': [
'src/binding.cpp', 'src/binding.cpp',
'src/sass_context_wrapper.cpp' 'src/create_string.cpp',
'src/custom_function_bridge.cpp',
'src/custom_importer_bridge.cpp',
'src/sass_context_wrapper.cpp',
'src/sass_types/boolean.cpp',
'src/sass_types/color.cpp',
'src/sass_types/error.cpp',
'src/sass_types/factory.cpp',
'src/sass_types/list.cpp',
'src/sass_types/map.cpp',
'src/sass_types/null.cpp',
'src/sass_types/number.cpp',
'src/sass_types/string.cpp'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")', '<!(node -e "require(\'nan\')")',
...@@ -47,9 +59,7 @@ ...@@ -47,9 +59,7 @@
'-std=c++11', '-std=c++11',
'-stdlib=libc++' '-stdlib=libc++'
], ],
'OTHER_LDFLAGS': [ 'OTHER_LDFLAGS': [],
'-stdlib=libc++'
],
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES',
'MACOSX_DEPLOYMENT_TARGET': '10.7' 'MACOSX_DEPLOYMENT_TARGET': '10.7'
...@@ -73,6 +83,7 @@ ...@@ -73,6 +83,7 @@
}], }],
['OS!="win"', { ['OS!="win"', {
'cflags_cc+': [ 'cflags_cc+': [
'-fexceptions',
'-std=c++0x' '-std=c++0x'
] ]
}] }]
......
...@@ -137,6 +137,67 @@ function getOptions(options, cb) { ...@@ -137,6 +137,67 @@ function getOptions(options, cb) {
} }
/** /**
* Executes a callback and transforms any exception raised into a sass error
*
* @param {Function} callback
* @param {Array} arguments
* @api private
*/
function tryCallback(callback, args) {
try {
return callback.apply(this, args);
} catch (e) {
if (typeof e === 'string') {
return new binding.types.Error(e);
} else if (e instanceof Error) {
return new binding.types.Error(e.message);
} else {
return new binding.types.Error('An unexpected error occurred');
}
}
}
/**
* Normalizes the signature of custom functions to make it possible to just supply the
* function name and have the signature default to `fn(...)`. The callback is adjusted
* to transform the input sass list into discrete arguments.
*
* @param {String} signature
* @param {Function} callback
* @return {Object}
* @api private
*/
function normalizeFunctionSignature(signature, callback) {
if (!/^\*|@warn|@error|@debug|\w+\(.*\)$/.test(signature)) {
if (!/\w+/.test(signature)) {
throw new Error('Invalid function signature format "' + signature + '"');
}
return {
signature: signature + '(...)',
callback: function() {
var args = Array.prototype.slice.call(arguments),
list = args.shift(),
i;
for (i = list.getLength() - 1; i >= 0; i--) {
args.unshift(list.getValue(i));
}
return callback.apply(this, args);
}
};
}
return {
signature: signature,
callback: callback
};
}
/**
* Render * Render
* *
* @param {Object} options * @param {Object} options
...@@ -172,13 +233,9 @@ module.exports.render = function(options, cb) { ...@@ -172,13 +233,9 @@ module.exports.render = function(options, cb) {
var importer = options.importer; var importer = options.importer;
if (importer) { if (importer) {
options.importer = function(file, prev, key) { options.importer = function(file, prev, bridge) {
function done(data) { function done(data) {
console.log(data); // ugly hack bridge.success(data);
binding.importedCallback({
index: key,
objectLiteral: data
});
} }
var result = importer.call(options.context, file, prev, done); var result = importer.call(options.context, file, prev, done);
...@@ -189,6 +246,31 @@ module.exports.render = function(options, cb) { ...@@ -189,6 +246,31 @@ module.exports.render = function(options, cb) {
}; };
} }
var functions = options.functions;
if (functions) {
options.functions = {};
Object.keys(functions).forEach(function(signature) {
var cb = normalizeFunctionSignature(signature, functions[signature]);
options.functions[cb.signature] = function() {
var args = Array.prototype.slice.call(arguments),
bridge = args.pop();
function done(data) {
bridge.success(data);
}
var result = tryCallback(cb.callback, args.concat(done));
if (result) {
done(result);
}
};
});
}
options.data ? binding.render(options) : binding.renderFile(options); options.data ? binding.render(options) : binding.renderFile(options);
}; };
...@@ -206,10 +288,24 @@ module.exports.renderSync = function(options) { ...@@ -206,10 +288,24 @@ module.exports.renderSync = function(options) {
if (importer) { if (importer) {
options.importer = function(file, prev) { options.importer = function(file, prev) {
return { objectLiteral: importer.call(options.context, file, prev) }; return importer.call(options.context, file, prev);
}; };
} }
var functions = options.functions;
if (options.functions) {
options.functions = {};
Object.keys(functions).forEach(function(signature) {
var cb = normalizeFunctionSignature(signature, functions[signature]);
options.functions[cb.signature] = function() {
return tryCallback(cb.callback, arguments);
};
});
}
var status = options.data ? binding.renderSync(options) : binding.renderFileSync(options); var status = options.data ? binding.renderSync(options) : binding.renderFileSync(options);
var result = options.result; var result = options.result;
...@@ -228,3 +324,12 @@ module.exports.renderSync = function(options) { ...@@ -228,3 +324,12 @@ module.exports.renderSync = function(options) {
*/ */
module.exports.info = process.sass.versionInfo; module.exports.info = process.sass.versionInfo;
/**
* Expose sass types
*/
module.exports.types = binding.types;
module.exports.TRUE = binding.types.Boolean.TRUE;
module.exports.FALSE = binding.types.Boolean.FALSE;
module.exports.NULL = binding.types.Null.NULL;
\ No newline at end of file
...@@ -61,9 +61,7 @@ ...@@ -61,9 +61,7 @@
'-std=c++11', '-std=c++11',
'-stdlib=libc++' '-stdlib=libc++'
], ],
'OTHER_LDFLAGS': [ 'OTHER_LDFLAGS': [],
'-stdlib=libc++'
],
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES',
'MACOSX_DEPLOYMENT_TARGET': '10.7' 'MACOSX_DEPLOYMENT_TARGET': '10.7'
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
"coverage": "node scripts/coverage.js", "coverage": "node scripts/coverage.js",
"install": "node scripts/install.js", "install": "node scripts/install.js",
"postinstall": "node scripts/build.js", "postinstall": "node scripts/build.js",
"pretest": "node_modules/.bin/jshint bin lib test", "pretest": "node_modules/.bin/jshint bin lib scripts test",
"test": "node_modules/.bin/mocha test" "test": "node_modules/.bin/mocha test"
}, },
"files": [ "files": [
......
...@@ -53,7 +53,7 @@ function afterBuild(options) { ...@@ -53,7 +53,7 @@ function afterBuild(options) {
*/ */
function build(options) { function build(options) {
var arguments = [ var args = [
path.join('node_modules', 'pangyp', 'bin', 'node-gyp'), path.join('node_modules', 'pangyp', 'bin', 'node-gyp'),
'rebuild', 'rebuild',
].concat( ].concat(
...@@ -63,9 +63,9 @@ function build(options) { ...@@ -63,9 +63,9 @@ function build(options) {
}) })
).concat(options.args); ).concat(options.args);
console.log(['Building:', process.sass.runtime.execPath].concat(arguments).join(' ')); console.log(['Building:', process.sass.runtime.execPath].concat(args).join(' '));
var proc = spawn(process.sass.runtime.execPath, arguments, { var proc = spawn(process.sass.runtime.execPath, args, {
stdio: [0, 1, 2] stdio: [0, 1, 2]
}); });
......
#include <nan.h> #include <nan.h>
#include <vector> #include <vector>
#include "sass_context_wrapper.h" #include "sass_context_wrapper.h"
#include "custom_function_bridge.h"
char* create_string(Local<Value> value) { #include "create_string.h"
if (value->IsNull() || !value->IsString()) { #include "sass_types/factory.h"
return 0;
}
String::Utf8Value string(value);
char *str = (char *)malloc(string.length() + 1);
strcpy(str, *string);
return str;
}
std::vector<sass_context_wrapper*> imports_collection;
void prepare_import_results(Local<Value> returned_value, sass_context_wrapper* ctx_w) {
NanScope();
if (returned_value->IsArray()) {
Handle<Array> array = Handle<Array>::Cast(returned_value);
ctx_w->imports = sass_make_import_list(array->Length());
for (size_t i = 0; i < array->Length(); ++i) {
Local<Value> value = array->Get(static_cast<uint32_t>(i));
if (!value->IsObject())
continue;
Local<Object> object = Local<Object>::Cast(value);
char* path = create_string(object->Get(NanNew<String>("file")));
char* contents = create_string(object->Get(NanNew<String>("contents")));
ctx_w->imports[i] = sass_make_import_entry(path, (!contents || contents[0] == '\0') ? 0 : strdup(contents), 0);
}
}
else if (returned_value->IsObject()) {
ctx_w->imports = sass_make_import_list(1);
Local<Object> object = Local<Object>::Cast(returned_value);
char* path = create_string(object->Get(NanNew<String>("file")));
char* contents = create_string(object->Get(NanNew<String>("contents")));
ctx_w->imports[0] = sass_make_import_entry(path, (!contents || contents[0] == '\0') ? 0 : strdup(contents), 0);
}
else {
ctx_w->imports = sass_make_import_list(1);
ctx_w->imports[0] = sass_make_import_entry(ctx_w->file, 0, 0);
}
}
void dispatched_async_uv_callback(uv_async_t *req) {
NanScope();
sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(req->data);
TryCatch try_catch;
imports_collection.push_back(ctx_w);
Handle<Value> argv[] = {
NanNew<String>(strdup(ctx_w->file ? ctx_w->file : 0)),
NanNew<String>(strdup(ctx_w->prev ? ctx_w->prev : 0)),
NanNew<Number>(imports_collection.size() - 1)
};
NanNew<Value>(ctx_w->importer_callback->Call(3, argv));
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
struct Sass_Import** sass_importer(const char* file, const char* prev, void* cookie) struct Sass_Import** sass_importer(const char* file, const char* prev, void* cookie)
{ {
sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(cookie); sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(cookie);
CustomImporterBridge& bridge = *(ctx_w->importer_bridge);
if (!ctx_w->is_sync) { std::vector<void*> argv;
/* that is async: Render() or RenderFile(), argv.push_back((void*) file);
* the default even loop is unblocked so it argv.push_back((void*) prev);
* can run uv_async_send without a push.
*/
std::unique_lock<std::mutex> lock(*ctx_w->importer_mutex); return bridge(argv);
}
ctx_w->file = file ? strdup(file) : 0; union Sass_Value* sass_custom_function(const union Sass_Value* s_args, void* cookie)
ctx_w->prev = prev ? strdup(prev) : 0; {
ctx_w->async.data = (void*)ctx_w; CustomFunctionBridge& bridge = *(static_cast<CustomFunctionBridge*>(cookie));
uv_async_send(&ctx_w->async); std::vector<void*> argv;
ctx_w->importer_condition_variable->wait(lock); for (unsigned l = sass_list_get_length(s_args), i = 0; i < l; i++) {
argv.push_back((void*) sass_list_get_value(s_args, i));
} }
else {
NanScope();
Handle<Value> argv[] = {
NanNew<String>(file),
NanNew<String>(prev)
};
Local<Object> returned_value = Local<Object>::Cast(NanNew<Value>(ctx_w->importer_callback->Call(2, argv)));
prepare_import_results(returned_value->Get(NanNew("objectLiteral")), ctx_w); try {
return bridge(argv);
} catch (const std::exception& e) {
return sass_make_error(e.what());
} }
return ctx_w->imports;
} }
void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ctx_w, bool is_file, bool is_sync) { void ExtractOptions(Local<Object> options, void* cptr, sass_context_wrapper* ctx_w, bool is_file, bool is_sync) {
NanScope(); NanScope();
struct Sass_Context* ctx; struct Sass_Context* ctx;
...@@ -124,7 +51,6 @@ void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ct ...@@ -124,7 +51,6 @@ void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ct
struct Sass_Options* sass_options = sass_context_get_options(ctx); struct Sass_Options* sass_options = sass_context_get_options(ctx);
ctx_w->importer_callback = NULL;
ctx_w->is_sync = is_sync; ctx_w->is_sync = is_sync;
if (!is_sync) { if (!is_sync) {
...@@ -141,8 +67,7 @@ void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ct ...@@ -141,8 +67,7 @@ void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ct
Local<Function> importer_callback = Local<Function>::Cast(options->Get(NanNew("importer"))); Local<Function> importer_callback = Local<Function>::Cast(options->Get(NanNew("importer")));
if (importer_callback->IsFunction()) { if (importer_callback->IsFunction()) {
ctx_w->importer_callback = new NanCallback(importer_callback); ctx_w->importer_bridge = new CustomImporterBridge(new NanCallback(importer_callback), ctx_w->is_sync);
uv_async_init(uv_default_loop(), &ctx_w->async, (uv_async_cb)dispatched_async_uv_callback);
sass_option_set_importer(sass_options, sass_make_importer(sass_importer, ctx_w)); sass_option_set_importer(sass_options, sass_make_importer(sass_importer, ctx_w));
} }
...@@ -160,9 +85,34 @@ void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ct ...@@ -160,9 +85,34 @@ void extract_options(Local<Object> options, void* cptr, sass_context_wrapper* ct
sass_option_set_source_map_file(sass_options, create_string(options->Get(NanNew("sourceMap")))); sass_option_set_source_map_file(sass_options, create_string(options->Get(NanNew("sourceMap"))));
sass_option_set_include_path(sass_options, create_string(options->Get(NanNew("includePaths")))); sass_option_set_include_path(sass_options, create_string(options->Get(NanNew("includePaths"))));
sass_option_set_precision(sass_options, options->Get(NanNew("precision"))->Int32Value()); sass_option_set_precision(sass_options, options->Get(NanNew("precision"))->Int32Value());
Local<Object> custom_functions = Local<Object>::Cast(options->Get(NanNew("functions")));
if (custom_functions->IsObject()) {
Local<Array> signatures = custom_functions->GetOwnPropertyNames();
unsigned num_signatures = signatures->Length();
Sass_C_Function_List fn_list = sass_make_function_list(num_signatures);
for (unsigned i = 0; i < num_signatures; i++) {
Local<String> signature = Local<String>::Cast(signatures->Get(NanNew(i)));
Local<Function> callback = Local<Function>::Cast(custom_functions->Get(signature));
if (!signature->IsString() || !callback->IsFunction()) {
NanThrowError(NanNew("options.functions must be a (signature -> function) hash"));
}
CustomFunctionBridge* bridge = new CustomFunctionBridge(new NanCallback(callback), ctx_w->is_sync);
ctx_w->function_bridges.push_back(bridge);
Sass_C_Function_Callback fn = sass_make_function(create_string(signature), sass_custom_function, bridge);
sass_function_set_list_entry(fn_list, i, fn);
}
sass_option_set_c_functions(sass_options, fn_list);
}
} }
void get_stats(sass_context_wrapper* ctx_w, Sass_Context* ctx) { void GetStats(sass_context_wrapper* ctx_w, Sass_Context* ctx) {
NanScope(); NanScope();
char** included_files = sass_context_get_included_files(ctx); char** included_files = sass_context_get_included_files(ctx);
...@@ -177,7 +127,7 @@ void get_stats(sass_context_wrapper* ctx_w, Sass_Context* ctx) { ...@@ -177,7 +127,7 @@ void get_stats(sass_context_wrapper* ctx_w, Sass_Context* ctx) {
NanNew(ctx_w->result)->Get(NanNew("stats"))->ToObject()->Set(NanNew("includedFiles"), arr); NanNew(ctx_w->result)->Get(NanNew("stats"))->ToObject()->Set(NanNew("includedFiles"), arr);
} }
int get_result(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = false) { int GetResult(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = false) {
NanScope(); NanScope();
int status = sass_context_get_error_status(ctx); int status = sass_context_get_error_status(ctx);
...@@ -188,7 +138,7 @@ int get_result(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = fa ...@@ -188,7 +138,7 @@ int get_result(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = fa
NanNew(ctx_w->result)->Set(NanNew("css"), NanNewBufferHandle(css, static_cast<uint32_t>(strlen(css)))); NanNew(ctx_w->result)->Set(NanNew("css"), NanNewBufferHandle(css, static_cast<uint32_t>(strlen(css))));
get_stats(ctx_w, ctx); GetStats(ctx_w, ctx);
if (map) { if (map) {
NanNew(ctx_w->result)->Set(NanNew("map"), NanNewBufferHandle(map, static_cast<uint32_t>(strlen(map)))); NanNew(ctx_w->result)->Set(NanNew("map"), NanNewBufferHandle(map, static_cast<uint32_t>(strlen(map))));
...@@ -201,7 +151,7 @@ int get_result(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = fa ...@@ -201,7 +151,7 @@ int get_result(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = fa
return status; return status;
} }
void make_callback(uv_work_t* req) { void MakeCallback(uv_work_t* req) {
NanScope(); NanScope();
TryCatch try_catch; TryCatch try_catch;
...@@ -215,7 +165,7 @@ void make_callback(uv_work_t* req) { ...@@ -215,7 +165,7 @@ void make_callback(uv_work_t* req) {
ctx = sass_file_context_get_context(ctx_w->fctx); ctx = sass_file_context_get_context(ctx_w->fctx);
} }
int status = get_result(ctx_w, ctx); int status = GetResult(ctx_w, ctx);
if (status == 0 && ctx_w->success_callback) { if (status == 0 && ctx_w->success_callback) {
// if no error, do callback(null, result) // if no error, do callback(null, result)
...@@ -233,10 +183,6 @@ void make_callback(uv_work_t* req) { ...@@ -233,10 +183,6 @@ void make_callback(uv_work_t* req) {
node::FatalException(try_catch); node::FatalException(try_catch);
} }
if (ctx_w->importer_callback) {
uv_close((uv_handle_t*)&ctx_w->async, NULL);
}
sass_free_context_wrapper(ctx_w); sass_free_context_wrapper(ctx_w);
} }
...@@ -248,9 +194,9 @@ NAN_METHOD(render) { ...@@ -248,9 +194,9 @@ NAN_METHOD(render) {
struct Sass_Data_Context* dctx = sass_make_data_context(source_string); struct Sass_Data_Context* dctx = sass_make_data_context(source_string);
sass_context_wrapper* ctx_w = sass_make_context_wrapper(); sass_context_wrapper* ctx_w = sass_make_context_wrapper();
extract_options(options, dctx, ctx_w, false, false); ExtractOptions(options, dctx, ctx_w, false, false);
int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)make_callback); int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback);
assert(status == 0); assert(status == 0);
...@@ -266,11 +212,11 @@ NAN_METHOD(render_sync) { ...@@ -266,11 +212,11 @@ NAN_METHOD(render_sync) {
struct Sass_Context* ctx = sass_data_context_get_context(dctx); struct Sass_Context* ctx = sass_data_context_get_context(dctx);
sass_context_wrapper* ctx_w = sass_make_context_wrapper(); sass_context_wrapper* ctx_w = sass_make_context_wrapper();
extract_options(options, dctx, ctx_w, false, true); ExtractOptions(options, dctx, ctx_w, false, true);
compile_data(dctx); compile_data(dctx);
int result = get_result(ctx_w, ctx, true); int result = GetResult(ctx_w, ctx, true);
sass_free_context_wrapper(ctx_w); sass_free_context_wrapper(ctx_w);
...@@ -285,9 +231,9 @@ NAN_METHOD(render_file) { ...@@ -285,9 +231,9 @@ NAN_METHOD(render_file) {
struct Sass_File_Context* fctx = sass_make_file_context(input_path); struct Sass_File_Context* fctx = sass_make_file_context(input_path);
sass_context_wrapper* ctx_w = sass_make_context_wrapper(); sass_context_wrapper* ctx_w = sass_make_context_wrapper();
extract_options(options, fctx, ctx_w, true, false); ExtractOptions(options, fctx, ctx_w, true, false);
int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)make_callback); int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback);
assert(status == 0); assert(status == 0);
...@@ -303,47 +249,22 @@ NAN_METHOD(render_file_sync) { ...@@ -303,47 +249,22 @@ NAN_METHOD(render_file_sync) {
struct Sass_Context* ctx = sass_file_context_get_context(fctx); struct Sass_Context* ctx = sass_file_context_get_context(fctx);
sass_context_wrapper* ctx_w = sass_make_context_wrapper(); sass_context_wrapper* ctx_w = sass_make_context_wrapper();
extract_options(options, fctx, ctx_w, true, true); ExtractOptions(options, fctx, ctx_w, true, true);
compile_file(fctx); compile_file(fctx);
int result = get_result(ctx_w, ctx, true); int result = GetResult(ctx_w, ctx, true);
sass_wrapper_dispose(ctx_w, input_path); sass_wrapper_dispose(ctx_w, input_path);
NanReturnValue(NanNew<Boolean>(result == 0)); NanReturnValue(NanNew<Boolean>(result == 0));
} }
NAN_METHOD(imported_callback) {
NanScope();
TryCatch try_catch;
Local<Object> options = args[0]->ToObject();
Local<Value> returned_value = options->Get(NanNew("objectLiteral"));
size_t index = options->Get(NanNew("index"))->Int32Value();
if (index >= imports_collection.size()) {
NanReturnUndefined();
}
sass_context_wrapper* ctx_w = imports_collection[index];
prepare_import_results(returned_value, ctx_w);
ctx_w->importer_condition_variable->notify_all();
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
NanReturnValue(NanNew<Number>(0));
}
void RegisterModule(v8::Handle<v8::Object> target) { void RegisterModule(v8::Handle<v8::Object> target) {
NODE_SET_METHOD(target, "render", render); NODE_SET_METHOD(target, "render", render);
NODE_SET_METHOD(target, "renderSync", render_sync); NODE_SET_METHOD(target, "renderSync", render_sync);
NODE_SET_METHOD(target, "renderFile", render_file); NODE_SET_METHOD(target, "renderFile", render_file);
NODE_SET_METHOD(target, "renderFileSync", render_file_sync); NODE_SET_METHOD(target, "renderFileSync", render_file_sync);
NODE_SET_METHOD(target, "importedCallback", imported_callback); SassTypes::Factory::initExports(target);
} }
NODE_MODULE(binding, RegisterModule); NODE_MODULE(binding, RegisterModule);
#ifndef CALLBACK_BRIDGE_H
#define CALLBACK_BRIDGE_H
#include <vector>
#include <nan.h>
#include <condition_variable>
#include <algorithm>
#define COMMA ,
using namespace v8;
template <typename T, typename L = void*>
class CallbackBridge {
public:
CallbackBridge(NanCallback*, bool);
virtual ~CallbackBridge();
// Executes the callback
T operator()(std::vector<void*>);
protected:
// We will expose a bridge object to the JS callback that wraps this instance so we don't loose context.
// This is the V8 constructor for such objects.
static Handle<Function> get_wrapper_constructor();
static NAN_METHOD(New);
static NAN_METHOD(ReturnCallback);
static Persistent<Function> wrapper_constructor;
Persistent<Object> wrapper;
// The callback that will get called in the main thread after the worker thread used for the sass
// compilation step makes a call to uv_async_send()
static void dispatched_async_uv_callback(uv_async_t*);
// The V8 values sent to our ReturnCallback must be read on the main thread not the sass worker thread.
// This gives a chance to specialized subclasses to transform those values into whatever makes sense to
// sass before we resume the worker thread.
virtual T post_process_return_value(Handle<Value>) const =0;
virtual std::vector<Handle<Value>> pre_process_args(std::vector<L>) const =0;
NanCallback* callback;
bool is_sync;
std::mutex cv_mutex;
std::condition_variable condition_variable;
uv_async_t async;
std::vector<L> argv;
bool has_returned;
T return_value;
};
template <typename T, typename L>
Persistent<Function> CallbackBridge<T, L>::wrapper_constructor;
template <typename T, typename L>
CallbackBridge<T, L>::CallbackBridge(NanCallback* callback, bool is_sync) : callback(callback), is_sync(is_sync) {
// This assumes the main thread will be the one instantiating the bridge
if (!is_sync) {
uv_async_init(uv_default_loop(), &this->async, (uv_async_cb) dispatched_async_uv_callback);
this->async.data = (void*) this;
}
NanAssignPersistent(wrapper, NanNew(CallbackBridge<T, L>::get_wrapper_constructor())->NewInstance());
NanSetInternalFieldPointer(NanNew(wrapper), 0, this);
}
template <typename T, typename L>
CallbackBridge<T, L>::~CallbackBridge() {
delete this->callback;
NanDisposePersistent(this->wrapper);
if (!is_sync) {
uv_close((uv_handle_t*)&this->async, NULL);
}
}
template <typename T, typename L>
T CallbackBridge<T, L>::operator()(std::vector<void*> argv) {
// argv.push_back(wrapper);
if (this->is_sync) {
std::vector<Handle<Value>> argv_v8 = pre_process_args(argv);
argv_v8.push_back(NanNew(wrapper));
return this->post_process_return_value(
NanNew<Value>(this->callback->Call(argv_v8.size(), &argv_v8[0]))
);
}
this->argv = argv;
std::unique_lock<std::mutex> lock(this->cv_mutex);
this->has_returned = false;
uv_async_send(&this->async);
this->condition_variable.wait(lock, [this] { return this->has_returned; });
return this->return_value;
}
template <typename T, typename L>
void CallbackBridge<T, L>::dispatched_async_uv_callback(uv_async_t *req) {
CallbackBridge* bridge = static_cast<CallbackBridge*>(req->data);
NanScope();
TryCatch try_catch;
std::vector<Handle<Value>> argv_v8 = bridge->pre_process_args(bridge->argv);
argv_v8.push_back(NanNew(bridge->wrapper));
NanNew<Value>(bridge->callback->Call(argv_v8.size(), &argv_v8[0]));
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
template <typename T, typename L>
NAN_METHOD(CallbackBridge<T COMMA L>::ReturnCallback) {
NanScope();
CallbackBridge<T, L>* bridge = static_cast<CallbackBridge<T, L>*>(NanGetInternalFieldPointer(args.This(), 0));
TryCatch try_catch;
bridge->return_value = bridge->post_process_return_value(args[0]);
{
std::lock_guard<std::mutex> lock(bridge->cv_mutex);
bridge->has_returned = true;
}
bridge->condition_variable.notify_all();
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
NanReturnUndefined();
}
template <typename T, typename L>
Handle<Function> CallbackBridge<T, L>::get_wrapper_constructor() {
if (wrapper_constructor.IsEmpty()) {
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew("CallbackBridge"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
tpl->PrototypeTemplate()->Set(
NanNew("success"),
NanNew<FunctionTemplate>(ReturnCallback)->GetFunction()
);
NanAssignPersistent(wrapper_constructor, tpl->GetFunction());
}
return NanNew(wrapper_constructor);
}
template <typename T, typename L>
NAN_METHOD(CallbackBridge<T COMMA L>::New) {
NanScope();
NanReturnValue(args.This());
}
#endif
#include <nan.h>
#include <string.h>
#include "create_string.h"
char* create_string(Local<Value> value) {
if (value->IsNull() || !value->IsString()) {
return 0;
}
String::Utf8Value string(value);
char *str = (char *)malloc(string.length() + 1);
strcpy(str, *string);
return str;
}
#ifndef CREATE_STRING_H
#define CREATE_STRING_H
#include <nan.h>
using namespace v8;
char* create_string(Local<Value>);
#endif
\ No newline at end of file
#include <nan.h>
#include "custom_function_bridge.h"
#include <sass_context.h>
#include "sass_types/factory.h"
Sass_Value* CustomFunctionBridge::post_process_return_value(Handle<Value> val) const {
try {
return SassTypes::Factory::unwrap(val)->get_sass_value();
} catch (const std::invalid_argument& e) {
return sass_make_error(e.what());
}
}
std::vector<Handle<Value>> CustomFunctionBridge::pre_process_args(std::vector<void*> in) const {
std::vector<Handle<Value>> argv = std::vector<Handle<Value>>();
for (void* value : in) {
argv.push_back(
SassTypes::Factory::create(static_cast<Sass_Value*>(value))->get_js_object()
);
}
return argv;
}
#ifndef CUSTOM_FUNCTION_BRIDGE_H
#define CUSTOM_FUNCTION_BRIDGE_H
#include <nan.h>
#include "callback_bridge.h"
#include <sass_context.h>
using namespace v8;
class CustomFunctionBridge : public CallbackBridge<Sass_Value*> {
public:
CustomFunctionBridge(NanCallback* cb, bool is_sync) : CallbackBridge<Sass_Value*>(cb, is_sync) {}
private:
Sass_Value* post_process_return_value(Handle<Value>) const;
std::vector<Handle<Value>> pre_process_args(std::vector<void*>) const;
};
#endif
#include <nan.h>
#include <sass_context.h>
#include "custom_importer_bridge.h"
#include "create_string.h"
SassImportList CustomImporterBridge::post_process_return_value(Handle<Value> val) const {
SassImportList imports;
NanScope();
Local<Value> returned_value = NanNew(val);
if (returned_value->IsArray()) {
Handle<Array> array = Handle<Array>::Cast(returned_value);
imports = sass_make_import_list(array->Length());
for (size_t i = 0; i < array->Length(); ++i) {
Local<Value> value = array->Get(static_cast<uint32_t>(i));
if (!value->IsObject())
continue;
Local<Object> object = Local<Object>::Cast(value);
char* path = create_string(object->Get(NanNew<String>("file")));
char* contents = create_string(object->Get(NanNew<String>("contents")));
imports[i] = sass_make_import_entry(path, (!contents || contents[0] == '\0') ? 0 : strdup(contents), 0);
}
}
else if (returned_value->IsObject()) {
imports = sass_make_import_list(1);
Local<Object> object = Local<Object>::Cast(returned_value);
char* path = create_string(object->Get(NanNew<String>("file")));
char* contents = create_string(object->Get(NanNew<String>("contents")));
imports[0] = sass_make_import_entry(path, (!contents || contents[0] == '\0') ? 0 : strdup(contents), 0);
}
else {
imports = sass_make_import_list(1);
imports[0] = sass_make_import_entry((char const*) this->argv[0], 0, 0);
}
return imports;
}
std::vector<Handle<Value>> CustomImporterBridge::pre_process_args(std::vector<void*> in) const {
std::vector<Handle<Value>> out;
for (void* ptr : in) {
out.push_back(NanNew<String>((char const*) ptr));
}
return out;
}
#ifndef CUSTOM_IMPORTER_BRIDGE_H
#define CUSTOM_IMPORTER_BRIDGE_H
#include <nan.h>
#include <sass_context.h>
#include "callback_bridge.h"
using namespace v8;
typedef Sass_Import** SassImportList;
class CustomImporterBridge : public CallbackBridge<SassImportList> {
public:
CustomImporterBridge(NanCallback* cb, bool is_sync) : CallbackBridge<SassImportList>(cb, is_sync) {}
private:
SassImportList post_process_return_value(Handle<Value>) const;
std::vector<Handle<Value>> pre_process_args(std::vector<void*>) const;
};
#endif
...@@ -23,12 +23,7 @@ extern "C" { ...@@ -23,12 +23,7 @@ extern "C" {
} }
sass_context_wrapper* sass_make_context_wrapper() { sass_context_wrapper* sass_make_context_wrapper() {
sass_context_wrapper* ctx_w = (sass_context_wrapper*)calloc(1, sizeof(sass_context_wrapper)); return (sass_context_wrapper*)calloc(1, sizeof(sass_context_wrapper));
ctx_w->importer_mutex = new std::mutex();
ctx_w->importer_condition_variable = new std::condition_variable();
return ctx_w;
} }
void sass_wrapper_dispose(struct sass_context_wrapper* ctx_w, char* string = 0) { void sass_wrapper_dispose(struct sass_context_wrapper* ctx_w, char* string = 0) {
...@@ -39,20 +34,24 @@ extern "C" { ...@@ -39,20 +34,24 @@ extern "C" {
sass_delete_file_context(ctx_w->fctx); sass_delete_file_context(ctx_w->fctx);
} }
delete ctx_w->file;
delete ctx_w->prev;
delete ctx_w->error_callback; delete ctx_w->error_callback;
delete ctx_w->success_callback; delete ctx_w->success_callback;
delete ctx_w->importer_callback;
delete ctx_w->importer_mutex;
delete ctx_w->importer_condition_variable;
NanDisposePersistent(ctx_w->result); NanDisposePersistent(ctx_w->result);
if(string) { if(string) {
free(string); free(string);
} }
if (!ctx_w->function_bridges.empty()) {
for (CustomFunctionBridge* bridge : ctx_w->function_bridges) {
delete bridge;
}
}
if (ctx_w->importer_bridge) {
delete ctx_w->importer_bridge;
}
} }
void sass_free_context_wrapper(sass_context_wrapper* ctx_w) { void sass_free_context_wrapper(sass_context_wrapper* ctx_w) {
......
#include <stdlib.h> #ifndef SASS_CONTEXT_WRAPPER
#define SASS_CONTEXT_WRAPPER
#include <vector>
#include <nan.h> #include <nan.h>
#include <stdlib.h>
#include <condition_variable> #include <condition_variable>
#include <sass_context.h> #include <sass_context.h>
#include "custom_function_bridge.h"
#include "custom_importer_bridge.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
...@@ -17,13 +23,8 @@ extern "C" { ...@@ -17,13 +23,8 @@ extern "C" {
// binding related // binding related
bool is_sync; bool is_sync;
void* cookie; void* cookie;
const char* prev;
const char* file;
std::mutex* importer_mutex;
std::condition_variable* importer_condition_variable;
// libsass related // libsass related
Sass_Import** imports;
Sass_Data_Context* dctx; Sass_Data_Context* dctx;
Sass_File_Context* fctx; Sass_File_Context* fctx;
...@@ -35,7 +36,9 @@ extern "C" { ...@@ -35,7 +36,9 @@ extern "C" {
Persistent<Object> result; Persistent<Object> result;
NanCallback* error_callback; NanCallback* error_callback;
NanCallback* success_callback; NanCallback* success_callback;
NanCallback* importer_callback;
std::vector<CustomFunctionBridge*> function_bridges;
CustomImporterBridge* importer_bridge;
}; };
struct sass_context_wrapper* sass_make_context_wrapper(void); struct sass_context_wrapper* sass_make_context_wrapper(void);
...@@ -45,3 +48,5 @@ extern "C" { ...@@ -45,3 +48,5 @@ extern "C" {
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif
#include <nan.h>
#include <sass_values.h>
#include "boolean.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
Persistent<Function> Boolean::constructor;
bool Boolean::constructor_locked = false;
Boolean::Boolean(bool v) : value(v) {}
Boolean& Boolean::get_singleton(bool v) {
static Boolean instance_false(false), instance_true(true);
return v ? instance_true : instance_false;
}
Handle<Function> Boolean::get_constructor() {
if (constructor.IsEmpty()) {
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew("SassBoolean"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
tpl->PrototypeTemplate()->Set(NanNew("getValue"), NanNew<FunctionTemplate>(GetValue)->GetFunction());
NanAssignPersistent(constructor, tpl->GetFunction());
NanAssignPersistent(get_singleton(false).js_object, NanNew(constructor)->NewInstance());
NanSetInternalFieldPointer(NanNew(get_singleton(false).js_object), 0, &get_singleton(false));
NanNew(constructor)->Set(NanNew("FALSE"), NanNew(get_singleton(false).js_object));
NanAssignPersistent(get_singleton(true).js_object, NanNew(constructor)->NewInstance());
NanSetInternalFieldPointer(NanNew(get_singleton(true).js_object), 0, &get_singleton(true));
NanNew(constructor)->Set(NanNew("TRUE"), NanNew(get_singleton(true).js_object));
constructor_locked = true;
}
return NanNew(constructor);
}
Sass_Value* Boolean::get_sass_value() {
return sass_make_boolean(value);
}
Local<Object> Boolean::get_js_object() {
return NanNew(this->js_object);
}
NAN_METHOD(Boolean::New) {
NanScope();
if (args.IsConstructCall()) {
if (constructor_locked) {
return NanThrowError(NanNew("Cannot instantiate SassBoolean"));
}
} else {
if (args.Length() != 1 || !args[0]->IsBoolean()) {
return NanThrowError(NanNew("Expected one boolean argument"));
}
NanReturnValue(NanNew(get_singleton(args[0]->ToBoolean()->Value()).get_js_object()));
}
NanReturnUndefined();
}
NAN_METHOD(Boolean::GetValue) {
NanScope();
NanReturnValue(NanNew(static_cast<Boolean*>(Factory::unwrap(args.This()))->value));
}
}
#ifndef SASS_TYPES_BOOLEAN_H
#define SASS_TYPES_BOOLEAN_H
#include <nan.h>
#include <sass_values.h>
#include "value.h"
namespace SassTypes
{
using namespace v8;
class Boolean : public Value {
public:
static Boolean& get_singleton(bool);
static Handle<Function> get_constructor();
Sass_Value* get_sass_value();
Local<Object> get_js_object();
static NAN_METHOD(New);
static NAN_METHOD(GetValue);
private:
Boolean(bool);
bool value;
Persistent<Object> js_object;
static Persistent<Function> constructor;
static bool constructor_locked;
};
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "color.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
Color::Color(Sass_Value* v) : SassValueWrapper(v) {}
Sass_Value* Color::construct(const std::vector<Local<v8::Value>> raw_val) {
double a = 1.0, r = 0, g = 0, b = 0;
unsigned argb;
switch (raw_val.size()) {
case 1:
if (!raw_val[0]->IsNumber()) {
throw std::invalid_argument("Only argument should be an integer.");
}
argb = raw_val[0]->ToInt32()->Value();
a = (double) ((argb >> 030) & 0xff) / 0xff;
r = (double) ((argb >> 020) & 0xff);
g = (double) ((argb >> 010) & 0xff);
b = (double) (argb & 0xff);
break;
case 4:
if (!raw_val[3]->IsNumber()) {
throw std::invalid_argument("Constructor arguments should be numbers exclusively.");
}
a = raw_val[3]->ToNumber()->Value();
// fall through vvv
case 3:
if (!raw_val[0]->IsNumber() || !raw_val[1]->IsNumber() || !raw_val[2]->IsNumber()) {
throw std::invalid_argument("Constructor arguments should be numbers exclusively.");
}
r = raw_val[0]->ToNumber()->Value();
g = raw_val[1]->ToNumber()->Value();
b = raw_val[2]->ToNumber()->Value();
break;
case 0:
break;
default:
throw std::invalid_argument("Constructor should be invoked with either 0, 1, 3 or 4 arguments.");
}
return sass_make_color(r, g, b, a);
}
void Color::initPrototype(Handle<ObjectTemplate> proto) {
proto->Set(NanNew("getR"), NanNew<FunctionTemplate>(GetR)->GetFunction());
proto->Set(NanNew("getG"), NanNew<FunctionTemplate>(GetG)->GetFunction());
proto->Set(NanNew("getB"), NanNew<FunctionTemplate>(GetB)->GetFunction());
proto->Set(NanNew("getA"), NanNew<FunctionTemplate>(GetA)->GetFunction());
proto->Set(NanNew("setR"), NanNew<FunctionTemplate>(SetR)->GetFunction());
proto->Set(NanNew("setG"), NanNew<FunctionTemplate>(SetG)->GetFunction());
proto->Set(NanNew("setB"), NanNew<FunctionTemplate>(SetB)->GetFunction());
proto->Set(NanNew("setA"), NanNew<FunctionTemplate>(SetA)->GetFunction());
}
NAN_METHOD(Color::GetR) {
NanScope();
NanReturnValue(NanNew(sass_color_get_r(unwrap(args.This())->value)));
}
NAN_METHOD(Color::GetG) {
NanScope();
NanReturnValue(NanNew(sass_color_get_g(unwrap(args.This())->value)));
}
NAN_METHOD(Color::GetB) {
NanScope();
NanReturnValue(NanNew(sass_color_get_b(unwrap(args.This())->value)));
}
NAN_METHOD(Color::GetA) {
NanScope();
NanReturnValue(NanNew(sass_color_get_a(unwrap(args.This())->value)));
}
NAN_METHOD(Color::SetR) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied value should be a number"));
}
sass_color_set_r(unwrap(args.This())->value, args[0]->ToNumber()->Value());
NanReturnUndefined();
}
NAN_METHOD(Color::SetG) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied value should be a number"));
}
sass_color_set_g(unwrap(args.This())->value, args[0]->ToNumber()->Value());
NanReturnUndefined();
}
NAN_METHOD(Color::SetB) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied value should be a number"));
}
sass_color_set_b(unwrap(args.This())->value, args[0]->ToNumber()->Value());
NanReturnUndefined();
}
NAN_METHOD(Color::SetA) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied value should be a number"));
}
sass_color_set_a(unwrap(args.This())->value, args[0]->ToNumber()->Value());
NanReturnUndefined();
}
}
#ifndef SASS_TYPES_COLOR_H
#define SASS_TYPES_COLOR_H
#include <nan.h>
#include <sass_values.h>
#include "sass_value_wrapper.h"
namespace SassTypes
{
using namespace v8;
class Color : public SassValueWrapper<Color> {
public:
Color(Sass_Value*);
static char const* get_constructor_name() { return "SassColor"; }
static Sass_Value* construct(const std::vector<Local<v8::Value>>);
static void initPrototype(Handle<ObjectTemplate>);
static NAN_METHOD(GetR);
static NAN_METHOD(GetG);
static NAN_METHOD(GetB);
static NAN_METHOD(GetA);
static NAN_METHOD(SetR);
static NAN_METHOD(SetG);
static NAN_METHOD(SetB);
static NAN_METHOD(SetA);
};
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "error.h"
#include "../create_string.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
Error::Error(Sass_Value* v) : SassValueWrapper(v) {}
Sass_Value* Error::construct(const std::vector<Local<v8::Value>> raw_val) {
char const* value = "";
if (raw_val.size() >= 1) {
if (!raw_val[0]->IsString()) {
throw std::invalid_argument("Argument should be a string.");
}
value = create_string(raw_val[0]);
}
return sass_make_error(value);
}
void Error::initPrototype(Handle<ObjectTemplate>) {}
}
#ifndef SASS_TYPES_ERROR_H
#define SASS_TYPES_ERROR_H
#include <nan.h>
#include <sass_values.h>
#include "sass_value_wrapper.h"
namespace SassTypes
{
using namespace v8;
class Error : public SassValueWrapper<Error> {
public:
Error(Sass_Value*);
static char const* get_constructor_name() { return "SassError"; }
static Sass_Value* construct(const std::vector<Local<v8::Value>>);
static void initPrototype(Handle<ObjectTemplate>);
};
}
#endif
#include <nan.h>
#include "factory.h"
#include <sass_values.h>
#include "value.h"
#include "number.h"
#include "string.h"
#include "color.h"
#include "boolean.h"
#include "list.h"
#include "map.h"
#include "null.h"
#include "error.h"
using namespace v8;
namespace SassTypes
{
Value* Factory::create(Sass_Value* v) {
switch (sass_value_get_tag(v)) {
case SASS_NUMBER:
return new Number(v);
case SASS_STRING:
return new String(v);
case SASS_COLOR:
return new Color(v);
case SASS_BOOLEAN:
return &Boolean::get_singleton(sass_boolean_get_value(v));
case SASS_LIST:
return new List(v);
case SASS_MAP:
return new Map(v);
case SASS_NULL:
return &Null::get_singleton();
case SASS_ERROR:
return new Error(v);
default:
throw std::invalid_argument("Unknown type encountered.");
}
}
void Factory::initExports(Handle<Object> exports) {
Local<Object> types = NanNew<Object>();
exports->Set(NanNew("types"), types);
types->Set(NanNew("Number"), Number::get_constructor());
types->Set(NanNew("String"), String::get_constructor());
types->Set(NanNew("Color"), Color::get_constructor());
types->Set(NanNew("Boolean"), Boolean::get_constructor());
types->Set(NanNew("List"), List::get_constructor());
types->Set(NanNew("Map"), Map::get_constructor());
types->Set(NanNew("Null"), Null::get_constructor());
types->Set(NanNew("Error"), Error::get_constructor());
}
Value* Factory::unwrap(Handle<v8::Value> obj) {
// Todo: non-SassValue objects could easily fall under that condition, need to be more specific.
if (!obj->IsObject() || obj->ToObject()->InternalFieldCount() != 1) {
throw std::invalid_argument("A SassValue object was expected.");
}
return static_cast<Value*>(NanGetInternalFieldPointer(obj->ToObject(), 0));
}
}
#ifndef SASS_TYPES_FACTORY_H
#define SASS_TYPES_FACTORY_H
#include <nan.h>
#include <sass_values.h>
#include "value.h"
namespace SassTypes
{
using namespace v8;
// This is the guru that knows everything about instantiating the right subclass of SassTypes::Value
// to wrap a given Sass_Value object.
class Factory {
public:
static void initExports(Handle<Object>);
static Value* create(Sass_Value*);
static Value* unwrap(Handle<v8::Value>);
};
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "list.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
List::List(Sass_Value* v) : SassValueWrapper(v) {}
Sass_Value* List::construct(const std::vector<Local<v8::Value>> raw_val) {
size_t length = 0;
bool comma = true;
if (raw_val.size() >= 1) {
if (!raw_val[0]->IsNumber()) {
throw std::invalid_argument("First argument should be an integer.");
}
length = raw_val[0]->ToInt32()->Value();
if (raw_val.size() >= 2) {
if (!raw_val[1]->IsBoolean()) {
throw std::invalid_argument("Second argument should be a boolean.");
}
comma = raw_val[1]->ToBoolean()->Value();
}
}
return sass_make_list(length, comma ? SASS_COMMA : SASS_SPACE);
}
void List::initPrototype(Handle<ObjectTemplate> proto) {
proto->Set(NanNew("getLength"), NanNew<FunctionTemplate>(GetLength)->GetFunction());
proto->Set(NanNew("getSeparator"), NanNew<FunctionTemplate>(GetSeparator)->GetFunction());
proto->Set(NanNew("setSeparator"), NanNew<FunctionTemplate>(SetSeparator)->GetFunction());
proto->Set(NanNew("getValue"), NanNew<FunctionTemplate>(GetValue)->GetFunction());
proto->Set(NanNew("setValue"), NanNew<FunctionTemplate>(SetValue)->GetFunction());
}
NAN_METHOD(List::GetValue) {
NanScope();
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied index should be an integer"));
}
Sass_Value* list = unwrap(args.This())->value;
size_t index = args[0]->ToInt32()->Value();
if (index >= sass_list_get_length(list)) {
return NanThrowError(NanNew("Out of bound index"));
}
NanReturnValue(Factory::create(sass_list_get_value(list, args[0]->ToInt32()->Value()))->get_js_object());
}
NAN_METHOD(List::SetValue) {
if (args.Length() != 2) {
return NanThrowError(NanNew("Expected two arguments"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied index should be an integer"));
}
if (!args[1]->IsObject()) {
return NanThrowError(NanNew("Supplied value should be a SassValue object"));
}
Value* sass_value = Factory::unwrap(args[1]);
sass_list_set_value(unwrap(args.This())->value, args[0]->ToInt32()->Value(), sass_value->get_sass_value());
NanReturnUndefined();
}
NAN_METHOD(List::GetSeparator) {
NanScope();
NanReturnValue(NanNew(sass_list_get_separator(unwrap(args.This())->value) == SASS_COMMA));
}
NAN_METHOD(List::SetSeparator) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsBoolean()) {
return NanThrowError(NanNew("Supplied value should be a boolean"));
}
sass_list_set_separator(unwrap(args.This())->value, args[0]->ToBoolean()->Value() ? SASS_COMMA : SASS_SPACE);
NanReturnUndefined();
}
NAN_METHOD(List::GetLength) {
NanScope();
NanReturnValue(NanNew<v8::Number>(sass_list_get_length(unwrap(args.This())->value)));
}
}
#ifndef SASS_TYPES_LIST_H
#define SASS_TYPES_LIST_H
#include <nan.h>
#include <sass_values.h>
#include "sass_value_wrapper.h"
namespace SassTypes
{
using namespace v8;
class List : public SassValueWrapper<List> {
public:
List(Sass_Value*);
static char const* get_constructor_name() { return "SassList"; }
static Sass_Value* construct(const std::vector<Local<v8::Value>>);
static void initPrototype(Handle<ObjectTemplate>);
static NAN_METHOD(GetValue);
static NAN_METHOD(SetValue);
static NAN_METHOD(GetSeparator);
static NAN_METHOD(SetSeparator);
static NAN_METHOD(GetLength);
};
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "map.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
Map::Map(Sass_Value* v) : SassValueWrapper(v) {}
Sass_Value* Map::construct(const std::vector<Local<v8::Value>> raw_val) {
size_t length = 0;
if (raw_val.size() >= 1) {
if (!raw_val[0]->IsNumber()) {
throw std::invalid_argument("First argument should be an integer.");
}
length = raw_val[0]->ToInt32()->Value();
}
return sass_make_map(length);
}
void Map::initPrototype(Handle<ObjectTemplate> proto) {
proto->Set(NanNew("getLength"), NanNew<FunctionTemplate>(GetLength)->GetFunction());
proto->Set(NanNew("getKey"), NanNew<FunctionTemplate>(GetKey)->GetFunction());
proto->Set(NanNew("setKey"), NanNew<FunctionTemplate>(SetKey)->GetFunction());
proto->Set(NanNew("getValue"), NanNew<FunctionTemplate>(GetValue)->GetFunction());
proto->Set(NanNew("setValue"), NanNew<FunctionTemplate>(SetValue)->GetFunction());
}
NAN_METHOD(Map::GetValue) {
NanScope();
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied index should be an integer"));
}
Sass_Value* map = unwrap(args.This())->value;
size_t index = args[0]->ToInt32()->Value();
if (index >= sass_map_get_length(map)) {
return NanThrowError(NanNew("Out of bound index"));
}
NanReturnValue(NanNew(Factory::create(sass_map_get_value(map, args[0]->ToInt32()->Value()))->get_js_object()));
}
NAN_METHOD(Map::SetValue) {
if (args.Length() != 2) {
return NanThrowError(NanNew("Expected two arguments"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied index should be an integer"));
}
if (!args[1]->IsObject()) {
return NanThrowError(NanNew("Supplied value should be a SassValue object"));
}
Value* sass_value = Factory::unwrap(args[1]);
sass_map_set_value(unwrap(args.This())->value, args[0]->ToInt32()->Value(), sass_value->get_sass_value());
NanReturnUndefined();
}
NAN_METHOD(Map::GetKey) {
NanScope();
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied index should be an integer"));
}
Sass_Value* map = unwrap(args.This())->value;
size_t index = args[0]->ToInt32()->Value();
if (index >= sass_map_get_length(map)) {
return NanThrowError(NanNew("Out of bound index"));
}
NanReturnValue(Factory::create(sass_map_get_key(map, args[0]->ToInt32()->Value()))->get_js_object());
}
NAN_METHOD(Map::SetKey) {
if (args.Length() != 2) {
return NanThrowError(NanNew("Expected two arguments"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied index should be an integer"));
}
if (!args[1]->IsObject()) {
return NanThrowError(NanNew("Supplied value should be a SassValue object"));
}
Value* sass_value = Factory::unwrap(args[1]);
sass_map_set_key(unwrap(args.This())->value, args[0]->ToInt32()->Value(), sass_value->get_sass_value());
NanReturnUndefined();
}
NAN_METHOD(Map::GetLength) {
NanScope();
NanReturnValue(NanNew<v8::Number>(sass_map_get_length(unwrap(args.This())->value)));
}
}
#ifndef SASS_TYPES_MAP_H
#define SASS_TYPES_MAP_H
#include <nan.h>
#include <sass_values.h>
#include "sass_value_wrapper.h"
namespace SassTypes
{
using namespace v8;
class Map : public SassValueWrapper<Map> {
public:
Map(Sass_Value*);
static char const* get_constructor_name() { return "SassMap"; }
static Sass_Value* construct(const std::vector<Local<v8::Value>>);
static void initPrototype(Handle<ObjectTemplate>);
static NAN_METHOD(GetValue);
static NAN_METHOD(SetValue);
static NAN_METHOD(GetKey);
static NAN_METHOD(SetKey);
static NAN_METHOD(GetLength);
};
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "null.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
Persistent<Function> Null::constructor;
bool Null::constructor_locked = false;
Null::Null() {}
Null& Null::get_singleton() {
static Null singleton_instance;
return singleton_instance;
}
Handle<Function> Null::get_constructor() {
if (constructor.IsEmpty()) {
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew("SassNull"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
NanAssignPersistent(constructor, tpl->GetFunction());
NanAssignPersistent(get_singleton().js_object, NanNew(constructor)->NewInstance());
NanSetInternalFieldPointer(NanNew(get_singleton().js_object), 0, &get_singleton());
NanNew(constructor)->Set(NanNew("NULL"), NanNew(get_singleton().js_object));
constructor_locked = true;
}
return NanNew(constructor);
}
Sass_Value* Null::get_sass_value() {
return sass_make_null();
}
Local<Object> Null::get_js_object() {
return NanNew(this->js_object);
}
NAN_METHOD(Null::New) {
NanScope();
if (args.IsConstructCall()) {
if (constructor_locked) {
return NanThrowError(NanNew("Cannot instantiate SassNull"));
}
} else {
NanReturnValue(NanNew(get_singleton().get_js_object()));
}
NanReturnUndefined();
}
}
#ifndef SASS_TYPES_NULL_H
#define SASS_TYPES_NULL_H
#include <nan.h>
#include <sass_values.h>
#include "value.h"
namespace SassTypes
{
using namespace v8;
class Null : public Value {
public:
static Null& get_singleton();
static Handle<Function> get_constructor();
Sass_Value* get_sass_value();
Local<Object> get_js_object();
static NAN_METHOD(New);
private:
Null();
Persistent<Object> js_object;
static Persistent<Function> constructor;
static bool constructor_locked;
};
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "number.h"
#include "../create_string.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
Number::Number(Sass_Value* v) : SassValueWrapper(v) {}
Sass_Value* Number::construct(const std::vector<Local<v8::Value>> raw_val) {
double value = 0;
char const* unit = "";
if (raw_val.size() >= 1) {
if (!raw_val[0]->IsNumber()) {
throw std::invalid_argument("First argument should be a number.");
}
value = raw_val[0]->ToNumber()->Value();
if (raw_val.size() >= 2) {
if (!raw_val[1]->IsString()) {
throw std::invalid_argument("Second argument should be a string.");
}
unit = create_string(raw_val[1]);
}
}
return sass_make_number(value, unit);
}
void Number::initPrototype(Handle<ObjectTemplate> proto) {
proto->Set(NanNew("getValue"), NanNew<FunctionTemplate>(GetValue)->GetFunction());
proto->Set(NanNew("getUnit"), NanNew<FunctionTemplate>(GetUnit)->GetFunction());
proto->Set(NanNew("setValue"), NanNew<FunctionTemplate>(SetValue)->GetFunction());
proto->Set(NanNew("setUnit"), NanNew<FunctionTemplate>(SetUnit)->GetFunction());
}
NAN_METHOD(Number::GetValue) {
NanScope();
NanReturnValue(NanNew(sass_number_get_value(unwrap(args.This())->value)));
}
NAN_METHOD(Number::GetUnit) {
NanScope();
NanReturnValue(NanNew(sass_number_get_unit(unwrap(args.This())->value)));
}
NAN_METHOD(Number::SetValue) {
NanScope();
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsNumber()) {
return NanThrowError(NanNew("Supplied value should be a number"));
}
sass_number_set_value(unwrap(args.This())->value, args[0]->ToNumber()->Value());
NanReturnUndefined();
}
NAN_METHOD(Number::SetUnit) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsString()) {
return NanThrowError(NanNew("Supplied value should be a string"));
}
sass_number_set_unit(unwrap(args.This())->value, create_string(args[0]));
NanReturnUndefined();
}
}
#ifndef SASS_TYPES_NUMBER_H
#define SASS_TYPES_NUMBER_H
#include <nan.h>
#include <sass_values.h>
#include "sass_value_wrapper.h"
namespace SassTypes
{
using namespace v8;
class Number : public SassValueWrapper<Number> {
public:
Number(Sass_Value*);
static char const* get_constructor_name() { return "SassNumber"; }
static Sass_Value* construct(const std::vector<Local<v8::Value>>);
static void initPrototype(Handle<ObjectTemplate>);
static NAN_METHOD(GetValue);
static NAN_METHOD(GetUnit);
static NAN_METHOD(SetValue);
static NAN_METHOD(SetUnit);
};
}
#endif
#ifndef SASS_TYPES_SASS_VALUE_WRAPPER_H
#define SASS_TYPES_SASS_VALUE_WRAPPER_H
#include <stdexcept>
#include <vector>
#include <nan.h>
#include <sass_values.h>
#include "value.h"
#include "factory.h"
namespace SassTypes
{
using namespace v8;
// Include this in any SassTypes::Value subclasses to handle all the heavy lifting of constructing JS
// objects and wrapping sass values inside them
template <class T>
class SassValueWrapper : public Value {
public:
static char const* get_constructor_name() { return "SassValue"; }
SassValueWrapper(Sass_Value*);
virtual ~SassValueWrapper();
Sass_Value* get_sass_value();
Local<Object> get_js_object();
static Handle<Function> get_constructor();
static Local<FunctionTemplate> get_constructor_template();
static NAN_METHOD(New);
protected:
Sass_Value* value;
static T* unwrap(Local<Object>);
private:
static Persistent<Function> constructor;
Persistent<Object> js_object;
};
template <class T>
Persistent<Function> SassValueWrapper<T>::constructor;
template <class T>
SassValueWrapper<T>::SassValueWrapper(Sass_Value* v) {
this->value = sass_clone_value(v);
}
template <class T>
SassValueWrapper<T>::~SassValueWrapper() {
NanDisposePersistent(this->js_object);
sass_delete_value(this->value);
}
template <class T>
Sass_Value* SassValueWrapper<T>::get_sass_value() {
return sass_clone_value(this->value);
}
template <class T>
Local<Object> SassValueWrapper<T>::get_js_object() {
if (this->js_object.IsEmpty()) {
Local<Object> wrapper = NanNew(T::get_constructor())->NewInstance();
delete static_cast<T*>(NanGetInternalFieldPointer(wrapper, 0));
NanSetInternalFieldPointer(wrapper, 0, this);
NanAssignPersistent(this->js_object, wrapper);
}
return NanNew(this->js_object);
}
template <class T>
Local<FunctionTemplate> SassValueWrapper<T>::get_constructor_template() {
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew(NanNew(T::get_constructor_name())));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
T::initPrototype(tpl->PrototypeTemplate());
return tpl;
}
template <class T>
Handle<Function> SassValueWrapper<T>::get_constructor() {
if (constructor.IsEmpty()) {
NanAssignPersistent(constructor, T::get_constructor_template()->GetFunction());
}
return NanNew(constructor);
}
template <class T>
NAN_METHOD(SassValueWrapper<T>::New) {
NanScope();
if (!args.IsConstructCall()) {
unsigned argc = args.Length();
std::vector<Handle<v8::Value>> argv;
argv.reserve(argc);
for (unsigned i = 0; i < argc; i++) {
argv.push_back(args[i]);
}
NanReturnValue(NanNew(T::get_constructor())->NewInstance(argc, &argv[0]));
}
std::vector<Local<v8::Value>> localArgs(args.Length());
for (auto i = 0; i < args.Length(); ++i) {
localArgs[i] = args[i];
}
try {
Sass_Value* value = T::construct(localArgs);
T* obj = new T(value);
sass_delete_value(value);
NanSetInternalFieldPointer(args.This(), 0, obj);
NanAssignPersistent(obj->js_object, args.This());
} catch (const std::exception& e) {
return NanThrowError(NanNew(e.what()));
}
NanReturnUndefined();
}
template <class T>
T* SassValueWrapper<T>::unwrap(Local<Object> obj) {
return static_cast<T*>(Factory::unwrap(obj));
}
}
#endif
#include <nan.h>
#include <sass_values.h>
#include "string.h"
#include "../create_string.h"
#include "sass_value_wrapper.h"
using namespace v8;
namespace SassTypes
{
String::String(Sass_Value* v) : SassValueWrapper(v) {}
Sass_Value* String::construct(const std::vector<Local<v8::Value>> raw_val) {
char const* value = "";
if (raw_val.size() >= 1) {
if (!raw_val[0]->IsString()) {
throw std::invalid_argument("Argument should be a string.");
}
value = create_string(raw_val[0]);
}
return sass_make_string(value);
}
void String::initPrototype(Handle<ObjectTemplate> proto) {
proto->Set(NanNew("getValue"), NanNew<FunctionTemplate>(GetValue)->GetFunction());
proto->Set(NanNew("setValue"), NanNew<FunctionTemplate>(SetValue)->GetFunction());
}
NAN_METHOD(String::GetValue) {
NanScope();
NanReturnValue(NanNew(sass_string_get_value(unwrap(args.This())->value)));
}
NAN_METHOD(String::SetValue) {
if (args.Length() != 1) {
return NanThrowError(NanNew("Expected just one argument"));
}
if (!args[0]->IsString()) {
return NanThrowError(NanNew("Supplied value should be a string"));
}
sass_string_set_value(unwrap(args.This())->value, create_string(args[0]));
NanReturnUndefined();
}
}
#ifndef SASS_TYPES_STRING_H
#define SASS_TYPES_STRING_H
#include <nan.h>
#include <sass_values.h>
#include "sass_value_wrapper.h"
namespace SassTypes
{
using namespace v8;
class String : public SassValueWrapper<String> {
public:
String(Sass_Value*);
static char const* get_constructor_name() { return "SassString"; }
static Sass_Value* construct(const std::vector<Local<v8::Value>>);
static void initPrototype(Handle<ObjectTemplate>);
static NAN_METHOD(GetValue);
static NAN_METHOD(SetValue);
};
}
#endif
#ifndef SASS_TYPES_VALUE_H
#define SASS_TYPES_VALUE_H
#include <nan.h>
#include <sass_values.h>
namespace SassTypes
{
using namespace v8;
// This is the interface that all sass values must comply with
class Value {
public:
virtual Sass_Value* get_sass_value() =0;
virtual Local<Object> get_js_object() =0;
};
}
#endif
...@@ -356,6 +356,566 @@ describe('api', function() { ...@@ -356,6 +356,566 @@ describe('api', function() {
}); });
}); });
describe('.render(functions)', function() {
it('should call custom defined nullary function', function(done) {
sass.render({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
return new sass.types.Number(42, 'px');
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: 42px; }');
done();
});
});
it('should call custom function with multiple args', function(done) {
sass.render({
data: 'div { color: foo(3, 42px); }',
functions: {
'foo($a, $b)': function(factor, size) {
return new sass.types.Number(factor.getValue() * size.getValue(), size.getUnit());
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: 126px; }');
done();
});
});
it('should work with custom functions that return data asynchronously', function(done) {
sass.render({
data: 'div { color: foo(42px); }',
functions: {
'foo($a)': function(size, done) {
setTimeout(function() {
done(new sass.types.Number(66, 'em'));
}, 50);
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: 66em; }');
done();
});
});
it('should let custom functions call setter methods on wrapped sass values (number)', function(done) {
sass.render({
data: 'div { width: foo(42px); height: bar(42px); }',
functions: {
'foo($a)': function(size) {
size.setUnit('rem');
return size;
},
'bar($a)': function(size) {
size.setValue(size.getValue() * 2);
return size;
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n width: 42rem;\n height: 84px; }');
done();
});
});
it('should properly convert strings when calling custom functions', function(done) {
sass.render({
data: 'div { color: foo("bar"); }',
functions: {
'foo($a)': function(str) {
str = str.getValue().replace(/['"]/g, '');
return new sass.types.String('"' + str + str + '"');
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: "barbar"; }');
done();
});
});
it('should let custom functions call setter methods on wrapped sass values (string)', function(done) {
sass.render({
data: 'div { width: foo("bar"); }',
functions: {
'foo($a)': function(str) {
var unquoted = str.getValue().replace(/['"]/g, '');
str.setValue('"' + unquoted + unquoted + '"');
return str;
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n width: "barbar"; }');
done();
});
});
it('should properly convert colors when calling custom functions', function(done) {
sass.render({
data: 'div { color: foo(#f00); background-color: bar(); border-color: baz(); }',
functions: {
'foo($a)': function(color) {
assert.equal(color.getR(), 255);
assert.equal(color.getG(), 0);
assert.equal(color.getB(), 0);
assert.equal(color.getA(), 1.0);
return new sass.types.Color(255, 255, 0, 0.5);
},
'bar()': function() {
return new sass.types.Color(0x33ff00ff);
},
'baz()': function() {
return new sass.types.Color(0xffff0000);
}
}
}, function(error, result) {
assert.equal(
result.css.toString().trim(),
'div {\n color: rgba(255, 255, 0, 0.5);' +
'\n background-color: rgba(255, 0, 255, 0.2);' +
'\n border-color: red; }'
);
done();
});
});
it('should properly convert boolean when calling custom functions', function(done) {
sass.render({
data: 'div { color: if(foo(true, false), #fff, #000);' +
'\n background-color: if(foo(true, true), #fff, #000); }',
functions: {
'foo($a, $b)': function(a, b) {
return sass.types.Boolean(a.getValue() && b.getValue());
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: #000;\n background-color: #fff; }');
done();
});
});
it('should let custom functions call setter methods on wrapped sass values (boolean)', function(done) {
sass.render({
data: 'div { color: if(foo(false), #fff, #000); background-color: if(foo(true), #fff, #000); }',
functions: {
'foo($a)': function(a) {
return a.getValue() ? sass.types.Boolean.FALSE : sass.types.Boolean.TRUE;
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: #fff;\n background-color: #000; }');
done();
});
});
it('should properly convert lists when calling custom functions', function(done) {
sass.render({
data: '$test-list: (bar, #f00, 123em); @each $item in foo($test-list) { .#{$item} { color: #fff; } }',
functions: {
'foo($l)': function(list) {
assert.equal(list.getLength(), 3);
assert.ok(list.getValue(0) instanceof sass.types.String);
assert.equal(list.getValue(0).getValue(), 'bar');
assert.ok(list.getValue(1) instanceof sass.types.Color);
assert.equal(list.getValue(1).getR(), 0xff);
assert.equal(list.getValue(1).getG(), 0);
assert.equal(list.getValue(1).getB(), 0);
assert.ok(list.getValue(2) instanceof sass.types.Number);
assert.equal(list.getValue(2).getValue(), 123);
assert.equal(list.getValue(2).getUnit(), 'em');
var out = new sass.types.List(3);
out.setValue(0, new sass.types.String('foo'));
out.setValue(1, new sass.types.String('bar'));
out.setValue(2, new sass.types.String('baz'));
return out;
}
}
}, function(error, result) {
assert.equal(
result.css.toString().trim(),
'.foo {\n color: #fff; }\n\n.bar {\n color: #fff; }\n\n.baz {\n color: #fff; }'
);
done();
});
});
it('should properly convert maps when calling custom functions', function(done) {
sass.render({
data: '$test-map: foo((abc: 123, #def: true)); div { color: if(map-has-key($test-map, hello), #fff, #000); }' +
'span { color: map-get($test-map, baz); }',
functions: {
'foo($m)': function(map) {
assert.equal(map.getLength(), 2);
assert.ok(map.getKey(0) instanceof sass.types.String);
assert.ok(map.getKey(1) instanceof sass.types.Color);
assert.ok(map.getValue(0) instanceof sass.types.Number);
assert.ok(map.getValue(1) instanceof sass.types.Boolean);
assert.equal(map.getKey(0).getValue(), 'abc');
assert.equal(map.getValue(0).getValue(), 123);
assert.equal(map.getKey(1).getR(), 0xdd);
assert.equal(map.getValue(1).getValue(), true);
var out = new sass.types.Map(3);
out.setKey(0, new sass.types.String('hello'));
out.setValue(0, new sass.types.String('world'));
out.setKey(1, new sass.types.String('foo'));
out.setValue(1, new sass.types.String('bar'));
out.setKey(2, new sass.types.String('baz'));
out.setValue(2, new sass.types.String('qux'));
return out;
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: #fff; }\n\nspan {\n color: qux; }');
done();
});
});
it('should properly convert null when calling custom functions', function(done) {
sass.render({
data: 'div { color: if(foo("bar"), #fff, #000); } ' +
'span { color: if(foo(null), #fff, #000); }' +
'table { color: if(bar() == null, #fff, #000); }',
functions: {
'foo($a)': function(a) {
return sass.types.Boolean(a instanceof sass.types.Null);
},
'bar()': function() {
return sass.NULL;
}
}
}, function(error, result) {
assert.equal(
result.css.toString().trim(),
'div {\n color: #000; }\n\nspan {\n color: #fff; }\n\ntable {\n color: #fff; }'
);
done();
});
});
it('should be possible to carry sass values across different renders', function(done) {
var persistentMap;
sass.render({
data: 'div { color: foo((abc: #112233, #ddeeff: true)); }',
functions: {
foo: function(m) {
persistentMap = m;
return sass.types.Color(0, 0, 0);
}
}
}, function() {
sass.render({
data: 'div { color: map-get(bar(), abc); background-color: baz(); }',
functions: {
bar: function() {
return persistentMap;
},
baz: function() {
return persistentMap.getKey(1);
}
}
}, function(errror, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: #112233;\n background-color: #ddeeff; }');
done();
});
});
});
it('should let us register custom functions without signatures', function(done) {
sass.render({
data: 'div { color: foo(20, 22); }',
functions: {
foo: function(a, b) {
return new sass.types.Number(a.getValue() + b.getValue(), 'em');
}
}
}, function(error, result) {
assert.equal(result.css.toString().trim(), 'div {\n color: 42em; }');
done();
});
});
it('should fail when returning anything other than a sass value from a custom function', function(done) {
sass.render({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
return {};
}
}
}, function(error) {
assert.ok(/A SassValue object was expected/.test(error.message));
done();
});
});
it('should properly bubble up standard JS errors thrown by custom functions', function(done) {
sass.render({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
throw new RangeError('This is a test error');
}
}
}, function(error) {
assert.ok(/This is a test error/.test(error.message));
done();
});
});
it('should properly bubble up unknown errors thrown by custom functions', function(done) {
sass.render({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
throw {};
}
}
}, function(error) {
assert.ok(/unexpected error/.test(error.message));
done();
});
});
it('should properly bubble up errors from sass value constructors', function(done) {
sass.render({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
return sass.types.Boolean('foo');
}
}
}, function(error) {
assert.ok(/Expected one boolean argument/.test(error.message));
done();
});
});
it('should properly bubble up errors from sass value setters', function(done) {
sass.render({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
var ret = new sass.types.Number(42);
ret.setUnit(123);
return ret;
}
}
}, function(error) {
assert.ok(/Supplied value should be a string/.test(error.message));
done();
});
});
it('should always map null, true and false to the same (immutable) object', function(done) {
var counter = 0;
sass.render({
data: 'div { color: foo(bar(null)); background-color: baz("foo" == "bar"); }',
functions: {
foo: function(a) {
assert.ok(
'Supplied value should be the same instance as sass.TRUE',
a === sass.TRUE
);
assert.ok(
'sass.types.Boolean(true) should return a singleton',
sass.types.Boolean(true) === sass.types.Boolean(true) &&
sass.types.Boolean(true) === sass.TRUE
);
counter++;
return sass.types.String('foo');
},
bar: function(a) {
assert.ok(
'Supplied value should be the same instance as sass.NULL',
a === sass.NULL
);
assert.throws(function() {
return new sass.types.Null();
}, /Cannot instantiate SassNull/);
counter++;
return sass.TRUE;
},
baz: function(a) {
assert.ok(
'Supplied value should be the same instance as sass.FALSE',
a === sass.FALSE
);
assert.throws(function() {
return new sass.types.Boolean(false);
}, /Cannot instantiate SassBoolean/);
assert.ok(
'sass.types.Boolean(false) should return a singleton',
sass.types.Boolean(false) === sass.types.Boolean(false) &&
sass.types.Boolean(false) === sass.FALSE
);
counter++;
return sass.types.String('baz');
}
}
}, function() {
assert.ok(counter === 3);
done();
});
});
});
describe('.renderSync(functions)', function() {
it('should call custom function in sync mode', function(done) {
var result = sass.renderSync({
data: 'div { width: cos(0) * 50px; }',
functions: {
'cos($a)': function(angle) {
if (!(angle instanceof sass.types.Number)) {
throw new TypeError('Unexpected type for "angle"');
}
return new sass.types.Number(Math.cos(angle.getValue()));
}
}
});
assert.equal(result.css.toString().trim(), 'div {\n width: 50px; }');
done();
});
it('should return a list of selectors after calling the headings custom function', function(done) {
var result = sass.renderSync({
data: '#{headings(2,5)} { color: #08c; }',
functions: {
'headings($from: 0, $to: 6)': function(from, to) {
var i, f = from.getValue(), t = to.getValue(),
list = new sass.types.List(t - f + 1);
for (i = f; i <= t; i++) {
list.setValue(i - f, new sass.types.String('h' + i));
}
return list;
}
}
});
assert.equal(result.css.toString().trim(), 'h2, h3, h4, h5 {\n color: #08c; }');
done();
});
it('should let custom function invoke sass types constructors without the `new` keyword', function(done) {
var result = sass.renderSync({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
return sass.types.Number(42, 'em');
}
}
});
assert.equal(result.css.toString().trim(), 'div {\n color: 42em; }');
done();
});
it('should let us register custom functions without signatures', function(done) {
var result = sass.renderSync({
data: 'div { color: foo(20, 22); }',
functions: {
foo: function(a, b) {
return new sass.types.Number(a.getValue() + b.getValue(), 'em');
}
}
});
assert.equal(result.css.toString().trim(), 'div {\n color: 42em; }');
done();
});
it('should fail when returning anything other than a sass value from a custom function', function(done) {
assert.throws(function() {
sass.renderSync({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
return {};
}
}
});
}, /A SassValue object was expected/);
done();
});
it('should properly bubble up standard JS errors thrown by custom functions', function(done) {
assert.throws(function() {
sass.renderSync({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
throw new RangeError('This is a test error');
}
}
});
}, /This is a test error/);
done();
});
it('should properly bubble up unknown errors thrown by custom functions', function(done) {
assert.throws(function() {
sass.renderSync({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
throw {};
}
}
});
}, /unexpected error/);
done();
});
it('should properly bubble up errors from sass value getters/setters/constructors', function(done) {
assert.throws(function() {
sass.renderSync({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
return sass.types.Boolean('foo');
}
}
});
}, /Expected one boolean argument/);
assert.throws(function() {
sass.renderSync({
data: 'div { color: foo(); }',
functions: {
'foo()': function() {
var ret = new sass.types.Number(42);
ret.setUnit(123);
return ret;
}
}
});
}, /Supplied value should be a string/);
done();
});
});
describe('.renderSync(options)', function() { describe('.renderSync(options)', function() {
it('should compile sass to css with file', function(done) { it('should compile sass to css with file', function(done) {
var expected = read(fixture('simple/expected.css'), 'utf8').trim(); var expected = read(fixture('simple/expected.css'), 'utf8').trim();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment