changeset 185:975dffc6567a

Tests: use ptree directly in mlk-map, closes #704 @1h
author David Demelier <markand@malikania.fr>
date Sat, 20 Oct 2018 20:38:43 +0200
parents eaf3cea01010
children 16ff680a8a94
files tests/tools/CMakeLists.txt tests/tools/map/CMakeLists.txt tests/tools/map/main.cpp tools/map/base64.hpp tools/map/main.cpp
diffstat 5 files changed, 181 insertions(+), 205 deletions(-) [+]
line wrap: on
line diff
--- a/tests/tools/CMakeLists.txt	Fri Oct 19 20:30:16 2018 +0200
+++ b/tests/tools/CMakeLists.txt	Sat Oct 20 20:38:43 2018 +0200
@@ -16,4 +16,5 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
+add_subdirectory(map)
 add_subdirectory(tileset)
--- a/tests/tools/map/CMakeLists.txt	Fri Oct 19 20:30:16 2018 +0200
+++ b/tests/tools/map/CMakeLists.txt	Sat Oct 20 20:38:43 2018 +0200
@@ -52,7 +52,7 @@
 endforeach()
 
 malikania_create_test(
-	NAME map
+	NAME mlk-map
 	LIBRARIES json
 	SOURCES
 		${sources}
--- a/tests/tools/map/main.cpp	Fri Oct 19 20:30:16 2018 +0200
+++ b/tests/tools/map/main.cpp	Sat Oct 20 20:38:43 2018 +0200
@@ -69,20 +69,22 @@
 	return result;
 }
 
-#define TEST_BODY(file, title)                                                      \
-    auto json = json::parse(std::ifstream(BINARY_DIRECTORY "/" file ".json"));      \
-                                                                                    \
-    BOOST_REQUIRE_EQUAL(json["title"].get<std::string>(), title);                   \
-    BOOST_REQUIRE_EQUAL(json["tileset"].get<std::string>(), "minimal.png");         \
-    BOOST_REQUIRE_EQUAL(json["cell"][0].get<int>(), 32);                            \
-    BOOST_REQUIRE_EQUAL(json["cell"][1].get<int>(), 32);                            \
-    BOOST_REQUIRE_EQUAL(json["size"][0].get<int>(), 10);                            \
-    BOOST_REQUIRE_EQUAL(json["size"][1].get<int>(), 10);                            \
-                                                                                    \
-    auto total = json["size"][0].get<int>() * json["size"][1].get<int>();           \
-    auto back = load(BINARY_DIRECTORY "/" file ".map", total, true);                \
-                                                                                    \
-    BOOST_REQUIRE_EQUAL_COLLECTIONS(back.begin(), back.end(),                       \
+#define TEST_BODY(file, title)                                                  \
+    auto path = CMAKE_CURRENT_BINARY_DIR "/" file ".json";                      \
+    auto json = json::parse(std::ifstream(path));                               \
+                                                                                \
+    BOOST_TEST(json["title"].get<std::string>() == title);                      \
+    BOOST_TEST(json["tileset"].get<std::string>() == "minimal.png");            \
+    BOOST_TEST(json["cell"][0].get<int>() == 32);                               \
+    BOOST_TEST(json["cell"][1].get<int>() == 32);                               \
+    BOOST_TEST(json["size"][0].get<int>() == 10);                               \
+    BOOST_TEST(json["size"][1].get<int>() == 10);                               \
+                                                                                \
+    auto map = CMAKE_CURRENT_BINARY_DIR "/" file ".map";                        \
+    auto total = json["size"][0].get<int>() * json["size"][1].get<int>();       \
+    auto back = load(map, total, true);                                         \
+                                                                                \
+    BOOST_REQUIRE_EQUAL_COLLECTIONS(back.begin(), back.end(),                   \
         true_background.begin(), true_background.end());
 
 } // !namespace
