From e797dcf6e90061fb4c566f7aabdcd72368cb6398 Mon Sep 17 00:00:00 2001 From: Andreas-Joachim Peters Date: Wed, 8 Oct 2014 16:18:32 +0200 Subject: [PATCH] common: Formatter: add TableFormatter class For more human readable and shell parsable output. Signed-off-by: Andreas Peters --- src/common/Formatter.cc | 354 ++++++++++++++++++++++++- src/common/Formatter.h | 49 +++- src/test/Makefile.am | 4 + src/test/common/test_tableformatter.cc | 236 +++++++++++++++++ 4 files changed, 641 insertions(+), 2 deletions(-) create mode 100644 src/test/common/test_tableformatter.cc diff --git a/src/common/Formatter.cc b/src/common/Formatter.cc index b9ad5bff75cf3..a438ad778e93e 100644 --- a/src/common/Formatter.cc +++ b/src/common/Formatter.cc @@ -26,6 +26,11 @@ #include #include #include +#include +#include +#include + + // ----------------------- namespace ceph { @@ -77,6 +82,10 @@ new_formatter(const std::string &type) return new XMLFormatter(false); else if (mytype == "xml-pretty") return new XMLFormatter(true); + else if (mytype == "table") + return new TableFormatter(); + else if (mytype == "table-kv") + return new TableFormatter(true); else return (Formatter *)NULL; } @@ -294,7 +303,7 @@ void JSONFormatter::write_raw_data(const char *data) m_ss << data; } -const char *XMLFormatter::XML_1_DTD = +const char *XMLFormatter::XML_1_DTD = ""; XMLFormatter::XMLFormatter(bool pretty) @@ -509,4 +518,347 @@ std::string XMLFormatter::escape_xml_str(const char *str) return std::string(&escaped[0]); } +TableFormatter::TableFormatter(bool keyval) : m_keyval(keyval) +{ + reset(); +} + +void TableFormatter::flush(std::ostream& os) +{ + finish_pending_string(); + std::vector column_size = m_column_size; + std::vector column_name = m_column_name; + + std::set need_header_set; + + // auto-sizing columns + for (size_t i=0; i< m_vec.size(); i++) { + for (size_t j=0; j< m_vec[i].size(); j++) { + column_size.resize(m_vec[i].size()); + column_name.resize(m_vec[i].size()); + if (i>0) { + if (m_vec[i-1][j] != m_vec[i][j]) { + // changing row labels require to show the header + need_header_set.insert(i); + column_name[i] = m_vec[i][j].first; + } + } else { + column_name[i] = m_vec[i][j].first; + } + + if (m_vec[i][j].second.length()> column_size[j]) + column_size[j]=m_vec[i][j].second.length(); + if (m_vec[i][j].first.length()> column_size[j]) + column_size[j]=m_vec[i][j].first.length(); + } + } + + bool need_header=false; + if ( (column_size.size() == m_column_size.size() ) ) { + for (size_t i=0; i >::const_iterator iter = attrs->attrs.begin(); + iter != attrs->attrs.end(); ++iter) { + std::pair p = *iter; + attrs_ss << " " << p.first << "=" << "\"" << p.second << "\""; + } + + attrs_str = attrs_ss.str(); +} + +void TableFormatter::finish_pending_string() +{ + if (m_pending_name.length()) { + std::string ss = m_ss.str(); + m_ss.clear(); + m_ss.str(""); + std::string pending_name=m_pending_name; + m_pending_name=""; + dump_string(pending_name.c_str(),ss); + } +} +} + diff --git a/src/common/Formatter.h b/src/common/Formatter.h index 5b76c3a5c4e22..7c8a9a84c7736 100644 --- a/src/common/Formatter.h +++ b/src/common/Formatter.h @@ -6,10 +6,12 @@ #include #include #include +#include #include #include #include #include +#include #include "include/buffer.h" @@ -95,7 +97,7 @@ class JSONFormatter : public Formatter { bool is_array; json_formatter_stack_entry_d() : size(0), is_array(false) {} }; - + bool m_pretty; void open_section(const char *name, bool is_array); void print_quoted_string(const char *s); @@ -146,5 +148,50 @@ class XMLFormatter : public Formatter { std::string m_pending_string_name; }; +class TableFormatter : public Formatter { + public: + TableFormatter(bool keyval=false); + + void flush(std::ostream& os); + void reset(); + virtual void open_array_section(const char *name); + void open_array_section_in_ns(const char *name, const char *ns); + void open_object_section(const char *name); + void open_object_section_in_ns(const char *name, const char *ns); + + void open_array_section_with_attrs(const char *name, const FormatterAttrs& attrs); + void open_object_section_with_attrs(const char *name, const FormatterAttrs& attrs); + + void close_section(); + void dump_unsigned(const char *name, uint64_t u); + void dump_int(const char *name, int64_t u); + void dump_float(const char *name, double d); + void dump_string(const char *name, std::string s); + void dump_format_va(const char *name, const char *ns, bool quoted, const char *fmt, va_list ap); + void dump_string_with_attrs(const char *name, std::string s, const FormatterAttrs& attrs); + std::ostream& dump_stream(const char *name); + + int get_len() const; + void write_raw_data(const char *data); + void get_attrs_str(const FormatterAttrs *attrs, std::string& attrs_str); + + private: + void open_section_in_ns(const char *name, const char *ns, const FormatterAttrs *attrs); + std::vector< std::vector > > m_vec; + std::stringstream m_ss; + size_t m_vec_index(const char* name); + std::string get_section_name(const char* name); + void finish_pending_string(); + std::string m_pending_name; + bool m_keyval; + + int m_section_open; + std::vector< std::string > m_section; + std::map m_section_cnt; + std::vector m_column_size; + std::vector< std::string > m_column_name; +}; + + } #endif diff --git a/src/test/Makefile.am b/src/test/Makefile.am index d714f1ad719e3..1b352f3419401 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -652,6 +652,10 @@ unittest_msgr_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL) unittest_msgr_CXXFLAGS = $(UNITTEST_CXXFLAGS) check_PROGRAMS += unittest_msgr +unittest_tableformatter_SOURCES = test/common/test_tableformatter.cc +unittest_tableformatter_CXXFLAGS = $(UNITTEST_CXXFLAGS) +unittest_tableformatter_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL) +check_PROGRAMS += unittest_tableformatter check_SCRIPTS += test/pybind/test_ceph_argparse.py diff --git a/src/test/common/test_tableformatter.cc b/src/test/common/test_tableformatter.cc new file mode 100644 index 0000000000000..4c2abbadc5e4e --- /dev/null +++ b/src/test/common/test_tableformatter.cc @@ -0,0 +1,236 @@ +#include "gtest/gtest.h" + +#include "common/Formatter.h" +#include +#include +#include + +TEST(tableformatter, singleline) { + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n"; + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, multiline) { + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.dump_int("integer",20); + formatter.dump_float("float",20.0); + formatter.dump_string("string","string"); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "| 20 | 20 | string |\n" + "+----------+--------+---------+\n"; + + formatter.flush(sout); + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, multiflush) { + std::stringstream sout1; + std::stringstream sout2; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.flush(sout1); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp,sout1.str()); + + formatter.dump_int("integer",20); + formatter.dump_float("float",20.0); + formatter.dump_string("string","string"); + formatter.flush(sout2); + + cmp = "" + "| 20 | 20 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp,sout2.str()); + +} + +TEST(tableformatter, multireset) { + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.flush(sout); + formatter.reset(); + formatter.dump_int("integer",20); + formatter.dump_float("float",20.0); + formatter.dump_string("string","string"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 20 | 20 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, changingheaderlength) { + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.flush(sout); + formatter.dump_int("integer",20); + formatter.dump_float("float",20.0); + formatter.dump_string("string","stringstring"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+----------+--------+---------------+\n" + "| integer | float | string |\n" + "+----------+--------+---------------+\n" + "| 20 | 20 | stringstring |\n" + "+----------+--------+---------------+\n"; + + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, changingheader) { + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.flush(sout); + formatter.dump_int("longinteger",20); + formatter.dump_float("double",20.0); + formatter.dump_string("char*","stringstring"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+--------------+---------+---------------+\n" + "| longinteger | double | char* |\n" + "+--------------+---------+---------------+\n" + "| 20 | 20 | stringstring |\n" + "+--------------+---------+---------------+\n"; + + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, extendingheader) { + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer",10); + formatter.dump_float("float",10.0); + formatter.dump_string("string","string"); + formatter.flush(sout); + formatter.dump_int("integer",20); + formatter.dump_float("float",20.0); + formatter.dump_string("string","string"); + formatter.dump_string("char*","abcde"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+----------+--------+---------+--------+\n" + "| integer | float | string | char* |\n" + "+----------+--------+---------+--------+\n" + "| 20 | 20 | string | abcde |\n" + "+----------+--------+---------+--------+\n"; + + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, stream) { + std::stringstream sout; + TableFormatter* formatter = (TableFormatter*) new_formatter("table"); + formatter->dump_stream("integer") << 10; + formatter->dump_stream("float") << 10.0; + formatter->dump_stream("string") << "string"; + formatter->flush(sout); + delete formatter; + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp,sout.str()); +} + +TEST(tableformatter, multiline_keyval) { + std::stringstream sout; + TableFormatter* formatter = (TableFormatter*) new_formatter("table-kv"); + formatter->dump_int("integer",10); + formatter->dump_float("float",10.0); + formatter->dump_string("string","string"); + formatter->dump_int("integer",20); + formatter->dump_float("float",20.0); + formatter->dump_string("string","string"); + formatter->flush(sout); + delete formatter; + + std::string cmp = "" + "key::integer=\"10\" key::float=\"10\" key::string=\"string\" \n" + "key::integer=\"20\" key::float=\"20\" key::string=\"string\" \n"; + + EXPECT_EQ(cmp,sout.str()); +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; make -j4 && + * make unittest_tableformatter && + * ./unittest_tableformatter + * ' + * End: + */ + + + -- 2.39.5