]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
Add formatters
authorJosé Iván López González <jlopez@suse.com>
Wed, 30 Oct 2019 23:13:47 +0000 (23:13 +0000)
committerJosé Iván López González <jlopez@suse.com>
Wed, 30 Oct 2019 23:13:47 +0000 (23:13 +0000)
client/utils/CsvFormatter.cc [new file with mode: 0644]
client/utils/CsvFormatter.h [new file with mode: 0644]
client/utils/JsonFormatter.cc [new file with mode: 0644]
client/utils/JsonFormatter.h [new file with mode: 0644]
client/utils/Makefile.am
client/utils/TableFormatter.cc [new file with mode: 0644]
client/utils/TableFormatter.h [new file with mode: 0644]
testsuite/Makefile.am
testsuite/csv-formatter.cc [new file with mode: 0644]
testsuite/json-formatter.cc [new file with mode: 0644]

diff --git a/client/utils/CsvFormatter.cc b/client/utils/CsvFormatter.cc
new file mode 100644 (file)
index 0000000..b566b89
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/trim.hpp>
+
+#include "client/utils/CsvFormatter.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       namespace
+       {
+
+           const string DEFAULT_SEPARATOR = ",";
+
+
+           string double_quotes(const string value)
+           {
+               return boost::algorithm::replace_all_copy(value, "\"", "\"\"" );
+           }
+
+
+           string enclose_with_quotes(const string value)
+           {
+               return "\"" + value + "\"";
+           }
+
+       }
+
+
+       const string CsvFormatter::default_separator()
+       {
+           return DEFAULT_SEPARATOR;
+       }
+
+
+       CsvFormatter::CsvFormatter(
+           vector<string> columns,
+           vector<vector<string>> rows) :
+           _columns(columns), _rows(rows), _separator(default_separator())
+       {}
+
+
+       CsvFormatter::CsvFormatter(
+           vector<string> columns,
+           vector<vector<string>> rows,
+           const string separator) :
+           _columns(columns), _rows(rows), _separator(separator)
+       {}
+
+
+       string CsvFormatter::output() const
+       {
+           string cvs_output;
+
+           cvs_output = csv_line(_columns);
+
+           for (auto row : _rows)
+               cvs_output += csv_line(row);
+
+           return cvs_output;
+       }
+
+
+       string CsvFormatter::csv_line(vector<string> values) const
+       {
+           vector<string> csv_values;
+
+           for (auto value : values)
+               csv_values.push_back(csv_value(value));
+
+           return boost::algorithm::join(csv_values, _separator) + "\n";
+       }
+
+
+       string CsvFormatter::csv_value(const string value) const
+       {
+           string fixed_value = boost::algorithm::trim_copy(value);
+
+           if (has_special_chars(value))
+               fixed_value = enclose_with_quotes(double_quotes(value));
+
+           return fixed_value;
+       }
+
+
+       bool CsvFormatter::has_special_chars(const string value) const
+       {
+           vector<string> special_chars = { _separator, "\n", "\"" };
+
+           for (auto special_char : special_chars)
+           {
+               if (value.find(special_char) != string::npos)
+                   return true;
+           }
+
+           return false;
+       }
+
+    }
+}
\ No newline at end of file
diff --git a/client/utils/CsvFormatter.h b/client/utils/CsvFormatter.h
new file mode 100644 (file)
index 0000000..24c8c32
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_CSV_FORMATTER_H
+#define SNAPPER_CLI_CSV_FORMATTER_H
+
+#include <string>
+#include <vector>
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class CsvFormatter
+       {
+
+       public:
+
+           static const std::string default_separator();
+
+           CsvFormatter(
+               std::vector<std::string> columns,
+               std::vector<std::vector<std::string>> rows);
+
+           CsvFormatter(
+               std::vector<std::string> columns,
+               std::vector<std::vector<std::string>> rows,
+               const std::string separator);
+
+           std::string output() const;
+
+       private:
+
+           std::string csv_line(std::vector<std::string> values) const;
+
+           std::string csv_value(const std::string value) const;
+
+           bool has_special_chars(const std::string value) const;
+
+           std::vector<std::string> _columns;
+
+           std::vector<std::vector<std::string>> _rows;
+
+           const std::string _separator;
+
+       };
+
+    }
+}
+
+#endif
diff --git a/client/utils/JsonFormatter.cc b/client/utils/JsonFormatter.cc
new file mode 100644 (file)
index 0000000..4fd8150
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include "client/utils/JsonFormatter.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       namespace
+       {
+
+           string indent(u_int indent_level)
+           {
+               return string(indent_level * 2, ' ');
+           }
+
+
+           string escape(const string& value)
+           {
+               string fixed_value = value;
+
+               boost::algorithm::replace_all(fixed_value, "\\", "\\\\");
+               boost::algorithm::replace_all(fixed_value, "\"", "\\\"");
+
+               return fixed_value;
+           }
+
+
+           string quote(const string& value)
+           {
+               return "\"" + value + "\"";
+           }
+
+
+           string to_json(const string& value)
+           {
+               return quote(escape(boost::algorithm::trim_copy(value)));
+           }
+
+       }
+
+
+       JsonFormatter::JsonFormatter(const Data& data) :
+           _data(data), _inline(false)
+       {}
+
+
+       string JsonFormatter::output(u_int indent_level) const
+       {
+           string first_indent = _inline ? "" : indent(indent_level);
+
+           return first_indent + "{" + "\n" +
+               json_attributes(indent_level + 1) + "\n" +
+               indent(indent_level) + "}";
+       }
+
+
+       string JsonFormatter::json_attributes(u_int indent_level) const
+       {
+           vector<string> attributes;
+
+           for (auto& data_pair : _data)
+           {
+               string value = data_pair.second;
+
+               if (!skip_format_value(data_pair.first))
+                   value = to_json(value);
+
+               attributes.push_back(indent(indent_level) + to_json(data_pair.first) + ": " + value);
+           }
+
+           return boost::algorithm::join(attributes, ",\n");
+       }
+
+
+       bool JsonFormatter::skip_format_value(const string& key) const
+       {
+           auto it = find(_skip_format_values.begin(), _skip_format_values.end(), key);
+
+           if (it != _skip_format_values.end())
+               return true;
+
+           return false;
+       }
+
+
+       JsonFormatter::JsonFormatter::List::List(const vector<string>& data) :
+           _data(data)
+       {}
+
+
+       string JsonFormatter::List::output(u_int indent_level) const
+       {
+           return "[\n" +
+               boost::algorithm::join(_data, ",\n") + "\n" +
+               indent(indent_level) + "]";
+       }
+
+    }
+}
\ No newline at end of file
diff --git a/client/utils/JsonFormatter.h b/client/utils/JsonFormatter.h
new file mode 100644 (file)
index 0000000..79f7672
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_JSON_FORMATTER_H
+#define SNAPPER_CLI_JSON_FORMATTER_H
+
+#include <string>
+#include <vector>
+#include <utility>
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       /* Very simplistic JSON formatter, but enough for current requirements.
+        *
+        * If needed, this could be replaced by some modern library, for example:
+        * https://github.com/nlohmann/json
+        */
+       class JsonFormatter
+       {
+
+       public:
+
+           class List;
+
+           using Data = std::vector<std::pair<std::string, std::string>>;
+
+           JsonFormatter(const Data& data);
+
+           void skip_format_values(const std::vector<std::string>& skip_format_values)
+           {
+               _skip_format_values = skip_format_values;
+           }
+
+           void set_inline(bool is_inline)
+           {
+               _inline = is_inline;
+           }
+
+           std::string output(u_int indent_level = 0) const;
+
+       private:
+
+           std::string json_attributes(u_int indent_level) const;
+
+           bool skip_format_value(const std::string& key) const;
+
+           const Data& _data;
+
+           std::vector<std::string> _skip_format_values;
+
+           bool _inline;
+       };
+
+
+       class JsonFormatter::List
+       {
+
+       public:
+
+           List(const std::vector<std::string>& data);
+
+           std::string output(u_int indent_level = 0) const;
+
+       private:
+
+           const std::vector<std::string>& _data;
+
+       };
+
+    }
+}
+
+#endif
index 7ebc87b5768dc3a858140fca90676ff7e185147b..092b3cfd0734daa69c1a2bfc530a8a23bb707a2d 100644 (file)
@@ -6,14 +6,18 @@ AM_CPPFLAGS = -I$(top_srcdir)
 
 noinst_LTLIBRARIES = libutils.la
 
