diff --git a/API.md b/API.md new file mode 100644 index 0000000..6044177 --- /dev/null +++ b/API.md @@ -0,0 +1,43 @@ +# HelloWorld + +Main class, called HelloWorld + +**Examples** + +```javascript +var HelloWorld = require('index.js'); +var HW = new HelloWorld(); +``` + +## wave + +Say howdy to the world + +**Examples** + +```javascript +var wave = HW.wave(); +console.log(wave); // => 'howdy world!' +``` + +Returns **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** a happy-go-lucky string saying hi + +## shout + +Shout a phrase really loudly by adding an exclamation to the end, asynchronously + +**Parameters** + +- `phrase` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** to shout +- `different` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** ways to shout +- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** from whence the shout comes, returns a string + +**Examples** + +```javascript +var HW = new HelloWorld(); +HW.shout('rawr', {}, function(err, shout) { + if (err) throw err; + console.log(shout); // => 'rawr!' +}); +``` diff --git a/Makefile b/Makefile index e69de29..847a2f2 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,5 @@ +default: + npm install --build-from-source + +docs: + npm run docs \ No newline at end of file diff --git a/package.json b/package.json index e7da514..ae59bfd 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "./lib/index.js", "scripts": { "test": "tape test/*.test.js", - "install": "node-pre-gyp install --fallback-to-build" + "install": "node-pre-gyp install --fallback-to-build", + "docs": "documentation build src/*.cpp --polyglot -f md -o API.md" }, "author": "Mapbox", "license": "ISC", @@ -14,6 +15,7 @@ "node-pre-gyp": "^0.6.28" }, "devDependencies": { + "documentation": "^4.0.0-beta5", "tape": "^4.5.1" }, "binary": { diff --git a/src/hello_world.cpp b/src/hello_world.cpp index d9f94f4..7b073f6 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -3,6 +3,13 @@ #include #include +/** + * Main class, called HelloWorld + * @class HelloWorld + * @example + * var HelloWorld = require('index.js'); + * var HW = new HelloWorld(); + */ NAN_METHOD(HelloWorld::New) { if (info.IsConstructCall()) @@ -26,17 +33,166 @@ NAN_METHOD(HelloWorld::New) } } +Nan::Persistent &HelloWorld::constructor() +{ + static Nan::Persistent init; + return init; +} + +/** + * Say howdy to the world + * + * @name wave + * @memberof HelloWorld + * @returns {String} a happy-go-lucky string saying hi + * @example + * var wave = HW.wave(); + * console.log(wave); // => 'howdy world!' + */ NAN_METHOD(HelloWorld::wave) { // info comes from the NAN_METHOD macro, which returns differently // according to the version of node - info.GetReturnValue().Set(Nan::New("howdy world!").ToLocalChecked()); + info.GetReturnValue().Set(Nan::New("howdy world").ToLocalChecked()); } -Nan::Persistent &HelloWorld::constructor() +/** + * Shout a phrase really loudly by adding an exclamation to the end, asynchronously + * + * @name shout + * @memberof HelloWorld + * @param {String} phrase to shout + * @param {Object} different ways to shout + * @param {Function} callback - from whence the shout comes, returns a string + * @example + * var HW = new HelloWorld(); + * HW.shout('rawr', {}, function(err, shout) { + * if (err) throw err; + * console.log(shout); // => 'rawr!' + * }); + * + */ + +// this is the cpp object that will be passed around in 'shout' and callbacks +// referred to as a "baton" +class AsyncBaton { - static Nan::Persistent init; - return init; + public: + uv_work_t request; // required + Nan::Persistent cb; // callback function type + std::string phrase; + bool louder; + std::string error_name; + std::string result; +}; + +NAN_METHOD(HelloWorld::shout) +{ + std::string phrase = ""; + bool louder = false; + + // check first argument, should be a 'phrase' string + if (!info[0]->IsString()) + { + Nan::ThrowTypeError("first arg 'phrase' must be a string"); + return; + } + phrase = *v8::String::Utf8Value((info[0])->ToString()); + + // check second argument, should be an 'options' object + if (!info[1]->IsObject()) + { + Nan::ThrowTypeError("second arg 'options' must be an object"); + return; + } + + v8::Local options = info[1].As(); + if (options->Has(Nan::New("louder").ToLocalChecked())) + { + v8::Local louder_val = options->Get(Nan::New("louder").ToLocalChecked()); + if (!louder_val->IsBoolean()) + { + Nan::ThrowError("option 'louder' must be a boolean"); + return; + } + louder = louder_val->BooleanValue(); + } + + // check third argument, should be a 'callback' function + if (!info[2]->IsFunction()) + { + Nan::ThrowTypeError("third arg 'callback' must be a function"); + return; + } + v8::Local callback = info[2]; + + // set up the baton to pass into our threadpool + AsyncBaton *baton = new AsyncBaton(); + baton->request.data = baton; + baton->phrase = phrase; + baton->louder = louder; + baton->cb.Reset(callback.As()); + + /* + `uv_queue_work` is the all-important way to pass info into the threadpool. + It cannot take v8 objects, so we need to do some manipulation above to convert into cpp objects + otherwise things get janky. It takes four arguments: + + 1) which loop to use, node only has one so we pass in a pointer to the default + 2) the baton defined above, we use this to access information important for the method + 3) operations to be executed within the threadpool + 4) operations to be executed after #3 is complete to pass into the callback + */ + uv_queue_work(uv_default_loop(), &baton->request, AsyncShout, (uv_after_work_cb)AfterShout); + return; +} + +// this is where we actually exclaim our shout phrase +void HelloWorld::AsyncShout(uv_work_t* req) +{ + AsyncBaton *baton = static_cast(req->data); + + /***************** custom code here ******************/ + try + { + std::string return_string = baton->phrase + "!"; + + if (baton->louder) + { + return_string += "!!!!"; + } + + baton->result = return_string; + } + catch (std::exception const& ex) + { + baton->error_name = ex.what(); + } + /***************** end custom code *******************/ + +} + +// handle results from AsyncShout - if there are errors return those +// otherwise return the type & info to our callback +void HelloWorld::AfterShout(uv_work_t* req) +{ + Nan::HandleScope scope; + + AsyncBaton *baton = static_cast(req->data); + + if (!baton->error_name.empty()) + { + v8::Local argv[1] = { Nan::Error(baton->error_name.c_str()) }; + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(baton->cb), 1, argv); + } + else + { + v8::Local argv[2] = { Nan::Null(), Nan::New(baton->result.data()).ToLocalChecked() }; + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(baton->cb), 2, argv); + } + + baton->cb.Reset(); + delete baton; } NAN_MODULE_INIT(HelloWorld::Init) @@ -47,16 +203,13 @@ NAN_MODULE_INIT(HelloWorld::Init) fnTp->InstanceTemplate()->SetInternalFieldCount(1); fnTp->SetClassName(whoami); + // custom methods added here SetPrototypeMethod(fnTp, "wave", wave); + SetPrototypeMethod(fnTp, "shout", shout); const auto fn = Nan::GetFunction(fnTp).ToLocalChecked(); - constructor().Reset(fn); - Nan::Set(target, whoami, fn); } -NODE_MODULE(HelloWorld, HelloWorld::Init); - - - +NODE_MODULE(HelloWorld, HelloWorld::Init); \ No newline at end of file diff --git a/src/hello_world.hpp b/src/hello_world.hpp index 84c67bc..e9c5bae 100644 --- a/src/hello_world.hpp +++ b/src/hello_world.hpp @@ -17,10 +17,15 @@ class HelloWorld: public Nan::ObjectWrap // initializer static NAN_MODULE_INIT(Init); - // method new used for the constructor + // methods required for the constructor static NAN_METHOD(New); + static Nan::Persistent &constructor(); - // custom method called wave + // wave, custom sync method static NAN_METHOD(wave); - static Nan::Persistent &constructor(); + + // shout, custom async method + static NAN_METHOD(shout); + static void AsyncShout(uv_work_t* req); + static void AfterShout(uv_work_t* req); }; \ No newline at end of file diff --git a/src/hey.cpp b/src/hey.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/test/hello_world.test.js b/test/hello_world.test.js index 6ab046d..b45a87a 100644 --- a/test/hello_world.test.js +++ b/test/hello_world.test.js @@ -1,9 +1,67 @@ var test = require('tape'); var HelloWorld = require('../lib/index.js'); +var HW = new HelloWorld(); -test('wave hi', function(t) { - var HW = new HelloWorld(); +test('wave success', function(t) { var hello = HW.wave(); - t.equal(hello, 'howdy world!', 'output of HelloWorld.wave'); + t.equal(hello, 'howdy world', 'output of HelloWorld.wave'); t.end(); -}) \ No newline at end of file +}); + +test('shout success', function(t) { + HW.shout('rawr', {}, function(err, shout) { + if (err) throw err; + t.equal(shout, 'rawr!'); + t.end(); + }); +}); + +test('shout success - options.louder', function(t) { + HW.shout('rawr', { louder: true }, function(err, shout) { + if (err) throw err; + t.equal(shout, 'rawr!!!!!'); + t.end(); + }); +}); + +// we have to wrap these in try/catch statements right now +// https://github.com/mapbox/node-cpp-skel/issues/4 +test('shout error - non string phrase', function(t) { + try { + HW.shout(4, {}, function(err, shout) {}); + } catch (err) { + t.ok(err, 'expected error'); + t.ok(err.message.indexOf('phrase') > -1, 'proper error message'); + t.end(); + } +}); + +test('shout error - no options object', function(t) { + try { + HW.shout('rawr', true, function(err, shout) {}); + } catch (err) { + t.ok(err, 'expected error'); + t.ok(err.message.indexOf('options') > -1, 'proper error message'); + t.end(); + } +}); + +test('shout error - options.louder non boolean', function(t) { + try { + HW.shout('rawr', { louder: 3 }, function(err, shout) {}); + } catch (err) { + t.ok(err, 'expected error'); + t.ok(err.message.indexOf('louder') > -1, 'proper error message'); + t.end(); + } +}); + +test('shout error - no callback', function(t) { + try { + HW.shout('rawr', {}); + } catch (err) { + t.ok(err, 'expected error'); + t.ok(err.message.indexOf('callback') > -1, 'proper error message'); + t.end(); + } +}); \ No newline at end of file