common: Formatter: add TableFormatter class

For more human readable and shell parsable output.

Signed-off-by: Andreas Peters <andreas.joachim.peters@cern.ch>
This commit is contained in:
Andreas-Joachim Peters 2014-10-08 16:18:32 +02:00 committed by Loic Dachary
parent 1e444e9103
commit e797dcf6e9
4 changed files with 641 additions and 2 deletions

View File

@ -26,6 +26,11 @@
#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);
}
}
}

View 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

View 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

View File

@ -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:
*/