stamd

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

main.cpp (5122B)


      1 #include <filesystem>
      2 #include <fstream>
      3 #include <iostream>
      4 #include <memory>
      5 #include <sstream>
      6 #include <string>
      7 #include <unordered_map>
      8 #include <vector>
      9 
     10 #include <md4c-html.h>
     11 #include <poafloc/poafloc.hpp>
     12 
     13 #include "article.hpp"
     14 #include "indexer.hpp"
     15 #include "options.hpp"
     16 #include "utility.hpp"
     17 
     18 void preprocess(stamd::Article& article, std::istream& ist)
     19 {
     20   std::string line;
     21   std::string key;
     22   std::string value;
     23 
     24   while (std::getline(ist, line))
     25   {
     26     if (line.empty()) break;
     27     if (line[0] != '@') break;
     28 
     29     {
     30       std::istringstream iss(line.substr(1));
     31       std::getline(iss, key, ':');
     32       std::getline(iss, value);
     33 
     34       trim(key);
     35       trim(value);
     36     }
     37 
     38     if (key == "hidden") article.set_hidden(true);
     39     else if (key == "nonav") article.set_nonav(true);
     40     else if (key != "categories") article.insert(key, value);
     41     else
     42     {
     43       std::istringstream iss(value);
     44       while (std::getline(iss, value, ',')) article.insert(trim(value));
     45     }
     46   }
     47 }
     48 
     49 struct arguments_t
     50 {
     51   std::filesystem::path output_dir = ".";
     52   std::vector<std::filesystem::path> files;
     53   bool index = false;
     54 
     55   stamd::options_t options;
     56 };
     57 
     58 int parse_opt(int key, const char* arg, poafloc::Parser* parser)
     59 {
     60   auto* args = static_cast<arguments_t*>(parser->input());
     61   switch (key)
     62   {
     63     case 'o':
     64       args->output_dir = arg;
     65       break;
     66     case 'i':
     67       args->index = true;
     68       break;
     69     case 'b':
     70       args->options.base_url = arg;
     71       break;
     72     case 'a':
     73       args->options.author = arg;
     74       break;
     75     case 'e':
     76       args->options.email = arg;
     77       break;
     78     case 'd':
     79       args->options.description = arg;
     80       break;
     81     case 's':
     82       args->options.summary = arg;
     83       break;
     84     case poafloc::ARG:
     85       args->files.emplace_back(arg);
     86       break;
     87     case poafloc::END:
     88       if (args->options.base_url.empty()
     89           || args->options.base_url.back() != '/')
     90       {
     91         args->options.base_url += '/';
     92       }
     93       break;
     94     default:
     95       break;
     96   }
     97   return 0;
     98 }
     99 
    100 // NOLINTBEGIN
    101 // clang-format off
    102 static const poafloc::option_t options[] = {
    103     {0, 0, 0, 0, "Output mode", 1},
    104     {"output", 'o', "DIR", 0, "Output directory"},
    105     {"index", 'i', 0, 0, "Generate all of the indices"},
    106     {0, 0, 0, 0, "General information", 2},
    107     {"base", 'b', "URL", 0, "Base URL for the content"},
    108     {"author", 'a', "NAME", 0, "Name of the author, if not specified in article"},
    109     {"email", 'e', "EMAIL", 0, "Email of the author, if not specified in article"},
    110     {"summary", 's', "SMRY", 0, "A summary, if not specified in article"},
    111     {"description", 'd', "DESC", 0, "Description of RSS feed"},
    112     {0, 0, 0, 0, "Informational Options", -1},
    113     {0},
    114 };
    115 // clang-format on
    116 
    117 static const poafloc::arg_t arg {
    118     options,
    119     parse_opt,
    120     "config_file",
    121     "",
    122 };
    123 // NOLINTEND
    124 
    125 void process_output(const MD_CHAR* str, MD_SIZE size, void* data)
    126 {
    127   std::ofstream& ofs = *static_cast<std::ofstream*>(data);
    128   ofs << std::string(str, size);
    129 }
    130 
    131 int main(int argc, char* argv[])
    132 {
    133   using namespace stamd;  // NOLINT
    134 
    135   arguments_t args;
    136 
    137   if (poafloc::parse(&arg, argc, argv, 0, &args) != 0)
    138   {
    139     std::cerr << "There was an error while parsing arguments";
    140     return 1;
    141   }
    142 
    143   using category_map_t = std::unordered_map<std::string, Indexer>;
    144 
    145   category_map_t category_map;
    146   Indexer index(args.options);
    147 
    148   for (const auto& path : args.files)
    149   {
    150     const std::string filename = path.stem().string() + ".html";
    151 
    152     const auto article = make_shared<stamd::Article>(filename, args.options);
    153     index.add(article);
    154 
    155     std::ifstream ifs(path.string());
    156     preprocess(*article, ifs);
    157 
    158     std::stringstream sst;
    159     sst << ifs.rdbuf();
    160 
    161     // filename can change in preprocessing phase
    162     std::ofstream ofs(args.output_dir / article->get_filename());
    163 
    164     article->write_header(ofs);
    165     md_html(sst.str().c_str(),
    166             static_cast<MD_SIZE>(sst.str().size()),
    167             process_output,
    168             &ofs,
    169             MD_DIALECT_GITHUB,
    170             0);
    171     article->write_footer(ofs);
    172 
    173     if (article->is_hidden()) continue;
    174 
    175     index.add(article->get_categories());
    176     for (const auto& category : article->get_categories())
    177     {
    178       auto [it, _] = category_map.emplace(category, args.options);
    179       it->second.add(article);
    180     }
    181   }
    182 
    183   if (!args.index) return 0;
    184 
    185   index.sort();
    186 
    187   std::ofstream ofs_rss(args.output_dir / "rss.xml");
    188   index.create_rss(ofs_rss, "index");
    189 
    190   std::ofstream ofs_atom(args.output_dir / "atom.xml");
    191   index.create_atom(ofs_atom, "index");
    192 
    193   std::ofstream ofs_index(args.output_dir / "index.html");
    194   index.create_index(ofs_index, "blog");
    195 
    196   for (auto& [category_name, category_index] : category_map)
    197   {
    198     auto ctgry = category_name;
    199     std::ofstream ost(args.output_dir / (normalize(ctgry) + ".html"));
    200 
    201     category_index.sort();
    202     category_index.create_index(ost, category_name);
    203   }
    204 
    205   std::ofstream ofs_robots(args.output_dir / "robots.txt");
    206   index.create_robots(ofs_robots);
    207 
    208   std::ofstream ofs_sitemap(args.output_dir / "sitemap.xml");
    209   index.create_sitemap(ofs_sitemap);
    210 
    211   return 0;
    212 }