stamd

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

commit 3390e3be634ba5943cef85e6d83eb81764b4e942
parent a3ec3431bef4146ae13f8a5d5f27d98dd70f7669
author Dimitrije Dobrota <mail@dimitrijedobrota.com>
date Fri, 2 May 2025 15:21:04 +0200

Use Hemplate 4.0

Diffstat:
M .clang-format | ++++++++++ ----------
M .clang-tidy | ++++++++++++++++++++ ---------------------
M CMakeLists.txt | ++ --
M source/article.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++ ----------------------------------
M source/article.hpp | +++++++++++++++ -------------
M source/indexer.cpp | ++++++++++++++++++++++++++++++++++++++++++++++++ ----------------------------------
M source/indexer.hpp | +++++ ----
M source/main.cpp | +++++++++++++++++++++++++++++++++++++++++++++++++ ---------------------------------
M source/options.hpp | ++ -
M source/utility.hpp | +++++++++++++++++++ --------

10 files changed, 448 insertions(+), 321 deletions(-)


diff --git a/ .clang-format b/ .clang-format

@@ -2,12 +2,12 @@ Language: Cpp # BasedOnStyle: Chromium AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: true
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignEscapedNewlines: DontAlign
AlignOperands: DontAlign AlignTrailingComments: false AllowAllArgumentsOnNextLine: true

@@ -17,9 +17,9 @@ AllowShortEnumsOnASingleLine: false AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLoopsOnASingleLine: true
AllowShortLambdasOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true

@@ -29,16 +29,16 @@ BinPackParameters: false BraceWrapping: AfterCaseLabel: false AfterClass: true
AfterControlStatement: Always
AfterControlStatement: MultiLine
AfterEnum: true AfterFunction: true
AfterNamespace: false
AfterNamespace: true
AfterObjCDeclaration: false AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: false
BeforeElse: true
BeforeElse: false
BeforeLambdaBody: true BeforeWhile: false IndentBraces: false

@@ -54,7 +54,7 @@ BreakConstructorInitializersBeforeComma: true BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: true BreakStringLiterals: true
ColumnLimit: 79
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false

diff --git a/ .clang-tidy b/ .clang-tidy

@@ -4,28 +4,23 @@ # modernize-use-nodiscard: too aggressive, attribute is situationally useful Checks: "*,\ -altera-*,\
-boost*,\
-cppcoreguidelines-avoid-do-while,\
-cppcoreguidelines-pro-bounds-constant-array-index,\
-fuchsia-*,\
-llvmlibc-*,\
-*-braces-around-statements,\
-bugprone-argument-comment,\
-bugprone-easily-swappable-parameters,\
-concurrency-mt-unsafe,\
-cppcoreguidelines-avoid-magic-numbers,\
-fuchsia-multiple-inheritance,\
-hicpp-signed-bitwise,\
-google-readability-todo,\
-llvm-header-guard,\ -llvm-include-order,\
-misc-no-recursion,\
-llvmlibc-*,\
-misc-include-cleaner,\
-misc-non-private-member-variables-in-classes,\
-modernize-use-nodiscard,\
-misc-no-recursion,\
-modernize-use-trailing-return-type,\
-readability-function-cognitive-complexity,\
-readability-magic-numbers
-readability-suspicious-call-argument,\
-*-ranges,\
" WarningsAsErrors: '' CheckOptions:
- key: 'misc-include-cleaner.IgnoreHeaders'
value: 'poafloc.*;md4c.*'
- key: 'bugprone-argument-comment.StrictMode' value: 'true' # Prefer using enum classes with 2 values for parameters instead of bools

@@ -61,7 +56,7 @@ CheckOptions: - key: 'readability-identifier-naming.AbstractClassCase' value: 'lower_case' - key: 'readability-identifier-naming.ClassCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.ClassMemberCase'

