commit 68acb3567a5817c6f1545d11b6c7d74497d8a0e4
parent 9fa65259932392378f0c05e045f95c009db79b79
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Fri, 3 Jan 2025 21:35:07 +0100
Version 0.3
* Improve naming convention
* Improve decomposition
* Rework category index creation
* Each article is aware of cmd options
Diffstat:
9 files changed, 107 insertions(+), 86 deletions(-)
diff --git a/.clang-tidy b/.clang-tidy
@@ -61,7 +61,7 @@ CheckOptions:
- key: 'readability-identifier-naming.AbstractClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassCase'
- value: 'camelBack'
+ value: 'CamelCase'
- key: 'readability-identifier-naming.ClassConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMemberCase'
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)
project(
stamd
- VERSION 1.2.10
+ VERSION 0.3.0
DESCRIPTION "Static Markdown Page Generator"
HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamd.git"
LANGUAGES CXX
diff --git a/README.md b/README.md
@@ -38,11 +38,15 @@ See the [BUILDING](BUILDING.md) document.
## Version History
+- 0.3
+ * Generate sitemap.xml and robots.txt
+ * Generate rss.xml and atom.txt feeds
+ * Configurable links
+ * Category indexes
- 0.2
* C++ rewrite
* Improved stability
* Improve readability
- * Generate sitemap.xml and robots.txt
- 0.1
* Quick and dirty proof of concept written in C
diff --git a/source/article.cpp b/source/article.cpp
@@ -13,7 +13,7 @@
namespace stamd {
-std::optional<std::string> article::get(const std::string& key) const
+std::optional<std::string> Article::get(const std::string& key) const
{
const auto itr = m_symbols.find(key);
if (itr == end(m_symbols))
@@ -24,39 +24,37 @@ std::optional<std::string> article::get(const std::string& key) const
return itr->second;
}
-std::string article::get_filename() const
+std::string Article::get_filename() const
{
return m_symbols.find("filename")->second;
}
-std::string article::get_date() const
+std::string Article::get_date() const
{
return get("date").value_or("0000-00-00");
}
-std::string article::get_title() const
+std::string Article::get_title() const
{
return get("title").value_or(get_filename());
}
-std::string article::get_language() const
+std::string Article::get_language() const
{
return get("language").value_or("en");
}
-void article::print_nav(std::ostream& ost)
+void Article::print_nav(std::ostream& ost, const std::string& base)
{
using namespace hemplate; // NOLINT
- static const char* base = "https://dimitrijedobrota.com/blog";
-
ost << html::nav()
.add(html::a("<-- back", {{"class", "back"}}))
.add(html::a("index", {{"href", base}}))
.add(html::a("home -->", {{"href", "/"}}));
}
-void article::print_categories(std::ostream& ost,
+void Article::print_categories(std::ostream& ost,
const categories_t& categories)
{
using namespace hemplate; // NOLINT
@@ -74,7 +72,7 @@ void article::print_categories(std::ostream& ost,
ost << html::nav();
}
-void article::write_header(std::ostream& ost) const
+void Article::write_header(std::ostream& ost) const
{
using namespace hemplate; // NOLINT
@@ -134,7 +132,7 @@ void article::write_header(std::ostream& ost) const
if (!m_nonav)
{
ost << html::header();
- print_nav(ost);
+ print_nav(ost, m_options.base_url + "blog");
ost << html::hr();
ost << html::header();
}
@@ -147,7 +145,7 @@ void article::write_header(std::ostream& ost) const
if (!m_categories.empty()) print_categories(ost, m_categories);
}
-void article::write_footer(std::ostream& ost) const
+void Article::write_footer(std::ostream& ost) const
{
using namespace hemplate; // NOLINT
@@ -157,7 +155,7 @@ void article::write_footer(std::ostream& ost) const
{
ost << html::footer();
ost << html::hr();
- print_nav(ost);
+ print_nav(ost, m_options.base_url + "blog");
ost << html::footer();
}
diff --git a/source/article.hpp b/source/article.hpp
@@ -5,16 +5,21 @@
#include <string>
#include <unordered_map>
+#include "options.hpp"
+
namespace stamd {
-class article
+class Article
{
public:
using symbols_t = std::unordered_map<std::string, std::string>;
using categories_t = std::set<std::string>;
- explicit article(std::string filename, categories_t categories = {})
+ explicit Article(std::string filename,
+ options_t options,
+ categories_t categories = {})
: m_categories(std::move(categories))
+ , m_options(std::move(options))
, m_symbols({{"filename", filename}})
{
}
@@ -43,7 +48,7 @@ public:
std::string get_language() const;
private:
- static void print_nav(std::ostream& ost);
+ static void print_nav(std::ostream& ost, const std::string& base);
static void print_categories(std::ostream& ost,
const categories_t& categories);
@@ -51,6 +56,7 @@ private:
bool m_nonav = false;
categories_t m_categories;
+ options_t m_options;
symbols_t m_symbols;
};
diff --git a/source/indexer.cpp b/source/indexer.cpp
@@ -3,7 +3,6 @@
#include <ctime>
#include <format>
#include <iterator>
-#include <memory>
#include <ostream>
#include <string>
@@ -16,13 +15,17 @@
namespace stamd {
-indexer::article_s& indexer::add(const article_s& article)
+void Indexer::add(const article_s& article)
{
m_articles.emplace_back(article);
- return m_articles.back();
}
-void indexer::sort()
+void Indexer::add(categories_t categories)
+{
+ m_categories.merge(categories);
+}
+
+void Indexer::sort()
{
std::sort(begin(m_articles),
end(m_articles),
@@ -82,21 +85,18 @@ std::string to_rfc3339(const std::string& date)
return std::format(rfc3339_f, chrono_time);
}
-void indexer::create_index(std::ostream& ost,
- const std::string& name,
- const categories_t& categories)
+void Indexer::create_index(std::ostream& ost, const std::string& name)
{
using namespace hemplate; // NOLINT
- auto index = std::make_shared<stamd::article>(name, categories);
+ const Article index(name, m_options, m_categories);
- index->write_header(ost);
+ index.write_header(ost);
ost << html::h1(name);
ost << html::ul().set("class", "index");
for (const auto& article : m_articles)
{
if (article->is_hidden()) continue;
- if (name != "blog" && !article->get_categories().contains(name)) continue;
const auto& filename = article->get_filename();
const auto& title = article->get_title();
@@ -107,10 +107,10 @@ void indexer::create_index(std::ostream& ost,
.add(html::a(title).set("href", filename));
};
ost << html::ul();
- index->write_footer(ost);
+ index.write_footer(ost);
}
-void indexer::create_atom(std::ostream& ost, const std::string& name) const
+void Indexer::create_atom(std::ostream& ost, const std::string& name) const
{
using namespace hemplate; // NOLINT
@@ -148,7 +148,7 @@ void indexer::create_atom(std::ostream& ost, const std::string& name) const
ost << atom::feed();
}
-void indexer::create_rss(std::ostream& ost, const std::string& name) const
+void Indexer::create_rss(std::ostream& ost, const std::string& name) const
{
using namespace hemplate; // NOLINT
@@ -185,7 +185,7 @@ void indexer::create_rss(std::ostream& ost, const std::string& name) const
ost << rss::rss();
}
-void indexer::create_sitemap(std::ostream& ost) const
+void Indexer::create_sitemap(std::ostream& ost) const
{
using namespace hemplate; // NOLINT
@@ -205,7 +205,7 @@ void indexer::create_sitemap(std::ostream& ost) const
ost << sitemap::urlset();
}
-void indexer::create_robots(std::ostream& ost) const
+void Indexer::create_robots(std::ostream& ost) const
{
static const std::string& base_url = m_options.base_url;
diff --git a/source/indexer.hpp b/source/indexer.hpp
@@ -5,36 +5,25 @@
#include <vector>
#include "article.hpp"
+#include "options.hpp"
namespace stamd {
-class indexer
+class Indexer
{
public:
- using article_s = std::shared_ptr<article>;
+ using article_s = std::shared_ptr<Article>;
using article_list = std::vector<article_s>;
- using categories_t = article::categories_t;
+ using categories_t = Article::categories_t;
- struct options_t
- {
- std::string base_url;
- std::string author;
- std::string email;
- std::string description;
- std::string summary;
- };
-
- explicit indexer(options_t options)
+ explicit Indexer(options_t options)
: m_options(std::move(options))
{
- if (m_options.base_url.empty() || m_options.base_url.back() != '/')
- {
- m_options.base_url += '/';
- }
}
- article_s& add(const article_s& article);
+ void add(const article_s& article);
+ void add(categories_t categories);
void sort();
@@ -43,14 +32,11 @@ public:
void create_atom(std::ostream& ost, const std::string& name) const;
void create_rss(std::ostream& ost, const std::string& name) const;
- void create_index(std::ostream& ost,
- const std::string& name,
- const categories_t& categories);
-
- void create_categories() const;
+ void create_index(std::ostream& ost, const std::string& name);
private:
options_t m_options;
+ categories_t m_categories;
article_list m_articles;
};
diff --git a/source/main.cpp b/source/main.cpp
@@ -12,9 +12,10 @@
#include "article.hpp"
#include "indexer.hpp"
+#include "options.hpp"
#include "utility.hpp"
-void preprocess(stamd::article& article, std::istream& ist)
+void preprocess(stamd::Article& article, std::istream& ist)
{
std::string line;
std::string key;
@@ -51,7 +52,7 @@ struct arguments_t
std::vector<std::filesystem::path> files;
bool index = false;
- stamd::indexer::options_t options;
+ stamd::options_t options;
};
int parse_opt(int key, const char* arg, poafloc::Parser* parser)
@@ -83,6 +84,13 @@ int parse_opt(int key, const char* arg, poafloc::Parser* parser)
case poafloc::ARG:
args->files.emplace_back(arg);
break;
+ case poafloc::END:
+ if (args->options.base_url.empty()
+ || args->options.base_url.back() != '/')
+ {
+ args->options.base_url += '/';
+ }
+ break;
default:
break;
}
@@ -132,28 +140,27 @@ int main(int argc, char* argv[])
return 1;
}
- using category_map_t =
- std::unordered_map<std::string, indexer::article_list>;
+ using category_map_t = std::unordered_map<std::string, Indexer>;
- stamd::indexer::categories_t categories;
category_map_t category_map;
- indexer indexer(args.options);
+ Indexer index(args.options);
for (const auto& path : args.files)
{
const std::string filename = path.stem().string() + ".html";
- std::ifstream ifs(path.string());
- auto& article = indexer.add(make_shared<stamd::article>(filename));
+ const auto article = make_shared<stamd::Article>(filename, args.options);
+ index.add(article);
+ std::ifstream ifs(path.string());
preprocess(*article, ifs);
- // filename can change in preprocessing phase
- std::ofstream ofs(args.output_dir / article->get_filename());
std::stringstream sst;
-
sst << ifs.rdbuf();
+ // filename can change in preprocessing phase
+ std::ofstream ofs(args.output_dir / article->get_filename());
+
article->write_header(ofs);
md_html(sst.str().c_str(),
static_cast<MD_SIZE>(sst.str().size()),
@@ -163,39 +170,43 @@ int main(int argc, char* argv[])
0);
article->write_footer(ofs);
- if (!article->is_hidden())
+ if (article->is_hidden()) continue;
+
+ index.add(article->get_categories());
+ for (const auto& category : article->get_categories())
{
- categories.merge(article->get_categories());
- for (const auto& category : article->get_categories())
- category_map[category].emplace_back(article);
+ auto [it, _] = category_map.emplace(category, args.options);
+ it->second.add(article);
}
}
if (!args.index) return 0;
- indexer.sort();
+ index.sort();
- std::ofstream rss(args.output_dir / "rss.xml");
- indexer.create_rss(rss, "index");
+ std::ofstream ofs_rss(args.output_dir / "rss.xml");
+ index.create_rss(ofs_rss, "index");
- std::ofstream atom(args.output_dir / "atom.xml");
- indexer.create_atom(atom, "index");
+ std::ofstream ofs_atom(args.output_dir / "atom.xml");
+ index.create_atom(ofs_atom, "index");
- std::ofstream index(args.output_dir / "index.html");
- indexer.create_index(index, "blog", categories);
+ std::ofstream ofs_index(args.output_dir / "index.html");
+ index.create_index(ofs_index, "blog");
- for (const auto& [category, articles] : category_map)
+ for (auto& [category_name, category_index] : category_map)
{
- auto ctgry = category;
+ auto ctgry = category_name;
std::ofstream ost(args.output_dir / (normalize(ctgry) + ".html"));
- indexer.create_index(ost, category, {});
+
+ category_index.sort();
+ category_index.create_index(ost, category_name);
}
- std::ofstream robots(args.output_dir / "robots.txt");
- indexer.create_robots(robots);
+ std::ofstream ofs_robots(args.output_dir / "robots.txt");
+ index.create_robots(ofs_robots);
- std::ofstream sitemap(args.output_dir / "sitemap.xml");
- indexer.create_sitemap(sitemap);
+ std::ofstream ofs_sitemap(args.output_dir / "sitemap.xml");
+ index.create_sitemap(ofs_sitemap);
return 0;
}
diff --git a/source/options.hpp b/source/options.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <string>
+
+namespace stamd {
+
+struct options_t
+{
+ std::string base_url; // url with trailing '/'
+ std::string author;
+ std::string email;
+ std::string description;
+ std::string summary;
+};
+
+} // namespace stamd