Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Commit

Permalink
Merge pull request #576 from am11/importer
Browse files Browse the repository at this point in the history
Feature: Custom importer (#530)
  • Loading branch information
am11 committed Dec 18, 2014
2 parents 7c354bf + 5a6bd67 commit 4bea070
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 57 deletions.
12 changes: 12 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -173,6 +174,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;
Expand Down
200 changes: 164 additions & 36 deletions src/binding.cpp
Original file line number Diff line number Diff line change
@@ -1,44 +1,104 @@
#include <nan.h>
#include <vector>
#include "sass_context_wrapper.h"

char* CreateString(Local<Value> value) {
if(value->IsNull() || !value->IsString()) {
if (value->IsNull() || !value->IsString()) {
return const_cast<char*>(""); // 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<Object> options, void* cptr, sass_context_wrapper* ctx_w, bool isFile) {
if (ctx_w) {
NanAssignPersistent(ctx_w->stats, options->Get(NanNew("stats"))->ToObject());
std::vector<sass_context_wrapper*> imports_collection;

// async (callback) style
Local<Function> callback = Local<Function>::Cast(options->Get(NanNew("success")));
Local<Function> errorCallback = Local<Function>::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<sass_context_wrapper*>(req->data);

TryCatch try_catch;

imports_collection.push_back(ctx_w);

Handle<Value> argv[] = {
NanNew<String>(strdup(ctx_w->file)),
NanNew<String>(strdup(ctx_w->prev)),
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)
{
sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(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<Object> 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<Function> success_callback = Local<Function>::Cast(options->Get(NanNew("success")));
Local<Function> error_callback = Local<Function>::Cast(options->Get(NanNew("error")));

ctx_w->success_callback = new NanCallback(success_callback);
ctx_w->error_callback = new NanCallback(error_callback);
}

Local<Function> importer_callback = Local<Function>::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());
Expand All @@ -56,7 +116,7 @@ void FillStatsObj(Handle<Object> stats, Sass_Context* ctx) {
char** included_files = sass_context_get_included_files(ctx);
Handle<Array> arr = NanNew<Array>();

if(included_files) {
if (included_files) {
for (int i = 0; included_files[i] != nullptr; ++i) {
arr->Set(i, NanNew<String>(included_files[i]));
}
Expand All @@ -72,14 +132,15 @@ void FillStatsObj(Handle<Object> stats, Sass_Context* ctx) {

if (sass_context_get_source_map_string(ctx)) {
source_map = NanNew<String>(sass_context_get_source_map_string(ctx));
} else {
}
else {
source_map = NanNew<String>("{}");
}

(*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;
Expand All @@ -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);
Expand All @@ -104,15 +166,16 @@ void MakeCallback(uv_work_t* req) {
NanNew<String>(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<Value> argv[] = {
NanNew<String>(err),
NanNew<Integer>(error_status)
};
ctx_w->errorCallback->Call(2, argv);
ctx_w->error_callback->Call(2, argv);
}
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -161,7 +223,7 @@ NAN_METHOD(RenderSync) {

Local<String> error = NanNew<String>(sass_context_get_error_json(ctx));

sass_delete_data_context(dctx);
sass_free_context_wrapper(ctx_w);
NanThrowError(error);

NanReturnUndefined();
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -208,17 +270,83 @@ NAN_METHOD(RenderFileSync) {

Local<String> error = NanNew<String>(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<Object> options = args[0]->ToObject();
char* source_string = CreateString(options->Get(NanNew("index")));
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];

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(i);

if (!value->IsObject())
continue;

Local<Object> object = Local<Object>::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> object = Local<Object>::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<Number>(0));
}

void RegisterModule(v8::Handle<v8::Object> 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);
23 changes: 18 additions & 5 deletions src/sass_context_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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);
}
Expand Down
Loading

0 comments on commit 4bea070

Please sign in to comment.