--- a/tools/map/base64.hpp	Fri Oct 19 20:30:16 2018 +0200
+++ b/tools/map/base64.hpp	Sat Oct 20 20:38:43 2018 +0200
@@ -16,8 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#ifndef MALIKANIA_BASE64_HPP
-#define MALIKANIA_BASE64_HPP
+#ifndef MALIKANIA_MAP_BASE64_HPP
+#define MALIKANIA_MAP_BASE64_HPP
 
 /**
  * \file base64.hpp
@@ -47,9 +47,9 @@
  *
  * ````
  * try {
- *  std::string text = base64::decode(msg);
+ * 	std::string text = base64::decode(msg);
  * } catch (const std::exception &ex) {
- *  std::cerr << ex.what() << std::endl;
+ * 	std::cerr << ex.what() << std::endl;
  * }
  * ````
  */
@@ -58,13 +58,12 @@
 #include <cctype>
 #include <stdexcept>
 #include <string>
-
-namespace mlk {
+#include <string_view>
 
 /**
  * \brief main %base64 namespace.
  */
-namespace base64 {
+namespace mlk::base64 {
 
 /**
  * Check if the character is a %base64 character, A-Za-z0-9 and +/.
@@ -72,9 +71,9 @@
  * \param ch the character to test
  * \return true if valid
  */
-inline bool is_base64(char ch) noexcept
+inline auto is_base64(char ch) noexcept -> bool
 {
-    return std::isalnum(ch) != 0 || ch == '+' || ch == '/';
+	return std::isalnum(ch) != 0 || ch == '+' || ch == '/';
 }
 
 /**
@@ -84,9 +83,9 @@
  * \param ch the character
  * \return true if the character is valid
  */
-inline bool is_valid(char ch) noexcept
+inline auto is_valid(char ch) noexcept -> bool
 {
-    return is_base64(ch) || ch == '=';
+	return is_base64(ch) || ch == '=';
 }
 
 /**
@@ -96,13 +95,13 @@
  * \param value the value
  * \return the %base64 character for value
  */
-inline char lookup(unsigned char value) noexcept
+inline auto lookup(unsigned char value) noexcept -> char
 {
-    static const char *table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	static std::string_view table("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
 
-    assert(value < 64);
+	assert(value < 64);
 
-    return table[value];
+	return table[value];
 }
 
 /**
@@ -115,18 +114,18 @@
  * auto b64 = base64::rlookup('D') // 3
  * ````
  */
-inline unsigned char rlookup(char ch) noexcept
+inline auto rlookup(char ch) noexcept -> unsigned char
 {
-    assert(is_base64(ch));
+	assert(is_base64(ch));
 
-    if (ch >= '0' && ch <= '9')
-        return static_cast<unsigned char>(ch + 4);
-    if (ch >= 'A' && ch <= 'Z')
-        return static_cast<unsigned char>(ch - 65);
-    if (ch >= 'a' && ch <= 'z')
-        return static_cast<unsigned char>(ch - 71);
+	if (ch >= '0' && ch <= '9')
+		return static_cast<unsigned char>(ch + 4);
+	if (ch >= 'A' && ch <= 'Z')
+		return static_cast<unsigned char>(ch - 65);
+	if (ch >= 'a' && ch <= 'z')
+		return static_cast<unsigned char>(ch - 71);
 
-    return (ch == '+') ? 62U : 63U;
+	return (ch == '+') ? 62U : 63U;
 }
 
 /**
@@ -142,22 +141,22 @@
  * \return output
  */
 template <typename InputIt, typename OutputIt>
-OutputIt encode(InputIt input, InputIt end, OutputIt output)
+auto encode(InputIt input, InputIt end, OutputIt output) -> OutputIt
 {
-    while (input != end) {
-        char inputbuf[3] = { 0, 0, 0 };
-        int count;
+	while (input != end) {
+		char inputbuf[3] = { 0, 0, 0 };
+		int count;
 
-        for (count = 0; count < 3 && input != end; ++count)
-            inputbuf[count] = *input++;
+		for (count = 0; count < 3 && input != end; ++count)
+			inputbuf[count] = *input++;
 
-        *output++ = lookup(inputbuf[0] >> 2 & 0x3f);
-        *output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f));
-        *output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03));
-        *output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f);
-    }
+		*output++ = lookup(inputbuf[0] >> 2 & 0x3f);
+		*output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f));
+		*output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03));
+		*output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f);
+	}
 
