reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit b98a8652ae8bd66501f76b0e35066f5e9a1cbfc4
parent 133831039a4cc180cfa375d403105bdbd094c643
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Tue,  2 Feb 2016 17:17:50 -0500

add description and links to the repository indexes

Diffstat:
Msrc/about.cpp | 63++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/about.hpp | 10++++++++++
Msrc/dialog.cpp | 8++++----
Msrc/dialog.hpp | 6++++--
Msrc/index.cpp | 33++++++++++++++++++++++++++++++---
Msrc/index.hpp | 16+++++++++++++++-
Msrc/index_v1.cpp | 47++++++++++++++++++++++++++++++++++++++++++-----
Msrc/package.cpp | 2+-
Msrc/package.hpp | 2+-
Msrc/resource.hpp | 4++--
Mtest/index.cpp | 43+++++++++++++++++++++++++++++++++++++++++++
Mtest/index_v1.cpp | 31+++++++++++++++++++++++++++++++
Atest/indexes/v1/ReaPack/metadata.xml | 14++++++++++++++
Mtest/package.cpp | 4++--
14 files changed, 251 insertions(+), 32 deletions(-)

diff --git a/src/about.cpp b/src/about.cpp @@ -20,10 +20,13 @@ #include "encoding.hpp" #include "index.hpp" #include "listview.hpp" +#include "menu.hpp" #include "resource.hpp" #include "richedit.hpp" #include "tabbar.hpp" +#include <boost/algorithm/string/replace.hpp> + using namespace std; About::About(const RemoteIndex *index) @@ -61,6 +64,9 @@ void About::onInit() {AUTO_STR("Installed Files"), {}}, }); + m_website = getControl(IDC_WEBSITE); + m_donate = getControl(IDC_DONATE); + populate(); #ifdef LVSCW_AUTOSIZE_USEHEADER @@ -72,10 +78,21 @@ void About::onInit() void About::onCommand(const int id) { switch(id) { + case IDC_WEBSITE: + selectLink(id, m_websiteLinks); + break; + case IDC_DONATE: + selectLink(id, m_donationLinks); + break; case IDOK: case IDCANCEL: close(); break; + default: + if(id >> 8 == IDC_WEBSITE) + openLink(m_websiteLinks[id & 0xff]); + else if(id >> 8 == IDC_DONATE) + openLink(m_donationLinks[id & 0xff]); } } @@ -86,17 +103,15 @@ void About::populate() auto_snprintf(title, sizeof(title), AUTO_STR("About %s"), name.c_str()); SetWindowText(handle(), title); - const char *tmpRtf = \ - "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1348\\cocoasubrtf170\n" - "{\\fonttbl\\f0\\fnil\\fcharset134 STHeitiSC-Light;}\n" - "{\\colortbl;\\red255\\green255\\blue255;}\n" - "\\margl1440\\margr1440\\vieww10800\\viewh8400\\viewkind0\n" - "\\pard\\tx566\\tx1133\\tx1700\\tx2267\\tx2834\\tx3401\\tx3968\\tx4535\\tx5102\\tx5669\\tx6236\\tx6803\\pardirnatural\n" - "\\f0\\fs24 \\cf0 http://perdu.com test" - "{\\field{\\*\\fldinst{HYPERLINK \"https://msdn.microsoft.com/en-us/library/windows/desktop/bb787974%28v=vs.85%29.aspx\"}}{\\fldrslt \\f0\\fs24 \\cf0 \\'d0\\'c2\\'ca\\'c0\\'bd\\'e7\\'a4\\'e8\\'a4\\'ea}}}\n" - ; - - if(!m_about->setRichText(tmpRtf)) { + m_websiteLinks = m_index->links(RemoteIndex::WebsiteLink); + if(m_websiteLinks.empty()) + hide(m_website); + + m_donationLinks = m_index->links(RemoteIndex::DonationLink); + if(m_donationLinks.empty()) + hide(m_donate); + + if(!m_about->setRichText(m_index->aboutText())) { // if description is invalid or empty, don't display it m_tabs->removeTab(0); m_tabs->setCurrentIndex(0); @@ -144,3 +159,29 @@ void About::updatePackages() m_currentCat = catIndex; } + +void About::selectLink(const int ctrl, const std::vector<const Link *> &links) +{ + const int count = (int)links.size(); + + if(count > 1) { + Menu menu; + + for(int i = 0; i < count; i++) { + const string &name = boost::replace_all_copy(links[i]->name, "&", "&&"); + menu.addAction(make_autostring(name).c_str(), i | (ctrl << 8)); + } + + RECT rect; + GetWindowRect(getControl(ctrl), &rect); + menu.show(rect.left, rect.bottom - 1, handle()); + } + else if(count == 1) + openLink(links.front()); +} + +void About::openLink(const Link *link) +{ + const auto_string &url = make_autostring(link->url); + ShellExecute(nullptr, AUTO_STR("open"), url.c_str(), nullptr, nullptr, SW_SHOW); +} diff --git a/src/about.hpp b/src/about.hpp @@ -20,6 +20,9 @@ #include "dialog.hpp" +#include <vector> + +struct Link; class ListView; class RemoteIndex; class RichEdit; @@ -36,6 +39,8 @@ protected: private: void populate(); void updatePackages(); + void selectLink(const int control, const std::vector<const Link *> &); + void openLink(const Link *); const RemoteIndex *m_index; int m_currentCat; @@ -44,6 +49,11 @@ private: RichEdit *m_about; ListView *m_cats; ListView *m_packages; + HWND m_website; + HWND m_donate; + + std::vector<const Link *> m_websiteLinks; + std::vector<const Link *> m_donationLinks; }; #endif diff --git a/src/dialog.cpp b/src/dialog.cpp @@ -130,14 +130,14 @@ void Dialog::Destroy(Dialog *dlg) delete dlg; } -void Dialog::show() +void Dialog::show(HWND handle) { - ShowWindow(m_handle, SW_SHOW); + ShowWindow(handle, SW_SHOW); } -void Dialog::hide() +void Dialog::hide(HWND handle) { - ShowWindow(m_handle, SW_HIDE); + ShowWindow(handle, SW_HIDE); } void Dialog::close(const INT_PTR result) diff --git a/src/dialog.hpp b/src/dialog.hpp @@ -72,8 +72,10 @@ public: void setEnabled(const bool enable) { setEnabled(enable, m_handle); } void setEnabled(const bool, HWND); - void show(); - void hide(); + void show(HWND); + void show() { show(m_handle); } + void hide(HWND); + void hide() { hide(m_handle); } void close(const INT_PTR = 0); void center(); void setFocus(); diff --git a/src/index.cpp b/src/index.cpp @@ -28,13 +28,13 @@ using namespace std; static FILE *OpenFile(const char *path) { - #ifdef _WIN32 +#ifdef _WIN32 FILE *file = nullptr; _wfopen_s(&file, make_autostring(path).c_str(), L"rb"); return file; - #else +#else return fopen(path, "rb"); - #endif +#endif } Path RemoteIndex::pathFor(const string &name) @@ -42,6 +42,14 @@ Path RemoteIndex::pathFor(const string &name) return Path::prefixCache(name + ".xml"); } +auto RemoteIndex::linkTypeFor(const char *rel) -> LinkType +{ + if(!strcmp(rel, "donation")) + return DonationLink; + + return WebsiteLink; +} + RemoteIndex *RemoteIndex::load(const string &name) { TiXmlDocument doc; @@ -104,6 +112,25 @@ void RemoteIndex::addCategory(Category *cat) cat->packages().begin(), cat->packages().end()); } +void RemoteIndex::addLink(const LinkType type, const Link &link) +{ + if(link.url.find("http") == 0) + m_links.insert({type, link}); +} + +auto RemoteIndex::links(const LinkType type) const -> LinkList +{ + const auto begin = m_links.lower_bound(type); + const auto end = m_links.upper_bound(type); + + LinkList list(m_links.count(type)); + + for(auto it = begin; it != end; it++) + list[distance(begin, it)] = &it->second; + + return list; +} + Category::Category(const string &name, RemoteIndex *ri) : m_index(ri), m_name(name) { diff --git a/src/index.hpp b/src/index.hpp @@ -18,7 +18,7 @@ #ifndef REAPACK_INDEX_HPP #define REAPACK_INDEX_HPP -#include <memory> +#include <map> #include <string> #include <vector> @@ -30,9 +30,16 @@ typedef std::vector<Category *> CategoryList; class Path; class TiXmlElement; +struct Link { std::string name; std::string url; }; + class RemoteIndex { public: + enum LinkType { WebsiteLink, DonationLink }; + typedef std::multimap<LinkType, Link> LinkMap; + typedef std::vector<const Link *> LinkList; + static Path pathFor(const std::string &name); + static LinkType linkTypeFor(const char *rel); static RemoteIndex *load(const std::string &name); RemoteIndex(const std::string &name); @@ -40,6 +47,11 @@ public: const std::string &name() const { return m_name; } + void setAboutText(const std::string &rtf) { m_about = rtf; } + const std::string &aboutText() const { return m_about; } + void addLink(const LinkType, const Link &); + LinkList links(const LinkType type) const; + void addCategory(Category *cat); const CategoryList &categories() const { return m_categories; } Category *category(const size_t i) const { return m_categories[i]; } @@ -50,6 +62,8 @@ private: static RemoteIndex *loadV1(TiXmlElement *, const std::string &); std::string m_name; + std::string m_about; + LinkMap m_links; CategoryList m_categories; PackageList m_packages; }; diff --git a/src/index_v1.cpp b/src/index_v1.cpp @@ -25,6 +25,7 @@ using namespace std; +static void LoadMetadataV1(TiXmlElement *, RemoteIndex *ri); static void LoadCategoryV1(TiXmlElement *, RemoteIndex *ri); static void LoadPackageV1(TiXmlElement *, Category *cat); static void LoadVersionV1(TiXmlElement *, Package *pkg); @@ -37,18 +38,54 @@ RemoteIndex *RemoteIndex::loadV1(TiXmlElement *root, const string &name) // thrown during the loading process unique_ptr<RemoteIndex> ptr(ri); - TiXmlElement *catNode = root->FirstChildElement("category"); + TiXmlElement *node = root->FirstChildElement("category"); - while(catNode) { - LoadCategoryV1(catNode, ri); + while(node) { + LoadCategoryV1(node, ri); - catNode = catNode->NextSiblingElement("category"); + node = node->NextSiblingElement("category"); } + node = root->FirstChildElement("metadata"); + + if(node) + LoadMetadataV1(node, ri); + ptr.release(); return ri; } +void LoadMetadataV1(TiXmlElement *meta, RemoteIndex *ri) +{ + TiXmlElement *node = meta->FirstChildElement("description"); + + if(node) { + const char *rtf = node->GetText(); + + if(rtf) + ri->setAboutText(rtf); + } + + node = meta->FirstChildElement("link"); + + while(node) { + const char *rel = node->Attribute("rel"); + const char *url = node->Attribute("href"); + const char *name = node->GetText(); + + if(!rel) rel = ""; + if(!name) { + if(!url) url = ""; + name = url; + } + else if(!url) url = name; + + ri->addLink(RemoteIndex::linkTypeFor(rel), {name, url}); + + node = node->NextSiblingElement("link"); + } +} + void LoadCategoryV1(TiXmlElement *catNode, RemoteIndex *ri) { const char *name = catNode->Attribute("name"); @@ -78,7 +115,7 @@ void LoadPackageV1(TiXmlElement *packNode, Category *cat) const char *name = packNode->Attribute("name"); if(!name) name = ""; - Package *pack = new Package(Package::ConvertType(type), name, cat); + Package *pack = new Package(Package::typeFor(type), name, cat); unique_ptr<Package> ptr(pack); TiXmlElement *verNode = packNode->FirstChildElement("version"); diff --git a/src/package.cpp b/src/package.cpp @@ -24,7 +24,7 @@ using namespace std; -Package::Type Package::ConvertType(const char *type) +Package::Type Package::typeFor(const char *type) { if(!strcmp(type, "script")) return ScriptType; diff --git a/src/package.hpp b/src/package.hpp @@ -33,7 +33,7 @@ public: ScriptType, }; - static Type ConvertType(const char *); + static Type typeFor(const char *); Package(const Type, const std::string &name, Category * = nullptr); ~Package(); diff --git a/src/resource.hpp b/src/resource.hpp @@ -43,8 +43,8 @@ #define IDC_IMPORT 204 #define IDC_TABS 205 #define IDC_ENABLE 206 -#define IDC_DONATE 207 -#define IDC_WEBSITE 208 +#define IDC_WEBSITE 207 +#define IDC_DONATE 208 #define IDC_ABOUT 209 #define IDC_CATEGORIES 210 #define IDC_PACKAGES 211 diff --git a/test/index.cpp b/test/index.cpp @@ -201,3 +201,46 @@ TEST_CASE("category full name", M) { Category cat2("Category Name", &ri); REQUIRE(cat2.fullName() == "Remote Name/Category Name"); } + +TEST_CASE("repository description", M) { + RemoteIndex ri("Remote Name"); + CHECK(ri.aboutText().empty()); + + ri.setAboutText("Hello World"); + REQUIRE(ri.aboutText() == "Hello World"); +} + +TEST_CASE("repository links", M) { + RemoteIndex ri("Remote name"); + CHECK(ri.links(RemoteIndex::WebsiteLink).empty()); + CHECK(ri.links(RemoteIndex::DonationLink).empty()); + + SECTION("website links") { + ri.addLink(RemoteIndex::WebsiteLink, {"First", "http://example.com"}); + REQUIRE(ri.links(RemoteIndex::WebsiteLink).size() == 1); + ri.addLink(RemoteIndex::WebsiteLink, {"Second", "http://example.com"}); + + const auto &links = ri.links(RemoteIndex::WebsiteLink); + REQUIRE(links.size() == 2); + REQUIRE(links[0]->name == "First"); + REQUIRE(links[1]->name == "Second"); + + REQUIRE(ri.links(RemoteIndex::DonationLink).empty()); + } + + SECTION("donation links") { + ri.addLink(RemoteIndex::DonationLink, {"First", "http://example.com"}); + REQUIRE(ri.links(RemoteIndex::DonationLink).size() == 1); + } + + SECTION("drop invalid links") { + ri.addLink(RemoteIndex::WebsiteLink, {"name", "not http(s)"}); + REQUIRE(ri.links(RemoteIndex::WebsiteLink).empty()); + } +} + +TEST_CASE("link type from string", M) { + REQUIRE(RemoteIndex::linkTypeFor("website") == RemoteIndex::WebsiteLink); + REQUIRE(RemoteIndex::linkTypeFor("donation") == RemoteIndex::DonationLink); + REQUIRE(RemoteIndex::linkTypeFor("bacon") == RemoteIndex::WebsiteLink); +} diff --git a/test/index_v1.cpp b/test/index_v1.cpp @@ -177,3 +177,34 @@ TEST_CASE("full index", M) { REQUIRE(source->file() == "test.lua"); REQUIRE(source->url() == "https://google.com/"); } + +TEST_CASE("read index metadata", M) { + UseRootPath root(RIPATH); + + RemoteIndex *ri = RemoteIndex::load("metadata"); + RIPTR(ri); + + SECTION("description") { + REQUIRE(ri->aboutText() == "Chunky\nBacon"); + } + + SECTION("website links") { + const auto &links = ri->links(RemoteIndex::WebsiteLink); + REQUIRE(links.size() == 4); + REQUIRE(links[0]->name == "http://cfillion.tk"); + REQUIRE(links[0]->url == "http://cfillion.tk"); + REQUIRE(links[1]->name == "https://github.com/cfillion"); + REQUIRE(links[1]->url == "https://github.com/cfillion"); + REQUIRE(links[2]->name == "http://twitter.com/cfi30"); + REQUIRE(links[2]->url == "http://twitter.com/cfi30"); + REQUIRE(links[3]->name == "http://google.com"); + REQUIRE(links[3]->url == "http://google.com"); + } + + SECTION("donation links") { + const auto &links = ri->links(RemoteIndex::DonationLink); + REQUIRE(links.size() == 1); + REQUIRE(links[0]->name == "Donate"); + REQUIRE(links[0]->url == "http://paypal.com"); + } +} diff --git a/test/indexes/v1/ReaPack/metadata.xml b/test/indexes/v1/ReaPack/metadata.xml @@ -0,0 +1,14 @@ +<index version="1"> + <metadata> + <link rel="website" href="http://cfillion.tk" /> + <link rel="website" href="https://github.com/cfillion"></link> + <link rel="website">http://twitter.com/cfi30</link> + <link>http://google.com</link> + <link /> + <description><![CDATA[Chunky +Bacon]]> + </description> + <link rel="donation" href="http://paypal.com">Donate</link> + <link rel="website" href="/"></link> + </metadata> +</index> diff --git a/test/package.cpp b/test/package.cpp @@ -14,11 +14,11 @@ static const char *M = "[package]"; TEST_CASE("package type from string", M) { SECTION("unknown") { - REQUIRE(Package::ConvertType("yoyo") == Package::UnknownType); + REQUIRE(Package::typeFor("yoyo") == Package::UnknownType); } SECTION("script") { - REQUIRE(Package::ConvertType("script") == Package::ScriptType); + REQUIRE(Package::typeFor("script") == Package::ScriptType); } }