From f2a63cb3eb7307700d75ffcbb72962a39ae8ae8d Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Wed, 21 Feb 2018 21:41:04 +0000 Subject: [PATCH] Add Group class to API --- CMakeLists.txt | 4 + include/loot/metadata/group.h | 105 ++++++++++++ src/api/metadata/group.cpp | 50 ++++++ src/api/metadata/yaml/group.h | 86 ++++++++++ src/tests/api/internals/main.cpp | 1 + src/tests/api/internals/metadata/group_test.h | 150 ++++++++++++++++++ 6 files changed, 396 insertions(+) create mode 100644 include/loot/metadata/group.h create mode 100644 src/api/metadata/group.cpp create mode 100644 src/api/metadata/yaml/group.h create mode 100644 src/tests/api/internals/metadata/group_test.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce3bff78..90694d36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,7 @@ set (LOOT_API_SRC "${CMAKE_BINARY_DIR}/generated/loot_version.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/condition_evaluator.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/conditional_metadata.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/file.cpp" + "${CMAKE_SOURCE_DIR}/src/api/metadata/group.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/location.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/message.cpp" "${CMAKE_SOURCE_DIR}/src/api/metadata/message_content.cpp" @@ -202,6 +203,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h" "${CMAKE_SOURCE_DIR}/include/loot/loot_version.h" "${CMAKE_SOURCE_DIR}/include/loot/metadata/conditional_metadata.h" "${CMAKE_SOURCE_DIR}/include/loot/metadata/file.h" + "${CMAKE_SOURCE_DIR}/include/loot/metadata/group.h" "${CMAKE_SOURCE_DIR}/include/loot/metadata/location.h" "${CMAKE_SOURCE_DIR}/include/loot/metadata/message.h" "${CMAKE_SOURCE_DIR}/include/loot/metadata/message_content.h" @@ -216,6 +218,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/condition_evaluator.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/condition_grammar.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/file.h" + "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/group.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/location.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/message.h" "${CMAKE_SOURCE_DIR}/src/api/metadata/yaml/message_content.h" @@ -249,6 +252,7 @@ set (LOOT_TESTS_HEADERS "${CMAKE_SOURCE_DIR}/src/tests/api/internals/game/game_t "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/condition_grammar_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/conditional_metadata_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/file_test.h" + "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/group_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/location_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/message_test.h" "${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/message_content_test.h" diff --git a/include/loot/metadata/group.h b/include/loot/metadata/group.h new file mode 100644 index 00000000..73b32f4e --- /dev/null +++ b/include/loot/metadata/group.h @@ -0,0 +1,105 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2018 WrinklyNinja + + This file is part of LOOT. + + LOOT is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + LOOT 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 LOOT. If not, see + . + */ +#ifndef LOOT_METADATA_GROUP +#define LOOT_METADATA_GROUP + +#include +#include + +#include "loot/api_decorator.h" + +namespace loot { +/** + * Represents a group to which plugin metadata objects can belong. + */ +class Group { +public: + /** + * Construct a Group with the name "default" and an empty set of groups to + * load after. + * @return A Group object. + */ + LOOT_API Group(); + + /** + * Construct a File with the given name and an empty set of groups to load + * after. + * @param name + * The group name. + * @return A Group object. + */ + LOOT_API Group(const std::string& name); + + /** + * Construct a File with the given name and set of groups to load after. + * @param name + * The group name. + * @param afterGroups + * The names of groups this group loads after. + * @return A Group object. + */ + LOOT_API Group(const std::string& name, + const std::unordered_set& afterGroups); + + /** + * Check if two Group objects are equal by comparing their names. + * @returns True if the names are case-sensitively equal, false otherwise. + */ + LOOT_API bool operator==(const Group& rhs) const; + + /** + * Get the name of the group. + * @return The group's name. + */ + LOOT_API std::string GetName() const; + + /** + * Get the set of groups this group loads after. + * @return A set of group names. + */ + LOOT_API std::unordered_set GetAfterGroups() const; + +private: + std::string name_; + std::unordered_set afterGroups_; +}; +} + +namespace std { +/** + * A specialisation of std::hash for loot::Group. + */ +template<> +struct hash { + /** + * Calculate a hash value for a loot::Group object. + * @return The hash generated from the group's name. + */ + size_t operator()(const loot::Group& group) const { + return hash()(group.GetName()); + } +}; +} + +#endif diff --git a/src/api/metadata/group.cpp b/src/api/metadata/group.cpp new file mode 100644 index 00000000..742e7d9a --- /dev/null +++ b/src/api/metadata/group.cpp @@ -0,0 +1,50 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2018 WrinklyNinja + + This file is part of LOOT. + + LOOT is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + LOOT 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 LOOT. If not, see + . + */ + +#include "loot/metadata/group.h" + +#include + +#include "api/metadata/yaml/group.h" + +namespace loot { +Group::Group() : name_("default") {} + +Group::Group(const std::string& name) : name_(name) {} + +Group::Group(const std::string& name, + const std::unordered_set& afterGroups) : + name_(name), + afterGroups_(afterGroups) {} + +bool Group::operator==(const Group& rhs) const { + return name_ == rhs.name_; +} + +std::string Group::GetName() const { return name_; } + +std::unordered_set Group::GetAfterGroups() const { + return afterGroups_; +} +} diff --git a/src/api/metadata/yaml/group.h b/src/api/metadata/yaml/group.h new file mode 100644 index 00000000..3b991098 --- /dev/null +++ b/src/api/metadata/yaml/group.h @@ -0,0 +1,86 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2012-2016 WrinklyNinja + + This file is part of LOOT. + + LOOT is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + LOOT 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 LOOT. If not, see + . + */ +#ifndef LOOT_YAML_GROUP +#define LOOT_YAML_GROUP + +#include +#include + +#include + +#include "api/metadata/yaml/set.h" +#include "loot/metadata/group.h" + +namespace YAML { +template<> +struct convert { + static Node encode(const loot::Group& rhs) { + Node node; + node["name"] = rhs.GetName(); + + auto afterGroups = rhs.GetAfterGroups(); + if (!afterGroups.empty()) + node["after"] = afterGroups; + + return node; + } + + static bool decode(const Node& node, loot::Group& rhs) { + if (!node.IsMap()) + throw RepresentationException( + node.Mark(), "bad conversion: 'group' object must be a map"); + + if (!node["name"]) + throw RepresentationException( + node.Mark(), + "bad conversion: 'name' key missing from 'file' map object"); + + std::string name = node["name"].as(); + + if (node["after"]) { + rhs = loot::Group(name, node["after"].as>()); + } else { + rhs = loot::Group(name); + } + + return true; + } +}; + +inline Emitter& operator<<(Emitter& out, const loot::Group& rhs) { + out << BeginMap + << Key << "name" << Value << YAML::SingleQuoted << rhs.GetName(); + + auto afterGroups = rhs.GetAfterGroups(); + if (!afterGroups.empty()) { + out << Key << "after" << Value << afterGroups; + } + + out << EndMap; + + return out; +} +} + +#endif diff --git a/src/tests/api/internals/main.cpp b/src/tests/api/internals/main.cpp index 11e8d67e..6d4330bb 100644 --- a/src/tests/api/internals/main.cpp +++ b/src/tests/api/internals/main.cpp @@ -36,6 +36,7 @@ #include "tests/api/internals/metadata/condition_grammar_test.h" #include "tests/api/internals/metadata/conditional_metadata_test.h" #include "tests/api/internals/metadata/file_test.h" +#include "tests/api/internals/metadata/group_test.h" #include "tests/api/internals/metadata/location_test.h" #include "tests/api/internals/metadata/message_content_test.h" #include "tests/api/internals/metadata/message_test.h" diff --git a/src/tests/api/internals/metadata/group_test.h b/src/tests/api/internals/metadata/group_test.h new file mode 100644 index 00000000..30fbe986 --- /dev/null +++ b/src/tests/api/internals/metadata/group_test.h @@ -0,0 +1,150 @@ +/* LOOT + +A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and +Fallout: New Vegas. + +Copyright (C) 2014-2016 WrinklyNinja + +This file is part of LOOT. + +LOOT is free software: you can redistribute +it and/or modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +LOOT 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 LOOT. If not, see +. +*/ + +#ifndef LOOT_TESTS_API_INTERNALS_METADATA_GROUP_TEST +#define LOOT_TESTS_API_INTERNALS_METADATA_GROUP_TEST + +#include "loot/metadata/group.h" + +#include + +#include "api/metadata/yaml/group.h" + +namespace loot { +namespace test { +TEST(Group, defaultConstructorShouldCreateDefaultGroup) { + Group group; + + EXPECT_EQ("default", group.GetName()); + EXPECT_TRUE(group.GetAfterGroups().empty()); +} + +TEST(Group, nameConstructorShouldCreateNamedGroup) { + Group group("group1"); + + EXPECT_EQ("group1", group.GetName()); + EXPECT_TRUE(group.GetAfterGroups().empty()); +} + +TEST(Group, allArgsConstructorShouldStoreGivenValues) { + Group group("group1", std::unordered_set({"other_group"})); + + EXPECT_EQ("group1", group.GetName()); + EXPECT_EQ(std::unordered_set({"other_group"}), group.GetAfterGroups()); +} + +TEST(Group, groupsWithCaseInsensitiveEqualNameStringsShouldNotBeEqual) { + Group group1("name"); + Group group2("Name"); + + EXPECT_FALSE(group1 == group2); +} + +TEST(Group, groupsWithCaseSensitiveEqualNameStringsShouldBeEqual) { + Group group1("name"); + Group group2("name"); + + EXPECT_TRUE(group1 == group2); +} + +TEST(Group, groupsWithDifferentNamesShouldBeUnequal) { + Group group1("name1"); + Group group2("name2"); + + EXPECT_FALSE(group1 == group2); +} + +TEST(Group, emittingAsYamlShouldOmitAfterKeyIfAfterGroupsIsEmpty) { + Group group; + + YAML::Emitter emitter; + emitter << group; + + EXPECT_STREQ("name: 'default'", emitter.c_str()); +} + +TEST(Group, emittingAsYamlShouldIncludeAfterKeyIfAfterGroupsIsNotEmpty) { + Group group("group1", std::unordered_set({ "other_group" })); + + YAML::Emitter emitter; + emitter << group; + + EXPECT_STREQ( + "name: 'group1'\n" + "after:\n" + " - other_group", + emitter.c_str()); +} + +TEST(Group, encodingAsYamlShouldOmitAfterKeyIfAfterGroupsIsEmpty) { + Group group; + YAML::Node node; + node = group; + + EXPECT_EQ("default", node["name"].as()); + EXPECT_FALSE(node["after"]); +} + +TEST(Group, encodingAsYamlShouldIncludeAfterKeyIfAfterGroupsIsNotEmpty) { + Group group("group1", std::unordered_set({ "other_group" })); + YAML::Node node; + node = group; + + std::unordered_set expectedAfterGroups = { "other_group" }; + EXPECT_EQ("group1", node["name"].as()); + EXPECT_EQ(expectedAfterGroups, node["after"].as>()); +} + +TEST(Group, decodingFromYamlShouldSetGivenName) { + YAML::Node node = YAML::Load("{name: group1}"); + Group group = node.as(); + + EXPECT_EQ("group1", group.GetName()); + EXPECT_TRUE(group.GetAfterGroups().empty()); +} + +TEST(Group, decodingFromYamlShouldSetAfterGroupsIfAnyAreGiven) { + YAML::Node node = YAML::Load("{name: group1, after: [ other_group ]}"); + Group group = node.as(); + + std::unordered_set expectedAfterGroups = { "other_group" }; + EXPECT_EQ("group1", group.GetName()); + EXPECT_EQ(expectedAfterGroups, group.GetAfterGroups()); +} + +TEST(Group, decodingFromYamlShouldThrowIfTheNameKeyIsMissing) { + YAML::Node node = YAML::Load("{after: []}"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST(Group, decodingFromYamlShouldThrowIfAListIsGiven) { + YAML::Node node = YAML::Load("[0, 1, 2]"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} +} +} + +#endif