Namespaces
Variants
Actions

User:Space Mission

From cppreference.com

Contents

Today is 2024/07/23 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;
}