-    return output;
+	return output;
 }
 
 /**
@@ -174,39 +173,39 @@
  * \throw std::invalid_argument on bad %base64 string
  */
 template <typename InputIt, typename OutputIt>
-OutputIt decode(InputIt input, InputIt end, OutputIt output)
+auto decode(InputIt input, InputIt end, OutputIt output) -> OutputIt
 {
-    while (input != end) {
-        char inputbuf[4] = { -1, -1, -1, -1 };
-        int count;
+	while (input != end) {
+		char inputbuf[4] = { -1, -1, -1, -1 };
+		int count;
 
-        for (count = 0; count < 4 && input != end; ++count) {
-            // Check if the character is valid and get its value.
-            if ((*input == '=' && count <= 1) || !is_valid(*input))
-                throw std::invalid_argument("invalid base64 string");
-            if (is_base64(*input))
-                inputbuf[count] = static_cast<char>(rlookup(*input));
+		for (count = 0; count < 4 && input != end; ++count) {
+			// Check if the character is valid and get its value.
+			if ((*input == '=' && count <= 1) || !is_valid(*input))
+				throw std::invalid_argument("invalid base64 string");
+			if (is_base64(*input))
+				inputbuf[count] = static_cast<char>(rlookup(*input));
 
-            input++;
-        }
+			input++;
+		}
 
-        if (count != 4)
-            throw std::invalid_argument("truncated string");
+		if (count != 4)
+			throw std::invalid_argument("truncated string");
 
-        *output++ = static_cast<char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03));
+		*output++ = static_cast<char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03));
 
-        if (inputbuf[2] != -1)
-            *output++ = static_cast<char>(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f));
-        if (inputbuf[3] != -1) {
-            // "XY=Z" is not allowed.
-            if (inputbuf[2] == -1)
-                throw std::invalid_argument("invalid base64 string");
+		if (inputbuf[2] != -1)
+			*output++ = static_cast<char>(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f));
+		if (inputbuf[3] != -1) {
+			// "XY=Z" is not allowed.
+			if (inputbuf[2] == -1)
+				throw std::invalid_argument("invalid base64 string");
 
-            *output++ = static_cast<char>(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f));
-        }
-    }
+			*output++ = static_cast<char>(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f));
+		}
+	}
 
-    return output;
+	return output;
 }
 
 /**
@@ -215,13 +214,13 @@
  * \param input the input string
  * \return the %base64 formatted string
  */
-inline std::string encode(const std::string &input)
+inline auto encode(std::string_view input) -> std::string
 {
-    std::string result;
+	std::string result;
 
-    encode(input.begin(), input.end(), std::back_inserter(result));
+	encode(input.begin(), input.end(), std::back_inserter(result));
 
-    return result;
+	return result;
 }
 
 /**
@@ -231,17 +230,15 @@
  * \return the original string
  * \throw std::invalid_argument on bad %base64 string
  */
-inline std::string decode(const std::string &input)
+inline auto decode(std::string_view input) -> std::string
 {
-    std::string result;
+	std::string result;
 
-    decode(input.begin(), input.end(), std::back_inserter(result));
+	decode(input.begin(), input.end(), std::back_inserter(result));
 
-    return result;
+	return result;
 }
 
-} // !base64
+} // !mlk::base64
 
