--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
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
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
--- /dev/null
+
+#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);
+}
--- /dev/null
+
+#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);
+}