stamd

Static Markdown Page Generator
git clone git://git.dimitrijedobrota.com/stamd.git
Log | Files | Refs | README | LICENSE

commit 4b021f8ceab0565d655f5afecf4286cd18b63f42
parent 0683d0e43044ef897c4d7dbc1cffbda2010193a9
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Wed, 26 Jun 2024 18:29:41 +0200

Consistency and stability improvements

Diffstat:
MCMakeLists.txt | 2+-
Msource/article.cpp | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msource/article.hpp | 27++++++++++++++-------------
Msource/index.cpp | 27+++++++++++++++------------
Msource/main.cpp | 40+++++++++++++++++++++++++++++++---------
5 files changed, 131 insertions(+), 61 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( stamd - VERSION 0.2.1 + VERSION 0.2.2 DESCRIPTION "Static Markdown Page Generator" HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamd.git" LANGUAGES CXX diff --git a/source/article.cpp b/source/article.cpp @@ -1,4 +1,5 @@ #include <format> +#include <iostream> #include <numeric> #include "article.hpp" @@ -8,18 +9,47 @@ #include "utility.hpp" +std::optional<std::string> article::get(const std::string& key) const +{ + const auto itr = m_symbols.find(key); + if (itr == end(m_symbols)) + { + std::cerr << "Warning: getting invalid value for: " << key << std::endl; + return {}; + } + return itr->second; +} + +std::string article::get_filename() const +{ + return m_symbols.find("filename")->second; +} + +std::string article::get_date() const +{ + return get("date").value_or("0000-00-00"); +} + +std::string article::get_title() const +{ + return get("title").value_or(get_filename()); +} + +std::string article::get_language() const +{ + return get("language").value_or("en"); +} + void article::print_nav(std::ostream& ost) { using namespace hemplate; // NOLINT static const char* base = "https://dimitrijedobrota.com/blog"; - ost << html::div() - .add(html::nav() - .add(html::a("&lt;-- back", {{"class", "back"}})) - .add(html::a("index", {{"href", base}})) - .add(html::a("hime --&gt;", {{"href", "/"}}))) - .add(html::hr()); + ost << html::nav() + .add(html::a("&lt;-- back", {{"class", "back"}})) + .add(html::a("index", {{"href", base}})) + .add(html::a("home --&gt;", {{"href", "/"}})); } void article::print_categories(std::ostream& ost, @@ -27,20 +57,16 @@ void article::print_categories(std::ostream& ost, { using namespace hemplate; // NOLINT - ost << html::div( - attributeList({{"class", "categories"}}), - std::accumulate( - begin(categories), - end(categories), - elementList(html::h3("Categories: "), html::p()), - [](elementList&& list, std::string ctgry) - { - normalize(ctgry); - list.add( - html::a(ctgry, {{"href", std::format("./{}.html", ctgry)}})); - return std::move(list); - }) - .add(html::p())); + ost << html::nav().set("class", "categories"); + ost << html::h3("Categories: "); + ost << html::p(); + for (auto ctgry : categories) + { + normalize(ctgry); + ost << html::a(ctgry, {{"href", std::format("./{}.html", ctgry)}}); + } + ost << html::p(); + ost << html::nav(); } void article::write(const std::string& data, std::ostream& ost) @@ -48,8 +74,10 @@ void article::write(const std::string& data, std::ostream& ost) using namespace hemplate; // NOLINT static const char* description_s = - "Dimitrije Dobrota's personal site. You can find my daily findings in a " - "form of articles on my blog as well as various programming projects."; + "Dimitrije Dobrota's personal site. You can find my daily " + "findings in a " + "form of articles on my blog as well as various programming " + "projects."; static const attributeList icon = {{"rel", "icon"}, {"type", "image/png"}}; static const attributeList style = {{"rel", "stylesheet"}, @@ -83,21 +111,37 @@ void article::write(const std::string& data, std::ostream& ost) .set("type", "checkbox") .set("id", "theme_switch") .set("class", "theme_switch"); + + ost << html::div().set("id", "content"); + + if (!m_nonav) + { + ost << html::header(); + print_nav(ost); + ost << html::hr(); + ost << html::header(); + } + ost << html::main(); - ost << html::div().set("class", "content"); ost << html::label(" ") .set("for", "theme_switch") .set("class", "switch_label"); - if (!m_nonav) print_nav(ost); if (!m_categories.empty()) print_categories(ost, m_categories); ost << data; - if (!m_nonav) print_nav(ost); + ost << html::main(); + + if (!m_nonav) + { + ost << html::footer(); + ost << html::hr(); + print_nav(ost); + ost << html::footer(); + } ost << html::div(); - ost << html::main(); ost << html::script(" ").set("source", "/scripts/main.js"); ost << html::body(); ost << html::html(); diff --git a/source/article.hpp b/source/article.hpp @@ -1,5 +1,6 @@ #pragma once +#include <optional> #include <set> #include <string> #include <unordered_map> @@ -11,17 +12,17 @@ public: using symbols_t = std::unordered_map<std::string, std::string>; using categories_t = std::set<std::string>; - explicit article(std::string name, categories_t categories = {}) - : m_name(std::move(name)) - , m_categories(std::move(categories)) + explicit article(std::string filename, categories_t categories = {}) + : m_categories(std::move(categories)) + , m_symbols({{"filename", filename}}) { } void write(const std::string& data, std::ostream& ost); - void emplace(const std::string& category) { m_categories.emplace(category); } - void emplace(const std::string& key, const std::string& value) + void insert(const std::string& category) { m_categories.emplace(category); } + void insert(const std::string& key, const std::string& value) { - m_symbols.emplace(key, value); + m_symbols.insert_or_assign(key, value); } auto get_categories() const { return m_categories; } @@ -31,21 +32,21 @@ public: bool is_hidden() const { return m_hidden; } - const std::string& get_language() { return m_symbols.find("lang")->second; } - const std::string& get_title() { return m_symbols.find("title")->second; } - const std::string& get_date() { return m_symbols.find("date")->second; } + std::optional<std::string> get(const std::string& key) const; + + std::string get_filename() const; + std::string get_date() const; + std::string get_title() const; + std::string get_language() const; private: static void print_nav(std::ostream& ost); static void print_categories(std::ostream& ost, const categories_t& categories); - std::string m_name; - bool m_hidden = false; bool m_nonav = false; categories_t m_categories; - symbols_t m_symbols = { - {"title", "test"}, {"lang", "en"}, {"date", "1970-01-01"}}; + symbols_t m_symbols; }; diff --git a/source/index.cpp b/source/index.cpp @@ -25,12 +25,14 @@ void create_index(const std::string& name, for (const auto& article : articles) { if (article->is_hidden()) continue; + + const auto& filename = article->get_filename(); const auto& title = article->get_title(); - const auto& date = article->get_date(); + const auto& date = article->get_date(); strs << html::li() .add(html::div(std::format("{} - ", date))) - .add(html::div().add(html::a(title).set("href", title))); + .add(html::div().add(html::a(title).set("href", filename))); }; strs << html::ul(); @@ -55,11 +57,11 @@ void create_atom(std::ostream& ost, elementList(), [](elementList&& list, const auto& article) { - const auto title = article->get_title(); + const auto filename = article->get_filename(); list.add(atom::entry() - .add(atom::title(title)) + .add(atom::title(filename)) .add(atom::link().set( - "href", std::format("{}/{}.html", base, title))) + "href", std::format("{}/{}", base, filename))) .add(atom::updated(updated)) .add(atom::summary(summary))); return std::move(list); @@ -83,6 +85,7 @@ void create_rss(std::ostream& ost, const article_list& articles) { using namespace hemplate; // NOLINT + static const char* author = "Dimitrije Dobrota"; static const char* email = "mail@dimitrijedobrota.com"; static const char* base = "https://dimitrijedobrota.com/blog"; @@ -96,11 +99,11 @@ void create_rss(std::ostream& ost, elementList(), [](elementList&& list, const auto& article) { - const auto title = article->get_title(); + const auto filename = article->get_filename(); list.add(rss::item() - .add(rss::title(title)) - .add(rss::link(std::format("{}/{}.html", base, title))) - .add(rss::guid(std::format("{}/{}.html", base, title))) + .add(rss::title(filename)) + .add(rss::link(std::format("{}/{}", base, filename))) + .add(rss::guid(std::format("{}/{}", base, filename))) .add(rss::pubDate(updated)) .add(rss::author(std::format("{} ({})", email, author)))); return std::move(list); @@ -130,11 +133,11 @@ void create_sitemap(std::ostream& ost, const article_list& articles) ost << sitemap::urlset(); for (const auto& article : articles) { - const auto& title = article->get_title(); - const auto& date = article->get_date(); + const auto& name = article->get_filename(); + const auto& date = article->get_date(); ost << sitemap::url() - .add(sitemap::loc(std::format("{}/{}", base, title))) + .add(sitemap::loc(std::format("{}/{}.html", base, name))) .add(sitemap::lastmod(date)); } ost << sitemap::urlset(); diff --git a/source/main.cpp b/source/main.cpp @@ -1,3 +1,4 @@ +#include <filesystem> #include <fstream> #include <iostream> #include <sstream> @@ -17,7 +18,7 @@ void preprocess(article& article, std::istream& ist) while (std::getline(ist, line)) { - if (line.empty()) continue; + if (line.empty()) break; if (line[0] != '@') break; { @@ -31,11 +32,11 @@ void preprocess(article& article, std::istream& ist) if (key == "hidden") article.set_hidden(true); else if (key == "nonav") article.set_nonav(true); - else if (key != "categories") article.emplace(key, value); + else if (key != "categories") article.insert(key, value); else { std::istringstream iss(value); - while (std::getline(iss, value, ',')) article.emplace(trim(value)); + while (std::getline(iss, value, ',')) article.insert(trim(value)); } } } @@ -43,7 +44,10 @@ void preprocess(article& article, std::istream& ist) struct arguments_t { std::string output_dir = "."; - std::vector<std::string> articles; + std::vector<std::filesystem::path> files; + bool index = false; + + std::string base = "https://dimitrijedobrota.com/blog"; }; int parse_opt(int key, const char* arg, poafloc::Parser* parser) @@ -54,8 +58,14 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser) case 'o': args->output_dir = arg; break; + case 'b': + args->base = arg; + break; + case 'i': + args->index = true; + break; case poafloc::ARG: - args->articles.emplace_back(arg); + args->files.emplace_back(arg); break; default: break; @@ -66,6 +76,8 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser) // NOLINTBEGIN static const poafloc::option_t options[] = { {"output", 'o', "DIR", 0, "Output directory"}, + {"index", 'i', 0, 0, "Generate all of the indices"}, + {"base", 'b', "URL", 0, "Base URL for the content"}, {0}, }; @@ -96,15 +108,18 @@ int main(int argc, char* argv[]) article_list all_articles; maddy::Parser parser; - for (const auto& name : args.articles) + for (const auto& path : args.files) { - std::ofstream ofs(name + ".out"); - std::ifstream ifs(name); + const std::string filename = path.stem().string() + ".html"; - all_articles.push_back(make_shared<article>(name)); + std::ifstream ifs(path.string()); + all_articles.push_back(make_shared<article>(filename)); auto& article = all_articles.back(); preprocess(*article, ifs); + + // filename can change in preprocessing phase + std::ofstream ofs(article->get_filename()); article->write(parser.Parse(ifs), ofs); if (!article->is_hidden()) @@ -115,6 +130,13 @@ int main(int argc, char* argv[]) } } + if (!args.index) return 0; + + sort(begin(all_articles), + end(all_articles), + [](const auto& lft, const auto& rht) + { return lft->get_date() > rht->get_date(); }); + std::ofstream atom("atom.xml"); std::ofstream rss("rss.xml"); std::ofstream sitemap("sitemap.xml");