diff --git a/src/core/desktopfileentry.cpp b/src/core/desktopfileentry.cpp index 43365c7..5c1bef7 100644 --- a/src/core/desktopfileentry.cpp +++ b/src/core/desktopfileentry.cpp @@ -7,116 +7,124 @@ #include "desktopfileentry.h" using boost::lexical_cast; -using namespace linuxdeploy::core::log; -class DesktopFileEntry::PrivateData { -public: - std::string key; - std::string value; +namespace linuxdeploy { + namespace core { + using namespace log; + + namespace desktopfile { + class DesktopFileEntry::PrivateData { + public: + std::string key; + std::string value; -public: - void copyData(const std::shared_ptr& other) { - key = other->key; - value = other->value; - } + public: + void copyData(const std::shared_ptr& other) { + key = other->key; + value = other->value; + } - void assertValueNotEmpty() { - if (value.empty()) - throw std::invalid_argument("value is empty"); + void assertValueNotEmpty() { + if (value.empty()) + throw std::invalid_argument("value is empty"); + } + }; + + DesktopFileEntry::DesktopFileEntry() : d(new PrivateData) {} + + DesktopFileEntry::DesktopFileEntry(std::string key, std::string value) : DesktopFileEntry() { + d->key = std::move(key); + d->value = std::move(value); + } + + DesktopFileEntry::DesktopFileEntry(const DesktopFileEntry& other) : DesktopFileEntry() { + d->copyData(other.d); + } + + DesktopFileEntry& DesktopFileEntry::operator=(const DesktopFileEntry& other) { + if (this != &other) { + d.reset(new PrivateData); + d->copyData(other.d); + } + + return *this; + } + + DesktopFileEntry& DesktopFileEntry::operator=(DesktopFileEntry&& other) noexcept { + if (this != &other) { + d = other.d; + other.d = nullptr; + } + + return *this; + } + + bool DesktopFileEntry::operator==(const DesktopFileEntry& other) const { + return d->key == other.d->key && d->value == other.d->value; + } + + bool DesktopFileEntry::operator!=(const DesktopFileEntry& other) const { + return !operator==(other); + } + + bool DesktopFileEntry::isEmpty() const { + return d->key.empty(); + } + + const std::string& DesktopFileEntry::key() const { + return d->key; + } + + const std::string& DesktopFileEntry::value() const { + return d->value; + } + + int DesktopFileEntry::asInt() const { + d->assertValueNotEmpty(); + + return lexical_cast(value()); + } + + long DesktopFileEntry::asLong() const { + d->assertValueNotEmpty(); + + return lexical_cast(value()); + } + + double DesktopFileEntry::asDouble() const { + d->assertValueNotEmpty(); + + return lexical_cast(value()); + } + + std::vector DesktopFileEntry::parseStringList() const { + const auto& value = this->value(); + + if (value.empty()) + return {}; + + if (value.back() != ';') + ldLog() << LD_DEBUG << "desktop file string list does not end with semicolon:" << value + << std::endl; + + std::vector list; + + std::stringstream ss(value); + + std::string currentVal; + while (std::getline(ss, currentVal, ';')) { + // the last value will be empty, as in desktop files, lists shall end with a semicolon + // therefore we skip all empty values (assuming that empty values in lists in desktop files don't make sense anyway) + if (!currentVal.empty()) + list.emplace_back(currentVal); + } + + return list; + } + + DesktopFileEntry::operator std::string() const { + return value(); + } + } } }; - -DesktopFileEntry::DesktopFileEntry() : d(new PrivateData) {} - -DesktopFileEntry::DesktopFileEntry(std::string key, std::string value) : DesktopFileEntry() { - d->key = std::move(key); - d->value = std::move(value); -} - -DesktopFileEntry::DesktopFileEntry(const DesktopFileEntry& other) : DesktopFileEntry() { - d->copyData(other.d); -} - -DesktopFileEntry& DesktopFileEntry::operator=(const DesktopFileEntry& other) { - if (this != &other) { - d.reset(new PrivateData); - d->copyData(other.d); - } - - return *this; -} - -DesktopFileEntry& DesktopFileEntry::operator=(DesktopFileEntry&& other) noexcept { - if (this != &other) { - d = other.d; - other.d = nullptr; - } - - return *this; -} - -bool DesktopFileEntry::operator==(const DesktopFileEntry& other) const { - return d->key == other.d->key && d->value == other.d->value; -} - -bool DesktopFileEntry::operator!=(const DesktopFileEntry& other) const { - return !operator==(other); -} - -bool DesktopFileEntry::isEmpty() const { - return d->key.empty(); -} - -const std::string& DesktopFileEntry::key() const { - return d->key; -} - -const std::string& DesktopFileEntry::value() const { - return d->value; -} - -int DesktopFileEntry::asInt() const { - d->assertValueNotEmpty(); - - return lexical_cast(value()); -} - -long DesktopFileEntry::asLong() const { - d->assertValueNotEmpty(); - - return lexical_cast(value()); -} - -double DesktopFileEntry::asDouble() const { - d->assertValueNotEmpty(); - - return lexical_cast(value()); -} - -std::vector DesktopFileEntry::parseStringList() const { - const auto& value = this->value(); - - if (value.empty()) - return {}; - - if (value.back() != ';') - ldLog() << LD_DEBUG << "desktop file string list does not end with semicolon:" << value << std::endl; - - std::vector list; - - std::stringstream ss(value); - - std::string currentVal; - while (std::getline(ss, currentVal, ';')) { - // the last value will be empty, as in desktop files, lists shall end with a semicolon - // therefore we skip all empty values (assuming that empty values in lists in desktop files don't make sense anyway) - if (!currentVal.empty()) - list.emplace_back(currentVal); - } - - return list; -} - -DesktopFileEntry::operator std::string() const { - return value(); -} diff --git a/src/core/desktopfileentry.h b/src/core/desktopfileentry.h index da6b9b1..555f2b4 100644 --- a/src/core/desktopfileentry.h +++ b/src/core/desktopfileentry.h @@ -7,62 +7,69 @@ // library headers #include -class DesktopFileEntry { -private: - // opaque data class pattern - class PrivateData; - std::shared_ptr d; +namespace linuxdeploy { + namespace core { + namespace desktopfile { + class DesktopFileEntry { + private: + // opaque data class pattern + class PrivateData; -public: - // default constructor - DesktopFileEntry(); + std::shared_ptr d; - // construct from key and value - explicit DesktopFileEntry(std::string key, std::string value); + public: + // default constructor + DesktopFileEntry(); - // copy constructor - DesktopFileEntry(const DesktopFileEntry& other); + // construct from key and value + explicit DesktopFileEntry(std::string key, std::string value); - // copy assignment constructor - DesktopFileEntry& operator=(const DesktopFileEntry& other); + // copy constructor + DesktopFileEntry(const DesktopFileEntry& other); - // move assignment operator - DesktopFileEntry& operator=(DesktopFileEntry&& other) noexcept; + // copy assignment constructor + DesktopFileEntry& operator=(const DesktopFileEntry& other); - // equality operator - bool operator==(const DesktopFileEntry& other) const; + // move assignment operator + DesktopFileEntry& operator=(DesktopFileEntry&& other) noexcept; - // inequality operator - bool operator!=(const DesktopFileEntry& other) const; + // equality operator + bool operator==(const DesktopFileEntry& other) const; -public: - // checks whether a key and value have been set - bool isEmpty() const; + // inequality operator + bool operator!=(const DesktopFileEntry& other) const; - // return entry's key - const std::string& key() const; + public: + // checks whether a key and value have been set + bool isEmpty() const; - // return entry's value - const std::string& value() const; + // return entry's key + const std::string& key() const; -public: - // allow conversion of entry to string - // returns value - explicit operator std::string() const; + // return entry's value + const std::string& value() const; - // convert value to integer - // throws boost::bad_lexical_cast in case of type errors - int asInt() const; + public: + // allow conversion of entry to string + // returns value + explicit operator std::string() const; - // convert value to long - // throws boost::bad_lexical_cast in case of type errors - long asLong() const; + // convert value to integer + // throws boost::bad_lexical_cast in case of type errors + int asInt() const; - // convert value to double - // throws boost::bad_lexical_cast in case of type errors - double asDouble() const; + // convert value to long + // throws boost::bad_lexical_cast in case of type errors + long asLong() const; - // split CSV list value into vector - // the separator used to split the string is a semicolon as per desktop file spec - std::vector parseStringList() const; -}; + // convert value to double + // throws boost::bad_lexical_cast in case of type errors + double asDouble() const; + + // split CSV list value into vector + // the separator used to split the string is a semicolon as per desktop file spec + std::vector parseStringList() const; + }; + } + } +} diff --git a/src/core/desktopfilereader.cpp b/src/core/desktopfilereader.cpp index 25eed6a..2e5d00a 100644 --- a/src/core/desktopfilereader.cpp +++ b/src/core/desktopfilereader.cpp @@ -11,151 +11,157 @@ namespace bf = boost::filesystem; -// describes a single section -typedef std::unordered_map section_t; +namespace linuxdeploy { + namespace core { + namespace desktopfile { + // describes a single section + typedef std::unordered_map section_t; -// describes all sections in the desktop file -typedef std::unordered_map sections_t; + // describes all sections in the desktop file + typedef std::unordered_map sections_t; -class DesktopFileReader::PrivateData { -public: - bf::path path; - sections_t sections; + class DesktopFileReader::PrivateData { + public: + bf::path path; + sections_t sections; -public: - bool isEmpty() { - return sections.empty(); - } - - void assertPathIsNotEmptyAndFileExists() { - if (path.empty()) - throw std::invalid_argument("empty path is not permitted"); - } - - void copyData(const std::shared_ptr& other) { - path = other->path; - } - - void parse(std::istream& file) { - std::string line; - bool first = true; - - std::string currentSectionName; - - while (std::getline(file, line)) { - if (first) { - first = false; - // said to allow handling of UTF-16/32 documents, not entirely sure why - if (line[0] == static_cast(0xEF)) - { - line.erase(0, 3); - return; + public: + bool isEmpty() { + return sections.empty(); } - } - if (!line.empty()) { - auto len = line.length(); - if (len > 0 && !((len >= 2 && (line[0] == '/' && line[1] == '/')) || (len >= 1 && line[0] == '#'))) { - if (line[0] == '[') { - // this line apparently introduces a new section - size_t length = len - 2; - auto title = line.substr(1, line.find(']') - 1); + void assertPathIsNotEmptyAndFileExists() { + if (path.empty()) + throw std::invalid_argument("empty path is not permitted"); + } - // set up the new section - sections.insert(std::make_pair(title, section_t())); - currentSectionName = std::move(title); - } else { - // we require at least one section to be present in the desktop file - if (currentSectionName.empty()) - throw std::invalid_argument("No section in desktop file"); + void copyData(const std::shared_ptr& other) { + path = other->path; + } - // this line should be a normal key-value pair - std::string key = line.substr(0, line.find('=')); - std::string value = line.substr(line.find('=') + 1, line.size()); + void parse(std::istream& file) { + std::string line; + bool first = true; - // we can strip away any sort of leading or trailing whitespace safely - linuxdeploy::util::trim(key); - linuxdeploy::util::trim(value); + std::string currentSectionName; - // empty keys are not allowed for obvious reasons - if (key.empty()) - throw std::invalid_argument("Empty keys are not allowed"); + while (std::getline(file, line)) { + if (first) { + first = false; + // said to allow handling of UTF-16/32 documents, not entirely sure why + if (line[0] == static_cast(0xEF)) { + line.erase(0, 3); + return; + } + } - // who are we to judge whether empty values are an issue - // that'd require checking the key names and implementing checks per key according to the - // freedesktop.org spec - sections[currentSectionName][key] = DesktopFileEntry(key, value); + if (!line.empty()) { + auto len = line.length(); + if (len > 0 && + !((len >= 2 && (line[0] == '/' && line[1] == '/')) || (len >= 1 && line[0] == '#'))) { + if (line[0] == '[') { + // this line apparently introduces a new section + size_t length = len - 2; + auto title = line.substr(1, line.find(']') - 1); + + // set up the new section + sections.insert(std::make_pair(title, section_t())); + currentSectionName = std::move(title); + } else { + // we require at least one section to be present in the desktop file + if (currentSectionName.empty()) + throw std::invalid_argument("No section in desktop file"); + + // this line should be a normal key-value pair + std::string key = line.substr(0, line.find('=')); + std::string value = line.substr(line.find('=') + 1, line.size()); + + // we can strip away any sort of leading or trailing whitespace safely + linuxdeploy::util::trim(key); + linuxdeploy::util::trim(value); + + // empty keys are not allowed for obvious reasons + if (key.empty()) + throw std::invalid_argument("Empty keys are not allowed"); + + // who are we to judge whether empty values are an issue + // that'd require checking the key names and implementing checks per key according to the + // freedesktop.org spec + sections[currentSectionName][key] = DesktopFileEntry(key, value); + } + } + } } } + }; + + DesktopFileReader::DesktopFileReader() : d(new PrivateData) {} + + DesktopFileReader::DesktopFileReader(boost::filesystem::path path) : DesktopFileReader() { + d->path = std::move(path); + d->assertPathIsNotEmptyAndFileExists(); + + std::ifstream ifs(d->path.string()); + if (!ifs) + throw std::invalid_argument("could not open file: " + d->path.string()); + + d->parse(ifs); + } + + DesktopFileReader::DesktopFileReader(std::istream& is) : DesktopFileReader() { + d->parse(is); + } + + DesktopFileReader::DesktopFileReader(const DesktopFileReader& other) : DesktopFileReader() { + d->copyData(other.d); + } + + DesktopFileReader& DesktopFileReader::operator=(const DesktopFileReader& other) { + if (this != &other) { + // set up a new instance of PrivateData, and copy data over from other object + d.reset(new PrivateData); + d->copyData(other.d); + } + + return *this; + } + + DesktopFileReader& DesktopFileReader::operator=(DesktopFileReader&& other) noexcept { + if (this != &other) { + // move other object's data into this one, and remove reference there + d = other.d; + other.d = nullptr; + } + + return *this; + } + + bool DesktopFileReader::isEmpty() const { + return d->isEmpty(); + } + + bool DesktopFileReader::operator==(const DesktopFileReader& other) const { + return d->path == other.d->path; + } + + bool DesktopFileReader::operator!=(const DesktopFileReader& other) const { + return !operator==(other); + } + + boost::filesystem::path DesktopFileReader::path() const { + return d->path; + } + + section_t DesktopFileReader::operator[](const std::string& name) { + auto it = d->sections.find(name); + + // the map would lazy-initialize a new entry in case the section doesn't exist + // therefore explicitly checking whether the section exists, throwing an exception in case it does not + if (it == d->sections.end()) + throw std::range_error("could not find section " + name); + + return it->second; } } } -}; - -DesktopFileReader::DesktopFileReader() : d(new PrivateData) {} - -DesktopFileReader::DesktopFileReader(boost::filesystem::path path) : DesktopFileReader() { - d->path = std::move(path); - d->assertPathIsNotEmptyAndFileExists(); - - std::ifstream ifs(d->path.string()); - if (!ifs) - throw std::invalid_argument("could not open file: " + d->path.string()); - - d->parse(ifs); -} - -DesktopFileReader::DesktopFileReader(std::istream& is) : DesktopFileReader() { - d->parse(is); -} - -DesktopFileReader::DesktopFileReader(const DesktopFileReader& other) : DesktopFileReader() { - d->copyData(other.d); -} - -DesktopFileReader& DesktopFileReader::operator=(const DesktopFileReader& other) { - if (this != &other) { - // set up a new instance of PrivateData, and copy data over from other object - d.reset(new PrivateData); - d->copyData(other.d); - } - - return *this; -} - -DesktopFileReader& DesktopFileReader::operator=(DesktopFileReader&& other) noexcept { - if (this != &other) { - // move other object's data into this one, and remove reference there - d = other.d; - other.d = nullptr; - } - - return *this; -} - -bool DesktopFileReader::isEmpty() const { - return d->isEmpty(); -} - -bool DesktopFileReader::operator==(const DesktopFileReader& other) const { - return d->path == other.d->path; -} - -bool DesktopFileReader::operator!=(const DesktopFileReader& other) const { - return !operator==(other); -} - -boost::filesystem::path DesktopFileReader::path() const { - return d->path; -} - -section_t DesktopFileReader::operator[](const std::string& name) { - auto it = d->sections.find(name); - - // the map would lazy-initialize a new entry in case the section doesn't exist - // therefore explicitly checking whether the section exists, throwing an exception in case it does not - if (it == d->sections.end()) - throw std::range_error("could not find section " + name); - - return it->second; } diff --git a/src/core/desktopfilereader.h b/src/core/desktopfilereader.h index a830f51..8857feb 100644 --- a/src/core/desktopfilereader.h +++ b/src/core/desktopfilereader.h @@ -11,45 +11,52 @@ // local includes #include "desktopfileentry.h" -class DesktopFileReader { -private: - // opaque data class pattern - class PrivateData; - std::shared_ptr d; +namespace linuxdeploy { + namespace core { + namespace desktopfile { + class DesktopFileReader { + private: + // opaque data class pattern + class PrivateData; -public: - // default constructor - DesktopFileReader(); + std::shared_ptr d; - // construct from path - explicit DesktopFileReader(boost::filesystem::path path); + public: + // default constructor + DesktopFileReader(); - // construct from existing istream - explicit DesktopFileReader(std::istream& is); + // construct from path + explicit DesktopFileReader(boost::filesystem::path path); - // copy constructor - DesktopFileReader(const DesktopFileReader& other); + // construct from existing istream + explicit DesktopFileReader(std::istream& is); - // copy assignment constructor - DesktopFileReader& operator=(const DesktopFileReader& other); + // copy constructor + DesktopFileReader(const DesktopFileReader& other); - // move assignment operator - DesktopFileReader& operator=(DesktopFileReader&& other) noexcept; + // copy assignment constructor + DesktopFileReader& operator=(const DesktopFileReader& other); - // equality operator - bool operator==(const DesktopFileReader& other) const; + // move assignment operator + DesktopFileReader& operator=(DesktopFileReader&& other) noexcept; - // inequality operator - bool operator!=(const DesktopFileReader& other) const; + // equality operator + bool operator==(const DesktopFileReader& other) const; -public: - // checks whether parsed data is available - bool isEmpty() const; + // inequality operator + bool operator!=(const DesktopFileReader& other) const; - // returns desktop file path - boost::filesystem::path path() const; + public: + // checks whether parsed data is available + bool isEmpty() const; - // get a specific section from the parsed data - // throws std::range_error if section does not exist - std::unordered_map operator[](const std::string& name); -}; + // returns desktop file path + boost::filesystem::path path() const; + + // get a specific section from the parsed data + // throws std::range_error if section does not exist + std::unordered_map operator[](const std::string& name); + }; + } + } +} diff --git a/tests/core/test_desktopfileentry.cpp b/tests/core/test_desktopfileentry.cpp index d05946d..0192664 100644 --- a/tests/core/test_desktopfileentry.cpp +++ b/tests/core/test_desktopfileentry.cpp @@ -7,6 +7,7 @@ #include "../../src/core/desktopfileentry.h" using boost::bad_lexical_cast; +using namespace linuxdeploy::core::desktopfile; namespace bf = boost::filesystem; diff --git a/tests/core/test_desktopfilereader.cpp b/tests/core/test_desktopfilereader.cpp index 229b24b..7ea2509 100644 --- a/tests/core/test_desktopfilereader.cpp +++ b/tests/core/test_desktopfilereader.cpp @@ -5,6 +5,7 @@ // local headers #include "../../src/core/desktopfilereader.h" +using namespace linuxdeploy::core::desktopfile; namespace bf = boost::filesystem; class DesktopFileReaderFixture : public ::testing::Test {