├── .travis.yml ├── LICENSE ├── README.md ├── demo.cc ├── drecho.cpp └── drecho.hpp /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | 4 | compiler: 5 | - clang 6 | - gcc 7 | 8 | install: 9 | - wget --quiet -O - https://raw.githubusercontent.com/r-lyeh/depot/master/travis.pre.sh | bash -x 10 | 11 | script: 12 | - wget --quiet -O - https://raw.githubusercontent.com/r-lyeh/depot/master/travis.build.sh | bash -x 13 | - wget --quiet -O - https://raw.githubusercontent.com/r-lyeh/depot/master/travis.run.sh | bash -x 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DrEcho :pill: 2 | ====== 3 | 4 | - Dr Echo to spice your terminal up. Written in C++11. 5 | - Dr Echo is cross-platform. 6 | - Dr Echo is zlib/libpng licensed. 7 | 8 | ### API 9 | 10 | ```c++ 11 | #include 12 | #include "drecho.hpp" 13 | 14 | // default settings 15 | const bool dr::log_timestamp = true; 16 | const bool dr::log_branch = true; 17 | const bool dr::log_branch_scope = true; 18 | const bool dr::log_text = true; 19 | const bool dr::log_errno = true; 20 | const bool dr::log_location = true; 21 | 22 | int main(void) { 23 | // hello world, classic 24 | std::cout << "classic hello world" << std::endl; 25 | 26 | // DrEcho spices up this 27 | dr::echo << "new hello world. notice the extra timestamp on the left" << std::endl; 28 | 29 | // DrEcho can also detect posix, OpenGL and win32 errors automatically 30 | errno = EAGAIN; 31 | dr::echo << "an error will be catched on this line" << std::endl; 32 | 33 | // Set up a few custom keywords to highlight 34 | dr::highlight( DR_YELLOW, { "warn", "warning" } ); 35 | dr::highlight( DR_GREEN, { "info", "debug" } ); 36 | dr::highlight( DR_CYAN, { "another", "branch" } ); 37 | dr::echo << "this is another warning with a few debug keywords" << std::endl; 38 | 39 | // Spice up cout from now 40 | dr::capture( std::cout ); 41 | std::cout << "this cout will be highlighted" << std::endl; 42 | 43 | // Showcase tree usage 44 | { 45 | dr::tab scope; 46 | std::cout << "this is branch #1" << std::endl; 47 | { 48 | dr::tab scope; 49 | std::cout << "this is branch #1.1" << std::endl; 50 | std::cout << "this is branch #1.2" << std::endl; 51 | { 52 | dr::tab scope; 53 | std::cout << "this is branch #1.2.1" << std::endl; 54 | std::cout << "this is branch #1.2.2" << std::endl; 55 | } 56 | std::cout << "this is branch #1.3" << std::endl; 57 | } 58 | std::cout << "this is branch #2" << std::endl; 59 | } 60 | 61 | // Release our spiced up cout 62 | dr::release( std::cout ); 63 | std::cout << ">> back to ordinary world" << std::endl; 64 | } 65 | ``` 66 | 67 | ### Possible output 68 | 69 | ![image](https://raw.github.com/r-lyeh/depot/master/drecho.png) 70 | 71 | ### Changelog 72 | - v1.0.0 (2016/04/11): Initial semantic versioning adherence 73 | -------------------------------------------------------------------------------- /demo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "drecho.hpp" 3 | 4 | // default settings 5 | const bool dr::log_timestamp = true; 6 | const bool dr::log_branch = true; 7 | const bool dr::log_branch_scope = true; 8 | const bool dr::log_text = true; 9 | const bool dr::log_errno = true; 10 | const bool dr::log_location = true; 11 | 12 | int main(void) { 13 | // hello world, classic 14 | std::cout << "classic hello world" << std::endl; 15 | 16 | // DrEcho spices up this 17 | dr::echo << "new hello world. notice the extra timestamp on the left" << std::endl; 18 | 19 | // DrEcho can also detect posix, OpenGL and win32 errors automatically 20 | errno = EAGAIN; 21 | dr::echo << "an error will be catched on this line" << std::endl; 22 | 23 | // Set up a few custom keywords to highlight 24 | dr::highlight( DR_YELLOW, { "warn", "warning" } ); 25 | dr::highlight( DR_GREEN, { "info", "debug" } ); 26 | dr::highlight( DR_CYAN, { "another", "branch" } ); 27 | dr::echo << "this is another warning with a few debug keywords" << std::endl; 28 | 29 | // Spice up cout from now 30 | dr::capture( std::cout ); 31 | std::cout << "this cout will be highlighted" << std::endl; 32 | 33 | // Showcase tree usage 34 | { 35 | dr::tab scope; 36 | std::cout << "this is branch #1" << std::endl; 37 | { 38 | dr::tab scope; 39 | std::cout << "this is branch #1.1" << std::endl; 40 | std::cout << "this is branch #1.2" << std::endl; 41 | { 42 | dr::tab scope; 43 | std::cout << "this is branch #1.2.1" << std::endl; 44 | std::cout << "this is branch #1.2.2" << std::endl; 45 | } 46 | std::cout << "this is branch #1.3" << std::endl; 47 | } 48 | std::cout << "this is branch #2" << std::endl; 49 | } 50 | 51 | // Release our spiced up cout 52 | dr::release( std::cout ); 53 | std::cout << ">> back to ordinary world" << std::endl; 54 | } 55 | -------------------------------------------------------------------------------- /drecho.cpp: -------------------------------------------------------------------------------- 1 | // DrEcho spices your terminal up 2 | // - rlyeh, zlib/libpng licensed. 3 | 4 | // @todo optional DR_ASSERT on detected errors 5 | 6 | // @todo .html logs, unconditionally 7 | // @todo .flat logs on linux, with no ansi codes 8 | // @todo .ansi logs on windows, and then provide tint.exe viewer in tools/ 9 | 10 | // @todo hotkeys filtering in runtime (ie, strike 'd'e'b'u'g' keys to filter lines with 'debug' keywords only) 11 | // @todo clipboard filtering in runtime (ie, copy this 'debug' text to filter lines with 'debug' keywords only) 12 | // @todo also negative logic ('error -debug' : lines with 'error' keyword that have no 'debug' keywords in it) 13 | 14 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if defined(__APPLE__) 31 | # include 32 | # include 33 | # include 34 | # define DR_GLCONTEXT() glXGetCurrentContext() 35 | #elif defined(_WIN32) 36 | # pragma comment(lib, "opengl32.lib") 37 | # pragma comment(lib, "glu32.lib") 38 | # include 39 | # include 40 | # include 41 | # define DR_GLCONTEXT() wglGetCurrentContext() 42 | #else 43 | # include 44 | # include 45 | # include 46 | # define DR_GLCONTEXT() glXGetCurrentContext() 47 | #endif 48 | 49 | #ifdef _WIN32 50 | # include 51 | # include 52 | # define $win(...) __VA_ARGS__ 53 | # define $welse(...) 54 | #else 55 | # include 56 | # include 57 | # define $win(...) 58 | # define $welse(...) __VA_ARGS__ 59 | #endif 60 | 61 | #ifdef _MSC_VER 62 | # define $msvc(...) __VA_ARGS__ 63 | # define $melse(...) 64 | #else 65 | # define $msvc(...) 66 | # define $melse(...) __VA_ARGS__ 67 | #endif 68 | 69 | #ifdef _OPENMP 70 | #include 71 | namespace dr { 72 | double clock() { 73 | return omp_get_wtime(); 74 | } 75 | const double epoch = dr::clock(); 76 | } 77 | #else 78 | #include 79 | namespace dr { 80 | double clock() { 81 | static const auto epoch = std::chrono::steady_clock::now(); 82 | return std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::steady_clock::now() - epoch ).count() / 1000.0; 83 | } 84 | const double epoch = dr::clock(); 85 | } 86 | #endif 87 | 88 | #define DR_CLOCK fmod( dr::clock() - dr::epoch, 10000. ) 89 | #define DR_CLOCKs "%08.3fs" 90 | 91 | #define DR_QUOTE(...) #__VA_ARGS__ 92 | 93 | #include "drecho.hpp" 94 | #undef echo 95 | #undef $cout 96 | #undef $cerr 97 | #undef $clog 98 | 99 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 100 | 101 | namespace 102 | { 103 | typedef $win(WORD) $welse(const char*) PlatformColorCode; 104 | 105 | PlatformColorCode GetPlatformColorCode(int color) { 106 | switch (color) { 107 | // from http://en.wikipedia.org/wiki/ANSI_escape_code 108 | case DR_BLACK: return $welse(NULL) $win(0); 109 | case DR_RED: return $welse("31") $win(FOREGROUND_RED | FOREGROUND_INTENSITY); 110 | case DR_GREEN: return $welse("32") $win(FOREGROUND_GREEN | FOREGROUND_INTENSITY); 111 | case DR_YELLOW: return $welse("33") $win(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); 112 | case DR_BLUE: return $welse("34") $win(FOREGROUND_BLUE | FOREGROUND_INTENSITY); 113 | case DR_MAGENTA: return $welse("35") $win(FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY); 114 | case DR_CYAN: return $welse("36") $win(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY); 115 | $win( default: case DR_DEFAULT: ) 116 | case DR_WHITE: return $welse("37") $win(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); 117 | 118 | $welse( default: case DR_DEFAULT: ) 119 | case DR_GRAY: return $welse("90") $win(FOREGROUND_INTENSITY); 120 | case DR_RED_ALT: return $welse("91") $win(FOREGROUND_RED); 121 | case DR_GREEN_ALT: return $welse("92") $win(FOREGROUND_GREEN); 122 | case DR_YELLOW_ALT: return $welse("93") $win(FOREGROUND_RED | FOREGROUND_GREEN); 123 | case DR_BLUE_ALT: return $welse("94") $win(FOREGROUND_BLUE); 124 | case DR_MAGENTA_ALT: return $welse("95") $win(FOREGROUND_BLUE | FOREGROUND_RED); 125 | case DR_CYAN_ALT: return $welse("96") $win(FOREGROUND_BLUE | FOREGROUND_GREEN); 126 | case DR_WHITE_ALT: return $welse("97") $win(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); 127 | } 128 | } 129 | 130 | template 131 | std::string to_string( T number ) { 132 | std::stringstream ss; 133 | return ss << number ? ss.str() : std::string("0"); 134 | } 135 | } 136 | 137 | namespace dr 138 | { 139 | // excerpt from https://github.com/r-lyeh/apathy library following 140 | namespace apathy 141 | { 142 | std::deque split( const std::string &str, char sep ) 143 | { 144 | std::deque tokens; 145 | tokens.push_back( std::string() ); 146 | 147 | for( std::string::const_iterator it = str.begin(), end = str.end(); it != end; ++it ) 148 | { 149 | if( *it == sep ) 150 | { 151 | tokens.push_back( std::string() + sep ); 152 | tokens.push_back( std::string() ); 153 | } 154 | else 155 | { 156 | tokens.back() += *it; 157 | } 158 | } 159 | 160 | return tokens; 161 | } 162 | 163 | class sbb : public std::streambuf 164 | { 165 | public: 166 | 167 | typedef void (*proc)( bool open, bool feed, bool close, const std::string &text ); 168 | typedef std::set< proc > set; 169 | set cb; 170 | 171 | sbb() 172 | {} 173 | 174 | sbb( const sbb &other ) { 175 | operator=(other); 176 | } 177 | 178 | sbb &operator=( const sbb &other ) { 179 | if( this != &other ) { 180 | cb = other.cb; 181 | } 182 | return *this; 183 | } 184 | 185 | sbb( void (*cbb)( bool, bool, bool, const std::string & ) ) { 186 | insert( cbb ); 187 | } 188 | 189 | ~sbb() { 190 | clear(); 191 | } 192 | 193 | void log( const std::string &line ) { 194 | if( !line.size() ) 195 | return; 196 | 197 | std::deque lines = split( line, '\n' ); 198 | 199 | for( set::iterator jt = cb.begin(), jend = cb.end(); jt != jend; ++jt ) 200 | for( std::deque::iterator it = lines.begin(), end = lines.end(); it != end; ++it ) 201 | { 202 | if( *it != "\n" ) 203 | (**jt)( false, false, false, *it ); 204 | else 205 | (**jt)( false, true, false, std::string() ); 206 | } 207 | } 208 | 209 | virtual int_type overflow( int_type c = traits_type::eof() ) { 210 | return log( std::string() + (char)(c) ), 1; 211 | } 212 | 213 | virtual std::streamsize xsputn( const char *c_str, std::streamsize n ) { 214 | return log( std::string( c_str, (unsigned)n ) ), n; 215 | } 216 | 217 | void clear() { 218 | for( const auto &jt : cb ) { 219 | (*jt)( false, false, true, std::string() ); 220 | } 221 | cb.clear(); 222 | } 223 | 224 | void insert( proc p ) { 225 | if( !p ) 226 | return; 227 | 228 | // make a dummy call to ensure any static object of this callback are deleted after ~sbb() call (RAII) 229 | p( 0, 0, 0, std::string() ); 230 | p( true, false, false, std::string() ); 231 | 232 | // insert into map 233 | cb.insert( p ); 234 | } 235 | 236 | void erase( proc p ) { 237 | p( false, false, true, std::string() ); 238 | cb.erase( p ); 239 | } 240 | }; 241 | 242 | struct captured_ostream { 243 | std::streambuf *copy = 0; 244 | sbb sb; 245 | }; 246 | 247 | std::map< std::ostream *, captured_ostream > loggers; 248 | 249 | namespace ostream 250 | { 251 | void attach( std::ostream &_os, void (*custom_stream_callback)( bool open, bool feed, bool close, const std::string &line ) ) 252 | { 253 | std::ostream *os = &_os; 254 | 255 | ( loggers[ os ] = loggers[ os ] ); 256 | 257 | if( !loggers[ os ].copy ) 258 | { 259 | // capture ostream 260 | loggers[ os ].copy = os->rdbuf( &loggers[ os ].sb ); 261 | } 262 | 263 | loggers[ os ].sb.insert( custom_stream_callback ); 264 | } 265 | 266 | void detach( std::ostream &_os, void (*custom_stream_callback)( bool open, bool feed, bool close, const std::string &line ) ) 267 | { 268 | std::ostream *os = &_os; 269 | 270 | attach( _os, custom_stream_callback ); 271 | 272 | loggers[ os ].sb.erase( custom_stream_callback ); 273 | 274 | if( !loggers[ os ].sb.cb.size() ) 275 | { 276 | // release original stream 277 | os->rdbuf( loggers[ os ].copy ); 278 | } 279 | } 280 | 281 | void detach( std::ostream &_os ) 282 | { 283 | std::ostream *os = &_os; 284 | 285 | ( loggers[ os ] = loggers[ os ] ).sb.clear(); 286 | 287 | // release original stream 288 | os->rdbuf( loggers[ os ].copy ); 289 | } 290 | 291 | std::ostream &make( void (*proc)( bool open, bool feed, bool close, const std::string &line ) ) 292 | { 293 | static struct container 294 | { 295 | std::map< void (*)( bool open, bool feed, bool close, const std::string &text ), sbb > map; 296 | std::vector< std::ostream * > list; 297 | 298 | container() 299 | {} 300 | 301 | ~container() 302 | { 303 | for( std::vector< std::ostream * >::const_iterator 304 | it = list.begin(), end = list.end(); it != end; ++it ) 305 | delete *it; 306 | } 307 | 308 | std::ostream &insert( void (*proc)( bool open, bool feed, bool close, const std::string &text ) ) 309 | { 310 | ( map[ proc ] = map[ proc ] ) = sbb(proc); 311 | 312 | list.push_back( new std::ostream( &map[proc] ) ); 313 | return *list.back(); 314 | } 315 | } _; 316 | 317 | return _.insert( proc ); 318 | } 319 | } // ns ::apathy::ostream 320 | } // ns ::apathy 321 | } // ns :: 322 | 323 | 324 | namespace dr 325 | { 326 | int printf( int color, const char* fmt, ... ) { 327 | int num; 328 | va_list args; 329 | va_start(args, fmt); 330 | 331 | $win( 332 | const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); 333 | 334 | CONSOLE_SCREEN_BUFFER_INFO buffer_info; 335 | GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); 336 | const WORD previous_attr = buffer_info.wAttributes; 337 | 338 | fflush(stdout); 339 | SetConsoleTextAttribute(stdout_handle, GetPlatformColorCode(color)); 340 | 341 | num = vprintf(fmt, args); 342 | 343 | fflush(stdout); 344 | SetConsoleTextAttribute(stdout_handle, previous_attr); 345 | ) 346 | $welse( 347 | const char* color_code = GetPlatformColorCode(color); 348 | // 24-bit console ESC[ … 38;2;;; … m Select RGB foreground color 349 | // 256-color console ESC[38;5;m 350 | // 0x00-0x07: standard colors (as in ESC [ 30..37 m) 351 | // 0x08-0x0F: high intensity colors (as in ESC [ 90..97 m) 352 | // 0x10-0xE7: 6*6*6=216 colors: 16 + 36*r + 6*g + b (0≤r,g,b≤5) 353 | // 0xE8-0xFF: grayscale from black to white in 24 steps 354 | if (color_code) fprintf(stdout, "\033[0;3%sm", color_code); 355 | num = vprintf(fmt, args); 356 | ::printf("%s","\033[m"); 357 | ) 358 | 359 | va_end(args); 360 | return num; 361 | } 362 | 363 | int print( int color, const std::string &str ) { 364 | return dr::printf( color, "%s", str.c_str() ); 365 | } 366 | } 367 | 368 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 369 | 370 | namespace dr { 371 | 372 | std::string &file() { 373 | static std::string st; 374 | return st; 375 | } 376 | std::string &prefix() { 377 | static std::string st; 378 | return st; 379 | } 380 | std::string &spent() { 381 | static std::string st; 382 | return st; 383 | } 384 | unsigned &color() { 385 | static unsigned st = 0; 386 | return st; 387 | } 388 | 389 | scope::scope() : clock(dr::clock()) { 390 | prefix().push_back(' '); 391 | } 392 | scope::~scope() { 393 | spent() = std::string("scoped for ") + to_string( dr::clock() - clock ) + "s"; 394 | prefix().pop_back(); 395 | } 396 | } 397 | 398 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 399 | 400 | namespace dr { 401 | void clear_errors() { 402 | errno = 0; 403 | $win( SetLastError( ERROR_SUCCESS ) ); 404 | if( DR_GLCONTEXT() ) { 405 | do {} while( glGetError() != GL_NO_ERROR ); 406 | } 407 | } 408 | std::string get_any_error() { 409 | std::string err, gl, os; 410 | 411 | if( errno ) { 412 | err.resize( 2048 ); 413 | 414 | $msvc( strerror_s(&err[0], err.size(), errno) ); 415 | $melse( strcpy(&err[0], strerror(errno) ) ); 416 | 417 | err = std::string("(errno ") + to_string(errno) + ": " + err.c_str() + std::string(")"); 418 | } 419 | 420 | if( DR_GLCONTEXT() ) { 421 | GLenum gl_code; 422 | do { 423 | gl_code = glGetError(); 424 | if( gl_code != GL_NO_ERROR ) { 425 | gl += std::string("(glerrno ") + to_string(gl_code) + ": " + std::string((const char *)gluErrorString(gl_code)) + ")"; 426 | } 427 | } while( gl_code != GL_NO_ERROR ); 428 | } 429 | 430 | $win( 431 | DWORD os_code = GetLastError(); 432 | 433 | if( os_code ) { 434 | LPVOID lpMsgBuf; 435 | DWORD buflen = FormatMessage( 436 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 437 | FORMAT_MESSAGE_FROM_SYSTEM | 438 | FORMAT_MESSAGE_IGNORE_INSERTS, 439 | NULL, 440 | os_code, 441 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 442 | (LPTSTR) &lpMsgBuf, 443 | 0, NULL 444 | ); 445 | 446 | if( buflen > 2 ) { 447 | std::string text( (const char *)lpMsgBuf, buflen - 2 ); // remove \r\n 448 | os = std::string("(w32errno ") + to_string(os_code) + ": " + text + ")"; 449 | } 450 | 451 | if( buflen ) { 452 | LocalFree( lpMsgBuf ); 453 | } 454 | } 455 | ) 456 | 457 | return err + gl + os; 458 | } 459 | } 460 | 461 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 462 | 463 | namespace dr { 464 | 465 | namespace { 466 | std::set< std::ostream * > captured; 467 | std::map< std::string, DR_COLOR > vhighlights; 468 | } 469 | 470 | void logger( bool open, bool feed, bool close, const std::string &line ); 471 | 472 | bool capture( std::ostream &os_ ) { 473 | std::ostream *os = &os_; 474 | if( os != &dr::echo ) { 475 | if( dr::captured.find(os) == dr::captured.end() ) { 476 | dr::captured.insert(os); 477 | apathy::ostream::attach( *os, dr::logger ); 478 | return true; 479 | } 480 | } 481 | return false; 482 | } 483 | 484 | bool release( std::ostream &os_ ) { 485 | std::ostream *os = &os_; 486 | if( os != &dr::echo ) { 487 | if( dr::captured.find(os) != dr::captured.end() ) { 488 | dr::captured.erase(os); 489 | apathy::ostream::detach( *os ); 490 | return true; 491 | } 492 | } 493 | return false; 494 | } 495 | 496 | std::string lowercase( std::string text ) { 497 | for( auto &ch : text ) { 498 | if( ch >= 'A' && ch <= 'Z' ) ch = ( ch - 'A' ) + 'a'; 499 | } 500 | return text; 501 | } 502 | 503 | // tokenize_incl_separators 504 | std::vector< std::string > split( const std::string &string, const std::string &delimiters ) { 505 | std::string str; 506 | std::vector< std::string > tokens; 507 | for( auto &ch : string ) { 508 | if( delimiters.find_first_of( ch ) != std::string::npos ) { 509 | if( str.size() ) tokens.push_back( str ), str = ""; 510 | tokens.push_back( std::string() + ch ); 511 | } else str += ch; 512 | } 513 | return str.empty() ? tokens : ( tokens.push_back( str ), tokens ); 514 | } 515 | 516 | void highlight( DR_COLOR color, const std::vector &user_highlights ) { 517 | for( auto &highlight : user_highlights ) { 518 | dr::vhighlights[ lowercase(highlight) ] = color; 519 | } 520 | } 521 | 522 | std::vector highlights( DR_COLOR color ) { 523 | std::vector out; 524 | for( auto &hl : dr::vhighlights ) { 525 | if( hl.second == color ) out.push_back( hl.first ); 526 | } 527 | return out; 528 | } 529 | 530 | std::string location( const std::string &func, const std::string &file, int line ) { 531 | dr::file() = std::string("(at ") + func + "() " + file + ":" + to_string(line) + ")"; 532 | return std::string(); 533 | } 534 | 535 | void logger( bool open, bool feed, bool close, const std::string &line ) 536 | { 537 | static std::string cache; 538 | 539 | if( open ) 540 | {} 541 | else 542 | if( close ) 543 | {} 544 | else 545 | if( feed ) 546 | { 547 | if( cache.empty() ) 548 | return; 549 | 550 | static size_t num_errors = 0; 551 | std::string err = dr::get_any_error(); 552 | // num lines to display in red 553 | if( !err.empty() ) num_errors += 1; //5 554 | 555 | if( dr::log_timestamp ) { 556 | dr::printf( DR_WHITE_ALT, DR_CLOCKs " ", DR_CLOCK ); 557 | } 558 | 559 | std::string prefix = dr::prefix(); 560 | static int prevlvl = 0; 561 | int lvl = prefix.size(), last = lvl - 1; 562 | bool pushes = (lvl > prevlvl), pops = (lvl < prevlvl), same = (lvl == prevlvl); 563 | if( dr::log_branch ) { 564 | dr::printf( DR_GRAY, "|" ); 565 | for( int i = 0; i < prefix.size(); i ++ ) { 566 | int color = ( DR_GRAY + 1 + i ) % DR_TOTAL_COLORS; 567 | /**/ if( lvl < prevlvl ) { 568 | dr::printf( color, i==last ? "/" : "|" ); 569 | } 570 | else if( lvl > prevlvl ) { 571 | dr::printf( color, i==last ? "\\" : "|" ); 572 | } 573 | else { 574 | dr::printf( color, i==last ? "|" : "|" ); 575 | } 576 | } 577 | prevlvl = lvl; 578 | dr::printf( DR_DEFAULT, " " ); 579 | } 580 | 581 | if( dr::log_text ) { 582 | std::vector tags = split(lowercase(cache), "!\"#~$%&/(){}[]|,;.:<>+-/*@'\"\t\n\\ "); 583 | for( auto &tag : tags ) { 584 | auto find = dr::vhighlights.find(tag); 585 | int color = ( find == dr::vhighlights.end() ? DR_DEFAULT : find->second ); 586 | dr::print( color, tag ); 587 | } 588 | } 589 | 590 | if( dr::log_errno ) { 591 | dr::printf( num_errors ? DR_RED : DR_DEFAULT, " %s", err.c_str() ); 592 | } 593 | 594 | if( dr::log_location ) { 595 | if( dr::file().size() ) { 596 | dr::printf( DR_GRAY, " %s", dr::file().c_str() ); 597 | dr::file() = std::string(); 598 | } 599 | } 600 | 601 | if( dr::log_branch_scope ) { 602 | if( pops ) { 603 | dr::printf( DR_MAGENTA, " %s", spent().c_str() ); 604 | spent() = std::string(); 605 | } 606 | } 607 | 608 | num_errors = 0; 609 | dr::clear_errors(); 610 | 611 | fputs( "\n", stdout ); 612 | 613 | cache = std::string(); 614 | } 615 | else 616 | { 617 | cache += line; 618 | } 619 | } 620 | } 621 | 622 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 623 | 624 | namespace dr { 625 | std::ostream &echo = apathy::ostream::make(dr::logger); 626 | } 627 | 628 | #undef $welse 629 | #undef $win 630 | 631 | #undef $msvc 632 | #undef $melse 633 | -------------------------------------------------------------------------------- /drecho.hpp: -------------------------------------------------------------------------------- 1 | // DrEcho spices your terminal up 2 | // - rlyeh, zlib/libpng licensed. 3 | 4 | // @todo : see drecho.cpp 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define DRECHO_VERSION "1.0.0" // (2016/04/11): Initial semantic versioning adherence 15 | 16 | // DR API 17 | 18 | enum DR_COLOR { 19 | DR_RED, 20 | DR_GREEN, 21 | DR_YELLOW, 22 | DR_BLUE, 23 | DR_MAGENTA, 24 | DR_CYAN, 25 | DR_WHITE, 26 | DR_GRAY, 27 | DR_RED_ALT, 28 | DR_GREEN_ALT, 29 | DR_YELLOW_ALT, 30 | DR_BLUE_ALT, 31 | DR_MAGENTA_ALT, 32 | DR_CYAN_ALT, 33 | DR_WHITE_ALT, 34 | 35 | DR_NONE, 36 | DR_DEFAULT = DR_NONE, 37 | DR_TOTAL_COLORS = DR_NONE, 38 | 39 | DR_BLACK, 40 | 41 | DR_PURPLE = DR_MAGENTA, 42 | DR_PURPLE_ALT = DR_MAGENTA_ALT 43 | }; 44 | 45 | namespace dr { 46 | 47 | // app-defined boolean settings (default: true) { 48 | extern const bool log_timestamp; 49 | extern const bool log_branch; 50 | extern const bool log_branch_scope; 51 | extern const bool log_text; 52 | extern const bool log_errno; 53 | extern const bool log_location; 54 | // } 55 | 56 | // api for high-level logging 57 | extern std::ostream &echo; 58 | bool capture( std::ostream &os = std::cout ); 59 | bool release( std::ostream &os = std::cout ); 60 | void highlight( DR_COLOR color, const std::vector &highlights ); 61 | std::vector highlights( DR_COLOR color ); 62 | 63 | // api for low-level printing 64 | int print( int color, const std::string &str ); 65 | int printf( int color, const char *str, ... ); 66 | 67 | // api for errors 68 | std::string get_any_error(); 69 | void clear_errors(); 70 | 71 | // api for time 72 | double clock(); 73 | 74 | // api for scopes 75 | struct scope { 76 | scope(/*attribs: tab, time, color*/); 77 | ~scope(); 78 | double clock; 79 | }; 80 | 81 | using tab = scope; 82 | 83 | // helper classes and utilities 84 | struct concat : public std::stringstream { 85 | template concat & operator,(const T & val) { 86 | *this << val; 87 | return *this; 88 | } 89 | }; 90 | 91 | std::string location( const std::string &func, const std::string &file, int line ); 92 | } 93 | 94 | // -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< -- 8< 95 | 96 | #ifdef _MSC_VER 97 | #define DR_LINE __LINE__ 98 | #define DR_FUNC __FUNCTION__ 99 | #define DR_FILE __FILE__ 100 | #else 101 | #define DR_LINE __LINE__ 102 | #define DR_FUNC __PRETTY_FUNCTION__ 103 | #define DR_FILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) 104 | #endif 105 | 106 | // API for macros 107 | #if defined(NDEBUG) || defined(_NDEBUG) 108 | # define DR_LOG(...) 109 | # define DR_SCOPE(...) 110 | #else 111 | # define DR_LOG(...) do { dr::echo << ( dr::concat(), __VA_ARGS__ ).str() << std::endl; } while(0) 112 | # define DR_SCOPE(...) dr::scope dr_scope(__VA_ARGS__) 113 | # define echo echo << dr::location(DR_FUNC,DR_FILE,DR_LINE) 114 | # define $cerr cerr << dr::location(DR_FUNC,DR_FILE,DR_LINE) 115 | # define $cout cout << dr::location(DR_FUNC,DR_FILE,DR_LINE) 116 | # define $clog clog << dr::location(DR_FUNC,DR_FILE,DR_LINE) 117 | #endif 118 | --------------------------------------------------------------------------------