stamd

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

main.cpp (5398B)


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