]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
common: Formatter: add TableFormatter class
authorAndreas-Joachim Peters <Andreas.Joachim.Peters@cern.ch>
Wed, 8 Oct 2014 14:18:32 +0000 (16:18 +0200)
committerLoic Dachary <ldachary@redhat.com>
Fri, 9 Jan 2015 12:20:43 +0000 (13:20 +0100)
For more human readable and shell parsable output.

Signed-off-by: Andreas Peters <andreas.joachim.peters@cern.ch>
src/common/Formatter.cc
src/common/Formatter.h
src/test/Makefile.am
src/test/common/test_tableformatter.cc [new file with mode: 0644]

index b9ad5bff75cf3ebf9bb3b587ad6bad298252b641..a438ad778e93ea4ddb264ad9594b08699a71d39c 100644 (file)
 #include <stdio.h>
 #include <stdlib.h>
 #include <vector>
+#include <string>
+#include <set>
+#include <boost/format.hpp>
+
+
 
 // -----------------------
 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 =
   "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
 
 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<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);
+  }
+}
+}
+
index 5b76c3a5c4e22b66eb8efeb5477b56ff1de74ecf..7c8a9a84c773685bdba445a85b6439852a84155c 100644 (file)
@@ -6,10 +6,12 @@
 #include <deque>
 #include <iostream>
 #include <list>
+#include <vector>
 #include <ostream>
 #include <sstream>
 #include <stdarg.h>
 #include <string>
+#include <map>
 
 #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<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
index d714f1ad719e326e5d0e9334950f3386d9949361..1b352f34194013d1b11e148e7c273aa778eb0ca6 100644 (file)
@@ -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 (file)
index 0000000..4c2abba
--- /dev/null
@@ -0,0 +1,236 @@
+#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:
+ */
+
+
+