/* KLayout Layout Viewer Copyright (C) 2006-2019 Matthias Koefferlein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "utTestConsole.h" #include "tlUnitTest.h" #if !defined(_MSC_VER) # include #endif #if !defined(_WIN32) # include # include #endif #if defined(_WIN32) # include #endif namespace ut { // ------------------------------------------------ // tl::Channel implementations for redirecting the log output class InfoChannel : public tl::Channel { public: InfoChannel (int verbosity) : m_verbosity (verbosity) { // .. nothing yet .. } protected: virtual void puts (const char *s) { if (tl::verbosity () >= m_verbosity) { TestConsole::instance ()->basic_write (s); } } virtual void endl () { if (tl::verbosity () >= m_verbosity) { TestConsole::instance ()->basic_write ("\n"); } } virtual void end () { TestConsole::instance ()->flush (); } virtual void begin () { // .. nothing yet .. } private: int m_verbosity; }; class WarningChannel : public tl::Channel { public: WarningChannel () { // .. nothing yet .. } protected: virtual void puts (const char *s) { TestConsole::instance ()->basic_write (s); } virtual void endl () { TestConsole::instance ()->basic_write ("\n"); } virtual void end () { TestConsole::instance ()->end (); TestConsole::instance ()->flush (); } virtual void begin () { TestConsole::instance ()->begin_warn (); } }; class ErrorChannel : public tl::Channel { public: ErrorChannel () { // .. nothing yet .. } protected: virtual void puts (const char *s) { TestConsole::instance ()->basic_write (s); } virtual void endl () { TestConsole::instance ()->basic_write ("\n"); } virtual void end () { TestConsole::instance ()->end (); TestConsole::instance ()->flush (); } virtual void begin () { TestConsole::instance ()->begin_error (); } }; class CtrlChannel : public tl::Channel { public: CtrlChannel (bool with_xml) : m_with_xml (with_xml) { // .. nothing yet .. } protected: virtual void puts (const char *s) { if (m_with_xml == tl::xml_format ()) { TestConsole::instance ()->raw_write (s); } } virtual void endl () { if (m_with_xml == tl::xml_format ()) { TestConsole::instance ()->raw_write ("\n"); } } virtual void end () { if (m_with_xml == tl::xml_format ()) { TestConsole::instance ()->end (); TestConsole::instance ()->flush (); } } virtual void begin () { if (m_with_xml == tl::xml_format ()) { TestConsole::instance ()->begin_info (); } } private: bool m_with_xml; }; // ------------------------------------------------ // TestConsole implementation const char *ANSI_RED = "\033[31;1m"; const char *ANSI_BLUE = "\033[34m"; const char *ANSI_GREEN = "\033[32m"; const char *ANSI_RESET = "\033[0m"; TestConsole::TestConsole (FILE *file) : m_file (file), m_col (0), m_max_col (250), m_columns (50), m_rows (0), m_file_is_tty (false) { ms_instance = this; #if defined(_MSC_VER) m_file_is_tty = false; #else m_file_is_tty = isatty (fileno (file)); #endif #if !defined(_WIN32) if (m_file_is_tty) { struct winsize ws; ioctl (fileno (stdout), TIOCGWINSZ, &ws); m_columns = std::max (0, (int) ws.ws_col); m_rows = std::max (0, (int) ws.ws_row); } #endif redirect (); } TestConsole::~TestConsole () { restore (); if (ms_instance == this) { ms_instance = 0; } } int TestConsole::columns () { int c = m_columns - tl::indent (); return c > 0 ? c : 0; } void TestConsole::write_str (const char *text, output_stream os) { if (os == OS_stderr) { begin_error (); basic_write (text); end (); } else { basic_write (text); } } void TestConsole::raw_write (const char *text) { fputs (text, m_file); } void TestConsole::flush () { fflush (m_file); } bool TestConsole::is_tty () { // NOTE: this assumes we are delivering to stdout return m_file_is_tty && ! tl::xml_format (); } void TestConsole::begin_error () { if (is_tty ()) { fputs (ANSI_RED, m_file); } } void TestConsole::begin_info () { if (is_tty ()) { fputs (ANSI_GREEN, m_file); } } void TestConsole::begin_warn () { if (is_tty ()) { fputs (ANSI_BLUE, m_file); } } void TestConsole::end () { if (is_tty ()) { fputs (ANSI_RESET, m_file); } } void TestConsole::basic_write (const char *s) { if (tl::xml_format ()) { for (const char *cp = s; *cp; ++cp) { if (*cp == '&') { fputs ("&", m_file); } else if (*cp == '<') { fputs ("<", m_file); } else if (*cp == '>') { fputs (">", m_file); } else { fputc (*cp, m_file); } } } else { // line length limitation - this assumes we are always printing to the same terminal // or we don't mix stderr/stdout. const char *cp; for (cp = s; *cp; ++cp) { if (*cp == '\n' || *cp == '\r') { m_col = 0; fputc (*cp, m_file); } else { if (m_col == 0) { for (int i = 0; i < tl::indent (); ++i) { fputc (' ', m_file); } m_col = tl::indent (); } if (m_col > m_max_col) { // ignore char } else if (m_col == m_max_col) { fputs (" ...", m_file); ++m_col; } else if (*cp == '\033') { // skip ANSI escape sequences (no increment of s_col) const char *cpend = cp + 1; if (*cpend == '[') { ++cpend; while (*cpend && *cpend != 'm') { ++cpend; } if (*cpend) { ++cpend; } } while (cp != cpend) { fputc (*cp++, m_file); } --cp; } else { fputc (*cp, m_file); ++m_col; } } } } } void TestConsole::redirect () { // redirect the log channels tl::warn.clear (); tl::warn.add (new ut::WarningChannel (), true); tl::info.clear (); tl::info.add (new ut::InfoChannel (0), true); tl::log.clear (); tl::log.add (new ut::InfoChannel (10), true); tl::error.clear (); tl::error.add (new ut::ErrorChannel (), true); } void TestConsole::restore () { // TODO: we should basically restore the original channels tl::warn.clear (); tl::info.clear (); tl::log.clear (); tl::error.clear (); } TestConsole *TestConsole::ms_instance = 0; tl::LogTee noctrl (new CtrlChannel (false), true); tl::LogTee ctrl (new CtrlChannel (true), true); }