Changes
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 60f1395..964f737 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,7 +37,7 @@ include_directories(include)
# but for now we will just list them manually or add a library target.
# Let's create a core library for the logic to share between main and tests.
-add_library(macrodown_lib STATIC src/macro_engine.cpp src/parser.cpp src/block_parser.cpp src/converter.cpp src/standard_library.cpp)
+add_library(macrodown_lib STATIC src/macro_engine.cpp src/parser.cpp src/block_parser.cpp src/converter.cpp src/standard_library.cpp src/macrodown.cpp)
target_include_directories(macrodown_lib PUBLIC include)
target_link_libraries(macrodown_lib PUBLIC uni-algo::uni-algo)
@@ -47,7 +47,7 @@ target_link_libraries(macrodown PRIVATE macrodown_lib)
# Testing
enable_testing()
-add_executable(macrodown_test tests/test_main.cpp tests/test_macro_engine.cpp tests/test_block_parser.cpp tests/test_integration.cpp tests/test_nodes.cpp)
+add_executable(macrodown_test tests/test_main.cpp tests/test_macro_engine.cpp tests/test_block_parser.cpp tests/test_nodes.cpp tests/test_macrodown.cpp)
target_link_libraries(macrodown_test PRIVATE macrodown_lib GTest::gtest_main)
target_include_directories(macrodown_test PRIVATE include)
diff --git a/design.md b/design.md
index 5a7f723..01250a2 100644
--- a/design.md
+++ b/design.md
@@ -104,6 +104,14 @@ struct Block {
## 4. Component Design
+### 4.0 Top-level Interface (`MacroDown`)
+The `MacroDown` class provides a simplified two-step interface for rendering documents, as required by the specification.
+
+* **Step 1: Parse** (`parse`): Takes a source string and returns a single root `Node` (the syntax tree).
+* **Step 2: Render** (`render`): Takes the root `Node` and produces the final HTML string using the internal `Evaluator`.
+
+It automatically initializes the standard library of macros.
+
### 4.1 Block Parser (`BlockParser`)
* **Input**: Line iterator.
* **Mechanism**:
diff --git a/include/macrodown.h b/include/macrodown.h
new file mode 100644
index 0000000..64ba453
--- /dev/null
+++ b/include/macrodown.h
@@ -0,0 +1,42 @@
+#ifndef MACRODOWN_H
+#define MACRODOWN_H
+
+#include <string>
+#include <memory>
+#include "nodes.h"
+#include "macro_engine.h"
+
+namespace macrodown
+{
+
+class MacroDown
+{
+public:
+ MacroDown();
+
+ /**
+ * Step 1: Parse the document into a syntax tree.
+ * @param input The Markdown source text.
+ * @return The root node of the syntax tree.
+ */
+ std::unique_ptr<Node> parse(const std::string& input);
+
+ /**
+ * Step 2: Render the syntax tree into HTML.
+ * @param root The root node of the syntax tree (from Step 1).
+ * @return The rendered HTML string.
+ */
+ std::string render(const Node& root);
+
+ /**
+ * Access the evaluator to define custom macros.
+ */
+ Evaluator& getEvaluator() { return evaluator_; }
+
+private:
+ Evaluator evaluator_;
+};
+
+} // namespace macrodown
+
+#endif // MACRODOWN_H
diff --git a/src/macrodown.cpp b/src/macrodown.cpp
new file mode 100644
index 0000000..cc53173
--- /dev/null
+++ b/src/macrodown.cpp
@@ -0,0 +1,43 @@
+#include "macrodown.h"
+#include "block_parser.h"
+#include "converter.h"
+#include "standard_library.h"
+
+namespace macrodown
+{
+
+MacroDown::MacroDown()
+{
+ StandardLibrary::registerMacros(evaluator_);
+}
+
+std::unique_ptr<Node> MacroDown::parse(const std::string& input)
+{
+ auto block_root = BlockParser::parse(input);
+ auto macro_nodes = Converter::convert(block_root.get());
+
+ if(macro_nodes.empty())
+ {
+ return std::make_unique<Node>(Group{});
+ }
+
+ if(macro_nodes.size() == 1)
+ {
+ return std::move(macro_nodes[0]);
+ }
+
+ Group root_group;
+ for(auto& node : macro_nodes)
+ {
+ root_group.addChild(std::move(node));
+ }
+
+ return std::make_unique<Node>(std::move(root_group));
+}
+
+std::string MacroDown::render(const Node& root)
+{
+ return evaluator_.evaluate(root);
+}
+
+} // namespace macrodown
diff --git a/src/main.cpp b/src/main.cpp
index 6b0a83a..548bcea 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,10 +1,7 @@
#include <iostream>
#include <fstream>
#include <sstream>
-#include "block_parser.h"
-#include "converter.h"
-#include "macro_engine.h"
-#include "standard_library.h"
+#include "macrodown.h"
using namespace macrodown;
@@ -32,21 +29,13 @@ int main(int argc, char** argv)
input = buffer.str();
}
- // 1. Setup Environment
- Evaluator evaluator;
- StandardLibrary::registerMacros(evaluator);
+ MacroDown md;
- // 2. Parse Blocks
- auto root = BlockParser::parse(input);
+ // Step 1: Parse to AST
+ auto root = md.parse(input);
- // 3. Convert to Macro AST
- auto macro_nodes = Converter::convert(root.get());
-
- // 4. Evaluate and Output
- for(const auto& node : macro_nodes)
- {
- std::cout << evaluator.evaluate(*node);
- }
+ // Step 2: Render to HTML
+ std::cout << md.render(*root);
return 0;
}
\ No newline at end of file
diff --git a/tests/test_integration.cpp b/tests/test_integration.cpp
deleted file mode 100644
index 07f325a..0000000
--- a/tests/test_integration.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-#include <gtest/gtest.h>
-#include "block_parser.h"
-#include "converter.h"
-#include "macro_engine.h"
-#include <iostream>
-
-using namespace macrodown;
-
-class IntegrationTest : public ::testing::Test {
-protected:
- Evaluator evaluator;
-
- void SetUp() override {
- // Define standard library macros for testing
- evaluator.define("p", {"t"}, "<p>%t</p>");
- evaluator.define("h1", {"t"}, "<h1>%t</h1>");
- evaluator.define("em", {"t"}, "<em>%t</em>");
- evaluator.define("strong", {"t"}, "<strong>%t</strong>");
- evaluator.define("code", {"t"}, "<code>%t</code>");
- evaluator.define("link", {"url", "text"}, "<a href=\"%url\">%text</a>");
- evaluator.define("quote", {"t"}, "<blockquote>%t</blockquote>");
- }
-
- std::string process(const std::string& input) {
- auto root = BlockParser::parse(input);
- auto nodes = Converter::convert(root.get());
- std::string result;
- for (const auto& node : nodes) {
- result += evaluator.evaluate(*node);
- }
- return result;
- }
-};
-
-TEST_F(IntegrationTest, BasicParagraph) {
- EXPECT_EQ(process("Hello World"), "<p>Hello World</p>");
-}
-
-TEST_F(IntegrationTest, Heading) {
- EXPECT_EQ(process("# Title"), "<h1>Title</h1>");
-}
-
-TEST_F(IntegrationTest, Emphasis) {
- EXPECT_EQ(process("Hello *World*"), "<p>Hello <em>World</em></p>");
-}
-
-TEST_F(IntegrationTest, Strong) {
- EXPECT_EQ(process("**Bold**"), "<p><strong>Bold</strong></p>");
-}
-
-TEST_F(IntegrationTest, Link) {
- EXPECT_EQ(process("[Click](http://example.com)"), "<p><a href=\"http://example.com\">Click</a></p>");
-}
-
-TEST_F(IntegrationTest, Code) {
- EXPECT_EQ(process("Use `printf`"), "<p>Use <code>printf</code></p>");
-}
-
-TEST_F(IntegrationTest, MacroInMarkdown) {
- // Define custom macro
- evaluator.define("greet", {"name"}, "Hello, %name!");
-
- // Use it in Markdown
- EXPECT_EQ(process("Say %greet{User}"), "<p>Say Hello, User!</p>");
-}
-
-TEST_F(IntegrationTest, Mixed) {
- std::string input = "# Header\n\nParagraph with *em* and %code{macros}.";
- std::string expected = "<h1>Header</h1><p>Paragraph with <em>em</em> and <code>macros</code>.</p>";
- EXPECT_EQ(process(input), expected);
-}
-
-TEST_F(IntegrationTest, BlockQuote) {
- std::string input = "> Hello\n> World";
- // Quote contains Paragraph.
- // Converter: Quote -> %quote{ content }.
- // Content of Quote is Block (Paragraph).
- // Paragraph -> %p{...}.
- // So %quote{ %p{...} }.
- // Evaluator: %quote expands to <blockquote>%t</blockquote>.
- // %t is result of expanding arg.
- // Arg is %p... -> <p>...</p>.
- // Result: <blockquote><p>...</p></blockquote>.
-
- // Note: My Converter for Quote:
- // macro "quote".
- // Args: GroupNode of children.
- // Children: P -> %p.
-
- // BUT: My generic "quote" definition takes "t".
- // Does it capture all children?
- // Converter produces: MacroNode("quote", args=[GroupNode(child1, child2...)])
- // So it has 1 argument. Correct.
-
- EXPECT_EQ(process(input), "<blockquote><p>Hello\nWorld</p></blockquote>");
-}
diff --git a/tests/test_macrodown.cpp b/tests/test_macrodown.cpp
new file mode 100644
index 0000000..78704e3
--- /dev/null
+++ b/tests/test_macrodown.cpp
@@ -0,0 +1,63 @@
+#include <gtest/gtest.h>
+#include "macrodown.h"
+
+using namespace macrodown;
+
+class MacroDownTest : public ::testing::Test
+{
+protected:
+ MacroDown md;
+};
+
+TEST_F(MacroDownTest, TwoStepRendering)
+{
+ std::string input = "# Hello\n\nWorld";
+
+ // Step 1: Parse
+ auto root = md.parse(input);
+ ASSERT_NE(root, nullptr);
+
+ // Step 2: Render
+ std::string html = md.render(*root);
+ EXPECT_EQ(html, "<h1>Hello</h1>\n<p>World</p>\n");
+}
+
+TEST_F(MacroDownTest, CustomMacro)
+{
+ md.getEvaluator().define("greet", {"name"}, "Hello, %name!");
+
+ std::string input = "Say %greet{User}";
+ auto root = md.parse(input);
+ std::string html = md.render(*root);
+
+ EXPECT_EQ(html, "<p>Say Hello, User!</p>\n");
+}
+
+TEST_F(MacroDownTest, MarkdownElements)
+{
+ // Emphasis
+ EXPECT_EQ(md.render(*md.parse("*em*")), "<p><em>em</em></p>\n");
+
+ // Strong
+ EXPECT_EQ(md.render(*md.parse("**bold**")), "<p><strong>bold</strong></p>\n");
+
+ // Link
+ EXPECT_EQ(md.render(*md.parse("[Link](url)")), "<p><a href=\"url\">Link</a></p>\n");
+
+ // Code
+ EXPECT_EQ(md.render(*md.parse("`code`")), "<p><code>code</code></p>\n");
+}
+
+TEST_F(MacroDownTest, BlockQuote)
+{
+ std::string input = "> Hello\n> World";
+ std::string expected = "<blockquote>\n<p>Hello\nWorld</p>\n</blockquote>\n";
+ EXPECT_EQ(md.render(*md.parse(input)), expected);
+}
+
+TEST_F(MacroDownTest, MixedContent)
+{
+ std::string input = "# Header\n\nParagraph with *em* and %code{macros}.";
+ std::string expected = "<h1>Header</h1>\n<p>Paragraph with <em>em</em> and <code>macros</code>.</p>\n";
+ EXPECT_EQ(md.render(*md.parse(input)), expected);
+}
\ No newline at end of file