From 18d6c48fabb0635fd77fd70379616796f9efa065 Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 Jun 2016 12:15:56 -0700 Subject: [PATCH 1/7] adding an async function called "shout" --- src/hello_world.cpp | 153 ++++++++++++++++++++++++++++++++++++--- src/hello_world.hpp | 11 ++- src/hey.cpp | 0 test/hello_world.test.js | 34 ++++++++- 4 files changed, 181 insertions(+), 17 deletions(-) delete mode 100644 src/hey.cpp diff --git a/src/hello_world.cpp b/src/hello_world.cpp index d9f94f4..a9c4b92 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -3,6 +3,12 @@ #include #include +/** + * Main method, called HelloWorld + * + * @example + * var HW = new HelloWorld(); + */ NAN_METHOD(HelloWorld::New) { if (info.IsConstructCall()) @@ -26,17 +32,147 @@ NAN_METHOD(HelloWorld::New) } } +Nan::Persistent &HelloWorld::constructor() +{ + static Nan::Persistent init; + return init; +} + +/** + * Say hello to the world + * + * @returns {String} howdy world + * @example + * var HW = new HelloWorld(); + * 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 + * + * @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 our method and callbacks +// referred to as a "baton" +typedef struct { + uv_work_t request; // required + Nan::Persistent cb; // callback function type + std::string phrase; + // std::string format; + // palette_ptr palette; + std::string error_name; + std::string result; +} helloworld_shout_baton; + +NAN_METHOD(HelloWorld::shout) { - static Nan::Persistent init; - return init; + std::string phrase = ""; + // palette_ptr palette; + + // 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 'object' must be an object"); + return; + + // we'll want to do more to the object here + } + // v8::Local options = info[1].As(); + + // 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 + helloworld_shout_baton *baton = new helloworld_shout_baton(); + baton->request.data = baton; + baton->phrase = phrase; + baton->cb.Reset(callback.As()); + + // this is the all-important way to pass info into the threadpool using uv_queue_work + // it cannot take v8 objects, so we need to do some manipulation above to convert into cpp objects + // otherwise things get janky + // 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) +{ + helloworld_shout_baton *baton = static_cast(req->data); + + /***************** custom code here ******************/ + + std::string return_string = baton->phrase + '!'; + + /***************** end custom code *******************/ + + try + { + baton->result = return_string; + } + catch (std::exception const& ex) + { + baton->error_name = ex.what(); + } +} + +// 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; + + helloworld_shout_baton *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 +183,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 02204ab..77d36ac 100644 --- a/src/hello_world.hpp +++ b/src/hello_world.hpp @@ -15,10 +15,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..a8aa07a 100644 --- a/test/hello_world.test.js +++ b/test/hello_world.test.js @@ -1,9 +1,35 @@ 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', 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', function(t) { + HW.shout('rawr', {}, function(err, shout) { + if (err) throw err; + console.log(shout); + t.end(); + }); +}); + +test('shout error - non string phrase', function(t) { + HW.shout(4, {}, function(err, shout) { + t.ok(err); + t.end(); + }); +}); + +// test('shout error - no options object', function(t) { +// HW.shout('rawr', true, function(err, shout) { +// t.ok(err); +// t.end(); +// }); +// }); + +// test('shout error - no callback', function(t) { +// t.ok(function() { W.shout('rawr', {}); }); +// }); \ No newline at end of file From 0524c12cd5112b5f5d10df7630f07b56679b6c19 Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 Jun 2016 15:53:05 -0700 Subject: [PATCH 2/7] shout louder --- src/hello_world.cpp | 29 ++++++++++++++++++++++++----- test/hello_world.test.js | 16 ++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/hello_world.cpp b/src/hello_world.cpp index a9c4b92..b919b56 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -77,7 +77,7 @@ typedef struct { Nan::Persistent cb; // callback function type std::string phrase; // std::string format; - // palette_ptr palette; + bool louder; std::string error_name; std::string result; } helloworld_shout_baton; @@ -85,7 +85,7 @@ typedef struct { NAN_METHOD(HelloWorld::shout) { std::string phrase = ""; - // palette_ptr palette; + bool louder = false; // check first argument, should be a 'phrase' string if (!info[0]->IsString()) @@ -100,10 +100,19 @@ NAN_METHOD(HelloWorld::shout) { Nan::ThrowTypeError("second arg 'object' must be an object"); return; + } - // we'll want to do more to the object here + 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(); } - // v8::Local options = info[1].As(); // check third argument, should be a 'callback' function if (!info[2]->IsFunction()) @@ -117,6 +126,7 @@ NAN_METHOD(HelloWorld::shout) helloworld_shout_baton *baton = new helloworld_shout_baton(); baton->request.data = baton; baton->phrase = phrase; + baton->louder = louder; baton->cb.Reset(callback.As()); // this is the all-important way to pass info into the threadpool using uv_queue_work @@ -138,7 +148,16 @@ void HelloWorld::AsyncShout(uv_work_t* req) /***************** custom code here ******************/ - std::string return_string = baton->phrase + '!'; + std::string return_string; + + if (baton->louder == true) + { + return_string = baton->phrase + "!!!!"; + } + else + { + return_string = baton->phrase + "!"; + } /***************** end custom code *******************/ diff --git a/test/hello_world.test.js b/test/hello_world.test.js index a8aa07a..c72bd4f 100644 --- a/test/hello_world.test.js +++ b/test/hello_world.test.js @@ -11,18 +11,26 @@ test('wave', function(t) { test('shout', function(t) { HW.shout('rawr', {}, function(err, shout) { if (err) throw err; - console.log(shout); + t.equal(shout, 'rawr!'); t.end(); }); }); -test('shout error - non string phrase', function(t) { - HW.shout(4, {}, function(err, shout) { - t.ok(err); +test('shout loud', function(t) { + HW.shout('rawr', { louder: true }, function(err, shout) { + if (err) throw err; + t.equal(shout, 'rawr!!!!'); t.end(); }); }); +// test('shout error - non string phrase', function(t) { +// HW.shout(4, {}, function(err, shout) { +// t.ok(err); +// t.end(); +// }); +// }); + // test('shout error - no options object', function(t) { // HW.shout('rawr', true, function(err, shout) { // t.ok(err); From 4e8bfd1547abf24458aade9b3d281d80e07b6151 Mon Sep 17 00:00:00 2001 From: mapsam Date: Fri, 17 Jun 2016 15:57:16 -0700 Subject: [PATCH 3/7] simplify louder check --- src/hello_world.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/hello_world.cpp b/src/hello_world.cpp index b919b56..f4ed4f9 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -148,15 +148,11 @@ void HelloWorld::AsyncShout(uv_work_t* req) /***************** custom code here ******************/ - std::string return_string; + std::string return_string = baton->phrase + "!"; - if (baton->louder == true) + if (baton->louder) { - return_string = baton->phrase + "!!!!"; - } - else - { - return_string = baton->phrase + "!"; + return_string += "!!!!"; } /***************** end custom code *******************/ From e6b659af2f38a4f74be7fd6f826a561fd760be2e Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 17 Jun 2016 15:54:48 -0700 Subject: [PATCH 4/7] convert Baton to a class using C++ style (rather than C Struct) --- src/hello_world.cpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/hello_world.cpp b/src/hello_world.cpp index f4ed4f9..385a67f 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -72,15 +72,16 @@ NAN_METHOD(HelloWorld::wave) // this is the cpp object that will be passed around in our method and callbacks // referred to as a "baton" -typedef struct { +class AsyncBaton +{ + public: uv_work_t request; // required Nan::Persistent cb; // callback function type std::string phrase; - // std::string format; bool louder; std::string error_name; std::string result; -} helloworld_shout_baton; +}; NAN_METHOD(HelloWorld::shout) { @@ -123,7 +124,7 @@ NAN_METHOD(HelloWorld::shout) v8::Local callback = info[2]; // set up the baton to pass into our threadpool - helloworld_shout_baton *baton = new helloworld_shout_baton(); + AsyncBaton *baton = new AsyncBaton(); baton->request.data = baton; baton->phrase = phrase; baton->louder = louder; @@ -144,27 +145,25 @@ NAN_METHOD(HelloWorld::shout) // this is where we actually exclaim our shout phrase void HelloWorld::AsyncShout(uv_work_t* req) { - helloworld_shout_baton *baton = static_cast(req->data); + AsyncBaton *baton = static_cast(req->data); /***************** custom code here ******************/ - - std::string return_string = baton->phrase + "!"; - - if (baton->louder) + try { - return_string += "!!!!"; - } - /***************** end custom code *******************/ + std::string return_string = baton->phrase + "!"; - try - { - baton->result = return_string; + if (baton->louder) + { + 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 @@ -173,7 +172,7 @@ void HelloWorld::AfterShout(uv_work_t* req) { Nan::HandleScope scope; - helloworld_shout_baton *baton = static_cast(req->data); + AsyncBaton *baton = static_cast(req->data); if (!baton->error_name.empty()) { From 4118273249b30bf7f1b07e991a75bfe350982589 Mon Sep 17 00:00:00 2001 From: mapsam Date: Sat, 18 Jun 2016 09:08:55 -0700 Subject: [PATCH 5/7] try/catch error tests --- src/hello_world.cpp | 2 +- test/hello_world.test.js | 62 ++++++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/hello_world.cpp b/src/hello_world.cpp index f4ed4f9..7da0502 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -98,7 +98,7 @@ NAN_METHOD(HelloWorld::shout) // check second argument, should be an 'options' object if (!info[1]->IsObject()) { - Nan::ThrowTypeError("second arg 'object' must be an object"); + Nan::ThrowTypeError("second arg 'options' must be an object"); return; } diff --git a/test/hello_world.test.js b/test/hello_world.test.js index c72bd4f..b45a87a 100644 --- a/test/hello_world.test.js +++ b/test/hello_world.test.js @@ -2,13 +2,13 @@ var test = require('tape'); var HelloWorld = require('../lib/index.js'); var HW = new HelloWorld(); -test('wave', function(t) { +test('wave success', function(t) { var hello = HW.wave(); t.equal(hello, 'howdy world', 'output of HelloWorld.wave'); t.end(); }); -test('shout', function(t) { +test('shout success', function(t) { HW.shout('rawr', {}, function(err, shout) { if (err) throw err; t.equal(shout, 'rawr!'); @@ -16,28 +16,52 @@ test('shout', function(t) { }); }); -test('shout loud', function(t) { +test('shout success - options.louder', function(t) { HW.shout('rawr', { louder: true }, function(err, shout) { if (err) throw err; - t.equal(shout, 'rawr!!!!'); + t.equal(shout, 'rawr!!!!!'); t.end(); }); }); -// test('shout error - non string phrase', function(t) { -// HW.shout(4, {}, function(err, shout) { -// t.ok(err); -// 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 - no options object', function(t) { -// HW.shout('rawr', true, function(err, shout) { -// t.ok(err); -// 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) { -// t.ok(function() { W.shout('rawr', {}); }); -// }); \ No newline at end of file +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 From fb18d0b988e159a7783fbf17820c4a604a70dbe5 Mon Sep 17 00:00:00 2001 From: mapsam Date: Sat, 18 Jun 2016 09:39:31 -0700 Subject: [PATCH 6/7] set baton result --- Makefile | 2 ++ src/hello_world.cpp | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index e69de29..477b65a 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,2 @@ +default: + npm install --build-from-source \ No newline at end of file diff --git a/src/hello_world.cpp b/src/hello_world.cpp index 104f7d8..8a99f1d 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -130,14 +130,16 @@ NAN_METHOD(HelloWorld::shout) baton->louder = louder; baton->cb.Reset(callback.As()); - // this is the all-important way to pass info into the threadpool using uv_queue_work - // it cannot take v8 objects, so we need to do some manipulation above to convert into cpp objects - // otherwise things get janky - // 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` 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; } @@ -150,13 +152,14 @@ void HelloWorld::AsyncShout(uv_work_t* req) /***************** custom code here ******************/ try { - std::string return_string = baton->phrase + "!"; if (baton->louder) { return_string += "!!!!"; } + + baton->result = return_string; } catch (std::exception const& ex) { From f17066be37f51d4135ea6f87c25e8a33bfec9114 Mon Sep 17 00:00:00 2001 From: mapsam Date: Mon, 20 Jun 2016 12:16:58 -0700 Subject: [PATCH 7/7] generate API.md --- API.md | 23 +++++++++++++++++++++++ Makefile | 2 ++ package.json | 4 +++- src/hello_world.cpp | 21 +++++++++++++++++---- 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..734e837 --- /dev/null +++ b/API.md @@ -0,0 +1,23 @@ +# HelloWorld + +HelloWorld main class + +**Examples** + +```javascript +var HelloWorld = require('index.js'); +var HW = new HelloWorld(); +``` + +## wave + +Say howdy + +**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 diff --git a/Makefile b/Makefile index e69de29..5d5b063 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,2 @@ +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..0d5bf09 100644 --- a/src/hello_world.cpp +++ b/src/hello_world.cpp @@ -3,6 +3,13 @@ #include #include +/** + * HelloWorld main class + * @class HelloWorld + * @example + * var HelloWorld = require('index.js'); + * var HW = new HelloWorld(); + */ NAN_METHOD(HelloWorld::New) { if (info.IsConstructCall()) @@ -26,6 +33,15 @@ NAN_METHOD(HelloWorld::New) } } +/** + * Say howdy + * @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 @@ -56,7 +72,4 @@ NAN_MODULE_INIT(HelloWorld::Init) Nan::Set(target, whoami, fn); } -NODE_MODULE(HelloWorld, HelloWorld::Init); - - - +NODE_MODULE(HelloWorld, HelloWorld::Init); \ No newline at end of file