Refactoring louloulibs and cmake

Use OBJECT libraries
Remove the louloulibs directory
Write FOUND variables in the cache
This commit is contained in:
louiz’
2017-03-14 21:45:23 +01:00
parent 2d3806152e
commit 0ab40dc1ab
77 changed files with 219 additions and 280 deletions
+254
View File
@@ -0,0 +1,254 @@
#include <utils/encoding.hpp>
#include <utils/scopeguard.hpp>
#include <stdexcept>
#include <assert.h>
#include <string.h>
#include <iconv.h>
#include <cerrno>
#include <map>
#include <bitset>
/**
* The UTF-8-encoded character used as a place holder when a character conversion fails.
* This is U+FFFD � "replacement character"
*/
static const char* invalid_char = "\xef\xbf\xbd";
static const size_t invalid_char_len = 3;
namespace utils
{
/**
* Based on http://en.wikipedia.org/wiki/UTF-8#Description
*/
std::size_t get_next_codepoint_size(const unsigned char c)
{
if ((c & 0b11111000) == 0b11110000) // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
return 4;
else if ((c & 0b11110000) == 0b11100000) // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx
return 3;
else if ((c & 0b11100000) == 0b11000000) // 2 bytes: 110xxxxx 10xxxxxx
return 2;
return 1; // 1 byte: 0xxxxxxx
}
bool is_valid_utf8(const char* s)
{
if (!s)
return false;
const unsigned char* str = reinterpret_cast<const unsigned char*>(s);
while (*str)
{
const auto codepoint_size = get_next_codepoint_size(str[0]);
if (codepoint_size == 4)
{
if (!str[1] || !str[2] || !str[3]
|| ((str[1] & 0b11000000) != 0b10000000)
|| ((str[2] & 0b11000000) != 0b10000000)
|| ((str[3] & 0b11000000) != 0b10000000))
return false;
}
else if (codepoint_size == 3)
{
if (!str[1] || !str[2]
|| ((str[1] & 0b11000000) != 0b10000000)
|| ((str[2] & 0b11000000) != 0b10000000))
return false;
}
else if (codepoint_size == 2)
{
if (!str[1] ||
((str[1] & 0b11000000) != 0b10000000))
return false;
}
else if ((str[0] & 0b10000000) != 0)
return false;
str += codepoint_size;
}
return true;
}
std::string remove_invalid_xml_chars(const std::string& original)
{
// The given string MUST be a valid utf-8 string
std::vector<char> res(original.size(), '\0');
// pointer where we write valid chars
char* r = res.data();
const char* str = original.c_str();
std::bitset<20> codepoint;
while (*str)
{
// 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if ((str[0] & 0b11111000) == 0b11110000)
{
codepoint = ((str[0] & 0b00000111) << 18);
codepoint |= ((str[1] & 0b00111111) << 12);
codepoint |= ((str[2] & 0b00111111) << 6 );
codepoint |= ((str[3] & 0b00111111) << 0 );
if (codepoint.to_ulong() <= 0x10FFFF)
{
::memcpy(r, str, 4);
r += 4;
}
str += 4;
}
// 3 bytes: 1110xxx 10xxxxxx 10xxxxxx
else if ((str[0] & 0b11110000) == 0b11100000)
{
codepoint = ((str[0] & 0b00001111) << 12);
codepoint |= ((str[1] & 0b00111111) << 6);
codepoint |= ((str[2] & 0b00111111) << 0 );
if (codepoint.to_ulong() <= 0xD7FF ||
(codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD))
{
::memcpy(r, str, 3);
r += 3;
}
str += 3;
}
// 2 bytes: 110xxxxx 10xxxxxx
else if (((str[0]) & 0b11100000) == 0b11000000)
{
// All 2 bytes char are valid, don't even bother calculating
// the codepoint
::memcpy(r, str, 2);
r += 2;
str += 2;
}
// 1 byte: 0xxxxxxx
else if ((str[0] & 0b10000000) == 0)
{
codepoint = ((str[0] & 0b01111111));
if (codepoint.to_ulong() == 0x09 ||
codepoint.to_ulong() == 0x0A ||
codepoint.to_ulong() == 0x0D ||
codepoint.to_ulong() >= 0x20)
{
::memcpy(r, str, 1);
r += 1;
}
str += 1;
}
else
throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars");
}
return {res.data(), static_cast<size_t>(r - res.data())};
}
std::string convert_to_utf8(const std::string& str, const char* charset)
{
std::string res;
const iconv_t cd = iconv_open("UTF-8", charset);
if (cd == (iconv_t)-1)
throw std::runtime_error("Cannot convert into UTF-8");
// Make sure cd is always closed when we leave this function
const auto sg = utils::make_scope_guard([&cd](){ iconv_close(cd); });
size_t inbytesleft = str.size();
// iconv will not attempt to modify this buffer, but some plateform
// require a char** anyway
#ifdef ICONV_SECOND_ARGUMENT_IS_CONST
const char* inbuf_ptr = str.c_str();
#else
char* inbuf_ptr = const_cast<char*>(str.c_str());
#endif
size_t outbytesleft = str.size() * 4;
char* outbuf = new char[outbytesleft];
char* outbuf_ptr = outbuf;
// Make sure outbuf is always deleted when we leave this function
const auto sg2 = utils::make_scope_guard([outbuf](){ delete[] outbuf; });
bool done = false;
while (done == false)
{
size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft);
if ((size_t)-1 == error)
{
switch (errno)
{
case EILSEQ:
// Invalid byte found. Insert a placeholder instead of the
// converted character, jump one byte and continue
memcpy(outbuf_ptr, invalid_char, invalid_char_len);
outbuf_ptr += invalid_char_len;
inbytesleft--;
inbuf_ptr++;
break;
case EINVAL:
// A multibyte sequence is not terminated, but we can't
// provide any more data, so we just add a placeholder to
// indicate that the character is not properly converted,
// and we stop the conversion
memcpy(outbuf_ptr, invalid_char, invalid_char_len);
outbuf_ptr += invalid_char_len;
outbuf_ptr++;
done = true;
break;
case E2BIG: // This should never happen
default: // This should happen even neverer
done = true;
break;
}
}
else
{
// The conversion finished without any error, stop converting
done = true;
}
}
// Terminate the converted buffer, and copy that buffer it into the
// string we return
*outbuf_ptr = '\0';
res = outbuf;
return res;
}
}
namespace xep0106
{
static const std::map<const char, const std::string> encode_map = {
{' ', "\\20"},
{'"', "\\22"},
{'&', "\\26"},
{'\'',"\\27"},
{'/', "\\2f"},
{':', "\\3a"},
{'<', "\\3c"},
{'>', "\\3e"},
{'@', "\\40"},
};
void decode(std::string& s)
{
std::string::size_type pos;
for (const auto& pair: encode_map)
while ((pos = s.find(pair.second)) != std::string::npos)
s.replace(pos, pair.second.size(),
1, pair.first);
}
void encode(std::string& s)
{
std::string::size_type pos;
while ((pos = s.find_first_of(" \"&'/:<>@")) != std::string::npos)
{
auto it = encode_map.find(s[pos]);
assert(it != encode_map.end());
s.replace(pos, 1, it->second);
}
}
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <string>
namespace utils
{
/**
* Return the size, in bytes, of the next UTF-8 codepoint, based on
* the given char.
*/
std::size_t get_next_codepoint_size(const unsigned char c);
/**
* Returns true if the given null-terminated string is valid utf-8.
*
* Based on http://en.wikipedia.org/wiki/UTF-8#Description
*/
bool is_valid_utf8(const char* s);
/**
* Remove all invalid codepoints from the given utf-8-encoded string.
* The value returned is a copy of the string, without the removed chars.
*
* See http://www.w3.org/TR/xml/#charsets for the list of valid characters
* in XML.
*/
std::string remove_invalid_xml_chars(const std::string& original);
/**
* Convert the given string (encoded is "encoding") into valid utf-8.
* If some decoding fails, insert an utf-8 placeholder character instead.
*/
std::string convert_to_utf8(const std::string& str, const char* encoding);
}
namespace xep0106
{
/**
* Decode and encode inplace.
*/
void decode(std::string&);
void encode(std::string&);
}
+11
View File
@@ -0,0 +1,11 @@
#include <utils/get_first_non_empty.hpp>
bool is_empty(const std::string& val)
{
return val.empty();
}
bool is_empty(const int& val)
{
return val == 0;
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <string>
bool is_empty(const std::string& val);
bool is_empty(const int& val);
template <typename T>
T get_first_non_empty(T&& last)
{
return last;
}
template <typename T, typename... Args>
T get_first_non_empty(T&& first, Args&&... args)
{
if (!is_empty(first))
return first;
return get_first_non_empty(std::forward<Args>(args)...);
}
+9
View File
@@ -0,0 +1,9 @@
#include <utils/revstr.hpp>
namespace utils
{
std::string revstr(const std::string& original)
{
return {original.rbegin(), original.rend()};
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace utils
{
std::string revstr(const std::string& original);
}
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include <functional>
#include <memory>
#include <vector>
/**
* A class to be used to make sure some functions are called when the scope
* is left, because they will be called in the ScopeGuard's destructor. It
* can for example be used to delete some pointer whenever any exception is
* called. Example:
* {
* ScopeGuard scope;
* int* number = new int(2);
* scope.add_callback([number]() { delete number; });
* // Do some other stuff with the number. But these stuff might throw an exception:
* throw std::runtime_error("Some error not caught here, but in our caller");
* return true;
* }
* In this example, our pointer will always be deleted, even when the
* exception is thrown. If we want the functions to be called only when the
* scope is left because of an unexpected exception, we can use
* ScopeGuard::disable();
*/
namespace utils
{
class ScopeGuard
{
public:
/**
* The constructor can take a callback. But additional callbacks can be
* added later with add_callback()
*/
explicit ScopeGuard(std::function<void()>&& func):
enabled(true)
{
this->add_callback(std::move(func));
}
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(ScopeGuard&&) = delete;
ScopeGuard(ScopeGuard&&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
/**
* default constructor, the scope guard is enabled but empty, use
* add_callback()
*/
explicit ScopeGuard():
enabled(true)
{
}
/**
* Call all callbacks in the desctructor, unless it has been disabled.
*/
~ScopeGuard()
{
if (this->enabled)
for (auto& func: this->callbacks)
func();
}
/**
* Add a callback to be called in our destructor, one scope guard can be
* used for more than one task, if needed.
*/
void add_callback(std::function<void()>&& func)
{
this->callbacks.emplace_back(std::move(func));
}
/**
* Disable that scope guard, nothing will be done when the scope is
* exited.
*/
void disable()
{
this->enabled = false;
}
private:
bool enabled;
std::vector<std::function<void()>> callbacks;
};
template<typename F>
auto make_scope_guard(F f)
{
static struct Empty {} empty;
auto deleter = [f = std::move(f)](Empty*) { f(); };
return std::unique_ptr<Empty, decltype(deleter)>{&empty, std::move(deleter)};
}
}
+32
View File
@@ -0,0 +1,32 @@
#include <utils/sha1.hpp>
#include <biboumi.h>
#ifdef BOTAN_FOUND
# include <botan/hash.h>
# include <botan/hex.h>
#endif
#ifdef GCRYPT_FOUND
# include <gcrypt.h>
# include <vector>
# include <iomanip>
# include <sstream>
#endif
std::string sha1(const std::string& input)
{
#ifdef BOTAN_FOUND
auto sha1 = Botan::HashFunction::create_or_throw("SHA-1");
sha1->update(input);
return Botan::hex_encode(sha1->final(), false);
#endif
#ifdef GCRYPT_FOUND
const auto hash_length = gcry_md_get_algo_dlen(GCRY_MD_SHA1);
std::vector<uint8_t> output(hash_length, {});
gcry_md_hash_buffer(GCRY_MD_SHA1, output.data(), input.data(), input.size());
std::ostringstream digest;
for (std::size_t i = 0; i < hash_length; i++)
digest << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(output[i]);
return digest.str();
#endif
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string sha1(const std::string& input);
+19
View File
@@ -0,0 +1,19 @@
#include <utils/split.hpp>
#include <sstream>
namespace utils
{
std::vector<std::string> split(const std::string& s, const char delim, const bool allow_empty)
{
std::vector<std::string> ret;
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim))
{
if (item.empty() && !allow_empty)
continue ;
ret.emplace_back(std::move(item));
}
return ret;
}
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <vector>
namespace utils
{
std::vector<std::string> split(const std::string &s, const char delim, const bool allow_empty=true);
}
+28
View File
@@ -0,0 +1,28 @@
#include <utils/string.hpp>
#include <utils/encoding.hpp>
bool to_bool(const std::string& val)
{
return (val == "1" || val == "true");
}
std::vector<std::string> cut(const std::string& val, const std::size_t size)
{
std::vector<std::string> res;
std::string::size_type pos = 0;
while (pos < val.size())
{
// Get the number of chars, <= size, that contain only whole
// UTF-8 codepoints.
std::size_t s = 0;
auto codepoint_size = utils::get_next_codepoint_size(val[pos + s]);
while (s + codepoint_size <= size && pos + s < val.size())
{
s += codepoint_size;
codepoint_size = utils::get_next_codepoint_size(val[pos + s]);
}
res.emplace_back(val.substr(pos, s));
pos += s;
}
return res;
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <vector>
#include <string>
bool to_bool(const std::string& val);
std::vector<std::string> cut(const std::string& val, const std::size_t size);
+21
View File
@@ -0,0 +1,21 @@
#include <logger/logger.hpp>
#include <utils/system.hpp>
#include <sys/utsname.h>
#include <cstring>
using namespace std::string_literals;
namespace utils
{
std::string get_system_name()
{
struct utsname uts;
const int res = ::uname(&uts);
if (res == -1)
{
log_error("uname failed: ", std::strerror(errno));
return "Unknown";
}
return uts.sysname + " "s + uts.release;
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <string>
namespace utils
{
std::string get_system_name();
}
+70
View File
@@ -0,0 +1,70 @@
#include <utils/time.hpp>
#include <time.h>
#include <sstream>
#include <iomanip>
#include <locale>
#include "biboumi.h"
namespace utils
{
std::string to_string(const std::time_t& timestamp)
{
constexpr std::size_t stamp_size = 21;
char date_buf[stamp_size];
if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(&timestamp)) != stamp_size - 1)
return "";
return {std::begin(date_buf), std::end(date_buf) - 1};
}
std::time_t parse_datetime(const std::string& stamp)
{
static const char* format = "%Y-%m-%dT%H:%M:%S";
std::tm t = {};
#ifdef HAS_GET_TIME
std::istringstream ss(stamp);
ss.imbue(std::locale("C"));
std::string timezone;
ss >> std::get_time(&t, format) >> timezone;
if (ss.fail())
return -1;
#else
/* Y - m - d T H : M : S */
constexpr std::size_t stamp_size_without_tz = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2;
if (!strptime(stamp.data(), format, &t)) {
return -1;
}
const std::string timezone(stamp.data() + stamp_size_without_tz);
#endif
if (timezone.empty())
return -1;
if (timezone.compare(0, 1, "Z") != 0)
{
std::stringstream tz_ss;
tz_ss << timezone;
int multiplier = -1;
char prefix;
int hours;
char sep;
int minutes;
tz_ss >> prefix >> hours >> sep >> minutes;
if (tz_ss.fail())
return -1;
if (prefix == '-')
multiplier = +1;
else if (prefix != '+')
return -1;
t.tm_hour += multiplier * hours;
t.tm_min += multiplier * minutes;
}
return ::timegm(&t);
}
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <ctime>
#include <string>
namespace utils
{
std::string to_string(const std::time_t& timestamp);
std::time_t parse_datetime(const std::string& stamp);
}
+47
View File
@@ -0,0 +1,47 @@
#include <utils/timed_events.hpp>
TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point,
std::function<void()> callback, const std::string& name):
time_point(std::move(time_point)),
callback(callback),
repeat(false),
repeat_delay(0),
name(name)
{
}
TimedEvent::TimedEvent(std::chrono::milliseconds&& duration,
std::function<void()> callback, const std::string& name):
time_point(std::chrono::steady_clock::now() + duration),
callback(callback),
repeat(true),
repeat_delay(std::move(duration)),
name(name)
{
}
bool TimedEvent::is_after(const TimedEvent& other) const
{
return this->is_after(other.time_point);
}
bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const
{
return this->time_point > time_point;
}
std::chrono::milliseconds TimedEvent::get_timeout() const
{
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(this->time_point - std::chrono::steady_clock::now());
return std::max(diff, 0ms);
}
void TimedEvent::execute() const
{
this->callback();
}
const std::string& TimedEvent::get_name() const
{
return this->name;
}
+132
View File
@@ -0,0 +1,132 @@
#pragma once
#include <functional>
#include <string>
#include <chrono>
#include <vector>
using namespace std::literals::chrono_literals;
namespace utils {
static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1);
}
class TimedEventsManager;
/**
* A callback with an associated date.
*/
class TimedEvent
{
friend class TimedEventsManager;
public:
/**
* An event the occurs only once, at the given time_point
*/
explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point,
std::function<void()> callback, const std::string& name="");
explicit TimedEvent(std::chrono::milliseconds&& duration,
std::function<void()> callback, const std::string& name="");
explicit TimedEvent(TimedEvent&&) = default;
TimedEvent& operator=(TimedEvent&&) = default;
~TimedEvent() = default;
TimedEvent(const TimedEvent&) = delete;
TimedEvent& operator=(const TimedEvent&) = delete;
/**
* Whether or not this event happens after the other one.
*/
bool is_after(const TimedEvent& other) const;
bool is_after(const std::chrono::steady_clock::time_point& time_point) const;
/**
* Return the duration difference between now and the event time point.
* If the difference would be negative (i.e. the event is expired), the
* returned value is 0 instead. The value cannot then be negative.
*/
std::chrono::milliseconds get_timeout() const;
void execute() const;
const std::string& get_name() const;
private:
/**
* The next time point at which the event is executed.
*/
std::chrono::steady_clock::time_point time_point;
/**
* The function to execute.
*/
std::function<void()> callback;
/**
* Whether or not this events repeats itself until it is destroyed.
*/
bool repeat;
/**
* This value is added to the time_point each time the event is executed,
* if repeat is true. Otherwise it is ignored.
*/
std::chrono::milliseconds repeat_delay;
/**
* A name that is used to identify that event. If you want to find your
* event (for example if you want to cancel it), the name should be
* unique.
*/
std::string name;
};
/**
* A class managing a list of TimedEvents.
* They are sorted, new events can be added, removed, fetch, etc.
*/
class TimedEventsManager
{
public:
~TimedEventsManager() = default;
TimedEventsManager(const TimedEventsManager&) = delete;
TimedEventsManager(TimedEventsManager&&) = delete;
TimedEventsManager& operator=(const TimedEventsManager&) = delete;
TimedEventsManager& operator=(TimedEventsManager&&) = delete;
/**
* Return the unique instance of this class
*/
static TimedEventsManager& instance();
/**
* Add an event to the list of managed events. The list is sorted after
* this call.
*/
void add_event(TimedEvent&& event);
/**
* Returns the duration, in milliseconds, between now and the next
* available event. If the event is already expired (the duration is
* negative), 0 is returned instead (as in “it's not too late, execute it
* now”)
* Returns a negative value if no event is available.
*/
std::chrono::milliseconds get_timeout() const;
/**
* Execute all the expired events (if their expiration time is exactly
* now, or before now). The event is then removed from the list. If the
* event does repeat, its expiration time is updated and it is reinserted
* in the list at the correct position.
* Returns the number of executed events.
*/
std::size_t execute_expired_events();
/**
* Remove (and thus cancel) all the timed events with the given name.
* Returns the number of canceled events.
*/
std::size_t cancel(const std::string& name);
/**
* Return the number of managed events.
*/
std::size_t size() const;
private:
std::vector<TimedEvent> events;
explicit TimedEventsManager() = default;
};
+73
View File
@@ -0,0 +1,73 @@
#include <utils/timed_events.hpp>
TimedEventsManager& TimedEventsManager::instance()
{
static TimedEventsManager inst;
return inst;
}
void TimedEventsManager::add_event(TimedEvent&& event)
{
for (auto it = this->events.begin(); it != this->events.end(); ++it)
{
if (it->is_after(event))
{
this->events.emplace(it, std::move(event));
return;
}
}
this->events.emplace_back(std::move(event));
}
std::chrono::milliseconds TimedEventsManager::get_timeout() const
{
if (this->events.empty())
return utils::no_timeout;
return this->events.front().get_timeout();
}
std::size_t TimedEventsManager::execute_expired_events()
{
std::size_t count = 0;
const auto now = std::chrono::steady_clock::now();
for (auto it = this->events.begin(); it != this->events.end();)
{
if (!it->is_after(now))
{
TimedEvent copy(std::move(*it));
it = this->events.erase(it);
++count;
copy.execute();
if (copy.repeat)
{
copy.time_point += copy.repeat_delay;
this->add_event(std::move(copy));
}
continue;
}
else
break;
}
return count;
}
std::size_t TimedEventsManager::cancel(const std::string& name)
{
std::size_t res = 0;
for (auto it = this->events.begin(); it != this->events.end();)
{
if (it->get_name() == name)
{
it = this->events.erase(it);
res++;
}
else
++it;
}
return res;
}
std::size_t TimedEventsManager::size() const
{
return this->events.size();
}
+13
View File
@@ -0,0 +1,13 @@
#include <utils/tolower.hpp>
namespace utils
{
std::string tolower(const std::string& original)
{
std::string res;
res.reserve(original.size());
for (const char c: original)
res += static_cast<char>(std::tolower(c));
return res;
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace utils
{
std::string tolower(const std::string& original);
}
+29
View File
@@ -0,0 +1,29 @@
#include <utils/xdg.hpp>
#include <cstdlib>
#include "biboumi.h"
std::string xdg_path(const std::string& filename, const char* env_var)
{
const char* xdg_home = ::getenv(env_var);
if (xdg_home && xdg_home[0] == '/')
return std::string{xdg_home} + "/" PROJECT_NAME "/" + filename;
else
{
const char* home = ::getenv("HOME");
if (home)
return std::string{home} + "/" ".config" "/" PROJECT_NAME "/" + filename;
else
return filename;
}
}
std::string xdg_config_path(const std::string& filename)
{
return xdg_path(filename, "XDG_CONFIG_HOME");
}
std::string xdg_data_path(const std::string& filename)
{
return xdg_path(filename, "XDG_DATA_HOME");
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <string>
/**
* Returns a path for the given filename, according to the XDG base
* directory specification, see
* http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
*/
std::string xdg_config_path(const std::string& filename);
std::string xdg_data_path(const std::string& filename);