commit fb7e7a72ed65516a1abe35b0b882d3f7778677ad
parent ed02d2d82f4ff7754d0bb68b7e6aa2f9c7879d21
Author: cfillion <cfillion@users.noreply.github.com>
Date: Fri, 2 Jun 2017 20:29:10 -0400
Merge branch 'api'
Diffstat:
15 files changed, 528 insertions(+), 33 deletions(-)
diff --git a/src/api.cpp b/src/api.cpp
@@ -0,0 +1,264 @@
+/* ReaPack: Package manager for REAPER
+ * Copyright (C) 2015-2017 Christian Fillion
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "api.hpp"
+
+#include <boost/mpl/aux_/preprocessor/token_equal.hpp>
+#include <boost/preprocessor.hpp>
+
+#include <reaper_plugin_functions.h>
+
+#include "about.hpp"
+#include "errors.hpp"
+#include "index.hpp"
+#include "reapack.hpp"
+#include "registry.hpp"
+#include "remote.hpp"
+#include "transaction.hpp"
+
+#define API_PREFIX "ReaPack_"
+
+using namespace API;
+using namespace std;
+
+ReaPack *API::reapack = nullptr;
+
+struct PackageEntry {
+ Registry::Entry regEntry;
+ vector<Registry::File> files;
+};
+
+static set<PackageEntry *> s_entries;
+
+APIDef::APIDef(const APIFunc *func)
+ : m_func(func)
+{
+ plugin_register(m_func->cKey, m_func->cImpl);
+ plugin_register(m_func->reascriptKey, m_func->reascriptImpl);
+ plugin_register(m_func->definitionKey, m_func->definition);
+}
+
+APIDef::~APIDef()
+{
+ unregister(m_func->cKey, m_func->cImpl);
+ unregister(m_func->reascriptKey, m_func->reascriptImpl);
+ unregister(m_func->definitionKey, m_func->definition);
+}
+
+void APIDef::unregister(const char *key, void *ptr)
+{
+ char buf[255];
+ snprintf(buf, sizeof(buf), "-%s", key);
+ plugin_register(buf, ptr);
+}
+
+#define BOOST_MPL_PP_TOKEN_EQUAL_void(x) x
+#define IS_VOID(type) BOOST_MPL_PP_TOKEN_EQUAL(type, void)
+
+#define ARG_TYPE(arg) BOOST_PP_TUPLE_ELEM(2, 0, arg)
+#define ARG_NAME(arg) BOOST_PP_TUPLE_ELEM(2, 1, arg)
+
+#define ARGS(r, data, i, arg) BOOST_PP_COMMA_IF(i) ARG_TYPE(arg) ARG_NAME(arg)
+#define PARAMS(r, data, i, arg) BOOST_PP_COMMA_IF(i) (ARG_TYPE(arg))(intptr_t)argv[i]
+#define DEFARGS(r, macro, i, arg) \
+ BOOST_PP_EXPR_IF(i, ",") BOOST_PP_STRINGIZE(macro(arg))
+
+#define DEFINE_API(type, name, args, help, ...) \
+ namespace API_##name { \
+ static type cImpl(BOOST_PP_SEQ_FOR_EACH_I(ARGS, _, args)) __VA_ARGS__ \
+ static void *reascriptImpl(void **argv, int argc) { \
+ BOOST_PP_EXPR_IF(BOOST_PP_NOT(IS_VOID(type)), return (void *)(intptr_t)) \
+ cImpl(BOOST_PP_SEQ_FOR_EACH_I(PARAMS, _, args)); \
+ BOOST_PP_EXPR_IF(IS_VOID(type), return nullptr;) \
+ } \
+ static const char *definition = #type "\0" \
+ BOOST_PP_SEQ_FOR_EACH_I(DEFARGS, ARG_TYPE, args) "\0" \
+ BOOST_PP_SEQ_FOR_EACH_I(DEFARGS, ARG_NAME, args) "\0" help; \
+ }; \
+ APIFunc API::name = {\
+ "API_" API_PREFIX #name, (void *)&API_##name::cImpl, \
+ "APIvararg_" API_PREFIX #name, (void *)&API_##name::reascriptImpl, \
+ "APIdef_" API_PREFIX #name, (void *)API_##name::definition, \
+ }
+
+DEFINE_API(bool, AboutInstalledPackage, ((PackageEntry*, entry)), R"(
+ Show the about dialog of the given package entry.
+ The repository index is downloaded asynchronously if the cached copy doesn't exist or is older than one week.
+)", {
+ if(!s_entries.count(entry))
+ return false;
+
+ // the one given by the user may be deleted while we download the idnex
+ const Registry::Entry entryCopy = entry->regEntry;
+
+ const Remote &repo = reapack->remote(entryCopy.remote);
+ if(!repo)
+ return false;
+
+ Transaction *tx = reapack->setupTransaction();
+ if(!tx)
+ return false;
+
+ const vector<Remote> repos = {repo};
+
+ tx->fetchIndexes(repos);
+ tx->onFinish([=] {
+ const auto &indexes = tx->getIndexes(repos);
+ if(indexes.empty())
+ return;
+
+ const Package *pkg = indexes.front()->find(entryCopy.category, entryCopy.package);
+ if(pkg)
+ reapack->about()->setDelegate(make_shared<AboutPackageDelegate>(pkg, entryCopy.version));
+ });
+ tx->runTasks();
+
+ return true;
+});
+
+DEFINE_API(bool, AboutRepository, ((const char*, repoName)), R"(
+ Show the about dialog of the given repository. Returns true if the repository exists in the user configuration.
+ The repository index is downloaded asynchronously if the cached copy doesn't exist or is older than one week.
+)", {
+ if(const Remote &repo = reapack->remote(repoName)) {
+ reapack->about(repo);
+ return true;
+ }
+
+ return false;
+});
+
+DEFINE_API(int, CompareVersions, ((const char*, ver1))((const char*, ver2))
+ ((char*, errorOut))((int, errorOut_sz)), R"(
+ Returns 0 if both versions are equal, a positive value if ver1 is higher than ver2 and a negative value otherwise.
+)", {
+ VersionName a, b;
+ string error;
+
+ b.tryParse(ver2, &error);
+ a.tryParse(ver1, &error);
+
+ if(errorOut)
+ snprintf(errorOut, errorOut_sz, "%s", error.c_str());
+
+ return a.compare(b);
+});
+
+DEFINE_API(bool, EnumOwnedFiles, ((PackageEntry*, entry))((int, index))
+ ((char*, pathOut))((int, pathOut_sz))((int*, sectionsOut))((int*, typeOut)), R"(
+ Enumerate the files owned by the given package. Returns false when there is no more data.
+
+ sections: 0=not in action list, &1=main, &2=midi editor, &4=midi inline editor
+ type: see <a href="#ReaPack_GetEntryInfo">ReaPack_GetEntryInfo</a>.
+)", {
+ const size_t i = index;
+
+ if(!s_entries.count(entry) || i >= entry->files.size())
+ return false;
+
+ const Registry::File &file = entry->files[i];
+ if(pathOut)
+ snprintf(pathOut, pathOut_sz, "%s", Path::prefixRoot(file.path).join().c_str());
+ if(sectionsOut)
+ *sectionsOut = file.sections;
+ if(typeOut)
+ *typeOut = (int)file.type;
+
+ return entry->files.size() > i + 1;
+});
+
+DEFINE_API(bool, FreeEntry, ((PackageEntry*, entry)), R"(
+ Free resources allocated for the given package entry.
+)", {
+ if(!s_entries.count(entry))
+ return false;
+
+ s_entries.erase(entry);
+ delete entry;
+ return true;
+});
+
+DEFINE_API(bool, GetEntryInfo, ((PackageEntry*, entry))
+ ((char*, repoOut))((int, repoOut_sz))((char*, catOut))((int, catOut_sz))
+ ((char*, pkgOut))((int, pkgOut_sz))((char*, descOut))((int, descOut_sz))
+ ((int*, typeOut))((char*, verOut))((int, verOut_sz))
+ ((char*, authorOut))((int, authorOut_sz))
+ ((bool*, pinnedOut))((int*, fileCountOut)), R"(
+ Get the repository name, category, package name, package description, package type, the currently installed version, author name, pinned status and how many files are owned by the given package entry.
+
+ type: 1=script, 2=extension, 3=effect, 4=data, 5=theme, 6=langpack, 7=webinterface
+)", {
+ if(!s_entries.count(entry))
+ return false;
+
+ const Registry::Entry ®Entry = entry->regEntry;
+
+ if(repoOut)
+ snprintf(repoOut, repoOut_sz, "%s", regEntry.remote.c_str());
+ if(catOut)
+ snprintf(catOut, catOut_sz, "%s", regEntry.category.c_str());
+ if(pkgOut)
+ snprintf(pkgOut, pkgOut_sz, "%s", regEntry.package.c_str());
+ if(descOut)
+ snprintf(descOut, descOut_sz, "%s", regEntry.description.c_str());
+ if(typeOut)
+ *typeOut = (int)regEntry.type;
+ if(verOut)
+ snprintf(verOut, verOut_sz, "%s", regEntry.version.toString().c_str());
+ if(authorOut)
+ snprintf(authorOut, authorOut_sz, "%s", regEntry.author.c_str());
+ if(pinnedOut)
+ *pinnedOut = regEntry.pinned;
+ if(fileCountOut)
+ *fileCountOut = (int)entry->files.size();
+
+ return true;
+});
+
+DEFINE_API(PackageEntry*, GetOwner, ((const char*, fn))((char*, errorOut))((int, errorOut_sz)), R"(
+ Returns the package entry owning the given file.
+ Delete the returned object from memory after use with <a href="#ReaPack_FreeEntry">ReaPack_FreeEntry</a>.
+)", {
+ Path path(fn);
+
+ const Path &rp = ReaPack::resourcePath();
+
+ if(path.startsWith(rp))
+ path.remove(0, rp.size());
+
+ try {
+ const Registry reg(Path::prefixRoot(Path::REGISTRY));
+ const auto &owner = reg.getOwner(path);
+
+ if(owner) {
+ auto entry = new PackageEntry{owner, reg.getFiles(owner)};
+ s_entries.insert(entry);
+ return entry;
+ }
+ else if(errorOut)
+ snprintf(errorOut, errorOut_sz, "the file is not owned by any package entry");
+
+ return nullptr;
+ }
+ catch(const reapack_error &e)
+ {
+ if(errorOut)
+ snprintf(errorOut, errorOut_sz, "%s", e.what());
+
+ return nullptr;
+ }
+});
diff --git a/src/api.hpp b/src/api.hpp
@@ -0,0 +1,57 @@
+/* ReaPack: Package manager for REAPER
+ * Copyright (C) 2015-2017 Christian Fillion
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REAPACK_API_HPP
+#define REAPACK_API_HPP
+
+class ReaPack;
+
+struct APIFunc {
+ const char *cKey;
+ void *cImpl;
+
+ const char *reascriptKey;
+ void *reascriptImpl;
+
+ const char *definitionKey;
+ void *definition;
+};
+
+class APIDef {
+public:
+ APIDef(const APIFunc *);
+ ~APIDef();
+
+private:
+ void unregister(const char *key, void *ptr);
+
+ const APIFunc *m_func;
+};
+
+namespace API {
+ extern ReaPack *reapack;
+
+ extern APIFunc AboutInstalledPackage;
+ extern APIFunc AboutRepository;
+ extern APIFunc CompareVersions;
+ extern APIFunc EnumOwnedFiles;
+ extern APIFunc FreeEntry;
+ extern APIFunc GetEntryInfo;
+ extern APIFunc GetOwner;
+};
+
+#endif
diff --git a/src/main.cpp b/src/main.cpp
@@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "api.hpp"
#include "errors.hpp"
#include "menu.hpp"
#include "reapack.hpp"
@@ -140,6 +141,34 @@ static bool checkLocation(REAPER_PLUGIN_HINSTANCE module)
return false;
}
+static void setupActions()
+{
+ reapack->setupAction("REAPACK_SYNC", "ReaPack: Synchronize packages",
+ &reapack->syncAction, bind(&ReaPack::synchronizeAll, reapack));
+
+ reapack->setupAction("REAPACK_BROWSE", "ReaPack: Browse packages...",
+ &reapack->browseAction, bind(&ReaPack::browsePackages, reapack));
+
+ reapack->setupAction("REAPACK_IMPORT", "ReaPack: Import a repository...",
+ &reapack->importAction, bind(&ReaPack::importRemote, reapack));
+
+ reapack->setupAction("REAPACK_MANAGE", "ReaPack: Manage repositories...",
+ &reapack->configAction, bind(&ReaPack::manageRemotes, reapack));
+
+ reapack->setupAction("REAPACK_ABOUT", bind(&ReaPack::aboutSelf, reapack));
+}
+
+static void setupAPI()
+{
+ reapack->setupAPI(&API::AboutInstalledPackage);
+ reapack->setupAPI(&API::AboutRepository);
+ reapack->setupAPI(&API::CompareVersions);
+ reapack->setupAPI(&API::EnumOwnedFiles);
+ reapack->setupAPI(&API::FreeEntry);
+ reapack->setupAPI(&API::GetEntryInfo);
+ reapack->setupAPI(&API::GetOwner);
+}
+
extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec)
{
@@ -161,21 +190,10 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
if(!checkLocation(instance))
return 0;
- reapack = new ReaPack(instance);
-
- reapack->setupAction("REAPACK_SYNC", "ReaPack: Synchronize packages",
- &reapack->syncAction, bind(&ReaPack::synchronizeAll, reapack));
-
- reapack->setupAction("REAPACK_BROWSE", "ReaPack: Browse packages...",
- &reapack->browseAction, bind(&ReaPack::browsePackages, reapack));
-
- reapack->setupAction("REAPACK_IMPORT", "ReaPack: Import a repository...",
- &reapack->importAction, bind(&ReaPack::importRemote, reapack));
-
- reapack->setupAction("REAPACK_MANAGE", "ReaPack: Manage repositories...",
- &reapack->configAction, bind(&ReaPack::manageRemotes, reapack));
+ reapack = API::reapack = new ReaPack(instance);
- reapack->setupAction("REAPACK_ABOUT", bind(&ReaPack::aboutSelf, reapack));
+ setupActions();
+ setupAPI();
plugin_register("hookcommand", (void *)commandHook);
plugin_register("hookcustommenu", (void *)menuHook);
diff --git a/src/path.cpp b/src/path.cpp
@@ -105,6 +105,25 @@ void Path::clear()
m_parts.clear();
}
+void Path::remove(const size_t pos, size_t count)
+{
+ if(pos > size())
+ return;
+ else if(pos + count > size())
+ count = size() - pos;
+
+ auto begin = m_parts.begin();
+ advance(begin, pos);
+
+ auto end = begin;
+ advance(end, count);
+
+ m_parts.erase(begin, end);
+
+ if(!pos && m_absolute)
+ m_absolute = false;
+}
+
void Path::removeLast()
{
if(!empty())
@@ -161,6 +180,19 @@ string Path::last() const
return m_parts.back();
}
+bool Path::startsWith(const Path &o) const
+{
+ if(size() < o.size() || absolute() != o.absolute())
+ return false;
+
+ for(size_t i = 0; i < o.size(); i++) {
+ if(o[i] != at(i))
+ return false;
+ }
+
+ return true;
+}
+
bool Path::operator==(const Path &o) const
{
return m_parts == o.m_parts;
@@ -207,9 +239,7 @@ const Path &Path::operator+=(const Path &o)
const string &Path::at(const size_t index) const
{
auto it = m_parts.begin();
-
- for(size_t i = 0; i < index; i++)
- it++;
+ advance(it, index);
return *it;
}
diff --git a/src/path.hpp b/src/path.hpp
@@ -37,6 +37,7 @@ public:
void append(const std::string &part, bool traversal = true);
void append(const Path &other);
+ void remove(size_t pos, size_t count);
void removeLast();
void clear();
@@ -49,6 +50,7 @@ public:
std::string join(const char sep = 0) const;
std::string first() const;
std::string last() const;
+ bool startsWith(const Path &) const;
bool operator==(const Path &) const;
bool operator!=(const Path &) const;
diff --git a/src/reapack.cpp b/src/reapack.cpp
@@ -18,6 +18,7 @@
#include "reapack.hpp"
#include "about.hpp"
+#include "api.hpp"
#include "browser.hpp"
#include "config.hpp"
#include "download.hpp"
@@ -142,6 +143,11 @@ bool ReaPack::execActions(const int id, const int)
return true;
}
+void ReaPack::setupAPI(const APIFunc *func)
+{
+ m_api.push_back(std::make_unique<APIDef>(func));
+}
+
void ReaPack::synchronizeAll()
{
const vector<Remote> &remotes = m_config->remotes.getEnabled();
diff --git a/src/reapack.hpp b/src/reapack.hpp
@@ -19,7 +19,6 @@
#define REAPACK_REAPACK_HPP
#include "path.hpp"
-#include "registry.hpp"
#include <functional>
#include <map>
@@ -29,12 +28,14 @@
#include <reaper_plugin.h>
class About;
+class APIDef;
class Browser;
class Config;
class Manager;
class Progress;
class Remote;
class Transaction;
+struct APIFunc;
class ReaPack {
public:
@@ -58,6 +59,8 @@ public:
gaccel_register_t *action, const ActionCallback &);
bool execActions(int id, int);
+ void setupAPI(const APIFunc *func);
+
void synchronizeAll();
void setRemoteEnabled(bool enable, const Remote &);
void enable(const Remote &r) { setRemoteEnabled(true, r); }
@@ -82,6 +85,7 @@ private:
void teardownTransaction();
std::map<int, ActionCallback> m_actions;
+ std::vector<std::unique_ptr<APIDef> > m_api;
Config *m_config;
Transaction *m_tx;
diff --git a/src/registry.cpp b/src/registry.cpp
@@ -57,6 +57,10 @@ Registry::Registry(const Path &path)
m_forgetEntry = m_db.prepare("DELETE FROM entries WHERE id = ?");
// file queries
+ m_getOwner = m_db.prepare(
+ "SELECT e.id, remote, category, package, desc, e.type, version, author, pinned "
+ "FROM entries e JOIN files f ON f.entry = e.id WHERE f.path = ? LIMIT 1"
+ );
m_getFiles = m_db.prepare(
"SELECT path, main, type FROM files WHERE entry = ? ORDER BY path"
);
@@ -260,9 +264,13 @@ auto Registry::getFiles(const Entry &entry) const -> vector<File>
m_getFiles->bind(1, entry.id);
m_getFiles->exec([&] {
- File file{m_getFiles->stringColumn(0)};
- file.sections = static_cast<int>(m_getFiles->intColumn(1));
- file.type = static_cast<Package::Type>(m_getFiles->intColumn(2));
+ int col = 0;
+
+ File file{
+ m_getFiles->stringColumn(col++),
+ static_cast<int>(m_getFiles->intColumn(col++)),
+ static_cast<Package::Type>(m_getFiles->intColumn(col++)),
+ };
if(!file.type) // < v1.0rc2
file.type = entry.type;
@@ -288,6 +296,20 @@ auto Registry::getMainFiles(const Entry &entry) const -> vector<File>
return mainFiles;
}
+auto Registry::getOwner(const Path &path) const -> Entry
+{
+ Entry entry{};
+
+ m_getOwner->bind(1, path.join('/'));
+
+ m_getOwner->exec([&] {
+ fillEntry(m_getOwner, &entry);
+ return false;
+ });
+
+ return entry;
+}
+
void Registry::forget(const Entry &entry)
{
m_forgetFiles->bind(1, entry.id);
diff --git a/src/registry.hpp b/src/registry.hpp
@@ -29,7 +29,9 @@
class Registry {
public:
struct Entry {
- int64_t id;
+ typedef int64_t id_t;
+
+ id_t id;
std::string remote;
std::string category;
std::string package;
@@ -54,6 +56,7 @@ public:
Registry(const Path &path = {});
Entry getEntry(const Package *) const;
+ Entry getOwner(const Path &) const;
std::vector<Entry> getEntries(const std::string &) const;
std::vector<File> getFiles(const Entry &) const;
std::vector<File> getMainFiles(const Entry &) const;
@@ -77,6 +80,7 @@ private:
Statement *m_findEntry;
Statement *m_allEntries;
Statement *m_forgetEntry;
+ Statement *m_getOwner;
Statement *m_getFiles;
Statement *m_insertFile;
@@ -86,13 +90,11 @@ private:
size_t m_savePoint;
};
-namespace std
-{
- template<> struct hash<Registry::Entry>
- {
+namespace std {
+ template<> struct hash<Registry::Entry> {
std::size_t operator()(const Registry::Entry &e) const
{
- return std::hash<int64_t>()(e.id);
+ return std::hash<Registry::Entry::id_t>()(e.id);
}
};
}
diff --git a/src/version.cpp b/src/version.cpp
@@ -126,13 +126,16 @@ void VersionName::parse(const string &str)
m_stable = letters < 1;
}
-bool VersionName::tryParse(const string &str)
+bool VersionName::tryParse(const string &str, string *errorOut)
{
try {
parse(str);
return true;
}
- catch(const reapack_error &) {
+ catch(const reapack_error &err) {
+ if(errorOut)
+ *errorOut = err.what();
+
return false;
}
}
diff --git a/src/version.hpp b/src/version.hpp
@@ -38,7 +38,7 @@ public:
VersionName(const VersionName &);
void parse(const std::string &);
- bool tryParse(const std::string &);
+ bool tryParse(const std::string &, std::string *errorOut = nullptr);
size_t size() const { return m_segments.size(); }
bool isStable() const { return m_stable; }
diff --git a/test/api.cpp b/test/api.cpp
@@ -0,0 +1,39 @@
+#include <catch.hpp>
+
+#include "helper/io.hpp"
+
+#include <api.hpp>
+
+using namespace std;
+
+static const char *M = "[api]";
+
+TEST_CASE("CompareVersions", M) {
+ const auto CompareVersions = (int (*)(const char *, const char *,
+ char *, int))API::CompareVersions.cImpl;
+
+ char error[255] = {};
+
+ SECTION("equal") {
+ REQUIRE(CompareVersions("1.0", "1.0", error, sizeof(error)) == 0);
+ REQUIRE(strcmp(error, "") == 0);
+ }
+
+ SECTION("lower") {
+ REQUIRE(CompareVersions("1.0", "2.0", error, sizeof(error)) < 0);
+ REQUIRE(strcmp(error, "") == 0);
+ }
+
+ SECTION("higher") {
+ REQUIRE(CompareVersions("2.0", "1.0", error, sizeof(error)) > 0);
+ REQUIRE(strcmp(error, "") == 0);
+ }
+
+ SECTION("invalid") {
+ REQUIRE(CompareVersions("abc", "def", error, sizeof(error)) == 0);
+ REQUIRE(strcmp(error, "invalid version name 'abc'") == 0);
+ }
+
+ SECTION("invalid no error buffer")
+ CompareVersions("abc", "def", nullptr, 0); // no crash
+}
diff --git a/test/path.cpp b/test/path.cpp
@@ -228,7 +228,7 @@ TEST_CASE("directory traversal", M) {
}
}
-TEST_CASE("append full paths") {
+TEST_CASE("append full paths", M) {
Path a;
a += Path("a/b");
a.append(Path("c/d"));
@@ -237,9 +237,43 @@ TEST_CASE("append full paths") {
REQUIRE(a == Path("a/b/c/d/e/f"));
}
-TEST_CASE("temporary path") {
+TEST_CASE("temporary path", M) {
TempPath a(Path("hello/world"));
REQUIRE(a.target() == Path("hello/world"));
REQUIRE(a.temp() == Path("hello/world.part"));
}
+
+TEST_CASE("path starts with", M) {
+ const Path ref("a/b");
+
+ REQUIRE(ref.startsWith(ref));
+ REQUIRE(Path("a/b/c").startsWith(ref));
+ REQUIRE_FALSE(Path("/a/b/c").startsWith(ref));
+ REQUIRE_FALSE(Path("0/a/b/c").startsWith(ref));
+ REQUIRE_FALSE(Path("a").startsWith(ref));
+}
+
+TEST_CASE("remove path segments", M) {
+ Path path("/a/b/c/d/e");
+
+ SECTION("remove from start") {
+ path.remove(0, 1);
+ REQUIRE(path == Path("b/c/d/e"));
+ REQUIRE_FALSE(path.absolute());
+ }
+
+ SECTION("remove from middle") {
+ path.remove(1, 2);
+ REQUIRE(path == Path("/a/d/e"));
+#ifndef _WIN32
+ REQUIRE(path.absolute());
+#endif
+ }
+
+ SECTION("remove past the end") {
+ path.remove(4, 2);
+ path.remove(18, 1);
+ REQUIRE(path == Path("/a/b/c/d"));
+ }
+}
diff --git a/test/registry.cpp b/test/registry.cpp
@@ -168,7 +168,7 @@ TEST_CASE("get main files", M) {
MAKE_PACKAGE
Registry reg;
- REQUIRE((reg.getMainFiles({})).empty());
+ REQUIRE(reg.getMainFiles({}).empty());
Source *main1 = new Source({}, "url", &ver);
main1->setSections(Source::MIDIEditorSection);
@@ -205,3 +205,13 @@ TEST_CASE("pin registry entry", M) {
reg.setPinned(entry, false);
REQUIRE_FALSE(reg.getEntry(&pkg).pinned);
}
+
+TEST_CASE("get file owner", M) {
+ MAKE_PACKAGE
+
+ Registry reg;
+ REQUIRE_FALSE(reg.getOwner({}));
+
+ const Registry::Entry &entry = reg.push(&ver);
+ REQUIRE(reg.getOwner(src->targetPath()) == entry);
+}
diff --git a/test/version.cpp b/test/version.cpp
@@ -90,8 +90,12 @@ TEST_CASE("parse version failsafe", M) {
SECTION("invalid") {
REQUIRE_FALSE(ver.tryParse("hello"));
+ string error;
+ REQUIRE_FALSE(ver.tryParse("world", &error));
+
REQUIRE(ver.toString().empty());
REQUIRE(ver.size() == 0);
+ REQUIRE(error == "invalid version name 'world'");
}
}