stamd

A static markdown page generator written in C
git clone git://git.dimitrijedobrota.com/stamd.git
Log | Files | Refs | README | LICENSE

index.cpp (5485B)


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