-libutils_la_SOURCES =                  \
-       Table.cc        Table.h         \
-       text.cc         text.h          \
-       console.cc      console.h       \
-       equal-date.cc   equal-date.h    \
-       GetOpts.cc      GetOpts.h       \
-       Range.cc        Range.h         \
-       HumanString.cc  HumanString.h
+libutils_la_SOURCES =                          \
+       Table.cc            Table.h             \
+       text.cc             text.h              \
+       console.cc          console.h           \
+       equal-date.cc       equal-date.h        \
+       GetOpts.cc          GetOpts.h           \
+       Range.cc            Range.h             \
+       HumanString.cc      HumanString.h       \
+       TableFormatter.cc   TableFormatter.h    \
+       CsvFormatter.cc     CsvFormatter.h      \
+       JsonFormatter.cc    JsonFormatter.h
+
 
 libutils_la_LIBADD = ../../snapper/libsnapper.la
 
diff --git a/client/utils/TableFormatter.cc b/client/utils/TableFormatter.cc
new file mode 100644 (file)
index 0000000..330da3b
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#include <sstream>
+
+#include "client/utils/TableFormatter.h"
+
+using namespace std;
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       namespace
+       {
+           const TableLineStyle DEFAULT_STYLE = Table::defaultStyle;
+       }
+
+
+       TableLineStyle TableFormatter::default_style()
+       {
+           return DEFAULT_STYLE;
+       }
+
+
+       TableFormatter::TableFormatter(
+           vector<pair<string, TableAlign>> columns,
+           vector<vector<string>> rows) :
+           _columns(columns), _rows(rows), _style(default_style())
+       {}
+
+
+       TableFormatter::TableFormatter(
+           vector<pair<string, TableAlign>> columns,
+           vector<vector<string>> rows,
+           TableLineStyle style) :
+           _columns(columns), _rows(rows), _style(style)
+       {}
+
+
+       string TableFormatter::output() const
+       {
+           Table::defaultStyle = _style;
+
+           Table table;
+
+           TableHeader header;
+
+           for (auto column : _columns)
+               header.add(column.first, column.second);
+
+            table.setHeader(header);
+
+           for (auto row : _rows)
+           {
+               TableRow table_row;
+
+               for (auto value : row)
+                   table_row.add(value);
+
+               table.add(table_row);
+           }
+
+           ostringstream stream;
+
+           stream << table;
+
+           return stream.str();
+       }
+
+    }
+}
\ No newline at end of file
diff --git a/client/utils/TableFormatter.h b/client/utils/TableFormatter.h
new file mode 100644 (file)
index 0000000..ed1a3db
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) [2019] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+#ifndef SNAPPER_CLI_TABLE_FORMATTER_H
+#define SNAPPER_CLI_TABLE_FORMATTER_H
+
+#include <string>
+#include <vector>
+#include <utility>
+
+#include "client/utils/Table.h"
+
+namespace snapper
+{
+    namespace cli
+    {
+
+       class TableFormatter
+       {
+
+       public:
+
+           static TableLineStyle default_style();
+
+           TableFormatter(
+               std::vector<std::pair<std::string, TableAlign>> columns,
+               std::vector<std::vector<std::string>> rows);
+
+           TableFormatter(
+               std::vector<std::pair<std::string, TableAlign>> columns,
+               std::vector<std::vector<std::string>> rows,
+               TableLineStyle style);
+
+           std::string output() const;
+
+       private:
+
+           std::vector<std::pair<std::string, TableAlign>> _columns;
+
+           std::vector<std::vector<std::string>> _rows;
+
+           TableLineStyle _style;
+
+       };
+
+    }
+}
+
+#endif
index 770237cad3eec58408f46093d013dbec5cacad3b..31ec811711c8b0a41ee8bf8c03365bd5a3d96ad3 100644 (file)
@@ -6,8 +6,9 @@ AM_CPPFLAGS = -I$(top_srcdir) $(DBUS_CFLAGS)
 
 LDADD = ../snapper/libsnapper.la ../dbus/libdbus.la -lboost_unit_test_framework
 
