BareGit

Implement image markup support and refactor inline parsing

Author: MetroWind <chris.corsair@gmail.com>
Date: Fri Mar 13 21:50:05 2026 -0700
Commit: 9ed71fcc6dfbe91fa347415d9d04a746d10904e6

Changes

diff --git a/src/parser.cpp b/src/parser.cpp
index 10f8ebc..e159661 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -3,6 +3,7 @@
 #include "uni_algo/all.h"
 #include <iostream>
 #include <regex>
+#include <optional>
 
 namespace macrodown
 {
@@ -64,6 +65,7 @@ public:
             if (handleDelimitedMarkup()) continue;
             if (handleMacro()) continue;
             if (handleCode()) continue;
+            if (handleImage()) continue;
             if (handleLink()) continue;
             if (handleEmphasis()) continue;
 
@@ -310,57 +312,91 @@ private:
         return false;
     }
 
+    // Handles Markdown images (![alt](url)).
+    bool handleImage()
+    {
+        if (pos_ + 1 >= input32_.length() || input32_[pos_] != '!' || input32_[pos_ + 1] != '[')
+            return false;
+
+        auto res = findLabelAndUrl(pos_ + 2);
+        if (!res) return false;
+
+        flushText();
+        addLinkLikeMacro("img", res->url, res->label);
+        pos_ = res->end_pos;
+        return true;
+    }
+
     // Handles Markdown links ([text](url)).
     // Recursively parses the link text.
     bool handleLink()
     {
-        if (input32_[pos_] == '[')
-        {
-            size_t label_start = pos_ + 1;
-            size_t j = label_start;
-            int bracket_bal = 1;
-            while (j < input32_.length() && bracket_bal > 0)
-            {
-                if (input32_[j] == '[') bracket_bal++;
-                else if (input32_[j] == ']') bracket_bal--;
-                if (bracket_bal > 0) j++;
-            }
+        if (input32_[pos_] != '[')
+            return false;
 
-            if (j < input32_.length() && bracket_bal == 0)
-            {
-                size_t close_bracket = j;
-                if (close_bracket + 1 < input32_.length() && input32_[close_bracket + 1] == '(')
-                {
-                    size_t url_start = close_bracket + 2;
-                    size_t url_end = input32_.find(')', url_start);
-                    if (url_end != std::u32string::npos)
-                    {
-                        flushText();
-                        std::u32string label32 = input32_.substr(label_start, close_bracket - label_start);
-                        std::u32string url32 = input32_.substr(url_start, url_end - url_start);
-
-                        Macro macro;
-                        macro.name = "link";
-
-                        // Arg 1: URL
-                        Group group1;
-                        group1.addChild(std::make_unique<Node>(Text{una::utf32to8(url32)}));
-                        macro.arguments.push_back(std::make_unique<Node>(std::move(group1)));
-
-                        // Arg 2: Text (parsed)
-                        Group group2;
-                        auto sub = Parser::parse(una::utf32to8(label32), prefix_markups_, delimited_markups_);
-                        for (auto& n : sub) group2.addChild(std::move(n));
-                        macro.arguments.push_back(std::make_unique<Node>(std::move(group2)));
-
-                        nodes_.push_back(std::make_unique<Node>(std::move(macro)));
-                        pos_ = url_end + 1;
-                        return true;
-                    }
-                }
-            }
+        auto res = findLabelAndUrl(pos_ + 1);
+        if (!res) return false;
+
+        flushText();
+        addLinkLikeMacro("link", res->url, res->label);
+        pos_ = res->end_pos;
+        return true;
+    }
+
+    struct LinkResult
+    {
+        std::u32string label;
+        std::u32string url;
+        size_t end_pos;
+    };
+
+    std::optional<LinkResult> findLabelAndUrl(size_t label_start)
+    {
+        size_t j = label_start;
+        int bracket_bal = 1;
+        while (j < input32_.length() && bracket_bal > 0)
+        {
+            if (input32_[j] == '[') bracket_bal++;
+            else if (input32_[j] == ']') bracket_bal--;
+            if (bracket_bal > 0) j++;
         }
-        return false;
+
+        if (j >= input32_.length() || bracket_bal != 0)
+            return std::nullopt;
+
+        size_t close_bracket = j;
+        if (close_bracket + 1 >= input32_.length() || input32_[close_bracket + 1] != '(')
+            return std::nullopt;
+
+        size_t url_start = close_bracket + 2;
+        size_t url_end = input32_.find(')', url_start);
+        if (url_end == std::u32string::npos)
+            return std::nullopt;
+
+        return LinkResult{
+            input32_.substr(label_start, close_bracket - label_start),
+            input32_.substr(url_start, url_end - url_start),
+            url_end + 1
+        };
+    }
+
+    void addLinkLikeMacro(const std::string& name, const std::u32string& url, const std::u32string& label)
+    {
+        Macro macro;
+        macro.name = name;
+
+        // Arg 1: URL
+        Group group1;
+        group1.addChild(std::make_unique<Node>(Text{una::utf32to8(url)}));
+        macro.arguments.push_back(std::make_unique<Node>(std::move(group1)));
+
+        // Arg 2: Label (parsed)
+        Group group2;
+        auto sub = Parser::parse(una::utf32to8(label), prefix_markups_, delimited_markups_);
+        for (auto& n : sub) group2.addChild(std::move(n));
+        macro.arguments.push_back(std::make_unique<Node>(std::move(group2)));
+
+        nodes_.push_back(std::make_unique<Node>(std::move(macro)));
     }
 
     // Handles emphasis (*em*) and strong emphasis (**strong**).
diff --git a/tests/test_macrodown.cpp b/tests/test_macrodown.cpp
index 64d7c66..9e16cd3 100644
--- a/tests/test_macrodown.cpp
+++ b/tests/test_macrodown.cpp
@@ -40,6 +40,9 @@ TEST(MacroDownTest, MarkdownElements)
     // Link
     EXPECT_EQ(md.render(*md.parse("[Link](url)")), "<p><a href=\"url\">Link</a></p>\n");
 
+    // Image
+    EXPECT_EQ(md.render(*md.parse("![Alt Text](img.jpg)")), "<p><img src=\"img.jpg\" alt=\"Alt Text\" /></p>\n");
+
     // Code
     EXPECT_EQ(md.render(*md.parse("`code`")), "<p><code>code</code></p>\n");
 }