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