diff --git a/plugins/account_history/plugin.cpp b/plugins/account_history/plugin.cpp index 399636e155..7c5957a69b 100644 --- a/plugins/account_history/plugin.cpp +++ b/plugins/account_history/plugin.cpp @@ -149,7 +149,7 @@ if (options.count(name)) { \ history_operations result; const auto& idx = db.get_index().indices().get(); auto itr = idx.lower_bound(std::make_tuple(account, from)); - auto end = idx.upper_bound(std::make_tuple(account, std::max(int64_t(0), int64_t(itr->sequence) - limit))); + auto end = idx.upper_bound(std::make_tuple(account, std::max(int64_t(0), int64_t(itr->sequence) - limit + 1))); for (; itr != end; ++itr) { result[itr->sequence] = db.get(itr->op); result[itr->sequence].json_metadata = to_string(itr->json_metadata); @@ -245,7 +245,7 @@ if (options.count(name)) { \ } history_operations result; - while (!itrs.empty() && result.size() <= limit) { + while (!itrs.empty() && result.size() < limit) { auto itr = itrs.top().itr; itrs.pop(); result[itr->sequence] = db.get(itr->op); diff --git a/plugins/follow/follow_evaluators.cpp b/plugins/follow/follow_evaluators.cpp index 3cd7900f64..11da7e8f7e 100644 --- a/plugins/follow/follow_evaluators.cpp +++ b/plugins/follow/follow_evaluators.cpp @@ -239,12 +239,15 @@ namespace golos { auto feed_itr = comment_idx.find(boost::make_tuple(c.id, itr->follower)); if (feed_itr != comment_idx.end()) { + auto pos = std::find(feed_itr->reblogged_by.begin(), feed_itr->reblogged_by.end(), o.account); + if (pos == feed_itr->reblogged_by.end()) { + continue; + } if (feed_itr->reblogs <= 1) { db().remove(*feed_itr); } else { db().modify(*feed_itr, [&](feed_object& f) { - f.reblogged_by.erase(std::remove(f.reblogged_by.begin(), f.reblogged_by.end(), - o.account)); + f.reblogged_by.erase(pos); f.reblogs--; }); } diff --git a/plugins/follow/include/golos/plugins/follow/follow_objects.hpp b/plugins/follow/include/golos/plugins/follow/follow_objects.hpp index 4324d0bf8d..aceedaeb56 100644 --- a/plugins/follow/include/golos/plugins/follow/follow_objects.hpp +++ b/plugins/follow/include/golos/plugins/follow/follow_objects.hpp @@ -11,13 +11,14 @@ namespace golos { using chainbase::object; using chainbase::object_id; using chainbase::allocator; - using chainbase::shared_vector; using golos::chain::comment_object; using golos::chain::by_id; using golos::chain::comment_vote_index; using golos::chain::by_comment_voter; using golos::chain::shared_string; + namespace bip = boost::interprocess; + #ifndef FOLLOW_SPACE_ID #define FOLLOW_SPACE_ID 8 #endif @@ -53,14 +54,14 @@ namespace golos { template feed_object(Constructor &&c, allocator a) - :reblogged_by(a.get_segment_manager()) { + :reblogged_by(a) { c(*this); } id_type id; account_name_type account; - shared_vector reblogged_by; + bip::vector> reblogged_by; account_name_type first_reblogged_by; time_point_sec first_reblogged_on; comment_object::id_type comment; @@ -233,7 +234,7 @@ FC_REFLECT((golos::plugins::follow::follow_object), (id)(follower)(following)(wh CHAINBASE_SET_INDEX_TYPE(golos::plugins::follow::follow_object, golos::plugins::follow::follow_index) FC_REFLECT((golos::plugins::follow::feed_object), - (id)(account)(first_reblogged_by)(first_reblogged_on)(reblogged_by)(comment)(reblogs)(account_feed_id)) + (id)(account)(first_reblogged_by)(first_reblogged_on)(comment)(reblogs)(account_feed_id)) CHAINBASE_SET_INDEX_TYPE(golos::plugins::follow::feed_object, golos::plugins::follow::feed_index) FC_REFLECT((golos::plugins::follow::blog_object), (id)(account)(comment)(reblogged_on)(blog_feed_id)) diff --git a/plugins/follow/plugin.cpp b/plugins/follow/plugin.cpp index afd50b0ced..35fc2479e4 100644 --- a/plugins/follow/plugin.cpp +++ b/plugins/follow/plugin.cpp @@ -197,7 +197,7 @@ namespace golos { const auto& old_feed_idx = _db.get_index(); auto old_feed = old_feed_idx.lower_bound(itr->follower); - while (old_feed->account == itr->follower && + while (old_feed != old_feed_idx.end() && old_feed->account == itr->follower && next_id - old_feed->account_feed_id > _plugin.max_feed_size()) { _db.remove(*old_feed); old_feed = old_feed_idx.lower_bound(itr->follower); diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 4c352208b7..a19e736aad 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(build_helpers) add_subdirectory(cli_wallet) add_subdirectory(golosd) +add_subdirectory(historian) #add_subdirectory( delayed_node ) add_subdirectory(js_operation_serializer) add_subdirectory(meter) diff --git a/programs/historian/CMakeLists.txt b/programs/historian/CMakeLists.txt new file mode 100644 index 0000000000..82436e82a8 --- /dev/null +++ b/programs/historian/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(historian main.cpp) +if(UNIX AND NOT APPLE) + set(rt_library rt) +endif() + +target_link_libraries(historian + PRIVATE golos_chain + golos_protocol + fc + indicators + ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS}) + +install(TARGETS + historian + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) diff --git a/programs/historian/main.cpp b/programs/historian/main.cpp new file mode 100644 index 0000000000..1f2b392a97 --- /dev/null +++ b/programs/historian/main.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "./program_options.hpp" +#include "./progress_bar.hpp" +#include "./special_operation_visitor.hpp" + +namespace bfs = boost::filesystem; +namespace bpo = boost::program_options; +using bpo::options_description; +using bpo::variables_map; +using namespace golos::chain; + +int unsafe_main(int argc, char** argv); + +int main(int argc, char** argv) { + try { + return unsafe_main(argc, argv); + } catch (const fc::exception& e) { + std::cout << e.to_detail_string() << "\n"; + return -1; + } +} + +int unsafe_main(int argc, char** argv) { + auto po = get_program_options(argc, argv); + + if (!po.proceed) { + return po.exit_code; + } + + fc::install_stacktrace_crash_handler(); + + std::cout << "Opening block_log: " << po.blocklog_file.string() << "..." << std::endl; + + golos::chain::block_log bg; + bg.open(po.blocklog_file); + + std::cout << "Opened. Processing blocks..." << std::endl; + + auto head_block = bg.head(); + if (!head_block.valid()) { + std::cerr << "Blocklog is empty." << std::endl; + return -3; + } + auto head_block_num = head_block->block_num(); + + auto start_time = fc::time_point::now(); + + uint32_t start_block = std::stol(po.start); + uint32_t end_block = std::stol(po.end); + end_block = std::min(end_block, head_block_num); + uint32_t block_num = start_block; + + uint64_t found = 0; + uint64_t old_found = 0; + + progress_bar p; + + uint32_t old_pct = 0; + + auto save_found = [&](const std::string& str) { + std::ofstream output; + output.open(po.output_file, std::ios_base::app); + output << str << std::endl; + ++found; + }; + + special_operation_visitor sov(po, save_found); + + while (block_num <= end_block) { + auto block = bg.read_block_by_num(block_num); + if (!block) { + break; + } + + std::string op_name; + for (const auto& tx : block->transactions) { + for (const auto& op : tx.operations) { + op_name = ""; + op.visit(fc::get_operation_name(op_name)); + if (po.search_json.size()) { + if (!po.search_op.size() || op_name.find(po.search_op) != std::string::npos) { + auto json_str = fc::json::to_string(op); + if (json_str.find(po.search_json) != std::string::npos) { + save_found(op_name + " " + json_str); + } + } + } else if (po.search_text.size()) { + if (!po.search_op.size() || op_name.find(po.search_op) != std::string::npos) { + op.visit(sov); + } + } else { + if (op_name.find(po.search_op) != std::string::npos) { + save_found(op_name + " " + fc::json::to_string(op)); + } else { + op.visit(sov); + } + } + } + } + ++block_num; + + p.update_progress(found, &old_found, *block, end_block, &old_pct); + } + + p.complete(*head_block); + + std::cout << "Found: " << found << std::endl; + + auto end_time = fc::time_point::now(); + + std::cout << "Elapsed: " << fc::get_approximate_relative_time_string(start_time, end_time, "") << std::endl; + + return 0; +} diff --git a/programs/historian/program_options.hpp b/programs/historian/program_options.hpp new file mode 100644 index 0000000000..94f49be0e8 --- /dev/null +++ b/programs/historian/program_options.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +namespace bfs = boost::filesystem; +namespace bpo = boost::program_options; +using bpo::options_description; +using bpo::variables_map; + +struct program_options { + bfs::path blocklog_file; + std::string search_op; + std::string search_text; + std::string search_json; + std::string output_file; + std::string start; + std::string end; + int exit_code = 0; + bool proceed = false; +}; + +program_options get_program_options(int argc, char** argv) { + program_options po; + + options_description cli("historian allows you to check if some operation present in block_log.\n" + "\n" + "It requires block_log file. block_log.index is optional.\n" + "\n" + "Example of usage:\n" + "historian -b /home/block_log -o private_message\n" + "\n" + "Command line options"); + + cli.add_options() + ("blocklog,b", bpo::value(&po.blocklog_file), "Path to block_log.") + ("operation,o", bpo::value(&po.search_op), "Name of searching operation.") + ("text,t", bpo::value(&po.search_text), "Search compromised text. Can be used with -o or without.") + ("json,j", bpo::value(&po.search_json), "Search raw operation JSON. Examples: '\"author\":\"lex\"' or '\\\"follower\\\":\\\"lex' Can be used with -o or without.") + ("log,l", bpo::value(&po.output_file)->default_value("log.txt"), "Name of output file. Default is log.txt") + ("start,s", bpo::value(&po.start)->default_value("2"), "Start block number.") + ("end,e", bpo::value(&po.end)->default_value("4294967295"), "End block number.") + ("help,h", "Print this help message and exit.") + ; + + variables_map vmap; + bpo::store(bpo::parse_command_line(argc, argv, cli), vmap); + bpo::notify(vmap); + if (vmap.count("help") > 0 || vmap.count("blocklog") == 0 || + (vmap.count("operation") == 0 && vmap.count("text") == 0 && vmap.count("json") == 0)) { + cli.print(std::cerr); + return po; + } + + if (!bfs::exists(po.blocklog_file)) { + std::cerr << po.blocklog_file.string() << " not exists." << std::endl; + po.exit_code = -1; + return po; + } + + if (!bfs::is_regular_file(po.blocklog_file)) { + std::cerr << po.blocklog_file.string() << " is not a file, it is a directory or something another." << std::endl; + po.exit_code = -2; + return po; + } + + if (bfs::exists(po.output_file)) { + std::cerr << "Log file " << po.output_file << " already exists." << std::endl; + po.exit_code = -3; + return po; + } + + po.proceed = true; + return po; +} \ No newline at end of file diff --git a/programs/historian/progress_bar.hpp b/programs/historian/progress_bar.hpp new file mode 100644 index 0000000000..5aa1fcb269 --- /dev/null +++ b/programs/historian/progress_bar.hpp @@ -0,0 +1,56 @@ +#include +#include + +using namespace golos::chain; + +class progress_bar { +private: + indicators::ProgressBar* p = nullptr; + + std::string fmt_found(uint64_t found) { + if (found > 10000) { + return " >10000 found "; + } + return std::string(" ") + std::to_string(found) + " found "; + } +public: + progress_bar() { + p = new indicators::ProgressBar(); + p->set_option(indicators::option::BarWidth{40}); + p->set_option(indicators::option::Fill{"■"}); + p->set_option(indicators::option::Lead{"■"}); + p->set_option(indicators::option::ShowPercentage{true}); + p->set_option(indicators::option::PrefixText{this->fmt_found(0)}); + p->set_option(indicators::option::PostfixText{"2016-01-01T01:01:01"}); + } + + ~progress_bar() { + delete p; + } + + void update_progress(uint64_t found, uint64_t* old_found, + const signed_block& block, uint32_t head_block_num, + uint32_t* old_pct) { + auto block_num = block.block_num(); + + auto pct = 100 / std::max(uint32_t(1), head_block_num / block_num); + pct = std::min(pct, uint32_t(99)); + if (block_num % 50000 == 0) { + p->set_option(indicators::option::PostfixText{block.timestamp.to_iso_string()}); + } + if (found > *old_found) { + p->set_option(indicators::option::PrefixText{this->fmt_found(found)}); + p->set_progress(pct); + *old_found = found; + *old_pct = pct; + } else if (pct - *old_pct >= 2) { + p->set_progress(pct); + *old_pct = pct; + } + } + + void complete(const signed_block& head_block) { + p->set_option(indicators::option::PostfixText{head_block.timestamp.to_iso_string()}); + p->set_progress(100); + } +}; diff --git a/programs/historian/special_operation_visitor.hpp b/programs/historian/special_operation_visitor.hpp new file mode 100644 index 0000000000..385ead5a32 --- /dev/null +++ b/programs/historian/special_operation_visitor.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include + +#include "./program_options.hpp" + +class special_operation_visitor { +private: + const program_options& po; + std::function save_found; +public: + using result_type = void; + special_operation_visitor(const program_options& _po, + std::function _save_found) + : po(_po), save_found(_save_found) { + } + + void operator()(const golos::protocol::custom_json_operation& op) const { + if (po.search_text.size()) { + return; + } + fc::variant v = fc::json::from_string(op.json); + if (!v.is_array() || !v.size() || v.get_array()[0].is_array()) return; + auto op_type = v.get_array()[0].get_string(); + if (op_type.find(po.search_op) != std::string::npos) { + auto& vo = v.get_array()[1].get_object(); + save_found(op_type + " " + fc::json::to_string(vo)); + } + } + + bool check_for_compomise(const golos::protocol::operation& op, const std::string& field) const { + if (!po.search_text.size()) { + return true; + } + if (field.find(po.search_text) != std::string::npos) { + save_found(fc::json::to_string(op)); + return true; + } + return false; + } + + void operator()(const golos::protocol::comment_operation& op) const { + if (check_for_compomise(op, op.title)) return; + if (check_for_compomise(op, op.body)) return; + if (check_for_compomise(op, op.json_metadata)) return; + } + + void operator()(const golos::protocol::transfer_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::transfer_to_savings_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::transfer_from_savings_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::donate_operation& op) const { + if (!op.memo.comment) return; + if (check_for_compomise(op, *op.memo.comment)) return; + } + + void operator()(const golos::protocol::transfer_to_tip_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::transfer_from_tip_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::override_transfer_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::invite_donate_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + void operator()(const golos::protocol::invite_transfer_operation& op) const { + if (check_for_compomise(op, op.memo)) return; + } + + template + void operator()(Op&&) const { + } /// ignore all other ops +}; diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index a5db66ce25..0ad81ee236 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(appbase) add_subdirectory(fc) -add_subdirectory(chainbase) \ No newline at end of file +add_subdirectory(chainbase) +add_subdirectory(indicators) \ No newline at end of file diff --git a/thirdparty/indicators/CMakeLists.txt b/thirdparty/indicators/CMakeLists.txt new file mode 100644 index 0000000000..40499a8ba3 --- /dev/null +++ b/thirdparty/indicators/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(indicators INTERFACE) +target_include_directories(indicators INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/thirdparty/indicators/LICENSE b/thirdparty/indicators/LICENSE new file mode 100644 index 0000000000..bcae1c6a21 --- /dev/null +++ b/thirdparty/indicators/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Pranav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/thirdparty/indicators/LICENSE.termcolor b/thirdparty/indicators/LICENSE.termcolor new file mode 100644 index 0000000000..e2a9f53226 --- /dev/null +++ b/thirdparty/indicators/LICENSE.termcolor @@ -0,0 +1,31 @@ +Copyright (c) 2013, Ihor Kalnytskyi. +All rights reserved. + +Redistribution and use in source and binary forms of the software as well +as documentation, with or without modification, are permitted provided +that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +* The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/thirdparty/indicators/include/indicators/block_progress_bar.hpp b/thirdparty/indicators/include/indicators/block_progress_bar.hpp new file mode 100644 index 0000000000..1c2699296e --- /dev/null +++ b/thirdparty/indicators/include/indicators/block_progress_bar.hpp @@ -0,0 +1,290 @@ + +#ifndef INDICATORS_BLOCK_PROGRESS_BAR +#define INDICATORS_BLOCK_PROGRESS_BAR + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +class BlockProgressBar { + using Settings = std::tuple; + +public: + template ::type...>::value, + void *>::type = nullptr> + explicit BlockProgressBar(Args &&... args) + : settings_(details::get( + option::ForegroundColor{Color::unspecified}, std::forward(args)...), + details::get(option::BarWidth{100}, + std::forward(args)...), + details::get(option::Start{"["}, + std::forward(args)...), + details::get(option::End{"]"}, + std::forward(args)...), + details::get( + option::PrefixText{""}, std::forward(args)...), + details::get( + option::PostfixText{""}, std::forward(args)...), + details::get( + option::ShowPercentage{true}, std::forward(args)...), + details::get( + option::ShowElapsedTime{false}, std::forward(args)...), + details::get( + option::ShowRemainingTime{false}, std::forward(args)...), + details::get(option::Completed{false}, + std::forward(args)...), + details::get( + option::SavedStartTime{false}, std::forward(args)...), + details::get( + option::MaxPostfixTextLen{0}, std::forward(args)...), + details::get( + option::FontStyles{std::vector{}}, std::forward(args)...), + details::get( + option::MaxProgress{100}, std::forward(args)...), + details::get(option::Stream{std::cout}, + std::forward(args)...)) {} + + template + void set_option(details::Setting &&setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + } + + template + void set_option(const details::Setting &setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = setting.value; + } + + void set_option( + const details::Setting &setting) { + std::lock_guard lock(mutex_); + get_value() = setting.value; + if (setting.value.length() > get_value()) { + get_value() = setting.value.length(); + } + } + + void + set_option(details::Setting &&setting) { + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + auto &new_value = get_value(); + if (new_value.length() > get_value()) { + get_value() = new_value.length(); + } + } + + void set_progress(float value) { + { + std::lock_guard lock{mutex_}; + progress_ = value; + } + save_start_time(); + print_progress(); + } + + void tick() { + { + std::lock_guard lock{mutex_}; + progress_ += 1; + } + save_start_time(); + print_progress(); + } + + size_t current() { + std::lock_guard lock{mutex_}; + return (std::min)(static_cast(progress_), + size_t(get_value())); + } + + bool is_completed() const { return get_value(); } + + void mark_as_completed() { + get_value() = true; + print_progress(); + } + +private: + template + auto get_value() -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + template + auto get_value() const + -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + Settings settings_; + float progress_{0.0}; + std::chrono::time_point start_time_point_; + std::mutex mutex_; + + template friend class MultiProgress; + template friend class DynamicProgress; + std::atomic multi_progress_mode_{false}; + + void save_start_time() { + auto &show_elapsed_time = get_value(); + auto &saved_start_time = get_value(); + auto &show_remaining_time = get_value(); + if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { + start_time_point_ = std::chrono::high_resolution_clock::now(); + saved_start_time = true; + } + } + + std::pair get_prefix_text() { + std::stringstream os; + os << get_value(); + const auto result = os.str(); + const auto result_size = unicode::display_width(result); + return {result, result_size}; + } + + std::pair get_postfix_text() { + std::stringstream os; + const auto max_progress = get_value(); + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start_time_point_); + + if (get_value()) { + os << " " << (std::min)(static_cast(progress_ / max_progress * 100.0), size_t(100)) + << "%"; + } + + auto &saved_start_time = get_value(); + + if (get_value()) { + os << " ["; + if (saved_start_time) + details::write_duration(os, elapsed); + else + os << "00:00s"; + } + + if (get_value()) { + if (get_value()) + os << "<"; + else + os << " ["; + + if (saved_start_time) { + auto eta = std::chrono::nanoseconds( + progress_ > 0 + ? static_cast(std::ceil(float(elapsed.count()) * + max_progress / progress_)) + : 0); + auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); + details::write_duration(os, remaining); + } else { + os << "00:00s"; + } + + os << "]"; + } else { + if (get_value()) + os << "]"; + } + + os << " " << get_value(); + + const auto result = os.str(); + const auto result_size = unicode::display_width(result); + return {result, result_size}; + } + +public: + void print_progress(bool from_multi_progress = false) { + std::lock_guard lock{mutex_}; + + auto &os = get_value(); + + const auto max_progress = get_value(); + if (multi_progress_mode_ && !from_multi_progress) { + if (progress_ > max_progress) { + get_value() = true; + } + return; + } + + if (get_value() != Color::unspecified) + details::set_stream_color(os, get_value()); + + for (auto &style : get_value()) + details::set_font_style(os, style); + + const auto prefix_pair = get_prefix_text(); + const auto prefix_text = prefix_pair.first; + const auto prefix_length = prefix_pair.second; + os << "\r" << prefix_text; + + os << get_value(); + + details::BlockProgressScaleWriter writer{os, + get_value()}; + writer.write(progress_ / max_progress * 100); + + os << get_value(); + + const auto postfix_pair = get_postfix_text(); + const auto postfix_text = postfix_pair.first; + const auto postfix_length = postfix_pair.second; + os << postfix_text; + + // Get length of prefix text and postfix text + const auto start_length = get_value().size(); + const auto bar_width = get_value(); + const auto end_length = get_value().size(); + const auto terminal_width = terminal_size().second; + // prefix + bar_width + postfix should be <= terminal_width + const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); + if (remaining > 0) { + os << std::string(remaining, ' ') << "\r"; + } else if (remaining < 0) { + // Do nothing. Maybe in the future truncate postfix with ... + } + os.flush(); + + if (progress_ > max_progress) { + get_value() = true; + } + if (get_value() && + !from_multi_progress) // Don't std::endl if calling from MultiProgress + os << termcolor::reset << std::endl; + } +}; + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/color.hpp b/thirdparty/indicators/include/indicators/color.hpp new file mode 100644 index 0000000000..19dc0e59f2 --- /dev/null +++ b/thirdparty/indicators/include/indicators/color.hpp @@ -0,0 +1,9 @@ + +#ifndef INDICATORS_COLOR +#define INDICATORS_COLOR + +namespace indicators { +enum class Color { grey, red, green, yellow, blue, magenta, cyan, white, unspecified }; +} + +#endif diff --git a/thirdparty/indicators/include/indicators/cursor_control.hpp b/thirdparty/indicators/include/indicators/cursor_control.hpp new file mode 100644 index 0000000000..641156fa88 --- /dev/null +++ b/thirdparty/indicators/include/indicators/cursor_control.hpp @@ -0,0 +1,66 @@ + +#ifndef INDICATORS_CURSOR_CONTROL +#define INDICATORS_CURSOR_CONTROL + +#if defined(_MSC_VER) +#if !defined(NOMINMAX) +#define NOMINMAX +#endif +#include +#include +#else +#include +#endif + +namespace indicators { + +#if defined(_MSC_VER) + +static inline void show_console_cursor(bool const show) { + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + CONSOLE_CURSOR_INFO cursorInfo; + + GetConsoleCursorInfo(out, &cursorInfo); + cursorInfo.bVisible = show; // set the cursor visibility + SetConsoleCursorInfo(out, &cursorInfo); +} + +static inline void erase_line() { + auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + if (!hStdout) + return; + + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(hStdout, &csbiInfo); + + COORD cursor; + + cursor.X = 0; + cursor.Y = csbiInfo.dwCursorPosition.Y; + + DWORD count = 0; + + FillConsoleOutputCharacterA(hStdout, ' ', csbiInfo.dwSize.X, cursor, &count); + + FillConsoleOutputAttribute(hStdout, csbiInfo.wAttributes, csbiInfo.dwSize.X, + cursor, &count); + + SetConsoleCursorPosition(hStdout, cursor); +} + +#else + +static inline void show_console_cursor(bool const show) { + std::fputs(show ? "\033[?25h" : "\033[?25l", stdout); +} + +static inline void erase_line() { + std::fputs("\r\033[K", stdout); +} + +#endif + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/cursor_movement.hpp b/thirdparty/indicators/include/indicators/cursor_movement.hpp new file mode 100644 index 0000000000..1081b7f206 --- /dev/null +++ b/thirdparty/indicators/include/indicators/cursor_movement.hpp @@ -0,0 +1,50 @@ + +#ifndef INDICATORS_CURSOR_MOVEMENT +#define INDICATORS_CURSOR_MOVEMENT + +#if defined(_MSC_VER) +#if !defined(NOMINMAX) +#define NOMINMAX +#endif +#include +#include +#else +#include +#endif + +namespace indicators { + +#ifdef _MSC_VER + +static inline void move(int x, int y) { + auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + if (!hStdout) + return; + + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(hStdout, &csbiInfo); + + COORD cursor; + + cursor.X = csbiInfo.dwCursorPosition.X + x; + cursor.Y = csbiInfo.dwCursorPosition.Y + y; + SetConsoleCursorPosition(hStdout, cursor); +} + +static inline void move_up(int lines) { move(0, -lines); } +static inline void move_down(int lines) { move(0, -lines); } +static inline void move_right(int cols) { move(cols, 0); } +static inline void move_left(int cols) { move(-cols, 0); } + +#else + +static inline void move_up(int lines) { std::cout << "\033[" << lines << "A"; } +static inline void move_down(int lines) { std::cout << "\033[" << lines << "B"; } +static inline void move_right(int cols) { std::cout << "\033[" << cols << "C"; } +static inline void move_left(int cols) { std::cout << "\033[" << cols << "D"; } + +#endif + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/details/stream_helper.hpp b/thirdparty/indicators/include/indicators/details/stream_helper.hpp new file mode 100644 index 0000000000..48d06b88f3 --- /dev/null +++ b/thirdparty/indicators/include/indicators/details/stream_helper.hpp @@ -0,0 +1,223 @@ + +#ifndef INDICATORS_STREAM_HELPER +#define INDICATORS_STREAM_HELPER + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace indicators { +namespace details { + +inline void set_stream_color(std::ostream &os, Color color) { + switch (color) { + case Color::grey: + os << termcolor::grey; + break; + case Color::red: + os << termcolor::red; + break; + case Color::green: + os << termcolor::green; + break; + case Color::yellow: + os << termcolor::yellow; + break; + case Color::blue: + os << termcolor::blue; + break; + case Color::magenta: + os << termcolor::magenta; + break; + case Color::cyan: + os << termcolor::cyan; + break; + case Color::white: + os << termcolor::white; + break; + default: + assert(false); + } +} + +inline void set_font_style(std::ostream &os, FontStyle style) { + switch (style) { + case FontStyle::bold: + os << termcolor::bold; + break; + case FontStyle::dark: + os << termcolor::dark; + break; + case FontStyle::italic: + os << termcolor::italic; + break; + case FontStyle::underline: + os << termcolor::underline; + break; + case FontStyle::blink: + os << termcolor::blink; + break; + case FontStyle::reverse: + os << termcolor::reverse; + break; + case FontStyle::concealed: + os << termcolor::concealed; + break; + case FontStyle::crossed: + os << termcolor::crossed; + break; + default: + break; + } +} + +inline std::ostream &write_duration(std::ostream &os, std::chrono::nanoseconds ns) { + using namespace std; + using namespace std::chrono; + using days = duration>; + char fill = os.fill(); + os.fill('0'); + auto d = duration_cast(ns); + ns -= d; + auto h = duration_cast(ns); + ns -= h; + auto m = duration_cast(ns); + ns -= m; + auto s = duration_cast(ns); + if (d.count() > 0) + os << setw(2) << d.count() << "d:"; + if (h.count() > 0) + os << setw(2) << h.count() << "h:"; + os << setw(2) << m.count() << "m:" << setw(2) << s.count() << 's'; + os.fill(fill); + return os; +} + +class BlockProgressScaleWriter { +public: + BlockProgressScaleWriter(std::ostream &os, size_t bar_width) : os(os), bar_width(bar_width) {} + + std::ostream &write(float progress) { + std::string fill_text{"█"}; + std::vector lead_characters{" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"}; + auto value = (std::min)(1.0f, (std::max)(0.0f, progress / 100.0f)); + auto whole_width = std::floor(value * bar_width); + auto remainder_width = fmod((value * bar_width), 1.0f); + auto part_width = std::floor(remainder_width * lead_characters.size()); + std::string lead_text = lead_characters[size_t(part_width)]; + if ((bar_width - whole_width - 1) < 0) + lead_text = ""; + for (size_t i = 0; i < whole_width; ++i) + os << fill_text; + os << lead_text; + for (size_t i = 0; i < (bar_width - whole_width - 1); ++i) + os << " "; + return os; + } + +private: + std::ostream &os; + size_t bar_width = 0; +}; + +class ProgressScaleWriter { +public: + ProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill, + const std::string &lead, const std::string &remainder) + : os(os), bar_width(bar_width), fill(fill), lead(lead), remainder(remainder) {} + + std::ostream &write(float progress) { + auto pos = static_cast(progress * bar_width / 100.0); + for (size_t i = 0, current_display_width = 0; i < bar_width;) { + std::string next; + + if (i < pos) { + next = fill; + current_display_width = unicode::display_width(fill); + } else if (i == pos) { + next = lead; + current_display_width = unicode::display_width(lead); + } else { + next = remainder; + current_display_width = unicode::display_width(remainder); + } + + i += current_display_width; + + if (i > bar_width) { + // `next` is larger than the allowed bar width + // fill with empty space instead + os << std::string((bar_width - (i - current_display_width)), ' '); + break; + } + + os << next; + } + return os; + } + +private: + std::ostream &os; + size_t bar_width = 0; + std::string fill; + std::string lead; + std::string remainder; +}; + +class IndeterminateProgressScaleWriter { +public: + IndeterminateProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill, + const std::string &lead) + : os(os), bar_width(bar_width), fill(fill), lead(lead) {} + + std::ostream &write(size_t progress) { + for (size_t i = 0; i < bar_width;) { + std::string next; + size_t current_display_width = 0; + + if (i < progress) { + next = fill; + current_display_width = unicode::display_width(fill); + } else if (i == progress) { + next = lead; + current_display_width = unicode::display_width(lead); + } else { + next = fill; + current_display_width = unicode::display_width(fill); + } + + i += current_display_width; + + if (i > bar_width) { + // `next` is larger than the allowed bar width + // fill with empty space instead + os << std::string((bar_width - (i - current_display_width)), ' '); + break; + } + + os << next; + } + return os; + } + +private: + std::ostream &os; + size_t bar_width = 0; + std::string fill; + std::string lead; +}; + +} // namespace details +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/display_width.hpp b/thirdparty/indicators/include/indicators/display_width.hpp new file mode 100644 index 0000000000..3bef06bf39 --- /dev/null +++ b/thirdparty/indicators/include/indicators/display_width.hpp @@ -0,0 +1,319 @@ + +#ifndef INDICATORS_DISPLAY_WIDTH +#define INDICATORS_DISPLAY_WIDTH + +#include +#if __has_include() +#include +#define INDICATORS_HAVE_CODECVT 1 +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING +#endif +#include +#include +#include +#include + +namespace unicode { + +#if INDICATORS_HAVE_CODECVT +namespace details { + +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +struct interval { + int first; + int last; +}; + +/* auxiliary function for binary search in interval table */ +static inline int bisearch(wchar_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +static inline int mk_wcwidth(wchar_t ucs) { + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, + {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, + {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, + {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, + {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, + {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, + {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, + {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, + {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, + {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, + {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, + {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, + {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, + {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, + {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, + {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, + {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, + {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, + {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, + {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, + {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, + {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, + {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, + {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, + {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, + {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, + {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, + {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, + {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, + {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, + {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, + {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, + {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, + {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, + {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, + {0xE0020, 0xE007F}, {0xE0100, 0xE01EF}}; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + +static inline int mk_wcswidth(const wchar_t *pwcs, size_t n) { + int w, width = 0; + + for (; *pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +static inline int mk_wcwidth_cjk(wchar_t ucs) { + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8}, {0x00AA, 0x00AA}, {0x00AE, 0x00AE}, + {0x00B0, 0x00B4}, {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6}, {0x00D0, 0x00D0}, + {0x00D7, 0x00D8}, {0x00DE, 0x00E1}, {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED}, + {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA}, {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, + {0x0101, 0x0101}, {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B}, {0x0126, 0x0127}, + {0x012B, 0x012B}, {0x0131, 0x0133}, {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144}, + {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153}, {0x0166, 0x0167}, {0x016B, 0x016B}, + {0x01CE, 0x01CE}, {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4}, {0x01D6, 0x01D6}, + {0x01D8, 0x01D8}, {0x01DA, 0x01DA}, {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261}, + {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB}, {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, + {0x02D8, 0x02DB}, {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0391, 0x03A1}, {0x03A3, 0x03A9}, + {0x03B1, 0x03C1}, {0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F}, {0x0451, 0x0451}, + {0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022}, + {0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033}, {0x2035, 0x2035}, {0x203B, 0x203B}, + {0x203E, 0x203E}, {0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084}, {0x20AC, 0x20AC}, + {0x2103, 0x2103}, {0x2105, 0x2105}, {0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116}, + {0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B}, {0x2153, 0x2154}, {0x215B, 0x215E}, + {0x2160, 0x216B}, {0x2170, 0x2179}, {0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, + {0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203}, {0x2207, 0x2208}, + {0x220B, 0x220B}, {0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A}, + {0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E}, + {0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C}, {0x2252, 0x2252}, + {0x2260, 0x2261}, {0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283}, + {0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, + {0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573}, {0x2580, 0x258F}, + {0x2592, 0x2595}, {0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7}, + {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, + {0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609}, {0x260E, 0x260F}, + {0x2614, 0x2615}, {0x261C, 0x261C}, {0x261E, 0x261E}, {0x2640, 0x2640}, {0x2642, 0x2642}, + {0x2660, 0x2661}, {0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D}, {0x266F, 0x266F}, + {0x273D, 0x273D}, {0x2776, 0x277F}, {0xE000, 0xF8FF}, {0xFFFD, 0xFFFD}, {0xF0000, 0xFFFFD}, + {0x100000, 0x10FFFD}}; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + +static inline int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) { + int w, width = 0; + + for (; *pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + +// convert UTF-8 string to wstring +static inline std::wstring utf8_decode(const std::string &str) { + std::wstring_convert> myconv; + return myconv.from_bytes(str); +} + +// convert wstring to UTF-8 string +static inline std::string utf8_encode(const std::wstring &str) { + std::wstring_convert> myconv; + return myconv.to_bytes(str); +} + +} // namespace details + +static inline int display_width(const std::string &input) { + using namespace unicode::details; + return mk_wcswidth(utf8_decode(input).c_str(), input.size()); +} + +static inline int display_width(const std::wstring &input) { + return details::mk_wcswidth(input.c_str(), input.size()); +} + +#else + +static inline int display_width(const std::string &input) { + return input.length(); +} + +static inline int display_width(const std::wstring &input) { + return input.length(); +} + +#endif + +} // namespace unicode + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/dynamic_progress.hpp b/thirdparty/indicators/include/indicators/dynamic_progress.hpp new file mode 100644 index 0000000000..49d7d8947e --- /dev/null +++ b/thirdparty/indicators/include/indicators/dynamic_progress.hpp @@ -0,0 +1,122 @@ + +#ifndef INDICATORS_DYNAMIC_PROGRESS +#define INDICATORS_DYNAMIC_PROGRESS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +template class DynamicProgress { + using Settings = std::tuple; + +public: + template explicit DynamicProgress(Indicators &... bars) { + bars_ = {bars...}; + for (auto &bar : bars_) { + bar.get().multi_progress_mode_ = true; + ++total_count_; + ++incomplete_count_; + } + } + + Indicator &operator[](size_t index) { + print_progress(); + std::lock_guard lock{mutex_}; + return bars_[index].get(); + } + + size_t push_back(Indicator &bar) { + std::lock_guard lock{mutex_}; + bar.multi_progress_mode_ = true; + bars_.push_back(bar); + return bars_.size() - 1; + } + + template + void set_option(details::Setting &&setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + } + + template + void set_option(const details::Setting &setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = setting.value; + } + +private: + Settings settings_; + std::atomic started_{false}; + std::mutex mutex_; + std::vector> bars_; + std::atomic total_count_{0}; + std::atomic incomplete_count_{0}; + + template + auto get_value() -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + template + auto get_value() const + -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + +public: + void print_progress() { + std::lock_guard lock{mutex_}; + auto &hide_bar_when_complete = get_value(); + if (hide_bar_when_complete) { + // Hide completed bars + if (started_) { + for (size_t i = 0; i < incomplete_count_; ++i) { + move_up(1); + erase_line(); + std::cout << std::flush; + } + } + incomplete_count_ = 0; + for (auto &bar : bars_) { + if (!bar.get().is_completed()) { + bar.get().print_progress(true); + std::cout << "\n"; + ++incomplete_count_; + } + } + if (!started_) + started_ = true; + } else { + // Don't hide any bars + if (started_) + move_up(static_cast(total_count_)); + for (auto &bar : bars_) { + bar.get().print_progress(true); + std::cout << "\n"; + } + if (!started_) + started_ = true; + } + total_count_ = bars_.size(); + std::cout << termcolor::reset; + } +}; + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/font_style.hpp b/thirdparty/indicators/include/indicators/font_style.hpp new file mode 100644 index 0000000000..cb0b48030e --- /dev/null +++ b/thirdparty/indicators/include/indicators/font_style.hpp @@ -0,0 +1,9 @@ + +#ifndef INDICATORS_FONT_STYLE +#define INDICATORS_FONT_STYLE + +namespace indicators { +enum class FontStyle { bold, dark, italic, underline, blink, reverse, concealed, crossed }; +} + +#endif diff --git a/thirdparty/indicators/include/indicators/indeterminate_progress_bar.hpp b/thirdparty/indicators/include/indicators/indeterminate_progress_bar.hpp new file mode 100644 index 0000000000..08d6ae4cc3 --- /dev/null +++ b/thirdparty/indicators/include/indicators/indeterminate_progress_bar.hpp @@ -0,0 +1,237 @@ + +#ifndef INDICATORS_INDETERMINATE_PROGRESS_BAR +#define INDICATORS_INDETERMINATE_PROGRESS_BAR + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +class IndeterminateProgressBar { + using Settings = + std::tuple; + + enum class Direction { forward, backward }; + + Direction direction_{Direction::forward}; + +public: + template ::type...>::value, + void *>::type = nullptr> + explicit IndeterminateProgressBar(Args &&... args) + : settings_(details::get(option::BarWidth{100}, + std::forward(args)...), + details::get( + option::PrefixText{}, std::forward(args)...), + details::get( + option::PostfixText{}, std::forward(args)...), + details::get(option::Start{"["}, + std::forward(args)...), + details::get(option::End{"]"}, + std::forward(args)...), + details::get(option::Fill{"."}, + std::forward(args)...), + details::get(option::Lead{"<==>"}, + std::forward(args)...), + details::get( + option::MaxPostfixTextLen{0}, std::forward(args)...), + details::get(option::Completed{false}, + std::forward(args)...), + details::get( + option::ForegroundColor{Color::unspecified}, std::forward(args)...), + details::get( + option::FontStyles{std::vector{}}, std::forward(args)...), + details::get(option::Stream{std::cout}, + std::forward(args)...)) { + // starts with [<==>...........] + // progress_ = 0 + + // ends with [...........<==>] + // ^^^^^^^^^^^^^^^^^ bar_width + // ^^^^^^^^^^^^ (bar_width - len(lead)) + // progress_ = bar_width - len(lead) + progress_ = 0; + max_progress_ = get_value() - + get_value().size() + + get_value().size() + + get_value().size(); + } + + template + void set_option(details::Setting &&setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + } + + template + void set_option(const details::Setting &setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = setting.value; + } + + void set_option( + const details::Setting &setting) { + std::lock_guard lock(mutex_); + get_value() = setting.value; + if (setting.value.length() > get_value()) { + get_value() = setting.value.length(); + } + } + + void + set_option(details::Setting &&setting) { + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + auto &new_value = get_value(); + if (new_value.length() > get_value()) { + get_value() = new_value.length(); + } + } + + void tick() { + { + std::lock_guard lock{mutex_}; + if (get_value()) + return; + + progress_ += (direction_ == Direction::forward) ? 1 : -1; + if (direction_ == Direction::forward && progress_ == max_progress_) { + // time to go back + direction_ = Direction::backward; + } else if (direction_ == Direction::backward && progress_ == 0) { + direction_ = Direction::forward; + } + } + print_progress(); + } + + bool is_completed() { return get_value(); } + + void mark_as_completed() { + get_value() = true; + print_progress(); + } + +private: + template + auto get_value() -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + template + auto get_value() const + -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + size_t progress_{0}; + size_t max_progress_; + Settings settings_; + std::chrono::nanoseconds elapsed_; + std::mutex mutex_; + + template friend class MultiProgress; + template friend class DynamicProgress; + std::atomic multi_progress_mode_{false}; + + std::pair get_prefix_text() { + std::stringstream os; + os << get_value(); + const auto result = os.str(); + const auto result_size = unicode::display_width(result); + return {result, result_size}; + } + + std::pair get_postfix_text() { + std::stringstream os; + os << " " << get_value(); + + const auto result = os.str(); + const auto result_size = unicode::display_width(result); + return {result, result_size}; + } + +public: + void print_progress(bool from_multi_progress = false) { + std::lock_guard lock{mutex_}; + + auto &os = get_value(); + + if (multi_progress_mode_ && !from_multi_progress) { + return; + } + if (get_value() != Color::unspecified) + details::set_stream_color(os, get_value()); + + for (auto &style : get_value()) + details::set_font_style(os, style); + + const auto prefix_pair = get_prefix_text(); + const auto prefix_text = prefix_pair.first; + const auto prefix_length = prefix_pair.second; + os << "\r" << prefix_text; + + os << get_value(); + + details::IndeterminateProgressScaleWriter writer{ + os, get_value(), + get_value(), + get_value()}; + writer.write(progress_); + + os << get_value(); + + const auto postfix_pair = get_postfix_text(); + const auto postfix_text = postfix_pair.first; + const auto postfix_length = postfix_pair.second; + os << postfix_text; + + // Get length of prefix text and postfix text + const auto start_length = get_value().size(); + const auto bar_width = get_value(); + const auto end_length = get_value().size(); + const auto terminal_width = terminal_size().second; + // prefix + bar_width + postfix should be <= terminal_width + const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); + if (remaining > 0) { + os << std::string(remaining, ' ') << "\r"; + } else if (remaining < 0) { + // Do nothing. Maybe in the future truncate postfix with ... + } + os.flush(); + + if (get_value() && + !from_multi_progress) // Don't std::endl if calling from MultiProgress + os << termcolor::reset << std::endl; + } +}; + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/multi_progress.hpp b/thirdparty/indicators/include/indicators/multi_progress.hpp new file mode 100644 index 0000000000..16a9dc478d --- /dev/null +++ b/thirdparty/indicators/include/indicators/multi_progress.hpp @@ -0,0 +1,82 @@ + +#ifndef INDICATORS_MULTI_PROGRESS +#define INDICATORS_MULTI_PROGRESS +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace indicators { + +template class MultiProgress { +public: + template ::type> + explicit MultiProgress(Indicators &... bars) { + bars_ = {bars...}; + for (auto &bar : bars_) { + bar.get().multi_progress_mode_ = true; + } + } + + template + typename std::enable_if<(index >= 0 && index < count), void>::type set_progress(size_t value) { + if (!bars_[index].get().is_completed()) + bars_[index].get().set_progress(value); + print_progress(); + } + + template + typename std::enable_if<(index >= 0 && index < count), void>::type set_progress(float value) { + if (!bars_[index].get().is_completed()) + bars_[index].get().set_progress(value); + print_progress(); + } + + template + typename std::enable_if<(index >= 0 && index < count), void>::type tick() { + if (!bars_[index].get().is_completed()) + bars_[index].get().tick(); + print_progress(); + } + + template + typename std::enable_if<(index >= 0 && index < count), bool>::type is_completed() const { + return bars_[index].get().is_completed(); + } + +private: + std::atomic started_{false}; + std::mutex mutex_; + std::vector> bars_; + + bool _all_completed() { + bool result{true}; + for (size_t i = 0; i < count; ++i) + result &= bars_[i].get().is_completed(); + return result; + } + +public: + void print_progress() { + std::lock_guard lock{mutex_}; + if (started_) + move_up(count); + for (auto &bar : bars_) { + bar.get().print_progress(true); + std::cout << "\n"; + } + std::cout << termcolor::reset; + if (!started_) + started_ = true; + } +}; + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/progress_bar.hpp b/thirdparty/indicators/include/indicators/progress_bar.hpp new file mode 100644 index 0000000000..2d547da20f --- /dev/null +++ b/thirdparty/indicators/include/indicators/progress_bar.hpp @@ -0,0 +1,360 @@ + +#ifndef INDICATORS_PROGRESS_BAR +#define INDICATORS_PROGRESS_BAR + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +class ProgressBar { + using Settings = + std::tuple; + +public: + template ::type...>::value, + void *>::type = nullptr> + explicit ProgressBar(Args &&... args) + : settings_( + details::get( + option::BarWidth{100}, std::forward(args)...), + details::get( + option::PrefixText{}, std::forward(args)...), + details::get( + option::PostfixText{}, std::forward(args)...), + details::get( + option::Start{"["}, std::forward(args)...), + details::get( + option::End{"]"}, std::forward(args)...), + details::get( + option::Fill{"="}, std::forward(args)...), + details::get( + option::Lead{">"}, std::forward(args)...), + details::get( + option::Remainder{" "}, std::forward(args)...), + details::get( + option::MaxPostfixTextLen{0}, std::forward(args)...), + details::get( + option::Completed{false}, std::forward(args)...), + details::get( + option::ShowPercentage{false}, std::forward(args)...), + details::get( + option::ShowElapsedTime{false}, std::forward(args)...), + details::get( + option::ShowRemainingTime{false}, std::forward(args)...), + details::get( + option::SavedStartTime{false}, std::forward(args)...), + details::get( + option::ForegroundColor{Color::unspecified}, + std::forward(args)...), + details::get( + option::FontStyles{std::vector{}}, + std::forward(args)...), + details::get( + option::MinProgress{0}, std::forward(args)...), + details::get( + option::MaxProgress{100}, std::forward(args)...), + details::get( + option::ProgressType{ProgressType::incremental}, + std::forward(args)...), + details::get( + option::Stream{std::cout}, std::forward(args)...)) { + + // if progress is incremental, start from min_progress + // else start from max_progress + const auto type = get_value(); + if (type == ProgressType::incremental) + progress_ = get_value(); + else + progress_ = get_value(); + } + + template + void set_option(details::Setting &&setting) { + static_assert( + !std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + } + + template + void set_option(const details::Setting &setting) { + static_assert( + !std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = setting.value; + } + + void + set_option(const details::Setting< + std::string, details::ProgressBarOption::postfix_text> &setting) { + std::lock_guard lock(mutex_); + get_value() = setting.value; + if (setting.value.length() > + get_value()) { + get_value() = + setting.value.length(); + } + } + + void set_option( + details::Setting + &&setting) { + std::lock_guard lock(mutex_); + get_value() = + std::move(setting).value; + auto &new_value = get_value(); + if (new_value.length() > + get_value()) { + get_value() = + new_value.length(); + } + } + + void set_progress(size_t new_progress) { + { + std::lock_guard lock(mutex_); + progress_ = new_progress; + } + + save_start_time(); + print_progress(); + } + + void tick() { + { + std::lock_guard lock{mutex_}; + const auto type = get_value(); + if (type == ProgressType::incremental) + progress_ += 1; + else + progress_ -= 1; + } + save_start_time(); + print_progress(); + } + + size_t current() { + std::lock_guard lock{mutex_}; + return (std::min)( + progress_, + size_t(get_value())); + } + + bool is_completed() const { + return get_value(); + } + + void mark_as_completed() { + get_value() = true; + print_progress(); + } + +private: + template + auto get_value() + -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + template + auto get_value() const -> decltype( + (details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + size_t progress_{0}; + Settings settings_; + std::chrono::nanoseconds elapsed_; + std::chrono::time_point start_time_point_; + std::mutex mutex_; + + template friend class MultiProgress; + template friend class DynamicProgress; + std::atomic multi_progress_mode_{false}; + + void save_start_time() { + auto &show_elapsed_time = + get_value(); + auto &saved_start_time = + get_value(); + auto &show_remaining_time = + get_value(); + if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { + start_time_point_ = std::chrono::high_resolution_clock::now(); + saved_start_time = true; + } + } + + std::pair get_prefix_text() { + std::stringstream os; + os << get_value(); + const auto result = os.str(); + const auto result_size = unicode::display_width(result); + return {result, result_size}; + } + + std::pair get_postfix_text() { + std::stringstream os; + const auto max_progress = + get_value(); + + if (get_value()) { + os << " " + << (std::min)(static_cast(static_cast(progress_) / + max_progress * 100), + size_t(100)) + << "%"; + } + + auto &saved_start_time = + get_value(); + + if (get_value()) { + os << " ["; + if (saved_start_time) + details::write_duration(os, elapsed_); + else + os << "00:00s"; + } + + if (get_value()) { + if (get_value()) + os << "<"; + else + os << " ["; + + if (saved_start_time) { + auto eta = std::chrono::nanoseconds( + progress_ > 0 + ? static_cast(std::ceil(float(elapsed_.count()) * + max_progress / progress_)) + : 0); + auto remaining = eta > elapsed_ ? (eta - elapsed_) : (elapsed_ - eta); + details::write_duration(os, remaining); + } else { + os << "00:00s"; + } + + os << "]"; + } else { + if (get_value()) + os << "]"; + } + + os << " " << get_value(); + + const auto result = os.str(); + const auto result_size = unicode::display_width(result); + return {result, result_size}; + } + +public: + void print_progress(bool from_multi_progress = false) { + std::lock_guard lock{mutex_}; + + auto &os = get_value(); + + const auto type = get_value(); + const auto min_progress = + get_value(); + const auto max_progress = + get_value(); + if (multi_progress_mode_ && !from_multi_progress) { + if ((type == ProgressType::incremental && progress_ >= max_progress) || + (type == ProgressType::decremental && progress_ <= min_progress)) { + get_value() = true; + } + return; + } + auto now = std::chrono::high_resolution_clock::now(); + if (!get_value()) + elapsed_ = std::chrono::duration_cast( + now - start_time_point_); + + if (get_value() != + Color::unspecified) + details::set_stream_color( + os, get_value()); + + for (auto &style : get_value()) + details::set_font_style(os, style); + + const auto prefix_pair = get_prefix_text(); + const auto prefix_text = prefix_pair.first; + const auto prefix_length = prefix_pair.second; + os << "\r" << prefix_text; + + os << get_value(); + + details::ProgressScaleWriter writer{ + os, get_value(), + get_value(), + get_value(), + get_value()}; + writer.write(double(progress_) / double(max_progress) * 100.0f); + + os << get_value(); + + const auto postfix_pair = get_postfix_text(); + const auto postfix_text = postfix_pair.first; + const auto postfix_length = postfix_pair.second; + os << postfix_text; + + // Get length of prefix text and postfix text + const auto start_length = get_value().size(); + const auto bar_width = get_value(); + const auto end_length = get_value().size(); + const auto terminal_width = terminal_size().second; + // prefix + bar_width + postfix should be <= terminal_width + const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); + if (remaining > 0) { + os << std::string(remaining, ' ') << "\r"; + } else if (remaining < 0) { + // Do nothing. Maybe in the future truncate postfix with ... + } + os.flush(); + + if ((type == ProgressType::incremental && progress_ >= max_progress) || + (type == ProgressType::decremental && progress_ <= min_progress)) { + get_value() = true; + } + if (get_value() && + !from_multi_progress) // Don't std::endl if calling from MultiProgress + os << termcolor::reset << std::endl; + } +}; + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/progress_spinner.hpp b/thirdparty/indicators/include/indicators/progress_spinner.hpp new file mode 100644 index 0000000000..62d5b14ede --- /dev/null +++ b/thirdparty/indicators/include/indicators/progress_spinner.hpp @@ -0,0 +1,227 @@ + +#ifndef INDICATORS_PROGRESS_SPINNER +#define INDICATORS_PROGRESS_SPINNER + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +class ProgressSpinner { + using Settings = + std::tuple; + +public: + template ::type...>::value, + void *>::type = nullptr> + explicit ProgressSpinner(Args &&... args) + : settings_( + details::get( + option::ForegroundColor{Color::unspecified}, std::forward(args)...), + details::get(option::PrefixText{}, + std::forward(args)...), + details::get(option::PostfixText{}, + std::forward(args)...), + details::get(option::ShowPercentage{true}, + std::forward(args)...), + details::get( + option::ShowElapsedTime{false}, std::forward(args)...), + details::get( + option::ShowRemainingTime{false}, std::forward(args)...), + details::get(option::ShowSpinner{true}, + std::forward(args)...), + details::get( + option::SavedStartTime{false}, std::forward(args)...), + details::get(option::Completed{false}, + std::forward(args)...), + details::get( + option::MaxPostfixTextLen{0}, std::forward(args)...), + details::get( + option::SpinnerStates{ + std::vector{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}}, + std::forward(args)...), + details::get( + option::FontStyles{std::vector{}}, std::forward(args)...), + details::get(option::MaxProgress{100}, + std::forward(args)...), + details::get(option::Stream{std::cout}, + std::forward(args)...)) {} + + template + void set_option(details::Setting &&setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + } + + template + void set_option(const details::Setting &setting) { + static_assert(!std::is_same( + std::declval()))>::type>::value, + "Setting has wrong type!"); + std::lock_guard lock(mutex_); + get_value() = setting.value; + } + + void set_option( + const details::Setting &setting) { + std::lock_guard lock(mutex_); + get_value() = setting.value; + if (setting.value.length() > get_value()) { + get_value() = setting.value.length(); + } + } + + void + set_option(details::Setting &&setting) { + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + auto &new_value = get_value(); + if (new_value.length() > get_value()) { + get_value() = new_value.length(); + } + } + + void set_progress(size_t value) { + { + std::lock_guard lock{mutex_}; + progress_ = value; + } + save_start_time(); + print_progress(); + } + + void tick() { + { + std::lock_guard lock{mutex_}; + progress_ += 1; + } + save_start_time(); + print_progress(); + } + + size_t current() { + std::lock_guard lock{mutex_}; + return (std::min)(progress_, size_t(get_value())); + } + + bool is_completed() const { return get_value(); } + + void mark_as_completed() { + get_value() = true; + print_progress(); + } + +private: + Settings settings_; + size_t progress_{0}; + size_t index_{0}; + std::chrono::time_point start_time_point_; + std::mutex mutex_; + + template + auto get_value() -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + template + auto get_value() const + -> decltype((details::get_value(std::declval()).value)) { + return details::get_value(settings_).value; + } + + void save_start_time() { + auto &show_elapsed_time = get_value(); + auto &show_remaining_time = get_value(); + auto &saved_start_time = get_value(); + if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { + start_time_point_ = std::chrono::high_resolution_clock::now(); + saved_start_time = true; + } + } + +public: + void print_progress() { + std::lock_guard lock{mutex_}; + + auto &os = get_value(); + + const auto max_progress = get_value(); + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start_time_point_); + + if (get_value() != Color::unspecified) + details::set_stream_color(os, get_value()); + + for (auto &style : get_value()) + details::set_font_style(os, style); + + os << get_value(); + if (get_value()) + os << get_value() + [index_ % get_value().size()]; + if (get_value()) { + os << " " << std::size_t(progress_ / double(max_progress) * 100) << "%"; + } + + if (get_value()) { + os << " ["; + details::write_duration(os, elapsed); + } + + if (get_value()) { + if (get_value()) + os << "<"; + else + os << " ["; + auto eta = std::chrono::nanoseconds( + progress_ > 0 + ? static_cast(std::ceil(float(elapsed.count()) * + max_progress / progress_)) + : 0); + auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); + details::write_duration(os, remaining); + os << "]"; + } else { + if (get_value()) + os << "]"; + } + + if (get_value() == 0) + get_value() = 10; + os << " " << get_value() + << std::string(get_value(), ' ') << "\r"; + os.flush(); + index_ += 1; + if (progress_ > max_progress) { + get_value() = true; + } + if (get_value()) + os << termcolor::reset << std::endl; + } +}; + +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/progress_type.hpp b/thirdparty/indicators/include/indicators/progress_type.hpp new file mode 100644 index 0000000000..54da1e84b8 --- /dev/null +++ b/thirdparty/indicators/include/indicators/progress_type.hpp @@ -0,0 +1,9 @@ + +#ifndef INDICATORS_PROGRESS_TYPE +#define INDICATORS_PROGRESS_TYPE + +namespace indicators { +enum class ProgressType { incremental, decremental }; +} + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/setting.hpp b/thirdparty/indicators/include/indicators/setting.hpp new file mode 100644 index 0000000000..7298258eea --- /dev/null +++ b/thirdparty/indicators/include/indicators/setting.hpp @@ -0,0 +1,222 @@ + +/* +Activity Indicators for Modern C++ +https://github.com/p-ranav/indicators + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2019 Dawid Pilarski . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#ifndef INDICATORS_SETTING +#define INDICATORS_SETTING + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +namespace details { + +template struct if_else; + +template <> struct if_else { using type = std::true_type; }; + +template <> struct if_else { using type = std::false_type; }; + +template struct if_else_type; + +template struct if_else_type { + using type = True; +}; + +template struct if_else_type { + using type = False; +}; + +template struct conjuction; + +template <> struct conjuction<> : std::true_type {}; + +template +struct conjuction + : if_else_type>::type {}; + +template struct disjunction; + +template <> struct disjunction<> : std::false_type {}; + +template +struct disjunction + : if_else_type>::type {}; + +enum class ProgressBarOption { + bar_width = 0, + prefix_text, + postfix_text, + start, + end, + fill, + lead, + remainder, + max_postfix_text_len, + completed, + show_percentage, + show_elapsed_time, + show_remaining_time, + saved_start_time, + foreground_color, + spinner_show, + spinner_states, + font_styles, + hide_bar_when_complete, + min_progress, + max_progress, + progress_type, + stream +}; + +template struct Setting { + template ::value>::type> + explicit Setting(Args &&... args) : value(std::forward(args)...) {} + Setting(const Setting &) = default; + Setting(Setting &&) = default; + + static constexpr auto id = Id; + using type = T; + + T value{}; +}; + +template struct is_setting : std::false_type {}; + +template struct is_setting> : std::true_type {}; + +template +struct are_settings : if_else...>::value>::type {}; + +template <> struct are_settings<> : std::true_type {}; + +template struct is_setting_from_tuple; + +template struct is_setting_from_tuple> : std::true_type {}; + +template +struct is_setting_from_tuple> + : if_else...>::value>::type {}; + +template +struct are_settings_from_tuple + : if_else...>::value>::type {}; + +template struct always_true { static constexpr auto value = true; }; + +template Default &&get_impl(Default &&def) { + return std::forward(def); +} + +template +auto get_impl(Default && /*def*/, T &&first, Args &&... /*tail*/) -> + typename std::enable_if<(std::decay::type::id == Id), + decltype(std::forward(first))>::type { + return std::forward(first); +} + +template +auto get_impl(Default &&def, T && /*first*/, Args &&... tail) -> + typename std::enable_if<(std::decay::type::id != Id), + decltype(get_impl(std::forward(def), + std::forward(tail)...))>::type { + return get_impl(std::forward(def), std::forward(tail)...); +} + +template ::value, void>::type> +auto get(Default &&def, Args &&... args) + -> decltype(details::get_impl(std::forward(def), std::forward(args)...)) { + return details::get_impl(std::forward(def), std::forward(args)...); +} + +template using StringSetting = Setting; + +template using IntegerSetting = Setting; + +template using BooleanSetting = Setting; + +template struct option_idx; + +template +struct option_idx, counter> + : if_else_type<(Id == T::id), std::integral_constant, + option_idx, counter + 1>>::type {}; + +template struct option_idx, counter> { + static_assert(always_true<(ProgressBarOption)Id>::value, "No such option was found"); +}; + +template +auto get_value(Settings &&settings) + -> decltype((std::get::type>::value>( + std::declval()))) { + return std::get::type>::value>( + std::forward(settings)); +} + +} // namespace details + +namespace option { +using BarWidth = details::IntegerSetting; +using PrefixText = details::StringSetting; +using PostfixText = details::StringSetting; +using Start = details::StringSetting; +using End = details::StringSetting; +using Fill = details::StringSetting; +using Lead = details::StringSetting; +using Remainder = details::StringSetting; +using MaxPostfixTextLen = details::IntegerSetting; +using Completed = details::BooleanSetting; +using ShowPercentage = details::BooleanSetting; +using ShowElapsedTime = details::BooleanSetting; +using ShowRemainingTime = details::BooleanSetting; +using SavedStartTime = details::BooleanSetting; +using ForegroundColor = details::Setting; +using ShowSpinner = details::BooleanSetting; +using SpinnerStates = + details::Setting, details::ProgressBarOption::spinner_states>; +using HideBarWhenComplete = + details::BooleanSetting; +using FontStyles = + details::Setting, details::ProgressBarOption::font_styles>; +using MinProgress = details::IntegerSetting; +using MaxProgress = details::IntegerSetting; +using ProgressType = details::Setting; +using Stream = details::Setting; +} // namespace option +} // namespace indicators + +#endif \ No newline at end of file diff --git a/thirdparty/indicators/include/indicators/termcolor.hpp b/thirdparty/indicators/include/indicators/termcolor.hpp new file mode 100644 index 0000000000..3e82bdce41 --- /dev/null +++ b/thirdparty/indicators/include/indicators/termcolor.hpp @@ -0,0 +1,916 @@ +//! +//! termcolor +//! ~~~~~~~~~ +//! +//! termcolor is a header-only c++ library for printing colored messages +//! to the terminal. Written just for fun with a help of the Force. +//! +//! :copyright: (c) 2013 by Ihor Kalnytskyi +//! :license: BSD, see LICENSE for details +//! + +#ifndef TERMCOLOR_HPP_ +#define TERMCOLOR_HPP_ + +#include +#include + +// Detect target's platform and set some macros in order to wrap platform +// specific code this library depends on. +#if defined(_WIN32) || defined(_WIN64) +# define TERMCOLOR_TARGET_WINDOWS +#elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) +# define TERMCOLOR_TARGET_POSIX +#endif + +// If implementation has not been explicitly set, try to choose one based on +// target platform. +#if !defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) && !defined(TERMCOLOR_USE_WINDOWS_API) && !defined(TERMCOLOR_USE_NOOP) +# if defined(TERMCOLOR_TARGET_POSIX) +# define TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES +# define TERMCOLOR_AUTODETECTED_IMPLEMENTATION +# elif defined(TERMCOLOR_TARGET_WINDOWS) +# define TERMCOLOR_USE_WINDOWS_API +# define TERMCOLOR_AUTODETECTED_IMPLEMENTATION +# endif +#endif + +// These headers provide isatty()/fileno() functions, which are used for +// testing whether a standard stream refers to the terminal. +#if defined(TERMCOLOR_TARGET_POSIX) +# include +#elif defined(TERMCOLOR_TARGET_WINDOWS) +#if defined(_MSC_VER) +#if !defined(NOMINMAX) +#define NOMINMAX +#endif +#endif +# include +# include +#endif + + +namespace termcolor +{ + // Forward declaration of the `_internal` namespace. + // All comments are below. + namespace _internal + { + inline int colorize_index(); + inline FILE* get_standard_stream(const std::ostream& stream); + inline bool is_colorized(std::ostream& stream); + inline bool is_atty(const std::ostream& stream); + + #if defined(TERMCOLOR_TARGET_WINDOWS) + inline void win_change_attributes(std::ostream& stream, int foreground, int background=-1); + #endif + } + + inline + std::ostream& colorize(std::ostream& stream) + { + stream.iword(_internal::colorize_index()) = 1L; + return stream; + } + + inline + std::ostream& nocolorize(std::ostream& stream) + { + stream.iword(_internal::colorize_index()) = 0L; + return stream; + } + + inline + std::ostream& reset(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[00m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, -1); + #endif + } + return stream; + } + + inline + std::ostream& bold(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[1m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& dark(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[2m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& italic(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[3m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& underline(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[4m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, COMMON_LVB_UNDERSCORE); + #endif + } + return stream; + } + + inline + std::ostream& blink(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[5m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& reverse(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[7m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& concealed(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[8m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& crossed(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[9m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + template inline + std::ostream& color(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + char command[12]; + std::snprintf(command, sizeof(command), "\033[38;5;%dm", code); + stream << command; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + template inline + std::ostream& on_color(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + char command[12]; + std::snprintf(command, sizeof(command), "\033[48;5;%dm", code); + stream << command; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + template inline + std::ostream& color(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + char command[20]; + std::snprintf(command, sizeof(command), "\033[38;2;%d;%d;%dm", r, g, b); + stream << command; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + template inline + std::ostream& on_color(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + char command[20]; + std::snprintf(command, sizeof(command), "\033[48;2;%d;%d;%dm", r, g, b); + stream << command; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + #endif + } + return stream; + } + + inline + std::ostream& grey(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[30m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + 0 // grey (black) + ); + #endif + } + return stream; + } + + inline + std::ostream& red(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[31m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_RED + ); + #endif + } + return stream; + } + + inline + std::ostream& green(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[32m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_GREEN + ); + #endif + } + return stream; + } + + inline + std::ostream& yellow(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[33m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_GREEN | FOREGROUND_RED + ); + #endif + } + return stream; + } + + inline + std::ostream& blue(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[34m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE + ); + #endif + } + return stream; + } + + inline + std::ostream& magenta(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[35m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_RED + ); + #endif + } + return stream; + } + + inline + std::ostream& cyan(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[36m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_GREEN + ); + #endif + } + return stream; + } + + inline + std::ostream& white(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[37m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED + ); + #endif + } + return stream; + } + + + inline + std::ostream& bright_grey(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[90m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + 0 | FOREGROUND_INTENSITY // grey (black) + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_red(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[91m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_RED | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_green(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[92m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_GREEN | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_yellow(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[93m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_blue(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[94m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_magenta(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[95m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_cyan(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[96m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& bright_white(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[97m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY + ); + #endif + } + return stream; + } + + + inline + std::ostream& on_grey(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[40m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + 0 // grey (black) + ); + #endif + } + return stream; + } + + inline + std::ostream& on_red(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[41m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_RED + ); + #endif + } + return stream; + } + + inline + std::ostream& on_green(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[42m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN + ); + #endif + } + return stream; + } + + inline + std::ostream& on_yellow(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[43m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_RED + ); + #endif + } + return stream; + } + + inline + std::ostream& on_blue(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[44m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_BLUE + ); + #endif + } + return stream; + } + + inline + std::ostream& on_magenta(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[45m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_BLUE | BACKGROUND_RED + ); + #endif + } + return stream; + } + + inline + std::ostream& on_cyan(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[46m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_BLUE + ); + #endif + } + return stream; + } + + inline + std::ostream& on_white(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[47m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED + ); + #endif + } + + return stream; + } + + + inline + std::ostream& on_bright_grey(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[100m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + 0 | BACKGROUND_INTENSITY // grey (black) + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_red(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[101m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_RED | BACKGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_green(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[102m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_yellow(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[103m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_blue(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[104m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_BLUE | BACKGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_magenta(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[105m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_cyan(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[106m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY + ); + #endif + } + return stream; + } + + inline + std::ostream& on_bright_white(std::ostream& stream) + { + if (_internal::is_colorized(stream)) + { + #if defined(TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES) + stream << "\033[107m"; + #elif defined(TERMCOLOR_USE_WINDOWS_API) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED | BACKGROUND_INTENSITY + ); + #endif + } + + return stream; + } + + + + //! Since C++ hasn't a way to hide something in the header from + //! the outer access, I have to introduce this namespace which + //! is used for internal purpose and should't be access from + //! the user code. + namespace _internal + { + // An index to be used to access a private storage of I/O streams. See + // colorize / nocolorize I/O manipulators for details. Due to the fact + // that static variables ain't shared between translation units, inline + // function with local static variable is used to do the trick and share + // the variable value between translation units. + inline int colorize_index() + { + static int colorize_index = std::ios_base::xalloc(); + return colorize_index; + } + + //! Since C++ hasn't a true way to extract stream handler + //! from the a given `std::ostream` object, I have to write + //! this kind of hack. + inline + FILE* get_standard_stream(const std::ostream& stream) + { + if (&stream == &std::cout) + return stdout; + else if ((&stream == &std::cerr) || (&stream == &std::clog)) + return stderr; + + return nullptr; + } + + // Say whether a given stream should be colorized or not. It's always + // true for ATTY streams and may be true for streams marked with + // colorize flag. + inline + bool is_colorized(std::ostream& stream) + { + return is_atty(stream) || static_cast(stream.iword(colorize_index())); + } + + //! Test whether a given `std::ostream` object refers to + //! a terminal. + inline + bool is_atty(const std::ostream& stream) + { + FILE* std_stream = get_standard_stream(stream); + + // Unfortunately, fileno() ends with segmentation fault + // if invalid file descriptor is passed. So we need to + // handle this case gracefully and assume it's not a tty + // if standard stream is not detected, and 0 is returned. + if (!std_stream) + return false; + + #if defined(TERMCOLOR_TARGET_POSIX) + return ::isatty(fileno(std_stream)); + #elif defined(TERMCOLOR_TARGET_WINDOWS) + return ::_isatty(_fileno(std_stream)); + #else + return false; + #endif + } + + #if defined(TERMCOLOR_TARGET_WINDOWS) + //! Change Windows Terminal colors attribute. If some + //! parameter is `-1` then attribute won't changed. + inline void win_change_attributes(std::ostream& stream, int foreground, int background) + { + // yeah, i know.. it's ugly, it's windows. + static WORD defaultAttributes = 0; + + // Windows doesn't have ANSI escape sequences and so we use special + // API to change Terminal output color. That means we can't + // manipulate colors by means of "std::stringstream" and hence + // should do nothing in this case. + if (!_internal::is_atty(stream)) + return; + + // get terminal handle + HANDLE hTerminal = INVALID_HANDLE_VALUE; + if (&stream == &std::cout) + hTerminal = GetStdHandle(STD_OUTPUT_HANDLE); + else if (&stream == &std::cerr) + hTerminal = GetStdHandle(STD_ERROR_HANDLE); + + // save default terminal attributes if it unsaved + if (!defaultAttributes) + { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(hTerminal, &info)) + return; + defaultAttributes = info.wAttributes; + } + + // restore all default settings + if (foreground == -1 && background == -1) + { + SetConsoleTextAttribute(hTerminal, defaultAttributes); + return; + } + + // get current settings + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(hTerminal, &info)) + return; + + if (foreground != -1) + { + info.wAttributes &= ~(info.wAttributes & 0x0F); + info.wAttributes |= static_cast(foreground); + } + + if (background != -1) + { + info.wAttributes &= ~(info.wAttributes & 0xF0); + info.wAttributes |= static_cast(background); + } + + SetConsoleTextAttribute(hTerminal, info.wAttributes); + } + #endif // TERMCOLOR_TARGET_WINDOWS + + } // namespace _internal + +} // namespace termcolor + + +#undef TERMCOLOR_TARGET_POSIX +#undef TERMCOLOR_TARGET_WINDOWS + +#if defined(TERMCOLOR_AUTODETECTED_IMPLEMENTATION) +# undef TERMCOLOR_USE_ANSI_ESCAPE_SEQUENCES +# undef TERMCOLOR_USE_WINDOWS_API +#endif + +#endif // TERMCOLOR_HPP_ diff --git a/thirdparty/indicators/include/indicators/terminal_size.hpp b/thirdparty/indicators/include/indicators/terminal_size.hpp new file mode 100644 index 0000000000..0d0307755b --- /dev/null +++ b/thirdparty/indicators/include/indicators/terminal_size.hpp @@ -0,0 +1,44 @@ + +#ifndef INDICATORS_TERMINAL_SIZE +#define INDICATORS_TERMINAL_SIZE +#include + + +#if defined(_WIN32) +#include + +namespace indicators { + +static inline std::pair terminal_size() { + CONSOLE_SCREEN_BUFFER_INFO csbi; + int cols, rows; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; + rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + return {static_cast(rows), static_cast(cols)}; +} + +static inline size_t terminal_width() { return terminal_size().second; } + +} // namespace indicators + +#else + +#include //ioctl() and TIOCGWINSZ +#include // for STDOUT_FILENO + +namespace indicators { + +static inline std::pair terminal_size() { + struct winsize size{}; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); + return {static_cast(size.ws_row), static_cast(size.ws_col)}; +} + +static inline size_t terminal_width() { return terminal_size().second; } + +} // namespace indicators + +#endif + +#endif \ No newline at end of file