├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets └── hr.png ├── build.sh ├── env.sh ├── install.sh └── src ├── hr ├── hr.cc └── hr.hh ├── main.cc └── ob ├── color.hh ├── parg.hh ├── string.cc ├── string.hh └── term.hh /.gitignore: -------------------------------------------------------------------------------- 1 | *.*.sw* 2 | build/ 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8 FATAL_ERROR) 2 | 3 | set (TARGET "hr") 4 | project (${TARGET}) 5 | 6 | find_program (CCACHE_FOUND ccache) 7 | if (CCACHE_FOUND) 8 | message ("ccache found") 9 | set_property (GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 10 | set_property (GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) 11 | endif (CCACHE_FOUND) 12 | 13 | set (CMAKE_CXX_STANDARD 17) 14 | set (CMAKE_CXX_STANDARD_REQUIRED ON) 15 | 16 | set (CMAKE_CXX_FLAGS "-std=c++17 -fdiagnostics-color=auto") 17 | 18 | set (DEBUG_FLAGS "-g -Og -rdynamic -Wpedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused") 19 | set (DEBUG_LINK_FLAGS "-fprofile-arcs -ftest-coverage") 20 | 21 | set (RELEASE_FLAGS "-O3 -flto") 22 | set (RELEASE_LINK_FLAGS "-O3 -flto -s") 23 | 24 | set (CMAKE_CXX_FLAGS_DEBUG ${DEBUG_FLAGS}) 25 | set (CMAKE_EXE_LINKER_FLAGS_DEBUG ${DEBUG_LINK_FLAGS}) 26 | 27 | set (CMAKE_CXX_FLAGS_RELEASE ${RELEASE_FLAGS}) 28 | set (CMAKE_EXE_LINKER_FLAGS_RELEASE ${RELEASE_LINK_FLAGS}) 29 | 30 | message ("CMAKE_BUILD_TYPE is ${CMAKE_BUILD_TYPE}") 31 | 32 | set (SOURCES 33 | src/main.cc 34 | src/ob/string.cc 35 | src/hr/hr.cc 36 | ) 37 | 38 | add_executable ( 39 | ${TARGET} 40 | ${SOURCES} 41 | ) 42 | 43 | target_include_directories( 44 | ${TARGET} 45 | PRIVATE 46 | ./src 47 | ) 48 | 49 | target_link_libraries ( 50 | ${TARGET} 51 | ) 52 | 53 | install (TARGETS ${TARGET} DESTINATION bin) 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Brett Robinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hr 2 | A horizontal rule for the terminal. 3 | 4 | ![hr](https://raw.githubusercontent.com/octobanana/hr/master/assets/hr.png) 5 | 6 | ## Contents 7 | * [About](#about) 8 | * [Features](#features) 9 | * [Pre-Build](#pre-build) 10 | * [Dependencies](#dependencies) 11 | * [Included Libraries](#included-libraries) 12 | * [Environment](#environment) 13 | * [Build](#build) 14 | * [Install](#install) 15 | * [License](#license) 16 | 17 | ## About 18 | __Hr__ prints a repeated string of characters the width of the terminal. 19 | It is useful to visually divide output, such as in a shell script, or appending a dividing line with a fixed width to a file. 20 | The output can be configured through the use of command line options and flags. 21 | 22 | ### Features 23 | * display a horizontal line the exact width of the terminal 24 | * display a specific number of columns and rows 25 | * set the horizontal lines text and fill strings 26 | * toggle bold and underline styles 27 | * align text to the left, right, or center 28 | * set the foreground and background colours using 4-bit, 8-bit, and 24-bit colours 29 | 30 | ## Pre-Build 31 | This section describes what environments this program may run on, 32 | any prior requirements or dependencies needed, 33 | and any third party libraries used. 34 | 35 | > #### Important 36 | > Any shell commands using relative paths are expected to be executed in the 37 | > root directory of this repository. 38 | 39 | ### Dependencies 40 | * __C++17__ compiler/library 41 | * __CMake__ >= 3.8 42 | 43 | ### Included Libraries 44 | * [__parg__](https://github.com/octobanana/parg): 45 | for parsing CLI args, included as `./src/ob/parg.hh` 46 | 47 | ### Environment 48 | * __Linux__ (supported) 49 | * __BSD__ (supported) 50 | * __macOS__ (supported) 51 | 52 | ## Build 53 | The following shell command will build the project in release mode: 54 | ```sh 55 | ./build.sh 56 | ``` 57 | To build in debug mode, run the script with the `--debug` flag. 58 | 59 | ## Install 60 | The following shell command will install the project in release mode: 61 | ```sh 62 | ./install.sh 63 | ``` 64 | To install in debug mode, run the script with the `--debug` flag. 65 | 66 | ## License 67 | This project is licensed under the MIT License. 68 | 69 | Copyright (c) 2018-2019 [Brett Robinson](https://octobanana.com/) 70 | 71 | Permission is hereby granted, free of charge, to any person obtaining a copy 72 | of this software and associated documentation files (the "Software"), to deal 73 | in the Software without restriction, including without limitation the rights 74 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 75 | copies of the Software, and to permit persons to whom the Software is 76 | furnished to do so, subject to the following conditions: 77 | 78 | The above copyright notice and this permission notice shall be included in all 79 | copies or substantial portions of the Software. 80 | 81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 82 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 83 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 84 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 85 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 86 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 87 | SOFTWARE. 88 | -------------------------------------------------------------------------------- /assets/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octobanana/hr/b29f8455b9e238a7d938d522699c95c52b3631b9/assets/hr.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILD_TYPE="release" 5 | 6 | if [[ $# > 0 ]]; then 7 | if [[ $1 == "--debug" ]]; then 8 | BUILD_TYPE="debug" 9 | elif [[ $1 == "--release" ]]; then 10 | BUILD_TYPE="release" 11 | else 12 | printf "usage: ./build.sh [--debug|--release]\n"; 13 | exit 1 14 | fi 15 | fi 16 | 17 | # source environment variables 18 | source ./env.sh 19 | 20 | printf "\nBuilding ${APP} in ${BUILD_TYPE} mode\n" 21 | 22 | printf "\nCompiling ${APP}\n" 23 | mkdir -p build/${BUILD_TYPE} 24 | cd build/${BUILD_TYPE} 25 | cmake ../../ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} 26 | time make 27 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | printf "\nSetting Environment Variables\n" 2 | 3 | export APP="peaclock" 4 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILD_TYPE="release" 5 | 6 | if [[ $# > 0 ]]; then 7 | if [[ $1 == "--debug" ]]; then 8 | BUILD_TYPE="debug" 9 | elif [[ $1 == "--release" ]]; then 10 | BUILD_TYPE="release" 11 | else 12 | printf "usage: ./install.sh [--debug|--release]\n"; 13 | exit 1 14 | fi 15 | fi 16 | 17 | # source environment variables 18 | source ./env.sh 19 | 20 | # build 21 | ./build.sh --${BUILD_TYPE} 22 | cd build/${BUILD_TYPE} 23 | 24 | # install 25 | printf "\nInstalling ${APP} in ${BUILD_TYPE} mode\n" 26 | if command -v sudo > /dev/null; then 27 | sudo make install 28 | elif command -v doas > /dev/null; then 29 | doas make install 30 | else 31 | printf "Don't know how to elevate privileges, bailing.\n" 32 | exit 1 33 | fi 34 | -------------------------------------------------------------------------------- /src/hr/hr.cc: -------------------------------------------------------------------------------- 1 | #include "hr.hh" 2 | 3 | #include "ob/term.hh" 4 | #include "ob/color.hh" 5 | #include "ob/string.hh" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace aec = OB::Term::ANSI_Escape_Codes; 13 | 14 | namespace OB 15 | { 16 | 17 | std::string Hr::fill() 18 | { 19 | return fill_; 20 | } 21 | 22 | Hr& Hr::fill(std::string const& val) 23 | { 24 | fill_ = val; 25 | 26 | return *this; 27 | } 28 | 29 | std::string Hr::text() 30 | { 31 | return text_; 32 | } 33 | 34 | Hr& Hr::text(std::string const& val) 35 | { 36 | text_ = val; 37 | 38 | return *this; 39 | } 40 | 41 | std::string Hr::align() 42 | { 43 | return align_; 44 | } 45 | 46 | Hr& Hr::align(std::string const& val) 47 | { 48 | align_ = val; 49 | 50 | return *this; 51 | } 52 | 53 | bool Hr::bold() 54 | { 55 | return bold_; 56 | } 57 | 58 | Hr& Hr::bold(bool const val) 59 | { 60 | bold_ = val; 61 | 62 | return *this; 63 | } 64 | 65 | bool Hr::underline() 66 | { 67 | return underline_; 68 | } 69 | 70 | Hr& Hr::underline(bool const val) 71 | { 72 | underline_ = val; 73 | 74 | return *this; 75 | } 76 | 77 | std::string Hr::color() 78 | { 79 | return color_; 80 | } 81 | 82 | Hr& Hr::color(std::string const& val) 83 | { 84 | color_ = val; 85 | 86 | return *this; 87 | } 88 | 89 | std::string Hr::color_fg() 90 | { 91 | return color_fg_.value(); 92 | } 93 | 94 | bool Hr::color_fg(std::string const& val) 95 | { 96 | return color_fg_.key(val); 97 | } 98 | 99 | std::string Hr::color_bg() 100 | { 101 | return color_bg_.value(); 102 | } 103 | 104 | bool Hr::color_bg(std::string const& val) 105 | { 106 | return color_bg_.key(val); 107 | } 108 | 109 | std::size_t Hr::height() 110 | { 111 | return height_; 112 | } 113 | 114 | Hr& Hr::height(std::size_t const val) 115 | { 116 | height_ = val; 117 | 118 | return *this; 119 | } 120 | 121 | std::size_t Hr::width() 122 | { 123 | return width_; 124 | } 125 | 126 | Hr& Hr::width(std::size_t const val) 127 | { 128 | width_ = val; 129 | 130 | return *this; 131 | } 132 | 133 | std::string Hr::render() 134 | { 135 | std::ostringstream os; 136 | 137 | auto const is_term = OB::Term::is_term(STDOUT_FILENO); 138 | auto const use_color = color_ == "auto" ? is_term : color_ == "on"; 139 | 140 | if (use_color) 141 | { 142 | os 143 | << color_fg_ 144 | << color_bg_; 145 | 146 | if (bold_) 147 | { 148 | os << aec::bold; 149 | } 150 | 151 | if (underline_) 152 | { 153 | os << aec::underline; 154 | } 155 | } 156 | 157 | auto const width = term_width(is_term); 158 | 159 | std::string line; 160 | 161 | if (text_.size()) 162 | { 163 | if (align_ == "left") 164 | { 165 | line = text_.substr(0, width); 166 | 167 | if (line.size() < width) 168 | { 169 | line += repeat(width - line.size(), fill_); 170 | } 171 | } 172 | else if (align_ == "right") 173 | { 174 | line = text_.substr(text_.size() > width ? text_.size() - width : 0, width); 175 | 176 | if (line.size() < width) 177 | { 178 | line.insert(0, repeat(width - line.size(), fill_)); 179 | } 180 | } 181 | else // center 182 | { 183 | line = text_.substr(0, width); 184 | 185 | if (line.size() < width) 186 | { 187 | line.insert(0, repeat((width - text_.size()) / 2, fill_)); 188 | } 189 | 190 | if (line.size() < width) 191 | { 192 | line += repeat(width - line.size(), fill_); 193 | } 194 | } 195 | } 196 | else 197 | { 198 | line = repeat(width, fill_); 199 | } 200 | 201 | line += "\n"; 202 | 203 | os << line; 204 | 205 | if (height_ > 1) 206 | { 207 | os << OB::String::repeat(height_ - 1, line); 208 | } 209 | 210 | if (use_color) 211 | { 212 | os << aec::clear; 213 | } 214 | 215 | buf_ = os.str(); 216 | 217 | return buf_; 218 | } 219 | 220 | std::string Hr::str() const 221 | { 222 | return buf_; 223 | } 224 | 225 | std::size_t Hr::term_width(bool const is_term) const 226 | { 227 | if (width_ > 0) 228 | { 229 | return width_; 230 | } 231 | 232 | if (is_term) 233 | { 234 | std::size_t width {0}; 235 | OB::Term::width(width); 236 | 237 | return width; 238 | } 239 | 240 | return width_default_; 241 | } 242 | 243 | std::string Hr::repeat(std::size_t const len, std::string const& str) const 244 | { 245 | if (len == 0 || str.empty()) 246 | { 247 | return {}; 248 | } 249 | 250 | 251 | if (str.size() >= len) 252 | { 253 | return str.substr(0, len); 254 | } 255 | 256 | std::string res {OB::String::repeat(len / str.size(), str)}; 257 | 258 | if (res.size() < len) 259 | { 260 | res += str.substr(0, len - res.size()); 261 | } 262 | 263 | return res; 264 | } 265 | 266 | } // namespace OB 267 | -------------------------------------------------------------------------------- /src/hr/hr.hh: -------------------------------------------------------------------------------- 1 | #ifndef OB_HR_HH 2 | #define OB_HR_HH 3 | 4 | #include "ob/term.hh" 5 | #include "ob/color.hh" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace aec = OB::Term::ANSI_Escape_Codes; 12 | 13 | namespace OB 14 | { 15 | 16 | class Hr 17 | { 18 | public: 19 | 20 | std::string fill(); 21 | Hr& fill(std::string const& val); 22 | 23 | std::string text(); 24 | Hr& text(std::string const& val); 25 | 26 | std::string align(); 27 | Hr& align(std::string const& val); 28 | 29 | bool bold(); 30 | Hr& bold(bool const val); 31 | 32 | bool underline(); 33 | Hr& underline(bool const val); 34 | 35 | std::string color(); 36 | Hr& color(std::string const& val); 37 | 38 | std::string color_fg(); 39 | bool color_fg(std::string const& val); 40 | 41 | std::string color_bg(); 42 | bool color_bg(std::string const& val); 43 | 44 | std::size_t height(); 45 | Hr& height(std::size_t const val); 46 | 47 | std::size_t width(); 48 | Hr& width(std::size_t const val); 49 | 50 | std::string render(); 51 | 52 | std::string str() const; 53 | 54 | private: 55 | 56 | std::size_t const width_default_ {80}; 57 | std::string fill_ {"-"}; 58 | std::string text_ {""}; 59 | std::string align_ {""}; 60 | std::string color_ {""}; 61 | bool bold_ {false}; 62 | bool underline_ {false}; 63 | std::size_t width_ {0}; 64 | std::size_t height_ {1}; 65 | OB::Color color_fg_ {"", OB::Color::Type::fg}; 66 | OB::Color color_bg_ {"", OB::Color::Type::bg}; 67 | std::string buf_; 68 | 69 | std::size_t term_width(bool const is_term) const; 70 | std::string repeat(std::size_t const len, std::string const& str) const; 71 | }; // class Hr 72 | 73 | } // namespace OB 74 | 75 | #endif // OB_HR_HH 76 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | #include "hr/hr.hh" 2 | 3 | #include "ob/parg.hh" 4 | #include "ob/color.hh" 5 | #include "ob/string.hh" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using Parg = OB::Parg; 15 | using Hr = OB::Hr; 16 | 17 | int program_options(Parg& pg); 18 | 19 | int program_options(Parg& pg) 20 | { 21 | pg.name("hr").version("0.5.0 (13.05.2019)"); 22 | 23 | pg.description("A horizontal rule for the terminal."); 24 | 25 | pg.usage("[--text|-t ] [--string|-s ] [--align|-a ] [--bold|-B] [--underline|-U] [--colour-fg|-f ] [--colour-bg|-b ] [--columns|-c ] [--rows|-r ] [--colour|-C ]"); 26 | pg.usage("[--help|-h]"); 27 | pg.usage("[--version|-v]"); 28 | pg.usage("[--license]"); 29 | 30 | pg.info("Colours", { 31 | "clear", 32 | "reverse", 33 | "0-255", 34 | "#000-#fff", 35 | "#000000-#ffffff", 36 | "black[-bright]", 37 | "red[-bright]", 38 | "green[-bright]", 39 | "yellow[-bright]", 40 | "blue[-bright]", 41 | "magenta[-bright]", 42 | "cyan[-bright]", 43 | "white[-bright]", 44 | }); 45 | 46 | pg.info("Examples", { 47 | "hr", 48 | "hr --help", 49 | "hr --version", 50 | "hr --license", 51 | "hr -s '-~' -f green", 52 | "hr -s 'octo ' -f white-bright -c 50 -B", 53 | "hr -s ' ' -b cyan-bright -r 2", 54 | "hr -t OCTOBANANA -s '*' -f magenta-bright", 55 | "hr -t OCTOBANANA -s ' ' -f '#f34b7d' -BU", 56 | "hr -t OCTOBANANA -s ' ' -f '#55ff00' -a left -BU", 57 | "hr -t OCTOBANANA -s ' ' -f '#55ff00' -a right -BU", 58 | "hr -s 'SUCCESS ' -f green-bright -b reverse", 59 | "hr -s 'ERROR ' -f '#1b1e24' -b red-bright -B", 60 | "hr -s 'WARNING ' -f '#1b1e24' -b yellow-bright", 61 | }); 62 | 63 | pg.info("Exit Codes", {"0 -> normal", "1 -> error"}); 64 | 65 | pg.info("Repository", { 66 | "https://github.com/octobanana/hr.git", 67 | }); 68 | 69 | pg.info("Homepage", { 70 | "https://octobanana.com/software/hr", 71 | }); 72 | 73 | pg.author("Brett Robinson (octobanana) "); 74 | 75 | // flags 76 | pg.set("help,h", "Print the programs help output."); 77 | pg.set("version,v", "Print the programs version."); 78 | pg.set("license", "Print the programs license."); 79 | 80 | pg.set("bold,B", "set the output text to use bold styling"); 81 | pg.set("underline,U", "set the output text to use underline styling"); 82 | 83 | // options 84 | pg.set("string,s", "-", "string", "set the output fill string"); 85 | pg.set("text,t", "", "string", "set the output text"); 86 | pg.set("align,a", "left", "left|center|right", "set the output text alignment"); 87 | pg.set("colour-fg,f", "", "colour", "set the output foreground colour"); 88 | pg.set("colour-bg,b", "", "Colour", "set the output background colour"); 89 | pg.set("rows,r", "1", "integer", "number of rows to print"); 90 | pg.set("columns,c", "0", "integer", "number of columns to print"); 91 | pg.set("colour,C", "auto", "auto|on|off", "set the output colour preference"); 92 | 93 | pg.set_stdin(); 94 | 95 | int status {pg.parse()}; 96 | 97 | if (status < 0) 98 | { 99 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 100 | std::cerr << "Error: " << pg.error() << "\n"; 101 | 102 | auto const similar_names = pg.similar(); 103 | if (similar_names.size() > 0) 104 | { 105 | std::cerr 106 | << "Did you mean:\n"; 107 | for (auto const& e : similar_names) 108 | { 109 | std::cerr 110 | << " --" << e << "\n"; 111 | } 112 | } 113 | 114 | return -1; 115 | } 116 | 117 | if (pg.get("help")) 118 | { 119 | std::cout << pg.help(); 120 | return 1; 121 | } 122 | 123 | if (pg.get("version")) 124 | { 125 | std::cout << pg.name() << " v" << pg.version() << "\n"; 126 | return 1; 127 | } 128 | 129 | if (pg.get("license")) 130 | { 131 | std::cout << R"(MIT License 132 | 133 | Copyright (c) 2018-2019 Brett Robinson 134 | 135 | Permission is hereby granted, free of charge, to any person obtaining a copy 136 | of this software and associated documentation files (the "Software"), to deal 137 | in the Software without restriction, including without limitation the rights 138 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 139 | copies of the Software, and to permit persons to whom the Software is 140 | furnished to do so, subject to the following conditions: 141 | 142 | The above copyright notice and this permission notice shall be included in all 143 | copies or substantial portions of the Software. 144 | 145 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 146 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 147 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 148 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 149 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 150 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 151 | SOFTWARE.)" << "\n"; 152 | 153 | return 1; 154 | } 155 | 156 | if (pg.find("colour") && ! OB::String::assert_rx(pg.get("colour"), 157 | std::regex("^auto|on|off$"))) 158 | { 159 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 160 | std::cerr << "Error: invalid colour preference '" << pg.get("colour") << "', value must be either 'auto', 'on', or 'off'\n"; 161 | 162 | return -1; 163 | } 164 | 165 | if (pg.find("align") && ! OB::String::assert_rx(pg.get("align"), 166 | std::regex("^left|center|right$"))) 167 | { 168 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 169 | std::cerr << "Error: invalid alignment '" << pg.get("align") << "', value must be either 'left', 'center', or 'right'\n"; 170 | 171 | return -1; 172 | } 173 | 174 | if (pg.find("colour-fg") && ! OB::Color::valid(pg.get("colour-fg"))) 175 | { 176 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 177 | std::cerr << "Error: invalid colour '" << pg.get("colour-fg") << "'\n"; 178 | 179 | return -1; 180 | } 181 | 182 | if (pg.find("colour-bg") && ! OB::Color::valid(pg.get("colour-bg"))) 183 | { 184 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 185 | std::cerr << "Error: invalid colour '" << pg.get("colour-bg") << "'\n"; 186 | 187 | return -1; 188 | } 189 | 190 | if (pg.find("columns") && 191 | (pg.get("columns") == 0 || pg.get("columns") > 1000)) 192 | { 193 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 194 | std::cerr << "Error: invalid number of columns '" << pg.get("columns") << "', value must be > 0 and <= 1000\n"; 195 | 196 | return -1; 197 | } 198 | 199 | if (pg.find("rows") && 200 | (pg.get("rows") == 0 || pg.get("rows") > 1000)) 201 | { 202 | std::cerr << "Usage:\n" << pg.usage() << "\n"; 203 | std::cerr << "Error: invalid number of rows '" << pg.get("rows") << "', value must be > 0 and <= 1000\n"; 204 | 205 | return -1; 206 | } 207 | 208 | return 0; 209 | } 210 | 211 | int main(int argc, char *argv[]) 212 | { 213 | Parg pg {argc, argv}; 214 | int pstatus {program_options(pg)}; 215 | if (pstatus > 0) return 0; 216 | if (pstatus < 0) return 1; 217 | 218 | try 219 | { 220 | Hr hr; 221 | hr.fill(OB::String::replace(pg.get("string"), "\n", "")); 222 | hr.text(OB::String::replace(pg.get("text") + pg.get_stdin(), "\n", "")); 223 | hr.align(pg.get("align")); 224 | hr.bold(pg.get("bold")); 225 | hr.underline(pg.get("underline")); 226 | hr.color(pg.get("colour")); 227 | hr.color_fg(pg.get("colour-fg")); 228 | hr.color_bg(pg.get("colour-bg")); 229 | hr.width(pg.get("columns")); 230 | hr.height(pg.get("rows")); 231 | hr.render(); 232 | 233 | std::cout << "\r" << hr.str(); 234 | } 235 | catch (std::exception const& e) 236 | { 237 | std::cerr << "Error: " << e.what() << "\n"; 238 | 239 | return 1; 240 | } 241 | 242 | return 0; 243 | } 244 | -------------------------------------------------------------------------------- /src/ob/color.hh: -------------------------------------------------------------------------------- 1 | #ifndef OB_COLOR_HH 2 | #define OB_COLOR_HH 3 | 4 | #include "ob/string.hh" 5 | #include "ob/term.hh" 6 | namespace aec = OB::Term::ANSI_Escape_Codes; 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace OB 21 | { 22 | 23 | class Color 24 | { 25 | inline static const std::unordered_map color_fg { 26 | {"black", "\x1b[30m"}, 27 | {"black-bright", "\x1b[90m"}, 28 | {"red", "\x1b[31m"}, 29 | {"red-bright", "\x1b[91m"}, 30 | {"green", "\x1b[32m"}, 31 | {"green-bright", "\x1b[92m"}, 32 | {"yellow", "\x1b[33m"}, 33 | {"yellow-bright", "\x1b[93m"}, 34 | {"blue", "\x1b[34m"}, 35 | {"blue-bright", "\x1b[94m"}, 36 | {"magenta", "\x1b[35m"}, 37 | {"magenta-bright", "\x1b[95m"}, 38 | {"cyan", "\x1b[36m"}, 39 | {"cyan-bright", "\x1b[96m"}, 40 | {"white", "\x1b[37m"}, 41 | {"white-bright", "\x1b[97m"}, 42 | }; 43 | 44 | inline static const std::unordered_map color_bg { 45 | {"black", "\x1b[40m"}, 46 | {"black-bright", "\x1b[100m"}, 47 | {"red", "\x1b[41m"}, 48 | {"red-bright", "\x1b[101m"}, 49 | {"green", "\x1b[42m"}, 50 | {"green-bright", "\x1b[102m"}, 51 | {"yellow", "\x1b[43m"}, 52 | {"yellow-bright", "\x1b[103m"}, 53 | {"blue", "\x1b[44m"}, 54 | {"blue-bright", "\x1b[104m"}, 55 | {"magenta", "\x1b[45m"}, 56 | {"magenta-bright", "\x1b[105m"}, 57 | {"cyan", "\x1b[46m"}, 58 | {"cyan-bright", "\x1b[106m"}, 59 | {"white", "\x1b[47m"}, 60 | {"white-bright", "\x1b[107m"}, 61 | }; 62 | 63 | public: 64 | 65 | struct Mode 66 | { 67 | enum Type 68 | { 69 | null = 0, 70 | rainbow, 71 | candy, 72 | party 73 | }; 74 | }; 75 | 76 | struct RGB 77 | { 78 | // range [0-255] 79 | double r {0}; 80 | double g {0}; 81 | double b {0}; 82 | }; 83 | 84 | struct HSL 85 | { 86 | // range [0-1] 87 | double h {0}; 88 | double s {0}; 89 | double l {0}; 90 | }; 91 | 92 | struct Type 93 | { 94 | enum value 95 | { 96 | bg = 0, 97 | fg 98 | }; 99 | }; 100 | 101 | Color() = default; 102 | 103 | Color(Type::value const fg) noexcept : 104 | _fg {static_cast(fg)} 105 | { 106 | } 107 | 108 | Color(std::string const& k, Type::value const fg = Type::value::fg) : 109 | _fg {static_cast(fg)} 110 | { 111 | key(k); 112 | } 113 | 114 | Color(Color&&) = default; 115 | Color(Color const&) = default; 116 | ~Color() = default; 117 | 118 | Color& operator=(Color&& rhs) = default; 119 | Color& operator=(Color const&) = default; 120 | 121 | Color& operator=(std::string const& k) 122 | { 123 | clear(); 124 | key(k); 125 | 126 | return *this; 127 | } 128 | 129 | operator bool() const 130 | { 131 | return _valid; 132 | } 133 | 134 | operator std::string() const 135 | { 136 | return _key; 137 | } 138 | 139 | friend std::ostream& operator<<(std::ostream& os, Color const& obj) 140 | { 141 | os << obj.value(); 142 | 143 | return os; 144 | } 145 | 146 | friend std::string& operator+=(std::string& str, Color const& obj) 147 | { 148 | return str += obj.value(); 149 | } 150 | 151 | bool is_valid() const 152 | { 153 | return _valid; 154 | } 155 | 156 | Color& clear() 157 | { 158 | _key = "clear"; 159 | _value.clear(); 160 | _hsl = HSL {50, 50, 50}; 161 | _valid = false; 162 | 163 | return *this; 164 | } 165 | 166 | Color& fg() 167 | { 168 | if (! _fg) 169 | { 170 | _fg = true; 171 | key(_key); 172 | } 173 | 174 | return *this; 175 | } 176 | 177 | Color& bg() 178 | { 179 | if (_fg) 180 | { 181 | _fg = false; 182 | key(_key); 183 | } 184 | 185 | return *this; 186 | } 187 | 188 | bool is_fg() const 189 | { 190 | return _fg; 191 | } 192 | 193 | bool is_bg() const 194 | { 195 | return ! _fg; 196 | } 197 | 198 | Mode::Type mode() const 199 | { 200 | return _mode; 201 | } 202 | 203 | Color& step() 204 | { 205 | switch(_mode) 206 | { 207 | case Mode::rainbow: 208 | { 209 | _hsl.h = _hsl.h < 100 ? _hsl.h + 0.2 : 0; 210 | _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : 211 | aec::bg_true(hsl_to_hex(_hsl)); 212 | 213 | break; 214 | } 215 | 216 | case Mode::candy: 217 | { 218 | _hsl.h = _hsl.h < 100 ? _hsl.h + 0.4 : 0; 219 | _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : 220 | aec::bg_true(hsl_to_hex(_hsl)); 221 | 222 | break; 223 | } 224 | 225 | case Mode::party: 226 | { 227 | _hsl.h = random(0, 100); 228 | _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : 229 | aec::bg_true(hsl_to_hex(_hsl)); 230 | 231 | break; 232 | } 233 | 234 | case Mode::null: 235 | default: 236 | { 237 | break; 238 | } 239 | } 240 | 241 | return *this; 242 | } 243 | 244 | double hue() const 245 | { 246 | return _hsl.h; 247 | } 248 | 249 | Color& hue(double const val) 250 | { 251 | if (val < 0) 252 | { 253 | _hsl.h = 100; 254 | } 255 | else if (val > 100) 256 | { 257 | _hsl.h = 0; 258 | } 259 | else 260 | { 261 | _hsl.h = val; 262 | } 263 | 264 | _key = hsl_to_hex(_hsl); 265 | _value = _fg ? aec::fg_true(_key) : aec::bg_true(_key); 266 | 267 | return *this; 268 | } 269 | 270 | double sat() const 271 | { 272 | return _hsl.s; 273 | } 274 | 275 | Color& sat(double const val) 276 | { 277 | if (val < 0) 278 | { 279 | _hsl.s = 100; 280 | } 281 | else if (val > 100) 282 | { 283 | _hsl.s = 0; 284 | } 285 | else 286 | { 287 | _hsl.s = val; 288 | } 289 | 290 | _key = hsl_to_hex(_hsl); 291 | _value = _fg ? aec::fg_true(_key) : aec::bg_true(_key); 292 | 293 | return *this; 294 | } 295 | 296 | double lum() const 297 | { 298 | return _hsl.l; 299 | } 300 | 301 | Color& lum(double const val) 302 | { 303 | if (val < 0) 304 | { 305 | _hsl.l = 100; 306 | } 307 | else if (val > 100) 308 | { 309 | _hsl.l = 0; 310 | } 311 | else 312 | { 313 | _hsl.l = val; 314 | } 315 | 316 | _key = hsl_to_hex(_hsl); 317 | _value = _fg ? aec::fg_true(_key) : aec::bg_true(_key); 318 | 319 | return *this; 320 | } 321 | 322 | std::string key() const 323 | { 324 | return _key; 325 | } 326 | 327 | bool key(std::string const& k) 328 | { 329 | if (! k.empty()) 330 | { 331 | if (k == "clear") 332 | { 333 | // clear 334 | _key = k; 335 | _value = ""; 336 | _valid = true; 337 | } 338 | else if (k == "reverse") 339 | { 340 | // reverse 341 | _key = k; 342 | _value = "\x1b[7m"; 343 | _valid = true; 344 | } 345 | else if (k == "rainbow" || k == "candy" || k == "party") 346 | { 347 | switch(k.at(0)) 348 | { 349 | case 'r': 350 | { 351 | _mode = Mode::rainbow; 352 | 353 | break; 354 | } 355 | 356 | case 'c': 357 | { 358 | _mode = Mode::candy; 359 | 360 | break; 361 | } 362 | 363 | case 'p': 364 | { 365 | _mode = Mode::party; 366 | 367 | break; 368 | } 369 | 370 | default: 371 | { 372 | break; 373 | } 374 | } 375 | 376 | _key = k; 377 | _hsl.h = random(0, 100); 378 | _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : 379 | aec::bg_true(hsl_to_hex(_hsl)); 380 | _valid = true; 381 | } 382 | else 383 | { 384 | // 21-bit color 385 | if (k.at(0) == '#' && OB::String::assert_rx(k, 386 | std::regex("^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$"))) 387 | { 388 | _key = k; 389 | _value = _fg ? aec::fg_true(k) : aec::bg_true(k); 390 | _hsl = hex_to_hsl(k); 391 | _valid = true; 392 | } 393 | 394 | // 8-bit color 395 | else if (OB::String::assert_rx(k, std::regex("^[0-9]{1,3}$")) && 396 | std::stoi(k) >= 0 && std::stoi(k) <= 255) 397 | { 398 | _key = k; 399 | _value = _fg ? aec::fg_256(k) : aec::bg_256(k); 400 | _valid = true; 401 | } 402 | 403 | // 4-bit color 404 | else if (color_fg.find(k) != color_fg.end()) 405 | { 406 | _key = k; 407 | 408 | if (_fg) 409 | { 410 | _value = color_fg.at(k); 411 | } 412 | else 413 | { 414 | _value = color_bg.at(k); 415 | } 416 | 417 | _valid = true; 418 | } 419 | } 420 | } 421 | 422 | return _valid; 423 | } 424 | 425 | std::string value() const 426 | { 427 | return _value; 428 | } 429 | 430 | static bool valid(std::string const& k) 431 | { 432 | bool valid {false}; 433 | 434 | if (! k.empty()) 435 | { 436 | if (k == "clear") 437 | { 438 | valid = true; 439 | } 440 | else if (k == "reverse") 441 | { 442 | valid = true; 443 | } 444 | else if (k == "rainbow" || k == "candy" || k == "party") 445 | { 446 | valid = true; 447 | } 448 | else 449 | { 450 | // 24-bit color 451 | if (k.at(0) == '#' && OB::String::assert_rx(k, 452 | std::regex("^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$"))) 453 | { 454 | valid = true; 455 | } 456 | 457 | // 8-bit color 458 | else if (OB::String::assert_rx(k, std::regex("^[0-9]{1,3}$")) && 459 | std::stoi(k) >= 0 && std::stoi(k) <= 255) 460 | { 461 | valid = true; 462 | } 463 | 464 | // 4-bit color 465 | else if (color_fg.find(k) != color_fg.end()) 466 | { 467 | valid = true; 468 | } 469 | } 470 | } 471 | 472 | return valid; 473 | } 474 | 475 | private: 476 | 477 | HSL hex_to_hsl(std::string const& hex) const 478 | { 479 | return rgb_to_hsl(hex_to_rgb(hex)); 480 | } 481 | 482 | std::string hsl_to_hex(HSL hsl) const 483 | { 484 | return rgb_to_hex(hsl_to_rgb(hsl)); 485 | } 486 | 487 | std::string hex_encode(char const c) const 488 | { 489 | char s[3]; 490 | 491 | if (c & 0x80) 492 | { 493 | std::snprintf(&s[0], 3, "%02x", 494 | static_cast(c & 0xff) 495 | ); 496 | } 497 | else 498 | { 499 | std::snprintf(&s[0], 3, "%02x", 500 | static_cast(c) 501 | ); 502 | } 503 | 504 | return std::string(s); 505 | } 506 | 507 | std::size_t hex_decode(std::string const& str_) const 508 | { 509 | std::stringstream ss; 510 | ss << str_; 511 | std::size_t val; 512 | ss >> std::hex >> val; 513 | 514 | return val; 515 | } 516 | 517 | bool valid_hstr(std::string& str) const 518 | { 519 | std::smatch m; 520 | std::regex rx {"^#?((?:[0-9a-fA-F]{3}){1,2})$"}; 521 | 522 | if (std::regex_match(str, m, rx, std::regex_constants::match_not_null)) 523 | { 524 | std::string hstr {m[1]}; 525 | 526 | if (hstr.size() == 3) 527 | { 528 | std::stringstream ss; 529 | ss << hstr[0] << hstr[0] << hstr[1] << hstr[1] << hstr[2] << hstr[2]; 530 | hstr = ss.str(); 531 | } 532 | 533 | str = hstr; 534 | 535 | return true; 536 | } 537 | 538 | return false; 539 | } 540 | 541 | RGB hex_to_rgb(std::string str) const 542 | { 543 | if (! valid_hstr(str)) 544 | { 545 | return {}; 546 | } 547 | 548 | return RGB {static_cast(hex_decode(str.substr(0, 2))), static_cast(hex_decode(str.substr(2, 2))), static_cast(hex_decode(str.substr(4, 2)))}; 549 | } 550 | 551 | std::string rgb_to_hex(RGB rgb) const 552 | { 553 | std::ostringstream os; os 554 | << "#" 555 | << hex_encode(static_cast(rgb.r)) 556 | << hex_encode(static_cast(rgb.g)) 557 | << hex_encode(static_cast(rgb.b)); 558 | 559 | return os.str(); 560 | } 561 | 562 | double hue_to_rgb(double j, double i, double h) const 563 | { 564 | double r; 565 | 566 | if (h < 0) 567 | { 568 | h += 1; 569 | } 570 | 571 | if (h > 1) 572 | { 573 | h -= 1; 574 | } 575 | 576 | if (h < 1 / 6.0) 577 | { 578 | r = j + (i - j) * 6 * h; 579 | } 580 | else if (h < 1 / 2.0) 581 | { 582 | r = i; 583 | } 584 | else if (h < 2 / 3.0) 585 | { 586 | r = j + (i - j) * (2 / 3.0 - h) * 6; 587 | } 588 | else 589 | { 590 | r = j; 591 | } 592 | 593 | return r; 594 | } 595 | 596 | RGB hsl_to_rgb(HSL hsl) const 597 | { 598 | RGB rgb; 599 | 600 | hsl.h /= 100; 601 | hsl.s /= 100; 602 | hsl.l /= 100; 603 | 604 | if (hsl.s == 0) 605 | { 606 | rgb.r = hsl.l; 607 | rgb.g = hsl.l; 608 | rgb.b = hsl.l; 609 | } 610 | else 611 | { 612 | double const i {(hsl.l < 0.5) ? (hsl.l * (1 + hsl.s)) : ((hsl.l + hsl.s) - (hsl.l * hsl.s))}; 613 | double const j {2 * hsl.l - i}; 614 | 615 | rgb.r = hue_to_rgb(j, i, (hsl.h + 1 / 3.0)); 616 | rgb.g = hue_to_rgb(j, i, hsl.h); 617 | rgb.b = hue_to_rgb(j, i, (hsl.h - 1 / 3.0)); 618 | } 619 | 620 | rgb.r *= 255; 621 | rgb.g *= 255; 622 | rgb.b *= 255; 623 | 624 | return rgb; 625 | } 626 | 627 | HSL rgb_to_hsl(RGB rgb) const 628 | { 629 | rgb.r /= 255; 630 | rgb.g /= 255; 631 | rgb.b /= 255; 632 | 633 | auto const min = std::min(rgb.r, std::min(rgb.g, rgb.b)); 634 | auto const max = std::max(rgb.r, std::max(rgb.g, rgb.b)); 635 | 636 | double const init {static_cast(max + min) / 2}; 637 | HSL hsl {init, init, init}; 638 | 639 | if (min == max) 640 | { 641 | hsl.h = 0; 642 | hsl.s = 0; 643 | } 644 | else 645 | { 646 | double const v {static_cast(max - min)}; 647 | hsl.s = (hsl.l > 0.5) ? v / (2 - static_cast(max - min)) : v / static_cast(max + min); 648 | 649 | if (max == rgb.r) 650 | { 651 | hsl.h = (rgb.g - rgb.b) / v + (rgb.g < rgb.b ? 6 : 0); 652 | } 653 | else if (max == rgb.g) 654 | { 655 | hsl.h = (rgb.b - rgb.r) / v + 2; 656 | } 657 | else 658 | { 659 | hsl.h = (rgb.r - rgb.g) / v + 4; 660 | } 661 | 662 | hsl.h /= 6; 663 | } 664 | 665 | hsl.h *= 100; 666 | hsl.s *= 100; 667 | hsl.l *= 100; 668 | 669 | return hsl; 670 | } 671 | 672 | std::size_t random(std::size_t min, std::size_t max) const 673 | { 674 | std::random_device rd; 675 | std::mt19937 gen(rd()); 676 | std::uniform_int_distribution<> distr(min, max); 677 | 678 | return static_cast(distr(gen)); 679 | } 680 | 681 | bool _valid {false}; 682 | bool _fg {true}; 683 | std::string _key {"clear"}; 684 | std::string _value; 685 | 686 | bool _rainbow; 687 | Mode::Type _mode {Mode::null}; 688 | HSL _hsl {50, 50, 50}; 689 | }; // Color 690 | 691 | } // namespace OB 692 | 693 | #endif // OB_COLOR_HH 694 | -------------------------------------------------------------------------------- /src/ob/parg.hh: -------------------------------------------------------------------------------- 1 | // 2 | // MIT License 3 | // 4 | // Copyright (c) 2018-2019 Brett Robinson 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | 25 | #ifndef OB_PARG_HH 26 | #define OB_PARG_HH 27 | 28 | #include "ob/string.hh" 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | namespace OB 49 | { 50 | 51 | class Parg 52 | { 53 | public: 54 | 55 | Parg() 56 | { 57 | } 58 | 59 | Parg(int _argc, char** _argv) 60 | { 61 | argc_ = _argc; 62 | argvf(_argv); 63 | } 64 | 65 | Parg& name(std::string const& _name) 66 | { 67 | name_ = _name; 68 | return *this; 69 | } 70 | 71 | std::string name() const 72 | { 73 | return name_; 74 | } 75 | 76 | Parg& version(std::string const& _version) 77 | { 78 | version_ = _version; 79 | return *this; 80 | } 81 | 82 | std::string version() const 83 | { 84 | return version_; 85 | } 86 | 87 | Parg& usage(std::string const& _usage) 88 | { 89 | usage_ += " " + name_ + " " + _usage + "\n"; 90 | return *this; 91 | } 92 | 93 | std::string usage() const 94 | { 95 | return usage_; 96 | } 97 | 98 | Parg& description(std::string const& _description) 99 | { 100 | description_ = _description; 101 | return *this; 102 | } 103 | 104 | std::string description() const 105 | { 106 | return description_; 107 | } 108 | 109 | Parg& info(std::string const& _title, std::vector const& _text) 110 | { 111 | info_.emplace_back(info_pair{_title, _text}); 112 | return *this; 113 | } 114 | 115 | Parg& author(std::string const& _author) 116 | { 117 | author_ = _author; 118 | return *this; 119 | } 120 | 121 | std::string author() const 122 | { 123 | return author_; 124 | } 125 | 126 | std::string help() const 127 | { 128 | std::stringstream out; 129 | if (! description_.empty()) 130 | { 131 | out << name_ << ":" << "\n"; 132 | std::stringstream ss; 133 | out << " " << description_ << "\n"; 134 | out << ss.str() << "\n"; 135 | } 136 | 137 | if (! usage_.empty()) 138 | { 139 | out << "Usage: " << "\n" 140 | << usage_ << "\n"; 141 | } 142 | 143 | if (! modes_.empty()) 144 | { 145 | out << "Flags: " << "\n" 146 | << modes_; 147 | } 148 | 149 | if (! options_.empty()) 150 | { 151 | out << "\nOptions: " << "\n" 152 | << options_; 153 | } 154 | 155 | if (! info_.empty()) 156 | { 157 | for (auto const& e : info_) 158 | { 159 | out << "\n" << e.title << ":" << "\n"; 160 | 161 | for (auto const& t : e.text) 162 | { 163 | out << " " << t << "\n"; 164 | } 165 | } 166 | } 167 | 168 | if (! author_.empty()) 169 | { 170 | out << "\nAuthor: " << "\n"; 171 | std::stringstream ss; 172 | ss << " " << author_ << "\n"; 173 | out << ss.str(); 174 | } 175 | 176 | return out.str(); 177 | } 178 | 179 | int parse() 180 | { 181 | if (is_stdin_) 182 | { 183 | pipe_stdin(); 184 | } 185 | status_ = parse_args(argc_, argv_); 186 | return status_; 187 | } 188 | 189 | int parse(int argc, char** argv) 190 | { 191 | if (is_stdin_) 192 | { 193 | pipe_stdin(); 194 | } 195 | argc_ = argc; 196 | argvf(argv); 197 | status_ = parse_args(argc_, argv_); 198 | return status_; 199 | } 200 | 201 | int parse(std::string const& str) 202 | { 203 | auto args = str_to_args(str); 204 | status_ = parse_args(args.size(), args); 205 | return status_; 206 | } 207 | 208 | std::vector str_to_args(std::string const& str) 209 | { 210 | std::vector args; 211 | 212 | std::string const backslash {"\\"}; 213 | 214 | // parse str into arg vector as if it was parsed by the shell 215 | for (std::size_t i = 0; i < str.size(); ++i) 216 | { 217 | std::string e {str.at(i)}; 218 | 219 | // default 220 | if (e.find_first_not_of(" \n\t\"'") != std::string::npos) 221 | { 222 | bool escaped {false}; 223 | args.emplace_back(""); 224 | for (;i < str.size(); ++i) 225 | { 226 | e = str.at(i); 227 | if (! escaped && e.find_first_of(" \n\t") != std::string::npos) 228 | { 229 | --i; // put back unmatched char 230 | break; 231 | } 232 | else if (e == backslash) 233 | { 234 | escaped = true; 235 | } 236 | else if (escaped) 237 | { 238 | args.back() += e; 239 | escaped = false; 240 | } 241 | else 242 | { 243 | args.back() += e; 244 | } 245 | } 246 | continue; 247 | } 248 | 249 | // whitespace 250 | else if (e.find_first_of(" \n\t") != std::string::npos) 251 | { 252 | for (;i < str.size(); ++i) 253 | { 254 | e = str.at(i); 255 | if (e.find_first_not_of(" \n\t") != std::string::npos) 256 | { 257 | --i; // put back unmatched char 258 | break; 259 | } 260 | } 261 | continue; 262 | } 263 | 264 | // string 265 | else if (e.find_first_of("\"'") != std::string::npos) 266 | { 267 | std::string quote {e}; 268 | bool escaped {false}; 269 | ++i; // skip start quote 270 | args.emplace_back(""); 271 | for (;i < str.size(); ++i) 272 | { 273 | e = str.at(i); 274 | if (! escaped && e == quote) 275 | { 276 | break; 277 | // skip end quote 278 | } 279 | else if (e == backslash) 280 | { 281 | escaped = true; 282 | } 283 | else if (escaped) 284 | { 285 | args.back() += e; 286 | escaped = false; 287 | } 288 | else 289 | { 290 | args.back() += e; 291 | } 292 | } 293 | } 294 | } 295 | return args; 296 | } 297 | 298 | void set(std::string const& _name, std::string const& _info) 299 | { 300 | // sets a flag 301 | std::string delim {","}; 302 | if (_name.find(delim) != std::string::npos) 303 | { 304 | // short and long 305 | bool has_short {false}; 306 | std::string _long; 307 | std::string _short; 308 | 309 | auto const names = delimit(_name, delim); 310 | assert(names.size() >= 1 && names.size() <= 2); 311 | _long = names.at(0); 312 | 313 | if (names.size() == 2) has_short = true; 314 | if (has_short) 315 | { 316 | // short name must be one char 317 | assert(names.at(1).size() == 1); 318 | _short = names.at(1); 319 | } 320 | 321 | flags_[_short] = _long; 322 | data_[_long].long_ = _long; 323 | data_[_long].short_ = _short; 324 | data_[_long].mode_ = true; 325 | data_[_long].value_ = "0"; 326 | modes_.append(" -" + _short + ", --" + _long + "\n"); 327 | } 328 | else 329 | { 330 | if (_name.size() == 1) 331 | { 332 | // short 333 | flags_[_name] = _name; 334 | data_[_name].long_ = _name; 335 | data_[_name].short_ = _name; 336 | data_[_name].mode_ = true; 337 | data_[_name].value_ = "0"; 338 | modes_.append(" -" + _name + "\n"); 339 | } 340 | else 341 | { 342 | // long 343 | data_[_name].long_ = _name; 344 | data_[_name].mode_ = true; 345 | data_[_name].value_ = "0"; 346 | modes_.append(" --" + _name + "\n"); 347 | } 348 | } 349 | 350 | std::stringstream out; 351 | out << " " << _info << "\n"; 352 | modes_.append(out.str()); 353 | } 354 | 355 | void set(std::string const& _name, std::string const& _default, 356 | std::string const& _arg, std::string const& _info) 357 | { 358 | // sets an option 359 | std::string delim {","}; 360 | if (_name.find(delim) != std::string::npos) 361 | { 362 | bool has_short {false}; 363 | std::string _long; 364 | std::string _short; 365 | 366 | auto const names = delimit(_name, delim); 367 | assert(names.size() >= 1 && names.size() <= 2); 368 | _long = names.at(0); 369 | 370 | if (names.size() == 2) has_short = true; 371 | if (has_short) 372 | { 373 | // short name must be one char 374 | assert(names.at(1).size() == 1); 375 | _short = names.at(1); 376 | } 377 | 378 | flags_[_short] = _long; 379 | data_[_long].long_ = _long; 380 | data_[_long].short_ = _short; 381 | data_[_long].mode_ = false; 382 | data_[_long].value_ = _default; 383 | options_.append(" -" + _short + ", --" + _long + "=<" + _arg + ">\n"); 384 | } 385 | else 386 | { 387 | if (_name.size() == 1) 388 | { 389 | // short 390 | flags_[_name] = _name; 391 | data_[_name].long_ = _name; 392 | data_[_name].short_ = _name; 393 | data_[_name].mode_ = false; 394 | data_[_name].value_ = _default; 395 | options_.append(" -" + _name + "=<" + _arg + ">\n"); 396 | } 397 | else 398 | { 399 | // long 400 | data_[_name].long_ = _name; 401 | data_[_name].mode_ = false; 402 | data_[_name].value_ = _default; 403 | options_.append(" --" + _name + "=<" + _arg + ">\n"); 404 | } 405 | } 406 | 407 | std::stringstream out; 408 | out << " " << _info << "\n"; 409 | options_.append(out.str()); 410 | } 411 | 412 | template || 415 | std::is_same_v, 416 | int> = 0> 417 | T get(std::string const& _key) 418 | { 419 | if (data_.find(_key) == data_.end()) 420 | { 421 | throw std::logic_error("parg get '" + _key + "' is not defined"); 422 | } 423 | return data_[_key].value_; 424 | } 425 | 426 | template, 429 | int> = 0> 430 | T get(std::string const& _key) 431 | { 432 | if (data_.find(_key) == data_.end()) 433 | { 434 | throw std::logic_error("parg get '" + _key + "' is not defined"); 435 | } 436 | std::stringstream ss; 437 | ss << data_[_key].value_; 438 | T val; 439 | ss >> val; 440 | return val; 441 | } 442 | 443 | bool find(std::string const& _key) const 444 | { 445 | // key must exist 446 | if (data_.find(_key) == data_.end()) return false; 447 | return data_.at(_key).seen_; 448 | } 449 | 450 | Parg& set_pos(bool const _positional = true) 451 | { 452 | is_positional_ = _positional; 453 | return *this; 454 | } 455 | 456 | std::string get_pos() const 457 | { 458 | std::string str; 459 | if (positional_vec_.empty()) 460 | { 461 | return str; 462 | } 463 | for (auto const& e : positional_vec_) 464 | { 465 | str += e + " "; 466 | } 467 | str.pop_back(); 468 | return str; 469 | } 470 | 471 | std::vector get_pos_vec() const 472 | { 473 | return positional_vec_; 474 | } 475 | 476 | Parg& set_stdin(bool const _stdin = true) 477 | { 478 | is_stdin_ = _stdin; 479 | return *this; 480 | } 481 | 482 | std::string get_stdin() const 483 | { 484 | return stdin_; 485 | } 486 | 487 | int status() const 488 | { 489 | return status_; 490 | } 491 | 492 | std::string error() const 493 | { 494 | return error_; 495 | } 496 | 497 | std::vector const& similar() const 498 | { 499 | return similar_; 500 | } 501 | 502 | std::size_t flags_found() const 503 | { 504 | std::size_t count {0}; 505 | 506 | for (auto const& e : data_) 507 | { 508 | if (e.second.mode_ && e.second.seen_) 509 | { 510 | ++count; 511 | } 512 | } 513 | 514 | return count; 515 | } 516 | 517 | std::size_t options_found() const 518 | { 519 | std::size_t count {0}; 520 | 521 | for (auto const& e : data_) 522 | { 523 | if (! e.second.mode_ && e.second.seen_) 524 | { 525 | ++count; 526 | } 527 | } 528 | 529 | return count; 530 | } 531 | 532 | struct Option 533 | { 534 | std::string short_; 535 | std::string long_; 536 | bool mode_; 537 | std::string value_; 538 | bool seen_ {false}; 539 | }; 540 | 541 | struct info_pair 542 | { 543 | std::string title; 544 | std::vector text; 545 | }; 546 | 547 | private: 548 | 549 | int argc_ {0}; 550 | std::vector argv_; 551 | std::string name_; 552 | std::string version_; 553 | std::string usage_; 554 | std::string description_; 555 | std::string modes_; 556 | std::string options_; 557 | int options_indent_ {0}; 558 | std::vector info_; 559 | std::string author_; 560 | std::map data_; 561 | std::map flags_; 562 | bool is_positional_ {false}; 563 | std::string positional_; 564 | std::vector positional_vec_; 565 | std::string stdin_; 566 | bool is_stdin_ {false}; 567 | int status_ {0}; 568 | std::string error_; 569 | std::vector similar_; 570 | 571 | void argvf(char** _argv) 572 | { 573 | // removes first arg 574 | if (argc_ < 1) return; 575 | for (int i = 1; i < argc_; ++i) 576 | { 577 | argv_.emplace_back(_argv[i]); 578 | } 579 | // std::cerr << "argv: " << i << " -> " << argv_.at(i) << std::endl; 580 | --argc_; 581 | } 582 | 583 | int pipe_stdin() 584 | { 585 | if (! isatty(STDIN_FILENO)) 586 | { 587 | stdin_.assign((std::istreambuf_iterator(std::cin)), 588 | (std::istreambuf_iterator())); 589 | return 0; 590 | } 591 | stdin_ = ""; 592 | return -1; 593 | } 594 | 595 | std::vector delimit(std::string const& str, std::string const& delim) const 596 | { 597 | std::vector vtok; 598 | std::size_t start {0}; 599 | std::size_t end = str.find(delim); 600 | while (end != std::string::npos) { 601 | vtok.emplace_back(str.substr(start, end - start)); 602 | start = end + delim.length(); 603 | end = str.find(delim, start); 604 | } 605 | vtok.emplace_back(str.substr(start, end)); 606 | return vtok; 607 | } 608 | 609 | int parse_args(int _argc, std::vector const& _argv) 610 | { 611 | if (_argc < 1) return 1; 612 | 613 | bool dashdash {false}; 614 | 615 | // loop through arg vector 616 | for (int i = 0; i < _argc; ++i) 617 | { 618 | std::string const& tmp {_argv.at(static_cast(i))}; 619 | // std::cerr << "ARG: " << i << " -> " << tmp << std::endl; 620 | 621 | if (dashdash) 622 | { 623 | positional_vec_.emplace_back(tmp); 624 | continue; 625 | } 626 | 627 | if (tmp.size() > 1 && tmp.at(0) == '-' && tmp.at(1) != '-') 628 | { 629 | // short 630 | // std::cerr << "SHORT: " << tmp << std::endl; 631 | 632 | std::string c {tmp.at(1)}; 633 | if (flags_.find(c) != flags_.end() && !(data_.at(flags_.at(c)).mode_)) 634 | { 635 | // short arg 636 | // std::cerr << "SHORT: arg -> " << c << std::endl; 637 | 638 | if (data_.at(flags_.at(c)).seen_) 639 | { 640 | // error 641 | error_ = "flag '-" + c + "' has already been seen"; 642 | return -1; 643 | } 644 | 645 | if (tmp.size() > 2 && tmp.at(2) != '=') 646 | { 647 | data_.at(flags_.at(c)).value_ = tmp.substr(2, tmp.size() - 1); 648 | data_.at(flags_.at(c)).seen_ = true; 649 | } 650 | else if (tmp.size() > 3 && tmp.at(2) == '=') 651 | { 652 | data_.at(flags_.at(c)).value_ = tmp.substr(3, tmp.size() - 1); 653 | data_.at(flags_.at(c)).seen_ = true; 654 | } 655 | else if (i + 1 < _argc) 656 | { 657 | data_.at(flags_.at(c)).value_ = _argv.at(static_cast(i + 1)); 658 | data_.at(flags_.at(c)).seen_ = true; 659 | ++i; 660 | } 661 | else 662 | { 663 | // error 664 | error_ = "flag '-" + c + "' requires an arg"; 665 | return -1; 666 | } 667 | } 668 | else 669 | { 670 | // short mode 671 | for (std::size_t j = 1; j < tmp.size(); ++j) 672 | { 673 | std::string s {tmp.at(j)}; 674 | 675 | if (flags_.find(s) != flags_.end() && data_.at(flags_.at(s)).mode_) 676 | { 677 | // std::cerr << "SHORT: mode -> " << s << std::endl; 678 | 679 | if (data_.at(flags_.at(s)).seen_) 680 | { 681 | // error 682 | error_ = "flag '-" + s + "' has already been seen"; 683 | return -1; 684 | } 685 | 686 | data_.at(flags_.at(s)).value_ = "1"; 687 | data_.at(flags_.at(s)).seen_ = true; 688 | } 689 | else 690 | { 691 | // error 692 | error_ = "invalid flag '" + tmp + "'"; 693 | // find_similar(s); 694 | 695 | return -1; 696 | } 697 | } 698 | } 699 | } 700 | else if (tmp.size() > 2 && tmp.at(0) == '-' && tmp.at(1) == '-') 701 | { 702 | // long || -- 703 | // std::cerr << "LONG: " << tmp << std::endl; 704 | std::string c {tmp.substr(2, tmp.size() - 1)}; 705 | std::string a; 706 | 707 | auto const delim = c.find("="); 708 | if (delim != std::string::npos) 709 | { 710 | c = tmp.substr(2, delim); 711 | a = tmp.substr(3 + delim, tmp.size() - 1); 712 | } 713 | 714 | if (data_.find(c) != data_.end()) 715 | { 716 | if (data_.at(c).seen_) 717 | { 718 | // error 719 | error_ = "option '--" + c + "' has already been seen"; 720 | return -1; 721 | } 722 | 723 | if (data_.at(c).mode_ && a.size() == 0) 724 | { 725 | // std::cerr << "LONG: mode -> " << c << std::endl; 726 | data_.at(c).value_ = "1"; 727 | data_.at(c).seen_ = true; 728 | } 729 | else 730 | { 731 | // std::cerr << "LONG: arg -> " << c << std::endl; 732 | if (a.size() > 0) 733 | { 734 | data_.at(c).value_ = a; 735 | data_.at(c).seen_ = true; 736 | } 737 | else if (i + 1 < _argc) 738 | { 739 | data_.at(c).value_ = _argv.at(static_cast(i + 1)); 740 | data_.at(c).seen_ = true; 741 | ++i; 742 | } 743 | else 744 | { 745 | // error 746 | error_ = "option '--" + c + "' requires an arg"; 747 | return -1; 748 | } 749 | } 750 | } 751 | else 752 | { 753 | // error 754 | error_ = "invalid option '" + tmp + "'"; 755 | find_similar(c); 756 | return -1; 757 | } 758 | } 759 | else if (tmp.size() > 0 && is_positional_) 760 | { 761 | // positional 762 | // std::cerr << "POS: " << tmp << std::endl; 763 | if (tmp == "--") 764 | { 765 | dashdash = true; 766 | } 767 | else 768 | { 769 | positional_vec_.emplace_back(tmp); 770 | } 771 | } 772 | else 773 | { 774 | // error 775 | error_ = "no match for '" + tmp + "'"; 776 | find_similar(tmp); 777 | return -1; 778 | } 779 | } 780 | 781 | return 0; 782 | } 783 | 784 | void find_similar(std::string const& name) 785 | { 786 | int const weight_max {8}; 787 | std::vector> dist; 788 | 789 | for (auto const& [key, val] : data_) 790 | { 791 | int weight {0}; 792 | 793 | if (OB::String::starts_with(val.long_, name)) 794 | { 795 | weight = 0; 796 | } 797 | else 798 | { 799 | weight = OB::String::damerau_levenshtein(name, val.long_, 1, 2, 3, 0); 800 | } 801 | 802 | if (weight < weight_max) 803 | { 804 | dist.emplace_back(weight, val.long_); 805 | } 806 | } 807 | 808 | std::sort(dist.begin(), dist.end(), 809 | [](auto const& lhs, auto const& rhs) 810 | { 811 | return (lhs.first == rhs.first) ? 812 | (lhs.second.size() < rhs.second.size()) : 813 | (lhs.first < rhs.first); 814 | }); 815 | 816 | for (auto const& [key, val] : dist) 817 | { 818 | similar_.emplace_back(val); 819 | } 820 | 821 | size_t const similar_max {3}; 822 | if (similar_.size() > similar_max) 823 | { 824 | similar_.erase(similar_.begin() + similar_max, similar_.end()); 825 | } 826 | } 827 | 828 | }; // class Parg 829 | 830 | } // namespace OB 831 | 832 | #endif // OB_PARG_HH 833 | -------------------------------------------------------------------------------- /src/ob/string.cc: -------------------------------------------------------------------------------- 1 | #include "ob/string.hh" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace OB::String 15 | { 16 | 17 | std::string replace(std::string str, std::string const& key, std::string const& val) 18 | { 19 | std::size_t pos {0}; 20 | 21 | for (;;) 22 | { 23 | pos = str.find(key, pos); 24 | 25 | if (pos == std::string::npos) 26 | { 27 | break; 28 | } 29 | 30 | str.replace(pos, key.size(), val); 31 | pos += val.size(); 32 | } 33 | 34 | return str; 35 | } 36 | 37 | bool starts_with(std::string const& str, std::string const& val) 38 | { 39 | if (str.empty() || str.size() < val.size()) 40 | { 41 | return false; 42 | } 43 | 44 | if (str.compare(0, val.size(), val) == 0) 45 | { 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | bool assert_rx(std::string const& str, std::regex rx) 53 | { 54 | std::smatch m; 55 | 56 | if (std::regex_match(str, m, rx, std::regex_constants::match_not_null)) 57 | { 58 | return true; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | std::string repeat(std::size_t const num, std::string const& str) 65 | { 66 | if (num == 0) 67 | { 68 | return {}; 69 | } 70 | 71 | if (num == 1) 72 | { 73 | return str; 74 | } 75 | 76 | std::string res; 77 | res.reserve(str.size() * num); 78 | 79 | for (std::size_t i {0}; i < num; ++i) 80 | { 81 | res += str; 82 | } 83 | 84 | return res; 85 | } 86 | 87 | std::size_t damerau_levenshtein(std::string const& lhs, std::string const& rhs, 88 | std::size_t const weight_insert, std::size_t const weight_substitute, 89 | std::size_t const weight_delete, std::size_t const weight_transpose) 90 | { 91 | if (lhs == rhs) 92 | { 93 | return 0; 94 | } 95 | 96 | std::string_view lhsv {lhs}; 97 | std::string_view rhsv {rhs}; 98 | 99 | bool swapped {false}; 100 | if (lhsv.size() > rhsv.size()) 101 | { 102 | swapped = true; 103 | std::swap(lhsv, rhsv); 104 | } 105 | 106 | for (std::size_t i = 0; i < lhsv.size(); ++i) 107 | { 108 | if (lhsv.at(i) != rhsv.at(i)) 109 | { 110 | if (i) 111 | { 112 | lhsv.substr(i); 113 | rhsv.substr(i); 114 | } 115 | 116 | break; 117 | } 118 | } 119 | 120 | for (std::size_t i = 0; i < lhsv.size(); ++i) 121 | { 122 | if (lhsv.at(lhsv.size() - 1 - i) != rhsv.at(rhsv.size() - 1 - i)) 123 | { 124 | if (i) 125 | { 126 | lhsv.substr(0, lhsv.size() - 1 - i); 127 | rhsv.substr(0, rhsv.size() - 1 - i); 128 | } 129 | 130 | break; 131 | } 132 | } 133 | 134 | if (swapped) 135 | { 136 | std::swap(lhsv, rhsv); 137 | } 138 | 139 | if (lhsv.empty()) 140 | { 141 | return rhsv.size() * weight_insert; 142 | } 143 | 144 | if (rhsv.empty()) 145 | { 146 | return lhsv.size() * weight_delete; 147 | } 148 | 149 | std::vector v0 (rhsv.size() + 1, 0); 150 | std::vector v1 (rhsv.size() + 1, 0); 151 | std::vector v2 (rhsv.size() + 1, 0); 152 | 153 | for (std::size_t i = 0; i <= rhsv.size(); ++i) 154 | { 155 | v1.at(i) = i * weight_insert; 156 | } 157 | 158 | for (std::size_t i = 0; i < lhsv.size(); ++i) 159 | { 160 | v2.at(0) = (i + 1) * weight_delete; 161 | for (std::size_t j = 0; j < rhsv.size(); j++) 162 | { 163 | v2.at(j + 1) = std::min( 164 | // deletion 165 | v1.at(j + 1) + weight_delete, 166 | std::min( 167 | // insertion 168 | v2.at(j) + weight_insert, 169 | // substitution 170 | v1.at(j) + (weight_substitute * (lhsv.at(i) != rhsv.at(j))))); 171 | 172 | if (i > 0 && j > 0 && 173 | (lhsv.at(i - 1) == rhsv.at(j)) && 174 | (lhsv.at(i) == rhsv.at(j - 1))) 175 | { 176 | v2.at(j + 1) = std::min( 177 | v0.at(j + 1), 178 | // transposition 179 | v0.at(j - 1) + weight_transpose); 180 | } 181 | } 182 | 183 | std::swap(v0, v1); 184 | std::swap(v1, v2); 185 | } 186 | 187 | return v1.at(rhsv.size()); 188 | } 189 | 190 | } // namespace OB::String 191 | -------------------------------------------------------------------------------- /src/ob/string.hh: -------------------------------------------------------------------------------- 1 | #ifndef OB_STRING_HH 2 | #define OB_STRING_HH 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace OB::String 11 | { 12 | 13 | std::string replace(std::string str, std::string const& key, std::string const& val); 14 | 15 | bool starts_with(std::string const& str, std::string const& val); 16 | 17 | bool assert_rx(std::string const& str, std::regex rx); 18 | 19 | std::string repeat(std::size_t const num, std::string const& str); 20 | 21 | std::size_t damerau_levenshtein(std::string const& lhs, std::string const& rhs, 22 | std::size_t const weight_insert = 1, std::size_t const weight_substitute = 1, 23 | std::size_t const weight_delete = 1, std::size_t const weight_transpose = 1); 24 | 25 | } // namespace OB::String 26 | 27 | #endif // OB_STRING_HH 28 | -------------------------------------------------------------------------------- /src/ob/term.hh: -------------------------------------------------------------------------------- 1 | #ifndef OB_TERM_HH 2 | #define OB_TERM_HH 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace OB::Term 30 | { 31 | 32 | namespace Key 33 | { 34 | enum 35 | { 36 | null = 0, 37 | bell = 7, 38 | tab = 9, 39 | newline = 10, 40 | enter = 13, 41 | escape = 27, 42 | space = 32, 43 | backspace = 127, 44 | 45 | up = 0xF0000, 46 | down, 47 | left, 48 | right, 49 | home, 50 | end, 51 | delete_, 52 | insert, 53 | page_up, 54 | page_down 55 | }; 56 | } 57 | 58 | namespace Mouse 59 | { 60 | enum 61 | { 62 | null = 0, 63 | btn_release = 0xF1000, 64 | btn1_press, 65 | btn1_release, 66 | btn2_press, 67 | btn2_release, 68 | btn3_press, 69 | btn3_release, 70 | scroll_up, 71 | scroll_down 72 | }; 73 | } 74 | 75 | inline int constexpr ctrl_key(int const c) 76 | { 77 | return (c & 0x1F); 78 | } 79 | 80 | inline char32_t utf8_to_char32(std::string_view str) 81 | { 82 | if (str.empty()) 83 | { 84 | return 0; 85 | } 86 | 87 | if ((str.at(0) & 0x80) == 0) 88 | { 89 | return static_cast(str.at(0)); 90 | } 91 | else if ((str.at(0) & 0xE0) == 0xC0 && str.size() == 2) 92 | { 93 | return (static_cast(str[0] & 0x1F) << 6) | 94 | static_cast(str[1] & 0x3F); 95 | } 96 | else if ((str.at(0) & 0xF0) == 0xE0 && str.size() == 3) 97 | { 98 | return (static_cast(str[0] & 0x0F) << 12) | 99 | (static_cast(str[1] & 0x3F) << 6) | 100 | static_cast(str[2] & 0x3F); 101 | } 102 | else if ((str.at(0) & 0xF8) == 0xF0 && str.size() == 4) 103 | { 104 | return (static_cast(str[0] & 0x07) << 18) | 105 | (static_cast(str[1] & 0x3F) << 12) | 106 | (static_cast(str[2] & 0x3F) << 6) | 107 | static_cast(str[3] & 0x3F); 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | inline char32_t get_key(std::string* str = nullptr) 114 | { 115 | // NOTE term mode should be in raw state before call to this func 116 | 117 | char key[4] {0}; 118 | int ec = read(STDIN_FILENO, &key[0], 1); 119 | 120 | if ((ec == -1) && (errno != EAGAIN)) 121 | { 122 | throw std::runtime_error("read failed"); 123 | } 124 | 125 | if (ec == 0) 126 | { 127 | return Key::null; 128 | } 129 | 130 | // utf-8 multibyte code point 131 | if (key[0] & 0x80) 132 | { 133 | std::size_t bytes {0}; 134 | 135 | for (; bytes < 3; ++bytes) 136 | { 137 | if (! (key[0] & (0x80 >> (bytes + 1)))) 138 | { 139 | break; 140 | } 141 | } 142 | 143 | if ((ec = read(STDIN_FILENO, &key[1], bytes)) != static_cast(bytes)) 144 | { 145 | if ((ec == -1) && (errno != EAGAIN)) 146 | { 147 | throw std::runtime_error("read failed"); 148 | } 149 | 150 | return static_cast(key[0]); 151 | } 152 | 153 | ++bytes; 154 | 155 | if (str != nullptr) 156 | { 157 | str->assign(&key[0], bytes); 158 | } 159 | 160 | return utf8_to_char32(std::string_view(&key[0], bytes)); 161 | } 162 | 163 | // utf-8 single-byte code point 164 | if (str != nullptr) 165 | { 166 | str->assign(&key[0], 1); 167 | } 168 | 169 | // esc / esc sequence 170 | if (key[0] == Key::escape) 171 | { 172 | char seq[3] {0}; 173 | 174 | if ((ec = read(STDIN_FILENO, &seq[0], 1)) != 1) 175 | { 176 | if ((ec == -1) && (errno != EAGAIN)) 177 | { 178 | throw std::runtime_error("read failed"); 179 | } 180 | 181 | return static_cast(key[0]); 182 | } 183 | 184 | if ((ec = read(STDIN_FILENO, &seq[1], 1)) != 1) 185 | { 186 | if ((ec == -1) && (errno != EAGAIN)) 187 | { 188 | throw std::runtime_error("read failed"); 189 | } 190 | 191 | return static_cast(key[0]); 192 | } 193 | 194 | if (seq[0] == '[') 195 | { 196 | if (seq[1] >= '0' && seq[1] <= '9') 197 | { 198 | if ((ec = read(STDIN_FILENO, &seq[2], 1)) != 1) 199 | { 200 | if ((ec == -1) && (errno != EAGAIN)) 201 | { 202 | throw std::runtime_error("read failed"); 203 | } 204 | 205 | return static_cast(key[0]); 206 | } 207 | 208 | if (seq[2] == '~') 209 | { 210 | switch (seq[1]) 211 | { 212 | case '1': 213 | { 214 | return Key::home; 215 | } 216 | 217 | case '2': 218 | { 219 | return Key::insert; 220 | } 221 | 222 | case '3': 223 | { 224 | return Key::delete_; 225 | } 226 | 227 | case '4': 228 | { 229 | return Key::end; 230 | } 231 | 232 | case '5': 233 | { 234 | return Key::page_up; 235 | } 236 | 237 | case '6': 238 | { 239 | return Key::page_down; 240 | } 241 | 242 | default: 243 | { 244 | return static_cast(key[0]); 245 | } 246 | } 247 | } 248 | } 249 | else 250 | { 251 | switch (seq[1]) 252 | { 253 | case 'A': 254 | { 255 | return Key::up; 256 | } 257 | 258 | case 'B': 259 | { 260 | return Key::down; 261 | } 262 | 263 | case 'C': 264 | { 265 | return Key::right; 266 | } 267 | 268 | case 'D': 269 | { 270 | return Key::left; 271 | } 272 | 273 | case '<': 274 | { 275 | // 1000;1006 mouse event 276 | 277 | std::size_t constexpr buf_size {64}; 278 | char mouse[buf_size] {0}; 279 | 280 | for (std::size_t i = 0; i < buf_size; ++i) 281 | { 282 | if ((ec = read(STDIN_FILENO, &mouse[i], 1)) != 1) 283 | { 284 | if ((ec == -1) && (errno != EAGAIN)) 285 | { 286 | throw std::runtime_error("read failed"); 287 | } 288 | 289 | return static_cast(key[0]); 290 | } 291 | 292 | if (mouse[i] >= 0x40 && mouse[i] <= 0x7E) 293 | { 294 | if (str != nullptr) 295 | { 296 | str->assign(mouse, i + 1); 297 | } 298 | 299 | switch (mouse[i]) 300 | { 301 | case 'm': 302 | { 303 | switch (mouse[0]) 304 | { 305 | case '0': 306 | { 307 | return Mouse::btn1_release; 308 | } 309 | 310 | case '1': 311 | { 312 | return Mouse::btn2_release; 313 | } 314 | 315 | case '2': 316 | { 317 | return Mouse::btn3_release; 318 | } 319 | 320 | default: 321 | { 322 | break; 323 | } 324 | } 325 | 326 | break; 327 | } 328 | 329 | case 'M': 330 | { 331 | switch (mouse[0]) 332 | { 333 | case '0': 334 | { 335 | return Mouse::btn1_press; 336 | } 337 | 338 | case '1': 339 | { 340 | return Mouse::btn2_press; 341 | } 342 | 343 | case '2': 344 | { 345 | return Mouse::btn3_press; 346 | } 347 | 348 | case '6': 349 | { 350 | switch (mouse[1]) 351 | { 352 | case '4': 353 | { 354 | return Mouse::scroll_up; 355 | } 356 | 357 | case '5': 358 | { 359 | return Mouse::scroll_down; 360 | } 361 | 362 | default: 363 | { 364 | break; 365 | } 366 | } 367 | 368 | break; 369 | } 370 | 371 | default: 372 | { 373 | break; 374 | } 375 | } 376 | 377 | break; 378 | } 379 | 380 | default: 381 | { 382 | break; 383 | } 384 | } 385 | 386 | break; 387 | } 388 | } 389 | 390 | return static_cast(key[0]); 391 | } 392 | 393 | case 'M': 394 | { 395 | // 1000 mouse event 396 | 397 | char mouse[3] {0}; 398 | 399 | if ((ec = read(STDIN_FILENO, &mouse[0], 3)) != 3) 400 | { 401 | if ((ec == -1) && (errno != EAGAIN)) 402 | { 403 | throw std::runtime_error("read failed"); 404 | } 405 | 406 | return static_cast(key[0]); 407 | } 408 | 409 | if (str != nullptr) 410 | { 411 | str->assign(mouse, 3); 412 | } 413 | 414 | switch (mouse[0] & 0x03) 415 | { 416 | case 0: 417 | { 418 | if (mouse[0] & 0x40) 419 | { 420 | return Mouse::scroll_up; 421 | } 422 | 423 | return Mouse::btn1_press; 424 | } 425 | 426 | case 1: 427 | { 428 | if (mouse[0] & 0x40) 429 | { 430 | return Mouse::scroll_down; 431 | } 432 | 433 | return Mouse::btn2_press; 434 | } 435 | 436 | case 2: 437 | { 438 | return Mouse::btn3_press; 439 | } 440 | 441 | case 3: 442 | { 443 | return Mouse::btn_release; 444 | } 445 | 446 | default: 447 | { 448 | break; 449 | } 450 | } 451 | 452 | return Mouse::null; 453 | } 454 | 455 | default: 456 | { 457 | return static_cast(key[0]); 458 | } 459 | } 460 | } 461 | } 462 | } 463 | 464 | return static_cast(key[0]); 465 | } 466 | 467 | class Stdin 468 | { 469 | public: 470 | 471 | Stdin() 472 | { 473 | _fp = std::fopen("/dev/tty", "r"); 474 | if (! _fp) 475 | { 476 | throw std::runtime_error("could not open stdin"); 477 | } 478 | 479 | _fd = fileno(_fp); 480 | } 481 | 482 | ~Stdin() 483 | { 484 | if (_fp) 485 | { 486 | std::fclose(_fp); 487 | } 488 | } 489 | 490 | int fd() 491 | { 492 | return _fd; 493 | } 494 | 495 | FILE* fp() 496 | { 497 | return _fp; 498 | } 499 | 500 | private: 501 | 502 | int _fd {0}; 503 | FILE* _fp {NULL}; 504 | }; 505 | 506 | class Mode 507 | { 508 | public: 509 | 510 | Mode() 511 | { 512 | if (tcgetattr(_stdin.fd(), &_old) == -1) 513 | { 514 | throw std::runtime_error("tcgetattr failed"); 515 | } 516 | } 517 | 518 | ~Mode() 519 | { 520 | if (! _cooked) 521 | { 522 | set_cooked(); 523 | } 524 | } 525 | 526 | operator bool() 527 | { 528 | return _cooked; 529 | } 530 | 531 | void set_cooked() 532 | { 533 | if (_cooked) 534 | { 535 | return; 536 | } 537 | 538 | if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_old) == -1) 539 | { 540 | throw std::runtime_error("tcsetattr failed"); 541 | } 542 | 543 | _cooked = true; 544 | } 545 | 546 | void set_raw() 547 | { 548 | if (! _cooked) 549 | { 550 | return; 551 | } 552 | 553 | _raw = _old; 554 | // _raw.c_iflag &= static_cast(~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)); 555 | // _raw.c_oflag &= static_cast(~(OPOST)); 556 | _raw.c_lflag &= static_cast(~(ECHO | ECHONL | ICANON | ISIG | IEXTEN)); 557 | _raw.c_cflag &= static_cast(~(CSIZE | PARENB)); 558 | _raw.c_cflag |= static_cast(CS8); 559 | _raw.c_cc[VMIN] = _min; 560 | _raw.c_cc[VTIME] = _timeout; 561 | 562 | if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_raw) == -1) 563 | { 564 | throw std::runtime_error("tcsetattr failed"); 565 | } 566 | 567 | _cooked = false; 568 | } 569 | 570 | void set_timeout(int val = 0) 571 | { 572 | if (_timeout == val) 573 | { 574 | return; 575 | } 576 | 577 | _timeout = val; 578 | 579 | if (_cooked) 580 | { 581 | return; 582 | } 583 | 584 | _raw.c_cc[VTIME] = _timeout; 585 | 586 | if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_raw) == -1) 587 | { 588 | throw std::runtime_error("tcsetattr failed"); 589 | } 590 | } 591 | 592 | int get_timeout() 593 | { 594 | return _timeout; 595 | } 596 | 597 | void set_min(int val = 0) 598 | { 599 | if (_min == val) 600 | { 601 | return; 602 | } 603 | 604 | _min = val; 605 | 606 | if (_cooked) 607 | { 608 | return; 609 | } 610 | 611 | _raw.c_cc[VMIN] = _min; 612 | 613 | if (tcsetattr(_stdin.fd(), TCSAFLUSH, &_raw) == -1) 614 | { 615 | throw std::runtime_error("tcsetattr failed"); 616 | } 617 | } 618 | 619 | int get_min() 620 | { 621 | return _min; 622 | } 623 | 624 | private: 625 | 626 | Stdin _stdin; 627 | termios _old; 628 | termios _raw; 629 | bool _cooked {true}; 630 | int _timeout {0}; 631 | int _min {0}; 632 | }; // class Mode 633 | 634 | inline bool press_to_continue(std::string const& str = "ANY KEY", char32_t val = 0) 635 | { 636 | std::cerr 637 | << "Press " << str << " to continue"; 638 | 639 | Mode _term_mode; 640 | _term_mode.set_min(1); 641 | _term_mode.set_raw(); 642 | 643 | bool res {false}; 644 | char32_t key {0}; 645 | if ((key = get_key()) > 0) 646 | { 647 | res = (val == 0 ? true : val == key); 648 | } 649 | 650 | _term_mode.set_cooked(); 651 | 652 | std::cerr 653 | << "\n"; 654 | 655 | return res; 656 | } 657 | 658 | inline std::string env_var(std::string const& str) 659 | { 660 | std::string res; 661 | 662 | if (char const* envar = std::getenv(str.data())) 663 | { 664 | res = envar; 665 | } 666 | 667 | return res; 668 | } 669 | 670 | inline bool is_term(std::size_t fd_) 671 | { 672 | switch (fd_) 673 | { 674 | case STDIN_FILENO: return isatty(STDIN_FILENO); 675 | case STDOUT_FILENO: return isatty(STDOUT_FILENO); 676 | case STDERR_FILENO: return isatty(STDERR_FILENO); 677 | default: return false; 678 | } 679 | } 680 | 681 | inline bool is_colorterm() 682 | { 683 | auto const colorterm = env_var("COLORTERM"); 684 | 685 | if (colorterm == "truecolor" || colorterm == "24bit") 686 | { 687 | return true; 688 | } 689 | 690 | return false; 691 | } 692 | 693 | inline int width(std::size_t& width_, std::size_t fd_ = STDOUT_FILENO) 694 | { 695 | if (fd_ != STDIN_FILENO && fd_ != STDOUT_FILENO && fd_ != STDERR_FILENO) 696 | { 697 | return -1; 698 | } 699 | 700 | winsize w; 701 | ioctl(fd_, TIOCGWINSZ, &w); 702 | 703 | width_ = w.ws_col; 704 | 705 | return 0; 706 | } 707 | 708 | inline int height(std::size_t& height_, std::size_t fd_ = STDOUT_FILENO) 709 | { 710 | if (fd_ != STDIN_FILENO && fd_ != STDOUT_FILENO && fd_ != STDERR_FILENO) 711 | { 712 | return -1; 713 | } 714 | 715 | winsize w; 716 | ioctl(fd_, TIOCGWINSZ, &w); 717 | 718 | height_ = w.ws_row; 719 | 720 | return 0; 721 | } 722 | 723 | inline int size(std::size_t& width_, std::size_t& height_, std::size_t fd_ = STDOUT_FILENO) 724 | { 725 | if (fd_ != STDIN_FILENO && fd_ != STDOUT_FILENO && fd_ != STDERR_FILENO) 726 | { 727 | return -1; 728 | } 729 | 730 | winsize w; 731 | ioctl(fd_, TIOCGWINSZ, &w); 732 | 733 | width_ = w.ws_col; 734 | height_ = w.ws_row; 735 | 736 | return 0; 737 | } 738 | 739 | namespace ANSI_Escape_Codes 740 | { 741 | 742 | // constants 743 | std::string const space {" "}; 744 | 745 | // standard escaped characters 746 | std::string const cr {"\r"}; 747 | std::string const nl {"\n"}; 748 | std::string const crnl {cr + nl}; 749 | std::string const tab {"\t"}; 750 | std::string const alert {"\a"}; 751 | std::string const backspace {"\b"}; 752 | std::string const backslash {"\\"}; 753 | 754 | // escape code sequence 755 | std::string const esc {"\x1b"}; 756 | 757 | // reset terminal 758 | std::string const reset {esc + "c"}; 759 | 760 | // clear all attributes 761 | std::string const clear {esc + "[0m"}; 762 | 763 | // mouse 764 | std::string const mouse_enable {esc + "[?1000;1006h"}; 765 | std::string const mouse_disable {esc + "[?1000;1006l"}; 766 | 767 | // style 768 | std::string const bold {esc + "[1m"}; 769 | std::string const dim {esc + "[2m"}; 770 | std::string const italic {esc + "[3m"}; 771 | std::string const underline {esc + "[4m"}; 772 | std::string const blink {esc + "[5m"}; 773 | std::string const rblink {esc + "[6m"}; 774 | std::string const reverse {esc + "[7m"}; 775 | std::string const conceal {esc + "[8m"}; 776 | std::string const cross {esc + "[9m"}; 777 | 778 | // TODO add negative of each style attr 779 | std::string const nbold {esc + "[22m"}; 780 | 781 | // screen 782 | std::string const screen_push {esc + "[?1049h"}; 783 | std::string const screen_pop {esc + "[?1049l"}; 784 | std::string const screen_clear {esc + "[2J"}; 785 | 786 | // scroll 787 | std::string const scroll_up {esc + "M"}; 788 | std::string const scroll_down {esc + "D"}; 789 | 790 | // erase 791 | std::string const erase_end {esc + "[K"}; 792 | std::string const erase_start {esc + "[1K"}; 793 | std::string const erase_line {esc + "[2K"}; 794 | std::string const erase_down {esc + "[J"}; 795 | std::string const erase_up {esc + "[1J"}; 796 | 797 | // cursor visibility 798 | std::string const cursor_hide {esc + "[?25l"}; 799 | std::string const cursor_show {esc + "[?25h"}; 800 | 801 | // cursor position 802 | std::string const cursor_home {esc + "[H"}; 803 | std::string const cursor_save {esc + "7"}; 804 | std::string const cursor_load {esc + "8"}; 805 | 806 | // foreground color 807 | std::string const fg_black {esc + "[30m"}; 808 | std::string const fg_red {esc + "[31m"}; 809 | std::string const fg_green {esc + "[32m"}; 810 | std::string const fg_yellow {esc + "[33m"}; 811 | std::string const fg_blue {esc + "[34m"}; 812 | std::string const fg_magenta {esc + "[35m"}; 813 | std::string const fg_cyan {esc + "[36m"}; 814 | std::string const fg_white {esc + "[37m"}; 815 | 816 | // background color 817 | std::string const bg_black {esc + "[40m"}; 818 | std::string const bg_red {esc + "[41m"}; 819 | std::string const bg_green {esc + "[42m"}; 820 | std::string const bg_yellow {esc + "[43m"}; 821 | std::string const bg_blue {esc + "[44m"}; 822 | std::string const bg_magenta {esc + "[45m"}; 823 | std::string const bg_cyan {esc + "[46m"}; 824 | std::string const bg_white {esc + "[47m"}; 825 | 826 | // foreground color bright 827 | std::string const fg_black_bright {esc + "[90m"}; 828 | std::string const fg_red_bright {esc + "[91m"}; 829 | std::string const fg_green_bright {esc + "[92m"}; 830 | std::string const fg_yellow_bright {esc + "[99m"}; 831 | std::string const fg_blue_bright {esc + "[94m"}; 832 | std::string const fg_magenta_bright {esc + "[95m"}; 833 | std::string const fg_cyan_bright {esc + "[96m"}; 834 | std::string const fg_white_bright {esc + "[97m"}; 835 | 836 | // background color bright 837 | std::string const bg_black_bright {esc + "[100m"}; 838 | std::string const bg_red_bright {esc + "[101m"}; 839 | std::string const bg_green_bright {esc + "[102m"}; 840 | std::string const bg_yellow_bright {esc + "[103m"}; 841 | std::string const bg_blue_bright {esc + "[104m"}; 842 | std::string const bg_magenta_bright {esc + "[105m"}; 843 | std::string const bg_cyan_bright {esc + "[106m"}; 844 | std::string const bg_white_bright {esc + "[107m"}; 845 | 846 | // box drawing 847 | // TODO add box drawing chars 848 | 849 | inline std::string cursor_up(std::size_t val = 1) 850 | { 851 | return val ? esc + "[" + std::to_string(val) + "A" : ""; 852 | } 853 | 854 | inline std::string cursor_down(std::size_t val = 1) 855 | { 856 | return val ? esc + "[" + std::to_string(val) + "B" : ""; 857 | } 858 | 859 | inline std::string cursor_right(std::size_t val = 1) 860 | { 861 | return val ? esc + "[" + std::to_string(val) + "C" : ""; 862 | } 863 | 864 | inline std::string cursor_left(std::size_t val = 1) 865 | { 866 | return val ? esc + "[" + std::to_string(val) + "D" : ""; 867 | } 868 | 869 | inline std::string cursor_set(std::size_t x_, std::size_t y_) 870 | { 871 | std::stringstream ss; 872 | ss << esc << "[" << y_ << ";" << x_ << "H"; 873 | 874 | return ss.str(); 875 | } 876 | 877 | inline int cursor_get(std::size_t& x_, std::size_t& y_, bool mode_ = true) 878 | { 879 | Term::Mode mode; 880 | 881 | if (mode_) 882 | { 883 | mode.set_raw(); 884 | } 885 | 886 | std::cout << (esc + "[6n") << std::flush; 887 | 888 | char buf[32]; 889 | std::uint8_t i {0}; 890 | 891 | // attempt to read response for up to 1 second 892 | for (int retry = 20; i < sizeof(buf) - 1; ++i) 893 | { 894 | while (retry-- > 0 && read(STDIN_FILENO, &buf[i], 1) != 1) 895 | { 896 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 897 | } 898 | 899 | if (buf[i] == 'R') 900 | { 901 | break; 902 | } 903 | } 904 | 905 | buf[i] = '\0'; 906 | if (buf[0] != '\x1b' || buf[1] != '[') 907 | { 908 | return -1; 909 | } 910 | 911 | int x; 912 | int y; 913 | if (std::sscanf(&buf[2], "%d;%d", &y, &x) != 2) 914 | { 915 | return -1; 916 | } 917 | 918 | x_ = static_cast(x); 919 | y_ = static_cast(y); 920 | 921 | return 0; 922 | } 923 | 924 | inline std::string str_to_fg_color(std::string const& str_, bool bright_ = false) 925 | { 926 | if ("black" == str_) 927 | { 928 | if (bright_) 929 | { 930 | return fg_black_bright; 931 | } 932 | else 933 | { 934 | return fg_black; 935 | } 936 | } 937 | else if ("red" == str_) 938 | { 939 | if (bright_) 940 | { 941 | return fg_red_bright; 942 | } 943 | else 944 | { 945 | return fg_red; 946 | } 947 | } 948 | else if ("green" == str_) 949 | { 950 | if (bright_) 951 | { 952 | return fg_green_bright; 953 | } 954 | else 955 | { 956 | return fg_green; 957 | } 958 | } 959 | else if ("yellow" == str_) 960 | { 961 | if (bright_) 962 | { 963 | return fg_yellow_bright; 964 | } 965 | else 966 | { 967 | return fg_yellow; 968 | } 969 | } 970 | else if ("blue" == str_) 971 | { 972 | if (bright_) 973 | { 974 | return fg_blue_bright; 975 | } 976 | else 977 | { 978 | return fg_blue; 979 | } 980 | } 981 | else if ("magenta" == str_) 982 | { 983 | if (bright_) 984 | { 985 | return fg_magenta_bright; 986 | } 987 | else 988 | { 989 | return fg_magenta; 990 | } 991 | } 992 | else if ("cyan" == str_) 993 | { 994 | if (bright_) 995 | { 996 | return fg_cyan_bright; 997 | } 998 | else 999 | { 1000 | return fg_cyan; 1001 | } 1002 | } 1003 | else if ("white" == str_) 1004 | { 1005 | if (bright_) 1006 | { 1007 | return fg_white_bright; 1008 | } 1009 | else 1010 | { 1011 | return fg_white; 1012 | } 1013 | } 1014 | else 1015 | { 1016 | return {}; 1017 | } 1018 | } 1019 | 1020 | inline std::string str_to_bg_color(std::string const& str_, bool bright_ = false) 1021 | { 1022 | if ("black" == str_) 1023 | { 1024 | if (bright_) 1025 | { 1026 | return bg_black_bright; 1027 | } 1028 | else 1029 | { 1030 | return bg_black; 1031 | } 1032 | } 1033 | else if ("red" == str_) 1034 | { 1035 | if (bright_) 1036 | { 1037 | return bg_red_bright; 1038 | } 1039 | else 1040 | { 1041 | return bg_red; 1042 | } 1043 | } 1044 | else if ("green" == str_) 1045 | { 1046 | if (bright_) 1047 | { 1048 | return bg_green_bright; 1049 | } 1050 | else 1051 | { 1052 | return bg_green; 1053 | } 1054 | } 1055 | else if ("yellow" == str_) 1056 | { 1057 | if (bright_) 1058 | { 1059 | return bg_yellow_bright; 1060 | } 1061 | else 1062 | { 1063 | return bg_yellow; 1064 | } 1065 | } 1066 | else if ("blue" == str_) 1067 | { 1068 | if (bright_) 1069 | { 1070 | return bg_blue_bright; 1071 | } 1072 | else 1073 | { 1074 | return bg_blue; 1075 | } 1076 | } 1077 | else if ("magenta" == str_) 1078 | { 1079 | if (bright_) 1080 | { 1081 | return bg_magenta_bright; 1082 | } 1083 | else 1084 | { 1085 | return bg_magenta; 1086 | } 1087 | } 1088 | else if ("cyan" == str_) 1089 | { 1090 | if (bright_) 1091 | { 1092 | return bg_cyan_bright; 1093 | } 1094 | else 1095 | { 1096 | return bg_cyan; 1097 | } 1098 | } 1099 | else if ("white" == str_) 1100 | { 1101 | if (bright_) 1102 | { 1103 | return bg_white_bright; 1104 | } 1105 | else 1106 | { 1107 | return bg_white; 1108 | } 1109 | } 1110 | else 1111 | { 1112 | return {}; 1113 | } 1114 | } 1115 | 1116 | inline std::string fg_256(std::string const& str_) 1117 | { 1118 | auto const n = std::stoi(str_); 1119 | if (n < 0 || n > 256) return {}; 1120 | std::stringstream ss; 1121 | ss << esc << "[38;5;" << str_ << "m"; 1122 | 1123 | return ss.str(); 1124 | } 1125 | 1126 | inline std::string bg_256(std::string const& str_) 1127 | { 1128 | auto const n = std::stoi(str_); 1129 | if (n < 0 || n > 256) return {}; 1130 | std::stringstream ss; 1131 | ss << esc << "[48;5;" << str_ << "m"; 1132 | 1133 | return ss.str(); 1134 | } 1135 | 1136 | inline std::string htoi(std::string const& str_) 1137 | { 1138 | std::stringstream ss; 1139 | ss << str_; 1140 | unsigned int n; 1141 | ss >> std::hex >> n; 1142 | 1143 | return std::to_string(n); 1144 | } 1145 | 1146 | inline bool valid_hstr(std::string& str_) 1147 | { 1148 | std::smatch m; 1149 | std::regex rx {"^#?((?:[0-9a-fA-F]{3}){1,2})$"}; 1150 | 1151 | if (std::regex_match(str_, m, rx, std::regex_constants::match_not_null)) 1152 | { 1153 | std::string hstr {m[1]}; 1154 | 1155 | if (hstr.size() == 3) 1156 | { 1157 | std::stringstream ss; 1158 | ss << hstr[0] << hstr[0] << hstr[1] << hstr[1] << hstr[2] << hstr[2]; 1159 | hstr = ss.str(); 1160 | } 1161 | 1162 | str_ = hstr; 1163 | 1164 | return true; 1165 | } 1166 | 1167 | return false; 1168 | } 1169 | 1170 | inline std::string fg_true(std::string str_) 1171 | { 1172 | if (! valid_hstr(str_)) 1173 | { 1174 | return {}; 1175 | } 1176 | 1177 | std::string const h1 {str_.substr(0, 2)}; 1178 | std::string const h2 {str_.substr(2, 2)}; 1179 | std::string const h3 {str_.substr(4, 2)}; 1180 | 1181 | std::ostringstream os; os 1182 | << esc << "[38;2;" 1183 | << htoi(h1) << ";" 1184 | << htoi(h2) << ";" 1185 | << htoi(h3) << "m"; 1186 | 1187 | return os.str(); 1188 | } 1189 | 1190 | inline std::string bg_true(std::string str_) 1191 | { 1192 | if (! valid_hstr(str_)) 1193 | { 1194 | return {}; 1195 | } 1196 | 1197 | std::string const h1 {str_.substr(0, 2)}; 1198 | std::string const h2 {str_.substr(2, 2)}; 1199 | std::string const h3 {str_.substr(4, 2)}; 1200 | 1201 | std::stringstream ss; ss 1202 | << esc << "[48;2;" 1203 | << htoi(h1) << ";" 1204 | << htoi(h2) << ";" 1205 | << htoi(h3) << "m"; 1206 | 1207 | return ss.str(); 1208 | } 1209 | 1210 | template 1211 | std::string wrap(T const val_, std::string const attr_, bool color_ = true) 1212 | { 1213 | std::stringstream ss; 1214 | 1215 | if (color_) 1216 | { 1217 | ss 1218 | << attr_ 1219 | << val_ 1220 | << clear; 1221 | } 1222 | else 1223 | { 1224 | ss << val_; 1225 | } 1226 | 1227 | return ss.str(); 1228 | } 1229 | 1230 | template 1231 | std::string wrap(T const val_, std::vector const attr_, bool color_ = true) 1232 | { 1233 | std::stringstream ss; 1234 | 1235 | if (color_) 1236 | { 1237 | for (auto const& e : attr_) 1238 | { 1239 | ss << e; 1240 | } 1241 | ss << val_ << clear; 1242 | } 1243 | else 1244 | { 1245 | ss << val_ << clear; 1246 | } 1247 | 1248 | return ss.str(); 1249 | } 1250 | 1251 | } // namespace ANSI_Escape_Codes 1252 | 1253 | class ostream : public std::ostream 1254 | { 1255 | protected: 1256 | 1257 | class streambuf : public std::streambuf 1258 | { 1259 | using string = std::basic_string; 1260 | 1261 | public: 1262 | 1263 | explicit streambuf(std::streambuf* streambuf_, 1264 | std::size_t width_ = std::numeric_limits::max()) : 1265 | _streambuf {streambuf_}, 1266 | _width {width_} 1267 | { 1268 | } 1269 | 1270 | ~streambuf() 1271 | { 1272 | flush(); 1273 | } 1274 | 1275 | streambuf& flush() 1276 | { 1277 | if (! _buffer.empty()) 1278 | { 1279 | if (_size) 1280 | { 1281 | if (! _white_space && ! _buffer.empty() && _buffer.back() == ' ') 1282 | { 1283 | _buffer.erase(_buffer.size() - 1); 1284 | } 1285 | 1286 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1287 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1288 | } 1289 | else 1290 | { 1291 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1292 | } 1293 | 1294 | _streambuf->sputc('\n'); 1295 | } 1296 | 1297 | // if (_width != std::numeric_limits::max()) 1298 | // { 1299 | // while (_size + _prefix.size() < _width) 1300 | // { 1301 | // ++_size; 1302 | // _streambuf->sputc(' '); 1303 | // } 1304 | // } 1305 | 1306 | _size = 0; 1307 | _buffer.clear(); 1308 | _esc_seq.clear(); 1309 | 1310 | return *this; 1311 | } 1312 | 1313 | streambuf& endl() 1314 | { 1315 | if (! _buffer.empty()) 1316 | { 1317 | if (_size) 1318 | { 1319 | if (! _white_space && ! _buffer.empty() && _buffer.back() == ' ') 1320 | { 1321 | _buffer.erase(_buffer.size() - 1); 1322 | } 1323 | 1324 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1325 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1326 | } 1327 | else 1328 | { 1329 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1330 | } 1331 | } 1332 | 1333 | // if (_width != std::numeric_limits::max()) 1334 | // { 1335 | // while (_size + _prefix.size() < _width) 1336 | // { 1337 | // ++_size; 1338 | // _streambuf->sputc(' '); 1339 | // } 1340 | // } 1341 | 1342 | _streambuf->sputc('\n'); 1343 | 1344 | _size = 0; 1345 | _buffer.clear(); 1346 | _esc_seq.clear(); 1347 | 1348 | return *this; 1349 | } 1350 | 1351 | streambuf& push(std::size_t val_) 1352 | { 1353 | flush(); 1354 | 1355 | _level += val_; 1356 | 1357 | _prefix = string(_level * _indent, ' '); 1358 | 1359 | return *this; 1360 | } 1361 | 1362 | streambuf& pop(std::size_t val_) 1363 | { 1364 | flush(); 1365 | 1366 | if (_level > val_) 1367 | { 1368 | _level -= val_; 1369 | } 1370 | else 1371 | { 1372 | _level = 0; 1373 | } 1374 | 1375 | _prefix = string(_level * _indent, ' '); 1376 | 1377 | return *this; 1378 | } 1379 | 1380 | 1381 | streambuf& width(std::size_t val_) 1382 | { 1383 | _width = val_; 1384 | 1385 | return *this; 1386 | } 1387 | 1388 | streambuf& escape_codes(bool val_) 1389 | { 1390 | _escape_codes = val_; 1391 | _esc_seq.clear(); 1392 | 1393 | return *this; 1394 | } 1395 | 1396 | streambuf& line_wrap(bool val_) 1397 | { 1398 | _line_wrap = val_; 1399 | 1400 | return *this; 1401 | } 1402 | 1403 | streambuf& first_wrap(bool val_) 1404 | { 1405 | _first_wrap = val_; 1406 | 1407 | return *this; 1408 | } 1409 | 1410 | streambuf& auto_wrap(bool val_) 1411 | { 1412 | _auto_wrap = val_; 1413 | _prefix.clear(); 1414 | 1415 | return *this; 1416 | } 1417 | 1418 | streambuf& word_break(bool val_) 1419 | { 1420 | _word_break = val_; 1421 | 1422 | return *this; 1423 | } 1424 | 1425 | streambuf& white_space(bool val_) 1426 | { 1427 | _white_space = val_; 1428 | 1429 | return *this; 1430 | } 1431 | 1432 | streambuf& indent(std::size_t val_) 1433 | { 1434 | _indent = val_; 1435 | _prefix = string(_level * _indent, ' '); 1436 | 1437 | return *this; 1438 | } 1439 | 1440 | streambuf& level(std::size_t val_) 1441 | { 1442 | _level = val_; 1443 | _prefix = string(_level * _indent, ' '); 1444 | 1445 | return *this; 1446 | } 1447 | 1448 | protected: 1449 | 1450 | int_type overflow(int_type ch_) 1451 | { 1452 | if (traits_type::eq_int_type(traits_type::eof(), ch_)) 1453 | { 1454 | return traits_type::not_eof(ch_); 1455 | } 1456 | 1457 | // handle auto wrap prefix 1458 | if (ch_ != ' ' && ch_ != '\t') 1459 | { 1460 | _is_prefix = false; 1461 | } 1462 | 1463 | // handle ansi escape codes 1464 | if (! _esc_seq.empty()) 1465 | { 1466 | if (_esc_seq.size() == 1) 1467 | { 1468 | if (ch_ != '[' && ch_ != '(' && ch_ != ')' && ch_ != '#') 1469 | { 1470 | _esc_seq += ch_; 1471 | if (_escape_codes) 1472 | { 1473 | _buffer += _esc_seq; 1474 | } 1475 | _esc_seq.clear(); 1476 | } 1477 | else if (ch_ == '[' || ch_ == '(' || ch_ == ')' || ch_ == '#') 1478 | { 1479 | _esc_seq += ch_; 1480 | } 1481 | else if (ch_ == std::isalpha(static_cast(ch_))) 1482 | { 1483 | _esc_seq += ch_; 1484 | if (_escape_codes) 1485 | { 1486 | _buffer += _esc_seq; 1487 | } 1488 | _esc_seq.clear(); 1489 | } 1490 | else 1491 | { 1492 | _esc_seq.clear(); 1493 | } 1494 | } 1495 | else if (_esc_seq.size() == 2 && _esc_seq.back() == '#') 1496 | { 1497 | _esc_seq += ch_; 1498 | if (_escape_codes) 1499 | { 1500 | _buffer += _esc_seq; 1501 | } 1502 | _esc_seq.clear(); 1503 | } 1504 | else if (std::isalpha(static_cast(ch_))) 1505 | { 1506 | _esc_seq += ch_; 1507 | if (_escape_codes) 1508 | { 1509 | _buffer += _esc_seq; 1510 | } 1511 | _esc_seq.clear(); 1512 | } 1513 | else 1514 | { 1515 | _esc_seq += ch_; 1516 | } 1517 | 1518 | return ch_; 1519 | } 1520 | 1521 | switch (ch_) 1522 | { 1523 | case '\t': 1524 | { 1525 | if (! _first_wrap && _level == 0) 1526 | { 1527 | // don't wrap first line when level is 0 1528 | // block left intentionally empty 1529 | } 1530 | else if (_line_wrap && (_size + _indent >= _width - _prefix.size())) 1531 | { 1532 | if (auto pos = _buffer.find_last_of(" "); 1533 | _word_break && pos != string::npos) 1534 | { 1535 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1536 | _streambuf->sputn(_buffer.data(), static_cast(pos)); 1537 | 1538 | _size = _buffer.size() - pos - 1; 1539 | _buffer = _buffer.substr(pos + 1); 1540 | } 1541 | else 1542 | { 1543 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1544 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1545 | 1546 | // while (_size + _prefix.size() < _width) 1547 | // { 1548 | // ++_size; 1549 | // _streambuf->sputc(' '); 1550 | // } 1551 | 1552 | _size = 0; 1553 | _buffer.clear(); 1554 | } 1555 | 1556 | _streambuf->sputc('\n'); 1557 | } 1558 | 1559 | if (_auto_wrap && _is_prefix) 1560 | { 1561 | _level = 1; 1562 | _prefix += string(_indent, ' '); 1563 | } 1564 | else if (_white_space || _buffer.empty()) 1565 | { 1566 | _size += _indent; 1567 | _buffer += string(_indent, ' '); 1568 | } 1569 | else if (! _buffer.empty() && _buffer.back() != ' ') 1570 | { 1571 | _size += _indent; 1572 | _buffer += string(_indent, ' '); 1573 | } 1574 | 1575 | return ch_; 1576 | } 1577 | 1578 | case '\x1b': 1579 | { 1580 | _esc_seq.clear(); 1581 | _esc_seq += ch_; 1582 | 1583 | return ch_; 1584 | } 1585 | 1586 | case '\a': 1587 | { 1588 | _buffer += ch_; 1589 | 1590 | return ch_; 1591 | } 1592 | 1593 | case '\b': 1594 | { 1595 | --_size; 1596 | _buffer += ch_; 1597 | 1598 | return ch_; 1599 | } 1600 | 1601 | case '\n': 1602 | case '\r': 1603 | { 1604 | if (! _white_space && ! _buffer.empty() && _buffer.back() == ' ') 1605 | { 1606 | _buffer.erase(_buffer.size() - 1); 1607 | } 1608 | 1609 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1610 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1611 | 1612 | // if (_width != std::numeric_limits::max()) 1613 | // { 1614 | // while (_size + _prefix.size() < _width) 1615 | // { 1616 | // ++_size; 1617 | // _streambuf->sputc(' '); 1618 | // } 1619 | // } 1620 | 1621 | _streambuf->sputc(ch_); 1622 | 1623 | _size = 0; 1624 | _buffer.clear(); 1625 | 1626 | if (_auto_wrap) 1627 | { 1628 | _level = 0; 1629 | _is_prefix = true; 1630 | _prefix.clear(); 1631 | } 1632 | 1633 | return ch_; 1634 | } 1635 | 1636 | case ' ': 1637 | { 1638 | if (! _first_wrap && _level == 0) 1639 | { 1640 | // don't wrap first line when level is 0 1641 | // block left intentionally empty 1642 | } 1643 | else if (_line_wrap && (_size + 1 + _prefix.size() >= _width)) 1644 | { 1645 | ++_size; 1646 | _buffer += " "; 1647 | 1648 | if (auto pos = _buffer.find_last_of(" "); 1649 | _word_break && pos != string::npos) 1650 | { 1651 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1652 | _streambuf->sputn(_buffer.data(), static_cast(pos)); 1653 | 1654 | _size = _buffer.size() - pos - 1; 1655 | _buffer = _buffer.substr(pos + 1); 1656 | } 1657 | else 1658 | { 1659 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1660 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1661 | 1662 | // while (_size + _prefix.size() < _width) 1663 | // { 1664 | // ++_size; 1665 | // _streambuf->sputc(' '); 1666 | // } 1667 | 1668 | _size = 0; 1669 | _buffer.clear(); 1670 | } 1671 | 1672 | _streambuf->sputc('\n'); 1673 | 1674 | return ch_; 1675 | } 1676 | 1677 | if (_auto_wrap && _is_prefix) 1678 | { 1679 | _level = 1; 1680 | _prefix += " "; 1681 | } 1682 | else if (_white_space) 1683 | { 1684 | ++_size; 1685 | _buffer += " "; 1686 | } 1687 | else if (! _buffer.empty() && _buffer.back() != ' ') 1688 | { 1689 | ++_size; 1690 | _buffer += " "; 1691 | } 1692 | 1693 | return ch_; 1694 | } 1695 | 1696 | default: 1697 | { 1698 | if (! _first_wrap && _level == 0) 1699 | { 1700 | // don't wrap first line when level is 0 1701 | // block left intentionally empty 1702 | } 1703 | else if (_line_wrap && (_size + _prefix.size() >= _width)) 1704 | { 1705 | if (auto pos = _buffer.find_last_of(" "); 1706 | _word_break && pos != string::npos) 1707 | { 1708 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1709 | _streambuf->sputn(_buffer.data(), static_cast(pos)); 1710 | 1711 | _size = _buffer.size() - pos - 1; 1712 | _buffer = _buffer.substr(pos + 1); 1713 | } 1714 | else 1715 | { 1716 | _streambuf->sputn(_prefix.data(), static_cast(_prefix.size())); 1717 | _streambuf->sputn(_buffer.data(), static_cast(_buffer.size())); 1718 | 1719 | // while (_size + _prefix.size() < _width) 1720 | // { 1721 | // ++_size; 1722 | // _streambuf->sputc(' '); 1723 | // } 1724 | 1725 | _size = 0; 1726 | _buffer.clear(); 1727 | } 1728 | 1729 | _streambuf->sputc('\n'); 1730 | } 1731 | 1732 | ++_size; 1733 | _buffer += ch_; 1734 | 1735 | return ch_; 1736 | } 1737 | } 1738 | } 1739 | 1740 | // output streambuf 1741 | std::streambuf* _streambuf; 1742 | 1743 | // maximum output width 1744 | std::size_t _width; 1745 | 1746 | // number of spaces to indent 1747 | std::size_t _indent {2}; 1748 | 1749 | // indentation level 1750 | std::size_t _level {0}; 1751 | 1752 | // stream should wrap around at output width 1753 | bool _line_wrap {true}; 1754 | 1755 | // stream should wrap around at output width when level=0 1756 | bool _first_wrap {true}; 1757 | 1758 | // auto calc indent when wrapping line 1759 | bool _auto_wrap {false}; 1760 | 1761 | // stream should break words on wrap 1762 | bool _word_break {true}; 1763 | 1764 | // stream should preserve whitespace 1765 | bool _white_space {true}; 1766 | 1767 | // stream should output escape codes 1768 | bool _escape_codes {true}; 1769 | 1770 | // used to calc the indent for auto wrap 1771 | bool _is_prefix {true}; 1772 | 1773 | // size of buffer minus special chars 1774 | std::size_t _size {0}; 1775 | 1776 | // prefix string prepended onto start of a new line 1777 | string _prefix {""}; 1778 | 1779 | // buffer string for temporary storage of chars 1780 | string _buffer {""}; 1781 | 1782 | // buffer string for escape sequences 1783 | string _esc_seq {""}; 1784 | }; // class streambuf 1785 | 1786 | public: 1787 | 1788 | explicit ostream(std::ostream& os_, std::size_t indent_width_ = 2, 1789 | std::size_t output_width_ = std::numeric_limits::max()) : 1790 | std::ostream {&_stream}, 1791 | _stream {os_.rdbuf(), output_width_} 1792 | { 1793 | indent(indent_width_); 1794 | } 1795 | 1796 | ostream& flush() 1797 | { 1798 | _stream.flush(); 1799 | std::ostream::flush(); 1800 | 1801 | return *this; 1802 | } 1803 | 1804 | ostream& endl() 1805 | { 1806 | _stream.endl(); 1807 | std::ostream::flush(); 1808 | 1809 | return *this; 1810 | } 1811 | 1812 | ostream& push(std::size_t val_ = 1) 1813 | { 1814 | _stream.push(val_); 1815 | std::ostream::flush(); 1816 | 1817 | return *this; 1818 | } 1819 | 1820 | ostream& pop(std::size_t val_ = 1) 1821 | { 1822 | _stream.pop(val_); 1823 | std::ostream::flush(); 1824 | 1825 | return *this; 1826 | } 1827 | 1828 | ostream& width(std::size_t val_ = std::numeric_limits::max()) 1829 | { 1830 | _stream.width(val_); 1831 | 1832 | return *this; 1833 | } 1834 | 1835 | ostream& escape_codes(bool val_ = true) 1836 | { 1837 | _stream.escape_codes(val_); 1838 | 1839 | return *this; 1840 | } 1841 | 1842 | ostream& line_wrap(bool val_ = true) 1843 | { 1844 | _stream.line_wrap(val_); 1845 | 1846 | return *this; 1847 | } 1848 | 1849 | ostream& first_wrap(bool val_ = true) 1850 | { 1851 | _stream.first_wrap(val_); 1852 | 1853 | return *this; 1854 | } 1855 | 1856 | ostream& auto_wrap(bool val_ = true) 1857 | { 1858 | _stream.auto_wrap(val_); 1859 | 1860 | return *this; 1861 | } 1862 | 1863 | ostream& word_break(bool val_ = true) 1864 | { 1865 | _stream.word_break(val_); 1866 | 1867 | return *this; 1868 | } 1869 | 1870 | ostream& white_space(bool val_ = true) 1871 | { 1872 | _stream.white_space(val_); 1873 | 1874 | return *this; 1875 | } 1876 | 1877 | ostream& indent(std::size_t val_ = 2) 1878 | { 1879 | _stream.indent(val_); 1880 | 1881 | return *this; 1882 | } 1883 | 1884 | ostream& level(std::size_t val_ = 0) 1885 | { 1886 | _stream.level(val_); 1887 | 1888 | return *this; 1889 | } 1890 | 1891 | protected: 1892 | 1893 | streambuf _stream; 1894 | }; // class ostream 1895 | 1896 | namespace iomanip 1897 | { 1898 | 1899 | class flush 1900 | { 1901 | public: 1902 | 1903 | friend Term::ostream& operator<<(Term::ostream& os_, flush const&) 1904 | { 1905 | os_.flush(); 1906 | 1907 | return os_; 1908 | } 1909 | 1910 | friend Term::ostream& operator<<(std::ostream& os_, flush const&) 1911 | { 1912 | auto& derived = dynamic_cast(os_); 1913 | derived.flush(); 1914 | 1915 | return derived; 1916 | } 1917 | }; // class flush 1918 | 1919 | class endl 1920 | { 1921 | public: 1922 | 1923 | friend Term::ostream& operator<<(Term::ostream& os_, endl const&) 1924 | { 1925 | os_.endl(); 1926 | 1927 | return os_; 1928 | } 1929 | 1930 | friend Term::ostream& operator<<(std::ostream& os_, endl const&) 1931 | { 1932 | auto& derived = dynamic_cast(os_); 1933 | derived.endl(); 1934 | 1935 | return derived; 1936 | } 1937 | }; // class endl 1938 | 1939 | class push 1940 | { 1941 | public: 1942 | 1943 | explicit push(std::size_t val_ = 1) : 1944 | _val {val_} 1945 | { 1946 | } 1947 | 1948 | friend Term::ostream& operator<<(Term::ostream& os_, push const& obj_) 1949 | { 1950 | os_.push(obj_._val); 1951 | 1952 | return os_; 1953 | } 1954 | 1955 | friend Term::ostream& operator<<(std::ostream& os_, push const& obj_) 1956 | { 1957 | auto& derived = dynamic_cast(os_); 1958 | derived.push(obj_._val); 1959 | 1960 | return derived; 1961 | } 1962 | 1963 | private: 1964 | 1965 | std::size_t _val; 1966 | }; // class push 1967 | 1968 | class pop 1969 | { 1970 | public: 1971 | 1972 | explicit pop(std::size_t val_ = 1) : 1973 | _val {val_} 1974 | { 1975 | } 1976 | 1977 | friend Term::ostream& operator<<(Term::ostream& os_, pop const& obj_) 1978 | { 1979 | os_.pop(obj_._val); 1980 | 1981 | return os_; 1982 | } 1983 | 1984 | friend Term::ostream& operator<<(std::ostream& os_, pop const& obj_) 1985 | { 1986 | auto& derived = dynamic_cast(os_); 1987 | derived.pop(obj_._val); 1988 | 1989 | return derived; 1990 | } 1991 | 1992 | private: 1993 | 1994 | std::size_t _val; 1995 | }; // class pop 1996 | 1997 | class line_wrap 1998 | { 1999 | public: 2000 | 2001 | explicit line_wrap(bool val_ = true) : 2002 | _val {val_} 2003 | { 2004 | } 2005 | 2006 | friend Term::ostream& operator<<(Term::ostream& os_, line_wrap const& obj_) 2007 | { 2008 | os_.line_wrap(obj_._val); 2009 | 2010 | return os_; 2011 | } 2012 | 2013 | friend Term::ostream& operator<<(std::ostream& os_, line_wrap const& obj_) 2014 | { 2015 | auto& derived = dynamic_cast(os_); 2016 | derived.line_wrap(obj_._val); 2017 | 2018 | return derived; 2019 | } 2020 | 2021 | private: 2022 | 2023 | bool _val; 2024 | }; // class line_wrap 2025 | 2026 | class first_wrap 2027 | { 2028 | public: 2029 | 2030 | explicit first_wrap(bool val_ = true) : 2031 | _val {val_} 2032 | { 2033 | } 2034 | 2035 | friend Term::ostream& operator<<(Term::ostream& os_, first_wrap const& obj_) 2036 | { 2037 | os_.first_wrap(obj_._val); 2038 | 2039 | return os_; 2040 | } 2041 | 2042 | friend Term::ostream& operator<<(std::ostream& os_, first_wrap const& obj_) 2043 | { 2044 | auto& derived = dynamic_cast(os_); 2045 | derived.first_wrap(obj_._val); 2046 | 2047 | return derived; 2048 | } 2049 | 2050 | private: 2051 | 2052 | bool _val; 2053 | }; // class first_wrap 2054 | 2055 | class word_break 2056 | { 2057 | public: 2058 | 2059 | explicit word_break(bool val_ = true) : 2060 | _val {val_} 2061 | { 2062 | } 2063 | 2064 | friend Term::ostream& operator<<(Term::ostream& os_, word_break const& obj_) 2065 | { 2066 | os_.word_break(obj_._val); 2067 | 2068 | return os_; 2069 | } 2070 | 2071 | friend Term::ostream& operator<<(std::ostream& os_, word_break const& obj_) 2072 | { 2073 | auto& derived = dynamic_cast(os_); 2074 | derived.word_break(obj_._val); 2075 | 2076 | return derived; 2077 | } 2078 | 2079 | private: 2080 | 2081 | bool _val; 2082 | }; // class word_break 2083 | 2084 | class white_space 2085 | { 2086 | public: 2087 | 2088 | explicit white_space(bool val_ = true) : 2089 | _val {val_} 2090 | { 2091 | } 2092 | 2093 | friend Term::ostream& operator<<(Term::ostream& os_, white_space const& obj_) 2094 | { 2095 | os_.white_space(obj_._val); 2096 | 2097 | return os_; 2098 | } 2099 | 2100 | friend Term::ostream& operator<<(std::ostream& os_, white_space const& obj_) 2101 | { 2102 | auto& derived = dynamic_cast(os_); 2103 | derived.white_space(obj_._val); 2104 | 2105 | return derived; 2106 | } 2107 | 2108 | private: 2109 | 2110 | bool _val; 2111 | }; // class white_space 2112 | 2113 | class escape_codes 2114 | { 2115 | public: 2116 | 2117 | explicit escape_codes(bool val_ = true) : 2118 | _val {val_} 2119 | { 2120 | } 2121 | 2122 | friend Term::ostream& operator<<(Term::ostream& os_, escape_codes const& obj_) 2123 | { 2124 | os_.escape_codes(obj_._val); 2125 | 2126 | return os_; 2127 | } 2128 | 2129 | friend Term::ostream& operator<<(std::ostream& os_, escape_codes const& obj_) 2130 | { 2131 | auto& derived = dynamic_cast(os_); 2132 | derived.escape_codes(obj_._val); 2133 | 2134 | return derived; 2135 | } 2136 | 2137 | private: 2138 | 2139 | bool _val; 2140 | }; // class escape_codes 2141 | 2142 | class width 2143 | { 2144 | public: 2145 | 2146 | explicit width(bool val_ = true) : 2147 | _val {val_} 2148 | { 2149 | } 2150 | 2151 | friend Term::ostream& operator<<(Term::ostream& os_, width const& obj_) 2152 | { 2153 | os_.width(obj_._val); 2154 | 2155 | return os_; 2156 | } 2157 | 2158 | friend Term::ostream& operator<<(std::ostream& os_, width const& obj_) 2159 | { 2160 | auto& derived = dynamic_cast(os_); 2161 | derived.width(obj_._val); 2162 | 2163 | return derived; 2164 | } 2165 | 2166 | private: 2167 | 2168 | bool _val; 2169 | }; // class width 2170 | 2171 | class indent 2172 | { 2173 | public: 2174 | 2175 | explicit indent(bool val_ = true) : 2176 | _val {val_} 2177 | { 2178 | } 2179 | 2180 | friend Term::ostream& operator<<(Term::ostream& os_, indent const& obj_) 2181 | { 2182 | os_.indent(obj_._val); 2183 | 2184 | return os_; 2185 | } 2186 | 2187 | friend Term::ostream& operator<<(std::ostream& os_, indent const& obj_) 2188 | { 2189 | auto& derived = dynamic_cast(os_); 2190 | derived.indent(obj_._val); 2191 | 2192 | return derived; 2193 | } 2194 | 2195 | private: 2196 | 2197 | bool _val; 2198 | }; // class indent 2199 | 2200 | class level 2201 | { 2202 | public: 2203 | 2204 | explicit level(bool val_ = true) : 2205 | _val {val_} 2206 | { 2207 | } 2208 | 2209 | friend Term::ostream& operator<<(Term::ostream& os_, level const& obj_) 2210 | { 2211 | os_.level(obj_._val); 2212 | 2213 | return os_; 2214 | } 2215 | 2216 | friend Term::ostream& operator<<(std::ostream& os_, level const& obj_) 2217 | { 2218 | auto& derived = dynamic_cast(os_); 2219 | derived.level(obj_._val); 2220 | 2221 | return derived; 2222 | } 2223 | 2224 | private: 2225 | 2226 | bool _val; 2227 | }; // class level 2228 | 2229 | } // namespace iomanip 2230 | 2231 | } // namespace OB::Term 2232 | 2233 | #endif // OB_TERM_HH 2234 | --------------------------------------------------------------------------------