/* ***************************************************************************** * Algorithm Intuition support * Copyright (c) 2021 killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals; using index_t = std::unordered_map; struct indexes { index_t std; index_t other; enum relation { nolink, local, help, external, anchor, }; struct lookup_result_t { relation rel; std::optional url; std::string classes; [[nodiscard]] auto to_link(const std::string_view text) const -> std::string { if (url) { const auto r = [&] { switch (rel) { case nolink: return ""sv; case local: return "target=\"_blank\" "sv; case help: return "rel=\"help\""sv; case external: return "target=\"_blank\" rel=\"external noopener\""sv; case anchor: return ""sv; } }(); return kblib::concat(""sv, text, ""sv); } else { return std::string(text); } } }; [[nodiscard]] auto lookup(const std::string_view src, const std::string_view name) const -> lookup_result_t { if (src.empty()) { return {}; } const auto id = kblib::concat(src, "::"sv, name); if (src == "here"sv) { return {anchor, std::string(name), {}}; } else if (src == "std"sv) { const auto url = std.find(id); if (url != std.end()) { return {external, "https://en.cppreference.com/w/"s + url->second, "doc-link"s}; } else { std::cerr << "Unknown cppreference link " << std::quoted(id) << '\n'; return {}; } } else { const auto url = other.find(id); if (url != other.end()) { } if (src == "kblib"sv) { return {local, "https://files.fileswhatever.net/code/kblib/doc/html/" "namespacekblib.html", "doc-link"s}; } else if (src == "CH"sv) { return {nolink, {}, {}}; } else if (src == "wg21"sv) { return {external, kblib::concat("https://wg21.link/"sv, name), "doc-link ext-ref"s}; } else if (src == "YT") { return {external, kblib::concat("https://www.youtube.com/watch?v="sv, name), "ext-ref"s}; } else if (src == "FN") { return {help, kblib::concat("#fn-"sv, name), {}}; } else { std::cerr << "Unknown link source " << std::quoted(src) << '\n'; return {}; } } } }; [[nodiscard]] static auto read_index(std::istream& in) -> index_t; [[nodiscard]] static auto format(std::string_view in, const indexes& idxs) -> std::string; struct header { struct col { std::string display; std::vector classes; }; std::vector> cols; }; [[nodiscard]] static auto read_header(const YAML::Node& in) -> header { assert(in.IsSequence()); header h; for (const auto& sel : in) { assert(sel.IsMap()); assert(sel.size() == 1); const auto& el = *sel.begin(); assert(el.first.IsDefined()); h.cols.push_back({el.first.as(), {el.second[0].as(), el.second[1].as>()}}); } return h; } using row = std::map; [[nodiscard]] static auto format_row(const header& h, const row& r, const indexes& idxs) -> std::string; template [[nodiscard]] CTRE_FLATTEN constexpr CTRE_FORCE_INLINE auto operator""_fixed() noexcept { return ctll::fixed_string{{charpack...}}; } template [[nodiscard]] constexpr auto operator+(const ctll::fixed_string a, const ctll::fixed_string b) -> ctll::fixed_string { char ret[A + B + 1]{}; const auto p = std::copy(a.begin(), a.end(), ret); std::copy(b.begin(), b.end(), p); return ctll::fixed_string{ret}; } auto main(int argc, char** argv) -> int { try { TCLAP::CmdLine cmd("Generate algorithm intuition table"); const TCLAP::UnlabeledValueArg infile( "input", "Input file", true, "", "filename.yml", cmd); const TCLAP::UnlabeledValueArg outfile( "output", "Output file", true, "", "filename.html", cmd); const TCLAP::ValueArg index_s("s", "index", "Index file", false, "data/index-cpp.idx", "filename.idx", cmd); const TCLAP::ValueArg index_o("o", "oindex", "Index file", false, "data/other.idx", "filename.idx", cmd); const TCLAP::SwitchArg standalone( "S", "standalone", "include enough HTML to be a standalone document", cmd); cmd.parse(argc, argv); auto index_file_s = std::ifstream(index_s.getValue()); if (not index_file_s) { std::cerr << "could not read index file " << index_s.getValue() << '\n'; } auto index_file_o = std::ifstream(index_o.getValue()); if (not index_file_o) { std::cerr << "could not read index file " << index_o.getValue() << '\n'; } const auto idxs = indexes{read_index(index_file_s), read_index(index_file_o)}; try { const auto input = YAML::LoadFile(infile.getValue()); const auto h = read_header(input["header"]); std::ofstream out(outfile.getValue()); int rows = 0; if (standalone.getValue()) { out << ""; } for (const auto& el : input["data"]) { // std::cout << "---\n" << el << '\n'; const auto r = el.as(); out << format_row(h, r, idxs); ++rows; } if (standalone.getValue()) { out << "
"; } std::cout << "Translated " << rows << " rows.\n"; } catch (YAML::ParserException& e) { std::cerr << e.what(); } } catch (TCLAP::ArgException& e) { std::cerr << e.what(); } catch (...) { std::cerr << "Unknown exception\n"; } return 0; } auto parse(std::string_view in) {} auto filter_display(std::string_view name) -> std::string { std::string lname; std::remove_copy(name.begin(), name.end(), std::back_inserter(lname), '@'); return lname; } auto filter_wbr(std::string_view in, std::string_view to = ""sv) -> std::string { std::string out; const auto a = "@"sv; kblib::search_replace_copy(in.begin(), in.end(), a.begin(), a.end(), to.begin(), to.end(), std::back_inserter(out)); return out; } auto format_row(const header& h, const row& r, const indexes& idxs) -> std::string { std::string ret; const auto id = std::string_view(r.at("id")); const auto src = id.substr(0, id.find(':')); const auto selflink = kblib::concat("
#"sv); kblib::append(ret, R"(
"sv, selflink, "
"sv, format(r.at("name"), idxs), "
\n"sv); for (const auto& [name, cdata] : kblib::indirect(h.cols.begin() + 1, h.cols.end())) { kblib::append(ret, "\t"sv, format(r.at(name), idxs), "\n"sv); } kblib::append(ret, ""sv); return ret; } auto read_index(std::istream& in) -> index_t { index_t ret; std::string line; int lines{}; int dups{}; while (std::getline(in, line)) { using namespace ctre::literals; const auto match = "(?.*?) => (?.*)"_ctre.match(line); static constexpr auto key = "key"_fixed; static constexpr auto path = "path"_fixed; auto r = ret.insert( {match.get().to_string(), match.get().to_string()}); if (not r.second) { /* std::clog << "Warning: duplicate index key " << std::quoted(match.get<1>().to_view()) << '\n'; std::clog << std::quoted(line) << '\n'; //*/ ++dups; } ++lines; } std::cout << "read " << lines << " lines (" << dups << " dups) from index\n"; return ret; } auto format(std::string_view in, const indexes& idxs) -> std::string { // Literal text, not in braces static constexpr auto lit_part = R"((?[^{]+)|)"_fixed; static constexpr auto lit = "lit"_fixed; /* Immediately after the {, certain flag characters can appear to modify the * generated text. $ is for red color, ^ is for superscript, % indicates * monospaced text (this is the default unless overriding text is provided), * # makes the link a local reference, and ~ makes the link an anchor. */ static constexpr auto flag_part = R"(\{(?[$^%#~]*))"_fixed; static constexpr auto flag = "flag"_fixed; /* Normally, the name is used to generate the displayed text, but non-default * text may be provided, followed by a |. */ static constexpr auto text_part = R"((?:(?[^"|}]*)\|)?)"_fixed; static constexpr auto text = "txt"_fixed; /* If the source field is non-empty, the name is looked up and a link is * created. */ static constexpr auto src_part = R"((?\w*):)"_fixed; static constexpr auto src = "src"_fixed; /* The name is the primary semantic content of the tag. It may contain @ * characters, which are removed and replaced by word break hints. */ static constexpr auto name_part = R"((?[^}]+)\})"_fixed; static constexpr auto name = "name"_fixed; static constexpr auto ref = lit_part + flag_part + text_part + src_part + name_part; std::string ret; for (auto m : ctre::range(in)) { // std::clog << std::quoted(m.get<0>().view()) << '\n'; if (const auto l = m.get()) { ret += l.view(); } else { enum F { red = 1, footnote = 2, link = 4, mono = 8, anchor_ = 16, local_ = 32 }; const auto flags = [&]() -> F { const auto f = m.get(); return F((f.view().find('$') != std::string::npos ? red : 0u) | (f.view().find('^') != std::string::npos ? footnote : 0u) | (f.view().find('%') != std::string::npos ? mono : 0u) | (f.view().find('#') != std::string::npos ? local_ : 0u) | (f.view().find('~') != std::string::npos ? anchor_ : 0u) | (not m.get().view().empty() ? link : 0u)); }(); if (flags & anchor_) { throw std::logic_error("anchors not yet implemented"); } auto code_class = "code-word"s; if (flags & red) { code_class += " red"sv; } if (not (flags & link)) { if ((flags & footnote) or m.get()) { std::clog << "invalid code " << std::quoted(m.get<0>().view()) << '\n'; } else { kblib::append(ret, "().to_string())); } kblib::append(ret, "\">"sv, filter_wbr(m.get().to_string()), ""sv); } } else { const auto lsrc = m.get().view(); const auto r_name = m.get().view(); const auto lname = filter_display(r_name); auto url = std::invoke([&] { if (flags & local_) { return indexes::lookup_result_t{ indexes::anchor, kblib::concat('#', lsrc, ':', lname), {}}; } else { return idxs.lookup(lsrc, lname); } }); std::string link_text; if (const auto t = m.get()) { if (flags & mono) { link_text = kblib::concat(""sv, filter_wbr(t.view()), ""sv); } else { link_text = filter_wbr(t.view()); } } else { kblib::append(link_text, ""sv, filter_wbr(r_name), ""sv); } if (flags & footnote) { url.classes += " fn-ref"sv; } kblib::append(ret, url.to_link(link_text)); } } } return ret; }