Namespaces
Variants
Actions

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
  
===== Useful WikiMedia links =====
+
==== Links useful for editing ====
* [https://meta.wikimedia.org/wiki/Help:Variable Variables]
+
* [https://meta.wikimedia.org/wiki/Help:Variable WikiMedia: Variables]
* [https://meta.wikimedia.org/wiki/Help:Advanced_templates Advanced templates]
+
* [https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions WikiMedia: Parser Functions]
* [https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions Parser Functions]
+
* [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 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 ======
+
==== A note on syntax highlight ====
cppreference uses ''GeShi'' syntax highlighter, which does not "know" new (C++17/20) keywords. However, it is still possible to make them be highlighted (in the ''source'' blocks) by editing an 'on-server' file that is a copy of [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.
+
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.
  
<br>
+
==== 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}}
  
<i><small>Not to be lost:</small></i>
+
====Utilities====
{{cot|Feature test extractor for [[cpp/utility/feature_test]] }}
+
{{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 <stdexcept>
 
 
#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};
    //
 
    //! If set to `true` - generate the whole target page:
 
    //! [page](cppreference.com/w/cpp/utility/feature_test).
 
    //! Otherwise (`false`) generate the table only.
 
    static inline constexpr bool generate_the_whole_wiki_page {true};
 
 
     //
 
     //
 
     //! 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 but, still exists locally (left from a previous 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 the the page [or (conditionally) only the table]:
+
  *  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 page_head();
 
    [[nodiscard]] static std::string_view page_tail();
 
 
     [[nodiscard]] static std::string_view table_head();
 
     [[nodiscard]] static std::string_view table_head();
     [[nodiscard]] static std::string_view table_tail();
+
     [[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 not headers_.empty();
+
     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  cppreference page and table on the page related heads and tails.
+
  * @brief  Generates the header of the table.
 
  */
 
  */
inline std::string_view FeatureTestTable::page_head() {
 
        if constexpr (option::generate_the_whole_wiki_page)
 
            return
 
        "{{title|Library feature-test macros {{mark c++20}}}}\n"
 
        "{{cpp/utility/navbar}}\n"
 
        "\n"
 
        "Each of following macros is defined if the header {{header|version}} or one of "
 
        "the corresponding headers specified in the table is included.\n"sv;
 
        else
 
            return ""sv;
 
        }
 
 
inline std::string_view FeatureTestTable::page_tail() {
 
    if constexpr (option::generate_the_whole_wiki_page)
 
        return
 
    "\n"
 
    "===See also===\n"
 
    "{{dsc begin}}\n"
 
    "{{dsc | [[cpp/feature_test|'''Feature testing''']] {{mark c++20}} | A set of "
 
    "preprocessor macros to test the corresponding to C++ language and library "
 
    "features }}\n"
 
    "{{dsc end}}\n"
 
    "\n"
 
    "{{langlinks|es|ja|ru|zh}}\n"sv;
 
    else
 
        return ""sv;
 
}
 
 
 
inline std::string_view FeatureTestTable::table_head() {
 
inline std::string_view FeatureTestTable::table_head() {
        return
+
    return
 
R"--(
 
R"--(
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
Line 310: Line 290:
 
}
 
}
  
inline std::string_view FeatureTestTable::table_tail() { return "|}"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";
 +
}
  
 
/**
 
/**
Line 323: Line 311:
 
     /* cppreference table entry sample:
 
     /* cppreference table entry sample:
 
     |-
 
     |-
     | {{tt|__cpp_lib_byte}}
+
     | {{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 the whole cppreference page (or only the table) and prints it out.
+
  * @brief  Generates/prints the table with standard library feature-testing macros.
  * @note    The format of the output is the wiki-media language. The printed result is to
+
  * @note    The format of the output is the wiki-media language. The result is meant to
  *          be applied to [page](https://en.cppreference.com/w/cpp/utility/feature_test).
+
  *          be copied to [page](https://en.cppreference.com/w/cpp/utility/feature_test).
* @note    What will be printed (the page or mere the table) depends on the flag:
+
*          option::generate_the_whole_wiki_page.
+
 
  */
 
  */
 
inline bool FeatureTestTable::generate() {
 
inline bool FeatureTestTable::generate() {
Line 442: Line 429:
 
     }
 
     }
  
     std::cout << FeatureTestTable::page_head() << FeatureTestTable::table_head();
+
     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';
              << "<!-- Entries count: " << entry_count << " -->\n"
+
              << FeatureTestTable::page_tail();
+
  
 
     // Some sanity checks:
 
     // Some sanity checks:
     constexpr auto min_entries {146u}; // as per 2021-12-24
+
     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)

  1. cpp/feature_test#Library_features
  2. cpp/feature_test#Example
  3. cpp/utility/feature_test
  4. cpp/symbol_index/macro
  5. 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

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:

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;
}