reapack

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

commit d4d0a5e1549a19fcb12e9de3fd0b76d954db3112
parent c2777c139fa705579b18a37784eb9e6847090546
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Wed, 13 Sep 2017 21:29:20 -0400

redesign report dialog

Diffstat:
Msrc/about.cpp | 4++--
Msrc/errors.hpp | 7+++++++
Dsrc/ostream.cpp | 66------------------------------------------------------------------
Dsrc/ostream.hpp | 42------------------------------------------
Msrc/path.hpp | 5+++++
Msrc/reapack.cpp | 2+-
Msrc/receipt.cpp | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/receipt.hpp | 91++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/report.cpp | 134++++++++++++++-----------------------------------------------------------------
Msrc/report.hpp | 23+++++------------------
Msrc/resource.rc | 7++++---
Msrc/string.cpp | 32++++++++++++++++++++++++++++++--
Msrc/string.hpp | 2++
Msrc/tabbar.cpp | 1+
Msrc/tabbar.hpp | 5+++++
Msrc/task.cpp | 5+----
Msrc/time.cpp | 6++++++
Msrc/time.hpp | 2++
Msrc/version.cpp | 18++++++++++++++++++
Msrc/version.hpp | 2++
Mtest/helper.cpp | 18------------------
Mtest/helper.hpp | 4----
Dtest/ostream.cpp | 59-----------------------------------------------------------
Mtest/receipt.cpp | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mtest/string.cpp | 12++++++++++++
Mtest/version.cpp | 34++++++++++++++++++++++++++++++++++
26 files changed, 389 insertions(+), 403 deletions(-)

