diff --git a/lib/index.js b/lib/index.js index be514d16a..a9feb90a1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -150,6 +150,7 @@ function getOptions(options) { var error = options.error; var success = options.success; + var importer = options.importer; options.error = function(err, code) { try { @@ -171,6 +172,17 @@ function getOptions(options) { } }; + if (importer) { + options.importer = function(file, prev, key) { + importer(file, prev, function(data) { + binding.importedCallback({ + index: key, + objectLiteral: data + }); + }); + }; + } + delete options.image_path; delete options.include_paths; delete options.includePaths; diff --git a/src/binding.cpp b/src/binding.cpp index 89e3469e8..417dcc6f7 100644 --- a/src/binding.cpp +++ b/src/binding.cpp @@ -1,44 +1,104 @@ #include +#include #include "sass_context_wrapper.h" char* CreateString(Local value) { - if(value->IsNull() || !value->IsString()) { + if (value->IsNull() || !value->IsString()) { return const_cast(""); // return empty string. } String::Utf8Value string(value); - char *str = (char *) malloc(string.length() + 1); + char *str = (char *)malloc(string.length() + 1); strcpy(str, *string); return str; } -void ExtractOptions(Local options, void* cptr, sass_context_wrapper* ctx_w, bool isFile) { - if (ctx_w) { - NanAssignPersistent(ctx_w->stats, options->Get(NanNew("stats"))->ToObject()); +std::vector imports_collection; - // async (callback) style - Local callback = Local::Cast(options->Get(NanNew("success"))); - Local errorCallback = Local::Cast(options->Get(NanNew("error"))); - if (isFile) { - ctx_w->fctx = (struct Sass_File_Context*) cptr; - } else { - ctx_w->dctx = (struct Sass_Data_Context*) cptr; - } - ctx_w->request.data = ctx_w; - ctx_w->callback = new NanCallback(callback); - ctx_w->errorCallback = new NanCallback(errorCallback); +void dispatched_async_uv_callback(uv_async_t *req){ + NanScope(); + sass_context_wrapper* ctx_w = static_cast(req->data); + + TryCatch try_catch; + + imports_collection.push_back(ctx_w); + + Handle argv[] = { + NanNew(strdup(ctx_w->file)), + NanNew(strdup(ctx_w->prev)), + NanNew(imports_collection.size() - 1) + }; + + NanNew(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) +{ + sass_context_wrapper* ctx_w = static_cast(cookie); + + ctx_w->file = strdup(file); + ctx_w->prev = strdup(prev); + ctx_w->async.data = (void*)ctx_w; + uv_async_send(&ctx_w->async); + + if (ctx_w->success_callback) { + /* that is async: Render() or RenderFile(), + * the default even loop is unblocked so it + * can run uv_async_send without a push. + */ + uv_cond_wait(&ctx_w->importer_condition_variable, &ctx_w->importer_mutex); + } + else{ + /* that is sync: RenderSync() or RenderFileSync, + * we need to explicitly uv_run as the event loop + * is blocked; waiting down the chain. + */ + uv_run(ctx_w->async.loop, UV_RUN_DEFAULT); } + return ctx_w->imports; +} + +void ExtractOptions(Local options, void* cptr, sass_context_wrapper* ctx_w, bool isFile, bool isSync) { struct Sass_Context* ctx; if (isFile) { ctx = sass_file_context_get_context((struct Sass_File_Context*) cptr); - } else { + ctx_w->fctx = (struct Sass_File_Context*) cptr; + } + else { ctx = sass_data_context_get_context((struct Sass_Data_Context*) cptr); + ctx_w->dctx = (struct Sass_Data_Context*) cptr; } struct Sass_Options* sass_options = sass_context_get_options(ctx); + if (!isSync) { + NanAssignPersistent(ctx_w->stats, options->Get(NanNew("stats"))->ToObject()); + + ctx_w->request.data = ctx_w; + + // async (callback) style + Local success_callback = Local::Cast(options->Get(NanNew("success"))); + Local error_callback = Local::Cast(options->Get(NanNew("error"))); + + ctx_w->success_callback = new NanCallback(success_callback); + ctx_w->error_callback = new NanCallback(error_callback); + } + + Local importer_callback = Local::Cast(options->Get(NanNew("importer"))); + + ctx_w->importer_callback = new NanCallback(importer_callback); + + if (!importer_callback->IsUndefined()) { + 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_output_path(sass_options, CreateString(options->Get(NanNew("outFile")))); sass_option_set_image_path(sass_options, CreateString(options->Get(NanNew("imagePath")))); sass_option_set_output_style(sass_options, (Sass_Output_Style)options->Get(NanNew("style"))->Int32Value()); @@ -56,7 +116,7 @@ void FillStatsObj(Handle stats, Sass_Context* ctx) { char** included_files = sass_context_get_included_files(ctx); Handle arr = NanNew(); - if(included_files) { + if (included_files) { for (int i = 0; included_files[i] != nullptr; ++i) { arr->Set(i, NanNew(included_files[i])); } @@ -72,14 +132,15 @@ void FillStatsObj(Handle stats, Sass_Context* ctx) { if (sass_context_get_source_map_string(ctx)) { source_map = NanNew(sass_context_get_source_map_string(ctx)); - } else { + } + else { source_map = NanNew("{}"); } - (*stats)->Set(NanNew("sourceMap"), source_map); + (*stats)->Set(NanNew("sourceMap"), source_map); } -void MakeCallback(uv_work_t* req) { +void make_callback(uv_work_t* req) { NanScope(); TryCatch try_catch; @@ -91,7 +152,8 @@ void MakeCallback(uv_work_t* req) { ctx = sass_data_context_get_context(ctx_w->dctx); FillStatsObj(NanNew(ctx_w->stats), ctx); error_status = sass_context_get_error_status(ctx); - } else { + } + else { ctx = sass_file_context_get_context(ctx_w->fctx); FillStatsObj(NanNew(ctx_w->stats), ctx); error_status = sass_context_get_error_status(ctx); @@ -104,15 +166,16 @@ void MakeCallback(uv_work_t* req) { NanNew(val), NanNew(ctx_w->stats)->Get(NanNew("sourceMap")) }; - ctx_w->callback->Call(2, argv); - } else { + ctx_w->success_callback->Call(2, argv); + } + else { // if error, do callback(error) const char* err = sass_context_get_error_json(ctx); Local argv[] = { NanNew(err), NanNew(error_status) }; - ctx_w->errorCallback->Call(2, argv); + ctx_w->error_callback->Call(2, argv); } if (try_catch.HasCaught()) { node::FatalException(try_catch); @@ -129,11 +192,9 @@ NAN_METHOD(Render) { struct Sass_Data_Context* dctx = sass_make_data_context(source_string); sass_context_wrapper* ctx_w = sass_make_context_wrapper(); - ctx_w->dctx = dctx; + ExtractOptions(options, dctx, ctx_w, false, false); - ExtractOptions(options, dctx, ctx_w, false); - - int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback); + int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)make_callback); assert(status == 0); @@ -147,8 +208,9 @@ NAN_METHOD(RenderSync) { char* source_string = CreateString(options->Get(NanNew("data"))); struct Sass_Data_Context* dctx = sass_make_data_context(source_string); struct Sass_Context* ctx = sass_data_context_get_context(dctx); + sass_context_wrapper* ctx_w = sass_make_context_wrapper(); - ExtractOptions(options, dctx, NULL, false); + ExtractOptions(options, dctx, ctx_w, false, true); compile_data(dctx); FillStatsObj(options->Get(NanNew("stats"))->ToObject(), ctx); @@ -161,7 +223,7 @@ NAN_METHOD(RenderSync) { Local error = NanNew(sass_context_get_error_json(ctx)); - sass_delete_data_context(dctx); + sass_free_context_wrapper(ctx_w); NanThrowError(error); NanReturnUndefined(); @@ -175,10 +237,9 @@ NAN_METHOD(RenderFile) { struct Sass_File_Context* fctx = sass_make_file_context(input_path); sass_context_wrapper* ctx_w = sass_make_context_wrapper(); - ctx_w->fctx = fctx; - ExtractOptions(options, fctx, ctx_w, true); + ExtractOptions(options, fctx, ctx_w, true, false); - int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback); + int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)make_callback); assert(status == 0); free(input_path); @@ -193,8 +254,9 @@ NAN_METHOD(RenderFileSync) { char* input_path = CreateString(options->Get(NanNew("file"))); struct Sass_File_Context* fctx = sass_make_file_context(input_path); struct Sass_Context* ctx = sass_file_context_get_context(fctx); + sass_context_wrapper* ctx_w = sass_make_context_wrapper(); - ExtractOptions(options, fctx, NULL, true); + ExtractOptions(options, fctx, ctx_w, true, true); compile_file(fctx); FillStatsObj(options->Get(NanNew("stats"))->ToObject(), ctx); free(input_path); @@ -208,17 +270,83 @@ NAN_METHOD(RenderFileSync) { Local error = NanNew(sass_context_get_error_json(ctx)); - sass_delete_file_context(fctx); + sass_free_context_wrapper(ctx_w); NanThrowError(error); NanReturnUndefined(); } +NAN_METHOD(ImportedCallback) { + NanScope(); + + TryCatch try_catch; + + Local options = args[0]->ToObject(); + char* source_string = CreateString(options->Get(NanNew("index"))); + Local 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]; + + if (returned_value->IsArray()) { + Handle array = Handle::Cast(returned_value); + + ctx_w->imports = sass_make_import_list(array->Length()); + + for (size_t i = 0; i < array->Length(); ++i) { + Local value = array->Get(i); + + if (!value->IsObject()) + continue; + + Local object = Local::Cast(value); + char* path = CreateString(object->Get(String::New("file"))); + char* contents = CreateString(object->Get(String::New("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 = Local::Cast(returned_value); + char* path = CreateString(object->Get(String::New("file"))); + char* contents = CreateString(object->Get(String::New("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); + } + + uv_cond_signal(&ctx_w->importer_condition_variable); + + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + + if (!ctx_w->success_callback) { + /* + * that is sync: RenderSync() or RenderFileSync, + * we ran it explictly, so we stop it similarly. + */ + uv_stop(ctx_w->async.loop); + } + + NanReturnValue(NanNew(0)); +} + void RegisterModule(v8::Handle target) { NODE_SET_METHOD(target, "render", Render); NODE_SET_METHOD(target, "renderSync", RenderSync); NODE_SET_METHOD(target, "renderFile", RenderFile); NODE_SET_METHOD(target, "renderFileSync", RenderFileSync); + NODE_SET_METHOD(target, "importedCallback", ImportedCallback); } NODE_MODULE(binding, RegisterModule); diff --git a/src/libsass b/src/libsass index 5f3558d7c..7aaa45a97 160000 --- a/src/libsass +++ b/src/libsass @@ -1 +1 @@ -Subproject commit 5f3558d7ce36bb61202f1585ed8e32a52209e940 +Subproject commit 7aaa45a979ce65d50b3158ff50bf9409f8955063 diff --git a/src/sass_context_wrapper.cpp b/src/sass_context_wrapper.cpp index 5f29b0d1c..35023e1b1 100644 --- a/src/sass_context_wrapper.cpp +++ b/src/sass_context_wrapper.cpp @@ -8,7 +8,8 @@ extern "C" { if (ctx_w->dctx) { compile_data(ctx_w->dctx); - } else if (ctx_w->fctx) { + } + else if (ctx_w->fctx) { compile_file(ctx_w->fctx); } } @@ -22,19 +23,31 @@ extern "C" { } sass_context_wrapper* sass_make_context_wrapper() { - return (sass_context_wrapper*) calloc(1, sizeof(sass_context_wrapper)); + auto ctx_w = (sass_context_wrapper*)calloc(1, sizeof(sass_context_wrapper)); + uv_mutex_init(&ctx_w->importer_mutex); + uv_cond_init(&ctx_w->importer_condition_variable); + return ctx_w; } void sass_free_context_wrapper(sass_context_wrapper* ctx_w) { if (ctx_w->dctx) { sass_delete_data_context(ctx_w->dctx); - } else if (ctx_w->fctx) { + } + else if (ctx_w->fctx) { sass_delete_file_context(ctx_w->fctx); } NanDisposePersistent(ctx_w->stats); - delete ctx_w->callback; - delete ctx_w->errorCallback; + + delete ctx_w->success_callback; + delete ctx_w->error_callback; + delete ctx_w->importer_callback; + delete ctx_w->file; + delete ctx_w->prev; + delete ctx_w->cookie; + + uv_mutex_destroy(&ctx_w->importer_mutex); + uv_cond_destroy(&ctx_w->importer_condition_variable); free(ctx_w); } diff --git a/src/sass_context_wrapper.h b/src/sass_context_wrapper.h index a4c106a6b..902a70df1 100644 --- a/src/sass_context_wrapper.h +++ b/src/sass_context_wrapper.h @@ -5,23 +5,31 @@ extern "C" { #endif -using namespace v8; + using namespace v8; -void compile_data(struct Sass_Data_Context* dctx); -void compile_file(struct Sass_File_Context* fctx); -void compile_it(uv_work_t* req); + void compile_data(struct Sass_Data_Context* dctx); + void compile_file(struct Sass_File_Context* fctx); + void compile_it(uv_work_t* req); -struct sass_context_wrapper { - Sass_Data_Context* dctx; - Sass_File_Context* fctx; - Persistent stats; - uv_work_t request; - NanCallback* callback; - NanCallback* errorCallback; -}; + struct sass_context_wrapper { + Sass_Data_Context* dctx; + Sass_File_Context* fctx; + Persistent stats; + uv_work_t request; + uv_mutex_t importer_mutex; + uv_cond_t importer_condition_variable; + uv_async_t async; + const char* file; + const char* prev; + void* cookie; + Sass_Import** imports; + NanCallback* success_callback; + NanCallback* error_callback; + NanCallback* importer_callback; + }; -struct sass_context_wrapper* sass_make_context_wrapper(void); -void sass_free_context_wrapper(struct sass_context_wrapper* ctx_w); + struct sass_context_wrapper* sass_make_context_wrapper(void); + void sass_free_context_wrapper(struct sass_context_wrapper* ctx_w); #ifdef __cplusplus } diff --git a/test/fixtures/spec b/test/fixtures/spec index b85c617a0..074ae3b24 160000 --- a/test/fixtures/spec +++ b/test/fixtures/spec @@ -1 +1 @@ -Subproject commit b85c617a0e7e5367dd347b0b301ad1c15b6d5087 +Subproject commit 074ae3b24bfc3ff19a98efdec02415ed6fd5759a