summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorAlif Radhitya <radhitya@noreply.codeberg.org>2026-06-25 17:53:21 +0200
committerAlif Radhitya <radhitya@noreply.codeberg.org>2026-06-25 17:53:21 +0200
commite2ef625c779dffd6a580c0aa8d1d57dcf7abd7eb (patch)
tree32c9da33a864c744fede28fe11654fc7bbd479bc /src/main.cpp
parent829b4e3cf1e59732d0166cbd24d31c93c4095977 (diff)
parent85133d6dcaa7b02d67e243ce8df8324e46f4009f (diff)
Merge pull request 'farewell adieu rust' (#1) from exp into master
Reviewed-on: https://codeberg.org/radhitya/ahsi/pulls/1
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp250
1 files changed, 250 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..88a2f57
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,250 @@
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <toml++/toml.hpp>
+#include <fstream>
+#include <cassert>
+#include <cmark.h>
+#include <cstdlib>
+#include <inja/inja.hpp>
+#include <nlohmann/json.hpp>
+#include <cstring>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+#include <algorithm>
+
+using json = nlohmann::json;
+using namespace std::string_literals;
+
+struct MainConfig {
+ std::string title;
+ std::string url;
+ std::string author;
+ int posts_per_page = 5;
+};
+
+struct FrontmatterConfig {
+ std::string title;
+ std::string date;
+ std::string categories;
+ std::string tags;
+ bool draft;
+ std::string body;
+};
+
+struct Config {
+ MainConfig main;
+ FrontmatterConfig frontmatter;
+};
+
+
+struct Post {
+ std::string slug;
+ std::string title;
+ std::string date;
+ std::string body_html;
+};
+
+Config
+parse_config(const std::string& filepath)
+{
+ toml::table tbl;
+ Config cfg;
+
+ try {
+ tbl = toml::parse_file(filepath);
+ } catch (const toml::parse_error& err) {
+ std::cerr << "parsing failed: " << err << std::endl;
+ throw;
+ }
+
+ if (auto* main = tbl["main"].as_table()) {
+ cfg.main.title = (*main)["title"].value_or("untitled's blog");
+ cfg.main.url = (*main)["url"].value_or("localhost");
+ cfg.main.author = (*main)["author"].value_or("linus");
+ cfg.main.posts_per_page = (int)(*main)["posts_per_page"].value_or(5LL);
+ }
+ return cfg;
+}
+
+Config
+frontmatter(const std::string& filepath)
+{
+ Config cfg;
+ std::ifstream file(filepath);
+ std::string content((std::istreambuf_iterator<char>(file)),
+ std::istreambuf_iterator<char>());
+
+ const size_t first_fence = content.find("+++");
+ const size_t second_fence = (first_fence == std::string::npos)
+ ? std::string::npos
+ : content.find("+++", first_fence + 3);
+
+ if (first_fence != std::string::npos && second_fence != std::string::npos) {
+ std::string fm = content.substr(first_fence + 3,
+ second_fence - (first_fence+3));
+ try {
+ toml::parse_result result = toml::parse(fm);
+ const toml::table& tbl = result;
+ cfg.frontmatter.title = tbl["title"].value_or(""s);
+ cfg.frontmatter.date = tbl["date"].value_or(""s);
+ cfg.frontmatter.categories = tbl["categories"].value_or(""s);
+ cfg.frontmatter.tags = tbl["tags"].value_or(""s);
+ cfg.frontmatter.draft = tbl["draft"].value_or(true);
+ } catch(const toml::parse_error& err) {
+ std::cerr << "parsing failed: " << err << std::endl;
+ throw;
+ }
+ cfg.frontmatter.body = content.substr(second_fence + 3);
+ }
+ return cfg;
+}
+
+std::string
+markdown_processing(const std::string& md) {
+ char *raw = cmark_markdown_to_html(md.c_str(), md.size(),0);
+ std::string html(raw);
+ free(raw);
+ return html;
+}
+
+std::string
+strip_html(const std::string& html) {
+ std::string out;
+ bool in_tag = false;
+ for (char c : html) {
+ if (c == '<') in_tag = true;
+ else if (c == '>') in_tag = false;
+ else if (!in_tag) out += c;
+ }
+ return out;
+}
+
+int main() {
+ inja::Environment env;
+ nlohmann::json site;
+ system("rm -rf public");
+ if (mkdir("public", 0755) != 0) {
+ std::cerr << "cant create public" << std::endl;
+ return 1;
+ }
+ system("cp -r static/. public/");
+
+ Config my_config = parse_config("config.toml");
+ int ppp = my_config.main.posts_per_page;
+ if (ppp < 1) ppp = 5;
+
+ site["site"]["title"] = my_config.main.title;
+ site["site"]["author"] = my_config.main.author;
+ site["site"]["url"] = my_config.main.url;
+
+ std::vector<Post> posts;
+ std::string intro_html;
+
+ DIR *dir = opendir("content");
+ if (!dir) {
+ std::cerr << "cant open content/" << std::endl;
+ return 1;
+ }
+
+ struct dirent* ent;
+ while ((ent = readdir(dir)) != nullptr) {
+ if (ent->d_name[0] == '.') continue;
+ const char* ext = strrchr(ent->d_name, '.');
+ if (!ext || strcmp(ext, ".md") != 0) continue;
+
+ std::string fname(ent->d_name);
+ std::string slug(fname.c_str(), ext - ent->d_name);
+ std::string fpath = "content/" + fname;
+
+ Config page = frontmatter(fpath);
+
+ if (slug == "_index") {
+ intro_html = markdown_processing(page.frontmatter.body);
+ continue;
+ }
+
+ if (page.frontmatter.draft) continue;
+
+ posts.push_back({
+ slug,
+ page.frontmatter.title.empty() ? slug : page.frontmatter.title,
+ page.frontmatter.date,
+ markdown_processing(page.frontmatter.body)
+ });
+ }
+ closedir(dir);
+
+ std::sort(posts.begin(), posts.end(),
+ [](const Post& a, const Post& b) { return a.date > b.date; });
+
+ for (const auto& p : posts) {
+ nlohmann::json data = site;
+ data["title"] = p.title;
+ data["date"] = p.date;
+ data["draft"] = false;
+ data["body"] = p.body_html;
+ std::string html = env.render_file("templates/post.html", data);
+ std::string outdir = "public/" + p.slug;
+ mkdir(outdir.c_str(), 0755);
+ std::ofstream out(outdir + "/index.html");
+ out << html;
+ }
+
+ int total = (int)posts.size();
+ int pages = total > 0 ? (total + ppp - 1) / ppp : 1;
+
+ for (int page = 1; page <= pages; page++) {
+ nlohmann::json data = site;
+ data["intro"] = intro_html;
+ json arr = json::array();
+ int start = (page - 1) * ppp;
+ int end = std::min(start + ppp, total);
+ for (int i = start; i < end; i++) {
+ arr.push_back({
+ {"title", posts[i].title},
+ {"slug", posts[i].slug},
+ {"date", posts[i].date},
+ {"excerpt", posts[i].body_html}
+ });
+ }
+ data["posts"] = arr;
+ data["page"] = page;
+ data["total_pages"] = pages;
+ data["has_prev"] = (page > 1);
+ data["has_next"] = (page < pages);
+ data["prev_url"] = (page == 2) ? "/" : ("/page/" + std::to_string(page - 1) + "/");
+ data["next_url"] = "/page/" + std::to_string(page + 1) + "/";
+
+ std::string outdir = (page == 1) ? "public" : ("public/page/" + std::to_string(page));
+ if (page > 1) system(("mkdir -p " + outdir).c_str());
+ std::string html = env.render_file("templates/index.html", data);
+ std::ofstream out(outdir + "/index.html");
+ out << html;
+ }
+
+ json search_idx = json::array();
+ for (const auto& p : posts) {
+ search_idx.push_back({
+ {"title", p.title},
+ {"slug", p.slug},
+ {"date", p.date},
+ {"text", strip_html(p.body_html)}
+ });
+ }
+ std::ofstream sj("public/search.json");
+ sj << search_idx.dump();
+
+ nlohmann::json data = site;
+ data["title"] = "Search";
+ system("mkdir -p public/search");
+ std::string html = env.render_file("templates/search.html", data);
+ std::ofstream so("public/search/index.html");
+ so << html;
+
+ std::cout << "wrote " << posts.size() << " posts, "
+ << pages << " page(s), search" << std::endl;
+ return 0;
+}