Mercurial > code
changeset 526:b26d8be94adb
Xdg: resurrection
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 01 Jun 2016 17:14:58 +0200 |
parents | 17a733c5661a |
children | 8f8c32f102f1 |
files | CMakeLists.txt modules/xdg/CMakeLists.txt modules/xdg/doc/mainpage.cpp modules/xdg/test/main.cpp modules/xdg/xdg.hpp |
diffstat | 5 files changed, 498 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Jun 01 17:13:26 2016 +0200 +++ b/CMakeLists.txt Wed Jun 01 17:14:58 2016 +0200 @@ -52,3 +52,4 @@ add_subdirectory(modules/net) add_subdirectory(modules/options) add_subdirectory(modules/unicode) +add_subdirectory(modules/xdg)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/xdg/CMakeLists.txt Wed Jun 01 17:14:58 2016 +0200 @@ -0,0 +1,24 @@ +# +# CMakeLists.txt -- code building for common code +# +# Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +if (NOT WIN32) + code_define_module( + NAME xdg + SOURCES xdg.hpp + ) +endif () \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/xdg/doc/mainpage.cpp Wed Jun 01 17:14:58 2016 +0200 @@ -0,0 +1,13 @@ +/** + * \mainpage + * + * Welcome to the xdg library. + * + * ## Introduction + * + * Extract freedesktop's XDG directory specifications. + * + * ## Installation + * + * Just copy the file xdg.hpp and it them to your project. + */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/xdg/test/main.cpp Wed Jun 01 17:14:58 2016 +0200 @@ -0,0 +1,274 @@ +/* + * main.cpp -- main test file for XDG + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <gtest/gtest.h> + +#include <xdg.hpp> + +using namespace testing; + +namespace { + +std::string myhome; + +} + +#if defined(_WIN32) + +inline bool unsetenv(std::string name) +{ + name += "="; + + _putenv(name.c_str()); + + return true; +} + +inline bool setenv(const std::string &name, const std::string &value, bool) +{ + std::string t = name + "=" + value; + + _putenv(t.c_str()); + + return true; +} + +#endif + +TEST(HomeEmpty, config) +{ + ASSERT_TRUE(unsetenv("XDG_CONFIG_HOME") == 0); + + Xdg xdg; + + ASSERT_EQ(myhome + "/.config", xdg.configHome()); +} + +TEST(HomeEmpty, data) +{ + ASSERT_TRUE(unsetenv("XDG_DATA_HOME") == 0); + + Xdg xdg; + + ASSERT_EQ(myhome + "/.local/share", xdg.dataHome()); +} + +TEST(HomeEmpty, cache) +{ + ASSERT_TRUE(unsetenv("XDG_CACHE_HOME") == 0); + + Xdg xdg; + + ASSERT_EQ(myhome + "/.cache", xdg.cacheHome()); +} + +TEST(HomeEmpty, runtime) +{ + ASSERT_TRUE(unsetenv("XDG_RUNTIME_DIR") == 0); + + Xdg xdg; + + ASSERT_TRUE(xdg.runtimeDir().empty()); +} + +TEST(HomeValid, config) +{ + ASSERT_TRUE(setenv("XDG_CONFIG_HOME", "/test/config", true) == 0); + + Xdg xdg; + + ASSERT_EQ("/test/config", xdg.configHome()); +} + +TEST(HomeValid, data) +{ + ASSERT_TRUE(setenv("XDG_DATA_HOME", "/test/data", true) == 0); + + Xdg xdg; + + ASSERT_EQ("/test/data", xdg.dataHome()); +} + +TEST(HomeValid, cache) +{ + ASSERT_TRUE(setenv("XDG_CACHE_HOME", "/test/cache", true) == 0); + + Xdg xdg; + + ASSERT_EQ("/test/cache", xdg.cacheHome()); +} + +TEST(HomeValid, runtime) +{ + ASSERT_TRUE(setenv("XDG_RUNTIME_DIR", "/test/runtime", true) == 0); + + Xdg xdg; + + ASSERT_EQ("/test/runtime", xdg.runtimeDir()); +} + +TEST(HomeInvalid, config) +{ + ASSERT_TRUE(setenv("XDG_CONFIG_HOME", "invalid", true) == 0); + + Xdg xdg; + + ASSERT_EQ(myhome + "/.config", xdg.configHome()); +} + +TEST(HomeInvalid, data) +{ + ASSERT_TRUE(setenv("XDG_DATA_HOME", "invalid", true) == 0); + + Xdg xdg; + + ASSERT_EQ(myhome + "/.local/share", xdg.dataHome()); +} + +TEST(HomeInvalid, cache) +{ + ASSERT_TRUE(setenv("XDG_CACHE_HOME", "invalid", true) == 0); + + Xdg xdg; + + ASSERT_EQ(myhome + "/.cache", xdg.cacheHome()); +} + +TEST(HomeInvalid, runtime) +{ + ASSERT_TRUE(setenv("XDG_RUNTIME_DIR", "invalid", true) == 0); + + Xdg xdg; + + ASSERT_TRUE(xdg.runtimeDir().empty()); +} + +TEST(DirectoriesEmpty, config) +{ + ASSERT_TRUE(unsetenv("XDG_CONFIG_DIRS") == 0); + + Xdg xdg; + + const auto &list = xdg.configDirs(); + + ASSERT_EQ((size_t)1, list.size()); + ASSERT_EQ("/etc/xdg", list[0]); +} + +TEST(DirectoriesEmpty, data) +{ + ASSERT_TRUE(unsetenv("XDG_DATA_DIRS") == 0); + + Xdg xdg; + + const auto &list = xdg.dataDirs(); + + ASSERT_EQ((size_t)2, list.size()); + ASSERT_EQ("/usr/local/share", list[0]); + ASSERT_EQ("/usr/share", list[1]); +} + +TEST(DirectoriesValid, config) +{ + ASSERT_TRUE(setenv("XDG_CONFIG_DIRS", "/config1:/config2", true) == 0); + + Xdg xdg; + + const auto &list = xdg.configDirs(); + + ASSERT_EQ((size_t)2, list.size()); + ASSERT_EQ("/config1", list[0]); + ASSERT_EQ("/config2", list[1]); +} + +TEST(DirectoriesValid, data) +{ + ASSERT_TRUE(setenv("XDG_DATA_DIRS", "/data1:/data2", true) == 0); + + Xdg xdg; + + const auto &list = xdg.dataDirs(); + + ASSERT_EQ((size_t)2, list.size()); + ASSERT_EQ("/data1", list[0]); + ASSERT_EQ("/data2", list[1]); +} + +TEST(DirectoriesInvalid, config) +{ + ASSERT_TRUE(setenv("XDG_CONFIG_DIRS", "bad1:bad2", true) == 0); + + Xdg xdg; + + const auto &list = xdg.configDirs(); + + ASSERT_EQ((size_t)1, list.size()); + ASSERT_EQ("/etc/xdg", list[0]); +} + +TEST(DirectoriesInvalid, data) +{ + ASSERT_TRUE(setenv("XDG_DATA_DIRS", "bad1:bad2", true) == 0); + + Xdg xdg; + + const auto &list = xdg.dataDirs(); + + ASSERT_EQ((size_t)2, list.size()); + ASSERT_EQ("/usr/local/share", list[0]); + ASSERT_EQ("/usr/share", list[1]); +} + +TEST(DirectoriesMixed, config) +{ + ASSERT_TRUE(setenv("XDG_CONFIG_DIRS", "/config1:bad:/config2", true) == 0); + + Xdg xdg; + + const auto &list = xdg.configDirs(); + + ASSERT_EQ((size_t)2, list.size()); + ASSERT_EQ("/config1", list[0]); + ASSERT_EQ("/config2", list[1]); +} + +TEST(DirectoriesMixed, data) +{ + ASSERT_TRUE(setenv("XDG_DATA_DIRS", "/data1:bad:/data2", true) == 0); + + Xdg xdg; + + const auto &list = xdg.dataDirs(); + + ASSERT_EQ((size_t)2, list.size()); + ASSERT_EQ("/data1", list[0]); + ASSERT_EQ("/data2", list[1]); +} + +int main(int argc, char **argv) +{ + auto home = getenv("HOME"); + + if (home == nullptr) + return 0; + + myhome = home; + InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/xdg/xdg.hpp Wed Jun 01 17:14:58 2016 +0200 @@ -0,0 +1,186 @@ +/* + * xdg.hpp -- XDG directory specifications + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef XDG_HPP +#define XDG_HPP + +/** + * \file xdg.hpp + * \brief XDG directory specifications. + * \author David Demelier <markand@malikana.fr> + */ + +#include <cstdlib> +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +/** + * \class Xdg + * \brief XDG directory specifications. + * + * Read and get XDG directories. + * + * This file should compiles on Windows to facilitate portability but its functions must not be used. + */ +class Xdg { +private: + std::string m_configHome; + std::string m_dataHome; + std::string m_cacheHome; + std::string m_runtimeDir; + std::vector<std::string> m_configDirs; + std::vector<std::string> m_dataDirs; + + bool isabsolute(const std::string &path) const noexcept + { + return path.length() > 0 && path[0] == '/'; + } + + std::vector<std::string> split(const std::string &arg) const + { + std::stringstream iss(arg); + std::string item; + std::vector<std::string> elems; + + while (std::getline(iss, item, ':')) + if (isabsolute(item)) + elems.push_back(item); + + return elems; + } + + std::string envOrHome(const std::string &var, const std::string &repl) const + { + auto value = std::getenv(var.c_str()); + + if (value == nullptr || !isabsolute(value)) { + auto home = std::getenv("HOME"); + + if (home == nullptr) + throw std::runtime_error("could not get home directory"); + + return std::string(home) + "/" + repl; + } + + return value; + } + + std::vector<std::string> listOrDefaults(const std::string &var, const std::vector<std::string> &list) const + { + auto value = std::getenv(var.c_str()); + + if (!value) + return list; + + // No valid item at all? Use defaults. + auto result = split(value); + + return (result.size() == 0) ? list : result; + } + +public: + /** + * Open an xdg instance and load directories. + * + * \throw std::runtime_error on failures + */ + Xdg() + { + m_configHome = envOrHome("XDG_CONFIG_HOME", ".config"); + m_dataHome = envOrHome("XDG_DATA_HOME", ".local/share"); + m_cacheHome = envOrHome("XDG_CACHE_HOME", ".cache"); + + m_configDirs = listOrDefaults("XDG_CONFIG_DIRS", { "/etc/xdg" }); + m_dataDirs = listOrDefaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" }); + + /* + * Runtime directory is a special case and does not have a replacement, the application should manage + * this by itself. + */ + auto runtime = std::getenv("XDG_RUNTIME_DIR"); + if (runtime && isabsolute(runtime)) + m_runtimeDir = runtime; + } + + /** + * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config + * + * \return the config directory + */ + inline const std::string &configHome() const noexcept + { + return m_configHome; + } + + /** + * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share + * + * \return the data directory + */ + inline const std::string &dataHome() const noexcept + { + return m_dataHome; + } + + /** + * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache + * + * \return the cache directory + */ + inline const std::string &cacheHome() const noexcept + { + return m_cacheHome; + } + + /** + * Get the runtime directory. + * + * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty valus is returned and the user is + * responsible of using something else. + * + * \return the runtime directory + */ + inline const std::string &runtimeDir() const noexcept + { + return m_runtimeDir; + } + + /** + * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" } + * + * \return the list of config directories + */ + inline const std::vector<std::string> &configDirs() const noexcept + { + return m_configDirs; + } + + /** + * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share", "/usr/share" } + * + * \return the list of data directories + */ + inline const std::vector<std::string> &dataDirs() const noexcept + { + return m_dataDirs; + } +}; + +#endif // !XDG_HPP