← All tasks
cppcodex/cpp-t1 #30Not a task: already works

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 += "&amp;"; break;
      case '<': out += "&lt;"; break;
      case '>': out += "&gt;"; break;
      case '"': out += "&quot;"; break;
      case '\'': out += "&#39;"; 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 + "\">&#8617;</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;
  }
}