diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 29cf5d6..144e73c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,8 +12,8 @@ jobs: - uses: actions/checkout@v6 - uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" + cmakeVersion: "~4.2.0" # <--= optional, use most recent 4.2.x version + ninjaVersion: "^1.11.1" # <--= optional, use most recent 1.x version - name: build run: | mkdir tmp @@ -39,13 +39,13 @@ jobs: - uses: actions/checkout@v6 - uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" + cmakeVersion: "~4.2.0" # <--= optional, use most recent 4.2.x version + ninjaVersion: "^1.11.1" # <--= optional, use most recent 1.x version - name: build run: | mkdir tmp cd tmp - cmake -G "Visual Studio 17 2022" -A x64 -DMADDY_BUILD_WITH_TESTS=ON .. + cmake -G "Visual Studio 18 2026" -A x64 -DMADDY_BUILD_WITH_TESTS=ON .. cmake --build . --config Debug - name: run tests run: | @@ -57,8 +57,8 @@ jobs: - uses: actions/checkout@v6 - uses: lukka/get-cmake@latest with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" + cmakeVersion: "~4.2.0" # <--= optional, use most recent 4.2.x version + ninjaVersion: "^1.11.1" # <--= optional, use most recent 1.x version - name: build run: | mkdir tmp diff --git a/CHANGELOG.md b/CHANGELOG.md index a16cc40..c34c026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ maddy uses [semver versioning](https://semver.org/). ## Upcoming +* ![**ADDED**](https://img.shields.io/badge/-ADDED-%23099) New `maddy::types::MADDY_SPECIFIC_PARSER` config flag (on by default, keeping current behavior). Turning it off switches `TableParser` from maddy's own `|table>` sigil syntax to standard GitHub-flavored-Markdown pipe tables (which have no footer concept). +* ![**FIXED**](https://img.shields.io/badge/-FIXED-%23090) Only create strong and emphasis tags at word boundaries, i.e. `not only_internal_underscores`. +* ![**FIXED**](https://img.shields.io/badge/-FIXED-%23090) Correctly leave any leftover strong or emphasis delimiters outside the tag on either side, however many there are, i.e. `___text__` becomes `_text` and `_text_______` becomes `text______`. * ... ## version 1.6.0 2025-07-26 diff --git a/docs/definitions.md b/docs/definitions.md index 1a3800e..81d0e79 100644 --- a/docs/definitions.md +++ b/docs/definitions.md @@ -385,6 +385,53 @@ becomes ``` table header and footer are optional +This is maddy's own historical table syntax, and it's used by default +(`maddy::types::MADDY_SPECIFIC_PARSER`). If you need standard +GitHub-flavored-Markdown pipe tables instead, disable it in config: + +```cpp +std::shared_ptr config = std::make_shared(); +config->enabledParsers &= ~maddy::types::MADDY_SPECIFIC_PARSER; + +std::shared_ptr parser = std::make_shared(config); +std::string htmlOutput = parser->Parse(markdownInput); +``` + +After this, tables look like: + +``` +| Left header | middle header | last header | +| --- | --- | --- | +| cell 1 | cell 2 | cell 3 | +| cell 4 | cell 5 | cell 6 | +``` +becomes +```html + + + + + + + + + + + + + + + + + + + + +
Left headermiddle headerlast header
cell 1cell 2cell 3
cell 4cell 5cell 6
+``` +GFM pipe tables have no footer concept, so this mode never produces a +``. + ## LaTeX(MathJax) block support To turn on the LaTeX support - which basically is only a diff --git a/include/maddy/emphasizedparser.h b/include/maddy/emphasizedparser.h index 1705e6f..377e674 100644 --- a/include/maddy/emphasizedparser.h +++ b/include/maddy/emphasizedparser.h @@ -40,10 +40,13 @@ class EmphasizedParser : public LineParser */ void Parse(std::string& line) override { + // The leading and trailing `(_*)` groups absorb any leftover underscores + // from an unbalanced run (e.g. `__foo_` or `_foo____`), re-emitted + // outside the tag instead of into its content. static std::regex re( - R"((?!.*`.*|.*.*)_(?!.*`.*|.*<\/code>.*)([^_]*)_(?!.*`.*|.*<\/code>.*))" + R"((?!.*`.*|.*.*)\b(_*)_(?![\s_])(?!.*`.*|.*<\/code>.*)(.*?[^\s])_(_*)\b(?!.*`.*|.*<\/code>.*))" ); - static std::string replacement = "$1"; + static std::string replacement = "$1$2$3"; line = std::regex_replace(line, re, replacement); } diff --git a/include/maddy/parser.h b/include/maddy/parser.h index 660752f..a3c8a18 100644 --- a/include/maddy/parser.h +++ b/include/maddy/parser.h @@ -59,7 +59,7 @@ class Parser */ static const std::string& version() { - static const std::string v = "1.6.0"; // MADDY_VERSION_LINE_REPLACEMENT + static const std::string v = "1.5.0"; return v; } @@ -278,10 +278,17 @@ class Parser } else if ((!this->config || (this->config->enabledParsers & maddy::types::TABLE_PARSER) != 0) && - maddy::TableParser::IsStartingLine(line)) + maddy::TableParser::IsStartingLine( + line, + !this->config || (this->config->enabledParsers & + maddy::types::MADDY_SPECIFIC_PARSER) != 0 + )) { parser = std::make_shared( - [this](std::string& line) { this->runLineParser(line); }, nullptr + [this](std::string& line) { this->runLineParser(line); }, + nullptr, + !this->config || (this->config->enabledParsers & + maddy::types::MADDY_SPECIFIC_PARSER) != 0 ); } else if ((!this->config || (this->config->enabledParsers & diff --git a/include/maddy/parserconfig.h b/include/maddy/parserconfig.h index f95ee04..74ab2c9 100644 --- a/include/maddy/parserconfig.h +++ b/include/maddy/parserconfig.h @@ -44,8 +44,13 @@ enum PARSER_TYPE : uint32_t UNORDERED_LIST_PARSER = 0b100000000000000000, LATEX_BLOCK_PARSER = 0b1000000000000000000, - DEFAULT = 0b0111111111110111111, - ALL = 0b1111111111111111111, + // Not a parser of its own: gates maddy's own historical markdown dialect + // wherever a parser supports both that and a more standard alternative + // (currently just TableParser's `|table>` syntax vs. GFM pipe tables). + MADDY_SPECIFIC_PARSER = 0b10000000000000000000, + + DEFAULT = 0b10111111111110111111, + ALL = 0b11111111111111111111, }; // clang-format on diff --git a/include/maddy/strongparser.h b/include/maddy/strongparser.h index 348f2d4..b6c0de7 100644 --- a/include/maddy/strongparser.h +++ b/include/maddy/strongparser.h @@ -40,19 +40,21 @@ class StrongParser : public LineParser */ void Parse(std::string& line) override { - static std::vector res{ - std::regex{ - R"((?!.*`.*|.*.*)\*\*(?!.*`.*|.*<\/code>.*)([^\*\*]*)\*\*(?!.*`.*|.*<\/code>.*))" - }, - std::regex{ - R"((?!.*`.*|.*.*)__(?!.*`.*|.*<\/code>.*)([^__]*)__(?!.*`.*|.*<\/code>.*))" - } + // `*` is not a word character, so `\b` next to it does not mean + // "edge of a delimiter run" the way it does for `_`; the asterisk + // variant is left without a word-boundary anchor. + static std::regex reAsterisk{ + R"((?!.*`.*|.*.*)\*\*(?![\s])(?!.*`.*|.*<\/code>.*)(.*?[^\s])\*\*(?!.*`.*|.*<\/code>.*))" }; - static std::string replacement = "$1"; - for (const auto& re : res) - { - line = std::regex_replace(line, re, replacement); - } + // The leading and trailing `(_*)` groups absorb any leftover underscores + // from an unbalanced run on either side (e.g. `___text__` or + // `__text_______`), re-emitted outside the tag by the caller + // instead of being swallowed into its content. + static std::regex reUnderscore{ + R"((?!.*`.*|.*.*)\b(_*)__(?![\s_])(?!.*`.*|.*<\/code>.*)(.*?[^\s])__(_*)\b(?!.*`.*|.*<\/code>.*))" + }; + line = std::regex_replace(line, reAsterisk, "$1"); + line = std::regex_replace(line, reUnderscore, "$1$2$3"); } }; // class StrongParser diff --git a/include/maddy/tableparser.h b/include/maddy/tableparser.h index 9f1051f..7f7e6bc 100644 --- a/include/maddy/tableparser.h +++ b/include/maddy/tableparser.h @@ -7,10 +7,14 @@ // ----------------------------------------------------------------------------- #include +#include #include +#include #include +#include #include "maddy/blockparser.h" +#include "maddy/paragraphparser.h" // ----------------------------------------------------------------------------- @@ -21,7 +25,37 @@ namespace maddy { /** * TableParser * - * For more information, see the docs folder. + * Supports two independent syntaxes, chosen with `useMaddySpecificMarkdown` + * (see `maddy::types::MADDY_SPECIFIC_PARSER`). + * + * When true (the default, and maddy's original behavior), a table uses + * maddy's own sigils: + * + * ``` + * |table> + * Left header|middle header|last header + * - | - | - + * Cell A1|Cell B1|Cell C1 + * - | - | - + * Foot A|Foot B|Foot C + * |`. Since `IsStartingLine` only sees one line at a time in this + * mode, it can't yet tell a table header from an ordinary line that happens + * to contain a `|`; if the following line isn't a valid separator row, both + * lines are handed off to a ParagraphParser instead. * * @class */ @@ -35,32 +69,47 @@ class TableParser : public BlockParser * @param {std::function} parseLineCallback * @param {std::function(const std::string& * line)>} getBlockParserForLineCallback + * @param {bool} useMaddySpecificMarkdown */ TableParser( std::function parseLineCallback, std::function(const std::string& line)> - getBlockParserForLineCallback + getBlockParserForLineCallback, + bool useMaddySpecificMarkdown = true ) : BlockParser(parseLineCallback, getBlockParserForLineCallback) + , useMaddySpecificMarkdown(useMaddySpecificMarkdown) , isStarted(false) , isFinished(false) , currentBlock(0) , currentRow(0) + , gfmState(GfmState::EXPECT_HEADER) {} /** * IsStartingLine * - * If the line has exact `|table>`, then it is starting the table. + * With maddy-specific markdown, a table starts with exact `|table>`. + * With GFM markdown, a table can only start with a row that has at least + * one `|` separating two cells; whether it really is a table is only + * known once the following line (the separator row) has been seen. * * @method * @param {const std::string&} line + * @param {bool} useMaddySpecificMarkdown * @return {bool} */ - static bool IsStartingLine(const std::string& line) + static bool IsStartingLine( + const std::string& line, bool useMaddySpecificMarkdown = true + ) { - static std::string matchString("|table>"); - return line == matchString; + if (useMaddySpecificMarkdown) + { + static std::string matchString("|table>"); + return line == matchString; + } + + return IsTableRow(line); } /** @@ -74,52 +123,21 @@ class TableParser : public BlockParser */ void AddLine(std::string& line) override { - if (!this->isStarted && line == "|table>") + if (this->useMaddySpecificMarkdown) { - this->isStarted = true; - return; + this->AddLineMaddyStyle(line); } - - if (this->isStarted) + else { - if (line == "- | - | -") - { - ++this->currentBlock; - this->currentRow = 0; - return; - } - - if (line == "|parseBlock(emptyLine); - this->isFinished = true; - return; - } - - if (this->table.size() < this->currentBlock + 1) - { - this->table.push_back(std::vector>()); - } - this->table[this->currentBlock].push_back(std::vector()); - - std::string segment; - std::stringstream streamToSplit(line); - - while (std::getline(streamToSplit, segment, '|')) - { - this->parseLine(segment); - this->table[this->currentBlock][this->currentRow].push_back(segment); - } - - ++this->currentRow; + this->AddLineGfm(line); } } /** * IsFinished * - * A table ends with `|"; @@ -221,11 +241,223 @@ class TableParser : public BlockParser } private: + bool useMaddySpecificMarkdown; + + // --- maddy-specific-markdown mode state --- bool isStarted; bool isFinished; uint32_t currentBlock; uint32_t currentRow; std::vector>> table; + + void AddLineMaddyStyle(std::string& line) + { + if (!this->isStarted && line == "|table>") + { + this->isStarted = true; + return; + } + + if (this->isStarted) + { + if (line == "- | - | -") + { + ++this->currentBlock; + this->currentRow = 0; + return; + } + + if (line == "|parseBlock(emptyLine); + this->isFinished = true; + return; + } + + if (this->table.size() < this->currentBlock + 1) + { + this->table.push_back(std::vector>()); + } + this->table[this->currentBlock].push_back(std::vector()); + + std::string segment; + std::stringstream streamToSplit(line); + + while (std::getline(streamToSplit, segment, '|')) + { + this->parseLine(segment); + this->table[this->currentBlock][this->currentRow].push_back(segment); + } + + ++this->currentRow; + } + } + + // --- GFM-pipe-table mode state --- + enum class GfmState + { + EXPECT_HEADER, + EXPECT_SEPARATOR, + IN_BODY + }; + + GfmState gfmState; + std::string headerLine; + std::shared_ptr fallbackParser; + + void AddLineGfm(std::string& line) + { + if (this->fallbackParser) + { + this->fallbackParser->AddLine(line); + + if (this->fallbackParser->IsFinished()) + { + this->result << this->fallbackParser->GetResult().str(); + this->isFinished = true; + } + + return; + } + + switch (this->gfmState) + { + case GfmState::EXPECT_HEADER: + this->headerLine = line; + this->gfmState = GfmState::EXPECT_SEPARATOR; + return; + + case GfmState::EXPECT_SEPARATOR: + if (IsSeparatorRow(line) && + SplitRow(line).size() == SplitRow(this->headerLine).size()) + { + this->WriteGfmHeader(); + this->gfmState = GfmState::IN_BODY; + } + else + { + this->FallBackToParagraph(line); + } + return; + + case GfmState::IN_BODY: + if (line.empty()) + { + this->result << "
"; + this->isFinished = true; + } + else + { + this->WriteGfmRow(line); + } + return; + } + } + + static bool IsTableRow(const std::string& line) + { + return line.find('|') != std::string::npos && + line.find_first_not_of(" \t") != std::string::npos; + } + + static bool IsSeparatorRow(const std::string& line) + { + if (!IsTableRow(line)) + { + return false; + } + + static const std::regex cellRe("^:?-+:?$"); + + for (const std::string& cell : SplitRow(line)) + { + if (!std::regex_match(cell, cellRe)) + { + return false; + } + } + + return true; + } + + static std::vector SplitRow(const std::string& line) + { + std::vector cells; + std::stringstream stream(line); + std::string cell; + + while (std::getline(stream, cell, '|')) + { + Trim(cell); + + if (!cell.empty()) + { + cells.push_back(cell); + } + } + + return cells; + } + + static void Trim(std::string& str) + { + size_t first = str.find_first_not_of(" \t"); + + if (first == std::string::npos) + { + str.clear(); + return; + } + + size_t last = str.find_last_not_of(" \t"); + str = str.substr(first, last - first + 1); + } + + void WriteGfmHeader() + { + this->result << ""; + + for (std::string cell : SplitRow(this->headerLine)) + { + this->parseLine(cell); + this->result << ""; + } + + this->result << ""; + } + + void WriteGfmRow(const std::string& line) + { + this->result << ""; + + for (std::string cell : SplitRow(line)) + { + this->parseLine(cell); + this->result << ""; + } + + this->result << ""; + } + + void FallBackToParagraph(const std::string& secondLine) + { + this->fallbackParser = std::make_shared( + [this](std::string& l) { this->parseLine(l); }, nullptr, true + ); + + std::string first = this->headerLine; + this->fallbackParser->AddLine(first); + + std::string second = secondLine; + this->fallbackParser->AddLine(second); + + if (this->fallbackParser->IsFinished()) + { + this->result << this->fallbackParser->GetResult().str(); + this->isFinished = true; + } + } }; // class TableParser // ----------------------------------------------------------------------------- diff --git a/tests/maddy/test_maddy_emphasizedparser.cpp b/tests/maddy/test_maddy_emphasizedparser.cpp index 6442779..9e1cd52 100644 --- a/tests/maddy/test_maddy_emphasizedparser.cpp +++ b/tests/maddy/test_maddy_emphasizedparser.cpp @@ -21,6 +21,121 @@ TEST(MADDY_EMPHASIZEDPARSER, ItReplacesMarkdownWithEmphasizedHTML) ASSERT_EQ(expected, text); } +TEST(MADDY_EMPHASIZEDPARSER, ItReplacesUnderscoresAtStringEdges) +{ + std::string text = "_some text_"; + std::string expected = "some text"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotReplaceMarkdownWithInlineUnderscores) +{ + std::string text = "some text_bla_text testing _it_ out"; + std::string expected = "some text_bla_text testing it out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItOnlyReplacesUnderscoresAtWordBreaks) +{ + std::string text = "some _text_bla_ testing _it_ out"; + std::string expected = "some text_bla testing it out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItReplacesUnderscoresWithMultipleWords) +{ + std::string text = "some _text testing it_ out"; + std::string expected = "some text testing it out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItAllowsDoubleUnderscores) +{ + // Per CommonMark, a leftover delimiter from an unbalanced run renders + // outside the tag it didn't pair into, not inside it. + std::string text = "some __text testing it_ out"; + std::string expected = "some _text testing it out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItAllowsTrailingDoubleUnderscores) +{ + std::string text = "some _text testing it__ out"; + std::string expected = "some text testing it_ out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItAllowsManyLeadingLeftoverUnderscores) +{ + std::string text = "some ____text testing it_ out"; + std::string expected = "some ___text testing it out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItAllowsManyTrailingLeftoverUnderscores) +{ + std::string text = "some _text testing it____ out"; + std::string expected = "some text testing it___ out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItDoesntReplaceUnderscoresInsideCodeBlocks) +{ + std::string text = + "Stuff inside blocks _shouldn't be emphasized_ at all"; + std::string expected = + "Stuff inside blocks _shouldn't be emphasized_ at all"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotReplaceUnderscoresInURLs) +{ + std::string text = "[Link Title](http://example.com/what_you_didn't_know)"; + std::string expected = + "[Link Title](http://example.com/what_you_didn't_know)"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotParseInsideInlineCode) { std::string text = "some text `*bla*` `/**text*/` testing _it_ out"; @@ -32,3 +147,33 @@ TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotParseInsideInlineCode) ASSERT_EQ(expected, text); } + +TEST(MADDY_EMPHASIZEDPARSER, ItParsesOutsideCodeBlocks) +{ + std::string text = + "Stuff inside blocks _shouldn't be emphasized_ " + " but outside _should_."; + std::string expected = + "Stuff inside blocks _shouldn't be emphasized_ " + " but outside should."; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_EMPHASIZEDPARSER, ItParsesOutsideTickBlocks) +{ + std::string text = + "Stuff inside `blocks _shouldn't be emphasized_ `" + " but outside _should_."; + std::string expected = + "Stuff inside `blocks _shouldn't be emphasized_ `" + " but outside should."; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} diff --git a/tests/maddy/test_maddy_parser.cpp b/tests/maddy/test_maddy_parser.cpp index ed8530a..83a20cb 100644 --- a/tests/maddy/test_maddy_parser.cpp +++ b/tests/maddy/test_maddy_parser.cpp @@ -82,3 +82,46 @@ TEST(MADDY_PARSER, ItShouldNotParseInlineCodeInHeadlineIfDisabled) ASSERT_EQ(expectedHTML, output); } + +TEST(MADDY_PARSER, ItShouldParseGfmTablesWhenMaddySpecificParserIsDisabled) +{ + const std::string tableTest = + "| Left header | middle header | last header |\n" + "| --- | --- | --- |\n" + "| cell 1 | cell 2 | cell 3 |\n" + "| cell 4 | cell 5 | cell 6 |\n"; + const std::string expectedHTML = + "
" << cell << "
" << cell << "
Left headermiddle headerlast " + "header
cell 1cell 2cell " + "3
cell 4cell 5cell " + "6
"; + std::stringstream markdown(tableTest); + auto config = std::make_shared(); + config->enabledParsers &= ~maddy::types::MADDY_SPECIFIC_PARSER; + auto parser = std::make_shared(config); + + const std::string output = parser->Parse(markdown); + + ASSERT_EQ(expectedHTML, output); +} + +TEST( + MADDY_PARSER, + ItShouldNotParseMaddySpecificTableSyntaxWhenMaddySpecificParserIsDisabled +) +{ + const std::string tableTest = + "|table>\n" + "A|B\n" + "- | - | -\n" + "1|2\n" + "|(); + config->enabledParsers &= ~maddy::types::MADDY_SPECIFIC_PARSER; + auto parser = std::make_shared(config); + + const std::string output = parser->Parse(markdown); + + ASSERT_EQ(std::string::npos, output.find("")); +} diff --git a/tests/maddy/test_maddy_strongparser.cpp b/tests/maddy/test_maddy_strongparser.cpp index f006e26..1211b0e 100644 --- a/tests/maddy/test_maddy_strongparser.cpp +++ b/tests/maddy/test_maddy_strongparser.cpp @@ -83,3 +83,149 @@ TEST(MADDY_STRONGPARSER, ItDoesNotParseInsideInlineCode) ASSERT_EQ(test.expected, test.text); } } + +TEST(MADDY_STRONGPARSER, ItReplacesUnderscoresAtStringEdges) +{ + std::string text = "__some text__"; + std::string expected = "some text"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItDoesNotReplaceMarkdownWithInlineUnderscores) +{ + std::string text = "some text__bla__text testing __it__ out"; + std::string expected = "some text__bla__text testing it out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItOnlyReplacesUnderscoresAtWordBreaks) +{ + std::string text = "some __text__bla__ testing __it__ out"; + std::string expected = + "some text__bla testing it out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItReplacesUnderscoresWithMultipleWords) +{ + std::string text = "some __text testing it__ out"; + std::string expected = "some text testing it out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItAllowsTripleUnderscores) +{ + // Per CommonMark, a leftover delimiter from an unbalanced run renders + // outside the tag it didn't pair into, not inside it. + std::string text = "some ___text testing it__ out"; + std::string expected = "some _text testing it out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItAllowsTrailingTripleUnderscores) +{ + std::string text = "some __text testing it___ out"; + std::string expected = "some text testing it_ out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItAllowsManyLeadingLeftoverUnderscores) +{ + std::string text = "some ________text testing it__ out"; + std::string expected = "some ______text testing it out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItAllowsManyTrailingLeftoverUnderscores) +{ + std::string text = "some __text testing it_______ out"; + std::string expected = "some text testing it_____ out"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItDoesntReplaceUnderscoresInsideCodeBlocks) +{ + std::string text = + "Stuff inside blocks __shouldn't be strong__ at all"; + std::string expected = + "Stuff inside blocks __shouldn't be strong__ at all"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItDoesNotReplaceUnderscoresInURLs) +{ + std::string text = "[Link Title](http://example.com/what__you__didn't__know)"; + std::string expected = + "[Link Title](http://example.com/what__you__didn't__know)"; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItParsesOutsideCodeBlocks) +{ + std::string text = + "Stuff inside blocks __shouldn't be strong__ " + " but outside __should__."; + std::string expected = + "Stuff inside blocks __shouldn't be strong__ " + " but outside should."; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} + +TEST(MADDY_STRONGPARSER, ItParsesOutsideTickBlocks) +{ + std::string text = + "Stuff inside `blocks __shouldn't be strong__ `" + " but outside __should__."; + std::string expected = + "Stuff inside `blocks __shouldn't be strong__ `" + " but outside should."; + auto strongParser = std::make_shared(); + + strongParser->Parse(text); + + ASSERT_EQ(expected, text); +} diff --git a/tests/maddy/test_maddy_tableparser.cpp b/tests/maddy/test_maddy_tableparser.cpp index 5d50d92..592fd7e 100644 --- a/tests/maddy/test_maddy_tableparser.cpp +++ b/tests/maddy/test_maddy_tableparser.cpp @@ -67,3 +67,98 @@ TEST_F(MADDY_TABLEPARSER, ItReplacesMarkdownWithAnHtmlTable) ASSERT_EQ(expected, outputString); } + +// ----------------------------------------------------------------------------- +// GFM pipe-table mode (useMaddySpecificMarkdown = false) +// ----------------------------------------------------------------------------- + +class MADDY_TABLEPARSER_GFM : public ::testing::Test +{ +protected: + std::shared_ptr tableParser; + + void SetUp() override + { + this->tableParser = + std::make_shared(nullptr, nullptr, false); + } +}; + +TEST_F(MADDY_TABLEPARSER_GFM, IsStartingLineReturnsTrueForAnyLineWithAPipe) +{ + ASSERT_TRUE(maddy::TableParser::IsStartingLine("| a | b |", false)); +} + +TEST_F(MADDY_TABLEPARSER_GFM, IsStartingLineReturnsFalseForABlankLine) +{ + ASSERT_FALSE(maddy::TableParser::IsStartingLine(" ", false)); +} + +TEST_F(MADDY_TABLEPARSER_GFM, IsFinishedReturnsFalseInTheBeginning) +{ + ASSERT_FALSE(tableParser->IsFinished()); +} + +TEST_F(MADDY_TABLEPARSER_GFM, ItReplacesMarkdownWithAnHtmlTableAndHasNoFooter) +{ + std::vector markdown = { + "| Left header | middle header | last header |", + "| --- | --- | --- |", + "| cell 1 | cell 2 | cell 3 |", + "| cell 4 | cell 5 | cell 6 |", + "" + }; + std::string expected = + "
Left headermiddle headerlast " + "header
cell 1cell 2cell " + "3
cell 4cell 5cell " + "6
"; + + for (std::string md : markdown) + { + tableParser->AddLine(md); + } + + ASSERT_TRUE(tableParser->IsFinished()); + ASSERT_EQ(expected, tableParser->GetResult().str()); +} + +TEST_F( + MADDY_TABLEPARSER_GFM, + ItFallsBackToAParagraphWhenTheSecondLineIsNotASeparatorRow +) +{ + std::string first = "not | a | table"; + std::string second = "just some more text"; + std::string third = ""; + std::string expected = "

not | a | table just some more text

"; + + tableParser->AddLine(first); + tableParser->AddLine(second); + + if (!tableParser->IsFinished()) + { + tableParser->AddLine(third); + } + + ASSERT_TRUE(tableParser->IsFinished()); + ASSERT_EQ(expected, tableParser->GetResult().str()); +} + +TEST_F(MADDY_TABLEPARSER_GFM, OldMaddySpecificSyntaxIsNotRecognizedAsATable) +{ + std::string first = "|table>"; + std::string second = "Left header|middle header|last header"; + std::string third = ""; + + tableParser->AddLine(first); + tableParser->AddLine(second); + + if (!tableParser->IsFinished()) + { + tableParser->AddLine(third); + } + + ASSERT_TRUE(tableParser->IsFinished()); + ASSERT_EQ(std::string::npos, tableParser->GetResult().str().find("")); +}