Difference between revisions of "User:Space Mission"
From cppreference.com
m (→A note on syntax highlight: fmt) |
m (→Utilities: +updates to FTM table generator.) |
||
(10 intermediate revisions by one user not shown) | |||
Line 12: | Line 12: | ||
===== TODO ===== | ===== TODO ===== | ||
* add new range adaptors pages... | * add new range adaptors pages... | ||
+ | * gather new names (C++17/20/23) for GeShi highlighter/links generator | ||
− | ==== | + | ==== Links useful for editing ==== |
− | * [https://meta.wikimedia.org/wiki/Help:Variable Variables] | + | * [https://meta.wikimedia.org/wiki/Help:Variable WikiMedia: Variables] |
− | * [https:// | + | * [https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions WikiMedia: Parser Functions] |
− | * [https:// | + | * [https://meta.wikimedia.org/wiki/Help:Advanced_templates WikiMedia: Advanced templates] |
− | * [https://www.mediawiki.org/wiki/Help:Magic_words Enchantment] | + | * [https://www.mediawiki.org/wiki/Help:Magic_words WikiMedia: Enchantment] |
− | * [https://www.mediawiki.org/wiki/Extension:SyntaxHighlight | + | * [https://www.mediawiki.org/wiki/Extension:SyntaxHighlight WikiMedia: Syntax Highlight] |
+ | * [https://www.w3.org/wiki/Common_HTML_entities_used_for_typography Common HTML entities] | ||
+ | * Unicode symbols | ||
− | + | ==== A note on syntax highlight ==== | |
− | cppreference uses ''GeShi'' syntax highlighter, which does not | + | cppreference uses ''GeShi'' syntax highlighter, the currently used version of which does not recognize most of new (C++17/20/23) keywords. Nonetheless, there is a possibility to make them highlighted (in the source-blocks) by editing the 'on-server' file that is actually a copy of the [https://raw.githubusercontent.com/GeSHi/geshi-1.0/master/src/geshi/cpp.php geshi/cpp.php]. The new keywords (e.g. {{c|char8_t}}, {{c|consteval}}, etc.) should be added into {{c|1= 'KEYWORDS' => array}} of that file. |
− | + | ==== Some of my examples ==== | |
+ | {{cot|}} | ||
+ | * [[cpp/string/basic_string_view#Example|Optical illusion]] | ||
+ | * [https://en.cppreference.com/mwiki/index.php?title=cpp/chrono/year_month_weekday_last&oldid=122229#Example Month calendar] | ||
+ | * [[cpp/numeric/random/fisher_f_distribution#Example|Fisher distribution in ASCII]] | ||
+ | * ... * | ||
+ | {{cob}} | ||
− | + | ====Utilities==== | |
− | {{cot|Feature test extractor | + | {{petty|Not to be lost:}} |
+ | * [[Talk:cpp/symbol_index/macro#FTM finder|FTM difference finder]] (draft vs [[cpp/symbol_index/macro|Symbol Index]]). | ||
+ | * [[cpp/feature_test#Compiler Features Dump|Compiler Features Dump]]. | ||
+ | * FTM table generator for [[cpp/utility/feature_test]]: | ||
+ | {{cot|v1. Feature-test macros extractor / table generator ([[cpp/utility/feature_test]]); Source: [https://eel.is/c++draft online C++ draft (HTML)]}} | ||
<pre> | <pre> | ||
//! | //! | ||
Line 53: | Line 66: | ||
//! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless, | //! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless, | ||
//! : everything worked due to try_local_file_if_download_failed == true. | //! : everything worked due to try_local_file_if_download_failed == true. | ||
+ | //! 2023-02-27 : removed page's (volatile) header/footer generating code; left only the | ||
+ | //! : table generation; placed the macros counter in the table's footer. | ||
+ | //! 2023-04-05 : added support of macros w/o headers, e.g., __cpp_lib_modules. | ||
//! | //! | ||
//! TODO: add "freestanding" support, see P2198R4 (Freestanding Feature-Test Macros) | //! TODO: add "freestanding" support, see P2198R4 (Freestanding Feature-Test Macros) | ||
Line 64: | Line 80: | ||
#include <iostream> | #include <iostream> | ||
#include <regex> | #include <regex> | ||
− | |||
#include <sstream> | #include <sstream> | ||
+ | #include <stdexcept> | ||
#include <string> | #include <string> | ||
#include <string_view> | #include <string_view> | ||
Line 81: | Line 97: | ||
static inline constexpr auto command{ | static inline constexpr auto command{ | ||
"curl https://eel.is/c++draft/version.syn --silent -o "sv}; | "curl https://eel.is/c++draft/version.syn --silent -o "sv}; | ||
− | |||
− | |||
− | |||
− | |||
− | |||
// | // | ||
//! Do not fail if the source html file was not loaded (due to Internet problems) in | //! Do not fail if the source html file was not loaded (due to Internet problems) in | ||
− | //! this session | + | //! this session, but still exists locally (left from a previous session). |
//! Useful mostly for debugging. | //! Useful mostly for debugging. | ||
static inline constexpr bool try_local_file_if_download_failed {false}; | static inline constexpr bool try_local_file_if_download_failed {false}; | ||
Line 167: | Line 178: | ||
* Extracts data from a given string, that must be a part of | * Extracts data from a given string, that must be a part of | ||
* [table entry](https://eel.is/c++draft/version.syn) html page | * [table entry](https://eel.is/c++draft/version.syn) html page | ||
− | * and generates | + | * and generates the page [or (conditionally) only the table]: |
* [page](https://en.cppreference.com/w/cpp/utility/feature_test) | * [page](https://en.cppreference.com/w/cpp/utility/feature_test) | ||
*/ | */ | ||
Line 187: | Line 198: | ||
private: | private: | ||
− | |||
− | |||
[[nodiscard]] static std::string_view table_head(); | [[nodiscard]] static std::string_view table_head(); | ||
− | [[nodiscard]] static std:: | + | [[nodiscard]] static std::string table_tail(unsigned); |
private: | private: | ||
Line 245: | Line 254: | ||
headers_.emplace_back(m[1].first, m[1].second - m[1].first); | headers_.emplace_back(m[1].first, m[1].second - m[1].first); | ||
− | return | + | return true; |
} | } | ||
Line 266: | Line 275: | ||
tab.headers_[0] == "string" and tab.headers_[1] == "string_view"; | tab.headers_[0] == "string" and tab.headers_[1] == "string_view"; | ||
} | } | ||
− | |||
/** | /** | ||
− | * @brief | + | * @brief Generates the header of the table. |
*/ | */ | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
inline std::string_view FeatureTestTable::table_head() { | inline std::string_view FeatureTestTable::table_head() { | ||
− | + | return | |
R"--( | R"--( | ||
{| class="wikitable sortable" | {| class="wikitable sortable" | ||
Line 310: | Line 290: | ||
} | } | ||
− | inline std:: | + | /** |
+ | * @brief Generates the footer of the table. | ||
+ | */ | ||
+ | inline std::string FeatureTestTable::table_tail(unsigned entry_count) { | ||
+ | return | ||
+ | "|-\n" | ||
+ | "! colspan=\"3\" | Total number of macros: " + std::to_string(entry_count) + "\n" | ||
+ | "|}\n"; | ||
+ | } | ||
/** | /** | ||
Line 323: | Line 311: | ||
/* cppreference table entry sample: | /* cppreference table entry sample: | ||
|- | |- | ||
− | | | + | | {{tt|__cpp_lib_byte}} |
| 201603L | | 201603L | ||
| {{header|atomic}} {{header|filesystem}} ... | | {{header|atomic}} {{header|filesystem}} ... | ||
Line 406: | Line 394: | ||
while ((ok_ = !!std::getline(file_, cur_)) && cur_.find(significant) == ""sv.npos) | while ((ok_ = !!std::getline(file_, cur_)) && cur_.find(significant) == ""sv.npos) | ||
; | ; | ||
− | if (not ok_) throw std::logic_error("Invalid file format."); | + | if (not ok_) |
+ | throw std::logic_error("Invalid file format."); | ||
} else { // This is not the first run. Thus next_ contains the begin. | } else { // This is not the first run. Thus next_ contains the begin. | ||
cur_ = std::move(next_); | cur_ = std::move(next_); | ||
Line 419: | Line 408: | ||
/** | /** | ||
− | * @brief Generates | + | * @brief Generates/prints the table with standard library feature-testing macros. |
− | * @note The format of the output is the wiki-media language. The | + | * @note The format of the output is the wiki-media language. The result is meant to |
− | * be | + | * be copied to [page](https://en.cppreference.com/w/cpp/utility/feature_test). |
− | + | ||
− | + | ||
*/ | */ | ||
inline bool FeatureTestTable::generate() { | inline bool FeatureTestTable::generate() { | ||
Line 442: | Line 429: | ||
} | } | ||
− | std::cout | + | std::cout << FeatureTestTable::table_head(); |
auto entry_count {0U}; | auto entry_count {0U}; | ||
Line 460: | Line 447: | ||
} | } | ||
− | std::cout << FeatureTestTable::table_tail() << '\n' | + | std::cout << FeatureTestTable::table_tail(entry_count) << '\n'; |
− | + | ||
− | + | ||
// Some sanity checks: | // Some sanity checks: | ||
− | constexpr auto min_entries { | + | constexpr auto min_entries {179u}; // as per 2023-02-13 |
if (entry_count < min_entries) { | if (entry_count < min_entries) { | ||
std::cerr << "\nWARNING: Not enough entries! Expected at least " << | std::cerr << "\nWARNING: Not enough entries! Expected at least " << | ||
Line 497: | Line 482: | ||
int main() { | int main() { | ||
return self_tests() and FeatureTestTable::generate() ? EXIT_SUCCESS : EXIT_FAILURE; | return self_tests() and FeatureTestTable::generate() ? EXIT_SUCCESS : EXIT_FAILURE; | ||
+ | } | ||
+ | </pre> | ||
+ | {{cob}} | ||
+ | |||
+ | {{cot|v2. Feature-test macros extractor / table generator ([[cpp/utility/feature_test]]); Source: [https://github.com/cplusplus/draft github C++ draft (TEX)]}} | ||
+ | <pre> | ||
+ | //! \abstract This program downloads the "Language support library macros" | ||
+ | //! [page](https://github.com/cplusplus/draft/raw/main/source/support.tex) (used to be | ||
+ | //! [page](https://eel.is/c++draft/version.syn)), parses it, and then generates the | ||
+ | //! MediaWiki table for the [page](https://en.cppreference.com/w/cpp/utility/feature_test). | ||
+ | //! | ||
+ | //! \usage: just compile & run, the MediaWiki table will be sent to the terminal. | ||
+ | //! | ||
+ | //! \dependencies: [curl](https://en.wikipedia.org/wiki/CURL), C++23. | ||
+ | //! | ||
+ | //! \author: (c) 2021-2024. Space Mission. For cppreference.com internal usage. | ||
+ | //! \license: [CC-BY-SA](https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA). | ||
+ | //! | ||
+ | //! \news: | ||
+ | //! 2021-06-12 : initial release (used an array that contains the plain text copied from | ||
+ | //! : the eel page). | ||
+ | //! 2021-12-19 : added options and means to generate the whole wiki-page instead of only | ||
+ | //! : the wiki-table. Note that this requires updates if the original | ||
+ | //! : [page](cppreference.com/w/cpp/utility/feature_test) was changed. | ||
+ | //! 2021-12-20 : (optimizations) e.g. the output is generated w/o a temporary vector. | ||
+ | //! 2021-12-21 : added "curl" downloading of the source html file from | ||
+ | //! : [the site](https://eel.is/c++draft/version.syn). | ||
+ | //! 2021-12-23 : added data extractor from the html using std::regex library. | ||
+ | //! 2021-12-27 : added more test. | ||
+ | //! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless, | ||
+ | //! : everything worked due to try_local_file_if_download_failed == true. | ||
+ | //! 2023-02-27 : removed page's (volatile) header/footer generating code; left only the | ||
+ | //! : table generation; placed the macros counter in the table's footer. | ||
+ | //! 2023-04-05 : added support of macros w/o headers, e.g., __cpp_lib_modules. | ||
+ | //! 2024-07-21 : source switching: the source site now is github C++draft repo; the file | ||
+ | //! : format to parse is "tex"; added "freestanding" support. | ||
+ | //! 2024-07-22 : table style: added 4th ("freestanding") column; added FTM links. | ||
+ | |||
+ | #include <algorithm> | ||
+ | #include <cassert> | ||
+ | #include <cstdlib> | ||
+ | #include <filesystem> | ||
+ | #include <fstream> | ||
+ | #include <iomanip> | ||
+ | #include <iostream> | ||
+ | #include <regex> | ||
+ | #include <sstream> | ||
+ | #include <stdexcept> | ||
+ | #include <string> | ||
+ | #include <string_view> | ||
+ | |||
+ | using namespace std::literals; | ||
+ | |||
+ | /** | ||
+ | * @brief global options | ||
+ | */ | ||
+ | struct option { | ||
+ | //! Do not fail if the source file was not loaded (due to Internet problems) in | ||
+ | //! this session, but still exists locally (left from a previous session). | ||
+ | static inline constexpr bool try_local_file_if_download_failed{true}; | ||
+ | |||
+ | //! Remove downloaded source file (only if it was downloaded in this session). | ||
+ | static inline constexpr bool remove_downloaded_file_at_exit{false}; | ||
+ | |||
+ | //! Print log and additional info, e.g. success/failure. | ||
+ | static inline constexpr bool verbose{false}; | ||
+ | |||
+ | //! Test mode only. | ||
+ | static inline constexpr bool enable_self_tests{false}; | ||
+ | }; | ||
+ | |||
+ | //! | ||
+ | //! \brief The source file downloader that gets the source file from the Internet. | ||
+ | //! | ||
+ | class SourceDownloader final { | ||
+ | std::string file_name_; | ||
+ | |||
+ | public: | ||
+ | SourceDownloader() = default; | ||
+ | SourceDownloader(SourceDownloader const&) = default; | ||
+ | SourceDownloader& operator=(SourceDownloader const&) = default; | ||
+ | ~SourceDownloader(); | ||
+ | |||
+ | public: | ||
+ | [[nodiscard]] bool load(); | ||
+ | [[nodiscard]] const std::string& file_name() const noexcept { return file_name_; } | ||
+ | }; | ||
+ | |||
+ | /** | ||
+ | * @brief Loads source TEX file from the C++-draft github repo. | ||
+ | * @return true, if the file was downloaded successfully. | ||
+ | */ | ||
+ | inline bool SourceDownloader::load() { | ||
+ | file_name_ = std::filesystem::temp_directory_path() / "cxx_draft__support.tex"; | ||
+ | |||
+ | const std::string command{ | ||
+ | "curl -LJ https://github.com/cplusplus/draft/raw/main/source/support.tex "s + | ||
+ | (option::verbose ? "-o "s : "--silent -o "s) + file_name_}; | ||
+ | |||
+ | if constexpr (option::verbose) { | ||
+ | std::cout << "Downloading with: [" << command << "]\n" | ||
+ | "Destination file: " << file_name_ << '\n'; | ||
+ | } | ||
+ | |||
+ | if (const int ret_code{std::system(command.data())}; ret_code) { | ||
+ | if constexpr (option::verbose) { | ||
+ | std::cout << "Can't download the file: error #" << ret_code << '\n'; | ||
+ | // TODO: maybe decipher the error return code. | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | if constexpr (option::verbose) { | ||
+ | std::cout << "OK. The file was downloaded successfully.\n"; | ||
+ | } | ||
+ | |||
+ | return true; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Conditionally remove downloaded source file. | ||
+ | */ | ||
+ | inline SourceDownloader::~SourceDownloader() { | ||
+ | if constexpr (option::remove_downloaded_file_at_exit) | ||
+ | if (!file_name_.empty()) | ||
+ | std::filesystem::remove(file_name_); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief class FtmTableEntry. | ||
+ | * | ||
+ | * Extracts data from a given string, that must be a part of | ||
+ | * [source page](https://github.com/cplusplus/draft/raw/main/source/support.tex) | ||
+ | * tex page; generates the page [or (conditionally) only the table]: | ||
+ | * [target page](https://en.cppreference.com/w/cpp/utility/feature_test) | ||
+ | */ | ||
+ | class FtmTableEntry { | ||
+ | public: | ||
+ | FtmTableEntry() = default; | ||
+ | |||
+ | public: | ||
+ | [[nodiscard]] std::string generate_entry(std::string_view source); | ||
+ | void clear(); | ||
+ | |||
+ | private: | ||
+ | [[nodiscard]] bool parse_line(std::string_view source); | ||
+ | |||
+ | public: | ||
+ | static bool generate(); | ||
+ | |||
+ | public: | ||
+ | [[nodiscard]] static bool self_test(); | ||
+ | |||
+ | private: | ||
+ | [[nodiscard]] static std::string_view table_head() noexcept; | ||
+ | [[nodiscard]] static std::string table_tail(unsigned); | ||
+ | |||
+ | private: | ||
+ | std::vector<std::string_view> headers_; // e.g. {"vector", "type_traits"} | ||
+ | std::string macro_; // e.g. "__cpp_lib_any" | ||
+ | std::string_view date_; // e.g. "201606L" | ||
+ | bool free_{}; // true, if "freestanding" | ||
+ | }; | ||
+ | |||
+ | /** | ||
+ | * @brief Parses the source line and sets internal values for: | ||
+ | */ | ||
+ | inline void FtmTableEntry::clear() { | ||
+ | headers_.clear(); | ||
+ | macro_.clear(); | ||
+ | date_ = std::string_view{}; | ||
+ | free_ = false; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Parses the source line and sets internal values for: | ||
+ | * macro_, date_, headers_, free_. | ||
+ | * @param source, the TEX source line. | ||
+ | * @exception std::logic_error, if the source line has a wrong format. | ||
+ | * @return true, if line was parsed successfully. | ||
+ | */ | ||
+ | inline bool FtmTableEntry::parse_line(std::string_view source) { | ||
+ | clear(); | ||
+ | std::cmatch m; | ||
+ | constexpr std::regex::flag_type flags{std::regex_constants::ECMAScript | | ||
+ | std::regex_constants::optimize}; | ||
+ | |||
+ | // A fake example: | ||
+ | // #define @\defnlibxname{cpp_lib_atomic}@ 201911L // freestanding, also in | ||
+ | // \libheader{atomic}, \libheader{memory} | ||
+ | |||
+ | if (static const std::regex re_ftm_and_date{ | ||
+ | R"FTM(#define @\\defnlibxname\{(cpp_lib_[_a-z0-9]{3,50})\}@)FTM" | ||
+ | R"DATE([ ]+(20[1-4][0-9]{3}L)[ ]+// )DATE", | ||
+ | flags}; | ||
+ | !std::regex_search(source.data(), m, re_ftm_and_date) or m.size() != 3) | ||
+ | return false; | ||
+ | macro_ = "__" + std::string(m[1].first, m[1].second - m[1].first); | ||
+ | date_ = std::string_view(m[2].first, m[2].second - m[2].first); | ||
+ | |||
+ | // sanity check: | ||
+ | if (macro_.length() < "__cpp_lib_xxx"sv.length() or | ||
+ | date_.length() != (/*sample:*/"202002L"sv).length()) | ||
+ | return false; | ||
+ | |||
+ | source.remove_prefix(m.suffix().first - source.data()); // the tail | ||
+ | constexpr auto free{"freestanding"sv}; | ||
+ | // Extract "freestaning" flag, if any. | ||
+ | if (const auto pos{source.substr(0, free.length() + 8).find(free)}; pos != ""sv.npos) { | ||
+ | source.remove_prefix(pos + free.length()); | ||
+ | free_ = true; | ||
+ | } | ||
+ | |||
+ | // Obtain header(s) in cycle. | ||
+ | // TEX example: ... also in \libheader{atomic}, \libheader{memory} ... | ||
+ | static const std::regex re_header{ | ||
+ | R"RE(\libheader\{([a-z][_a-z\d\.]+)\})RE", flags}; | ||
+ | while (std::regex_search(source.data(), m, re_header) and m.size() == 2) { | ||
+ | headers_.emplace_back(m[1].first, m[1].second - m[1].first); | ||
+ | source.remove_prefix(m.suffix().first - source.data()); // update the tail | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Performs self test and returns. | ||
+ | * | ||
+ | * @return true, if self-test has passed. | ||
+ | */ | ||
+ | inline bool FtmTableEntry::self_test() { | ||
+ | FtmTableEntry tab; | ||
+ | if constexpr (1) { | ||
+ | tab.clear(); | ||
+ | constexpr auto fake_tex = | ||
+ | "#define @\\defnlibxname{cpp_lib_atomic_lock}@ " | ||
+ | "201907L // also in \\libheader{atomic}"sv; | ||
+ | if (!(tab.parse_line(fake_tex) and tab.macro_ == "__cpp_lib_atomic_lock" and | ||
+ | tab.date_ == "201907L" and tab.free_ == false and | ||
+ | tab.headers_.size() == 1 and tab.headers_[0] == "atomic")) { | ||
+ | std::cerr << "TESTS #1 FAILED.\n"; | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | if constexpr (1) { | ||
+ | tab.clear(); | ||
+ | constexpr auto fake_tex = | ||
+ | "#define @\\defnlibxname{cpp_lib_char8_t}@ 201907L // freestanding, " | ||
+ | "also in \\libheader{atomic}, \\libheader{filesystem}, " | ||
+ | "// \\libheader{istream}, \\libheader{limits}"sv; | ||
+ | if (!(tab.parse_line(fake_tex) and tab.macro_ == "__cpp_lib_char8_t" and | ||
+ | tab.date_ == "201907L" and tab.free_ and | ||
+ | tab.headers_.size() == 4 and | ||
+ | tab.headers_[0] == "atomic" and tab.headers_[1] == "filesystem" and | ||
+ | tab.headers_[2] == "istream" and tab.headers_[3] == "limits")) { | ||
+ | std::cerr << "TESTS #2 FAILED.\n"; | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | if constexpr (option::verbose) | ||
+ | std::cerr << "OK. Tests passed.\n"; | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Generates the header of the table. | ||
+ | */ | ||
+ | inline std::string_view FtmTableEntry::table_head() noexcept { | ||
+ | return | ||
+ | R"--( | ||
+ | {|class="wikitable sortable" style="font-size:100%;" | ||
+ | |- | ||
+ | !Macro name!!Value!!Header!!Free-<br>standing | ||
+ | )--"sv; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Generates the footer of the table. | ||
+ | */ | ||
+ | inline std::string FtmTableEntry::table_tail(unsigned entry_count) { | ||
+ | return | ||
+ | "|-\n" | ||
+ | "!colspan=\"4\"|Total number of macros: " + std::to_string(entry_count) + "\n" | ||
+ | "|}\n"; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Generates cppreference table entry. | ||
+ | * @param source - source string to parse. | ||
+ | * @return non-empty table entry string, if success. An empty string otherwise. | ||
+ | */ | ||
+ | inline std::string FtmTableEntry::generate_entry(std::string_view source) { | ||
+ | if (!parse_line(source)) | ||
+ | return {}; | ||
+ | |||
+ | /* cppreference table entry sample: | ||
+ | |- | ||
+ | |{{ftm link|__cpp_lib_byte}} | ||
+ | |201603L | ||
+ | |{{header|atomic}} {{header|filesystem}} ... | ||
+ | |{{yes}} | ||
+ | */ | ||
+ | std::ostringstream str("", std::ios_base::ate); | ||
+ | str << "|-\n" | ||
+ | "|{{ftm link|" << macro_ << "}}\n" | ||
+ | "|{{c|" << date_ << "}}\n" | ||
+ | "|"; | ||
+ | for (int n{}; auto const& header : headers_) | ||
+ | str << (n++ ? " " : "") << "{{header|" << header << "}}"; | ||
+ | str << (free_ ? "\n|{{yes}}" : "\n|"); | ||
+ | return str.str(); | ||
+ | } | ||
+ | |||
+ | class SourceFileReader { | ||
+ | public: | ||
+ | explicit SourceFileReader(std::string_view const file_name); | ||
+ | SourceFileReader(SourceFileReader const&) = delete; | ||
+ | SourceFileReader& operator=(SourceFileReader const&) = delete; | ||
+ | |||
+ | [[nodiscard]] operator std::ifstream& () noexcept { return file_; } | ||
+ | [[nodiscard]] std::string_view cur_line() const { return cur_; } | ||
+ | [[nodiscard]] bool is_open() const { return ok_; } | ||
+ | [[nodiscard]] bool next_line(); | ||
+ | |||
+ | private: | ||
+ | std::ifstream file_; | ||
+ | std::string cur_; | ||
+ | std::string next_; | ||
+ | bool ok_{false}; | ||
+ | }; | ||
+ | |||
+ | /** | ||
+ | * @brief Opens the source TEX file. | ||
+ | * Sets the success-flag that should be checked with is_open(). | ||
+ | * @param file_name - the source TEX file name. | ||
+ | */ | ||
+ | inline SourceFileReader::SourceFileReader(std::string_view const file_name) { | ||
+ | const auto file = std::filesystem::path{file_name}; | ||
+ | auto ec = std::error_code{}; | ||
+ | |||
+ | if (!std::filesystem::exists(file, ec)) { | ||
+ | std::cerr << "ERROR: source file not found: " << file << '\n'; | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | constexpr std::uintmax_t min_file_size{200'000}; // bytes | ||
+ | if (std::uintmax_t size; (size = std::filesystem::file_size(file, ec)) < min_file_size) { | ||
+ | std::cerr << "ERROR: source file: " << file << "\n" | ||
+ | " is too small, size = " << size << " bytes;\n" | ||
+ | " expected size >= " << min_file_size << " bytes\n"; | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | if (file_.open(file_name.data()); not(ok_ = file_.is_open())) { | ||
+ | std::cerr << "ERROR: can't open the source file: " << file << '\n'; | ||
+ | return; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Obtains the next line, if it is available (may concatinate few lines) | ||
+ | * @exception std::logic_error, in case of wrong TEX file format | ||
+ | * @return true, if next line is available | ||
+ | */ | ||
+ | inline bool SourceFileReader::next_line() { | ||
+ | if (not ok_) | ||
+ | return false; | ||
+ | |||
+ | constexpr auto intro{"#define "sv}; | ||
+ | constexpr auto outro{"\\end"sv}; // \end{codeblock} | ||
+ | |||
+ | if (cur_.empty()) { // This is a first run. Find the first valid line. | ||
+ | // Exprected preliminary line are: | ||
+ | // | ||
+ | // "Future revisions of this document might replace" | ||
+ | // " the values of these macros with greater values." | ||
+ | // "\end{note}" | ||
+ | // "" | ||
+ | // "\begin{codeblock}" | ||
+ | // "#define ..." <== intro | ||
+ | // | ||
+ | constexpr auto nearest{"Future revisions of this document"sv}; | ||
+ | while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(nearest)) {} | ||
+ | while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(intro)) {} | ||
+ | if (not ok_) | ||
+ | throw std::logic_error("Unexpected file format."); | ||
+ | } else { // This is not the first run. Thus next_ contains the beginning. | ||
+ | cur_ = std::move(next_); | ||
+ | } | ||
+ | |||
+ | // Find the next begin (or EOB), appending the dependent lines. | ||
+ | while ((ok_ = !!std::getline(file_, next_)) && !next_.starts_with(intro)) { | ||
+ | if (next_.starts_with(outro)) { | ||
+ | ok_ = false; // End Of Block | ||
+ | break; | ||
+ | } | ||
+ | cur_ += next_; | ||
+ | } | ||
+ | |||
+ | return true; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Generates/prints the table with standard library feature-testing macros. | ||
+ | * @note The result is a table described in MediaWiki language. The output should be | ||
+ | * copied onto "https://en.cppreference.com/w/cpp/utility/feature_test" page. | ||
+ | */ | ||
+ | inline bool FtmTableEntry::generate() { | ||
+ | SourceDownloader downloader; | ||
+ | if (not downloader.load()) { | ||
+ | if constexpr (option::try_local_file_if_download_failed) { | ||
+ | if constexpr (option::verbose) | ||
+ | std::cerr << "Trying to use local source file: " | ||
+ | << downloader.file_name() << "\n\n"; | ||
+ | } else { | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | auto lines{SourceFileReader{downloader.file_name()}}; | ||
+ | if (not lines.is_open()) { | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | std::cout << FtmTableEntry::table_head(); | ||
+ | |||
+ | auto entry_count{0U}; | ||
+ | |||
+ | try { | ||
+ | for (FtmTableEntry table; lines.next_line(); ) { | ||
+ | const std::string line{ table.generate_entry(lines.cur_line()) }; | ||
+ | if (not line.empty()) { | ||
+ | std::cout << line << '\n'; | ||
+ | ++entry_count; | ||
+ | } | ||
+ | } | ||
+ | } catch (std::logic_error const& ex) { | ||
+ | if constexpr (option::verbose) | ||
+ | std::cerr << "ERROR: " << ex.what() << '\n'; | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | std::cout << FtmTableEntry::table_tail(entry_count) << '\n'; | ||
+ | |||
+ | // A sanity check :) | ||
+ | constexpr auto min_entries{233u}; // as per 2024-07 | ||
+ | if (entry_count < min_entries) { | ||
+ | std::cerr << "\nWARNING: Too few entries! Expected " << min_entries << " or more.\n"; | ||
+ | } | ||
+ | |||
+ | return true; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @brief Performs self test and returns. | ||
+ | * | ||
+ | * @return true, if self-tests passed. | ||
+ | */ | ||
+ | inline bool self_tests() { | ||
+ | if constexpr (not option::enable_self_tests) | ||
+ | return true; | ||
+ | return FtmTableEntry::self_test(); | ||
+ | |||
+ | } | ||
+ | |||
+ | int main() { | ||
+ | return self_tests() and FtmTableEntry::generate() ? EXIT_SUCCESS : EXIT_FAILURE; | ||
} | } | ||
</pre> | </pre> | ||
{{cob}} | {{cob}} |
Latest revision as of 11:00, 22 July 2024
Contents |
Today is 2024/11/15 yesterday's tomorrow.
Update routinely (A reminder):
(check/fill/synchronize the following feature-test-macro pages)
- cpp/feature_test#Library_features
- cpp/feature_test#Example
- cpp/utility/feature_test
- cpp/symbol_index/macro
- cpp/compiler_support/23
TODO
- add new range adaptors pages...
- gather new names (C++17/20/23) for GeShi highlighter/links generator
Links useful for editing
- WikiMedia: Variables
- WikiMedia: Parser Functions
- WikiMedia: Advanced templates
- WikiMedia: Enchantment
- WikiMedia: Syntax Highlight
- Common HTML entities
- Unicode symbols
A note on syntax highlight
cppreference uses GeShi syntax highlighter, the currently used version of which does not recognize most of new (C++17/20/23) keywords. Nonetheless, there is a possibility to make them highlighted (in the source-blocks) by editing the 'on-server' file that is actually a copy of the geshi/cpp.php. The new keywords (e.g. char8_t, consteval, etc.) should be added into 'KEYWORDS' => array of that file.
Some of my examples
Utilities
Not to be lost:
- FTM difference finder (draft vs Symbol Index).
- Compiler Features Dump.
- FTM table generator for cpp/utility/feature_test:
v1. Feature-test macros extractor / table generator (cpp/utility/feature_test); Source: online C++ draft (HTML) |
---|
//! //! \abstract This program downloads the "Language support library macros" //! [page](https://eel.is/c++draft/version.syn), parses it, and then generates the //! wiki-source for the [page](https://en.cppreference.com/w/cpp/utility/feature_test). //! //! \usage: just compile & run, the wiki-page will be sent to terminal. //! //! \dependencies: the `curl` program, C++20. //! //! \author: (c) 2021. Space Mission. For cppreference.com internal usage. //! \license: [CC-BY-SA](https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA). //! //! \news: //! 2021-06-12 : initial release (used an array that contains the plain text copied from //! : the eel page). //! 2021-12-19 : added options and means to generate the whole wiki-page instead of only //! : the wiki-table. Note that this requires updates if the original //! : [page](cppreference.com/w/cpp/utility/feature_test) was changed. //! 2021-12-20 : (optimizations) e.g. the output is generated w/o a temporary vector. //! 2021-12-21 : added "curl" downloading of the source html file from //! : [the site](https://eel.is/c++draft/version.syn). //! 2021-12-23 : added data extractor from the html using std::regex library. //! 2021-12-27 : added more test. //! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless, //! : everything worked due to try_local_file_if_download_failed == true. //! 2023-02-27 : removed page's (volatile) header/footer generating code; left only the //! : table generation; placed the macros counter in the table's footer. //! 2023-04-05 : added support of macros w/o headers, e.g., __cpp_lib_modules. //! //! TODO: add "freestanding" support, see P2198R4 (Freestanding Feature-Test Macros) #include <algorithm> #include <cassert> #include <cstdlib> #include <filesystem> #include <fstream> #include <iomanip> #include <iostream> #include <regex> #include <sstream> #include <stdexcept> #include <string> #include <string_view> using namespace std::literals; /** * @brief global options */ struct option { // //! A command to download the source html page. This string will be appended with the //! target file and then executed by std::system( command / tmp-dir / file-name ); //! static inline constexpr auto command{ "curl https://eel.is/c++draft/version.syn --silent -o "sv}; // //! Do not fail if the source html file was not loaded (due to Internet problems) in //! this session, but still exists locally (left from a previous session). //! Useful mostly for debugging. static inline constexpr bool try_local_file_if_download_failed {false}; // //! Remove downloaded source html file (only if it was downloaded in this session). static inline constexpr bool remove_downloaded_file_at_exit {false}; // //! Print additional info, such as success/failure. static inline constexpr bool verbose {false}; // //! Test mode only. static inline constexpr bool enable_tests {false}; }; //! //! \brief A downloader: gets the source file from the Internet. //! class SourceDownloader final { std::string downloaded_file_name_; public: SourceDownloader() = default; SourceDownloader(SourceDownloader const&) = default; SourceDownloader& operator=(SourceDownloader const&) = default; ~SourceDownloader() { cleanup(); } public: [[nodiscard]] bool load(); [[nodiscard]] const std::string& file_name() const noexcept { return downloaded_file_name_; } void cleanup(); }; /** * @brief Loads source html file from the "https://eel.is/c++draft/". * @return return true if the file was downloaded successfully. */ inline bool SourceDownloader::load() { constexpr auto html {"eel_cpp_header_version.html"sv}; downloaded_file_name_ = std::filesystem::temp_directory_path() / html; const std::string command {option::command.data() + downloaded_file_name_}; if constexpr (option::verbose) { std::cout << "Downloading with: [" << command << "]\n"; std::cout << "Destination file: " << downloaded_file_name_ << '\n'; } if (const int ret_code {std::system(command.data())}; ret_code != 0) { if constexpr (option::verbose) { std::cout << "Can't download the file: error #" << ret_code << '\n'; // TODO: maybe decipher the error return code. } return false; } if constexpr (option::verbose) std::cout << "OK. The file was downloaded successfully.\n"; return true; } /** * @brief Reset file and (conditionally) remove downloaded html source file. */ inline void SourceDownloader::cleanup() { if constexpr (option::remove_downloaded_file_at_exit) { if (!downloaded_file_name_.empty()) { std::filesystem::remove(downloaded_file_name_); downloaded_file_name_.clear(); } } } /** * @brief class FeatureTestTable. * Extracts data from a given string, that must be a part of * [table entry](https://eel.is/c++draft/version.syn) html page * and generates the page [or (conditionally) only the table]: * [page](https://en.cppreference.com/w/cpp/utility/feature_test) */ class FeatureTestTable { public: FeatureTestTable() = default; public: [[nodiscard]] std::string generate_entry(std::string_view source); private: [[nodiscard]] bool parse_line(std::string_view source); public: static bool generate(); public: [[nodiscard]] static bool self_test(); private: [[nodiscard]] static std::string_view table_head(); [[nodiscard]] static std::string table_tail(unsigned); private: using string_view_vector = std::vector<std::string_view>; private: string_view_vector headers_; // e.g. {"vector", "type_traits"} std::string_view macro_; // e.g. "__cpp_lib_any" std::string_view date_; // e.g. "201606L" }; /** * @brief Parses the source line and sets internal values for macro_, date_, headers_ * @param[in] source - The source html line * @exception std::logic_error - throws if the source html has a wrong format. * @return true - if line was parsed successfully. */ inline bool FeatureTestTable::parse_line(std::string_view source) { headers_.clear(); macro_ = date_ = ""; const char* suffix; std::cmatch m; constexpr std::regex::flag_type flags{std::regex_constants::ECMAScript | std::regex_constants::optimize}; /* Obtain macro (e.g. "__cpp_lib_byte") and date (e.g. "201603L") * HTML example: <span class='preprocessordirective'>#define</span> <span id='lib:__cpp_lib_byte'>... ...<span class='literal'>202011L</span>... */ static const std::regex re_get_macro_and_date{ "'lib:(__cpp_lib_[_a-z0-9]{3,50})'.*'literal'>(20[1-4][0-9]{3}L)", flags }; if (!std::regex_search(source.data(), m, re_get_macro_and_date) or m.size() != 3) return false; macro_ = std::string_view(m[1].first, m[1].second - m[1].first); date_ = std::string_view(m[2].first, m[2].second - m[2].first); suffix = m.suffix().first; // contains the tail with at least one header if (macro_.length() < "__cpp_lib_xxx"sv.length() or date_.length() != (/*a sample:*/ "202002L"sv).length()) return false; /* Obtain header(s) in the cycle. * HTML example: ...<span id='headerref:<string_view>___'>... */ static const std::regex re_find_header{ R"regex((?:headerref:<)([_a-z\d]+(?:\.h)?)>)regex", flags}; for (; std::regex_search(suffix, m, re_find_header) and m.size() == 2; suffix = m.suffix().first) // contains the tail with zero or more headers headers_.emplace_back(m[1].first, m[1].second - m[1].first); return true; } /** * @brief Performs self test and returns. * * @return `true` if self-test passed. */ inline bool FeatureTestTable::self_test() { FeatureTestTable tab; constexpr auto fake_html = "<span class='preprocessordirective'>#define</span> <span id='lib:__cpp_lib_byte'>..." "...<span class='literal'>202011L</span>..." "...<span id='headerref:<string>___'>..." "...<span id='headerref:<string_view>___'>..."sv; return tab.parse_line(fake_html) and tab.macro_ == "__cpp_lib_byte" and tab.date_ == "202011L" and tab.headers_.size() == 2 and tab.headers_[0] == "string" and tab.headers_[1] == "string_view"; } /** * @brief Generates the header of the table. */ inline std::string_view FeatureTestTable::table_head() { return R"--( {| class="wikitable sortable" |- ! Macro name ! Value ! Header )--"sv; } /** * @brief Generates the footer of the table. */ inline std::string FeatureTestTable::table_tail(unsigned entry_count) { return "|-\n" "! colspan=\"3\" | Total number of macros: " + std::to_string(entry_count) + "\n" "|}\n"; } /** * @brief Generates cppreference table entry. * @param source - source string to parse. * @return non-empty table entry string, if success. An empty string otherwise. */ inline std::string FeatureTestTable::generate_entry(std::string_view source) { if (!parse_line(source)) return {}; /* cppreference table entry sample: |- | {{tt|__cpp_lib_byte}} | 201603L | {{header|atomic}} {{header|filesystem}} ... */ std::ostringstream str("", std::ios_base::ate); str << "|-\n" "| {{tt|" << macro_ << "}}\n" "| " << date_ << "\n" "|"; for (auto const& header : headers_) { str << " {{header|" << header << "}}"; } return str.str(); } // TODO: ? implement input_iterator interface for SourceFileReader: // begin(), end() [i.e. sentinel] operator*, and operator++. class SourceFileReader { public: SourceFileReader(SourceFileReader const&) = delete; SourceFileReader& operator=(SourceFileReader const&) = delete; explicit SourceFileReader(std::string_view const file_name); [[nodiscard]] bool is_open() const { return ok_; } [[nodiscard]] bool goto_next_line(); [[nodiscard]] std::string_view get_line() const { return cur_; } operator std::ifstream& () noexcept { return file_; } private: std::ifstream file_; std::string cur_; std::string next_; bool ok_{false}; }; /** * @brief Opens the source html file. * Sets the success-flag that should be checked with is_open(). * @param file_name - the source html file name. */ inline SourceFileReader::SourceFileReader(std::string_view const file_name) { const auto file = std::filesystem::path{file_name}; auto ec = std::error_code{}; if (!std::filesystem::exists(file, ec)) { std::cerr << "ERROR: source html file not found: " << file << '\n'; return; } constexpr auto min_file_size = std::uintmax_t{90'000}; // bytes if (auto size = std::uintmax_t{0}; (size = std::filesystem::file_size(file, ec)) < min_file_size) { std::cerr << "ERROR: source html file: " << file << '\n' << " is too small, size = " << size << " bytes;\n" << " expected size >= " << min_file_size << " bytes\n"; return; } file_.open(file_name.data()); if (not(ok_ = file_.is_open())) { std::cerr << "ERROR: can't open the source html file: " << file << '\n'; return; } } /** * @brief Go to the next line, if it is available. * @exception may throw std::logic_error in case of wrong file format * @return true, if next line is available */ inline bool SourceFileReader::goto_next_line() { if (not ok_) return false; constexpr auto significant {"preprocessordirective"sv}; if (cur_.empty()) { // This is a first run. Find the next valid line. while ((ok_ = !!std::getline(file_, cur_)) && cur_.find(significant) == ""sv.npos) ; if (not ok_) throw std::logic_error("Invalid file format."); } else { // This is not the first run. Thus next_ contains the begin. cur_ = std::move(next_); } // Find the next begin (or EOF), appending the dependent lines. while ((ok_ = !!std::getline(file_, next_)) && next_.find(significant) == ""sv.npos) { cur_ += next_; } return true; } /** * @brief Generates/prints the table with standard library feature-testing macros. * @note The format of the output is the wiki-media language. The result is meant to * be copied to [page](https://en.cppreference.com/w/cpp/utility/feature_test). */ inline bool FeatureTestTable::generate() { SourceDownloader html; if (not html.load()) { if constexpr (option::try_local_file_if_download_failed) { if constexpr (option::verbose) std::cerr << "Trying to use local source file: " << html.file_name() << "\n\n"; } else { return false; } } auto lines {SourceFileReader{html.file_name()}}; if (not lines.is_open()) { return false; } std::cout << FeatureTestTable::table_head(); auto entry_count {0U}; try { for (FeatureTestTable table; lines.goto_next_line(); ) { const std::string line{ table.generate_entry(lines.get_line()) }; if (not line.empty()) { std::cout << line << '\n'; ++entry_count; } } } catch (std::logic_error const& ex) { if constexpr (option::verbose) std::cerr << "ERROR: " << ex.what() << '\n'; return false; } std::cout << FeatureTestTable::table_tail(entry_count) << '\n'; // Some sanity checks: constexpr auto min_entries {179u}; // as per 2023-02-13 if (entry_count < min_entries) { std::cerr << "\nWARNING: Not enough entries! Expected at least " << min_entries << '\n'; } return true; } /** * @brief Performs self test and returns. * * @return `true` if self-tests passed. */ inline bool self_tests() { if constexpr (not option::enable_tests) return true; bool ok = FeatureTestTable::self_test(); if (ok) { if constexpr (option::verbose) std::cerr << "OK. Tests passed.\n"; } else { std::cerr << "TESTS FAILED.\n"; } return ok; } int main() { return self_tests() and FeatureTestTable::generate() ? EXIT_SUCCESS : EXIT_FAILURE; } |
v2. Feature-test macros extractor / table generator (cpp/utility/feature_test); Source: github C++ draft (TEX) |
---|
//! \abstract This program downloads the "Language support library macros" //! [page](https://github.com/cplusplus/draft/raw/main/source/support.tex) (used to be //! [page](https://eel.is/c++draft/version.syn)), parses it, and then generates the //! MediaWiki table for the [page](https://en.cppreference.com/w/cpp/utility/feature_test). //! //! \usage: just compile & run, the MediaWiki table will be sent to the terminal. //! //! \dependencies: [curl](https://en.wikipedia.org/wiki/CURL), C++23. //! //! \author: (c) 2021-2024. Space Mission. For cppreference.com internal usage. //! \license: [CC-BY-SA](https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA). //! //! \news: //! 2021-06-12 : initial release (used an array that contains the plain text copied from //! : the eel page). //! 2021-12-19 : added options and means to generate the whole wiki-page instead of only //! : the wiki-table. Note that this requires updates if the original //! : [page](cppreference.com/w/cpp/utility/feature_test) was changed. //! 2021-12-20 : (optimizations) e.g. the output is generated w/o a temporary vector. //! 2021-12-21 : added "curl" downloading of the source html file from //! : [the site](https://eel.is/c++draft/version.syn). //! 2021-12-23 : added data extractor from the html using std::regex library. //! 2021-12-27 : added more test. //! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless, //! : everything worked due to try_local_file_if_download_failed == true. //! 2023-02-27 : removed page's (volatile) header/footer generating code; left only the //! : table generation; placed the macros counter in the table's footer. //! 2023-04-05 : added support of macros w/o headers, e.g., __cpp_lib_modules. //! 2024-07-21 : source switching: the source site now is github C++draft repo; the file //! : format to parse is "tex"; added "freestanding" support. //! 2024-07-22 : table style: added 4th ("freestanding") column; added FTM links. #include <algorithm> #include <cassert> #include <cstdlib> #include <filesystem> #include <fstream> #include <iomanip> #include <iostream> #include <regex> #include <sstream> #include <stdexcept> #include <string> #include <string_view> using namespace std::literals; /** * @brief global options */ struct option { //! Do not fail if the source file was not loaded (due to Internet problems) in //! this session, but still exists locally (left from a previous session). static inline constexpr bool try_local_file_if_download_failed{true}; //! Remove downloaded source file (only if it was downloaded in this session). static inline constexpr bool remove_downloaded_file_at_exit{false}; //! Print log and additional info, e.g. success/failure. static inline constexpr bool verbose{false}; //! Test mode only. static inline constexpr bool enable_self_tests{false}; }; //! //! \brief The source file downloader that gets the source file from the Internet. //! class SourceDownloader final { std::string file_name_; public: SourceDownloader() = default; SourceDownloader(SourceDownloader const&) = default; SourceDownloader& operator=(SourceDownloader const&) = default; ~SourceDownloader(); public: [[nodiscard]] bool load(); [[nodiscard]] const std::string& file_name() const noexcept { return file_name_; } }; /** * @brief Loads source TEX file from the C++-draft github repo. * @return true, if the file was downloaded successfully. */ inline bool SourceDownloader::load() { file_name_ = std::filesystem::temp_directory_path() / "cxx_draft__support.tex"; const std::string command{ "curl -LJ https://github.com/cplusplus/draft/raw/main/source/support.tex "s + (option::verbose ? "-o "s : "--silent -o "s) + file_name_}; if constexpr (option::verbose) { std::cout << "Downloading with: [" << command << "]\n" "Destination file: " << file_name_ << '\n'; } if (const int ret_code{std::system(command.data())}; ret_code) { if constexpr (option::verbose) { std::cout << "Can't download the file: error #" << ret_code << '\n'; // TODO: maybe decipher the error return code. } return false; } if constexpr (option::verbose) { std::cout << "OK. The file was downloaded successfully.\n"; } return true; } /** * @brief Conditionally remove downloaded source file. */ inline SourceDownloader::~SourceDownloader() { if constexpr (option::remove_downloaded_file_at_exit) if (!file_name_.empty()) std::filesystem::remove(file_name_); } /** * @brief class FtmTableEntry. * * Extracts data from a given string, that must be a part of * [source page](https://github.com/cplusplus/draft/raw/main/source/support.tex) * tex page; generates the page [or (conditionally) only the table]: * [target page](https://en.cppreference.com/w/cpp/utility/feature_test) */ class FtmTableEntry { public: FtmTableEntry() = default; public: [[nodiscard]] std::string generate_entry(std::string_view source); void clear(); private: [[nodiscard]] bool parse_line(std::string_view source); public: static bool generate(); public: [[nodiscard]] static bool self_test(); private: [[nodiscard]] static std::string_view table_head() noexcept; [[nodiscard]] static std::string table_tail(unsigned); private: std::vector<std::string_view> headers_; // e.g. {"vector", "type_traits"} std::string macro_; // e.g. "__cpp_lib_any" std::string_view date_; // e.g. "201606L" bool free_{}; // true, if "freestanding" }; /** * @brief Parses the source line and sets internal values for: */ inline void FtmTableEntry::clear() { headers_.clear(); macro_.clear(); date_ = std::string_view{}; free_ = false; } /** * @brief Parses the source line and sets internal values for: * macro_, date_, headers_, free_. * @param source, the TEX source line. * @exception std::logic_error, if the source line has a wrong format. * @return true, if line was parsed successfully. */ inline bool FtmTableEntry::parse_line(std::string_view source) { clear(); std::cmatch m; constexpr std::regex::flag_type flags{std::regex_constants::ECMAScript | std::regex_constants::optimize}; // A fake example: // #define @\defnlibxname{cpp_lib_atomic}@ 201911L // freestanding, also in // \libheader{atomic}, \libheader{memory} if (static const std::regex re_ftm_and_date{ R"FTM(#define @\\defnlibxname\{(cpp_lib_[_a-z0-9]{3,50})\}@)FTM" R"DATE([ ]+(20[1-4][0-9]{3}L)[ ]+// )DATE", flags}; !std::regex_search(source.data(), m, re_ftm_and_date) or m.size() != 3) return false; macro_ = "__" + std::string(m[1].first, m[1].second - m[1].first); date_ = std::string_view(m[2].first, m[2].second - m[2].first); // sanity check: if (macro_.length() < "__cpp_lib_xxx"sv.length() or date_.length() != (/*sample:*/"202002L"sv).length()) return false; source.remove_prefix(m.suffix().first - source.data()); // the tail constexpr auto free{"freestanding"sv}; // Extract "freestaning" flag, if any. if (const auto pos{source.substr(0, free.length() + 8).find(free)}; pos != ""sv.npos) { source.remove_prefix(pos + free.length()); free_ = true; } // Obtain header(s) in cycle. // TEX example: ... also in \libheader{atomic}, \libheader{memory} ... static const std::regex re_header{ R"RE(\libheader\{([a-z][_a-z\d\.]+)\})RE", flags}; while (std::regex_search(source.data(), m, re_header) and m.size() == 2) { headers_.emplace_back(m[1].first, m[1].second - m[1].first); source.remove_prefix(m.suffix().first - source.data()); // update the tail } return true; } /** * @brief Performs self test and returns. * * @return true, if self-test has passed. */ inline bool FtmTableEntry::self_test() { FtmTableEntry tab; if constexpr (1) { tab.clear(); constexpr auto fake_tex = "#define @\\defnlibxname{cpp_lib_atomic_lock}@ " "201907L // also in \\libheader{atomic}"sv; if (!(tab.parse_line(fake_tex) and tab.macro_ == "__cpp_lib_atomic_lock" and tab.date_ == "201907L" and tab.free_ == false and tab.headers_.size() == 1 and tab.headers_[0] == "atomic")) { std::cerr << "TESTS #1 FAILED.\n"; return false; } } if constexpr (1) { tab.clear(); constexpr auto fake_tex = "#define @\\defnlibxname{cpp_lib_char8_t}@ 201907L // freestanding, " "also in \\libheader{atomic}, \\libheader{filesystem}, " "// \\libheader{istream}, \\libheader{limits}"sv; if (!(tab.parse_line(fake_tex) and tab.macro_ == "__cpp_lib_char8_t" and tab.date_ == "201907L" and tab.free_ and tab.headers_.size() == 4 and tab.headers_[0] == "atomic" and tab.headers_[1] == "filesystem" and tab.headers_[2] == "istream" and tab.headers_[3] == "limits")) { std::cerr << "TESTS #2 FAILED.\n"; return false; } } if constexpr (option::verbose) std::cerr << "OK. Tests passed.\n"; return true; } /** * @brief Generates the header of the table. */ inline std::string_view FtmTableEntry::table_head() noexcept { return R"--( {|class="wikitable sortable" style="font-size:100%;" |- !Macro name!!Value!!Header!!Free-<br>standing )--"sv; } /** * @brief Generates the footer of the table. */ inline std::string FtmTableEntry::table_tail(unsigned entry_count) { return "|-\n" "!colspan=\"4\"|Total number of macros: " + std::to_string(entry_count) + "\n" "|}\n"; } /** * @brief Generates cppreference table entry. * @param source - source string to parse. * @return non-empty table entry string, if success. An empty string otherwise. */ inline std::string FtmTableEntry::generate_entry(std::string_view source) { if (!parse_line(source)) return {}; /* cppreference table entry sample: |- |{{ftm link|__cpp_lib_byte}} |201603L |{{header|atomic}} {{header|filesystem}} ... |{{yes}} */ std::ostringstream str("", std::ios_base::ate); str << "|-\n" "|{{ftm link|" << macro_ << "}}\n" "|{{c|" << date_ << "}}\n" "|"; for (int n{}; auto const& header : headers_) str << (n++ ? " " : "") << "{{header|" << header << "}}"; str << (free_ ? "\n|{{yes}}" : "\n|"); return str.str(); } class SourceFileReader { public: explicit SourceFileReader(std::string_view const file_name); SourceFileReader(SourceFileReader const&) = delete; SourceFileReader& operator=(SourceFileReader const&) = delete; [[nodiscard]] operator std::ifstream& () noexcept { return file_; } [[nodiscard]] std::string_view cur_line() const { return cur_; } [[nodiscard]] bool is_open() const { return ok_; } [[nodiscard]] bool next_line(); private: std::ifstream file_; std::string cur_; std::string next_; bool ok_{false}; }; /** * @brief Opens the source TEX file. * Sets the success-flag that should be checked with is_open(). * @param file_name - the source TEX file name. */ inline SourceFileReader::SourceFileReader(std::string_view const file_name) { const auto file = std::filesystem::path{file_name}; auto ec = std::error_code{}; if (!std::filesystem::exists(file, ec)) { std::cerr << "ERROR: source file not found: " << file << '\n'; return; } constexpr std::uintmax_t min_file_size{200'000}; // bytes if (std::uintmax_t size; (size = std::filesystem::file_size(file, ec)) < min_file_size) { std::cerr << "ERROR: source file: " << file << "\n" " is too small, size = " << size << " bytes;\n" " expected size >= " << min_file_size << " bytes\n"; return; } if (file_.open(file_name.data()); not(ok_ = file_.is_open())) { std::cerr << "ERROR: can't open the source file: " << file << '\n'; return; } } /** * @brief Obtains the next line, if it is available (may concatinate few lines) * @exception std::logic_error, in case of wrong TEX file format * @return true, if next line is available */ inline bool SourceFileReader::next_line() { if (not ok_) return false; constexpr auto intro{"#define "sv}; constexpr auto outro{"\\end"sv}; // \end{codeblock} if (cur_.empty()) { // This is a first run. Find the first valid line. // Exprected preliminary line are: // // "Future revisions of this document might replace" // " the values of these macros with greater values." // "\end{note}" // "" // "\begin{codeblock}" // "#define ..." <== intro // constexpr auto nearest{"Future revisions of this document"sv}; while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(nearest)) {} while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(intro)) {} if (not ok_) throw std::logic_error("Unexpected file format."); } else { // This is not the first run. Thus next_ contains the beginning. cur_ = std::move(next_); } // Find the next begin (or EOB), appending the dependent lines. while ((ok_ = !!std::getline(file_, next_)) && !next_.starts_with(intro)) { if (next_.starts_with(outro)) { ok_ = false; // End Of Block break; } cur_ += next_; } return true; } /** * @brief Generates/prints the table with standard library feature-testing macros. * @note The result is a table described in MediaWiki language. The output should be * copied onto "https://en.cppreference.com/w/cpp/utility/feature_test" page. */ inline bool FtmTableEntry::generate() { SourceDownloader downloader; if (not downloader.load()) { if constexpr (option::try_local_file_if_download_failed) { if constexpr (option::verbose) std::cerr << "Trying to use local source file: " << downloader.file_name() << "\n\n"; } else { return false; } } auto lines{SourceFileReader{downloader.file_name()}}; if (not lines.is_open()) { return false; } std::cout << FtmTableEntry::table_head(); auto entry_count{0U}; try { for (FtmTableEntry table; lines.next_line(); ) { const std::string line{ table.generate_entry(lines.cur_line()) }; if (not line.empty()) { std::cout << line << '\n'; ++entry_count; } } } catch (std::logic_error const& ex) { if constexpr (option::verbose) std::cerr << "ERROR: " << ex.what() << '\n'; return false; } std::cout << FtmTableEntry::table_tail(entry_count) << '\n'; // A sanity check :) constexpr auto min_entries{233u}; // as per 2024-07 if (entry_count < min_entries) { std::cerr << "\nWARNING: Too few entries! Expected " << min_entries << " or more.\n"; } return true; } /** * @brief Performs self test and returns. * * @return true, if self-tests passed. */ inline bool self_tests() { if constexpr (not option::enable_self_tests) return true; return FtmTableEntry::self_test(); } int main() { return self_tests() and FtmTableEntry::generate() ? EXIT_SUCCESS : EXIT_FAILURE; } |