From 63bb0d89465041e3c239c3fc1bb8766a85a2a26a Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Mon, 25 May 2020 08:51:34 -0500 Subject: [PATCH] Progress bar now uses terminal size to figure out how many spaces to use to fill up the remainder of the terminal row --- include/indicators/progress_bar.hpp | 312 ++++++++++++++-------- include/indicators/terminal_size.hpp | 6 +- single_include/indicators/indicators.hpp | 318 +++++++++++++++-------- 3 files changed, 424 insertions(+), 212 deletions(-) diff --git a/include/indicators/progress_bar.hpp b/include/indicators/progress_bar.hpp index dcfae7a..51c328f 100644 --- a/include/indicators/progress_bar.hpp +++ b/include/indicators/progress_bar.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -21,42 +22,45 @@ namespace indicators { class ProgressBar { using Settings = - std::tuple; + std::tuple; public: template ::type...>::value, - void *>::type = nullptr> + typename std::enable_if< + details::are_settings_from_tuple< + Settings, typename std::decay::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::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::Completed{false}, std::forward(args)...), + details::get( + option::ShowPercentage{false}, std::forward(args)...), details::get( option::ShowElapsedTime{false}, std::forward(args)...), details::get( @@ -64,17 +68,20 @@ public: details::get( option::SavedStartTime{false}, std::forward(args)...), details::get( - option::ForegroundColor{Color::unspecified}, std::forward(args)...), + 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)...), + 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)...)) { + 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 @@ -87,38 +94,47 @@ public: template void set_option(details::Setting &&setting) { - static_assert(!std::is_same( - std::declval()))>::type>::value, - "Setting has wrong type!"); + 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!"); + 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) { + 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(); + if (setting.value.length() > + get_value()) { + get_value() = + setting.value.length(); } } - void - set_option(details::Setting &&setting) { + void set_option( + details::Setting + &&setting) { std::lock_guard lock(mutex_); - get_value() = std::move(setting).value; + get_value() = + std::move(setting).value; auto &new_value = get_value(); - if (new_value.length() > get_value()) { - get_value() = new_value.length(); + if (new_value.length() > + get_value()) { + get_value() = + new_value.length(); } } @@ -147,10 +163,14 @@ public: size_t current() { std::lock_guard lock{mutex_}; - return std::min(progress_, size_t(get_value())); + return std::min( + progress_, + size_t(get_value())); } - bool is_completed() const { return get_value(); } + bool is_completed() const { + return get_value(); + } void mark_as_completed() { get_value() = true; @@ -159,13 +179,14 @@ public: private: template - auto get_value() -> decltype((details::get_value(std::declval()).value)) { + 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)) { + auto get_value() const -> decltype( + (details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -180,61 +201,41 @@ private: 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(); + 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; } } -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); - + size_t get_prefix_length() { + std::stringstream os; os << get_value(); + return os.str().size(); + } - 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(); + size_t get_postfix_length() { + std::stringstream os; + const auto min_progress = + get_value(); + const auto max_progress = + get_value(); if (get_value()) { os << " " - << std::min(static_cast(static_cast(progress_) / max_progress * 100), + << std::min(static_cast(static_cast(progress_) / + max_progress * 100), size_t(100)) << "%"; } - auto &saved_start_time = get_value(); + auto &saved_start_time = + get_value(); if (get_value()) { os << " ["; @@ -252,7 +253,8 @@ public: if (saved_start_time) { auto eta = std::chrono::nanoseconds( - progress_ > 0 ? static_cast(elapsed_.count() * max_progress / progress_) + progress_ > 0 ? static_cast(elapsed_.count() * + max_progress / progress_) : 0); auto remaining = eta > elapsed_ ? (eta - elapsed_) : (elapsed_ - eta); details::write_duration(os, remaining); @@ -266,11 +268,115 @@ public: os << "]"; } - if (get_value() == 0) - get_value() = 10; - os << " " << get_value() - << std::string(get_value(), ' ') << "\r"; + os << " " << get_value(); + + return os.str().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); + + os << get_value(); + + 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(); + + 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(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(); + + // Get length of prefix text and postfix text + const auto prefix_length = get_prefix_length(); + const auto start_length = get_value().size(); + const auto bar_width = get_value(); + const auto end_length = get_value().size(); + const auto postfix_length = get_postfix_length(); + 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; diff --git a/include/indicators/terminal_size.hpp b/include/indicators/terminal_size.hpp index 29b21e7..f75f10d 100644 --- a/include/indicators/terminal_size.hpp +++ b/include/indicators/terminal_size.hpp @@ -7,7 +7,7 @@ namespace indicators { #if defined(_MSC_VER) #include -std::pair terminal_size() { +static inline std::pair terminal_size() { CONSOLE_SCREEN_BUFFER_INFO csbi; int columns, rows; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); @@ -22,13 +22,13 @@ size_t terminal_width() { return terminal_size().second; } #include //ioctl() and TIOCGWINSZ #include // for STDOUT_FILENO -std::pair terminal_size() { +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)}; } -size_t terminal_width() { return terminal_size().second; } +static inline size_t terminal_width() { return terminal_size().second; } #endif } // namespace indicators \ No newline at end of file diff --git a/single_include/indicators/indicators.hpp b/single_include/indicators/indicators.hpp index 9a91316..43d9854 100644 --- a/single_include/indicators/indicators.hpp +++ b/single_include/indicators/indicators.hpp @@ -1297,7 +1297,7 @@ namespace indicators { #if defined(_MSC_VER) #include -std::pair terminal_size() { +static inline std::pair terminal_size() { CONSOLE_SCREEN_BUFFER_INFO csbi; int columns, rows; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); @@ -1312,13 +1312,13 @@ size_t terminal_width() { return terminal_size().second; } #include //ioctl() and TIOCGWINSZ #include // for STDOUT_FILENO -std::pair terminal_size() { +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)}; } -size_t terminal_width() { return terminal_size().second; } +static inline size_t terminal_width() { return terminal_size().second; } #endif } // namespace indicators @@ -2150,6 +2150,7 @@ private: // #include #include #include +#include #include #include #include @@ -2160,42 +2161,45 @@ namespace indicators { class ProgressBar { using Settings = - std::tuple; + std::tuple; public: template ::type...>::value, - void *>::type = nullptr> + typename std::enable_if< + details::are_settings_from_tuple< + Settings, typename std::decay::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::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::Completed{false}, std::forward(args)...), + details::get( + option::ShowPercentage{false}, std::forward(args)...), details::get( option::ShowElapsedTime{false}, std::forward(args)...), details::get( @@ -2203,17 +2207,20 @@ public: details::get( option::SavedStartTime{false}, std::forward(args)...), details::get( - option::ForegroundColor{Color::unspecified}, std::forward(args)...), + 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)...), + 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)...)) { + 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 @@ -2226,38 +2233,47 @@ public: template void set_option(details::Setting &&setting) { - static_assert(!std::is_same( - std::declval()))>::type>::value, - "Setting has wrong type!"); + 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!"); + 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) { + 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(); + if (setting.value.length() > + get_value()) { + get_value() = + setting.value.length(); } } - void - set_option(details::Setting &&setting) { + void set_option( + details::Setting + &&setting) { std::lock_guard lock(mutex_); - get_value() = std::move(setting).value; + get_value() = + std::move(setting).value; auto &new_value = get_value(); - if (new_value.length() > get_value()) { - get_value() = new_value.length(); + if (new_value.length() > + get_value()) { + get_value() = + new_value.length(); } } @@ -2286,10 +2302,14 @@ public: size_t current() { std::lock_guard lock{mutex_}; - return std::min(progress_, size_t(get_value())); + return std::min( + progress_, + size_t(get_value())); } - bool is_completed() const { return get_value(); } + bool is_completed() const { + return get_value(); + } void mark_as_completed() { get_value() = true; @@ -2298,13 +2318,14 @@ public: private: template - auto get_value() -> decltype((details::get_value(std::declval()).value)) { + 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)) { + auto get_value() const -> decltype( + (details::get_value(std::declval()).value)) { return details::get_value(settings_).value; } @@ -2319,61 +2340,41 @@ private: 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(); + 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; } } -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); - + size_t get_prefix_length() { + std::stringstream os; os << get_value(); + return os.str().size(); + } - 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(); + size_t get_postfix_length() { + std::stringstream os; + const auto min_progress = + get_value(); + const auto max_progress = + get_value(); if (get_value()) { os << " " - << std::min(static_cast(static_cast(progress_) / max_progress * 100), + << std::min(static_cast(static_cast(progress_) / + max_progress * 100), size_t(100)) << "%"; } - auto &saved_start_time = get_value(); + auto &saved_start_time = + get_value(); if (get_value()) { os << " ["; @@ -2391,7 +2392,8 @@ public: if (saved_start_time) { auto eta = std::chrono::nanoseconds( - progress_ > 0 ? static_cast(elapsed_.count() * max_progress / progress_) + progress_ > 0 ? static_cast(elapsed_.count() * + max_progress / progress_) : 0); auto remaining = eta > elapsed_ ? (eta - elapsed_) : (elapsed_ - eta); details::write_duration(os, remaining); @@ -2405,11 +2407,115 @@ public: os << "]"; } - if (get_value() == 0) - get_value() = 10; - os << " " << get_value() - << std::string(get_value(), ' ') << "\r"; + os << " " << get_value(); + + return os.str().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); + + os << get_value(); + + 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(); + + 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(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(); + + // Get length of prefix text and postfix text + const auto prefix_length = get_prefix_length(); + const auto start_length = get_value().size(); + const auto bar_width = get_value(); + const auto end_length = get_value().size(); + const auto postfix_length = get_postfix_length(); + 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;