diff --git a/src/about.cpp b/src/about.cpp @@ -24,7 +24,6 @@ #include "index.hpp" #include "listview.hpp" #include "menu.hpp" -#include "ostream.hpp" #include "reapack.hpp" #include "registry.hpp" #include "remote.hpp" @@ -37,6 +36,7 @@ #include <boost/algorithm/string.hpp> #include <iomanip> +#include <sstream> using namespace std; @@ -556,7 +556,7 @@ void AboutPackageDelegate::updateList(const int index) return; const Version *ver = m_package->version(index); - OutputStream stream; + ostringstream stream; stream << *ver; Win32::setWindowText(m_dialog->getControl(IDC_CHANGELOG), stream.str().c_str()); diff --git a/src/errors.hpp b/src/errors.hpp @@ -18,6 +18,7 @@ #ifndef REAPACK_ERRORS_HPP #define REAPACK_ERRORS_HPP +#include <ostream> #include <stdexcept> #include "string.hpp" @@ -32,4 +33,10 @@ struct ErrorInfo { std::string context; }; +inline std::ostream &operator<<(std::ostream &os, const ErrorInfo &err) +{ + os << err.context << ":\r\n" << String::indent(err.message) << "\r\n"; + return os; +} + #endif diff --git a/src/ostream.cpp b/src/ostream.cpp @@ -1,66 +0,0 @@ -/* 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 "ostream.hpp" - -#include "version.hpp" - -#include <boost/algorithm/string/trim.hpp> -#include <locale> - -using namespace std; - -OutputStream::OutputStream() -{ - // enable number formatting (ie. "1,234" instead of "1234") - m_stream.imbue(locale("")); -} - -void OutputStream::indented(const string &text) -{ - istringstream stream(text); - string line; - - while(getline(stream, line, '\n')) { - boost::algorithm::trim(line); - - if(!line.empty()) - m_stream << "\x20\x20" << line; - - m_stream << "\r\n"; - } -} - -OutputStream &OutputStream::operator<<(const Version &ver) -{ - m_stream << 'v' << ver.name().toString(); - - if(!ver.author().empty()) - m_stream << " by " << ver.author(); - - const string &date = ver.time().toString(); - if(!date.empty()) - m_stream << " – " << date; - - m_stream << "\r\n"; - - const string &changelog = ver.changelog(); - indented(changelog.empty() ? "No changelog" : changelog); - - return *this; -} - diff --git a/src/ostream.hpp b/src/ostream.hpp @@ -1,42 +0,0 @@ -/* 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_OSTREAM_HPP -#define REAPACK_OSTREAM_HPP - -#include <sstream> - -class Version; - -class OutputStream { -public: - OutputStream(); - - std::ostringstream::pos_type tellp() { return m_stream.tellp(); } - std::string str() const { return m_stream.str(); } - - void indented(const std::string &); - - template<typename T> - OutputStream &operator<<(T t) { m_stream << t; return *this; } - OutputStream &operator<<(const Version &); - -private: - std::ostringstream m_stream; -}; - -#endif diff --git a/src/path.hpp b/src/path.hpp @@ -77,6 +77,11 @@ private: bool m_absolute; }; +inline std::ostream &operator<<(std::ostream &os, const Path &p) +{ + return os << p.join(); +}; + class UseRootPath { public: UseRootPath(const Path &); diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -358,7 +358,7 @@ Transaction *ReaPack::setupTransaction() LockDialog managerLock(m_manager); LockDialog browserLock(m_browser); - Dialog::Show<Report>(m_instance, m_mainWindow, *m_tx->receipt()); + Dialog::Show<Report>(m_instance, m_mainWindow, m_tx->receipt()); } }); diff --git a/src/receipt.cpp b/src/receipt.cpp @@ -19,10 +19,13 @@ #include "index.hpp" +#include <boost/range/adaptor/reversed.hpp> +#include <sstream> + using namespace std; Receipt::Receipt() - : m_needRestart(false) + : m_flags(NoFlags) { } @@ -30,24 +33,86 @@ bool Receipt::empty() const { return m_installs.empty() && - m_updates.empty() && m_removals.empty() && m_exports.empty() && m_errors.empty(); } -void Receipt::addInstall(const InstallTicket &ticket) +void Receipt::addInstall(const Version *ver, const Registry::Entry &entry) { - if(ticket.previous && ticket.previous.version < ticket.version->name()) - m_updates.push_back(ticket); - else - m_installs.push_back(ticket); + m_installs.emplace_back(InstallTicket{ver, entry}); + + if(ver->package()->type() == Package::ExtensionType) + m_flags |= RestartNeeded; +} - m_indexes.insert(ticket.version->package()->category() - ->index()->shared_from_this()); +void Receipt::addRemoval(const Path &path) +{ + m_removals.insert(path); } void Receipt::addRemovals(const set<Path> &pathList) { m_removals.insert(pathList.begin(), pathList.end()); } + +void Receipt::addExport(const Path &path) +{ + m_exports.insert(path); +} + +void Receipt::addError(const ErrorInfo &err) +{ + m_errors.push_back(err); +} + +ReceiptPages Receipt::pages() const +{ + return ReceiptPages{{ + {m_installs, "Installed"}, + {m_removals, "Removed"}, + {m_exports, "Exported"}, + {m_errors, "Error", "Errors"}, + }}; +} + +void ReceiptPage::setTitle(const char *title) +{ + ostringstream stream; + + // enable number formatting (ie. "1,234" instead of "1234") + stream.imbue(locale("")); + + stream << title << " (" << m_size << ')'; + m_title = stream.str(); +} + +InstallTicket::InstallTicket(const Version *ver, const Registry::Entry &previous) + : m_version(ver), m_previous(previous.version) +{ + m_isUpdate = previous && previous.version < ver->name(); + m_index = ver->package()->category()->index()->shared_from_this(); +} + +ostream &operator<<(ostream &os, const InstallTicket &t) +{ + if(os.tellp() > 0) + os << "\r\n"; + + os << t.m_version->package()->fullName() << "\r\n"; + + if(t.m_isUpdate) { + const auto &versions = t.m_version->package()->versions(); + + for(const Version *ver : versions | boost::adaptors::reversed) { + if(ver->name() <= t.m_previous) + break; + else if(ver->name() <= t.m_version->name()) + os << *ver; + } + } + else + os << *t.m_version; + + return os; +} diff --git a/src/receipt.hpp b/src/receipt.hpp @@ -18,58 +18,91 @@ #ifndef REAPACK_RECEIPT_HPP #define REAPACK_RECEIPT_HPP -#include "errors.hpp" #include "registry.hpp" +#include "errors.hpp" -#include <set> -#include <string> -#include <unordered_set> +#include <array> #include <vector> +#include <set> class Index; +class InstallTicket; class Path; +class ReceiptPage; +class Version; typedef std::shared_ptr<const Index> IndexPtr; - -struct InstallTicket { - const Version *version; - Registry::Entry previous; -}; +typedef std::array<ReceiptPage, 4> ReceiptPages; class Receipt { public: - Receipt(); + enum Flag { + NoFlags = 0, + RestartNeeded = 1<<0, + }; - bool empty() const; + Receipt(); - bool isRestartNeeded() const { return m_needRestart; } - void setRestartNeeded(bool newVal) { m_needRestart = newVal; } + bool test(Flag f) const { return (m_flags & f) != 0; } - void addInstall(const InstallTicket &); - const std::vector<InstallTicket> &installs() const { return m_installs; } - const std::vector<InstallTicket> &updates() const { return m_updates; } + bool empty() const; + ReceiptPages pages() const; - void addRemoval(const Path &p) { m_removals.insert(p); } + void addInstall(const Version *, const Registry::Entry &); + void addRemoval(const Path &p); void addRemovals(const std::set<Path> &); - const std::set<Path> &removals() const { return m_removals; } - - void addExport(const Path &p) { m_exports.insert(p); } - const std::set<Path> &exports() const { return m_exports; } - - void addError(const ErrorInfo &err) { m_errors.push_back(err); } - const std::vector<ErrorInfo> &errors() const { return m_errors; } - bool hasErrors() const { return !m_errors.empty(); } + void addExport(const Path &p); + void addError(const ErrorInfo &); private: - bool m_needRestart; - + int m_flags; std::vector<InstallTicket> m_installs; - std::vector<InstallTicket> m_updates; std::set<Path> m_removals; std::set<Path> m_exports; std::vector<ErrorInfo> m_errors; +}; - std::unordered_set<IndexPtr> m_indexes; // keep them alive! +class ReceiptPage { +public: + template<typename T> + ReceiptPage(const T &list, const char *singular, const char *plural = nullptr) + : m_size(list.size()) + { + setTitle(m_size == 1 || !plural ? singular : plural); + + std::ostringstream stream; + + for(const auto &item : list) + stream << item << "\r\n"; + + m_contents = stream.str(); + } + + const std::string &title() const { return m_title; } + const std::string &contents() const { return m_contents; } + bool empty() const { return m_size == 0; } + +private: + void setTitle(const char *title); + size_t m_size; + std::string m_title; + std::string m_contents; +}; + +class InstallTicket { +public: + InstallTicket(const Version *ver, const Registry::Entry &previousEntry); + +private: + friend std::ostream &operator<<(std::ostream &, const InstallTicket &); + + const Version *m_version; + VersionName m_previous; + bool m_isUpdate; + + IndexPtr m_index; // to keep it alive long enough }; +std::ostream &operator<<(std::ostream &, const InstallTicket &); + #endif diff --git a/src/report.cpp b/src/report.cpp @@ -17,17 +17,14 @@ #include "report.hpp" -#include "package.hpp" +#include "receipt.hpp" #include "resource.hpp" -#include "source.hpp" -#include "transaction.hpp" +#include "tabbar.hpp" #include "win32.hpp" -#include <boost/range/adaptor/reversed.hpp> - using namespace std; -Report::Report(const Receipt &receipt) +Report::Report(const Receipt *receipt) : Dialog(IDD_REPORT_DIALOG), m_receipt(receipt) { } @@ -36,120 +33,37 @@ void Report::onInit() { Dialog::onInit(); - fillReport(); - - SetFocus(getControl(IDOK)); -} - -void Report::printHeader(const char *title) -{ - if(m_stream.tellp()) - m_stream << "\r\n"; - - const string sep(10, '='); - m_stream << sep << ' ' << title << ": " << sep << "\r\n\r\n"; -} - -void Report::fillReport() -{ - const size_t installs = m_receipt.installs().size(); - const size_t updates = m_receipt.updates().size(); - const size_t removals = m_receipt.removals().size(); - const size_t errors = m_receipt.errors().size(); - - m_stream << installs << " installed package"; - if(installs != 1) m_stream << 's'; - - m_stream << ", " << updates << " update"; - if(updates != 1) m_stream << 's'; - - m_stream << ", " << removals << " removed file"; - if(removals != 1) m_stream << 's'; - - m_stream << " and " << errors << " error"; - if(errors != 1) m_stream << 's'; - - m_stream << "\r\n"; - - if(m_receipt.isRestartNeeded()) { - m_stream - << "\r\n" - << "Notice: One or more native REAPER extensions were installed.\r\n" - << "The newly installed files won't be loaded until REAPER is restarted." - << "\r\n"; - } - - if(errors) - printErrors(); - - if(installs) - printInstalls(); + HWND report = getControl(IDC_REPORT); + TabBar *tabbar = createControl<TabBar>(IDC_TABS, this); - if(updates) - printUpdates(); + tabbar->onTabChange([=] (const int i) { + Win32::setWindowText(report, m_pages[i].c_str()); + }); - if(removals) - printRemovals(); + bool firstPage = true; - if(installs + updates + removals == 0) { - Win32::setWindowText(getControl(IDC_LABEL), - "Oops! The following error(s) occured:"); - } - - Win32::setWindowText(getControl(IDC_REPORT), m_stream.str().c_str()); -} - -void Report::printInstalls() -{ - printHeader("Installed packages"); - - for(const InstallTicket &ticket : m_receipt.installs()) - m_stream << ticket.version->fullName() << "\r\n"; -} - -void Report::printUpdates() -{ - printHeader("Updates"); - - const auto start = m_stream.tellp(); + for(const ReceiptPage &page : m_receipt->pages()) { + m_pages.emplace_back(page.contents()); + tabbar->addTab({page.title().c_str()}); - for(const InstallTicket &ticket : m_receipt.updates()) { - const Package *pkg = ticket.version->package(); - const auto &versions = pkg->versions(); - - if(m_stream.tellp() != start) - m_stream << "\r\n"; - - m_stream << pkg->fullName() << ":\r\n"; - - for(const Version *ver : versions | boost::adaptors::reversed) { - if(ver->name() <= ticket.previous.version) - break; - else if(ver->name() <= ticket.version->name()) - m_stream << *ver; + if(firstPage && !page.empty()) { + tabbar->setCurrentIndex(tabbar->count() - 1); + firstPage = false; } } -} -void Report::printErrors() -{ - printHeader("Errors"); - - const auto start = m_stream.tellp(); - - for(const ErrorInfo &err : m_receipt.errors()) { - if(m_stream.tellp() != start) - m_stream << "\r\n"; + SetFocus(getControl(IDOK)); - m_stream << err.context << ":\r\n"; - m_stream.indented(err.message); - } + if(m_receipt->test(Receipt::RestartNeeded)) + startTimer(1); } -void Report::printRemovals() +void Report::onTimer(int timer) { - printHeader("Removed files"); + stopTimer(timer); - for(const Path &path : m_receipt.removals()) - m_stream << path.join() << "\r\n"; + Win32::messageBox(handle(), + "One or more native REAPER extensions were installed.\r\n" + "These newly installed files won't be loaded until REAPER is restarted.", + "ReaPack Notice", MB_OK); } diff --git a/src/report.hpp b/src/report.hpp @@ -20,32 +20,19 @@ #include "dialog.hpp" -#include "ostream.hpp" -#include "receipt.hpp" -#include "registry.hpp" - -class Package; -class Version; +class Receipt; class Report : public Dialog { public: - Report(const Receipt &); + Report(const Receipt *); protected: void onInit() override; - - void fillReport(); - - void printHeader(const char *); - - void printInstalls(); - void printUpdates(); - void printErrors(); - void printRemovals(); + void onTimer(int) override; private: - Receipt m_receipt; - OutputStream m_stream; + const Receipt *m_receipt; + std::vector<std::string> m_pages; }; #endif diff --git a/src/resource.rc b/src/resource.rc @@ -13,16 +13,17 @@ BEGIN PUSHBUTTON "&Cancel", IDCANCEL, 105, 60, 50, 14, NOT WS_TABSTOP END -IDD_REPORT_DIALOG DIALOGEX 0, 0, 280, 260 +IDD_REPORT_DIALOG DIALOGEX 0, 0, 300, 286 STYLE DIALOG_STYLE FONT DIALOG_FONT CAPTION "ReaPack: Transaction Report" BEGIN LTEXT "All done! Description of the changes:", IDC_LABEL, 5, 5, 270, 10 - EDITTEXT IDC_REPORT, 6, 18, 268, 215, WS_VSCROLL | ES_MULTILINE | + CONTROL "", IDC_TABS, WC_TABCONTROL, 0, 2, 16, 296, 246 + EDITTEXT IDC_REPORT, 9, 34, 281, 223, WS_VSCROLL | ES_MULTILINE | ES_READONLY | NOT WS_TABSTOP - DEFPUSHBUTTON "&OK", IDOK, 115, 240, 50, 14 + DEFPUSHBUTTON "&OK", IDOK, 125, 266, 50, 14 END IDD_CONFIG_DIALOG DIALOGEX 0, 0, 370, 240 diff --git a/src/string.cpp b/src/string.cpp @@ -17,7 +17,13 @@ #include "string.hpp" -std::string String::format(const char *fmt, ...) +#include <boost/algorithm/string/trim.hpp> +#include <cstdarg> +#include <sstream> + +using namespace std; + +string String::format(const char *fmt, ...) { va_list args; @@ -25,7 +31,7 @@ std::string String::format(const char *fmt, ...) const int size = vsnprintf(nullptr, 0, fmt, args); va_end(args); - std::string buf(size, 0); + string buf(size, 0); va_start(args, fmt); vsnprintf(&buf[0], size + 1, fmt, args); @@ -33,3 +39,25 @@ std::string String::format(const char *fmt, ...) return buf; } + +string String::indent(const string &text) +{ + ostringstream output; + istringstream input(text); + string line; + bool first = true; + + while(getline(input, line, '\n')) { + if(first) + first = false; + else + output << "\r\n"; + + boost::algorithm::trim(line); + + if(!line.empty()) + output << "\x20\x20" << line; + } + + return output.str(); +} diff --git a/src/string.hpp b/src/string.hpp @@ -25,6 +25,8 @@ namespace String { __attribute__((format(printf, 1, 2))) #endif std::string format(const char *fmt, ...); + + std::string indent(const std::string &); } #endif diff --git a/src/tabbar.cpp b/src/tabbar.cpp @@ -117,6 +117,7 @@ void TabBar::switchPage() } const int index = currentIndex(); + m_onTabChange(index); if(index < 0 || (size_t)index >= m_pages.size()) { m_lastPage = -1; diff --git a/src/tabbar.hpp b/src/tabbar.hpp @@ -20,12 +20,15 @@ #include "control.hpp" +#include <boost/signals2.hpp> #include <vector> class Dialog; class TabBar : public Control { public: + typedef boost::signals2::signal<void (int index)> TabSignal; + typedef std::vector<HWND> Page; struct Tab { const char *text; Page page; }; typedef std::vector<Tab> Tabs; @@ -38,6 +41,7 @@ public: void setFocus(); int count() const; void clear(); + void onTabChange(const TabSignal::slot_type &slot) { m_onTabChange.connect(slot); } protected: void onNotify(LPNMHDR, LPARAM) override; @@ -48,6 +52,7 @@ private: Dialog *m_parent; int m_lastPage; std::vector<Page> m_pages; + TabSignal m_onTabChange; }; #endif diff --git a/src/task.cpp b/src/task.cpp @@ -125,13 +125,10 @@ void InstallTask::commit() tx()->registerFile({false, m_oldEntry, file}); } - tx()->receipt()->addInstall({m_version, m_oldEntry}); + tx()->receipt()->addInstall(m_version, m_oldEntry); const Registry::Entry newEntry = tx()->registry()->push(m_version); - if(newEntry.type == Package::ExtensionType) - tx()->receipt()->setRestartNeeded(true); - if(m_pin) tx()->registry()->setPinned(newEntry, true); diff --git a/src/time.cpp b/src/time.cpp @@ -68,3 +68,9 @@ int Time::compare(const Time &o) const return 0; } + +ostream &operator<<(ostream &os, const Time &time) +{ + os << time.toString(); + return os; +} diff --git a/src/time.hpp b/src/time.hpp @@ -51,4 +51,6 @@ private: std::tm m_tm; }; +std::ostream &operator<<(std::ostream &os, const Time &time); + #endif diff --git a/src/version.cpp b/src/version.cpp @@ -73,6 +73,24 @@ bool Version::addSource(const Source *source) return true; } +ostream &operator<<(ostream &os, const Version &ver) +{ + os << 'v' << ver.name().toString(); + + if(!ver.author().empty()) + os << " by " << ver.author(); + + if(ver.time()) + os << " – " << ver.time(); + + os << "\r\n"; + + const string &changelog = ver.changelog(); + os << String::indent(changelog.empty() ? "No changelog" : changelog); + + return os; +} + VersionName::VersionName() : m_stable(true) {} diff --git a/src/version.hpp b/src/version.hpp @@ -99,4 +99,6 @@ private: std::set<Path> m_files; }; +std::ostream &operator<<(std::ostream &, const Version &); + #endif diff --git a/test/helper.cpp b/test/helper.cpp @@ -6,12 +6,6 @@ using namespace std; -ostream &operator<<(ostream &os, const Path &path) -{ - os << '"' << path.join() << '"'; - return os; -} - ostream &operator<<(ostream &os, const set<Path> &list) { os << '{'; @@ -23,15 +17,3 @@ ostream &operator<<(ostream &os, const set<Path> &list) return os; } - -ostream &operator<<(ostream &os, const Time &time) -{ - os << time.toString(); - return os; -} - -ostream &operator<<(ostream &os, const Version &ver) -{ - os << ver.name().toString(); - return os; -} diff --git a/test/helper.hpp b/test/helper.hpp @@ -3,12 +3,8 @@ class Path; class Time; -class Version; -std::ostream &operator<<(std::ostream &, const Path &); std::ostream &operator<<(std::ostream &, const std::set<Path> &); -std::ostream &operator<<(std::ostream &, const Time &); -std::ostream &operator<<(std::ostream &, const Version &); // include Catch only after having declared our ostream overloads #include <catch.hpp> diff --git a/test/ostream.cpp b/test/ostream.cpp @@ -1,59 +0,0 @@ -#include "helper.hpp" - -#include <ostream.hpp> - -#include <version.hpp> - -static const char *M = "[ostream]"; - -TEST_CASE("test number formatting", M) { - OutputStream stream; - stream << 1234; - REQUIRE(stream.str() == "1,234"); -} - -TEST_CASE("test indent string", M) { - OutputStream stream; - - SECTION("simple") - stream.indented("line1\nline2"); - - SECTION("already indented") - stream.indented(" line1\n line2"); - - REQUIRE(stream.str() == " line1\r\n line2\r\n"); -} - -TEST_CASE("output version", M) { - OutputStream stream; - Version ver("1.2.3", nullptr); - - SECTION("empty version") { - stream << ver; - REQUIRE(stream.str() == "v1.2.3\r\n No changelog\r\n"); - } - - SECTION("with author") { - ver.setAuthor("Hello World"); - stream << ver; - REQUIRE(stream.str() == "v1.2.3 by Hello World\r\n No changelog\r\n"); - } - - SECTION("with time") { - ver.setTime("2016-01-02T00:42:11Z"); - stream << ver; - REQUIRE(stream.str() == "v1.2.3 – January 02 2016\r\n No changelog\r\n"); - } - - SECTION("with changelog") { - ver.setChangelog("+ added super cool feature\n+ fixed all the bugs!"); - stream << ver; - REQUIRE(stream.str() == "v1.2.3\r\n + added super cool feature\r\n + fixed all the bugs!\r\n"); - } - - SECTION("changelog with empty lines") { - ver.setChangelog("line1\n\nline2"); - stream << ver; // no crash! - REQUIRE(stream.str() == "v1.2.3\r\n line1\r\n\r\n line2\r\n"); - } -} diff --git a/test/receipt.cpp b/test/receipt.cpp @@ -4,31 +4,23 @@ #include <index.hpp> +using Catch::Matchers::Contains; + using namespace std; static constexpr const char *M = "[receipt]"; -#define MAKE_VERSION \ - IndexPtr ri = make_shared<Index>("Index Name"); \ - Category cat("Category Name", ri.get()); \ - Package pkg(Package::ScriptType, "Package Name", &cat); \ - Version ver("1.0", &pkg); - -TEST_CASE("set isRestartNeeded", M) { - Receipt r; - REQUIRE_FALSE(r.isRestartNeeded()); - r.setRestartNeeded(true); - REQUIRE(r.isRestartNeeded()); -} - TEST_CASE("non-empty receipt", M) { - MAKE_VERSION; - Receipt r; REQUIRE(r.empty()); - SECTION("install") - r.addInstall({&ver, {}}); + SECTION("install") { + IndexPtr ri = make_shared<Index>("Index Name"); + Category cat("Category Name", ri.get()); + Package pkg(Package::ScriptType, "Package Name", &cat); + Version ver("1.0", &pkg); + r.addInstall(&ver, {}); + } SECTION("removal") r.addRemoval(Path("hello/world")); @@ -42,35 +34,99 @@ TEST_CASE("non-empty receipt", M) { REQUIRE_FALSE(r.empty()); } -TEST_CASE("install scratch or downgrade", M) { - MAKE_VERSION; +TEST_CASE("set RestartNeeded flag", M) { + IndexPtr ri = make_shared<Index>("Index Name"); + Category cat("Category Name", ri.get()); + Package script(Package::ScriptType, "Package Name", &cat); + Package ext(Package::ExtensionType, "Package Name", &cat); + Version scriptVer("1.0", &script); + Version extVer("1.0", &ext); + Receipt r; + REQUIRE_FALSE(r.test(Receipt::RestartNeeded)); - REQUIRE(r.installs().empty()); + r.addInstall(&scriptVer, {}); + REQUIRE_FALSE(r.test(Receipt::RestartNeeded)); - SECTION("install from stratch") - r.addInstall({&ver, {}}); + r.addInstall(&extVer, {}); + REQUIRE(r.test(Receipt::RestartNeeded)); +} - SECTION("downgrade") { - Registry::Entry entry{1}; - entry.version = VersionName("2.0"); - r.addInstall({&ver, entry}); +TEST_CASE("format receipt page title", M) { + SECTION("singular") { + ReceiptPage page{vector<int>{1}, "Singular", "Plural"}; + REQUIRE(page.title() == "Singular (1)"); + } + + SECTION("plural") { + ReceiptPage page{vector<int>{1, 2, 3}, "Singular", "Plural"}; + REQUIRE(page.title() == "Plural (3)"); + } + + SECTION("zero is plural") { + ReceiptPage page{vector<int>{}, "Singular", "Plural"}; + REQUIRE(page.title() == "Plural (0)"); + } + + SECTION("no plural") { + ReceiptPage page{vector<int>{1, 2, 3}, "Fallback"}; + REQUIRE(page.title() == "Fallback (3)"); } - REQUIRE(r.installs().size() == 1); - REQUIRE(r.updates().empty()); + SECTION("thousand divider") { + ReceiptPage page{vector<int>(42'000, 42), "Singular", "Plural"}; + REQUIRE(page.title() == "Plural (42,000)"); + } } -TEST_CASE("detect update tickets", M) { - MAKE_VERSION; +TEST_CASE("format install ticket") { + IndexPtr ri = make_shared<Index>("Index Name"); + Category cat("Category Name", ri.get()); + Package pkg(Package::ScriptType, "Package Name", &cat); - Receipt r; - REQUIRE(r.updates().empty()); + Version *v1 = new Version("1.0", &pkg); + v1->addSource(new Source({}, "https://google.com", v1)); + pkg.addVersion(v1); + + Version *v2 = new Version("2.0", &pkg); + v2->addSource(new Source({}, "https://google.com", v2)); + pkg.addVersion(v2); + Version *v3 = new Version("3.0", &pkg); + v3->addSource(new Source({}, "https://google.com", v3)); + pkg.addVersion(v3); + + ostringstream stream; Registry::Entry entry{1}; - entry.version = VersionName("0.9"); - r.addInstall({&ver, entry}); - REQUIRE(r.updates().size() == 1); - REQUIRE(r.installs().empty()); + SECTION("contains fullname") { + stream << InstallTicket{v3, {}}; + REQUIRE_THAT(stream.str(), Contains(pkg.fullName())); + } + + SECTION("prepend newline if stream nonempty") { + stream << "something"; + stream << InstallTicket{v3, {}}; + REQUIRE_THAT(stream.str(), Contains("something\r\n")); + } + + SECTION("installed from scratch") { + stream << InstallTicket{v2, {}}; + REQUIRE_THAT(stream.str(), + !Contains("v1.0") && Contains("v2.0") && !Contains("v3.0")); + } + + SECTION("update") { + entry.version = VersionName("1.0"); + stream << InstallTicket{v3, entry}; + REQUIRE_THAT(stream.str(), + !Contains("v1.0") && Contains("v2.0") && Contains("v3.0")); + } + + SECTION("downgrade") { + entry.version = VersionName("3.0"); + stream << InstallTicket{v1, entry}; + REQUIRE_THAT(stream.str(), + Contains("v1.0") && !Contains("v2.0") && !Contains("v3.0")); + } } diff --git a/test/string.cpp b/test/string.cpp @@ -11,3 +11,15 @@ TEST_CASE("string format", M) { CHECK(formatted.size() == 17); REQUIRE(formatted == "100% Hello World!"); } + +TEST_CASE("indent string", M) { + string actual; + + SECTION("simple") + actual = String::indent("line1\nline2"); + + SECTION("already indented") + actual = String::indent(" line1\n line2"); + + REQUIRE(actual == " line1\r\n line2"); +} diff --git a/test/version.cpp b/test/version.cpp @@ -292,3 +292,37 @@ TEST_CASE("version date", M) { ver.setTime("hello world"); REQUIRE(ver.time().year() == 2016); } + +TEST_CASE("output version", M) { + ostringstream stream; + Version ver("1.2.3", nullptr); + + SECTION("empty version") { + stream << ver; + REQUIRE(stream.str() == "v1.2.3\r\n No changelog"); + } + + SECTION("with author") { + ver.setAuthor("Hello World"); + stream << ver; + REQUIRE(stream.str() == "v1.2.3 by Hello World\r\n No changelog"); + } + + SECTION("with time") { + ver.setTime("2016-01-02T00:42:11Z"); + stream << ver; + REQUIRE(stream.str() == "v1.2.3 – January 02 2016\r\n No changelog"); + } + + SECTION("with changelog") { + ver.setChangelog("+ added super cool feature\n+ fixed all the bugs!"); + stream << ver; + REQUIRE(stream.str() == "v1.2.3\r\n + added super cool feature\r\n + fixed all the bugs!"); + } + + SECTION("changelog with empty lines") { + ver.setChangelog("line1\n\nline2"); + stream << ver; // no crash! + REQUIRE(stream.str() == "v1.2.3\r\n line1\r\n\r\n line2"); + } +}