diff --git a/README.md b/README.md index d61f90b..9527e01 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,16 @@ protagonist.parse('# My API', function(error, result) { }); ``` +### Synchronous usage + +If you for some reason need to use the api synchronous, that is also possible. +Please note that the recommended way is to use the asynchronous api as to not +block the event loop. + +```js +var result = protagonist.parseSync('# My API'); +``` + ### Parsing Result Parsing this blueprint: diff --git a/binding.gyp b/binding.gyp index fa0c357..64d31eb 100644 --- a/binding.gyp +++ b/binding.gyp @@ -16,7 +16,9 @@ ], "sources": [ "src/annotation.cc", - "src/parse.cc", + "src/options_parser.cc", + "src/parse_async.cc", + "src/parse_sync.cc", "src/protagonist.cc", "src/protagonist.h", "src/result.cc", diff --git a/src/options_parser.cc b/src/options_parser.cc new file mode 100644 index 0000000..06a43cc --- /dev/null +++ b/src/options_parser.cc @@ -0,0 +1,50 @@ +#include "protagonist.h" +#include "snowcrash.h" + +using namespace v8; +using namespace protagonist; + +//static const std::string RenderDescriptionsOptionKey = "renderDescriptions"; +static const std::string RequireBlueprintNameOptionKey = "requireBlueprintName"; +static const std::string ExportSourcemapOptionKey = "exportSourcemap"; + +OptionsResult* protagonist::ParseOptionsObject(Handle optionsObject) { + OptionsResult *optionsResult = (OptionsResult *) malloc(sizeof(OptionsResult)); + + optionsResult->options = 0; + optionsResult->error = NULL; + + const Local properties = optionsObject->GetPropertyNames(); + const uint32_t length = properties->Length(); + + for (uint32_t i = 0 ; i < length ; ++i) { + const Local key = properties->Get(i); + const Local value = optionsObject->Get(key); + + if (RequireBlueprintNameOptionKey == *String::Utf8Value(key)) { + // RequireBlueprintNameOption + if (value->IsTrue()) + optionsResult->options |= snowcrash::RequireBlueprintNameOption; + else + optionsResult->options &= snowcrash::RequireBlueprintNameOption; + } + else if (ExportSourcemapOptionKey == *String::Utf8Value(key)) { + // ExportSourcemapOption + if (value->IsTrue()) + optionsResult->options |= snowcrash::ExportSourcemapOption; + else + optionsResult->options &= snowcrash::ExportSourcemapOption; + } + else { + // Unrecognized option + std::stringstream ss; + ss << "unrecognized option '" << *String::Utf8Value(key) << "', expected: "; + ss << "'" << RequireBlueprintNameOptionKey << "' or '" << ExportSourcemapOptionKey << "'"; + + optionsResult->error = ss.str().c_str(); + return optionsResult; + } + } + + return optionsResult; +} diff --git a/src/parse.cc b/src/parse_async.cc similarity index 66% rename from src/parse.cc rename to src/parse_async.cc index 5ee7111..07d1549 100644 --- a/src/parse.cc +++ b/src/parse_async.cc @@ -8,10 +8,6 @@ using std::string; using namespace v8; using namespace protagonist; -//static const std::string RenderDescriptionsOptionKey = "renderDescriptions"; -static const std::string RequireBlueprintNameOptionKey = "requireBlueprintName"; -static const std::string ExportSourcemapOptionKey = "exportSourcemap"; - // Async Parse void AsyncParse(uv_work_t* request); @@ -58,8 +54,8 @@ NAN_METHOD(protagonist::Parse) { if (args.Length() == 3 && !args[1]->IsObject()) { NanThrowTypeError("wrong argument - object expected, `parse(string, options, callback)`"); NanReturnUndefined(); - } - + } + // Get source data String::Utf8Value sourceData(args[0]->ToString()); @@ -67,38 +63,15 @@ NAN_METHOD(protagonist::Parse) { snowcrash::BlueprintParserOptions options = 0; if (args.Length() == 3) { - Handle optionsObject = Handle::Cast(args[1]); - const Local properties = optionsObject->GetPropertyNames(); - const uint32_t length = properties->Length(); - - for (uint32_t i = 0 ; i < length ; ++i) { - const Local key = properties->Get(i); - const Local value = optionsObject->Get(key); - - if (RequireBlueprintNameOptionKey == *String::Utf8Value(key)) { - // RequireBlueprintNameOption - if (value->IsTrue()) - options |= snowcrash::RequireBlueprintNameOption; - else - options &= snowcrash::RequireBlueprintNameOption; - } - else if (ExportSourcemapOptionKey == *String::Utf8Value(key)) { - // ExportSourcemapOption - if (value->IsTrue()) - options |= snowcrash::ExportSourcemapOption; - else - options &= snowcrash::ExportSourcemapOption; - } - else { - // Unrecognized option - std::stringstream ss; - ss << "unrecognized option '" << *String::Utf8Value(key) << "', expected: "; - ss << "'" << RequireBlueprintNameOptionKey << "' or '" << ExportSourcemapOptionKey << "'"; - - NanThrowTypeError(ss.str().c_str()); - NanReturnUndefined(); - } - } + OptionsResult *optionsResult = ParseOptionsObject(Handle::Cast(args[1])); + + if (optionsResult->error != NULL) { + NanThrowTypeError(optionsResult->error); + NanReturnUndefined(); + } + + options = optionsResult->options; + free(optionsResult); } // Get Callback @@ -115,14 +88,14 @@ NAN_METHOD(protagonist::Parse) { request->data = baton; // Schedule the work request - int status = uv_queue_work(uv_default_loop(), - request, + int status = uv_queue_work(uv_default_loop(), + request, AsyncParse, (uv_after_work_cb)AsyncParseAfter); assert(status == 0); NanReturnUndefined(); -} +} void AsyncParse(uv_work_t* request) { Baton* baton = static_cast(request->data); diff --git a/src/parse_sync.cc b/src/parse_sync.cc new file mode 100644 index 0000000..f1e2ae9 --- /dev/null +++ b/src/parse_sync.cc @@ -0,0 +1,58 @@ +#include +#include +#include "protagonist.h" +#include "snowcrash.h" +#include "drafter.h" + +using std::string; +using namespace v8; +using namespace protagonist; + +NAN_METHOD(protagonist::ParseSync) { + NanScope(); + + // Check arguments + if (args.Length() != 1 && args.Length() != 2) { + NanThrowTypeError("wrong number of arguments, `parseSync(string, options)` expected"); + NanReturnUndefined(); + } + + if (!args[0]->IsString()) { + NanThrowTypeError("wrong argument - string expected, `parseSync(string, options)`"); + NanReturnUndefined(); + } + + if (args.Length() == 2 && !args[1]->IsObject()) { + NanThrowTypeError("wrong argument - object expected, `parseSync(string, options)`"); + NanReturnUndefined(); + } + + // Get source data + String::Utf8Value sourceData(args[0]->ToString()); + + // Prepare options + snowcrash::BlueprintParserOptions options = 0; + + if (args.Length() == 2) { + OptionsResult *optionsResult = ParseOptionsObject(Handle::Cast(args[1])); + + if (optionsResult->error != NULL) { + NanThrowTypeError(optionsResult->error); + NanReturnUndefined(); + } + + options = optionsResult->options; + free(optionsResult); + } + + // Parse the source data + snowcrash::ParseResult parseResult; + drafter::ParseBlueprint(*sourceData, options, parseResult); + + if (parseResult.report.error.code != snowcrash::Error::OK) { + NanThrowError(SourceAnnotation::WrapSourceAnnotation(parseResult.report.error)); + NanReturnUndefined(); + } + + NanReturnValue(Result::WrapResult(parseResult.report, parseResult.node, parseResult.sourceMap, options)); +} diff --git a/src/protagonist.cc b/src/protagonist.cc index 430c0d5..22217ec 100644 --- a/src/protagonist.cc +++ b/src/protagonist.cc @@ -13,6 +13,7 @@ void Init(Handle exports) { // Parse function exports->Set(NanNew("parse"), NanNew(Parse)->GetFunction()); + exports->Set(NanNew("parseSync"), NanNew(ParseSync)->GetFunction()); } NODE_MODULE(protagonist, Init) diff --git a/src/protagonist.h b/src/protagonist.h index 3c860f9..aeb86eb 100644 --- a/src/protagonist.h +++ b/src/protagonist.h @@ -11,6 +11,16 @@ namespace protagonist { + // + // Options parsing + /// + struct OptionsResult { + snowcrash::BlueprintParserOptions options; + const char *error; + }; + + OptionsResult* ParseOptionsObject(v8::Handle); + // // SourceAnnotation // @@ -55,6 +65,7 @@ namespace protagonist { // Parse function // extern NAN_METHOD(Parse); + extern NAN_METHOD(ParseSync); } #endif diff --git a/test/ast-test.coffee b/test/ast-test.coffee index b1f492a..6b0f43e 100644 --- a/test/ast-test.coffee +++ b/test/ast-test.coffee @@ -3,7 +3,7 @@ path = require 'path' {assert} = require 'chai' protagonist = require '../build/Release/protagonist' -describe "Parser AST", -> +describe "Parser AST - Async", -> ast_fixture = require './fixtures/sample-api-ast.json' ast_parsed = null @@ -24,4 +24,4 @@ describe "Parser AST", -> # Parser AST should conform to AST serialization JSON media type it '`ast` field conforms to `vnd.apiblueprint.ast.raw+json; version=3.0`', -> - assert.deepEqual ast_parsed, ast_parsed + assert.deepEqual ast_parsed, ast_fixture diff --git a/test/fixtures/sample-api-ast.json b/test/fixtures/sample-api-ast.json index bfe1485..627c85c 100644 --- a/test/fixtures/sample-api-ast.json +++ b/test/fixtures/sample-api-ast.json @@ -47,6 +47,10 @@ "description": "", "method": "GET", "parameters": [], + "attributes": { + "relation": "", + "uriTemplate": "" + }, "content": [], "examples": [ { @@ -68,10 +72,7 @@ "content": [ { "element": "dataStructure", - "name": { - "literal": "", - "variable": false - }, + "name": null, "typeDefinition": { "typeSpecification": { "name": { @@ -95,6 +96,10 @@ "description": "", "method": "DELETE", "parameters": [], + "attributes": { + "relation": "", + "uriTemplate": "" + }, "content": [], "examples": [ { @@ -124,10 +129,7 @@ }, "typeDefinition": { "typeSpecification": { - "name": { - "literal": "", - "variable": false - }, + "name": null, "nestedTypes": [] }, "attributes": [] @@ -230,6 +232,10 @@ "description": "", "method": "GET", "parameters": [], + "attributes": { + "relation": "", + "uriTemplate": "" + }, "content": [], "examples": [ { @@ -251,10 +257,7 @@ "content": [ { "element": "dataStructure", - "name": { - "literal": "", - "variable": false - }, + "name": null, "typeDefinition": { "typeSpecification": { "name": { @@ -278,6 +281,10 @@ "description": "", "method": "DELETE", "parameters": [], + "attributes": { + "relation": "", + "uriTemplate": "" + }, "content": [], "examples": [ { @@ -307,10 +314,7 @@ }, "typeDefinition": { "typeSpecification": { - "name": { - "literal": "", - "variable": false - }, + "name": null, "nestedTypes": [] }, "attributes": [] diff --git a/test/sync-test.coffee b/test/sync-test.coffee new file mode 100644 index 0000000..e08bd25 --- /dev/null +++ b/test/sync-test.coffee @@ -0,0 +1,24 @@ +fs = require 'fs' +path = require 'path' +{assert} = require 'chai' +protagonist = require '../build/Release/protagonist' + +describe "Parser AST - Sync", -> + + ast_fixture = require './fixtures/sample-api-ast.json' + ast_parsed = null + + # Read & parse blueprint fixture + before (done) -> + + fixture_path = path.join __dirname, './fixtures/sample-api.apib' + + fs.readFile fixture_path, 'utf8', (err, data) -> + return done err if err + + ast_parsed = protagonist.parseSync(data).ast + done() + + # Parser AST should conform to AST serialization JSON media type + it '`ast` field conforms to `vnd.apiblueprint.ast.raw+json; version=3.0`', -> + assert.deepEqual ast_parsed, ast_fixture