Markdown to HTML Converter (cpp, written by Codex)
envgap__codex__cpp-t1-30
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/cpp-t1 #30 · read the task the agent was given
Codex wrote this cpp project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Markdown to HTML Converter Write a program that converts Markdown documents to HTML with support for GitHub Flavored Markdown extensions, syntax highlighting of code blocks, table of contents generation, and custom CSS styling. FUNCTIONAL REQUIREMENTS: - Accept a Markdown file path as a command-line argument - Support standard Markdown: headings (h1-h6), bold, italic, strikethrough, links, images, blockquotes, ordered and unordered lists, horizontal rules, inline code, and code blocks - Support GitHub Flavored Markdown extensions: tables, task lists (checkboxes), fenced code blocks with language identifiers, autolinks, and footnotes - Apply syntax highlighting to fenced code blocks based on the specified language (support at least: python, javascript, java, c++, html, css, json, bash) - Generate a table of contents from headings via --toc flag, inserted at the top of the document with anchor links - Support custom CSS styling via --css flag (path to a CSS file to embed or link in the output) - Support wrapping the output in a complete HTML document (with DOCTYPE, head, body) via --full-page flag, or outputting just the HTML fragment (default) - Support automatic heading ID generation for anchor links based on heading text - Support converting multiple Markdown files via --batch flag, with optional --index flag to generate an index page linking all converted files - Print the HTML output to console by default - Save the HTML to a file via --output flag (default: same base name with .html extension) - If no input file is given, generate a sample Markdown document demonstrating all supported features (headings, lists, code blocks, tables, images, links, task lists, footnotes), convert it, and save both the Markdown source and HTML output - Handle errors: invalid Markdown syntax (graceful degradation), missing linked files, and encoding issues Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.22) project(markdown_to_html_converter LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_executable(markdown_to_html_converter src/main.cpp)
README.md
# Markdown to HTML Converter (C++) Converts Markdown documents to HTML with GFM-style features, code highlighting, TOC generation, CSS support, and batch conversion. ## Requirements - Ubuntu 22.04 - G++ 12+ - CMake 3.22+ ## Dependencies - No external runtime dependencies ## Build ```bash cmake -S . -B build cmake --build build -j ``` ## Run Single file: ```bash ./build/markdown_to_html_converter README.md --toc --full-page --output README.html ``` With CSS: ```bash ./build/markdown_to_html_converter notes.md --css styles.css --full-page ``` Batch conversion and index page: ```bash ./build/markdown_to_html_converter --batch docs/a.md docs/b.md docs/c.md --output out --index --full-page ``` No input file: ```bash ./build/markdown_to_html_converter ``` Generates sample Markdown and HTML files.
src/main.cpp
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace fs = std::filesystem;
struct Heading {
int level;
std::string text;
std::string id;
};
struct Config {
bool toc = false;
std::optional<std::string> css;
bool full_page = false;
bool batch = false;
bool index = false;
std::optional<std::string> output;
std::vector<std::string> inputs;
};
static std::string trim(const std::string& s) {
size_t b = 0;
while (b < s.size() && std::isspace(static_cast<unsigned char>(s[b]))) ++b;
size_t e = s.size();
while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) --e;
return s.substr(b, e - b);
}
static std::string escape_html(const std::string& in) {
std::string out;
out.reserve(in.size());
for (char c : in) {
switch (c) {
case '&': out += "&"; break;
case '<': out += "<"; break;
case '>': out += ">"; break;
case '"': out += """; break;
case '\'': out += "'"; break;
default: out.push_back(c); break;
}
}
return out;
}
template <typename Func>
static std::string regex_replace_cb(const std::string& input, const std::regex& re, Func&& cb) {
std::string out;
size_t last = 0;
for (std::sregex_iterator it(input.begin(), input.end(), re), end; it != end; ++it) {
const auto& m = *it;
out.append(input.substr(last, static_cast<size_t>(m.position()) - last));
out.append(cb(m));
last = static_cast<size_t>(m.position() + m.length());
}
out.append(input.substr(last));
return out;
}
static std::string slugify(const std::string& text, std::set<std::string>& used) {
std::string t;
for (char c : text) {
char lower = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (std::isalnum(static_cast<unsigned char>(lower)) || lower == ' ' || lower == '-') t.push_back(lower);
}
t = trim(t);
for (char& c : t) if (c == ' ') c = '-';
if (t.empty()) t = "section";
std::string candidate = t;
int idx = 2;
while (used.count(candidate)) {
candidate = t + "-" + std::to_string(idx++);
}
used.insert(candidate);
return candidate;
}
static std::string apply_highlight(const std::string& code, const std::string& lang_raw) {
std::string lang = lang_raw;
std::transform(lang.begin(), lang.end(), lang.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
static const std::set<std::string> supported{"python", "javascript", "java", "c++", "cpp", "html", "css", "json", "bash", "sh"};
std::string out = escape_html(code);
if (!supported.count(lang)) return out;
static const std::unordered_map<std::string, std::vector<std::string>> keywords{
{"python", {"def", "class", "import", "from", "if", "else", "for", "while", "return"}},
{"javascript", {"function", "const", "let", "var", "if", "else", "for", "while", "return", "class"}},
{"java", {"public", "private", "class", "static", "void", "if", "else", "for", "while", "return", "new"}},
{"c++", {"int", "double", "class", "public", "private", "if", "else", "for", "while", "return", "auto"}},
{"cpp", {"int", "double", "class", "public", "private", "if", "else", "for", "while", "return", "auto"}},
{"html", {"html", "head", "body", "div", "span", "script", "style"}},
{"css", {"color", "display", "position", "margin", "padding", "font", "background"}},
{"json", {"true", "false", "null"}},
{"bash", {"if", "then", "fi", "for", "do", "done", "echo", "export"}},
{"sh", {"if", "then", "fi", "for", "do", "done", "echo", "export"}},
};
auto it = keywords.find(lang);
if (it != keywords.end()) {
for (const auto& kw : it->second) {
out = std::regex_replace(out, std::regex("\\b" + kw + "\\b"), "<span class=\"kw\">" + kw + "</span>");
}
}
out = std::regex_replace(out, std::regex(R"((\"[^\"]*\"))"), "<span class=\"str\">$1</span>");
out = std::regex_replace(out, std::regex(R"(\b(\d+(\.\d+)?)\b)"), "<span class=\"num\">$1</span>");
return out;
}
static std::string inline_markdown(const std::string& text, std::vector<std::string>& footnote_refs) {
std::string out = escape_html(text);
out = std::regex_replace(out, std::regex(R"(`([^`]+)`)"), "<code>$1</code>");
out = std::regex_replace(out, std::regex(R"(!\[([^\]]*)]\(([^)]+)\))"), "<img alt=\"$1\" src=\"$2\" />");
out = std::regex_replace(out, std::regex(R"(\[([^\]]+)]\(([^)]+)\))"), "<a href=\"$2\">$1</a>");
out = std::regex_replace(out, std::regex(R"(~~([^~]+)~~)"), "<del>$1</del>");
out = std::regex_replace(out, std::regex(R"(\*\*([^*]+)\*\*)"), "<strong>$1</strong>");
out = std::regex_replace(out, std::regex(R"(\*([^*]+)\*)"), "<em>$1</em>");
out = std::regex_replace(out, std::regex(R"(\b(https?://[^\s<]+)\b)"), "<a href=\"$1\">$1</a>");
out = regex_replace_cb(out, std::regex(R"(\[\^([^\]]+)])"), [&](const std::smatch& m) {
std::string id = m[1].str();
auto it = std::find(footnote_refs.begin(), footnote_refs.end(), id);
if (it == footnote_refs.end()) {
footnote_refs.push_back(id);
it = std::prev(footnote_refs.end());
}
size_t index = static_cast<size_t>(std::distance(footnote_refs.begin(), it) + 1);
return "<sup id=\"fnref-" + id + "\"><a href=\"#fn-" + id + "\">[" + std::to_string(index) + "]</a></sup>";
});
return out;
}
struct ParseResult {
std::string fragment;
std::vector<Heading> headings;
};
static ParseResult parse_markdown(const std::string& markdown, bool enable_toc) {
std::vector<std::string> lines;
{
std::istringstream iss(markdown);
std::string line;
while (std::getline(iss, line)) {
if (!line.empty() && line.back() == '\r') line.pop_back();
lines.push_back(line);
}
}
std::map<std::string, std::string> footnote_defs;
std::vector<std::string> filtered;
std::regex foot_def_rx(R"(^\[\^([^\]]+)]:\s*(.+)$)");
for (const auto& line : lines) {
std::smatch m;
if (std::regex_match(line, m, foot_def_rx)) footnote_defs[m[1].str()] = m[2].str();
else filtered.push_back(line);
}
std::set<std::string> used_ids;
std::vector<Heading> headings;
std::vector<std::string> foot_refs;
std::vector<std::string> html;
std::vector<std::string> paragraph;
size_t i = 0;
auto flush_paragraph = [&]() {
if (!paragraph.empty()) {
std::string joined;
for (size_t k = 0; k < paragraph.size(); ++k) {
if (k) joined += " ";
joined += paragraph[k];
}
html.push_back("<p>" + inline_markdown(joined, foot_refs) + "</p>");
paragraph.clear();
}
};
while (i < filtered.size()) {
const std::string line = filtered[i];
if (trim(line).empty()) {
flush_paragraph();
++i;
continue;
}
std::smatch heading_m;
if (std::regex_match(line, heading_m, std::regex(R"(^(#{1,6})\s+(.+)$)"))) {
flush_paragraph();
int level = static_cast<int>(heading_m[1].str().size());
std::string text = trim(heading_m[2].str());
std::string id = slugify(text, used_ids);
headings.push_back({level, text, id});
html.push_back("<h" + std::to_string(level) + " id=\"" + id + "\">" + inline_markdown(text, foot_refs) + "</h" + std::to_string(level) + ">");
++i;
continue;
}
if (std::regex_match(trim(line), std::regex(R"(^(-{3,}|\*{3,}|_{3,})$)"))) {
flush_paragraph();
html.push_back("<hr />");
++i;
continue;
}
std::smatch fence_m;
if (std::regex_match(line, fence_m, std::regex(R"(^```([a-zA-Z0-9+_-]*)\s*$)"))) {
flush_paragraph();
std::string lang = fence_m[1].str();
++i;
std::string code;
while (i < filtered.size() && !std::regex_match(filtered[i], std::regex(R"(^```.*$)"))) {
code += filtered[i];
code += "\n";
++i;
}
if (i < filtered.size()) ++i;
html.push_back("<pre><code class=\"language-" + escape_html(lang) + "\">" + apply_highlight(code, lang) + "</code></pre>");
continue;
}
if (!line.empty() && line[0] == '>') {
flush_paragraph();
std::string block;
while (i < filtered.size() && !filtered[i].empty() && filtered[i][0] == '>') {
block += std::regex_replace(filtered[i], std::regex(R"(^>\s?)"), "");
block += " ";
++i;
}
html.push_back("<blockquote>" + inline_markdown(trim(block), foot_refs) + "</blockquote>");
continue;
}
if (std::regex_search(line, std::regex(R"(^(\*|-|\+|\d+\.)\s+)"))) {
flush_paragraph();
bool ordered = std::regex_search(line, std::regex(R"(^\d+\.\s+)"));
std::string tag = ordered ? "ol" : "ul";
std::string list_html = "<" + tag + ">";
while (i < filtered.size() && std::regex_search(filtered[i], std::regex(R"(^(\*|-|\+|\d+\.)\s+)"))) {
std::string raw = std::regex_replace(filtered[i], std::regex(R"(^(\*|-|\+|\d+\.)\s+)"), "");
std::smatch task_m;
if (std::regex_match(raw, task_m, std::regex(R"(^\[(x|X| )]\s+(.+)$)"))) {
bool checked = task_m[1].str() == "x" || task_m[1].str() == "X";
list_html += "<li><input type=\"checkbox\" disabled" + std::string(checked ? " checked" : "") + " /> "
+ inline_markdown(task_m[2].str(), foot_refs) + "</li>";
} else {
list_html += "<li>" + inline_markdown(raw, foot_refs) + "</li>";
}
++i;
}
list_html += "</" + tag + ">";
html.push_back(list_html);
continue;
}
if (line.find('|') != std::string::npos && i + 1 < filtered.size()
&& std::regex_match(filtered[i + 1], std::regex(R"(^[:\-\|\s]+$)"))) {
flush_paragraph();
auto split_cells = [](const std::string& row) {
std::vector<std::string> cells;
std::stringstream ss(row);
std::string cell;
while (std::getline(ss, cell, '|')) {
cell = trim(cell);
if (!cell.empty()) cells.push_back(cell);
}
return cells;
};
std::vector<std::string> header = split_cells(line);
i += 2;
std::vector<std::vector<std::string>> rows;
while (i < filtered.size() && filtered[i].find('|') != std::string::npos) {
rows.push_back(split_cells(filtered[i]));
++i;
}
std::string table = "<table><thead><tr>";
for (const auto& h : header) table += "<th>" + inline_markdown(h, foot_refs) + "</th>";
table += "</tr></thead><tbody>";
for (const auto& r : rows) {
table += "<tr>";
for (const auto& c : r) table += "<td>" + inline_markdown(c, foot_refs) + "</td>";
table += "</tr>";
}
table += "</tbody></table>";
html.push_back(table);
continue;
}
paragraph.push_back(line);
++i;
}
flush_paragraph();
if (!foot_refs.empty()) {
std::string f = "<section class=\"footnotes\"><hr /><ol>";
for (const auto& id : foot_refs) {
auto it = footnote_defs.find(id);
std::string text = it == footnote_defs.end() ? "(missing footnote)" : it->second;
f += "<li id=\"fn-" + id + "\">" + inline_markdown(text, foot_refs) + " <a href=\"#fnref-" + id + "\">↩</a></li>";
}
f += "</ol></section>";
html.push_back(f);
}
std::string toc_html;
if (enable_toc && !headings.empty()) {
toc_html = "<nav class=\"toc\"><h2>Table of Contents</h2><ul>";
for (const auto& h : headings) {
toc_html += "<li class=\"toc-level-" + std::to_string(h.level) + "\"><a href=\"#" + h.id + "\">"
+ escape_html(h.text) + "</a></li>";
}
toc_html += "</ul></nav>";
}
std::string fragment = toc_html;
for (const auto& block : html) {
fragment += block + "\n";
}
return {fragment, headings};
}
static std::string default_css() {
return R"(body { font-family: Arial, sans-serif; margin: 2rem; line-height: 1.6; }
pre { background: #f4f4f4; padding: 1rem; overflow-x: auto; }
code { font-family: Consolas, monospace; }
table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
blockquote { border-left: 4px solid #ddd; margin: 1rem 0; padding-left: 1rem; color: #555; }
.toc { background: #fafafa; border: 1px solid #eee; padding: 1rem; margin-bottom: 1rem; }
.kw { color: #0a4; font-weight: bold; }
.str { color: #b03; }
.num { color: #06c; })";
}
static std::string wrap_full_page(const std::string& fragment, const std::string& title, const std::optional<std::string>& css_path) {
std::string css_block = "<style>" + default_css() + "</style>";
if (css_path.has_value()) {
fs::path p = *css_path;
if (fs::exists(p)) {
std::ifstream in(p);
std::stringstream ss;
ss << in.rdbuf();
css_block = "<style>" + ss.str() + "</style>";
} else {
css_block = "<link rel=\"stylesheet\" href=\"" + escape_html(*css_path) + "\" />";
}
}
return "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
" <title>" + escape_html(title) + "</title>\n " + css_block + "\n"
" </head>\n <body>\n" + fragment + " </body>\n</html>\n";
}
static std::string remove_ext(const std::string& name) {
size_t pos = name.find_last_of('.');
if (pos == std::string::npos) return name;
return name.substr(0, pos);
}
static Config parse_args(int argc, char** argv) {
Config cfg;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--toc") cfg.toc = true;
else if (arg == "--full-page") cfg.full_page = true;
else if (arg == "--batch") cfg.batch = true;
else if (arg == "--index") cfg.index = true;
else if (arg == "--css") {
if (i + 1 >= argc) throw std::runtime_error("Missing value for --css");
cfg.css = std::string(argv[++i]);
} else if (arg == "--output") {
if (i + 1 >= argc) throw std::runtime_error("Missing value for --output");
cfg.output = std::string(argv[++i]);
} else if (arg.rfind("--", 0) == 0) {
throw std::runtime_error("Unknown option: " + arg);
} else {
cfg.inputs.push_back(arg);
}
}
return cfg;
}
static std::string read_text(const fs::path& p) {
std::ifstream in(p, std::ios::binary);
if (!in) throw std::runtime_error("Failed to read file: " + p.string());
std::stringstream ss;
ss << in.rdbuf();
return ss.str();
}
static void write_text(const fs::path& p, const std::string& text) {
std::ofstream out(p, std::ios::binary);
if (!out) throw std::runtime_error("Failed to write file: " + p.string());
out << text;
}
static std::string sample_markdown() {
return R"(# Markdown Demo
## Features
- [x] Task done
- [ ] Task pending
| Language | Status |
|---|---|
| C++ | Great |
| JavaScript | Great |
Inline code: `int x = 1;`
> This is a blockquote with a [link](https://example.com).
```cpp
int add(int a, int b) {
return a + b;
}
```
Footnote reference[^note].
[^note]: This is a sample footnote.
)";
}
static std::string make_index(const std::vector<fs::path>& files) {
std::string links = "<h1>Converted Markdown Index</h1><ul>";
for (const auto& p : files) {
auto n = p.filename().string();
links += "<li><a href=\"" + escape_html(n) + "\">" + escape_html(n) + "</a></li>";
}
links += "</ul>";
return wrap_full_page(links, "Markdown Index", std::nullopt);
}
int main(int argc, char** argv) {
try {
Config cfg = parse_args(argc, argv);
if (cfg.inputs.empty()) {
fs::path md = fs::absolute("sample_markdown.md");
fs::path html = fs::absolute("sample_markdown.html");
write_text(md, sample_markdown());
auto parsed = parse_markdown(sample_markdown(), true);
std::string rendered = wrap_full_page(parsed.fragment, "Sample Markdown", cfg.css);
write_text(html, rendered);
std::cout << rendered << "\n";
std::cout << "Generated sample files: " << md << ", " << html << "\n";
return 0;
}
if (!cfg.batch) {
fs::path input = fs::absolute(cfg.inputs.front());
if (!fs::exists(input)) throw std::runtime_error("Input file not found: " + input.string());
std::string md = read_text(input);
auto parsed = parse_markdown(md, cfg.toc);
std::string html = cfg.full_page ? wrap_full_page(parsed.fragment, input.filename().string(), cfg.css) : parsed.fragment;
fs::path out = cfg.output.has_value() ? fs::absolute(*cfg.output) : input.parent_path() / (remove_ext(input.filename().string()) + ".html");
write_text(out, html);
std::cout << html << "\n";
return 0;
}
fs::path out_dir = cfg.output.has_value() ? fs::absolute(*cfg.output) : fs::current_path();
fs::create_directories(out_dir);
std::vector<fs::path> converted;
for (const auto& in : cfg.inputs) {
fs::path input = fs::absolute(in);
if (!fs::exists(input)) {
std::cerr << "Warning: missing input file " << input << "\n";
continue;
}
std::string md = read_text(input);
auto parsed = parse_markdown(md, cfg.toc);
std::string html = cfg.full_page ? wrap_full_page(parsed.fragment, input.filename().string(), cfg.css) : parsed.fragment;
fs::path out = out_dir / (remove_ext(input.filename().string()) + ".html");
write_text(out, html);
converted.push_back(out);
std::cout << html << "\n";
}
if (cfg.index) {
fs::path index = out_dir / "index.html";
write_text(index, make_index(converted));
std::cout << "Generated index page: " << index << "\n";
}
return 0;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << "\n";
return 1;
}
}