misc: rename dmenutools to dmenu

Tue, 01 May 2018 12:32:16 +0200

author
David Demelier <markand@malikania.fr>
date
Tue, 01 May 2018 12:32:16 +0200
changeset 36
b970ba24bdef
parent 35
72274722586f
child 37
76d774822dac

misc: rename dmenutools to dmenu

CMakeLists.txt file | annotate | diff | comparison | revisions
cmake/function/DefineExecutable.cmake file | annotate | diff | comparison | revisions
libdmenu/CMakeLists.txt file | annotate | diff | comparison | revisions
libdmenu/dmenu/dmenu.cpp file | annotate | diff | comparison | revisions
libdmenu/dmenu/dmenu.hpp file | annotate | diff | comparison | revisions
libdmenu/dmenu/ini.cpp file | annotate | diff | comparison | revisions
libdmenu/dmenu/ini.hpp file | annotate | diff | comparison | revisions
libdmenu/dmenu/join.hpp file | annotate | diff | comparison | revisions
libdmenu/dmenu/sysconfig.hpp.in file | annotate | diff | comparison | revisions
libdmenu/dmenu/xdg.hpp file | annotate | diff | comparison | revisions
libdmenutools/CMakeLists.txt file | annotate | diff | comparison | revisions
libdmenutools/dmenu/dmenu.cpp file | annotate | diff | comparison | revisions
libdmenutools/dmenu/dmenu.hpp file | annotate | diff | comparison | revisions
libdmenutools/dmenu/ini.cpp file | annotate | diff | comparison | revisions
libdmenutools/dmenu/ini.hpp file | annotate | diff | comparison | revisions
libdmenutools/dmenu/join.hpp file | annotate | diff | comparison | revisions
libdmenutools/dmenu/sysconfig.hpp.in file | annotate | diff | comparison | revisions
libdmenutools/dmenu/xdg.hpp file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Tue May 01 12:16:12 2018 +0200
+++ b/CMakeLists.txt	Tue May 01 12:32:16 2018 +0200
@@ -27,7 +27,7 @@
 find_package(Boost REQUIRED COMPONENTS filesystem system)
 find_package(PkgConfig REQUIRED)
 
-add_subdirectory(libdmenutools)
+add_subdirectory(libdmenu)
 add_subdirectory(dmenu-background)
 add_subdirectory(dmenu-mpc)
 add_subdirectory(dmenu-ssh)
--- a/cmake/function/DefineExecutable.cmake	Tue May 01 12:16:12 2018 +0200
+++ b/cmake/function/DefineExecutable.cmake	Tue May 01 12:32:16 2018 +0200
@@ -60,7 +60,7 @@
         add_executable(dmenu-${EXE_NAME} ${EXE_SOURCES})
         target_include_directories(dmenu-${EXE_NAME} PRIVATE ${EXE_INCLUDES})
         target_compile_definitions(dmenu-${EXE_NAME} PRIVATE ${EXE_FLAGS})
