diff --git a/clang-format.bash b/clang-format.bash new file mode 100755 index 0000000..b6e453b --- /dev/null +++ b/clang-format.bash @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +find ./include ./test/ -type f \( -iname \*.cpp -o -iname \*.hpp \) | xargs clang-format -style="{ColumnLimit : 100}" -i diff --git a/include/progress/bar.hpp b/include/progress/bar.hpp index d629732..74859a8 100644 --- a/include/progress/bar.hpp +++ b/include/progress/bar.hpp @@ -1,84 +1,60 @@ #pragma once -#include -#include #include +#include #include +#include +#include #include class ProgressBar { - float _progress{0.0}; - size_t _bar_width{100}; - std::string _start{"["}; - std::string _fill{"="}; - std::string _lead{">"}; - std::string _remainder{" "}; - std::string _end{"]"}; - std::atomic _completed{false}; - std::atomic _show_percentage{true}; - std::mutex _mutex; - - void _print_progress() { - std::unique_lock lock{_mutex}; - std::cout << _start; - float pos = _progress * static_cast(_bar_width) / 100.0; - for (size_t i = 0; i < _bar_width; ++i) { - if (i < pos) std::cout << _fill; - else if (i == pos) std::cout << _lead; - else std::cout << _remainder; - } - std::cout << _end; - if (_show_percentage) - std::cout << " " << static_cast(_progress) << "%\r"; - else - std::cout << "\r"; - std::cout.flush(); - if (_completed) - std::cout << std::endl; - } - public: + enum class Color { GREY, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; + + void color(Color color) { + std::unique_lock lock{_mutex}; + _color = color; + } void bar_width(size_t bar_width) { std::unique_lock lock{_mutex}; _bar_width = bar_width; } - void start_with(const std::string& start) { + void start_with(const std::string &start) { std::unique_lock lock{_mutex}; _start = start; } - void fill_progress_with(const std::string& fill) { - std::unique_lock lock{_mutex}; + void fill_progress_with(const std::string &fill) { + std::unique_lock lock{_mutex}; _fill = fill; } - void lead_progress_with(const std::string& lead) { - std::unique_lock lock{_mutex}; + void lead_progress_with(const std::string &lead) { + std::unique_lock lock{_mutex}; _lead = lead; } - void fill_remainder_with(const std::string& remainder) { - std::unique_lock lock{_mutex}; + void fill_remainder_with(const std::string &remainder) { + std::unique_lock lock{_mutex}; _remainder = remainder; - } + } - void end_with(const std::string& end) { - std::unique_lock lock{_mutex}; + void end_with(const std::string &end) { + std::unique_lock lock{_mutex}; _end = end; } - void show_percentage(bool flag) { - _show_percentage = flag; - } + void show_percentage(bool flag) { _show_percentage = flag; } void set_progress(float value) { { std::unique_lock lock{_mutex}; - if (_completed) return; + if (_completed) + return; _progress = value; if (_progress >= 100.0) { - _completed = true; + _completed = true; } } _print_progress(); @@ -87,17 +63,76 @@ public: void tick() { { std::unique_lock lock{_mutex}; - if (_completed) return; + if (_completed) + return; _progress += 1; if (_progress >= 100.0) { - _completed = true; + _completed = true; } } _print_progress(); } - bool completed() { - return _completed; - } - + bool completed() { return _completed; } + +private: + float _progress{0.0}; + size_t _bar_width{100}; + std::string _start{"["}; + std::string _fill{"="}; + std::string _lead{">"}; + std::string _remainder{" "}; + std::string _end{"]"}; + std::atomic _completed{false}; + std::atomic _show_percentage{true}; + std::mutex _mutex; + Color _color; + + void _print_progress() { + std::unique_lock lock{_mutex}; + switch(_color) { + case Color::GREY: + std::cout << termcolor::grey; + break; + case Color::RED: + std::cout << termcolor::red; + break; + case Color::GREEN: + std::cout << termcolor::green; + break; + case Color::YELLOW: + std::cout << termcolor::yellow; + break; + case Color::BLUE: + std::cout << termcolor::blue; + break; + case Color::MAGENTA: + std::cout << termcolor::magenta; + break; + case Color::CYAN: + std::cout << termcolor::cyan; + break; + case Color::WHITE: + std::cout << termcolor::white; + break; + } + std::cout << _start; + float pos = _progress * static_cast(_bar_width) / 100.0; + for (size_t i = 0; i < _bar_width; ++i) { + if (i < pos) + std::cout << _fill; + else if (i == pos) + std::cout << _lead; + else + std::cout << _remainder; + } + std::cout << _end; + if (_show_percentage) + std::cout << " " << static_cast(_progress) << "%\r"; + else + std::cout << "\r"; + std::cout.flush(); + if (_completed) + std::cout << termcolor::reset << std::endl; + } }; diff --git a/include/progress/spinner.hpp b/include/progress/spinner.hpp index 7206665..a1ae75d 100644 --- a/include/progress/spinner.hpp +++ b/include/progress/spinner.hpp @@ -1,30 +1,25 @@ #pragma once -#include -#include #include +#include #include +#include #include #include class ProgressSpinner { std::string _name{"Running"}; - size_t _bar_width{80}; + size_t _bar_width{80}; std::mutex _mutex; float _progress{0.0}; size_t _index{0}; std::vector _states{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}; - void hide_cursor() { - std::cout << "\e[?25l"; - } + void hide_cursor() { std::cout << "\e[?25l"; } + + void show_cursor() { std::cout << "\e[?25h"; } - void show_cursor() { - std::cout << "\e[?25h"; - } - public: - - explicit ProgressSpinner(const std::string& name) : _name(name) {} + explicit ProgressSpinner(const std::string &name) : _name(name) {} void increment(float value) { std::unique_lock lock{_mutex}; diff --git a/include/progress/termcolor.hpp b/include/progress/termcolor.hpp new file mode 100644 index 0000000..c2eda0d --- /dev/null +++ b/include/progress/termcolor.hpp @@ -0,0 +1,447 @@ +//! +//! 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_ + +// the following snippet of code detects the current OS and +// defines the appropriate macro that is used to wrap some +// platform specific things +#if defined(_WIN32) || defined(_WIN64) +#define TERMCOLOR_OS_WINDOWS +#elif defined(__APPLE__) +#define TERMCOLOR_OS_MACOS +#elif defined(__unix__) || defined(__unix) +#define TERMCOLOR_OS_LINUX +#else +#error unsupported platform +#endif + +// This headers provides the `isatty()`/`fileno()` functions, +// which are used for testing whether a standart stream refers +// to the terminal. As for Windows, we also need WinApi funcs +// for changing colors attributes of the terminal. +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) +#include +#elif defined(TERMCOLOR_OS_WINDOWS) +#include +#include +#endif + +#include +#include + +namespace termcolor { +// Forward declaration of the `_internal` namespace. +// All comments are below. +namespace _internal { +// An index to be used to access a private storage of I/O streams. See +// colorize / nocolorize I/O manipulators for details. +static int colorize_index = std::ios_base::xalloc(); + +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_OS_WINDOWS) +inline void win_change_attributes(std::ostream &stream, int foreground, int background = -1); +#endif +} // namespace _internal + +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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[00m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[1m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &dark(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[2m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &italic(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[3m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &underline(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[4m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &blink(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[5m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &reverse(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[7m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &concealed(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[8m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &crossed(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[9m"; +#elif defined(TERMCOLOR_OS_WINDOWS) +#endif + } + return stream; +} + +inline std::ostream &grey(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[30m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[31m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[32m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[33m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[34m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[35m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[36m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[37m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _internal::win_change_attributes(stream, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); +#endif + } + return stream; +} + +inline std::ostream &on_grey(std::ostream &stream) { + if (_internal::is_colorized(stream)) { +#if defined(TERMCOLOR_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[40m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[41m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[42m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[43m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[44m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[45m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[46m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + stream << "\033[47m"; +#elif defined(TERMCOLOR_OS_WINDOWS) + _internal::win_change_attributes(stream, -1, + BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED); +#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 { +//! 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 0; +} + +// 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_OS_MACOS) || defined(TERMCOLOR_OS_LINUX) + return ::isatty(fileno(std_stream)); +#elif defined(TERMCOLOR_OS_WINDOWS) + return ::_isatty(_fileno(std_stream)); +#endif +} + +#if defined(TERMCOLOR_OS_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_OS_WINDOWS + +} // namespace _internal + +} // namespace termcolor + +#undef TERMCOLOR_OS_WINDOWS +#undef TERMCOLOR_OS_MACOS +#undef TERMCOLOR_OS_LINUX + +#endif // TERMCOLOR_HPP_ diff --git a/test/bar.cpp b/test/bar.cpp index 478fb48..d02277f 100644 --- a/test/bar.cpp +++ b/test/bar.cpp @@ -1,6 +1,7 @@ #include int main() { + ProgressBar bar; // Configure progress bar @@ -9,22 +10,23 @@ int main() { bar.fill_progress_with("■"); bar.lead_progress_with("■"); bar.fill_remainder_with("-"); - bar.end_with("]"); + bar.end_with(" ]"); + bar.color(ProgressBar::Color::GREEN); // As configured, the bar will look like this: - // - // [■■■■■■■■■■■■■■■■■■■■--------] 70% + // + // [■■■■■■■■■■■■■■■■■■■■--------] 70% // // auto job = [&bar]() { - while(true) { - if (bar.completed()) - break; - bar.tick(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - }; + while (true) { + if (bar.completed()) + break; + bar.tick(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }; std::thread first_job(job); std::thread second_job(job); @@ -36,5 +38,7 @@ int main() { third_job.join(); last_job.join(); + std::cout << "Done\n"; + return 0; }