For more human readable and shell parsable output.
Signed-off-by: Andreas Peters <andreas.joachim.peters@cern.ch>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
+#include <string>
+#include <set>
+#include <boost/format.hpp>
+
+
// -----------------------
namespace ceph {
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;
}
m_ss << data;
}
-const char *XMLFormatter::XML_1_DTD =
+const char *XMLFormatter::XML_1_DTD =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
XMLFormatter::XMLFormatter(bool pretty)
return std::string(&escaped[0]);
}
+TableFormatter::TableFormatter(bool keyval) : m_keyval(keyval)
+{
+ reset();
+}
+
+void TableFormatter::flush(std::ostream& os)
+{
+ finish_pending_string();
+ std::vector<size_t> column_size = m_column_size;
+ std::vector<std::string> column_name = m_column_name;
+
+ std::set<int> 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<column_size.size(); i++) {
+ if (column_size[i] != m_column_size[i]) {
+ need_header = true;
+ break;
+ }
+ }
+ } else {
+ need_header = true;
+ }
+
+ if (need_header) {
+ // first row always needs a header if there wasn't one before
+ need_header_set.insert(0);
+ }
+
+ m_column_size = column_size;
+ for (size_t i=0; i< m_vec.size(); i++) {
+ if (i==0) {
+ if (need_header_set.count(i)) {
+ // print the header
+ if (!m_keyval) {
+ os << "+";
+ for (size_t j=0; j< m_vec[i].size(); j++) {
+ for (size_t v=0; v< m_column_size[j]+3; v++)
+ os << "-";
+ os << "+";
+ }
+ os << "\n";
+ os << "|";
+
+ for (size_t j=0; j< m_vec[i].size(); j++) {
+ os << " ";
+ std::stringstream fs;
+ fs << boost::format ("%%-%is") % (m_column_size[j]+2);
+ os << boost::format (fs.str()) % m_vec[i][j].first;
+ os << "|";
+ }
+ os << "\n";
+ os << "+";
+ for (size_t j=0; j< m_vec[i].size(); j++) {
+ for (size_t v=0; v< m_column_size[j]+3;v++)
+ os << "-";
+ os << "+";
+ }
+ os << "\n";
+ }
+ }
+ }
+ // print body
+ if (!m_keyval)
+ os << "|";
+ for (size_t j=0; j< m_vec[i].size(); j++) {
+ if (!m_keyval)
+ os << " ";
+ std::stringstream fs;
+
+ if (m_keyval) {
+ os << "key::";
+ os << m_vec[i][j].first;
+ os << "=";
+ os << "\"";
+ os << m_vec[i][j].second;
+ os << "\" ";
+ } else {
+ fs << boost::format ("%%-%is") % (m_column_size[j]+2);
+ os << boost::format (fs.str()) % m_vec[i][j].second;
+ os << "|";
+ }
+ }
+
+ os << "\n";
+ if (!m_keyval) {
+ if ( i == (m_vec.size()-1) ) {
+ // print trailer
+ os << "+";
+ for (size_t j=0; j< m_vec[i].size(); j++) {
+ for (size_t v=0; v< m_column_size[j]+3;v++)
+ os << "-";
+ os << "+";
+ }
+ os << "\n";
+ }
+ }
+ m_vec[i].clear();
+ }
+ m_vec.clear();
+}
+
+void TableFormatter::reset()
+{
+ m_ss.clear();
+ m_ss.str("");
+ m_section_cnt.clear();
+ m_column_size.clear();
+ m_section_open = 0;
+}
+
+void TableFormatter::open_object_section(const char *name)
+{
+ open_section_in_ns(name, NULL, NULL);
+}
+
+void TableFormatter::open_object_section_with_attrs(const char *name, const FormatterAttrs& attrs)
+{
+ open_section_in_ns(name, NULL, NULL);
+}
+
+void TableFormatter::open_object_section_in_ns(const char *name, const char *ns)
+{
+ open_section_in_ns(name, NULL, NULL);
+}
+
+void TableFormatter::open_array_section(const char *name)
+{
+ open_section_in_ns(name, NULL, NULL);
+}
+
+void TableFormatter::open_array_section_with_attrs(const char *name, const FormatterAttrs& attrs)
+{
+ open_section_in_ns(name, NULL, NULL);
+}
+
+void TableFormatter::open_array_section_in_ns(const char *name, const char *ns)
+{
+ open_section_in_ns(name, NULL, NULL);
+}
+
+void TableFormatter::open_section_in_ns(const char *name, const char *ns, const FormatterAttrs *attrs)
+{
+ m_section.push_back(name);
+ m_section_open++;
+}
+
+void TableFormatter::close_section()
+{
+ //
+ m_section_open--;
+ if (m_section.size()) {
+ m_section_cnt[m_section.back()]=0;
+ m_section.pop_back();
+ }
+}
+
+size_t TableFormatter::m_vec_index(const char *name)
+{
+ std::string key(name);
+
+ size_t i = m_vec.size();
+ if (i)
+ i--;
+
+ // make sure there are vectors to push back key/val pairs
+ if (!m_vec.size())
+ m_vec.resize(1);
+
+ if (m_vec.size()) {
+ if (m_vec[i].size()) {
+ if (m_vec[i][0].first == key) {
+ // start a new column if a key is repeated
+ m_vec.resize(m_vec.size()+1);
+ i++;
+ }
+ }
+ }
+
+ return i;
+}
+
+std::string TableFormatter::get_section_name(const char* name)
+{
+ std::string t_name=name;
+ for (size_t i=0; i< m_section.size(); i++)
+ {
+ t_name.insert(0,":");
+ t_name.insert(0,m_section[i]);
+ }
+ if (m_section_open) {
+ std::stringstream lss;
+ lss << t_name;
+ lss << "[";
+ lss << m_section_cnt[t_name]++;
+ lss << "]";
+ return lss.str();
+ } else {
+ return t_name;
+ }
+}
+
+void TableFormatter::dump_unsigned(const char *name, uint64_t u)
+{
+ finish_pending_string();
+ size_t i = m_vec_index(name);
+ m_ss << u;
+ m_vec[i].push_back(std::make_pair(get_section_name(name),m_ss.str()));
+ m_ss.clear();
+ m_ss.str("");
+}
+
+void TableFormatter::dump_int(const char *name, int64_t u)
+{
+ finish_pending_string();
+ size_t i = m_vec_index(name);
+ m_ss << u;
+ m_vec[i].push_back(std::make_pair(get_section_name(name),m_ss.str()));
+ m_ss.clear();
+ m_ss.str("");
+}
+
+void TableFormatter::dump_float(const char *name, double d)
+{
+ finish_pending_string();
+ size_t i = m_vec_index(name);
+ m_ss << d;
+
+ m_vec[i].push_back(std::make_pair(get_section_name(name),m_ss.str()));
+ m_ss.clear();
+ m_ss.str("");
+}
+
+void TableFormatter::dump_string(const char *name, std::string s)
+{
+ finish_pending_string();
+ size_t i = m_vec_index(name);
+ m_ss << s;
+
+ m_vec[i].push_back(std::make_pair(get_section_name(name),m_ss.str()));
+ m_ss.clear();
+ m_ss.str("");
+}
+
+void TableFormatter::dump_string_with_attrs(const char *name, std::string s, const FormatterAttrs& attrs)
+{
+ finish_pending_string();
+ size_t i = m_vec_index(name);
+
+ std::string attrs_str;
+ get_attrs_str(&attrs, attrs_str);
+ m_ss << attrs_str << s;
+
+ m_vec[i].push_back(std::make_pair(get_section_name(name),m_ss.str()));
+ m_ss.clear();
+ m_ss.str("");
+}
+
+
+void TableFormatter::dump_format_va(const char* name, const char *ns, bool quoted, const char *fmt, va_list ap)
+{
+ finish_pending_string();
+ char buf[LARGE_SIZE];
+ vsnprintf(buf, LARGE_SIZE, fmt, ap);
+
+ size_t i = m_vec_index(name);
+ if (ns) {
+ m_ss << ns << "." << buf;
+ } else
+ m_ss << buf;
+
+ m_vec[i].push_back(std::make_pair(get_section_name(name),m_ss.str()));
+ m_ss.clear();
+ m_ss.str("");
+}
+
+std::ostream& TableFormatter::dump_stream(const char *name)
+{
+ finish_pending_string();
+ // we don't support this
+ m_pending_name=name;
+ return m_ss;
+}
+
+int TableFormatter::get_len() const
+{
+ // we don't know the size until flush is called
+ return 0;
+}
+
+void TableFormatter::write_raw_data(const char *data)
+{
+ // not supported
}
+
+void TableFormatter::get_attrs_str(const FormatterAttrs *attrs, std::string& attrs_str)
+{
+ std::stringstream attrs_ss;
+
+ for (std::list<std::pair<std::string, std::string> >::const_iterator iter = attrs->attrs.begin();
+ iter != attrs->attrs.end(); ++iter) {
+ std::pair<std::string, std::string> 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);
+ }
+}
+}
+
#include <deque>
#include <iostream>
#include <list>
+#include <vector>
#include <ostream>
#include <sstream>
#include <stdarg.h>
#include <string>
+#include <map>
#include "include/buffer.h"
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);
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<std::pair<std::string,std::string> > > 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<std::string,int> m_section_cnt;
+ std::vector<size_t> m_column_size;
+ std::vector< std::string > m_column_name;
+};
+
+
}
#endif
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
--- /dev/null
+#include "gtest/gtest.h"
+
+#include "common/Formatter.h"
+#include <iostream>
+#include <sstream>
+#include <string>
+
+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:
+ */
+
+
+