diff options
| author | Alif Radhitya <radhitya@noreply.codeberg.org> | 2026-06-25 17:53:21 +0200 |
|---|---|---|
| committer | Alif Radhitya <radhitya@noreply.codeberg.org> | 2026-06-25 17:53:21 +0200 |
| commit | e2ef625c779dffd6a580c0aa8d1d57dcf7abd7eb (patch) | |
| tree | 32c9da33a864c744fede28fe11654fc7bbd479bc /src/main.cpp | |
| parent | 829b4e3cf1e59732d0166cbd24d31c93c4095977 (diff) | |
| parent | 85133d6dcaa7b02d67e243ce8df8324e46f4009f (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.cpp | 250 |
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; +} |