-        target_link_libraries(dmenu-${EXE_NAME} libdmenutools ${EXE_LIBRARIES})
+        target_link_libraries(dmenu-${EXE_NAME} libdmenu ${EXE_LIBRARIES})
 
         install(
             TARGETS dmenu-${EXE_NAME}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/CMakeLists.txt	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,64 @@
+#
+# CMakeLists.txt -- CMake build system for dmenutools
+#
+# Copyright (c) 2018 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.
+#
+
+project(libdmenu)
+
+pkg_check_modules(libnotify IMPORTED_TARGET libnotify)
+
+option(WITH_NOTIFY "Enable libnotify support" On)
+
+if (NOT WITH_NOTIFY)
+    set(WITH_NOTIFY_MSG "No (disabled by user)" CACHE INTERNAL "" FORCE)
+elseif (NOT libnotify_FOUND)
+    set(WITH_NOTIFY_MSG "No (libnotify not found)" CACHE INTERNAL "" FORCE)
+else ()
+    set(WITH_NOTIFY_MSG "Yes" CACHE INTERNAL "" FORCE)
+    set(HAVE_NOTIFY On)
+endif ()
+
+set(
+    SOURCES
+    ${libdmenu_SOURCE_DIR}/dmenu/dmenu.cpp
+    ${libdmenu_SOURCE_DIR}/dmenu/dmenu.hpp
+    ${libdmenu_SOURCE_DIR}/dmenu/ini.cpp
+    ${libdmenu_SOURCE_DIR}/dmenu/ini.hpp
+    ${libdmenu_SOURCE_DIR}/dmenu/xdg.hpp
+)
+
+configure_file(
+    ${libdmenu_SOURCE_DIR}/dmenu/sysconfig.hpp.in
+    ${libdmenu_BINARY_DIR}/dmenu/sysconfig.hpp
+)
+
+add_library(libdmenu STATIC ${SOURCES})
+set_target_properties(libdmenu PROPERTIES PREFIX "")
+
+target_link_libraries(
+    libdmenu
+    Boost::filesystem
+    Boost::system
+    $<$<BOOL:${HAVE_NOTIFY}>:PkgConfig::libnotify>
+)
+
+target_include_directories(
+    libdmenu
+    PUBLIC
+        ${libdmenu_SOURCE_DIR}
+    PRIVATE
+        ${libdmenu_BINARY_DIR}/dmenu
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/dmenu.cpp	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,109 @@
+/*
+ * dmenu.cpp -- dmenu utilities
+ *
+ * Copyright (c) 2018 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 <iostream>
+#include <sstream>
+
+#include <boost/filesystem.hpp>
+#include <boost/process.hpp>
+
+#include "dmenu.hpp"
+#include "sysconfig.hpp"
+#include "xdg.hpp"
+
+namespace fs = boost::filesystem;
+namespace proc = boost::process;
+
+namespace dmenu {
+
+namespace {
+
+std::string build_args(const std::vector<std::string>& args)
+{
+    std::ostringstream oss;
+
+    oss << "dmenu";
+
+    for (const auto& arg : args)
+        oss << " " << arg;
+
+    return oss.str();
+}
+
+std::string get_result(proc::ipstream& out, proc::child& child)
+{
+    std::string line;
+
+    if (child.running())
+        std::getline(out, line);
+        
+    child.wait();
+
+    return line;
+}
+
+} // !namespace
+
+std::string run(const std::vector<std::string>& args,
+                const std::vector<std::string>& lines)
+{
+    proc::opstream in;
+    proc::ipstream out;
+    proc::child child(build_args(args), proc::std_in < in, proc::std_out > out);
+
+    for (const auto& line : lines)
+        in << line << std::endl;
+
+    in.pipe().close();
+
+    return get_result(out, child);
+}
+
+ini::section config(const std::string& section)
+{
+    const auto path = xdg().config_home() + "/dmenutools.conf";
+
+    if (!fs::exists(path))
+        return ini::section(section);
+
+    return ini::read_file(path).get(section);
+}
+
+void error(const std::string& summary, const std::string& message)
+{
+#if defined(HAVE_NOTIFY)
+    static bool is_initialized;
+
+    if (!is_initialized) {
+        notify_init("dmenu");
+        is_initialized = true;
+    }
+
+    NotifyNotification* n = notify_notification_new(summary.c_str(), message.c_str(), nullptr);
+    GError* error = nullptr;
+
+    if (!notify_notification_show(n, &error))
+        std::cerr << error->message << std::endl;
+
+    g_free(error);
+#else
+    std::cerr << summary << ": " << message << std::endl;
+#endif
+}
+
+} // !dmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/dmenu.hpp	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,64 @@
+/*
+ * dmenu.hpp -- dmenu utilities
+ *
+ * Copyright (c) 2018 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 DMENUTOOLS_DMENU_HPP
+#define DMENUTOOLS_DMENU_HPP
+
+/**
+ * \file dmenu.hpp
+ * \brief dmenu utilities.
+ */
+
+#include <string>
+#include <vector>
+
+#include "ini.hpp"
+
+namespace dmenu {
+
+/**
+ * Invoke dmenu with the given arguments.
+ *
+ * \param args the dmenu arguments
+ * \param lines the stdin input for dmenu
+ * \return the selected entry
+ */
+std::string run(const std::vector<std::string>& args,
+                const std::vector<std::string>& lines);
+
+/**
+ * Get the configuration section.
+ *
+ * \param section the desired section (e.g. background)
+ * \return the section
+ */
+ini::section config(const std::string& section);
+
+/**
+ * Show a notification error.
+ *
+ * If notification support is disabled, error is printed to std::cerr.
+ *
+ * \param summary the summary
+ * \param message the message content
+ */
+void error(const std::string& summary, const std::string& message);
+
+} // !dmenu
+
+#endif // !DMENUTOOLS_DMENU_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/ini.cpp	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,423 @@
+/*
+ * ini.cpp -- extended .ini file parser
+ *
+ * Copyright (c) 2013-2018 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 <cctype>
+#include <cstring>
+#include <iostream>
+#include <iterator>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+
+// for PathIsRelative.
+#if defined(_WIN32)
+#  include <Shlwapi.h>
+#endif
+
+#include "ini.hpp"
+
+namespace ini {
+
+namespace {
+
+using stream_iterator = std::istreambuf_iterator<char>;
+using token_iterator = std::vector<token>::const_iterator;
+
+inline bool is_absolute(const std::string& path) noexcept
+{
+#if defined(_WIN32)
+    return !PathIsRelative(path.c_str());
+#else
+    return path.size() > 0 && path[0] == '/';
+#endif
+}
+
+inline bool is_quote(char c) noexcept
+{
+    return c == '\'' || c == '"';
+}
+
+inline bool is_space(char c) noexcept
+{
+    // Custom version because std::isspace includes \n as space.
+    return c == ' ' || c == '\t';
+}
+
+inline bool is_list(char c) noexcept
+{
+    return c == '(' || c == ')' || c == ',';
+}
+
+inline bool is_reserved(char c) noexcept
+{
+    return is_list(c) || is_quote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '=';
+}
+
+void analyse_line(unsigned& line, unsigned& column, stream_iterator& it) noexcept
+{
+    assert(*it == '\n');
+
+    ++ line;
+    ++ it;
+    column = 0;
+}
+
+void analyse_comment(unsigned& column, stream_iterator& it, stream_iterator end) noexcept
+{
+    assert(*it == '#');
+
+    while (it != end && *it != '\n') {
+        ++ column;
+        ++ it;
+    }
+}
+
+void analyse_spaces(unsigned& column, stream_iterator& it, stream_iterator end) noexcept
+{
+    assert(is_space(*it));
+
+    while (it != end && is_space(*it)) {
+        ++ column;
+        ++ it;
+    }
+}
+
+void analyse_list(tokens& list, unsigned line, unsigned& column, stream_iterator& it) noexcept
+{
+    assert(is_list(*it));
+
+    switch (*it++) {
+    case '(':
+        list.emplace_back(token::list_begin, line, column++);
+        break;
+    case ')':
+        list.emplace_back(token::list_end, line, column++);
+        break;
+    case ',':
+        list.emplace_back(token::comma, line, column++);
+        break;
+    default:
+        break;
+    }
+}
+
+void analyse_section(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
+{
+    assert(*it == '[');
+
+    std::string value;
+    unsigned save = column;
+
+    // Read section name.
+    for (++it; it != end && *it != ']';) {
+        if (*it == '\n')
+            throw exception(line, column, "section not terminated, missing ']'");
+        if (is_reserved(*it))
+            throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'");
+
+        ++ column;
+        value += *it++;
+    }
+
+    if (it == end)
+        throw exception(line, column, "section name expected after '[', got <EOF>");
+    if (value.empty())
+        throw exception(line, column, "empty section name");
+
+    // Remove ']'.
+    ++ it;
+
+    list.emplace_back(token::section, line, save, std::move(value));
+}
+
+void analyse_assign(tokens& list, unsigned& line, unsigned& column, stream_iterator& it)
+{
+    assert(*it == '=');
+
+    list.push_back({ token::assign, line, column++ });
+    ++ it;
+}
+
+void analyse_quoted_word(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
+{
+    std::string value;
+    unsigned save = column;
+    char quote = *it++;
+
+    while (it != end && *it != quote) {
+        // TODO: escape sequence
+        ++ column;
+        value += *it++;
+    }
+
+    if (it == end)
+        throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>");
+
+    // Remove quote.
+    ++ it;
+
+    list.push_back({ token::quoted_word, line, save, std::move(value) });
+}
+
+void analyse_word(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
+{
+    assert(!is_reserved(*it));
+
+    std::string value;
+    unsigned save = column;
+
+    while (it != end && !std::isspace(*it) && !is_reserved(*it)) {
+        ++ column;
+        value += *it++;
+    }
+
+    list.push_back({ token::word, line, save, std::move(value) });
+}
+
+void analyse_include(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
+{
+    assert(*it == '@');
+
+    std::string include;
+    unsigned save = column;
+
+    // Read include.
+    ++ it;
+    while (it != end && !is_space(*it)) {
+        ++ column;
+        include += *it++;
+    }
+
+    if (include == "include")
+        list.push_back({ token::include, line, save });
+    else if (include == "tryinclude")
+        list.push_back({ token::tryinclude, line, save });
+    else
+        throw exception(line, column, "expected include or tryinclude after '@' token");
+}
+
+void parse_option_value_simple(option& option, token_iterator& it)
+{
+    assert(it->get_type() == token::word || it->get_type() == token::quoted_word);
+
+    option.push_back((it++)->get_value());
+}
+
+void parse_option_value_list(option& option, token_iterator& it, token_iterator end)
+{
+    assert(it->get_type() == token::list_begin);
+
+    token_iterator save = it++;
+
+    while (it != end && it->get_type() != token::list_end) {
+        switch (it->get_type()) {
+        case token::comma:
+            // Previous must be a word.
+            if (it[-1].get_type() != token::word && it[-1].get_type() != token::quoted_word)
+                throw exception(it->get_line(), it->get_column(), "unexpected comma after '" + it[-1].get_value() + "'");
+
+            ++ it;
+            break;
+        case token::word:
+        case token::quoted_word:
+            option.push_back((it++)->get_value());
+            break;
+        default:
+            throw exception(it->get_line(), it->get_column(), "unexpected '" + it[-1].get_value() + "' in list construct");
+            break;
+        }
+    }
+
+    if (it == end)
+        throw exception(save->get_line(), save->get_column(), "unterminated list construct");
+
+    // Remove ).
+    ++ it;
+}
+
+void parse_option(section& sc, token_iterator& it, token_iterator end)
+{
+    option option(it->get_value());
+    token_iterator save(it);
+
+    // No '=' or something else?
+    if (++it == end)
+        throw exception(save->get_line(), save->get_column(), "expected '=' assignment, got <EOF>");
+    if (it->get_type() != token::assign)
+        throw exception(it->get_line(), it->get_column(), "expected '=' assignment, got " + it->get_value());
+
+    // Empty options are allowed so just test for words.
+    if (++it != end) {
+        if (it->get_type() == token::word || it->get_type() == token::quoted_word)
+            parse_option_value_simple(option, it);
+        else if (it->get_type() == token::list_begin)
+            parse_option_value_list(option, it, end);
+    }
+
+    sc.push_back(std::move(option));
+}
+
+void parse_include(document& doc, const std::string& path, token_iterator& it, token_iterator end, bool required)
+{
+    token_iterator save(it);
+
+    if (++it == end)
+        throw exception(save->get_line(), save->get_column(), "expected file name after '@include' statement, got <EOF>");
+    if (it->get_type() != token::word && it->get_type() != token::quoted_word)
+        throw exception(it->get_line(), it->get_column(), "expected file name after '@include' statement, got " + it->get_value());
+
+    std::string value = (it++)->get_value();
+    std::string file;
+
+    if (!is_absolute(value)) {
+#if defined(_WIN32)
+        file = path + "\\" + value;
+#else
+        file = path + "/" + value;
+#endif
+    } else
+        file = value;
+
+    try {
+        /*
+         * If required is set to true, we have @include, otherwise the non-fatal
+         * @tryinclude keyword.
+         */
+        for (const auto& sc : read_file(file))
+            doc.push_back(sc);
+    } catch (...) {
+        if (required)
+            throw;
+    }
+}
+
+void parse_section(document& doc, token_iterator& it, token_iterator end)
+{
+    section sc(it->get_value());
+
+    // Skip [section].
+    ++ it;
+
+    // Read until next section.
+    while (it != end && it->get_type() != token::section) {
+        if (it->get_type() != token::word)
+            throw exception(it->get_line(), it->get_column(), "unexpected token '" + it->get_value() + "' in section definition");
+
+        parse_option(sc, it, end);
+    }
+
+    doc.push_back(std::move(sc));
+}
+
+} // !namespace
+
+tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end)
+{
+    tokens list;
+    unsigned line = 1;
+    unsigned column = 0;
+
+    while (it != end) {
+        if (*it == '\n')
+            analyse_line(line, column, it);
+        else if (*it == '#')
+            analyse_comment(column, it, end);
+        else if (*it == '[')
+            analyse_section(list, line, column, it, end);
+        else if (*it == '=')
+            analyse_assign(list, line, column, it);
+        else if (is_space(*it))
+            analyse_spaces(column, it, end);
+        else if (*it == '@')
+            analyse_include(list, line, column, it, end);
+        else if (is_quote(*it))
+            analyse_quoted_word(list, line, column, it, end);
+        else if (is_list(*it))
+            analyse_list(list, line, column, it);
+        else
+            analyse_word(list, line, column, it, end);
+    }
+
+    return list;
+}
+
+tokens analyse(std::istream& stream)
+{
+    return analyse(std::istreambuf_iterator<char>(stream), {});
+}
+
+document parse(const tokens& tokens, const std::string& path)
+{
+    document doc;
+    token_iterator it = tokens.cbegin();
+    token_iterator end = tokens.cend();
+
+    while (it != end) {
+        switch (it->get_type()) {
+        case token::include:
+            parse_include(doc, path, it, end, true);
+            break;
+        case token::tryinclude:
+            parse_include(doc, path, it, end, false);
+            break;
+        case token::section:
+            parse_section(doc, it, end);
+            break;
+        default:
+            throw exception(it->get_line(), it->get_column(), "unexpected '" + it->get_value() + "' on root document");
+        }
+    }
+
+    return doc;
+}
+
+document read_file(const std::string& filename)
+{
+    // Get parent path.
+    auto parent = filename;
+    auto pos = parent.find_last_of("/\\");
+
+    if (pos != std::string::npos)
+        parent.erase(pos);
+    else
+        parent = ".";
+
+    std::ifstream input(filename);
+
+    if (!input)
+        throw exception(0, 0, std::strerror(errno));
+
+    return parse(analyse(input), parent);
+}
+
+document read_string(const std::string& buffer)
+{
+    std::istringstream iss(buffer);
+
+    return parse(analyse(iss));
+}
+
+void dump(const tokens& tokens)
+{
+    for (const token& token: tokens) {
+        // TODO: add better description
+        std::cout << token.get_line() << ":" << token.get_column() << ": " << token.get_value() << std::endl;
+    }
+}
+
+} // !ini
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/ini.hpp	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,675 @@
+/*
+ * ini.hpp -- extended .ini file parser
+ *
+ * Copyright (c) 2013-2018 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 INI_HPP
+#define INI_HPP
+
+/**
+ * \file ini.hpp
+ * \brief Extended .ini file parser.
+ * \author David Demelier <markand@malikania.fr>
+ * \version 2.0.0
+ */
+
+/**
+ * \page Ini Ini
+ * \brief Extended .ini file parser.
+ *
+ * ## Export macros
+ *
+ * You must define `INI_DLL` globally and `INI_BUILDING_DLL` when compiling the
+ * library if you want a DLL, alternatively you can provide your own
+ * `INI_EXPORT` macro instead.
+ *
+ *   - \subpage ini-syntax
+ */
+
+/**
+ * \page ini-syntax Syntax
+ * \brief File syntax.
+ *
+ * The syntax is similar to most of `.ini` implementations as:
+ *
+ *   - a section is delimited by `[name]` can be redefined multiple times,
+ *   - an option **must** always be defined in a section,
+ *   - empty options must be surrounded by quotes,
+ *   - lists can not include trailing commas,
+ *   - include statements must always live at the beginning of files
+ *     (in no sections),
+ *   - comments start with # until the end of line,
+ *   - options with spaces **must** use quotes.
+ *
+ * # Basic file
+ *
+ * ````ini
+ * # This is a comment.
+ * [section]
+ * option1 = value1
+ * option2 = "value 2 with spaces"    # comment is also allowed here
+ * ````
+ *
+ * # Redefinition
+ *
+ * Sections can be redefined multiple times and are kept the order they are
+ * seen.
+ *
+ * ````ini
+ * [section]
+ * value = "1"
+ *
+ * [section]
+ * value = "2"
+ * ````
+ *
+ * The ini::document object will contains two ini::section.
+ *
+ * # Lists
+ *
+ * Lists are defined using `()` and commas, like values, they may have quotes.
+ *
+ * ````ini
+ * [section]
+ * names = ( "x1", "x2" )
+ *
+ * # This is also allowed.
+ * biglist = (
+ *   "abc",
+ *   "def"
+ * )
+ * ````
+ *
+ * # Include statement
+ *
+ * You can split a file into several pieces, if the include statement contains a
+ * relative path, the path will be relative to the current file being parsed.
+ *
+ * You **must** use the include statement before any section.
+ *
+ * If the file contains spaces, use quotes.
+ *
+ * ````ini
+ * # main.conf
+ * @include "foo.conf"
+ *
+ * # foo.conf
+ * [section]
+ * option1 = value1
+ * ````
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+/**
+ * \cond INI_HIDDEN_SYMBOLS
+ */
+
+#if !defined(INI_EXPORT)
+#   if defined(INI_DLL)
+#       if defined(_WIN32)
+#           if defined(INI_BUILDING_DLL)
+#               define INI_EXPORT __declspec(dllexport)
+#           else
+#               define INI_EXPORT __declspec(dllimport)
+#           endif
+#       else
+#           define INI_EXPORT
+#       endif
+#   else
+#       define INI_EXPORT
+#   endif
+#endif
+
+/**
+ * \endcond
+ */
+
+/**
+ * Namespace for ini related classes.
+ */
+namespace ini {
+
+class document;
+
+/**
+ * \brief exception in a file.
+ */
+class exception : public std::exception {
+private:
+    unsigned line_;
+    unsigned column_;
+    std::string message_;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param line the line
+     * \param column the column
+     * \param msg the message
+     */
+    inline exception(unsigned line, unsigned column, std::string msg) noexcept
+        : line_(line)
+        , column_(column)
+        , message_(std::move(msg))
+    {
+    }
+
+    /**
+     * Get the line number.
+     *
+     * \return the line
+     */
+    inline unsigned get_line() const noexcept
+    {
+        return line_;
+    }
+
+    /**
+     * Get the column number.
+     *
+     * \return the column
+     */
+    inline unsigned get_column() const noexcept
+    {
+        return column_;
+    }
+
+    /**
+     * Return the raw exception message (no line and column shown).
+     *
+     * \return the exception message
+     */
+    const char* what() const noexcept override
+    {
+        return message_.c_str();
+    }
+};
+
+/**
+ * \brief Describe a token read in the .ini source.
+ *
+ * This class can be used when you want to parse a .ini file yourself.
+ *
+ * \see analyse
+ */
+class token {
+public:
+    /**
+     * \brief token type.
+     */
+    enum type {
+        include,                //!< include statement
+        tryinclude,             //!< tryinclude statement
+        section,                //!< [section]
+        word,                   //!< word without quotes
+        quoted_word,            //!< word with quotes
+        assign,                 //!< = assignment
+        list_begin,             //!< begin of list (
+        list_end,               //!< end of list )
+        comma                   //!< list separation
+    };
+
+private:
+    type type_;
+    unsigned line_;
+    unsigned column_;
+    std::string value_;
+
+public:
+    /**
+     * Construct a token.
+     *
+     * \param type the type
+     * \param line the line
+     * \param column the column
+     * \param value the value
+     */
+    token(type type, unsigned line, unsigned column, std::string value = "") noexcept
+        : type_(type)
+        , line_(line)
+        , column_(column)
+    {
+        switch (type) {
+        case include:
+            value_ = "@include";
+            break;
+        case tryinclude:
+            value_ = "@tryinclude";
+            break;
+        case section:
+        case word:
+        case quoted_word:
+            value_ = value;
+            break;
+        case assign:
+            value_ = "=";
+            break;
+        case list_begin:
+            value_ = "(";
+            break;
+        case list_end:
+            value_ = ")";
+            break;
+        case comma:
+            value_ = ",";
+            break;
+        default:
+            break;
+        }
+    }
+
+    /**
+     * Get the type.
+     *
+     * \return the type
+     */
+    inline type get_type() const noexcept
+    {
+        return type_;
+    }
+
+    /**
+     * Get the line.
+     *
+     * \return the line
+     */
+    inline unsigned get_line() const noexcept
+    {
+        return line_;
+    }
+
+    /**
+     * Get the column.
+     *
+     * \return the column
+     */
+    inline unsigned get_column() const noexcept
+    {
+        return column_;
+    }
+
+    /**
+     * Get the value. For words, quoted words and section, the value is the
+     * content. Otherwise it's the characters parsed.
+     *
+     * \return the value
+     */
+    inline const std::string& get_value() const noexcept
+    {
+        return value_;
+    }
+};
+
+/**
+ * List of tokens in order they are analyzed.
+ */
+using tokens = std::vector<token>;
+
+/**
+ * \brief option definition.
+ */
+class option : public std::vector<std::string> {
+private:
+    std::string key_;
+
+public:
+    /**
+     * Construct an empty option.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     */
+    inline option(std::string key) noexcept
+        : std::vector<std::string>()
+        , key_(std::move(key))
+    {
+        assert(!key_.empty());
+    }
+
+    /**
+     * Construct a single option.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     * \param value the value
+     */
+    inline option(std::string key, std::string value) noexcept
+        : key_(std::move(key))
+    {
+        assert(!key_.empty());
+
+        push_back(std::move(value));
+    }
+
+    /**
+     * Construct a list option.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     * \param values the values
+     */
+    inline option(std::string key, std::vector<std::string> values) noexcept
+        : std::vector<std::string>(std::move(values))
+        , key_(std::move(key))
+    {
+        assert(!key_.empty());
+    }
+
+    /**
+     * Get the option key.
+     *
+     * \return the key
+     */
+    inline const std::string& get_key() const noexcept
+    {
+        return key_;
+    }
+
+    /**
+     * Get the option value.
+     *
+     * \return the value
+     */
+    inline const std::string& get_value() const noexcept
+    {
+        static std::string dummy;
+
+        return empty() ? dummy : (*this)[0];
+    }
+};
+
+/**
+ * \brief Section that contains one or more options.
+ */
+class section : public std::vector<option> {
+private:
+    std::string key_;
+
+public:
+    /**
+     * Construct a section with its name.
+     *
+     * \pre key must not be empty
+     * \param key the key
+     */
+    inline section(std::string key) noexcept
+        : key_(std::move(key))
+    {
+        assert(!key_.empty());
+    }
+
+    /**
+     * Get the section key.
+     *
+     * \return the key
+     */
+    inline const std::string& get_key() const noexcept
+    {
+        return key_;
+    }
+
+    /**
+     * Check if the section contains a specific option.
+     *
+     * \param key the option key
+     * \return true if the option exists
+     */
+    inline bool contains(const std::string& key) const noexcept
+    {
+        return find(key) != end();
+    }
+
+    /**
+     * Find an option or return an empty one if not found.
+     *
+     * \param key the key
+     * \return the option or empty one if not found
+     */
+    inline option get(const std::string& key) const noexcept
+    {
+        auto it = find(key);
+
+        if (it == end())
+            return option(key);
+
+        return *it;
+    }
+
+    /**
+     * Find an option by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline iterator find(const std::string& key) noexcept
+    {
+        return std::find_if(begin(), end(), [&] (const auto& o) {
+            return o.get_key() == key;
+        });
+    }
+
+    /**
+     * Find an option by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline const_iterator find(const std::string& key) const noexcept
+    {
+        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
+            return o.get_key() == key;
+        });
+    }
+
+    /**
+     * Access an option at the specified key.
+     *
+     * \param key the key
+     * \return the option
+     * \pre contains(key) must return true
+     */
+    inline option& operator[](const std::string& key)
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \param key the key
+     * \return the option
+     * \pre contains(key) must return true
+     */
+    inline const option& operator[](const std::string& key) const
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Inherited operators.
+     */
+    using std::vector<option>::operator[];
+};
+
+/**
+ * \brief Ini document description.
+ * \see read_file
+ * \see read_string
+ */
+class document : public std::vector<section> {
+public:
+    /**
+     * Check if a document has a specific section.
+     *
+     * \param key the key
+     * \return true if the document contains the section
+     */
+    inline bool contains(const std::string& key) const noexcept
+    {
+        return find(key) != end();
+    }
+
+    /**
+     * Find a section or return an empty one if not found.
+     *
+     * \param key the key
+     * \return the section or empty one if not found
+     */
+    inline section get(const std::string& key) const noexcept
+    {
+        auto it = find(key);
+
+        if (it == end())
+            return section(key);
+
+        return *it;
+    }
+
+    /**
+     * Find a section by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline iterator find(const std::string& key) noexcept
+    {
+        return std::find_if(begin(), end(), [&] (const auto& o) {
+            return o.get_key() == key;
+        });
+    }
+
+    /**
+     * Find a section by key and return an iterator.
+     *
+     * \param key the key
+     * \return the iterator or end() if not found
+     */
+    inline const_iterator find(const std::string& key) const noexcept
+    {
+        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
+            return o.get_key() == key;
+        });
+    }
+
+    /**
+     * Access a section at the specified key.
+     *
+     * \param key the key
+     * \return the section
+     * \pre contains(key) must return true
+     */
+    inline section& operator[](const std::string& key)
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \param key the key
+     * \return the section
+     * \pre contains(key) must return true
+     */
+    inline const section& operator[](const std::string& key) const
+    {
+        assert(contains(key));
+
+        return *find(key);
+    }
+
+    /**
+     * Inherited operators.
+     */
+    using std::vector<section>::operator[];
+};
+
+/**
+ * Analyse a stream and detect potential syntax errors. This does not parse the
+ * file like including other files in include statement.
+ *
+ * It does only analysis, for example if an option is defined under no section,
+ * this does not trigger an exception while it's invalid.
+ *
+ * \param it the iterator
+ * \param end where to stop
+ * \return the list of tokens
+ * \throws exception on errors
+ */
+INI_EXPORT tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end);
+
+/**
+ * Overloaded function for stream.
+ *
+ * \param stream the stream
+ * \return the list of tokens
+ * \throws exception on errors
+ */
+INI_EXPORT tokens analyse(std::istream& stream);
+
+/**
+ * Parse the produced tokens.
+ *
+ * \param tokens the tokens
+ * \param path the parent path
+ * \return the document
+ * \throw exception on errors
+ */
+INI_EXPORT document parse(const tokens& tokens, const std::string& path = ".");
+
+/**
+ * Parse a file.
+ *
+ * \param filename the file name
+ * \return the document
+ * \throw exception on errors
+ */
+INI_EXPORT document read_file(const std::string& filename);
+
+/**
+ * Parse a string.
+ *
+ * If the string contains include statements, they are relative to the current
+ * working directory.
+ *
+ * \param buffer the buffer
+ * \return the document
+ * \throw exception on exceptions
+ */
+INI_EXPORT document read_string(const std::string& buffer);
+
+/**
+ * Show all tokens and their description.
+ *
+ * \param tokens the tokens
+ */
+INI_EXPORT void dump(const tokens& tokens);
+
+} // !ini
+
+#endif // !INI_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/join.hpp	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,66 @@
+/*
+ * join.hpp -- join lists into a string
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+/**
+ * \file join.hpp
+ * \brief Join function
+ */
+
+#include <initializer_list>
+#include <string>
+#include <sstream>
+
+namespace dmenu {
+
+/**
+ * Join values by a separator and return a string.
+ *
+ * \param first the first iterator
+ * \param last the last iterator
+ * \param delim the optional delimiter
+ * \return the string
+ */
+template <typename InputIt, typename DelimType = char>
+std::string join(InputIt first, InputIt last, DelimType delim = ':')
+{
+    std::ostringstream oss;
+
+    if (first != last) {
+        oss << *first;
+
+        while (++first != last)
+            oss << delim << *first;
+    }
+
+    return oss.str();
+}
+
+/**
+ * Convenient overload.
+ *
+ * \param list the initializer list
+ * \param delim the delimiter
+ * \return the string
+ */
+template <typename T, typename DelimType = char>
+inline std::string join(std::initializer_list<T> list, DelimType delim = ':')
+{
+    return join(list.begin(), list.end(), delim);
+}
+
+} // !dmenu
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/sysconfig.hpp.in	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,28 @@
+/*
+ * sysconfig.hpp -- configuration file for dmenutools
+ *
+ * Copyright (c) 2013-2018 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 DMENUTOOLS_SYSCONFIG_HPP
+#define DMENUTOOLS_SYSCONFIG_HPP
+
+#cmakedefine HAVE_NOTIFY
+
+#if defined(HAVE_NOTIFY)
+#   include <libnotify/notify.h>
+#endif
+
+#endif // !DMENUTOOLS_SYSCONFIG_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libdmenu/dmenu/xdg.hpp	Tue May 01 12:32:16 2018 +0200
@@ -0,0 +1,194 @@
+/*
+ * xdg.hpp -- XDG directory specifications
+ *
+ * Copyright (c) 2013-2018 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 DMENUTOOLS_XDG_HPP
+#define DMENUTOOLS_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>
+
+namespace dmenu {
+
+/**
+ * \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 config_home_;
+    std::string data_home_;
+    std::string cache_home_;
+    std::string runtime_dir_;
+    std::vector<std::string> config_dirs_;
+    std::vector<std::string> data_dirs_;
+
+    inline bool is_absolute(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 (is_absolute(item))
+                elems.push_back(item);
+        }
+
+        return elems;
+    }
+
+    std::string env_or_home(const std::string& var, const std::string& repl) const
+    {
+        auto value = std::getenv(var.c_str());
+
+        if (value == nullptr || !is_absolute(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> list_or_defaults(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()
+    {
+        config_home_    = env_or_home("XDG_CONFIG_HOME", ".config");
+        data_home_      = env_or_home("XDG_DATA_HOME", ".local/share");
+        cache_home_     = env_or_home("XDG_CACHE_HOME", ".cache");
+
+        config_dirs_    = list_or_defaults("XDG_CONFIG_DIRS", { "/etc/xdg" });
+        data_dirs_      = list_or_defaults("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 && is_absolute(runtime))
+            runtime_dir_ = runtime;
+    }
+
+    /**
+     * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config
+     *
+     * \return the config directory
+     */
+    inline const std::string& config_home() const noexcept
+    {
+        return config_home_;
+    }
+
+    /**
+     * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share
+     *
+     * \return the data directory
+     */
+    inline const std::string& data_home() const noexcept
+    {
+        return data_home_;
+    }
+
+    /**
+     * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache
+     *
+     * \return the cache directory
+     */
+    inline const std::string& cache_home() const noexcept
+    {
+        return cache_home_;
+    }
+
+    /**
+     * Get the runtime directory.
+     *
+     * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty
+     * value is returned and the user is responsible of using something else.
+     *
+     * \return the runtime directory
+     */
+    inline const std::string& runtime_dir() const noexcept
+    {
+        return runtime_dir_;
+    }
+
+    /**
+     * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" }
+     *
+     * \return the list of config directories
+     */
+    inline const std::vector<std::string>& config_dirs() const noexcept
+    {
+        return config_dirs_;
+    }
+
+    /**
+     * 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>& data_dirs() const noexcept
+    {
+        return data_dirs_;
+    }
+};
+
+} // !dmenu
+
+#endif // !XDG_HPP
--- a/libdmenutools/CMakeLists.txt	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for dmenutools
-#
-# Copyright (c) 2018 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.
-#
-
-project(libdmenutools)
-
-pkg_check_modules(libnotify IMPORTED_TARGET libnotify)
-
-option(WITH_NOTIFY "Enable libnotify support" On)
-
-if (NOT WITH_NOTIFY)
-    set(WITH_NOTIFY_MSG "No (disabled by user)" CACHE INTERNAL "" FORCE)
-elseif (NOT libnotify_FOUND)
-    set(WITH_NOTIFY_MSG "No (libnotify not found)" CACHE INTERNAL "" FORCE)
-else ()
-    set(WITH_NOTIFY_MSG "Yes" CACHE INTERNAL "" FORCE)
-    set(HAVE_NOTIFY On)
-endif ()
-
-set(
-    SOURCES
-    ${libdmenutools_SOURCE_DIR}/dmenu/dmenu.cpp
-    ${libdmenutools_SOURCE_DIR}/dmenu/dmenu.hpp
-    ${libdmenutools_SOURCE_DIR}/dmenu/ini.cpp
-    ${libdmenutools_SOURCE_DIR}/dmenu/ini.hpp
-    ${libdmenutools_SOURCE_DIR}/dmenu/xdg.hpp
-)
-
-configure_file(
-    ${libdmenutools_SOURCE_DIR}/dmenu/sysconfig.hpp.in
-    ${libdmenutools_BINARY_DIR}/dmenu/sysconfig.hpp
-)
-
-add_library(libdmenutools STATIC ${SOURCES})
-set_target_properties(libdmenutools PROPERTIES PREFIX "")
-
-target_link_libraries(
-    libdmenutools
-    Boost::filesystem
-    Boost::system
-    $<$<BOOL:${HAVE_NOTIFY}>:PkgConfig::libnotify>
-)
-
-target_include_directories(
-    libdmenutools
-    PUBLIC
-        ${libdmenutools_SOURCE_DIR}
-    PRIVATE
-        ${libdmenutools_BINARY_DIR}/dmenu
-)
--- a/libdmenutools/dmenu/dmenu.cpp	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-/*
- * dmenu.cpp -- dmenu utilities
- *
- * Copyright (c) 2018 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 <iostream>
-#include <sstream>
-
-#include <boost/filesystem.hpp>
-#include <boost/process.hpp>
-
-#include "dmenu.hpp"
-#include "sysconfig.hpp"
-#include "xdg.hpp"
-
-namespace fs = boost::filesystem;
-namespace proc = boost::process;
-
-namespace dmenu {
-
-namespace {
-
-std::string build_args(const std::vector<std::string>& args)
-{
-    std::ostringstream oss;
-
-    oss << "dmenu";
-
-    for (const auto& arg : args)
-        oss << " " << arg;
-
-    return oss.str();
-}
-
-std::string get_result(proc::ipstream& out, proc::child& child)
-{
-    std::string line;
-
-    if (child.running())
-        std::getline(out, line);
-        
-    child.wait();
-
-    return line;
-}
-
-} // !namespace
-
-std::string run(const std::vector<std::string>& args,
-                const std::vector<std::string>& lines)
-{
-    proc::opstream in;
-    proc::ipstream out;
-    proc::child child(build_args(args), proc::std_in < in, proc::std_out > out);
-
-    for (const auto& line : lines)
-        in << line << std::endl;
-
-    in.pipe().close();
-
-    return get_result(out, child);
-}
-
-ini::section config(const std::string& section)
-{
-    const auto path = xdg().config_home() + "/dmenutools.conf";
-
-    if (!fs::exists(path))
-        return ini::section(section);
-
-    return ini::read_file(path).get(section);
-}
-
-void error(const std::string& summary, const std::string& message)
-{
-#if defined(HAVE_NOTIFY)
-    static bool is_initialized;
-
-    if (!is_initialized) {
-        notify_init("dmenu");
-        is_initialized = true;
-    }
-
-    NotifyNotification* n = notify_notification_new(summary.c_str(), message.c_str(), nullptr);
-    GError* error = nullptr;
-
-    if (!notify_notification_show(n, &error))
-        std::cerr << error->message << std::endl;
-
-    g_free(error);
-#else
-    std::cerr << summary << ": " << message << std::endl;
-#endif
-}
-
-} // !dmenu
--- a/libdmenutools/dmenu/dmenu.hpp	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * dmenu.hpp -- dmenu utilities
- *
- * Copyright (c) 2018 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 DMENUTOOLS_DMENU_HPP
-#define DMENUTOOLS_DMENU_HPP
-
-/**
- * \file dmenu.hpp
- * \brief dmenu utilities.
- */
-
-#include <string>
-#include <vector>
-
-#include "ini.hpp"
-
-namespace dmenu {
-
-/**
- * Invoke dmenu with the given arguments.
- *
- * \param args the dmenu arguments
- * \param lines the stdin input for dmenu
- * \return the selected entry
- */
-std::string run(const std::vector<std::string>& args,
-                const std::vector<std::string>& lines);
-
-/**
- * Get the configuration section.
- *
- * \param section the desired section (e.g. background)
- * \return the section
- */
-ini::section config(const std::string& section);
-
-/**
- * Show a notification error.
- *
- * If notification support is disabled, error is printed to std::cerr.
- *
- * \param summary the summary
- * \param message the message content
- */
-void error(const std::string& summary, const std::string& message);
-
-} // !dmenu
-
-#endif // !DMENUTOOLS_DMENU_HPP
--- a/libdmenutools/dmenu/ini.cpp	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,423 +0,0 @@
-/*
- * ini.cpp -- extended .ini file parser
- *
- * Copyright (c) 2013-2018 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 <cctype>
-#include <cstring>
-#include <iostream>
-#include <iterator>
-#include <fstream>
-#include <sstream>
-#include <stdexcept>
-
-// for PathIsRelative.
-#if defined(_WIN32)
-#  include <Shlwapi.h>
-#endif
-
-#include "ini.hpp"
-
-namespace ini {
-
-namespace {
-
-using stream_iterator = std::istreambuf_iterator<char>;
-using token_iterator = std::vector<token>::const_iterator;
-
-inline bool is_absolute(const std::string& path) noexcept
-{
-#if defined(_WIN32)
-    return !PathIsRelative(path.c_str());
-#else
-    return path.size() > 0 && path[0] == '/';
-#endif
-}
-
-inline bool is_quote(char c) noexcept
-{
-    return c == '\'' || c == '"';
-}
-
-inline bool is_space(char c) noexcept
-{
-    // Custom version because std::isspace includes \n as space.
-    return c == ' ' || c == '\t';
-}
-
-inline bool is_list(char c) noexcept
-{
-    return c == '(' || c == ')' || c == ',';
-}
-
-inline bool is_reserved(char c) noexcept
-{
-    return is_list(c) || is_quote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '=';
-}
-
-void analyse_line(unsigned& line, unsigned& column, stream_iterator& it) noexcept
-{
-    assert(*it == '\n');
-
-    ++ line;
-    ++ it;
-    column = 0;
-}
-
-void analyse_comment(unsigned& column, stream_iterator& it, stream_iterator end) noexcept
-{
-    assert(*it == '#');
-
-    while (it != end && *it != '\n') {
-        ++ column;
-        ++ it;
-    }
-}
-
-void analyse_spaces(unsigned& column, stream_iterator& it, stream_iterator end) noexcept
-{
-    assert(is_space(*it));
-
-    while (it != end && is_space(*it)) {
-        ++ column;
-        ++ it;
-    }
-}
-
-void analyse_list(tokens& list, unsigned line, unsigned& column, stream_iterator& it) noexcept
-{
-    assert(is_list(*it));
-
-    switch (*it++) {
-    case '(':
-        list.emplace_back(token::list_begin, line, column++);
-        break;
-    case ')':
-        list.emplace_back(token::list_end, line, column++);
-        break;
-    case ',':
-        list.emplace_back(token::comma, line, column++);
-        break;
-    default:
-        break;
-    }
-}
-
-void analyse_section(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
-{
-    assert(*it == '[');
-
-    std::string value;
-    unsigned save = column;
-
-    // Read section name.
-    for (++it; it != end && *it != ']';) {
-        if (*it == '\n')
-            throw exception(line, column, "section not terminated, missing ']'");
-        if (is_reserved(*it))
-            throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'");
-
-        ++ column;
-        value += *it++;
-    }
-
-    if (it == end)
-        throw exception(line, column, "section name expected after '[', got <EOF>");
-    if (value.empty())
-        throw exception(line, column, "empty section name");
-
-    // Remove ']'.
-    ++ it;
-
-    list.emplace_back(token::section, line, save, std::move(value));
-}
-
-void analyse_assign(tokens& list, unsigned& line, unsigned& column, stream_iterator& it)
-{
-    assert(*it == '=');
-
-    list.push_back({ token::assign, line, column++ });
-    ++ it;
-}
-
-void analyse_quoted_word(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
-{
-    std::string value;
-    unsigned save = column;
-    char quote = *it++;
-
-    while (it != end && *it != quote) {
-        // TODO: escape sequence
-        ++ column;
-        value += *it++;
-    }
-
-    if (it == end)
-        throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>");
-
-    // Remove quote.
-    ++ it;
-
-    list.push_back({ token::quoted_word, line, save, std::move(value) });
-}
-
-void analyse_word(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
-{
-    assert(!is_reserved(*it));
-
-    std::string value;
-    unsigned save = column;
-
-    while (it != end && !std::isspace(*it) && !is_reserved(*it)) {
-        ++ column;
-        value += *it++;
-    }
-
-    list.push_back({ token::word, line, save, std::move(value) });
-}
-
-void analyse_include(tokens& list, unsigned& line, unsigned& column, stream_iterator& it, stream_iterator end)
-{
-    assert(*it == '@');
-
-    std::string include;
-    unsigned save = column;
-
-    // Read include.
-    ++ it;
-    while (it != end && !is_space(*it)) {
-        ++ column;
-        include += *it++;
-    }
-
-    if (include == "include")
-        list.push_back({ token::include, line, save });
-    else if (include == "tryinclude")
-        list.push_back({ token::tryinclude, line, save });
-    else
-        throw exception(line, column, "expected include or tryinclude after '@' token");
-}
-
-void parse_option_value_simple(option& option, token_iterator& it)
-{
-    assert(it->get_type() == token::word || it->get_type() == token::quoted_word);
-
-    option.push_back((it++)->get_value());
-}
-
-void parse_option_value_list(option& option, token_iterator& it, token_iterator end)
-{
-    assert(it->get_type() == token::list_begin);
-
-    token_iterator save = it++;
-
-    while (it != end && it->get_type() != token::list_end) {
-        switch (it->get_type()) {
-        case token::comma:
-            // Previous must be a word.
-            if (it[-1].get_type() != token::word && it[-1].get_type() != token::quoted_word)
-                throw exception(it->get_line(), it->get_column(), "unexpected comma after '" + it[-1].get_value() + "'");
-
-            ++ it;
-            break;
-        case token::word:
-        case token::quoted_word:
-            option.push_back((it++)->get_value());
-            break;
-        default:
-            throw exception(it->get_line(), it->get_column(), "unexpected '" + it[-1].get_value() + "' in list construct");
-            break;
-        }
-    }
-
-    if (it == end)
-        throw exception(save->get_line(), save->get_column(), "unterminated list construct");
-
-    // Remove ).
-    ++ it;
-}
-
-void parse_option(section& sc, token_iterator& it, token_iterator end)
-{
-    option option(it->get_value());
-    token_iterator save(it);
-
-    // No '=' or something else?
-    if (++it == end)
-        throw exception(save->get_line(), save->get_column(), "expected '=' assignment, got <EOF>");
-    if (it->get_type() != token::assign)
-        throw exception(it->get_line(), it->get_column(), "expected '=' assignment, got " + it->get_value());
-
-    // Empty options are allowed so just test for words.
-    if (++it != end) {
-        if (it->get_type() == token::word || it->get_type() == token::quoted_word)
-            parse_option_value_simple(option, it);
-        else if (it->get_type() == token::list_begin)
-            parse_option_value_list(option, it, end);
-    }
-
-    sc.push_back(std::move(option));
-}
-
-void parse_include(document& doc, const std::string& path, token_iterator& it, token_iterator end, bool required)
-{
-    token_iterator save(it);
-
-    if (++it == end)
-        throw exception(save->get_line(), save->get_column(), "expected file name after '@include' statement, got <EOF>");
-    if (it->get_type() != token::word && it->get_type() != token::quoted_word)
-        throw exception(it->get_line(), it->get_column(), "expected file name after '@include' statement, got " + it->get_value());
-
-    std::string value = (it++)->get_value();
-    std::string file;
-
-    if (!is_absolute(value)) {
-#if defined(_WIN32)
-        file = path + "\\" + value;
-#else
-        file = path + "/" + value;
-#endif
-    } else
-        file = value;
-
-    try {
-        /*
-         * If required is set to true, we have @include, otherwise the non-fatal
-         * @tryinclude keyword.
-         */
-        for (const auto& sc : read_file(file))
-            doc.push_back(sc);
-    } catch (...) {
-        if (required)
-            throw;
-    }
-}
-
-void parse_section(document& doc, token_iterator& it, token_iterator end)
-{
-    section sc(it->get_value());
-
-    // Skip [section].
-    ++ it;
-
-    // Read until next section.
-    while (it != end && it->get_type() != token::section) {
-        if (it->get_type() != token::word)
-            throw exception(it->get_line(), it->get_column(), "unexpected token '" + it->get_value() + "' in section definition");
-
-        parse_option(sc, it, end);
-    }
-
-    doc.push_back(std::move(sc));
-}
-
-} // !namespace
-
-tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end)
-{
-    tokens list;
-    unsigned line = 1;
-    unsigned column = 0;
-
-    while (it != end) {
-        if (*it == '\n')
-            analyse_line(line, column, it);
-        else if (*it == '#')
-            analyse_comment(column, it, end);
-        else if (*it == '[')
-            analyse_section(list, line, column, it, end);
-        else if (*it == '=')
-            analyse_assign(list, line, column, it);
-        else if (is_space(*it))
-            analyse_spaces(column, it, end);
-        else if (*it == '@')
-            analyse_include(list, line, column, it, end);
-        else if (is_quote(*it))
-            analyse_quoted_word(list, line, column, it, end);
-        else if (is_list(*it))
-            analyse_list(list, line, column, it);
-        else
-            analyse_word(list, line, column, it, end);
-    }
-
-    return list;
-}
-
-tokens analyse(std::istream& stream)
-{
-    return analyse(std::istreambuf_iterator<char>(stream), {});
-}
-
-document parse(const tokens& tokens, const std::string& path)
-{
-    document doc;
-    token_iterator it = tokens.cbegin();
-    token_iterator end = tokens.cend();
-
-    while (it != end) {
-        switch (it->get_type()) {
-        case token::include:
-            parse_include(doc, path, it, end, true);
-            break;
-        case token::tryinclude:
-            parse_include(doc, path, it, end, false);
-            break;
-        case token::section:
-            parse_section(doc, it, end);
-            break;
-        default:
-            throw exception(it->get_line(), it->get_column(), "unexpected '" + it->get_value() + "' on root document");
-        }
-    }
-
-    return doc;
-}
-
-document read_file(const std::string& filename)
-{
-    // Get parent path.
-    auto parent = filename;
-    auto pos = parent.find_last_of("/\\");
-
-    if (pos != std::string::npos)
-        parent.erase(pos);
-    else
-        parent = ".";
-
-    std::ifstream input(filename);
-
-    if (!input)
-        throw exception(0, 0, std::strerror(errno));
-
-    return parse(analyse(input), parent);
-}
-
-document read_string(const std::string& buffer)
-{
-    std::istringstream iss(buffer);
-
-    return parse(analyse(iss));
-}
-
-void dump(const tokens& tokens)
-{
-    for (const token& token: tokens) {
-        // TODO: add better description
-        std::cout << token.get_line() << ":" << token.get_column() << ": " << token.get_value() << std::endl;
-    }
-}
-
-} // !ini
--- a/libdmenutools/dmenu/ini.hpp	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,675 +0,0 @@
-/*
- * ini.hpp -- extended .ini file parser
- *
- * Copyright (c) 2013-2018 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 INI_HPP
-#define INI_HPP
-
-/**
- * \file ini.hpp
- * \brief Extended .ini file parser.
- * \author David Demelier <markand@malikania.fr>
- * \version 2.0.0
- */
-
-/**
- * \page Ini Ini
- * \brief Extended .ini file parser.
- *
- * ## Export macros
- *
- * You must define `INI_DLL` globally and `INI_BUILDING_DLL` when compiling the
- * library if you want a DLL, alternatively you can provide your own
- * `INI_EXPORT` macro instead.
- *
- *   - \subpage ini-syntax
- */
-
-/**
- * \page ini-syntax Syntax
- * \brief File syntax.
- *
- * The syntax is similar to most of `.ini` implementations as:
- *
- *   - a section is delimited by `[name]` can be redefined multiple times,
- *   - an option **must** always be defined in a section,
- *   - empty options must be surrounded by quotes,
- *   - lists can not include trailing commas,
- *   - include statements must always live at the beginning of files
- *     (in no sections),
- *   - comments start with # until the end of line,
- *   - options with spaces **must** use quotes.
- *
- * # Basic file
- *
- * ````ini
- * # This is a comment.
- * [section]
- * option1 = value1
- * option2 = "value 2 with spaces"    # comment is also allowed here
- * ````
- *
- * # Redefinition
- *
- * Sections can be redefined multiple times and are kept the order they are
- * seen.
- *
- * ````ini
- * [section]
- * value = "1"
- *
- * [section]
- * value = "2"
- * ````
- *
- * The ini::document object will contains two ini::section.
- *
- * # Lists
- *
- * Lists are defined using `()` and commas, like values, they may have quotes.
- *
- * ````ini
- * [section]
- * names = ( "x1", "x2" )
- *
- * # This is also allowed.
- * biglist = (
- *   "abc",
- *   "def"
- * )
- * ````
- *
- * # Include statement
- *
- * You can split a file into several pieces, if the include statement contains a
- * relative path, the path will be relative to the current file being parsed.
- *
- * You **must** use the include statement before any section.
- *
- * If the file contains spaces, use quotes.
- *
- * ````ini
- * # main.conf
- * @include "foo.conf"
- *
- * # foo.conf
- * [section]
- * option1 = value1
- * ````
- */
-
-#include <algorithm>
-#include <cassert>
-#include <exception>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-/**
- * \cond INI_HIDDEN_SYMBOLS
- */
-
-#if !defined(INI_EXPORT)
-#   if defined(INI_DLL)
-#       if defined(_WIN32)
-#           if defined(INI_BUILDING_DLL)
-#               define INI_EXPORT __declspec(dllexport)
-#           else
-#               define INI_EXPORT __declspec(dllimport)
-#           endif
-#       else
-#           define INI_EXPORT
-#       endif
-#   else
-#       define INI_EXPORT
-#   endif
-#endif
-
-/**
- * \endcond
- */
-
-/**
- * Namespace for ini related classes.
- */
-namespace ini {
-
-class document;
-
-/**
- * \brief exception in a file.
- */
-class exception : public std::exception {
-private:
-    unsigned line_;
-    unsigned column_;
-    std::string message_;
-
-public:
-    /**
-     * Constructor.
-     *
-     * \param line the line
-     * \param column the column
-     * \param msg the message
-     */
-    inline exception(unsigned line, unsigned column, std::string msg) noexcept
-        : line_(line)
-        , column_(column)
-        , message_(std::move(msg))
-    {
-    }
-
-    /**
-     * Get the line number.
-     *
-     * \return the line
-     */
-    inline unsigned get_line() const noexcept
-    {
-        return line_;
-    }
-
-    /**
-     * Get the column number.
-     *
-     * \return the column
-     */
-    inline unsigned get_column() const noexcept
-    {
-        return column_;
-    }
-
-    /**
-     * Return the raw exception message (no line and column shown).
-     *
-     * \return the exception message
-     */
-    const char* what() const noexcept override
-    {
-        return message_.c_str();
-    }
-};
-
-/**
- * \brief Describe a token read in the .ini source.
- *
- * This class can be used when you want to parse a .ini file yourself.
- *
- * \see analyse
- */
-class token {
-public:
-    /**
-     * \brief token type.
-     */
-    enum type {
-        include,                //!< include statement
-        tryinclude,             //!< tryinclude statement
-        section,                //!< [section]
-        word,                   //!< word without quotes
-        quoted_word,            //!< word with quotes
-        assign,                 //!< = assignment
-        list_begin,             //!< begin of list (
-        list_end,               //!< end of list )
-        comma                   //!< list separation
-    };
-
-private:
-    type type_;
-    unsigned line_;
-    unsigned column_;
-    std::string value_;
-
-public:
-    /**
-     * Construct a token.
-     *
-     * \param type the type
-     * \param line the line
-     * \param column the column
-     * \param value the value
-     */
-    token(type type, unsigned line, unsigned column, std::string value = "") noexcept
-        : type_(type)
-        , line_(line)
-        , column_(column)
-    {
-        switch (type) {
-        case include:
-            value_ = "@include";
-            break;
-        case tryinclude:
-            value_ = "@tryinclude";
-            break;
-        case section:
-        case word:
-        case quoted_word:
-            value_ = value;
-            break;
-        case assign:
-            value_ = "=";
-            break;
-        case list_begin:
-            value_ = "(";
-            break;
-        case list_end:
-            value_ = ")";
-            break;
-        case comma:
-            value_ = ",";
-            break;
-        default:
-            break;
-        }
-    }
-
-    /**
-     * Get the type.
-     *
-     * \return the type
-     */
-    inline type get_type() const noexcept
-    {
-        return type_;
-    }
-
-    /**
-     * Get the line.
-     *
-     * \return the line
-     */
-    inline unsigned get_line() const noexcept
-    {
-        return line_;
-    }
-
-    /**
-     * Get the column.
-     *
-     * \return the column
-     */
-    inline unsigned get_column() const noexcept
-    {
-        return column_;
-    }
-
-    /**
-     * Get the value. For words, quoted words and section, the value is the
-     * content. Otherwise it's the characters parsed.
-     *
-     * \return the value
-     */
-    inline const std::string& get_value() const noexcept
-    {
-        return value_;
-    }
-};
-
-/**
- * List of tokens in order they are analyzed.
- */
-using tokens = std::vector<token>;
-
-/**
- * \brief option definition.
- */
-class option : public std::vector<std::string> {
-private:
-    std::string key_;
-
-public:
-    /**
-     * Construct an empty option.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     */
-    inline option(std::string key) noexcept
-        : std::vector<std::string>()
-        , key_(std::move(key))
-    {
-        assert(!key_.empty());
-    }
-
-    /**
-     * Construct a single option.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     * \param value the value
-     */
-    inline option(std::string key, std::string value) noexcept
-        : key_(std::move(key))
-    {
-        assert(!key_.empty());
-
-        push_back(std::move(value));
-    }
-
-    /**
-     * Construct a list option.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     * \param values the values
-     */
-    inline option(std::string key, std::vector<std::string> values) noexcept
-        : std::vector<std::string>(std::move(values))
-        , key_(std::move(key))
-    {
-        assert(!key_.empty());
-    }
-
-    /**
-     * Get the option key.
-     *
-     * \return the key
-     */
-    inline const std::string& get_key() const noexcept
-    {
-        return key_;
-    }
-
-    /**
-     * Get the option value.
-     *
-     * \return the value
-     */
-    inline const std::string& get_value() const noexcept
-    {
-        static std::string dummy;
-
-        return empty() ? dummy : (*this)[0];
-    }
-};
-
-/**
- * \brief Section that contains one or more options.
- */
-class section : public std::vector<option> {
-private:
-    std::string key_;
-
-public:
-    /**
-     * Construct a section with its name.
-     *
-     * \pre key must not be empty
-     * \param key the key
-     */
-    inline section(std::string key) noexcept
-        : key_(std::move(key))
-    {
-        assert(!key_.empty());
-    }
-
-    /**
-     * Get the section key.
-     *
-     * \return the key
-     */
-    inline const std::string& get_key() const noexcept
-    {
-        return key_;
-    }
-
-    /**
-     * Check if the section contains a specific option.
-     *
-     * \param key the option key
-     * \return true if the option exists
-     */
-    inline bool contains(const std::string& key) const noexcept
-    {
-        return find(key) != end();
-    }
-
-    /**
-     * Find an option or return an empty one if not found.
-     *
-     * \param key the key
-     * \return the option or empty one if not found
-     */
-    inline option get(const std::string& key) const noexcept
-    {
-        auto it = find(key);
-
-        if (it == end())
-            return option(key);
-
-        return *it;
-    }
-
-    /**
-     * Find an option by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline iterator find(const std::string& key) noexcept
-    {
-        return std::find_if(begin(), end(), [&] (const auto& o) {
-            return o.get_key() == key;
-        });
-    }
-
-    /**
-     * Find an option by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline const_iterator find(const std::string& key) const noexcept
-    {
-        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
-            return o.get_key() == key;
-        });
-    }
-
-    /**
-     * Access an option at the specified key.
-     *
-     * \param key the key
-     * \return the option
-     * \pre contains(key) must return true
-     */
-    inline option& operator[](const std::string& key)
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param key the key
-     * \return the option
-     * \pre contains(key) must return true
-     */
-    inline const option& operator[](const std::string& key) const
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Inherited operators.
-     */
-    using std::vector<option>::operator[];
-};
-
-/**
- * \brief Ini document description.
- * \see read_file
- * \see read_string
- */
-class document : public std::vector<section> {
-public:
-    /**
-     * Check if a document has a specific section.
-     *
-     * \param key the key
-     * \return true if the document contains the section
-     */
-    inline bool contains(const std::string& key) const noexcept
-    {
-        return find(key) != end();
-    }
-
-    /**
-     * Find a section or return an empty one if not found.
-     *
-     * \param key the key
-     * \return the section or empty one if not found
-     */
-    inline section get(const std::string& key) const noexcept
-    {
-        auto it = find(key);
-
-        if (it == end())
-            return section(key);
-
-        return *it;
-    }
-
-    /**
-     * Find a section by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline iterator find(const std::string& key) noexcept
-    {
-        return std::find_if(begin(), end(), [&] (const auto& o) {
-            return o.get_key() == key;
-        });
-    }
-
-    /**
-     * Find a section by key and return an iterator.
-     *
-     * \param key the key
-     * \return the iterator or end() if not found
-     */
-    inline const_iterator find(const std::string& key) const noexcept
-    {
-        return std::find_if(cbegin(), cend(), [&] (const auto& o) {
-            return o.get_key() == key;
-        });
-    }
-
-    /**
-     * Access a section at the specified key.
-     *
-     * \param key the key
-     * \return the section
-     * \pre contains(key) must return true
-     */
-    inline section& operator[](const std::string& key)
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param key the key
-     * \return the section
-     * \pre contains(key) must return true
-     */
-    inline const section& operator[](const std::string& key) const
-    {
-        assert(contains(key));
-
-        return *find(key);
-    }
-
-    /**
-     * Inherited operators.
-     */
-    using std::vector<section>::operator[];
-};
-
-/**
- * Analyse a stream and detect potential syntax errors. This does not parse the
- * file like including other files in include statement.
- *
- * It does only analysis, for example if an option is defined under no section,
- * this does not trigger an exception while it's invalid.
- *
- * \param it the iterator
- * \param end where to stop
- * \return the list of tokens
- * \throws exception on errors
- */
-INI_EXPORT tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end);
-
-/**
- * Overloaded function for stream.
- *
- * \param stream the stream
- * \return the list of tokens
- * \throws exception on errors
- */
-INI_EXPORT tokens analyse(std::istream& stream);
-
-/**
- * Parse the produced tokens.
- *
- * \param tokens the tokens
- * \param path the parent path
- * \return the document
- * \throw exception on errors
- */
-INI_EXPORT document parse(const tokens& tokens, const std::string& path = ".");
-
-/**
- * Parse a file.
- *
- * \param filename the file name
- * \return the document
- * \throw exception on errors
- */
-INI_EXPORT document read_file(const std::string& filename);
-
-/**
- * Parse a string.
- *
- * If the string contains include statements, they are relative to the current
- * working directory.
- *
- * \param buffer the buffer
- * \return the document
- * \throw exception on exceptions
- */
-INI_EXPORT document read_string(const std::string& buffer);
-
-/**
- * Show all tokens and their description.
- *
- * \param tokens the tokens
- */
-INI_EXPORT void dump(const tokens& tokens);
-
-} // !ini
-
-#endif // !INI_HPP
--- a/libdmenutools/dmenu/join.hpp	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/*
- * join.hpp -- join lists into a string
- *
- * Copyright (c) 2013-2018 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.
- */
-
-/**
- * \file join.hpp
- * \brief Join function
- */
-
-#include <initializer_list>
-#include <string>
-#include <sstream>
-
-namespace dmenu {
-
-/**
- * Join values by a separator and return a string.
- *
- * \param first the first iterator
- * \param last the last iterator
- * \param delim the optional delimiter
- * \return the string
- */
-template <typename InputIt, typename DelimType = char>
-std::string join(InputIt first, InputIt last, DelimType delim = ':')
-{
-    std::ostringstream oss;
-
-    if (first != last) {
-        oss << *first;
-
-        while (++first != last)
-            oss << delim << *first;
-    }
-
-    return oss.str();
-}
-
-/**
- * Convenient overload.
- *
- * \param list the initializer list
- * \param delim the delimiter
- * \return the string
- */
-template <typename T, typename DelimType = char>
-inline std::string join(std::initializer_list<T> list, DelimType delim = ':')
-{
-    return join(list.begin(), list.end(), delim);
-}
-
-} // !dmenu
--- a/libdmenutools/dmenu/sysconfig.hpp.in	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-/*
- * sysconfig.hpp -- configuration file for dmenutools
- *
- * Copyright (c) 2013-2018 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 DMENUTOOLS_SYSCONFIG_HPP
-#define DMENUTOOLS_SYSCONFIG_HPP
-
-#cmakedefine HAVE_NOTIFY
-
-#if defined(HAVE_NOTIFY)
-#   include <libnotify/notify.h>
-#endif
-
-#endif // !DMENUTOOLS_SYSCONFIG_HPP
--- a/libdmenutools/dmenu/xdg.hpp	Tue May 01 12:16:12 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- * xdg.hpp -- XDG directory specifications
- *
- * Copyright (c) 2013-2018 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 DMENUTOOLS_XDG_HPP
-#define DMENUTOOLS_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>
-
-namespace dmenu {
-
-/**
- * \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 config_home_;
-    std::string data_home_;
-    std::string cache_home_;
-    std::string runtime_dir_;
-    std::vector<std::string> config_dirs_;
-    std::vector<std::string> data_dirs_;
-
-    inline bool is_absolute(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 (is_absolute(item))
-                elems.push_back(item);
-        }
-
-        return elems;
-    }
-
-    std::string env_or_home(const std::string& var, const std::string& repl) const
-    {
-        auto value = std::getenv(var.c_str());
-
-        if (value == nullptr || !is_absolute(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> list_or_defaults(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()
-    {
-        config_home_    = env_or_home("XDG_CONFIG_HOME", ".config");
-        data_home_      = env_or_home("XDG_DATA_HOME", ".local/share");
-        cache_home_     = env_or_home("XDG_CACHE_HOME", ".cache");
-
-        config_dirs_    = list_or_defaults("XDG_CONFIG_DIRS", { "/etc/xdg" });
-        data_dirs_      = list_or_defaults("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 && is_absolute(runtime))
-            runtime_dir_ = runtime;
-    }
-
-    /**
-     * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config
-     *
-     * \return the config directory
-     */
-    inline const std::string& config_home() const noexcept
-    {
-        return config_home_;
-    }
-
-    /**
-     * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share
-     *
-     * \return the data directory
-     */
-    inline const std::string& data_home() const noexcept
-    {
-        return data_home_;
-    }
-
-    /**
-     * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache
-     *
-     * \return the cache directory
-     */
-    inline const std::string& cache_home() const noexcept
-    {
-        return cache_home_;
-    }
-
-    /**
-     * Get the runtime directory.
-     *
-     * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty
-     * value is returned and the user is responsible of using something else.
-     *
-     * \return the runtime directory
-     */
-    inline const std::string& runtime_dir() const noexcept
-    {
-        return runtime_dir_;
-    }
-
-    /**
-     * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" }
-     *
-     * \return the list of config directories
-     */
-    inline const std::vector<std::string>& config_dirs() const noexcept
-    {
-        return config_dirs_;
-    }
-
-    /**
-     * 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>& data_dirs() const noexcept
-    {
-        return data_dirs_;
-    }
-};
-
-} // !dmenu
-
-#endif // !XDG_HPP

mercurial