From d58953e408f7b4d1b11daa119ee1068f7188fa49 Mon Sep 17 00:00:00 2001 From: Michael Davis <michael.davis@cern.ch> Date: Mon, 17 Jun 2019 17:26:16 +0200 Subject: [PATCH] [cta-admin] Adds generic formatting for stream text output --- cmdline/CtaAdminCmd.cpp | 51 +++++---- cmdline/CtaAdminTextFormatter.cpp | 170 ++++++++++++++++++------------ cmdline/CtaAdminTextFormatter.hpp | 111 +++++++++++++------ 3 files changed, 210 insertions(+), 122 deletions(-) diff --git a/cmdline/CtaAdminCmd.cpp b/cmdline/CtaAdminCmd.cpp index 6279e19ed8..cbd6ae7892 100644 --- a/cmdline/CtaAdminCmd.cpp +++ b/cmdline/CtaAdminCmd.cpp @@ -27,9 +27,14 @@ #include <cmdline/CtaAdminTextFormatter.hpp> -// global synchronisation flag between main thread and stream handler thread +// GLOBAL VARIABLES : used to pass information between main thread and stream handler thread + +// global synchronisation flag std::atomic<bool> isHeaderSent(false); +// initialise an output buffer of 5 lines +cta::admin::TextFormatter formattedText(5); + namespace XrdSsiPb { @@ -83,17 +88,17 @@ void IStreamBuffer<cta::xrd::Data>::DataCallback(cta::xrd::Data record) const } // Format results in a tabular format for a human else switch(record.data_case()) { - case Data::kAflsItem: CtaAdminTextFormatter::print(record.afls_item()); break; - case Data::kAflsSummary: CtaAdminTextFormatter::print(record.afls_summary()); break; - case Data::kFrlsItem: CtaAdminTextFormatter::print(record.frls_item()); break; - case Data::kFrlsSummary: CtaAdminTextFormatter::print(record.frls_summary()); break; - case Data::kLpaItem: CtaAdminTextFormatter::print(record.lpa_item()); break; - case Data::kLpaSummary: CtaAdminTextFormatter::print(record.lpa_summary()); break; - case Data::kLprItem: CtaAdminTextFormatter::print(record.lpr_item()); break; - case Data::kLprSummary: CtaAdminTextFormatter::print(record.lpr_summary()); break; - case Data::kTplsItem: CtaAdminTextFormatter::print(record.tpls_item()); break; - case Data::kTalsItem: CtaAdminTextFormatter::print(record.tals_item()); break; - case Data::kRelsItem: CtaAdminTextFormatter::print(record.rels_item()); break; + case Data::kAflsItem: formattedText.print(record.afls_item()); break; + case Data::kAflsSummary: TextFormatter::print(record.afls_summary()); break; + case Data::kFrlsItem: TextFormatter::print(record.frls_item()); break; + case Data::kFrlsSummary: TextFormatter::print(record.frls_summary()); break; + case Data::kLpaItem: TextFormatter::print(record.lpa_item()); break; + case Data::kLpaSummary: TextFormatter::print(record.lpa_summary()); break; + case Data::kLprItem: TextFormatter::print(record.lpr_item()); break; + case Data::kLprSummary: TextFormatter::print(record.lpr_summary()); break; + case Data::kTplsItem: TextFormatter::print(record.tpls_item()); break; + case Data::kTalsItem: TextFormatter::print(record.tals_item()); break; + case Data::kRelsItem: TextFormatter::print(record.rels_item()); break; default: throw std::runtime_error("Received invalid stream data from CTA Frontend."); } @@ -227,17 +232,17 @@ void CtaAdminCmd::send() const std::cout << response.message_txt(); // Print streaming response header if(!isJson()) switch(response.show_header()) { - case HeaderType::ARCHIVEFILE_LS: CtaAdminTextFormatter::printAfLsHeader(); break; - case HeaderType::ARCHIVEFILE_LS_SUMMARY: CtaAdminTextFormatter::printAfLsSummaryHeader(); break; - case HeaderType::FAILEDREQUEST_LS: CtaAdminTextFormatter::printFrLsHeader(); break; - case HeaderType::FAILEDREQUEST_LS_SUMMARY: CtaAdminTextFormatter::printFrLsSummaryHeader(); break; - case HeaderType::LISTPENDINGARCHIVES: CtaAdminTextFormatter::printLpaHeader(); break; - case HeaderType::LISTPENDINGARCHIVES_SUMMARY: CtaAdminTextFormatter::printLpaSummaryHeader(); break; - case HeaderType::LISTPENDINGRETRIEVES: CtaAdminTextFormatter::printLprHeader(); break; - case HeaderType::LISTPENDINGRETRIEVES_SUMMARY: CtaAdminTextFormatter::printLprSummaryHeader(); break; - case HeaderType::TAPEPOOL_LS: CtaAdminTextFormatter::printTpLsHeader(); break; - case HeaderType::TAPE_LS: CtaAdminTextFormatter::printTapeLsHeader(); break; - case HeaderType::REPACK_LS: CtaAdminTextFormatter::printRepackLsHeader(); break; + case HeaderType::ARCHIVEFILE_LS: formattedText.printAfLsHeader(); break; + case HeaderType::ARCHIVEFILE_LS_SUMMARY: TextFormatter::printAfLsSummaryHeader(); break; + case HeaderType::FAILEDREQUEST_LS: TextFormatter::printFrLsHeader(); break; + case HeaderType::FAILEDREQUEST_LS_SUMMARY: TextFormatter::printFrLsSummaryHeader(); break; + case HeaderType::LISTPENDINGARCHIVES: TextFormatter::printLpaHeader(); break; + case HeaderType::LISTPENDINGARCHIVES_SUMMARY: TextFormatter::printLpaSummaryHeader(); break; + case HeaderType::LISTPENDINGRETRIEVES: TextFormatter::printLprHeader(); break; + case HeaderType::LISTPENDINGRETRIEVES_SUMMARY: TextFormatter::printLprSummaryHeader(); break; + case HeaderType::TAPEPOOL_LS: TextFormatter::printTpLsHeader(); break; + case HeaderType::TAPE_LS: TextFormatter::printTapeLsHeader(); break; + case HeaderType::REPACK_LS: TextFormatter::printRepackLsHeader(); break; case HeaderType::NONE: default: break; } diff --git a/cmdline/CtaAdminTextFormatter.cpp b/cmdline/CtaAdminTextFormatter.cpp index 2c344e9a93..10c11a5808 100644 --- a/cmdline/CtaAdminTextFormatter.cpp +++ b/cmdline/CtaAdminTextFormatter.cpp @@ -24,58 +24,98 @@ namespace cta { namespace admin { -void CtaAdminTextFormatter::printAfLsHeader() +const std::string TextFormatter::TEXT_RED = "\x1b[31;1m"; +const std::string TextFormatter::TEXT_NORMAL = "\x1b[0m"; + +/* + * Convert time to string + * + * NOTE: ctime is not thread-safe! + */ +std::string timeToString(const time_t &time) { - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(11) << std::right << "archive id" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "copy no" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "vid" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "fseq" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "block id" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "instance" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "disk id" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "size" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "checksum type" << ' ' - << std::setfill(' ') << std::setw(14) << std::right << "checksum value" << ' ' - << std::setfill(' ') << std::setw(16) << std::right << "storage class" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "owner" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "group" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "creation time" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "ss vid" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "ss fseq" << ' ' - << "path" - << TEXT_NORMAL << std::endl; + std::string timeString(ctime(&time)); + timeString.resize(timeString.size()-1); //remove newline + return timeString; } -void CtaAdminTextFormatter::print(const cta::admin::ArchiveFileLsItem &afls_item) + +void TextFormatter::flush() { + if(m_outputBuffer.empty()) return; + + auto numCols = m_outputBuffer.front().size(); + std::vector<unsigned int> colSize(numCols); + + // Calculate column widths + for(auto &l : m_outputBuffer) { + if(l.size() != numCols) throw std::runtime_error("TextFormatter::flush(): incorrect number of columns"); + for(size_t c = 0; c < l.size(); ++c) { + if(colSize.at(c) < l.at(c).size()) colSize[c] = l.at(c).size(); + } + } + + // Output columns + for(auto &l : m_outputBuffer) { + for(size_t c = 0; c < l.size(); ++c) { + std::cout << std::setfill(' ') + << std::setw(colSize.at(c)+1) + << std::right + << l.at(c) << ' '; + } + std::cout << std::endl; + } + + // Empty buffer + m_outputBuffer.clear(); +} + + +void TextFormatter::printAfLsHeader() { + push_back( + "archive id", + "copy no", + "vid", + "fseq", + "block id", + "instance", + "disk id", + "size", + "checksum type", + "checksum value", + "storage class", + "owner", + "group", + "creation time", + "ss vid", + "ss fseq", + "path" + ); +} + +void TextFormatter::print(const cta::admin::ArchiveFileLsItem &afls_item) { - std::cout << std::setfill(' ') << std::setw(11) << std::right << afls_item.af().archive_id() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.copy_nb() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().vid() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().f_seq() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.tf().block_id() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.af().disk_instance() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.af().disk_id() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << afls_item.af().size() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << afls_item.af().cs().type() << ' ' - << std::setfill(' ') << std::setw(14) << std::right << afls_item.af().cs().value() << ' ' - << std::setfill(' ') << std::setw(16) << std::right << afls_item.af().storage_class() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.af().df().owner() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.af().df().group() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << afls_item.af().creation_time() << ' '; - - if (afls_item.tf().superseded_by_vid().size()) { - std::cout << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().superseded_by_vid() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().superseded_by_f_seq() << ' '; - } else { - std::cout << std::setfill(' ') << std::setw(7) << std::right << "-" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "-" << ' '; - } - std::cout << afls_item.af().df().path() - << std::endl; + push_back( + afls_item.af().archive_id(), + afls_item.copy_nb(), + afls_item.tf().vid(), + afls_item.tf().f_seq(), + afls_item.tf().block_id(), + afls_item.af().disk_instance(), + afls_item.af().disk_id(), + afls_item.af().size(), + afls_item.af().cs().type(), + afls_item.af().cs().value(), + afls_item.af().storage_class(), + afls_item.af().df().owner(), + afls_item.af().df().group(), + afls_item.af().creation_time(), + afls_item.tf().superseded_by_vid().size() ? afls_item.tf().superseded_by_vid() : "-", + afls_item.tf().superseded_by_vid().size() ? std::to_string(afls_item.tf().superseded_by_f_seq()) : "-", + afls_item.af().df().path() + ); } -void CtaAdminTextFormatter::printAfLsSummaryHeader() +void TextFormatter::printAfLsSummaryHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(13) << std::right << "total files" << ' ' @@ -83,14 +123,14 @@ void CtaAdminTextFormatter::printAfLsSummaryHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::ArchiveFileLsSummary &afls_summary) +void TextFormatter::print(const cta::admin::ArchiveFileLsSummary &afls_summary) { std::cout << std::setfill(' ') << std::setw(13) << std::right << afls_summary.total_files() << ' ' << std::setfill(' ') << std::setw(12) << std::right << afls_summary.total_size() << ' ' << std::endl; } -void CtaAdminTextFormatter::printFrLsHeader() +void TextFormatter::printFrLsHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(12) << std::right << "request type" << ' ' @@ -102,7 +142,7 @@ void CtaAdminTextFormatter::printFrLsHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::FailedRequestLsItem &frls_item) +void TextFormatter::print(const cta::admin::FailedRequestLsItem &frls_item) { std::string request_type; std::string tapepool_vid; @@ -133,7 +173,7 @@ void CtaAdminTextFormatter::print(const cta::admin::FailedRequestLsItem &frls_it } } -void CtaAdminTextFormatter::printFrLsSummaryHeader() +void TextFormatter::printFrLsSummaryHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(12) << std::right << "request type" << ' ' @@ -142,7 +182,7 @@ void CtaAdminTextFormatter::printFrLsSummaryHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::FailedRequestLsSummary &frls_summary) +void TextFormatter::print(const cta::admin::FailedRequestLsSummary &frls_summary) { std::string request_type = frls_summary.request_type() == cta::admin::RequestType::ARCHIVE_REQUEST ? "archive" : @@ -154,7 +194,7 @@ void CtaAdminTextFormatter::print(const cta::admin::FailedRequestLsSummary &frls << std::endl; } -void CtaAdminTextFormatter::printLpaHeader() +void TextFormatter::printLpaHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(18) << std::right << "tapepool" << ' ' @@ -172,7 +212,7 @@ void CtaAdminTextFormatter::printLpaHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingArchivesItem &lpa_item) +void TextFormatter::print(const cta::admin::ListPendingArchivesItem &lpa_item) { std::cout << std::setfill(' ') << std::setw(18) << std::right << lpa_item.tapepool() << ' ' << std::setfill(' ') << std::setw(11) << std::right << lpa_item.af().archive_id() << ' ' @@ -189,7 +229,7 @@ void CtaAdminTextFormatter::print(const cta::admin::ListPendingArchivesItem &lpa << std::endl; } -void CtaAdminTextFormatter::printLpaSummaryHeader() +void TextFormatter::printLpaSummaryHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(18) << std::right << "tapepool" << ' ' @@ -198,7 +238,7 @@ void CtaAdminTextFormatter::printLpaSummaryHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingArchivesSummary &lpa_summary) +void TextFormatter::print(const cta::admin::ListPendingArchivesSummary &lpa_summary) { std::cout << std::setfill(' ') << std::setw(18) << std::right << lpa_summary.tapepool() << ' ' << std::setfill(' ') << std::setw(13) << std::right << lpa_summary.total_files() << ' ' @@ -206,7 +246,7 @@ void CtaAdminTextFormatter::print(const cta::admin::ListPendingArchivesSummary & << std::endl; } -void CtaAdminTextFormatter::printLprHeader() +void TextFormatter::printLprHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(13) << std::right << "vid" << ' ' @@ -221,7 +261,7 @@ void CtaAdminTextFormatter::printLprHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingRetrievesItem &lpr_item) +void TextFormatter::print(const cta::admin::ListPendingRetrievesItem &lpr_item) { std::cout << std::setfill(' ') << std::setw(13) << std::right << lpr_item.tf().vid() << ' ' << std::setfill(' ') << std::setw(11) << std::right << lpr_item.af().archive_id() << ' ' @@ -235,7 +275,7 @@ void CtaAdminTextFormatter::print(const cta::admin::ListPendingRetrievesItem &lp << std::endl; } -void CtaAdminTextFormatter::printLprSummaryHeader() +void TextFormatter::printLprSummaryHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(13) << std::right << "vid" << ' ' @@ -244,7 +284,7 @@ void CtaAdminTextFormatter::printLprSummaryHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingRetrievesSummary &lpr_summary) +void TextFormatter::print(const cta::admin::ListPendingRetrievesSummary &lpr_summary) { std::cout << std::setfill(' ') << std::setw(13) << std::right << lpr_summary.vid() << ' ' << std::setfill(' ') << std::setw(13) << std::right << lpr_summary.total_files() << ' ' @@ -252,7 +292,7 @@ void CtaAdminTextFormatter::print(const cta::admin::ListPendingRetrievesSummary << std::endl; } -void CtaAdminTextFormatter::printTpLsHeader() +void TextFormatter::printTpLsHeader() { std::cout << TEXT_RED << std::setfill(' ') << std::setw(18) << std::right << "name" << ' ' @@ -276,7 +316,7 @@ void CtaAdminTextFormatter::printTpLsHeader() << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::printTapeLsHeader(){ +void TextFormatter::printTapeLsHeader(){ std::cout << TEXT_RED << std::setfill(' ') << std::setw(7) << std::right << "vid" << ' ' << std::setfill(' ') << std::setw(10) << std::right << "media type" << ' ' @@ -307,7 +347,7 @@ void CtaAdminTextFormatter::printTapeLsHeader(){ } -void CtaAdminTextFormatter::print(const cta::admin::TapeLsItem &tals_item){ +void TextFormatter::print(const cta::admin::TapeLsItem &tals_item){ std::cout << std::setfill(' ') << std::setw(7) << std::right << tals_item.vid() << ' ' << std::setfill(' ') << std::setw(10) << std::right << tals_item.media_type() << ' ' << std::setfill(' ') << std::setw(7) << std::right << tals_item.vendor() << ' ' @@ -350,7 +390,7 @@ void CtaAdminTextFormatter::print(const cta::admin::TapeLsItem &tals_item){ << std::endl; } -void CtaAdminTextFormatter::printRepackLsHeader(){ +void TextFormatter::printRepackLsHeader(){ std::cout << TEXT_RED << std::setfill(' ') << std::setw(7) << std::right << "vid" << ' ' << std::setfill(' ') << std::setw(50) << std::right << "repackBufferURL" << ' ' @@ -370,7 +410,7 @@ void CtaAdminTextFormatter::printRepackLsHeader(){ << TEXT_NORMAL << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::RepackLsItem &rels_item){ +void TextFormatter::print(const cta::admin::RepackLsItem &rels_item){ std::cout << std::setfill(' ') << std::setw(7) << std::right << rels_item.vid() << ' ' << std::setfill(' ') << std::setw(50) << std::right << rels_item.repack_buffer_url() << ' ' << std::setfill(' ') << std::setw(17) << std::right << rels_item.user_provided_files() << ' ' @@ -388,7 +428,7 @@ void CtaAdminTextFormatter::print(const cta::admin::RepackLsItem &rels_item){ << rels_item.status() << std::endl; } -void CtaAdminTextFormatter::print(const cta::admin::TapePoolLsItem &tpls_item) +void TextFormatter::print(const cta::admin::TapePoolLsItem &tpls_item) { std::string encrypt_str = tpls_item.encrypt() ? "true" : "false"; uint64_t avail = tpls_item.capacity_bytes() > tpls_item.data_bytes() ? diff --git a/cmdline/CtaAdminTextFormatter.hpp b/cmdline/CtaAdminTextFormatter.hpp index 3c3c36b3ef..30d2fe0593 100644 --- a/cmdline/CtaAdminTextFormatter.hpp +++ b/cmdline/CtaAdminTextFormatter.hpp @@ -24,46 +24,89 @@ namespace cta { namespace admin { -class CtaAdminTextFormatter +class TextFormatter { public: - // Output headers - static void printAfLsHeader(); - static void printAfLsSummaryHeader(); - static void printFrLsHeader(); - static void printFrLsSummaryHeader(); - static void printLpaHeader(); - static void printLpaSummaryHeader(); - static void printLprHeader(); - static void printLprSummaryHeader(); - static void printTpLsHeader(); - static void printTapeLsHeader(); - static void printRepackLsHeader(); + /*! + * Constructor + * + * @param[in] bufLines Number of text lines to buffer before flushing formatted output + * (Not used for JSON output which does not need to be formatted + * so can be streamed directly) + */ + TextFormatter(unsigned int bufLines = 1000) : + m_bufLines(bufLines) { + m_outputBuffer.reserve(bufLines); + } + + ~TextFormatter() { + flush(); + } + + // Output headers + void printAfLsHeader(); + static void printAfLsSummaryHeader(); + static void printFrLsHeader(); + static void printFrLsSummaryHeader(); + static void printLpaHeader(); + static void printLpaSummaryHeader(); + static void printLprHeader(); + static void printLprSummaryHeader(); + static void printTpLsHeader(); + static void printTapeLsHeader(); + static void printRepackLsHeader(); - // Output records - static void print(const ArchiveFileLsItem &afls_item); - static void print(const ArchiveFileLsSummary &afls_summary); - static void print(const FailedRequestLsItem &frls_item); - static void print(const FailedRequestLsSummary &frls_summary); - static void print(const ListPendingArchivesItem &lpa_item); - static void print(const ListPendingArchivesSummary &lpa_summary); - static void print(const ListPendingRetrievesItem &lpr_item); - static void print(const ListPendingRetrievesSummary &lpr_summary); - static void print(const TapePoolLsItem &tpls_item); - static void print(const TapeLsItem &tals_item); - static void print(const RepackLsItem &rels_item); + // Output records + void print(const ArchiveFileLsItem &afls_item); + static void print(const ArchiveFileLsSummary &afls_summary); + static void print(const FailedRequestLsItem &frls_item); + static void print(const FailedRequestLsSummary &frls_summary); + static void print(const ListPendingArchivesItem &lpa_item); + static void print(const ListPendingArchivesSummary &lpa_summary); + static void print(const ListPendingRetrievesItem &lpr_item); + static void print(const ListPendingRetrievesSummary &lpr_summary); + static void print(const TapePoolLsItem &tpls_item); + static void print(const TapeLsItem &tals_item); + static void print(const RepackLsItem &rels_item); private: - // Static method to convert time to string - static std::string timeToString(const time_t &time) - { - std::string timeString(ctime(&time)); - timeString.resize(timeString.size()-1); //remove newline - return timeString; - } + //! Add a line to the buffer + template<typename... Args> + void push_back(Args... args) { + std::vector<std::string> line; + buildVector(line, args...); + m_outputBuffer.push_back(line); + if(m_outputBuffer.size() >= m_bufLines) flush(); + } + + //! Recursive variadic function to build a log string from an arbitrary number of items of arbitrary type + template<typename T, typename... Args> + void buildVector(std::vector<std::string> &line, const T &item, Args... args) { + buildVector(line, item); + buildVector(line, args...); + } + + //! Base case function to add one item to the log + template<typename T> + void buildVector(std::vector<std::string> &line, const T &item) { + line.push_back(std::to_string(item)); + } + + void buildVector(std::vector<std::string> &line, const std::string &item) { + line.push_back(item); + } + + void buildVector(std::vector<std::string> &line, const char *item) { + line.push_back(std::string(item)); + } + + void flush(); //!< Flush buffer to stdout + + unsigned int m_bufLines; //!< Number of text lines to buffer before flushing formatted output + std::vector<std::vector<std::string>> m_outputBuffer; //!< Buffer for text output (not used for JSON) - static constexpr const char* const TEXT_RED = "\x1b[31;1m"; //!< Terminal formatting code for red text - static constexpr const char* const TEXT_NORMAL = "\x1b[0m"; //!< Terminal formatting code for normal text + static const std::string TEXT_RED; //!< Terminal formatting code for red text + static const std::string TEXT_NORMAL; //!< Terminal formatting code for normal text }; }} -- GitLab