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 + " - ")) 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