-check_PROGRAMS = sysconfig-get1.test dirname1.test basename1.test              \
-       equal-date.test dbus-escape.test cmp-lt.test humanstring.test table.test
+check_PROGRAMS = sysconfig-get1.test dirname1.test basename1.test                  \
+       equal-date.test dbus-escape.test cmp-lt.test humanstring.test table.test    \
+       csv-formatter.test json-formatter.test
 
 if ENABLE_BTRFS_QUOTA
 check_PROGRAMS +=  qgroup1.test
@@ -25,3 +26,6 @@ humanstring_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
 
 table_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
 
+csv_formatter_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
+
+json_formatter_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la
diff --git a/testsuite/csv-formatter.cc b/testsuite/csv-formatter.cc
new file mode 100644 (file)
index 0000000..8851bed
--- /dev/null
@@ -0,0 +1,33 @@
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE snapper
+
+#include <boost/test/unit_test.hpp>
+
+#include <vector>
+#include <string>
+
+#include "../client/utils/CsvFormatter.h"
+
+using namespace std;
+
+BOOST_AUTO_TEST_CASE(test1)
+{
+    vector<string> columns = { "column1", "column2", "column3" };;
+
+    vector<vector<string>> rows = {
+       { "value;1", "value\n2", "value\"3" },
+       { "value1", "\"value2\"", "value3" }
+    };
+
+    string separator = ";";
+
+    snapper::cli::CsvFormatter formatter(columns, rows, separator);
+
+    string result =
+       "column1;column2;column3\n"
+       "\"value;1\";\"value\n2\";\"value\"\"3\"\n"
+       "value1;\"\"\"value2\"\"\";value3\n";
+
+    BOOST_CHECK_EQUAL(formatter.output(), result);
+}
diff --git a/testsuite/json-formatter.cc b/testsuite/json-formatter.cc
new file mode 100644 (file)
index 0000000..74801b5
--- /dev/null
@@ -0,0 +1,116 @@
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE snapper
+
+#include <boost/test/unit_test.hpp>
+
+#include <vector>
+#include <string>
+
+#include "../client/utils/JsonFormatter.h"
+
+using namespace std;
+
+BOOST_AUTO_TEST_CASE(test1_escape_values)
+{
+    snapper::cli::JsonFormatter::Data data = {
+       { "key1", "value1" },
+       { "key2", "value\"2" },
+       { "key3", "value\\3" },
+       { "key4", "\"value4\"" }
+    };
+
+    string expected_result =
+       "{\n"
+       "  \"key1\": \"value1\",\n"
+       "  \"key2\": \"value\\\"2\",\n"
+       "  \"key3\": \"value\\\\3\",\n"
+       "  \"key4\": \"\\\"value4\\\"\"\n"
+       "}";
+
+    snapper::cli::JsonFormatter formatter(data);
+
+    BOOST_CHECK_EQUAL(formatter.output(), expected_result);
+}
+
+
+BOOST_AUTO_TEST_CASE(test2_skip_format)
+{
+    snapper::cli::JsonFormatter::Data data = {
+       { "key1", "value1" },
+       { "key2", "value2" }
+    };
+
+    string expected_result =
+       "{\n"
+       "  \"key1\": value1,\n"
+       "  \"key2\": \"value2\"\n"
+       "}";
+
+    snapper::cli::JsonFormatter formatter(data);
+
+    formatter.skip_format_values({ "key1" });
+
+    BOOST_CHECK_EQUAL(formatter.output(), expected_result);
+}
+
+
+BOOST_AUTO_TEST_CASE(test3_indent)
+{
+    // Using an ident level
+
+    snapper::cli::JsonFormatter::Data data = {
+       { "key1", "value1" },
+       { "key2", "value2" }
+    };
+
+    string expected_result =
+       "  {\n"
+       "    \"key1\": \"value1\",\n"
+       "    \"key2\": \"value2\"\n"
+       "  }";
+
+    snapper::cli::JsonFormatter formatter(data);
+
+    BOOST_CHECK_EQUAL(formatter.output(1), expected_result);
+}
+
+
+BOOST_AUTO_TEST_CASE(test4_inline)
+{
+    // Using inline
+
+    snapper::cli::JsonFormatter::Data data = {
+       { "key1", "value1" },
+       { "key2", "value2" }
+    };
+
+    string expected_result =
+       "{\n"
+       "    \"key1\": \"value1\",\n"
+       "    \"key2\": \"value2\"\n"
+       "  }";
+
+    snapper::cli::JsonFormatter formatter(data);
+
+    formatter.set_inline(true);
+
+    BOOST_CHECK_EQUAL(formatter.output(1), expected_result);
+}
+
+
+BOOST_AUTO_TEST_CASE(test5_list)
+{
+    vector<string> data = { "value1", "value2", "value3" };
+
+    snapper::cli::JsonFormatter::List formatter(data);
+
+    string result =
+       "[\n"
+       "value1,\n"
+       "value2,\n"
+       "value3\n"
+       "]";
+
+    BOOST_CHECK_EQUAL(formatter.output(), result);
+}