stamd

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

indexer.cpp (5552B)


      1 #include <algorithm>
      2 #include <chrono>
      3 #include <format>
      4 #include <fstream>
      5 #include <numeric>
      6 #include <sstream>
      7 
      8 #include "indexer.hpp"
      9 
     10 #include <hemplate/attribute.hpp>
     11 #include <hemplate/classes.hpp>
     12 
     13 namespace stamd {
     14 
     15 indexer::article_s& indexer::add(const article_s& article)
     16 {
     17   m_articles.emplace_back(article);
     18   return m_articles.back();
     19 }
     20 
     21 void indexer::sort()
     22 {
     23   std::sort(begin(m_articles),
     24             end(m_articles),
     25             [](const auto& lft, const auto& rht)
     26             { return lft->get_date() > rht->get_date(); });
     27 }
     28 
     29 std::tm get_time(const std::string& date)
     30 {
     31   int year  = 0;
     32   int month = 0;
     33   int day   = 0;
     34 
     35   std::sscanf(date.c_str(), "%d-%d-%d", &year, &month, &day);
     36 
     37   tm time = {
     38       .tm_sec  = 0,
     39       .tm_min  = 0,
     40       .tm_hour = 0,
     41       .tm_mday = day,
     42       .tm_mon  = month - 1,
     43       .tm_year = year - 1900,
     44   };
     45 
     46   return time;
     47 }
     48 
     49 #define rfc882_f "{:%a, %d %b %Y %H:%M:%S %z}"  // NOLINT
     50 #define rfc3339_f "{:%FT%H:%M:%SZ}"  // NOLINT
     51 
     52 std::string to_rfc882(const std::string& date)
     53 {
     54   using namespace std::chrono;  // NOLINT
     55 
     56   tm time = get_time(date);
     57 
     58   const auto tmp = std::mktime(&time);
     59   const auto chrono_time =
     60       time_point_cast<seconds>(system_clock::from_time_t(tmp));
     61 
     62   return std::format(rfc882_f, chrono_time);
     63 }
     64 
     65 std::string to_rfc3339(const std::string& date)
     66 {
     67   using namespace std::chrono;  // NOLINT
     68 
     69   tm time = get_time(date);
     70 
     71   const auto tmp = std::mktime(&time);
     72   const auto chrono_time =
     73       time_point_cast<seconds>(system_clock::from_time_t(tmp));
     74 
     75   return std::format(rfc3339_f, chrono_time);
     76 }
     77 
     78 void indexer::create_index(std::ostream& ost,
     79                            const std::string& name,
     80                            const categories_t& categories)
     81 {
     82   using namespace hemplate;  // NOLINT
     83 
     84   auto index = std::make_shared<stamd::article>(name, categories);
     85 
     86   index->write_header(ost);
     87   ost << html::h1(name);
     88   ost << html::ul().set("class", "index");
     89   for (const auto& article : m_articles)
     90   {
     91     if (article->is_hidden()) continue;
     92 
     93     const auto& filename = article->get_filename();
     94     const auto& title    = article->get_title();
     95     const auto& date     = article->get_date();
     96 
     97     ost << html::li()
     98                .add(html::span(date + " -&nbsp"))
     99                .add(html::a(title).set("href", filename));
    100   };
    101   ost << html::ul();
    102   index->write_footer(ost);
    103 
    104   add(index);
    105 }
    106 
    107 void indexer::create_atom(std::ostream& ost, const std::string& name) const
    108 {
    109   using namespace hemplate;  // NOLINT
    110 
    111   const std::string& base_url = m_options.base_url;
    112 
    113   auto const time =
    114       std::chrono::current_zone()->to_local(std::chrono::system_clock::now());
    115 
    116   ost << xml();
    117   ost << atom::feed();
    118   ost << atom::title(name);
    119   ost << atom::id(base_url);
    120   ost << atom::updated(std::format(rfc3339_f, time));
    121   ost << atom::author().add(atom::name(name));
    122   ost << atom::link(" ",
    123                     {{"rel", "self"}, {"href", base_url + "blog/atom.xml"}});
    124   ost << atom::link(
    125       " ", {{"href", base_url}, {"rel", "alternate"}, {"type", "text/html"}});
    126 
    127   for (const auto& article : m_articles)
    128   {
    129     const auto filename = article->get_filename();
    130     const auto title    = article->get_title();
    131     const auto date     = article->get_date();
    132     const auto summary  = article->get("summary").value_or(m_options.summary);
    133 
    134     ost << atom::entry()
    135                .add(atom::title(title))
    136                .add(atom::id(base_url + filename))
    137                .add(atom::link(" ").set("href", base_url + filename))
    138                .add(atom::updated(to_rfc3339(date)))
    139                .add(atom::summary(summary));
    140   }
    141 
    142   ost << atom::feed();
    143 }
    144 
    145 void indexer::create_rss(std::ostream& ost, const std::string& name) const
    146 {
    147   using namespace hemplate;  // NOLINT
    148 
    149   const std::string& base_url    = m_options.base_url;
    150   const std::string& description = m_options.description;
    151 
    152   ost << xml();
    153   ost << rss::rss();
    154   ost << rss::channel();
    155 
    156   ost << rss::title(name);
    157   ost << rss::link(base_url);
    158   ost << rss::description(description);
    159   ost << rss::generator("stamd");
    160   ost << rss::language("en-us");
    161   ost << rss::atomLink().set("href", base_url + "blog/rss.xml");
    162 
    163   for (const auto& article : m_articles)
    164   {
    165     const auto filename = article->get_filename();
    166     const auto date     = article->get_date();
    167     const auto author   = article->get("author").value_or(m_options.author);
    168     const auto email    = article->get("email").value_or(m_options.email);
    169 
    170     ost << rss::item()
    171                .add(rss::title(filename))
    172                .add(rss::link(base_url + filename))
    173                .add(rss::guid(base_url + filename))
    174                .add(rss::pubDate(to_rfc882(date)))
    175                .add(rss::author(std::format("{} ({})", email, author)));
    176   }
    177 
    178   ost << rss::channel();
    179   ost << rss::rss();
    180 }
    181 
    182 void indexer::create_sitemap(std::ostream& ost) const
    183 {
    184   using namespace hemplate;  // NOLINT
    185 
    186   static const std::string& base_url = m_options.base_url;
    187 
    188   ost << xml();
    189   ost << sitemap::urlset();
    190   for (const auto& article : m_articles)
    191   {
    192     const auto& filename = article->get_filename();
    193     const auto& date     = article->get_date();
    194 
    195     ost << sitemap::url()
    196                .add(sitemap::loc(base_url + filename))
    197                .add(sitemap::lastmod(date));
    198   }
    199   ost << sitemap::urlset();
    200 }
    201 
    202 void indexer::create_robots(std::ostream& ost) const
    203 {
    204   static const std::string& base_url = m_options.base_url;
    205 
    206   ost << "User-agent: *";
    207   ost << std::format("Sitemap: {}/sitemap.xml", base_url);
    208 }
    209 
    210 }  // namespace stamd