1 /*
2  markdown_test.cpp     MindForger markdown test
3 
4  Copyright (C) 2016-2020 Martin Dvorak <martin.dvorak@mindforger.com>
5 
6  This program is free software; you can redistribute it and/or
7  modify it under the terms of the GNU General Public License
8  as published by the Free Software Foundation; either version 2
9  of the License, or (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <cstdlib>
21 #include <iostream>
22 #include <memory>
23 #include <cstdio>
24 #ifndef _WIN32
25 #  include <unistd.h>
26 #endif
27 
28 #include <gtest/gtest.h>
29 
30 #include "../../../src/representations/markdown/markdown_lexem.h"
31 #include "../../../src/representations/markdown/markdown_ast_node.h"
32 #include "../../../src/representations/markdown/markdown_lexer_sections.h"
33 #include "../../../src/representations/markdown/markdown_parser_sections.h"
34 #include "../../../src/representations/markdown/markdown_outline_representation.h"
35 
36 #include "../../../src/config/configuration.h"
37 #include "../../../src/mind/ontology/ontology.h"
38 #include "../../../src/mind/ai/autolinking_preprocessor.h"
39 #include "../../../src/persistence/filesystem_persistence.h"
40 
41 #include "../test_gear.h"
42 
43 using namespace std;
44 using namespace m8r;
45 
46 extern char* getMindforgerGitHomePath();
47 
TEST(MarkdownParserTestCase,MarkdownLexerSections)48 TEST(MarkdownParserTestCase, MarkdownLexerSections)
49 {
50     unique_ptr<string> fileName
51             = unique_ptr<string>(new string{"/lib/test/resources/basic-repository/memory/outline.md"});
52     fileName.get()->insert(0, getMindforgerGitHomePath());
53     MarkdownLexerSections lexer(fileName.get());
54 
55     // minimal MD
56     lexer.tokenize();
57     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
58     printLexems(lexems);
59 
60     // asserts
61     EXPECT_EQ(MarkdownLexemType::BEGIN_DOC, lexems[0]->getType());
62 
63     EXPECT_EQ(MarkdownLexemType::SECTION, lexems[1]->getType());
64     EXPECT_EQ(0, lexems[1]->getDepth());
65 
66     EXPECT_EQ(MarkdownLexemType::WHITESPACES, lexems[2]->getType());
67     EXPECT_EQ(MarkdownLexemType::TEXT, lexems[3]->getType());
68     EXPECT_EQ(0, lexems[3]->getOff());
69     EXPECT_EQ(2, lexems[3]->getIdx());
70     EXPECT_EQ(9, lexems[3]->getLng());
71 }
72 
TEST(MarkdownParserTestCase,MarkdownLexerSectionsNoMetadata)73 TEST(MarkdownParserTestCase, MarkdownLexerSectionsNoMetadata)
74 {
75     unique_ptr<string> fileName
76             = unique_ptr<string>(new string{"/lib/test/resources/basic-repository/memory/no-metadata.md"});
77     fileName.get()->insert(0, getMindforgerGitHomePath());
78     MarkdownLexerSections lexer(fileName.get());
79 
80     // minimal MD
81     lexer.tokenize();
82     printLexems(lexer.getLexems());
83 }
84 
TEST(MarkdownParserTestCase,MarkdownLexerSectionsPreamble)85 TEST(MarkdownParserTestCase, MarkdownLexerSectionsPreamble)
86 {
87     unique_ptr<string> fileName
88             = unique_ptr<string>(new string{"/lib/test/resources/apiary-repository/memory/01. Simplest API.md"});
89     fileName.get()->insert(0, getMindforgerGitHomePath());
90     MarkdownLexerSections lexer(fileName.get());
91 
92     // tokenize
93     lexer.tokenize();
94     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
95     printLexems(lexems);
96 
97     // asserts
98     EXPECT_EQ(MarkdownLexemType::BEGIN_DOC, lexems[0]->getType());
99     EXPECT_EQ(MarkdownLexemType::LINE, lexems[1]->getType());
100     EXPECT_EQ(MarkdownLexemType::BR, lexems[2]->getType());
101     EXPECT_EQ(MarkdownLexemType::BR, lexems[3]->getType());
102     EXPECT_EQ(MarkdownLexemType::SECTION, lexems[4]->getType());
103     EXPECT_EQ(0, lexems[4]->getDepth());
104 }
105 
TEST(MarkdownParserTestCase,MarkdownLexerSectionsPostDeclaredHeaders)106 TEST(MarkdownParserTestCase, MarkdownLexerSectionsPostDeclaredHeaders)
107 {
108     unique_ptr<string> fileName
109             = unique_ptr<string>(new string{"/lib/test/resources/bugs-repository/memory/feature-9-post-declared-headers.md"});
110     fileName.get()->insert(0, getMindforgerGitHomePath());
111     MarkdownLexerSections lexer(fileName.get());
112 
113     // tokenize
114     lexer.tokenize();
115     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
116     printLexems(lexems);
117 
118     // asserts
119     EXPECT_EQ(MarkdownLexemType::BEGIN_DOC, lexems[0]->getType());
120     EXPECT_EQ(MarkdownLexemType::SECTION_equals, lexems[1]->getType());
121     EXPECT_EQ(MarkdownLexemType::LINE, lexems[2]->getType());
122     EXPECT_EQ(MarkdownLexemType::BR, lexems[3]->getType());
123     EXPECT_EQ(MarkdownLexemType::LINE, lexems[4]->getType());
124     EXPECT_EQ(MarkdownLexemType::BR, lexems[5]->getType());
125 }
126 
TEST(MarkdownParserTestCase,MarkdownLexerSectionsPostDeclaredHeaders2)127 TEST(MarkdownParserTestCase, MarkdownLexerSectionsPostDeclaredHeaders2)
128 {
129     string content;
130     content.assign(
131         "Outline Name\n"
132         "========\n"
133         "O text.\n"
134         "\n"
135         "First Section\n"
136         "-------------\n"
137         "N1 text.\n"
138         "\n"
139         "## Second Section\n"
140         "\n"
141         "N2 text.\n"
142         "\n"
143         "Note 3\n"
144         "-------------\n"
145         "N2 text.\n"
146         "\n");
147 
148     MarkdownLexerSections lexer(nullptr);
149 
150     // tokenize
151     lexer.tokenize(&content);
152     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
153     ASSERT_TRUE(lexems.size());
154     printLexems(lexems);
155 
156     // asserts
157     EXPECT_EQ(MarkdownLexemType::BEGIN_DOC, lexems[0]->getType());
158     EXPECT_EQ(MarkdownLexemType::SECTION_equals, lexems[1]->getType());
159     EXPECT_EQ(MarkdownLexemType::LINE, lexems[2]->getType());
160     EXPECT_EQ(MarkdownLexemType::BR, lexems[3]->getType());
161     EXPECT_EQ(MarkdownLexemType::LINE, lexems[4]->getType());
162     EXPECT_EQ(MarkdownLexemType::BR, lexems[5]->getType());
163 }
164 
TEST(MarkdownParserTestCase,MarkdownLexerTimeScope)165 TEST(MarkdownParserTestCase, MarkdownLexerTimeScope)
166 {
167     string content;
168     content.assign(
169         "# Outline Name <!-- Metadata: scope: 1y2m3d4h5m; -->\n"
170         "O text.\n"
171         "\n"
172         "## First Section\n"
173         "N1 text.\n"
174         "\n"
175         "## Second Section\n"
176         "N2 text.\n"
177         "\n");
178 
179     MarkdownLexerSections lexer(nullptr);
180 
181     // tokenize
182     lexer.tokenize(&content);
183     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
184     ASSERT_TRUE(lexems.size());
185     printLexems(lexems);
186 
187     // asserts
188     EXPECT_EQ(MarkdownLexemType::BEGIN_DOC, lexems[0]->getType());
189     EXPECT_EQ(MarkdownLexemType::META_PROPERTY_scope, lexems[9]->getType());
190 }
191 
TEST(MarkdownParserTestCase,MarkdownLexerLinks)192 TEST(MarkdownParserTestCase, MarkdownLexerLinks)
193 {
194     string content;
195     content.assign(
196                 "# Outline Name <!-- Metadata: links: [same as](./o1.md); -->\n"
197                 "O text.\n"
198                 "\n"
199                 "## First Section <!-- Metadata: links: [opposite of](./x.md),[is a](./y.md#a-z); -->\n"
200                 "N1 text.\n"
201                 "\n"
202                 "## Second Section\n"
203                 "N2 text.\n"
204                 "\n");
205 
206     MarkdownLexerSections lexer(nullptr);
207 
208     // tokenize
209     lexer.tokenize(&content);
210     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
211     ASSERT_TRUE(lexems.size());
212     printLexems(lexems);
213 
214     // asserts
215     EXPECT_EQ(MarkdownLexemType::BEGIN_DOC, lexems[0]->getType());
216     EXPECT_EQ(MarkdownLexemType::META_PROPERTY_links, lexems[9]->getType());
217 }
218 
TEST(MarkdownParserTestCase,MarkdownParserSections)219 TEST(MarkdownParserTestCase, MarkdownParserSections)
220 {
221     unique_ptr<string> fileName
222             = unique_ptr<string>(new string{"/lib/test/resources/basic-repository/memory/outline.md"});
223     fileName.get()->insert(0, getMindforgerGitHomePath());
224 
225     // minimal MD
226     cout << endl << "- Lexer ----------------------------------------------";
227     MarkdownLexerSections lexer(fileName.get());
228     lexer.tokenize();
229     printLexems(lexer.getLexems());
230     cout << endl << "- Parser ----------------------------------------------";
231     MarkdownParserSections parser(lexer);
232     parser.parse();
233     EXPECT_FALSE(parser.hasMetadata());
234     printAst(parser.getAst());
235     cout << endl << "- DONE ----------------------------------------------";
236     cout << endl;
237 }
238 
TEST(MarkdownParserTestCase,MarkdownParserSectionsPreamble)239 TEST(MarkdownParserTestCase, MarkdownParserSectionsPreamble)
240 {
241     unique_ptr<string> fileName
242             = unique_ptr<string>(new string{"/lib/test/resources/apiary-repository/memory/01. Simplest API.md"});
243     fileName.get()->insert(0, getMindforgerGitHomePath());
244 
245     // minimal MD
246     cout << endl << "- Lexer ----------------------------------------------";
247     MarkdownLexerSections lexer(fileName.get());
248     lexer.tokenize();
249     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
250     printLexems(lexems);
251     EXPECT_EQ(62, lexems.size());
252 
253     cout << endl << "- Parser ----------------------------------------------";
254     MarkdownParserSections parser(lexer);
255     parser.parse();
256     EXPECT_TRUE(!parser.hasMetadata());
257     std::vector<MarkdownAstNodeSection*>* ast = parser.getAst();
258     printAst(ast);
259     EXPECT_EQ(4, ast->size());
260     // preamble section
261     EXPECT_TRUE(ast->at(0)->isPreambleSection());
262     EXPECT_EQ(nullptr, ast->at(0)->getText());
263 
264     cout << endl << "- DONE ----------------------------------------------";
265     cout << endl;
266 }
267 
TEST(MarkdownParserTestCase,MarkdownParserSectionsEmptyFirstLine)268 TEST(MarkdownParserTestCase, MarkdownParserSectionsEmptyFirstLine)
269 {
270     string repositoryPath{"/tmp"};
271     string fileName{"md-parser-first-empty-line-file.md"};
272     string content;
273     string filePath{repositoryPath+"/"+fileName};
274 
275     content.assign(
276         "\n"
277         "\n"
278         "\n"
279         "# First Markdown"
280         "\nFirst MD text."
281         "\n"
282         "\n## Note 1"
283         "\nNote 1 text."
284         "\n"
285         "\n## Note 2"
286         "\nNote 2 text."
287         "\n");
288     m8r::stringToFile(filePath, content);
289 
290     // minimal MD
291     cout << endl << "- Lexer ----------------------------------------------";
292     MarkdownLexerSections lexer(&filePath);
293     lexer.tokenize();
294     const std::vector<MarkdownLexem*>& lexems = lexer.getLexems();
295     printLexems(lexems);
296     EXPECT_EQ(31, lexems.size());
297 
298     cout << endl << "- Parser ----------------------------------------------";
299     MarkdownParserSections parser(lexer);
300     parser.parse();
301     EXPECT_TRUE(!parser.hasMetadata());
302     std::vector<MarkdownAstNodeSection*>* ast = parser.getAst();
303     printAst(ast);
304     EXPECT_EQ(4, ast->size());
305     // preamble section
306     EXPECT_TRUE(ast->at(0)->isPreambleSection());
307     EXPECT_EQ(nullptr, ast->at(0)->getText());
308 
309     cout << endl << "- DONE ----------------------------------------------";
310     cout << endl;
311 }
312 
TEST(MarkdownParserTestCase,MarkdownParserSectionsNoMetadata)313 TEST(MarkdownParserTestCase, MarkdownParserSectionsNoMetadata)
314 {
315     unique_ptr<string> fileName
316             = unique_ptr<string>(new string{"/lib/test/resources/basic-repository/memory/no-metadata.md"});
317     fileName.get()->insert(0, getMindforgerGitHomePath());
318 
319     // minimal MD
320     cout << endl << "- Lexer ----------------------------------------------";
321     MarkdownLexerSections lexer(fileName.get());
322     lexer.tokenize();
323     printLexems(lexer.getLexems());
324     cout << endl << "- Parser ----------------------------------------------";
325     MarkdownParserSections parser(lexer);
326     parser.parse();
327     EXPECT_TRUE(!parser.hasMetadata());
328     printAst(parser.getAst());
329     cout << endl << "- DONE ----------------------------------------------";
330     cout << endl;
331 }
332 
TEST(MarkdownParserTestCase,Bug37Meta)333 TEST(MarkdownParserTestCase, Bug37Meta)
334 {
335     string fileName{"/lib/test/resources/bugs-repository/memory/bug-37-meta.md"};
336     fileName.insert(0, getMindforgerGitHomePath());
337     cout << endl << "- Lexer ----------------------------------------------";
338     MarkdownLexerSections lexer(&fileName);
339     lexer.tokenize();
340     printLexems(lexer.getLexems());
341     cout << endl << "- Parser ----------------------------------------------";
342     MarkdownParserSections parser(lexer);
343     parser.parse();
344     EXPECT_TRUE(parser.hasMetadata());
345     printAst(parser.getAst());
346     ASSERT_EQ(4, parser.getAst()->size());
347     cout << endl << "- DONE ----------------------------------------------";
348     cout << endl;
349 }
350 
TEST(MarkdownParserTestCase,Bug37Nometa)351 TEST(MarkdownParserTestCase, Bug37Nometa)
352 {
353     string fileName{"/lib/test/resources/bugs-repository/memory/bug-37-nometa.md"};
354     fileName.insert(0, getMindforgerGitHomePath());
355     cout << endl << "- Lexer ----------------------------------------------";
356     MarkdownLexerSections lexer(&fileName);
357     lexer.tokenize();
358     printLexems(lexer.getLexems());
359     cout << endl << "- Parser ----------------------------------------------";
360     MarkdownParserSections parser(lexer);
361     parser.parse();
362     EXPECT_FALSE(parser.hasMetadata());
363     printAst(parser.getAst());
364     ASSERT_EQ(4, parser.getAst()->size());
365     cout << endl << "- DONE ----------------------------------------------";
366     cout << endl;
367 }
368 
TEST(MarkdownParserTestCase,Bug37Notrailing)369 TEST(MarkdownParserTestCase, Bug37Notrailing)
370 {
371     string fileName{"/lib/test/resources/bugs-repository/memory/bug-37-notrailing.md"};
372     fileName.insert(0, getMindforgerGitHomePath());
373     cout << endl << "- Lexer ----------------------------------------------";
374     MarkdownLexerSections lexer(&fileName);
375     lexer.tokenize();
376     printLexems(lexer.getLexems());
377     cout << endl << "- Parser ----------------------------------------------";
378     MarkdownParserSections parser(lexer);
379     parser.parse();
380     EXPECT_FALSE(parser.hasMetadata());
381     printAst(parser.getAst());
382     ASSERT_EQ(4, parser.getAst()->size());
383     cout << endl << "- DONE ----------------------------------------------";
384     cout << endl;
385 }
386 
TEST(MarkdownParserTestCase,MarkdownRepresentation)387 TEST(MarkdownParserTestCase, MarkdownRepresentation)
388 {
389     string repositoryPath{"/lib/test/resources/basic-repository"};
390     repositoryPath.insert(0, getMindforgerGitHomePath());
391 
392     m8r::Configuration& config = m8r::Configuration::getInstance();
393     config.clear();
394     config.setConfigFilePath("/tmp/cfg-mptc-mr.md");
395     config.setActiveRepository(config.addRepository(m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath)));
396 
397     m8r::Ontology ontology{};
398 
399     string text =
400             "## Canonical Message <!-- Metadata: tags: important; type: Goal; created: 2015-05-02 21:30:28; reads: 55; read: 2016-03-31 13:54:45; revision: 3; modified: 2016-03-31 13:54:45; importance: 3/5; urgency: 2/5; progress: 20%; -->\n"
401             "\n"
402             "This document contains ideas related to the definition of similarity codes for canonical messages.\n"
403             "\n";
404 
405     cout << endl << "- MD > Note ----------------------------------------------";
406     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
407 
408     unique_ptr<Note> note = unique_ptr<Note>(mdr.note(&text));
409     if(note) {
410         cout << endl << "    '" << note->getName() << "' (name)";
411         cout << endl << "    '" << note->getDepth() << "' (depth)";
412         cout << endl << "    " << (note->getPrimaryTag()?note->getPrimaryTag()->getName():"NULL") << " (primary tag)";
413         if(note->getTags()->size()) {
414             for(size_t t=0; t<note->getTags()->size(); t++) {
415                 cout << endl << "      " << (*note->getTags())[t]->getName() << " (tag)";
416             }
417         }
418         cout << endl << "    " << (note->getType()?note->getType()->getName():"NULL") << " (type)";
419         cout << endl << "      Description[" << note->getDescription().size() << "]:";
420         for(string* description:note->getDescription()) {
421             cout << endl << "        '" << *description << "' (description)";
422         }
423         cout << endl << "  " << note->getCreated() << " (created)";
424         cout << endl << "  " << note->getModified() << " (modified)";
425         cout << endl << "  " << note->getRevision() << " (revision)";
426         cout << endl << "  " << note->getReads() << " (reads)";
427         cout << endl << "  " << note->getRead() << " (read)";
428     } else {
429         cout << endl << "Note is NULL!";
430     }
431 
432     cout << endl << "- DONE ----------------------------------------------";
433 }
434 
TEST(MarkdownParserTestCase,MarkdownRepresentationPreamble)435 TEST(MarkdownParserTestCase, MarkdownRepresentationPreamble)
436 {
437     string repositoryPath{"/lib/test/resources/apiary-repository"};
438     repositoryPath.insert(0, getMindforgerGitHomePath());
439     m8r::Configuration& config = m8r::Configuration::getInstance();
440     config.clear();
441     config.setConfigFilePath("/tmp/cfg-mptc-mrp.md");
442     config.setActiveRepository(config.addRepository(m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath)));
443     m8r::Ontology ontology{};
444 
445     // parse
446     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
447     unique_ptr<string> fileName
448             = unique_ptr<string>(new string{"/lib/test/resources/apiary-repository/memory/01. Simplest API.md"});
449     fileName.get()->insert(0, getMindforgerGitHomePath());
450     File file{*fileName.get()};
451     m8r::Outline* o = mdr.outline(file);
452 
453     // asserts
454     EXPECT_NE(nullptr, o);
455     EXPECT_EQ(2, o->getNotesCount());
456 
457     cout << endl << "- Preamble ---";
458     EXPECT_EQ(2, o->getPreamble().size());
459     cout << endl << "'" << *(o->getPreamble()[0]) << "'";
460     cout << endl << "'" << *(o->getPreamble()[1]) << "'";
461     EXPECT_EQ("FORMAT: 1A", *(o->getPreamble()[0]));
462     EXPECT_EQ("", *(o->getPreamble()[1]));
463     EXPECT_TRUE(o->isApiaryBlueprint());
464 
465     cout << endl << "- Outline ---";
466     cout << endl << "'" << o->getName() << "'";
467     EXPECT_EQ("The Simplest API", o->getName());
468     EXPECT_EQ(15, o->getDescription().size());
469 
470     cout << endl << "- N[0] ---";
471     cout << endl << "'" << o->getNotes()[0]->getName() << "'";
472     EXPECT_EQ("API Blueprint", o->getNotes()[0]->getName());
473     EXPECT_EQ(3, o->getNotes()[0]->getDescription().size());
474 
475     cout << endl << "- N[1] ---";
476     cout << endl << "'" << o->getNotes()[1]->getName() << "'";
477     EXPECT_EQ("GET /message", o->getNotes()[1]->getName());
478 
479     // preamble serialization check
480     string* original = m8r::fileToString(o->getKey());
481     string* serialized = mdr.to(o);
482     EXPECT_EQ(*original, *serialized);
483 
484     delete original;
485     delete serialized;
486     delete o;
487     cout << endl << "- DONE ----------------------------------------------" << endl;
488 }
489 
TEST(MarkdownParserTestCase,MarkdownRepresentationPostDeclaredSection)490 TEST(MarkdownParserTestCase, MarkdownRepresentationPostDeclaredSection)
491 {
492     string repositoryPath{"/tmp"};
493     string fileName{"md-post-declared-section.md"};
494     string content;
495     string filePath{repositoryPath+"/"+fileName};
496 
497     content.assign(
498         "Outline Name\n"
499         "============\n"
500         "O text.\n"
501         "\n"
502         "First Section\n"
503         "-------------\n"
504         "N1 text.\n"
505         "\n"
506         "## Second Section\n"
507         "\n"
508         "N2 text.\n"
509         "\n"
510         "Note 2\n"
511         "------\n"
512         "\n"
513         "N2 text.\n"
514         "\n"
515         "Note 3\n"
516         "------\n"
517         "N3 text.\n"
518         "\n");
519     m8r::stringToFile(filePath, content);
520 
521     m8r::Repository* repository = m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath);
522     repository->setMode(m8r::Repository::RepositoryMode::FILE);
523     repository->setFile(fileName);
524     m8r::Configuration& config = m8r::Configuration::getInstance();
525     config.clear();
526     config.setConfigFilePath("/tmp/cfg-mptc-mrpdc.md");
527     config.setActiveRepository(config.addRepository(repository));
528     m8r::Ontology ontology{};
529 
530     // parse
531     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
532     File file{filePath};
533     m8r::Outline* o = mdr.outline(file);
534 
535     // asserts
536     EXPECT_NE(nullptr, o);
537     ASSERT_EQ(4, o->getNotesCount());
538 
539     cout << endl << "- O ---";
540     cout << endl << "Name: '" << o->getName() << "'";
541     EXPECT_EQ("Outline Name", o->getName());
542     cout << endl << "Desc: '" << o->getDescriptionAsString() << "'";
543     EXPECT_EQ(2, o->getDescription().size());
544 
545     EXPECT_EQ("First Section", o->getNotes()[0]->getName());
546     EXPECT_EQ(2, o->getNotes()[0]->getDescription().size());
547     EXPECT_EQ("Second Section", o->getNotes()[1]->getName());
548     EXPECT_EQ(3, o->getNotes()[1]->getDescription().size());
549     EXPECT_EQ("Note 2", o->getNotes()[2]->getName());
550     EXPECT_EQ(3, o->getNotes()[2]->getDescription().size());
551     EXPECT_EQ("Note 3", o->getNotes()[3]->getName());
552     EXPECT_EQ(2, o->getNotes()[3]->getDescription().size());
553 
554     // serialize
555     string* serialized = mdr.to(o);
556     cout << endl << "- SERIALIZED ---";
557     cout << endl << *serialized;
558     EXPECT_EQ(content, *serialized);
559 
560     delete serialized;
561     delete o;
562 }
563 
TEST(MarkdownParserTestCase,MarkdownRepresentationTrailingHashesSection)564 TEST(MarkdownParserTestCase, MarkdownRepresentationTrailingHashesSection)
565 {
566     string repositoryPath{"/tmp"};
567     string fileName{"md-trailing-hashes-section.md"};
568     string content;
569     string filePath{repositoryPath+"/"+fileName};
570 
571     content.assign(
572         "# Outline Name #\n"
573         "O text.\n"
574         "\n"
575         "# First Section #\n"
576         "N1 text.\n"
577         "\n"
578         "# WRONG 1 ##\n"
579         "W1 text.\n"
580         "\n"
581         "## Second Section ##\n"
582         "N2 text.\n"
583         "\n"
584         "## WRONG 2 #\n"
585         "W2 text.\n"
586         "\n"
587         "### Note 3 ###\n"
588         "N3 text.\n"
589         "\n");
590     m8r::stringToFile(filePath, content);
591 
592     m8r::Repository* repository = m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath);
593     repository->setMode(m8r::Repository::RepositoryMode::FILE);
594     repository->setFile(fileName);
595     m8r::Configuration& config = m8r::Configuration::getInstance();
596     config.clear();
597     config.setConfigFilePath("/tmp/cfg-mptc-mrthc.md");
598     config.setActiveRepository(config.addRepository(repository));
599     m8r::Ontology ontology{};
600 
601     // parse
602     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
603     File file{filePath};
604     m8r::Outline* o = mdr.outline(file);
605 
606     // asserts
607     EXPECT_NE(nullptr, o);
608     ASSERT_EQ(5, o->getNotesCount());
609 
610     cout << endl << "- O ---";
611     cout << endl << "Name: '" << o->getName() << "'";
612     EXPECT_EQ("Outline Name", o->getName());
613     cout << endl << "Desc: '" << o->getDescriptionAsString() << "'";
614     EXPECT_EQ(false, o->isPostDeclaredSection());
615     EXPECT_EQ(true, o->isTrailingHashesSection());
616 
617     EXPECT_EQ("First Section", o->getNotes()[0]->getName());
618     EXPECT_EQ(false, o->getNotes()[0]->isPostDeclaredSection());
619     EXPECT_EQ(true, o->getNotes()[0]->isTrailingHashesSection());
620     EXPECT_EQ(2, o->getNotes()[0]->getDescription().size());
621 
622     EXPECT_EQ("WRONG 1 ##", o->getNotes()[1]->getName());
623     EXPECT_EQ(false, o->getNotes()[1]->isPostDeclaredSection());
624     EXPECT_EQ(false, o->getNotes()[1]->isTrailingHashesSection());
625     EXPECT_EQ(2, o->getNotes()[1]->getDescription().size());
626 
627     EXPECT_EQ("Second Section", o->getNotes()[2]->getName());
628     EXPECT_EQ(false, o->getNotes()[2]->isPostDeclaredSection());
629     EXPECT_EQ(true, o->getNotes()[2]->isTrailingHashesSection());
630     EXPECT_EQ(2, o->getNotes()[2]->getDescription().size());
631 
632     EXPECT_EQ("WRONG 2 #", o->getNotes()[3]->getName());
633     EXPECT_EQ(false, o->getNotes()[3]->isPostDeclaredSection());
634     EXPECT_EQ(false, o->getNotes()[3]->isTrailingHashesSection());
635     EXPECT_EQ(2, o->getNotes()[3]->getDescription().size());
636 
637     EXPECT_EQ("Note 3", o->getNotes()[4]->getName());
638     EXPECT_EQ(false, o->getNotes()[4]->isPostDeclaredSection());
639     EXPECT_EQ(true, o->getNotes()[4]->isTrailingHashesSection());
640     EXPECT_EQ(2, o->getNotes()[4]->getDescription().size());
641 
642     // serialize
643     string* serialized = mdr.to(o);
644     cout << endl << "- SERIALIZED ---";
645     cout << endl << *serialized;
646     EXPECT_EQ(content, *serialized);
647 
648     delete serialized;
649     delete o;
650 }
651 
TEST(MarkdownParserTestCase,MarkdownRepresentationEmptyFirstLine)652 TEST(MarkdownParserTestCase, MarkdownRepresentationEmptyFirstLine)
653 {
654     string repositoryPath{"/tmp"};
655     string fileName{"md-parser-first-empty-line-file.md"};
656     string content;
657     string filePath{repositoryPath+"/"+fileName};
658 
659     content.assign(
660         "\n"
661         "\n"
662         "\n"
663         "# First Markdown"
664         "\nFirst MD text."
665         "\n"
666         "\n## Note 1"
667         "\nNote 1 text."
668         "\n"
669         "\n## Note 2"
670         "\nNote 2 text."
671         "\n");
672     m8r::stringToFile(filePath, content);
673 
674     m8r::Repository* repository = m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath);
675     repository->setMode(m8r::Repository::RepositoryMode::FILE);
676     repository->setFile(fileName);
677     m8r::Configuration& config = m8r::Configuration::getInstance();
678     config.clear();
679     config.setConfigFilePath("/tmp/cfg-mptc-mrefl.md");
680     config.setActiveRepository(config.addRepository(repository));
681     m8r::Ontology ontology{};
682 
683     // parse
684     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
685     File file{filePath};
686     m8r::Outline* o = mdr.outline(file);
687 
688     // asserts
689     EXPECT_NE(nullptr, o);
690     EXPECT_EQ(2, o->getNotesCount());
691 
692     cout << endl << "- Preamble ---";
693     EXPECT_EQ(3, o->getPreamble().size());
694     cout << endl << "'" << *(o->getPreamble()[0]) << "'";
695     cout << endl << "'" << *(o->getPreamble()[1]) << "'";
696     cout << endl << "'" << *(o->getPreamble()[2]) << "'";
697     EXPECT_EQ("", *(o->getPreamble()[0]));
698     EXPECT_EQ("", *(o->getPreamble()[1]));
699     EXPECT_EQ("", *(o->getPreamble()[2]));
700     EXPECT_TRUE(!o->isApiaryBlueprint());
701 
702     cout << endl << "- Outline ---";
703     cout << endl << "'" << o->getName() << "'";
704     EXPECT_EQ("First Markdown", o->getName());
705     EXPECT_EQ(2, o->getDescription().size());
706 
707     delete o;
708 }
709 
TEST(MarkdownParserTestCase,FileSystemPersistence)710 TEST(MarkdownParserTestCase, FileSystemPersistence)
711 {
712     string repositoryPath{"/lib/test/resources/basic-repository"};
713     repositoryPath.insert(0, getMindforgerGitHomePath());
714 
715     m8r::Configuration& config = m8r::Configuration::getInstance();
716     config.clear();
717     config.setConfigFilePath("/tmp/cfg-mptc-fsp.md");
718     config.setActiveRepository(config.addRepository(m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath)));
719     m8r::Ontology ontology{};
720     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
721     m8r::HtmlOutlineRepresentation htmlr{ontology, nullptr};
722     m8r::FilesystemPersistence persistence{mdr, htmlr};
723 
724     unique_ptr<string> text = unique_ptr<string>(new string{"abc"});
725     cout << persistence.createFileName(string("/tmp"), text.get(), string(FILE_EXTENSION_MD_MD));
726 }
727 
TEST(MarkdownParserBugsTestCase,EmptyNameSkipsEof)728 TEST(MarkdownParserBugsTestCase, EmptyNameSkipsEof)
729 {
730     string repositoryPath{"/lib/test/resources/bugs-repository"};
731     repositoryPath.insert(0, getMindforgerGitHomePath());
732 
733     m8r::Configuration& config = m8r::Configuration::getInstance();
734     config.clear();
735     config.setConfigFilePath("/tmp/cfg-mptc-ense.md");
736     config.setActiveRepository(config.addRepository(m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath)));
737     m8r::Ontology ontology{};
738 
739     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
740     string outlineFilename{"/lib/test/resources/bugs-repository/memory/bug-37.md"};
741     outlineFilename.insert(0, getMindforgerGitHomePath());
742     m8r::Outline* o = mdr.outline(outlineFilename);
743     string* s = mdr.to(o);
744 
745     cout << endl << "-----------------------------------------------";
746     cout << *s;
747     cout << endl << "-----------------------------------------------";
748 
749     // TODO asserts
750 
751     std::ofstream out("./tests/resources/bugs-repository/memory/bug-37-PARSER-OUTPUT.md");
752     out << *s;
753     out.close();
754 
755     delete o;
756     delete s;
757 
758     cout << endl << "- DONE ----------------------------------------------";
759 }
760 
TEST(MarkdownParserTestCase,TimeScope)761 TEST(MarkdownParserTestCase, TimeScope)
762 {
763     string repositoryPath{"/tmp"};
764     string fileName{"md-parser-time-scope.md"};
765     string content;
766     string filePath{repositoryPath+"/"+fileName};
767 
768     content.assign(
769                 "# Outline Name <!-- Metadata: scope: 1y2m3d4h5m; -->\n"
770                 "O text.\n"
771                 "\n"
772                 "## First Section\n"
773                 "N1 text.\n"
774                 "\n"
775                 "## Second Section\n"
776                 "N2 text.\n"
777                 "\n");
778     m8r::stringToFile(filePath, content);
779 
780     m8r::Repository* repository = m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath);
781     repository->setMode(m8r::Repository::RepositoryMode::FILE);
782     repository->setFile(fileName);
783     m8r::Configuration& config = m8r::Configuration::getInstance();
784     config.clear();
785     config.setConfigFilePath("/tmp/cfg-mptc-ts.md");
786     config.setActiveRepository(config.addRepository(repository));
787     m8r::Ontology ontology{};
788 
789     // parse
790     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
791     File file{filePath};
792     m8r::Outline* o = mdr.outline(file);
793 
794     // asserts
795     EXPECT_NE(nullptr, o);
796     string timeScopeAsString{};
797     o->getTimeScope().toString(timeScopeAsString);
798     cout << endl << "Time scope: " << timeScopeAsString << " (" << o->getTimeScope().relativeSecs << "s)";
799     EXPECT_EQ(36993900, o->getTimeScope().relativeSecs);
800     o->getTimeScope().toString(timeScopeAsString);
801     EXPECT_EQ("1y2m3d4h5m", timeScopeAsString);
802     EXPECT_EQ(2, o->getNotesCount());
803 
804     // serialize
805     string* serialized = mdr.to(o);
806     cout << endl << "- SERIALIZED ---";
807     cout << endl << *serialized;
808     EXPECT_NE(std::string::npos, serialized->find("scope: 1y2m3d4h5m;"));
809 
810     delete serialized;
811     delete o;
812 }
813 
TEST(MarkdownParserTestCase,Deadline)814 TEST(MarkdownParserTestCase, Deadline)
815 {
816     string repositoryPath{"/tmp"};
817     string fileName{"md-parser-deadline.md"};
818     string content;
819     string filePath{repositoryPath+"/"+fileName};
820 
821     content.assign(
822                 "# Outline Name\n"
823                 "O text.\n"
824                 "\n"
825                 "## First Section  <!-- Metadata: deadline: 2010-11-12 13:14:15; -->\n"
826                 "N1 text.\n"
827                 "\n"
828                 "## Second Section\n"
829                 "N2 text.\n"
830                 "\n");
831     m8r::stringToFile(filePath, content);
832 
833     m8r::Repository* repository = m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath);
834     repository->setMode(m8r::Repository::RepositoryMode::FILE);
835     repository->setFile(fileName);
836     m8r::Configuration& config = m8r::Configuration::getInstance();
837     config.clear();
838     config.setConfigFilePath("/tmp/cfg-mptc-d.md");
839     config.setActiveRepository(config.addRepository(repository));
840     m8r::Ontology ontology{};
841 
842     // parse
843     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
844     File file{filePath};
845     m8r::Outline* o = mdr.outline(file);
846 
847     // asserts
848     EXPECT_NE(nullptr, o);
849     EXPECT_EQ(2, o->getNotesCount());
850 
851     cout << endl << "Deadline: " << o->getNotes()[0]->getDeadline();
852 #ifdef _WIN32
853     tm deadLineDate = { 15,14,13,12,10,110, 0, 0, 0 };
854 #else
855     tm deadLineDate = { 15,14,13,12,10,110, 0, 0, 0, 0, 0 };
856 #endif // _WIN32
857     time_t deadLine = mktime(&deadLineDate);
858     EXPECT_EQ(deadLine, o->getNotes()[0]->getDeadline());
859 
860     // serialize
861     string* serialized = mdr.to(o);
862     cout << endl << "- SERIALIZED ---";
863     cout << endl << *serialized;
864     EXPECT_NE(std::string::npos, serialized->find("deadline: 2010-11-12 13:14:15"));
865 
866     delete serialized;
867     delete o;
868 }
869 
TEST(MarkdownParserTestCase,Links)870 TEST(MarkdownParserTestCase, Links)
871 {
872     string repositoryPath{"/tmp"};
873     string fileName{"md-parser-deadline.md"};
874     string content;
875     string filePath{repositoryPath+"/"+fileName};
876 
877     content.assign(
878                 "# Outline Name <!-- Metadata: links: [same as](./o1.md); -->\n"
879                 "O text.\n"
880                 "\n"
881                 "## First Section <!-- Metadata: links: [opposite of](./x.md),[is a](./y.md#a-z); -->\n"
882                 "N1 text.\n"
883                 "\n"
884                 "## Second Section\n"
885                 "N2 text.\n"
886                 "\n");
887     m8r::stringToFile(filePath, content);
888 
889     m8r::Repository* repository = m8r::RepositoryIndexer::getRepositoryForPath(repositoryPath);
890     repository->setMode(m8r::Repository::RepositoryMode::FILE);
891     repository->setFile(fileName);
892     m8r::Configuration& config = m8r::Configuration::getInstance();
893     config.clear();
894     config.setConfigFilePath("/tmp/cfg-mptc-l.md");
895     config.setActiveRepository(config.addRepository(repository));
896     m8r::Ontology ontology{};
897 
898     // parse
899     m8r::MarkdownOutlineRepresentation mdr{ontology, nullptr};
900     File file{filePath};
901     m8r::Outline* o = mdr.outline(file);
902 
903     // asserts
904     EXPECT_NE(nullptr, o);
905     EXPECT_EQ(2, o->getNotesCount());
906 
907     cout << endl << "O links: " << o->getLinksCount();
908     EXPECT_EQ(1, o->getLinksCount());
909 
910     cout << endl << "N links: " << o->getNotes()[0]->getLinksCount();
911     EXPECT_EQ(2, o->getNotes()[0]->getLinksCount());
912 
913     // serialize
914     string* serialized = mdr.to(o);
915     cout << endl << "- SERIALIZED ---";
916     cout << endl << *serialized;
917     EXPECT_NE(std::string::npos, serialized->find("[same as](./o1.md)"));
918     EXPECT_NE(std::string::npos, serialized->find("[opposite of](./x.md)"));
919     EXPECT_NE(std::string::npos, serialized->find("[is a](./y.md#a-z)"));
920 
921     delete serialized;
922     delete o;
923 }
924 
TEST(MarkdownParserTestCase,Bug622Loop64kLinesOverflow)925 TEST(MarkdownParserTestCase, Bug622Loop64kLinesOverflow)
926 {
927     string fileName{"/lib/test/resources/bugs-repository/memory/bug-622-70k-lines.md"};
928     fileName.insert(0, getMindforgerGitHomePath());
929     cout << endl << "- Lexer ----------------------------------------------";
930     MarkdownLexerSections lexer(&fileName);
931     lexer.tokenize();
932     // file too big - do NOT print: printLexems(lexer.getLexems());
933     cout << endl << "- Parser ----------------------------------------------";
934     MarkdownParserSections parser(lexer);
935     parser.parse();
936     // file too big - do NOT print: printAst(parser.getAst());
937     ASSERT_EQ(71234, parser.getAst()->size());
938     cout << endl << "- DONE ----------------------------------------------";
939     cout << endl;
940 }
941