@@ -83,9 +78,9 @@ CheckOptions: - key: 'readability-identifier-naming.ConstexprVariableCase' value: 'lower_case' - key: 'readability-identifier-naming.EnumCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumConstantCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.FunctionCase' value: 'lower_case' - key: 'readability-identifier-naming.GlobalConstantCase'

@@ -139,7 +134,7 @@ CheckOptions: - key: 'readability-identifier-naming.PublicMethodCase' value: 'lower_case' - key: 'readability-identifier-naming.ScopedEnumConstantCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticConstantCase' value: 'lower_case' - key: 'readability-identifier-naming.StaticVariableCase'

@@ -152,18 +147,22 @@ CheckOptions: value: 'CamelCase' - key: 'readability-identifier-naming.TypeAliasCase' value: 'lower_case'
- key: 'readability-identifier-naming.TypeAliasIgnoredRegexp'
value: 'N'
- key: 'readability-identifier-naming.TypedefCase' value: 'lower_case' - key: 'readability-identifier-naming.TypeTemplateParameterCase' value: 'CamelCase'
- key: 'readability-identifier-naming.TypeTemplateParameterIgnoredRegexp'
value: 'expr-type'
- key: 'readability-identifier-naming.UnionCase' value: 'lower_case' - key: 'readability-identifier-naming.ValueTemplateParameterCase'
value: 'CamelCase'
value: 'lower_case'
- key: 'readability-identifier-naming.VariableCase' value: 'lower_case' - key: 'readability-identifier-naming.VirtualMethodCase' value: 'lower_case'
- key: 'readability-identifier-length.IgnoredVariableNames'
value: "^[abcdxyznm]$"
- key: 'readability-identifier-length.IgnoredParameterNames'
value: "^[abcdxyznm]$"
...

diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( stamd
VERSION 0.3.2
VERSION 0.4.0
DESCRIPTION "Static Markdown Page Generator" HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamd.git" LANGUAGES CXX

@@ -17,7 +17,7 @@ include(cmake/variables.cmake) find_package(md4c CONFIG REQUIRED) find_package(poafloc 1 CONFIG REQUIRED)
find_package(hemplate 0.2.2 CONFIG REQUIRED)
find_package(hemplate 0.4.0 CONFIG REQUIRED)
# ---- Declare library ----

diff --git a/ source/article.cpp b/ source/article.cpp

@@ -1,5 +1,4 @@ #include <format>
#include <iostream>
#include <iterator> #include <numeric> #include <optional>

@@ -12,13 +11,13 @@ #include "utility.hpp"
namespace stamd {
namespace stamd
{
std::optional<std::string> Article::get(const std::string& key) const { const auto itr = m_symbols.find(key);
if (itr == end(m_symbols))
{
if (itr == end(m_symbols)) {
// std::cerr << "Warning: getting invalid value for: " << key << std::endl; return {}; }

@@ -59,128 +58,177 @@ std::string Article::get_keywords() const { static const auto concat = [](const categories_t& categories) {
if (categories.empty()) return std::string();
return std::accumulate(std::next(std::begin(categories)),
std::end(categories),
*categories.begin(),
[](const auto& acc, const auto& str)
{ return acc + ", " + str; });
if (categories.empty()) {
return std::string();
}
return std::accumulate(
std::next(std::begin(categories)),
std::end(categories),
*categories.begin(),
[](const auto& acc, const auto& str)
{
return acc + ", " + str;
}
);
}; return get("keywords").value_or(concat(m_categories)); }
void Article::print_nav(std::ostream& ost, const std::string& base)
{
using namespace hemplate; // NOLINT
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,
const categories_t& categories)
hemplate::element Article::print_nav(const std::string& base)
{
using namespace hemplate; // NOLINT
ost << html::nav().set("class", "categories");
ost << html::h3("Categories: ");
ost << html::p();
for (const auto& category : categories)
{
auto ctgry = category;
normalize(ctgry);
ost << html::a(category, {{"href", std::format("./{}.html", ctgry)}});
}
ost << html::p();
ost << html::nav();
using namespace hemplate::html; // NOLINT
return nav {
a {
{{"class", "back"}},
"&lt;-- back",
},
a {
{{"href", base}},
"index",
},
a {
{{"href", "/"}},
"home --&gt;",
},
};
}
void Article::write_header(std::ostream& ost) const
hemplate::element Article::print_categories(const categories_t& categories)
{
using namespace hemplate; // NOLINT
ost << html::doctype();
ost << html::html().set("lang", get_language());
ost << html::head()
.add(html::title(get_title()))
// Meta tags
.add(html::meta({{"charset", "UTF-8"}}))
.add(html::meta({{"name", "author"}, {"content", get_author()}}))
.add(html::meta(
{{"name", "description"}, {"content", get_desciprtion()}}))
.add(html::meta(
{{"name", "keywords"}, {"content", get_keywords()}}))
.add(html::meta(
{{"content", "width=device-width, initial-scale=1"},
{"name", "viewport"}}))
// Stylesheets
.add(html::link({{"rel", "stylesheet"}, {"type", "text/css"}})
.set("href", "/css/index.css"))
.add(html::link({{"rel", "stylesheet"}, {"type", "text/css"}})
.set("href", "/css/colors.css"))
// Rss feed
.add(html::link({{"rel", "alternate"},
{"type", "application/atom+xml"},
{"title", "RSS feed"},
{"href", "/blog/rss.xml"}}))
// Atom feed
.add(html::link({{"rel", "alternate"},
{"type", "application/atom+xml"},
{"title", "Atom feed"},
{"href", "/blog/atom.xml"}}))
// Icons
.add(html::link({{"rel", "icon"}, {"type", "image/png"}})
.set("sizes", "32x32")
.set("href", "/img/favicon-32x32.png"))
.add(html::link({{"rel", "icon"}, {"type", "image/png"}})
.set("sizes", "16x16")
.set("href", "/img/favicon-16x16.png"));
ost << html::body();
ost << html::input()
.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, m_options.base_url + "blog");
ost << html::hr();
ost << html::header();
}
ost << html::main();
ost << html::label(" ")
.set("for", "theme_switch")
.set("class", "switch_label");
if (!m_categories.empty()) print_categories(ost, m_categories);
using namespace hemplate::html; // NOLINT
return nav {
{{"class", "categories"}},
h3 {"Categories: "},
p {
transform(
categories,
[](const auto& category)
{
auto ctgry = category;
normalize(ctgry);
return a {
{{"href", std::format("./{}.html", ctgry)}},
category,
};
}
),
},
};
}
void Article::write_footer(std::ostream& ost) const
hemplate::element Article::write(const content_t& content) const
{
using namespace hemplate; // NOLINT
ost << html::main();
if (!m_nonav)
{
ost << html::footer();
ost << html::hr();
print_nav(ost, m_options.base_url + "blog");
ost << html::footer();
}
ost << html::div();
ost << html::script(" ").set("src", "/scripts/main.js");
ost << html::body();
ost << html::html();
using namespace hemplate::html; // NOLINT
return element {
doctype {},
html {
{{"lang", get_language()}},
head {
title {get_title()},
},
// Meta tags
meta {
{{"charset", "UTF-8"}},
},
meta {
{{"name", "author"}, {"content", get_author()}},
},
meta {{
{"name", "description"},
{"content", get_desciprtion()},
}},
meta {{
{"name", "keywords"},
{"content", get_keywords()},
}},
meta {
{{"content", "width=device-width, initial-scale=1"},
{"name", "viewport"}}
},
// Stylesheets
link {{
{"rel", "stylesheet"},
{"type", "text/css"},
{"href", "/css/index.css"},
}},
link {{
{"rel", "stylesheet"},
{"type", "text/css"},
{"href", "/css/colors.css"},
}},
// Rss feed
link {{
{"rel", "alternate"},
{"type", "application/atom+xml"},
{"title", "RSS feed"},
{"href", "/blog/rss.xml"},
}},
// Atom feed
link {{
{"rel", "alternate"},
{"type", "application/atom+xml"},
{"title", "Atom feed"},
{"href", "/blog/atom.xml"},
}},
// Icons
link {{
{"rel", "icon"},
{"type", "image/png"},
{"sizes", "32x32"},
{"href", "/img/favicon-32x32.png"},
}},
link {{
{"rel", "icon"},
{"type", "image/png"},
{"sizes", "16x16"},
{"href", "/img/favicon-16x16.png"},
}},
body {
input {{
{"type", "checkbox"},
{"id", "theme_switch"},
{"class", "theme_switch"},
}},
hemplate::html::div {
{{"id", "content"}},
m_nonav ? element{} : [&] {
return header {
print_nav(m_options.base_url + "blog"),
hr{},
};
}(),
main {
label {
{{"for", "theme_switch"}, {"class", "switch_label"},},
},
m_categories.empty() ? element {} : [&]() {
return print_categories(m_categories);
}(),
content(),
},
m_nonav ? element{} : [&] {
return footer {
hr{},
print_nav(m_options.base_url + "blog"),
};
}(),
},
script {{{"set", "/scripts/main.js"}}},
},
},
};
} } // namespace stamd

diff --git a/ source/article.hpp b/ source/article.hpp

@@ -5,27 +5,30 @@ #include <string> #include <unordered_map>
#include <hemplate/html.hpp>
#include "options.hpp"
namespace stamd {
namespace stamd
{
class Article { public:
using symbols_t = std::unordered_map<std::string, std::string>;
using symbols_t = std::unordered_map<std::string, std::string>;
using categories_t = std::set<std::string>;
explicit Article(std::string filename,
options_t options,
categories_t categories = {})
explicit Article(
std::string filename, options_t options, categories_t categories = {}
)
: m_filename(std::move(filename)) , m_categories(std::move(categories)) , m_options(std::move(options)) { }
void write_header(std::ostream& ost) const;
void write_footer(std::ostream& ost) const;
using content_t = std::function<hemplate::element()>;
hemplate::element write(const content_t& content) const;
void insert(const std::string& category) { m_categories.emplace(category); } void insert(const std::string& key, const std::string& value)

@@ -35,8 +38,8 @@ public: auto get_categories() const { return m_categories; }
void set_hidden(bool state) { m_hidden = state; }
void set_nonav(bool state) { m_nonav = state; }
void set_hidden() { m_hidden = true; }
void set_nonav() { m_nonav = true; }
bool is_hidden() const { return m_hidden; }

@@ -51,12 +54,11 @@ public: std::string get_keywords() const; private:
static void print_nav(std::ostream& ost, const std::string& base);
static void print_categories(std::ostream& ost,
const categories_t& categories);
static hemplate::element print_nav(const std::string& base);
static hemplate::element print_categories(const categories_t& categories);
bool m_hidden = false;
bool m_nonav = false;
bool m_nonav = false;
std::string m_filename; categories_t m_categories;

diff --git a/ source/indexer.cpp b/ source/indexer.cpp

@@ -9,12 +9,28 @@ #include "indexer.hpp"
#include <hemplate/attribute.hpp>
#include <hemplate/classes.hpp>
#include <hemplate/atom.hpp>
#include <hemplate/html.hpp>
#include <hemplate/rss.hpp>
#include <hemplate/sitemap.hpp>
#include "article.hpp"
namespace stamd {
namespace
{
int64_t parse_time(const std::string& date)
{
std::tm tms = {};
std::stringstream stream(date);
stream >> std::get_time(&tms, "%Y-%m-%d");
return std::mktime(&tms);
}
} // namespace
namespace stamd
{
void Indexer::add(const article_s& article) {

@@ -28,135 +44,167 @@ void Indexer::add(categories_t categories) void Indexer::sort() {
std::sort(begin(m_articles),
end(m_articles),
[](const auto& lft, const auto& rht)
{ return lft->get_date() > rht->get_date(); });
std::sort(
begin(m_articles),
end(m_articles),
[](const auto& lft, const auto& rht)
{
return lft->get_date() > rht->get_date();
}
);
}
int64_t parse_time(const std::string& date)
void Indexer::create_index(std::ostream& ost, const std::string& doc_title)
{
std::tm tms = {};
std::stringstream stream(date);
stream >> std::get_time(&tms, "%Y-%m-%d");
return std::mktime(&tms);
using namespace hemplate::html; // NOLINT
const Article index(doc_title, m_options, m_categories);
ost << index.write(
[&]
{
return element {
h1 {doc_title},
ul {
{{"class", "index"}},
transform(
m_articles,
[](const auto& article) -> element
{
if (article->is_hidden()) {
return {};
}
return li {
span {article->get_date(), " -&nbsp"},
a {
{{"href", article->get_filename()}},
article->get_title(),
},
};
}
),
},
};
}
);
}
void Indexer::create_index(std::ostream& ost, const std::string& name)
void Indexer::create_atom(std::ostream& ost, const std::string& doc_title) const
{
using namespace hemplate; // NOLINT
const Article index(name, m_options, m_categories);
using namespace hemplate::atom; // NOLINT
using hemplate::atom::link;
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;
const auto& filename = article->get_filename();
const auto& title = article->get_title();
const auto& date = article->get_date();
ost << html::li()
.add(html::span(date + " -&nbsp"))
.add(html::a(title).set("href", filename));
const std::string& base_url = m_options.base_url;
const std::string& author_name = m_options.author;
ost << element {
xml {},
feed {},
title {doc_title},
id {base_url},
updated {format_time_now()},
author {name {author_name}},
link {{
{"href", base_url + "/atom.xml"},
{"rel", "self"},
}},
link {{
{"href", base_url + "blog/atom.xml"},
{"rel", "alternate"},
{"type", "text/html"},
}},
feed {
transform(
m_articles,
[&](const auto& article)
{
const auto filename = article->get_filename();
const auto art_title = article->get_title();
const auto date = article->get_date();
const auto art_summary =
article->get("summary").value_or(m_options.summary);
return entry {
title {art_title},
id {base_url + filename},
link {{{"href", base_url + filename}}},
updated {format_time(parse_time((date)))},
summary {art_summary},
};
}
),
},
};
ost << html::ul();
index.write_footer(ost);
}
void Indexer::create_atom(std::ostream& ost, const std::string& name) const
void Indexer::create_rss(std::ostream& ost, const std::string& doc_title) const
{
using namespace hemplate; // NOLINT
using namespace hemplate::rss; // NOLINT
using hemplate::rss::link;
const std::string& base_url = m_options.base_url;
ost << xml();
ost << atom::feed();
ost << atom::title(name);
ost << atom::id(base_url);
ost << atom::updated(atom::format_time_now());
ost << atom::author().add(atom::name(name));
ost << atom::link(" ",
{{"rel", "self"}, {"href", base_url + "blog/atom.xml"}});
ost << atom::link(
" ", {{"href", base_url}, {"rel", "alternate"}, {"type", "text/html"}});
for (const auto& article : m_articles)
{
const auto filename = article->get_filename();
const auto title = article->get_title();
const auto date = article->get_date();
const auto summary = article->get("summary").value_or(m_options.summary);
ost << atom::entry()
.add(atom::title(title))
.add(atom::id(base_url + filename))
.add(atom::link(" ").set("href", base_url + filename))
.add(atom::updated(atom::format_time(parse_time((date)))))
.add(atom::summary(summary));
}
ost << atom::feed();
}
void Indexer::create_rss(std::ostream& ost, const std::string& name) const
{
using namespace hemplate; // NOLINT
const std::string& base_url = m_options.base_url;
const std::string& description = m_options.description;
ost << xml();
ost << rss::rss();
ost << rss::channel();
ost << rss::title(name);
ost << rss::link(base_url);
ost << rss::description(description);
ost << rss::generator("stamd");
ost << rss::language("en-us");
ost << rss::atomLink().set("href", base_url + "blog/rss.xml");
for (const auto& article : m_articles)
{
const auto filename = article->get_filename();
const auto date = article->get_date();
const auto author = article->get("author").value_or(m_options.author);
const auto email = article->get("email").value_or(m_options.email);
ost << rss::item()
.add(rss::title(filename))
.add(rss::link(base_url + filename))
.add(rss::guid(base_url + filename))
.add(rss::pubDate(rss::format_time(parse_time(date))))
.add(rss::author(std::format("{} ({})", email, author)));
}
ost << rss::channel();
ost << rss::rss();
const std::string& desc = m_options.description;
ost << element {
xml {},
rss {
channel {
title {doc_title},
link {base_url},
description {desc},
generator {"stamd"},
language {"en-us"},
atomLink {base_url + "blog/rss.xml"},
transform(
m_articles,
[&](const auto& article)
{
const auto filename = article->get_filename();
const auto date = article->get_date();
const auto author_name =
article->get("author").value_or(m_options.author);
const auto email =
article->get("email").value_or(m_options.email);
return item {
title {filename},
link {base_url + filename},
guid {base_url + filename},
pubDate {format_time(parse_time(date))},
author {std::format("{} ({})", email, author_name)},
};
}
),
},
},
};
} void Indexer::create_sitemap(std::ostream& ost) const {
using namespace hemplate; // NOLINT
using namespace hemplate::sitemap; // NOLINT
static const std::string& base_url = m_options.base_url;
ost << xml();
ost << sitemap::urlset();
for (const auto& article : m_articles)
{
const auto& filename = article->get_filename();
const auto& date = article->get_date();
ost << sitemap::url()
.add(sitemap::loc(base_url + filename))
.add(sitemap::lastmod(date));
}
ost << sitemap::urlset();
ost << element {
xml {},
urlset {
transform(
m_articles,
[&](const auto& article)
{
const auto& filename = article->get_filename();
const auto& date = article->get_date();
return url {
loc {base_url + filename},
lastmod {date},
};
}
),
}
};
} void Indexer::create_robots(std::ostream& ost) const

diff --git a/ source/indexer.hpp b/ source/indexer.hpp

@@ -7,7 +7,8 @@ #include "article.hpp" #include "options.hpp"
namespace stamd {
namespace stamd
{
class Indexer {

@@ -30,9 +31,9 @@ public: void create_robots(std::ostream& ost) const; void create_sitemap(std::ostream& ost) const;
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);
void create_atom(std::ostream& ost, const std::string& doc_title) const;
void create_rss(std::ostream& ost, const std::string& doc_title) const;
void create_index(std::ostream& ost, const std::string& doc_title);
private: options_t m_options;

diff --git a/ source/main.cpp b/ source/main.cpp

@@ -15,16 +15,19 @@ #include "options.hpp" #include "utility.hpp"
namespace
{
void preprocess(stamd::Article& article, std::istream& ist) { std::string line; std::string key; std::string value;
while (std::getline(ist, line))
{
if (line.empty()) break;
if (line[0] != '@') break;
while (std::getline(ist, line)) {
if (line.empty() && line[0] != '@') {
break;
}
{ std::istringstream iss(line.substr(1));

@@ -35,13 +38,17 @@ void preprocess(stamd::Article& article, std::istream& ist) trim(value); }
if (key == "hidden") article.set_hidden(true);
else if (key == "nonav") article.set_nonav(true);
else if (key != "categories") article.insert(key, value);
else
{
if (key == "hidden") {
article.set_hidden();
} else if (key == "nonav") {
article.set_nonav();
} else if (key != "categories") {
article.insert(key, value);
} else {
std::istringstream iss(value);
while (std::getline(iss, value, ',')) article.insert(trim(value));
while (std::getline(iss, value, ',')) {
article.insert(trim(value));
}
} } }

@@ -58,8 +65,7 @@ struct arguments_t int parse_opt(int key, const char* arg, poafloc::Parser* parser) { auto* args = static_cast<arguments_t*>(parser->input());
switch (key)
{
switch (key) {
case 'o': args->output_dir = arg; break;

@@ -122,11 +128,7 @@ static const poafloc::arg_t arg { }; // NOLINTEND
void process_output(const MD_CHAR* str, MD_SIZE size, void* data)
{
std::ofstream& ofs = *static_cast<std::ofstream*>(data);
ofs << std::string(str, size);
}
} // namespace
int main(int argc, char* argv[]) {

@@ -134,8 +136,7 @@ int main(int argc, char* argv[]) arguments_t args;
if (poafloc::parse(&arg, argc, argv, 0, &args) != 0)
{
if (poafloc::parse(&arg, argc, argv, 0, &args) != 0) {
std::cerr << "There was an error while parsing arguments"; return 1; }

@@ -145,8 +146,7 @@ int main(int argc, char* argv[]) category_map_t category_map; Indexer index(args.options);
for (const auto& path : args.files)
{
for (const auto& path : args.files) {
const std::string filename = path.stem().string() + ".html"; const auto article = make_shared<stamd::Article>(filename, args.options);

@@ -161,26 +161,44 @@ int main(int argc, char* argv[]) // 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()),
process_output,
&ofs,
MD_DIALECT_GITHUB,
0);
article->write_footer(ofs);
if (article->is_hidden()) continue;
ofs << article->write(
[&]() -> hemplate::element
{
std::string html;
static auto process_output =
[](const MD_CHAR* str, MD_SIZE size, void* data)
{
std::string& buffer = *static_cast<std::string*>(data);
buffer += std::string(str, size);
};
md_html(
sst.str().c_str(),
static_cast<MD_SIZE>(sst.str().size()),
process_output,
&html,
MD_DIALECT_GITHUB,
0
);
return html;
}
);
if (article->is_hidden()) {
continue;
}
index.add(article->get_categories());
for (const auto& category : article->get_categories())
{
for (const auto& category : article->get_categories()) {
auto [it, _] = category_map.emplace(category, args.options); it->second.add(article); } }
if (!args.index) return 0;
if (!args.index) {
return 0;
}
index.sort();

@@ -193,8 +211,7 @@ int main(int argc, char* argv[]) std::ofstream ofs_index(args.output_dir / "index.html"); index.create_index(ofs_index, "blog");
for (auto& [category_name, category_index] : category_map)
{
for (auto& [category_name, category_index] : category_map) {
auto ctgry = category_name; std::ofstream ost(args.output_dir / (normalize(ctgry) + ".html"));

diff --git a/ source/options.hpp b/ source/options.hpp

@@ -2,7 +2,8 @@ #include <string>
namespace stamd {
namespace stamd
{
struct options_t {

diff --git a/ source/utility.hpp b/ source/utility.hpp

@@ -7,20 +7,31 @@ inline std::string& ltrim(std::string& str) { str.erase( str.begin(),
std::find_if(str.begin(),
str.end(),
[](unsigned char chr) { return std::isspace(chr) == 0; }));
std::find_if(
str.begin(),
str.end(),
[](unsigned char chr)
{
return std::isspace(chr) == 0;
}
)
);
return str; } inline std::string& rtrim(std::string& str) { str.erase(
std::find_if(str.rbegin(),
str.rend(),
[](unsigned char chr) { return std::isspace(chr) == 0; })
.base(),
str.end());
std::find_if(
str.rbegin(),
str.rend(),
[](unsigned char chr)
{
return std::isspace(chr) == 0;
}
).base(),
str.end()
);
return str; }