Skip to content
This repository has been archived by the owner on Oct 1, 2018. It is now read-only.

Regression fixes #155

Merged
merged 9 commits into from
Aug 5, 2014
32 changes: 13 additions & 19 deletions src/BlueprintParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ namespace snowcrash {

MarkdownNodeIterator cur = node;

if (cur->type == mdp::ParagraphMarkdownNodeType) {
while (cur->type == mdp::ParagraphMarkdownNodeType) {

parseMetadata(node, pd, report, out.metadata);
MetadataCollection metadata;
parseMetadata(cur, pd, report, metadata);

// First block is paragraph and is not metadata (no API name)
if (out.metadata.empty()) {
if (metadata.empty()) {
return processDescription(cur, pd, report, out);
} else {
out.metadata.insert(out.metadata.end(), metadata.begin(), metadata.end());
}

cur++;
Expand Down Expand Up @@ -148,23 +151,14 @@ namespace snowcrash {
Report& report,
Blueprint& out) {

if (out.name.empty()) {

if (pd.options & RequireBlueprintNameOption) {

// ERR: No API name specified
mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData);
report.error = Error(ExpectedAPINameMessage,
BusinessError,
sourceMap);
} else {
if (out.name.empty() &&
(pd.options & RequireBlueprintNameOption)) {

// WARN: No API name
mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData);
report.warnings.push_back(Warning(ExpectedAPINameMessage,
APINameWarning,
sourceMap));
}
// ERR: No API name specified
mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData);
report.error = Error(ExpectedAPINameMessage,
BusinessError,
sourceMap);
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/CodeBlockUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ namespace snowcrash {

content += remainingContent;
content += "\n";

// WARN: Not a preformatted code block but multiline signature
size_t level = codeBlockIndentationLevel(pd.parentSectionContext());
std::stringstream ss;

ss << SectionName(pd.sectionContext());
ss << " is expected to be a pre-formatted code block, separate it by a newline and ";
ss << "indent every of its line by ";
Expand Down Expand Up @@ -212,7 +213,7 @@ namespace snowcrash {
// WARN: Dangling asset
std::stringstream ss;
ss << "Dangling message-body asset, expected a pre-formatted code block, ";
ss << "indent every one of it's line by " << level*4 << " spaces or " << level << " tabs";
ss << "indent every of it's line by " << level*4 << " spaces or " << level << " tabs";

mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData);
report.warnings.push_back(Warning(ss.str(),
Expand Down
17 changes: 16 additions & 1 deletion src/HeadersParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,29 @@ namespace snowcrash {

mdp::ByteBuffer subject = node->children().front().text;
TrimString(subject);

if (RegexMatch(subject, HeadersRegex))
return HeadersSectionType;
}

return UndefinedSectionType;
}

static void finalize(const MarkdownNodeIterator& node,
SectionParserData& pd,
Report& report,
Headers& out) {

if (out.empty()) {

// WARN: No headers defined
mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData);
report.warnings.push_back(Warning("No headers defined in headers section",
EmptyDefinitionWarning,
sourceMap));
}
}

/** Retrieve headers from content */
static void headersFromContent(const MarkdownNodeIterator& node,
const mdp::ByteBuffer& content,
Expand Down
3 changes: 2 additions & 1 deletion src/ParameterParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ namespace snowcrash {
if (node->type == mdp::ListItemMarkdownNodeType
&& !node->children().empty()) {

mdp::ByteBuffer subject = node->children().front().text;
mdp::ByteBuffer subject, remainingContent;
subject = GetFirstLine(node->children().front().text, remainingContent);
TrimString(subject);

if (RegexMatch(subject, ParameterAbbrevDefinitionRegex)) {
Expand Down
35 changes: 28 additions & 7 deletions src/PayloadParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ namespace snowcrash {
if (!remainingContent.empty()) {
if (!isAbbreviated(pd.sectionContext())) {
out.description = remainingContent;
} else {
} else if (!parseSymbolReference(node, pd, remainingContent, report, out)) {
CodeBlockUtility::signatureContentAsCodeBlock(node, pd, report, out.body);
parseSymbolReference(pd, report, out);
}
}

Expand All @@ -83,7 +82,6 @@ namespace snowcrash {
Payload& out) {

mdp::ByteBuffer content;
CodeBlockUtility::contentAsCodeBlock(node, pd, report, content);

if (!out.symbol.empty()) {
//WARN: ignoring extraneous content after symbol reference
Expand All @@ -97,9 +95,14 @@ namespace snowcrash {
IgnoringWarning,
sourceMap));
} else {
out.body += content;

parseSymbolReference(pd, report, out);
if (!out.body.empty() ||
node->type != mdp::ParagraphMarkdownNodeType ||
!parseSymbolReference(node, pd, node->text, report, out)) {

CodeBlockUtility::contentAsCodeBlock(node, pd, report, content);
out.body += content;
}
}

return ++MarkdownNodeIterator(node);
Expand Down Expand Up @@ -454,11 +457,12 @@ namespace snowcrash {
return true;
}

static void parseSymbolReference(SectionParserData& pd,
static bool parseSymbolReference(const MarkdownNodeIterator& node,
SectionParserData& pd,
mdp::ByteBuffer& source,
Report& report,
Payload& out) {

mdp::ByteBuffer source = out.body;
SymbolName symbol;
ResourceModel model;

Expand All @@ -467,14 +471,31 @@ namespace snowcrash {
if (GetSymbolReference(source, symbol)) {
out.symbol = symbol;

// If symbol doesn't exist
if (pd.symbolTable.resourceModels.find(symbol) == pd.symbolTable.resourceModels.end()) {

// ERR: Undefined symbol
std::stringstream ss;
ss << "Undefined symbol " << symbol;

mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData);
report.error = Error(ss.str(), SymbolError, sourceMap);

return true;
}

model = pd.symbolTable.resourceModels.at(symbol);

out.description = model.description;
out.parameters = model.parameters;
out.headers = model.headers;
out.body = model.body;
out.schema = model.schema;

return true;
}

return false;
}
};

Expand Down
5 changes: 4 additions & 1 deletion src/SectionParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ namespace snowcrash {
cur = SectionProcessor<T>::processUnexpectedNode(cur, collection, pd, lastSectionType, report, out);
}

if (pd.sectionContext() != UndefinedSectionType) {
if (pd.sectionContext() != UndefinedSectionType ||
(cur->type != mdp::ParagraphMarkdownNodeType &&
cur->type != mdp::CodeMarkdownNodeType)) {

lastSectionType = pd.sectionContext();
}

Expand Down
43 changes: 37 additions & 6 deletions test/test-BlueprintParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,37 @@ TEST_CASE("Parse canonical blueprint", "[blueprint]")
REQUIRE(blueprint.resourceGroups[1].resources.empty());
}

TEST_CASE("Parse blueprint with multiple metadata sections", "[blueprint]")
{
mdp::ByteBuffer source = "FORMAT: 1A\n\n";
source += BlueprintFixture;

Blueprint blueprint;
Report report;
SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.empty());

REQUIRE(blueprint.metadata.size() == 2);
REQUIRE(blueprint.metadata[0].first == "FORMAT");
REQUIRE(blueprint.metadata[0].second == "1A");
REQUIRE(blueprint.metadata[1].first == "meta");
REQUIRE(blueprint.metadata[1].second == "verse");

REQUIRE(blueprint.name == "Snowcrash API");
REQUIRE(blueprint.description == "## Character\n\nUncle Enzo\n\n");
REQUIRE(blueprint.resourceGroups.size() == 2);

REQUIRE(blueprint.resourceGroups[0].name == "First");
REQUIRE(blueprint.resourceGroups[0].description == "p1\n");
REQUIRE(blueprint.resourceGroups[0].resources.size() == 1);

REQUIRE(blueprint.resourceGroups[1].name == "Second");
REQUIRE(blueprint.resourceGroups[1].description == "p2\n");
REQUIRE(blueprint.resourceGroups[1].resources.empty());
}

TEST_CASE("Parse API with Name and abbreviated resource", "[blueprint]")
{
mdp::ByteBuffer source = \
Expand Down Expand Up @@ -115,7 +146,7 @@ TEST_CASE("Parse nameless blueprint description", "[blueprint]")
SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 1); // expected API name
REQUIRE(report.warnings.empty());

REQUIRE(blueprint.name.empty());
REQUIRE(blueprint.description == "A\n\n# B\n");
Expand All @@ -131,7 +162,7 @@ TEST_CASE("Parse nameless blueprint with a list description", "[blueprint]")
SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 1); // expected API name
REQUIRE(report.warnings.empty());

REQUIRE(blueprint.name.empty());
REQUIRE(blueprint.description == "+ List\n");
Expand Down Expand Up @@ -175,7 +206,7 @@ TEST_CASE("Test parser options - required blueprint name", "[blueprint]")

SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint);
REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 1); // expected API name
REQUIRE(report.warnings.empty());

SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint, Symbols(), RequireBlueprintNameOption);
REQUIRE(report.error.code != Error::OK);
Expand Down Expand Up @@ -243,7 +274,7 @@ TEST_CASE("Blueprint starting with Resource Group should be parsed", "[blueprint
SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 1); // expected API name
REQUIRE(report.warnings.empty());

REQUIRE(blueprint.name.empty());
REQUIRE(blueprint.description.empty());
Expand All @@ -262,7 +293,7 @@ TEST_CASE("Blueprint starting with Resource should be parsed", "[blueprint]")
SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 1); // expected API name
REQUIRE(report.warnings.empty());

REQUIRE(blueprint.name.empty());
REQUIRE(blueprint.description.empty());
Expand All @@ -286,7 +317,7 @@ TEST_CASE("Checking a resource with global resources for duplicates", "[blueprin
SectionParserHelper<Blueprint, BlueprintParser>::parse(source, BlueprintSectionType, report, blueprint, Symbols(), 0, &blueprint);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 4); // expected API name & 2x no response & duplicate resource
REQUIRE(report.warnings.size() == 3); // 2x no response & duplicate resource

REQUIRE(blueprint.name.empty());
REQUIRE(blueprint.description.empty());
Expand Down
16 changes: 15 additions & 1 deletion test/test-HeadersParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ TEST_CASE("parse malformed headers fixture", "[headers]")
REQUIRE(headers[1].second == "Hello World!");
}

TEST_CASE("hparser/parse-multiple-blocks", "Parse header section composed of multiple blocks")
TEST_CASE("Parse header section composed of multiple blocks", "[headers]")
{
// Blueprint in question:
//R"(
Expand Down Expand Up @@ -116,3 +116,17 @@ TEST_CASE("hparser/parse-multiple-blocks", "Parse header section composed of mul
REQUIRE(headers[2].first == "X-My-Header");
REQUIRE(headers[2].second == "42");
}

TEST_CASE("Parse header section with missing headers", "[headers]")
{
mdp::ByteBuffer source = "+ Headers\n\n";

Headers headers;
Report report;
SectionParserHelper<Headers, HeadersParser>::parse(source, HeadersSectionType, report, headers);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.size() == 1); // no headers

REQUIRE(headers.size() == 0);
}
36 changes: 36 additions & 0 deletions test/test-ParametersParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,39 @@ TEST_CASE("Warn about multiple parameters with the same name", "[parameters]")
REQUIRE(parameters[0].name == "id");
REQUIRE(parameters[0].exampleValue == "43");
}

TEST_CASE("Recognize parameter when there is no description on its signature and remaining description is not a new node", "[parameters]")
{
mdp::ByteBuffer source = \
"+ Parameters\n\n"\
" + id (number) ... The ID number of the car\n"\
" + state (string)\n"\
" The desired state of the panoramic roof. The approximate percent open values for each state are `open` = 100%, `close` = 0%, `comfort` = 80%, and `vent` = ~15%\n"\
" + Values\n"\
" + `open`\n"\
" + `close`\n"\
" + `comfort`\n"\
" + `vent`";

Parameters parameters;
Report report;
SectionParserHelper<Parameters, ParametersParser>::parse(source, ParametersSectionType, report, parameters);

REQUIRE(report.error.code == Error::OK);
REQUIRE(report.warnings.empty());

REQUIRE(parameters.size() == 2);
REQUIRE(parameters[0].name == "id");
REQUIRE(parameters[0].type == "number");
REQUIRE(parameters[0].description == "The ID number of the car");

Parameter parameter = parameters[1];
REQUIRE(parameter.name == "state");
REQUIRE(parameter.type == "string");
REQUIRE(parameter.description == "\nThe desired state of the panoramic roof. The approximate percent open values for each state are `open` = 100%, `close` = 0%, `comfort` = 80%, and `vent` = ~15%\n\n");
REQUIRE(parameter.values.size() == 4);
REQUIRE(parameter.values[0] == "open");
REQUIRE(parameter.values[1] == "close");
REQUIRE(parameter.values[2] == "comfort");
REQUIRE(parameter.values[3] == "vent");
}
Loading