-} // !mlk
-
-#endif // !MALIKANIA_BASE64_HPP
+#endif // !MALIKANIA_MAP_BASE64_HPP
--- a/tools/map/main.cpp	Fri Oct 19 20:30:16 2018 +0200
+++ b/tools/map/main.cpp	Sat Oct 20 20:38:43 2018 +0200
@@ -31,6 +31,7 @@
 #include <vector>
 #include <stdexcept>
 #include <string>
+#include <utility>
 
 #include <boost/algorithm/string/trim.hpp>
 #include <boost/endian/conversion.hpp>
@@ -51,62 +52,29 @@
 namespace io = boost::iostreams;
 namespace ptree = boost::property_tree;
 
+using boost::format;
+using boost::str;
+
+using nlohmann::json;
+
+/*
+ * Byte array for layers.
+ */
+using layer = std::vector<std::uint32_t>;
+
 namespace mlk {
 
 namespace {
 
 /*
- * data
- * ------------------------------------------------------------------
- *
- * Store map data.
- */
-class data {
-public:
-	std::string title;
-	std::string tileset;
-	std::uint64_t width{0};
-	std::uint64_t height{0};
-	std::uint8_t cell_width{0};
-	std::uint8_t cell_height{0};
-	std::vector<std::uint32_t> background;
-	std::vector<std::uint32_t> foreground;
-};
-
-/*
- * load_properties
- * ------------------------------------------------------------------
- *
- * Extract the following properties from the map:
- *
- *  <map>
- *	  <properties>
- *		  <property name="title" value="..." />
- *	  </properties>
- *  </map>
- */
-void load_properties(data& result, const ptree::ptree& tree)
-{
-	for (const auto& p : tree) {
-		if (p.first != "property")
-			continue;
-
-		auto name = p.second.get<std::string>("<xmlattr>.name");
-
-		if (name == "title")
-			result.title = p.second.get<std::string>("<xmlattr>.value");
-	}
-}
-
-/*
  * load_layer_data_xml
  * ------------------------------------------------------------------
  *
  * Convert the <data> node when saved as XML.
  */
-std::vector<std::uint32_t> load_layer_data_xml(const ptree::ptree& tree)
+auto load_layer_data_xml(const ptree::ptree& tree) -> layer
 {
-	std::vector<std::uint32_t> data;
+	layer data;
 
 	for (const auto& t : tree)
 		data.push_back(t.second.get<int>("<xmlattr>.gid"));
@@ -120,10 +88,10 @@
  *
  * Convert the <data> node when saved as CSV.
  */
-std::vector<std::uint32_t> load_layer_data_csv(const std::string& csv)
+auto load_layer_data_csv(const std::string& csv) -> layer
 {
 	std::istringstream iss(csv);
-	std::vector<std::uint32_t> data;
+	layer data;
 
 	for (std::string line; std::getline(iss, line, ','); )
 		data.push_back(std::stoi(line));
@@ -139,10 +107,10 @@
  *
  * The data is an array of 32 bits unsigned int in the little endian format.
  */
-std::vector<std::uint32_t> load_layer_data_array(std::string bytes)
+auto load_layer_data_array(std::string bytes) -> layer
 {
 	std::uint32_t* array = reinterpret_cast<std::uint32_t*>(&bytes[0]);
-	std::vector<std::uint32_t> data;
+	layer data;
 
 	for (auto ptr = array; ptr != &array[bytes.length() / 4]; ++ptr)
 		data.push_back(*ptr);
@@ -154,19 +122,20 @@
  * load_layer_data_array_compressed
  * ------------------------------------------------------------------
  *
+ * Convert the <data> node when saved as compressed base64 data.
+ *
  * Decompress the data before parsing, supports zlib and gzip.
  */
-std::vector<std::uint32_t> load_layer_data_array_compressed(const std::string& bytes,
-															const std::string& compression)
+auto load_layer_data_array_compressed(const std::string& bytes,
+                                      const std::string& compression) -> layer
 {
 	io::filtering_streambuf<io::input> in;
 	std::string out;
 
-	if (compression == "zlib") {
+	if (compression == "zlib")
 		in.push(io::zlib_decompressor());
-	} else {
+	else
 		in.push(io::gzip_decompressor());
-	}
 
 	in.push(boost::make_iterator_range(bytes));
 	io::copy(in, io::back_inserter(out));
@@ -180,31 +149,30 @@
  *
  * Extract the <data> node from a <layer>
  */
-std::vector<std::uint32_t> load_layer_data(const ptree::ptree& tree)
+auto load_layer_data(const ptree::ptree& tree) -> layer
 {
 	auto encoding = tree.get_optional<std::string>("<xmlattr>.encoding");
 	auto compression = tree.get_optional<std::string>("<xmlattr>.compression");
-	std::vector<std::uint32_t> data;
 
-	if (!encoding) {
+	layer data;
+
+	if (!encoding)
 		data = load_layer_data_xml(tree);
-	} else if (*encoding == "csv") {
+	else if (*encoding == "csv")
 		data = load_layer_data_csv(boost::trim_copy(tree.get_value<std::string>()));
-	} else if (*encoding == "base64") {
+	else if (*encoding == "base64") {
 		auto b64 = base64::decode(boost::trim_copy(tree.get_value<std::string>()));
 
-		if (!compression) {
+		if (!compression)
 			data = load_layer_data_array(std::move(b64));
-		} else if (*compression == "zlib" || *compression == "gzip") {
+		else if (*compression == "zlib" || *compression == "gzip")
 			data = load_layer_data_array_compressed(b64, *compression);
-		}
 
 		// Convert endianness if system is not little endian.
-		if (boost::endian::order::native != boost::endian::order::little) {
+		if (boost::endian::order::native != boost::endian::order::little)
 			std::transform(data.begin(), data.end(), data.begin(), [] (auto c) {
 				return boost::endian::endian_reverse(c);
 			});
-		}
 	}
 
 	return data;
@@ -222,24 +190,31 @@
  *	  </layer>
  *  </map>
  */
-void load_layer(data& map, const ptree::ptree& tree)
+auto load_layers(const ptree::ptree& tree) -> std::pair<layer, layer>
 {
-	auto name = tree.get<std::string>("<xmlattr>.name");
+	layer background, foreground;
+
+	for (const auto& pair : tree.get_child("map")) {
+		if (pair.first != "layer")
+			continue;
+
+		const auto name = pair.second.get<std::string>("<xmlattr>.name");
 
-	if (name != "background" && name != "foreground") {
-		throw std::runtime_error(boost::str(boost::format(
-			"invalid layer name given '%s'\n"
-			"(only 'foreground' and 'background' is supported)"
-		) % name));
+		if (name != "background" && name != "foreground")
+			throw std::runtime_error(str(format(
+				"abort: invalid layer name given '%s'\n"
+				"(only 'foreground' and 'background' is supported)"
+			) % name));
+
+		const auto data = load_layer_data(pair.second.get_child("data"));
+
+		if (name == "background")
+			background = std::move(data);
+		else
+			foreground = std::move(data);
 	}
 
-	auto data = load_layer_data(tree.get_child("data"));
-
-	if (name == "background") {
-		map.background = std::move(data);
-	} else {
-		map.foreground = std::move(data);
-	}
+	return { background, foreground };
 }
 
 /*
@@ -248,27 +223,12 @@
  *
  * Load the .tmx file from the path and return it.
  */
-data load(const std::string& path)
+auto load_tmx(const fs::path& path) -> ptree::ptree
 {
-	data result;
 	ptree::ptree tree;
-	ptree::read_xml(path, tree);
+	ptree::read_xml(path.string(), tree);
 
-	result.width = tree.get<int>("map.<xmlattr>.width");
-	result.height = tree.get<int>("map.<xmlattr>.height");
-	result.cell_height = tree.get<int>("map.<xmlattr>.tilewidth");
-	result.cell_width = tree.get<int>("map.<xmlattr>.tileheight");
-	result.tileset = tree.get<std::string>("map.tileset.image.<xmlattr>.source");
-
-	for (const auto& pair : tree.get_child("map")) {
-		if (pair.first == "properties") {
-			load_properties(result, pair.second);
-		} else if (pair.first == "layer") {
-			load_layer(result, pair.second);
-		}
-	}
-
-	return result;
+	return tree;
 }
 
 /*
@@ -277,20 +237,35 @@
  *
  * Save map metadata into the specified file.
  */
-void save_meta(const data& map, const fs::path& path)
+void save_meta(const ptree::ptree& xml, const fs::path& path)
 {
-	auto json = nlohmann::json::object({
-		{ "title", map.title },
-		{ "tileset", map.tileset },
-		{ "size", { map.width, map.height } },
-		{ "cell", { map.cell_width, map.cell_height } },
+	auto json = json::object({
+		{ "tileset", xml.get<std::string>("map.tileset.image.<xmlattr>.source") },
+		{ "size", {
+			xml.get<int>("map.<xmlattr>.width"),
+			xml.get<int>("map.<xmlattr>.height")
+		}},
+		{ "cell", {
+			xml.get<int>("map.<xmlattr>.tilewidth"),
+			xml.get<int>("map.<xmlattr>.tileheight")
+		}}
+
 	});
 
+	for (const auto& pair : xml.get_child("map.properties")) {
+		if (pair.first != "property")
+			continue;
+
+		const auto name = pair.second.get<std::string>("<xmlattr>.name");
+		
+		if (name == "title")
+			json["title"] = pair.second.get<std::string>("<xmlattr>.value");
+	}
+
 	std::ofstream out(path.string(), std::ofstream::out | std::ofstream::trunc);
 
-	if (!out) {
+	if (!out)
 		throw std::runtime_error(std::strerror(errno));
-	}
 
 	out << json.dump(4) << std::endl;
 }
@@ -301,21 +276,22 @@
  *
  * Save the map tileset info the specified path.
  */
-void save_map(const data& map, const fs::path& path)
+void save_map(const ptree::ptree& xml, const fs::path& path)
 {
 	std::ofstream out(path.string(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
 
-	if (!out) {
+	if (!out)
 		throw std::runtime_error(std::strerror(errno));
-	}
+
+	auto [ background, foreground ] = load_layers(xml);
 
 	out.write(
-		reinterpret_cast<const char*>(map.background.data()),
-		sizeof (map.background[0]) * map.background.size()
+		reinterpret_cast<const char*>(background.data()),
+		sizeof (background[0]) * background.size()
 	);
 	out.write(
-		reinterpret_cast<const char*>(map.foreground.data()),
-		sizeof (map.foreground[0]) * map.foreground.size()
+		reinterpret_cast<const char*>(foreground.data()),
+		sizeof (foreground[0]) * foreground.size()
 	);
 }
 
@@ -324,13 +300,13 @@
  * ------------------------------------------------------------------
  *
  * Save the map data into the JSON file and map data.
- *
- * Check MAP.md for the specifications of the JSON file.
  */
-void save(const data& map, const fs::path& meta, const fs::path& pack)
+void save(const fs::path& input, const fs::path& meta, const fs::path& pack)
 {
-	save_meta(map, meta);
-	save_map(map, pack);
+	const auto xml = load_tmx(input);
+
+	save_meta(xml, meta);
+	save_map(xml, pack);
 }
 
 } // !namespace
@@ -361,7 +337,7 @@
 			map = fs::path(argv[0]).replace_extension(".map");
 		}
 
-		mlk::save(mlk::load(argv[0]), meta, map);
+		mlk::save(argv[0], meta, map);
 	} catch (const std::exception& ex) {
 		std::cerr << ex.what() << std::endl;
 		return 1;