changeset 514:16b9ebfd3f08

Irccd: get rid of fs.hpp, closes #594
author David Demelier <markand@malikania.fr>
date Tue, 24 Oct 2017 12:52:20 +0200
parents 928a40398dec
children 9c18bfff72ef
files MIGRATING.md cmake/function/IrccdDefineTest.cmake irccdctl/main.cpp libcommon/CMakeLists.txt libcommon/irccd/fs.cpp libcommon/irccd/fs.hpp libcommon/irccd/system.cpp libcommon/irccd/util.hpp libirccd-js/irccd/js_directory_module.cpp libirccd-js/irccd/js_file_module.cpp libirccd-js/irccd/js_plugin.cpp libirccd-test/irccd/js_test.hpp libirccd/irccd/config.cpp tests/CMakeLists.txt tests/js-directory/CMakeLists.txt tests/js-directory/main.cpp tests/js/main.cpp tests/util/main.cpp
diffstat 18 files changed, 284 insertions(+), 828 deletions(-) [+]
line wrap: on
line diff
--- a/MIGRATING.md	Mon Oct 23 21:30:17 2017 +0200
+++ b/MIGRATING.md	Tue Oct 24 12:52:20 2017 +0200
@@ -32,3 +32,7 @@
 
   - The method ElapsedTimer.reset has been removed, just use `start` instead
     when you want to accumulate time.
+
+#### Module Directory
+
+  - The property `Directory.count` has been removed.
--- a/cmake/function/IrccdDefineTest.cmake	Mon Oct 23 21:30:17 2017 +0200
+++ b/cmake/function/IrccdDefineTest.cmake	Tue Oct 24 12:52:20 2017 +0200
@@ -74,6 +74,7 @@
             CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
             CMAKE_CURRENT_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}"
             CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
+            TESTS_BINARY_DIR="${tests_BINARY_DIR}"
             SOURCEDIR="${CMAKE_CURRENT_SOURCE_DIR}"
             BINARYDIR="${CMAKE_CURRENT_BINARY_DIR}"
             IRCCD_TESTS_DIRECTORY="${CMAKE_BINARY_DIR}/tests"
--- a/irccdctl/main.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/irccdctl/main.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -24,7 +24,6 @@
 #include "alias.hpp"
 #include "cli.hpp"
 #include "client.hpp"
-#include "fs.hpp"
 #include "ini.hpp"
 #include "irccdctl.hpp"
 #include "logger.hpp"
--- a/libcommon/CMakeLists.txt	Mon Oct 23 21:30:17 2017 +0200
+++ b/libcommon/CMakeLists.txt	Tue Oct 24 12:52:20 2017 +0200
@@ -22,7 +22,6 @@
 
 set(
     HEADERS
-    ${libcommon_SOURCE_DIR}/irccd/fs.hpp
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
     ${libcommon_SOURCE_DIR}/irccd/logger.hpp
     ${libcommon_SOURCE_DIR}/irccd/net.hpp
@@ -35,7 +34,6 @@
 
 set(
     SOURCES
-    ${libcommon_SOURCE_DIR}/irccd/fs.cpp
     ${libcommon_SOURCE_DIR}/irccd/ini.cpp
     ${libcommon_SOURCE_DIR}/irccd/logger.cpp
     ${libcommon_SOURCE_DIR}/irccd/options.cpp
--- a/libcommon/irccd/fs.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,448 +0,0 @@
-/*
- * fs.cpp -- filesystem operations
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#if defined(_WIN32)
-#   if !defined(_CRT_SECURE_NO_WARNINGS)
-#       define _CRT_SECURE_NO_WARNINGS
-#   endif
-#   if !defined(WIN32_LEAN_AND_MEAN)
-#       define WIN32_LEAN_AND_MEAN
-#   endif
-#endif
-
-#include <algorithm>
-#include <cassert>
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <sstream>
-#include <stdexcept>
-
-#if defined(_WIN32)
-#   include <direct.h>
-#   include <windows.h>
-#   include <shlwapi.h>
-#else
-#   include <sys/types.h>
-#   include <dirent.h>
-#   include <unistd.h>
-#   include <climits>
-#endif
-
-#include "fs.hpp"
-
-namespace irccd {
-
-namespace fs {
-
-namespace {
-
-/*
- * error.
- * ------------------------------------------------------------------
- *
- * Function to retrieve system error in Windows API.
- */
-#if defined(_WIN32)
-
-std::string error()
-{
-    LPSTR error = nullptr;
-    std::string errmsg = "Unknown error";
-
-    FormatMessageA(
-        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
-        nullptr,
-        GetLastError(),
-        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-        (LPSTR)&error, 0, nullptr);
-
-    if (error) {
-        errmsg = std::string(error);
-        LocalFree(error);
-    }
-
-    return errmsg;
-}
-
-#endif
-
-/*
- * hasAccess.
- * ------------------------------------------------------------------
- *
- * Check if we have access to the file specified, mode is the same used as
- * std::fopen.
- */
-bool hasAccess(const std::string &path, const std::string &mode)
-{
-    assert(mode.length() == 1);
-    assert(mode[0] == 'r' || mode[0] == 'w');
-
-    auto fp = std::fopen(path.c_str(), mode.c_str());
-
-    if (fp == nullptr)
-        return false;
-
-    std::fclose(fp);
-
-    return true;
-}
-
-/*
- * typeOf.
- * ------------------------------------------------------------------
- *
- * Get the type of the specified file.
- *
- * Use GetFileAttributesA on Windows and stat if available.
- *
- * Receives the object as predicate parameter and return true on success.
- */
-#if defined(_WIN32)
-
-template <typename Predicate>
-bool typeOf(const std::string &path, Predicate &&predicate)
-{
-    DWORD result = GetFileAttributesA(path.c_str());
-
-    if (result == INVALID_FILE_ATTRIBUTES)
-        return false;
-
-    return predicate(result);
-}
-
-#elif defined(FS_HAVE_STAT)
-
-template <typename Predicate>
-bool typeOf(const std::string &path, Predicate &&predicate) noexcept
-{
-    struct stat st;
-
-    if (::stat(path.c_str(), &st) < 0)
-        return false;
-
-    return predicate(st);
-}
-
-#else
-
-template <typename Predicate>
-bool typeOf(const std::string &path, Predicate &&predicate) noexcept
-{
-    throw std::runtime_error(std::strerror(ENOSYS));
-}
-
-#endif
-
-} // !namespace
-
-/*
- * clean.
- * ------------------------------------------------------------------
- */
-std::string clean(std::string input)
-{
-    if (input.empty())
-        return input;
-
-    // First, remove any duplicates.
-    input.erase(std::unique(input.begin(), input.end(), [&] (char c1, char c2) {
-        return c1 == c2 && (c1 == '/' || c1 == '\\');
-    }), input.end());
-
-    // Add a trailing / or \\.
-    char c = input[input.length() - 1];
-
-    if (c != '/' && c != '\\')
-        input += separator();
-
-    // Now converts all / to \\ for Windows and the opposite for Unix.
-#if defined(_WIN32)
-    std::replace(input.begin(), input.end(), '/', '\\');
-#else
-    std::replace(input.begin(), input.end(), '\\', '/');
-#endif
-
-    return input;
-}
-
-/*
- * baseName.
- * ------------------------------------------------------------------
- */
-std::string baseName(std::string path)
-{
-    auto pos = path.find_last_of("\\/");
-
-    if (pos != std::string::npos)
-        path = path.substr(pos + 1);
-
-    return path;
-}
-
-/*
- * dirName.
- * ------------------------------------------------------------------
- */
-std::string dirName(std::string path)
-{
-    auto pos = path.find_last_of("\\/");
-
-    if (pos == std::string::npos)
-        path = ".";
-    else
-        path = path.substr(0, pos);
-
-    return path;
-}
-
-/*
- * isReadable.
- * ------------------------------------------------------------------
- */
-bool isReadable(const std::string &path) noexcept
-{
-    return hasAccess(path, "r");
-}
-
-/*
- * isWritable.
- * ------------------------------------------------------------------
- */
-bool isWritable(const std::string &path) noexcept
-{
-    return hasAccess(path, "w");
-}
-
-/*
- * isFile.
- * ------------------------------------------------------------------
- */
-bool isFile(const std::string &path)
-{
-    return typeOf(path, [] (const auto &object) {
-#if defined(_WIN32)
-        return (object & FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE;
-#elif defined(FS_HAVE_STAT)
-        return S_ISREG(object.st_mode);
-#endif
-    });
-}
-
-/*
- * isDirectory.
- * ------------------------------------------------------------------
- */
-bool isDirectory(const std::string &path)
-{
-    return typeOf(path, [] (const auto &object) {
-#if defined(_WIN32)
-        return (object & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
-#elif defined(FS_HAVE_STAT)
-        return S_ISDIR(object.st_mode);
-#endif
-    });
-}
-
-/*
- * stat.
- * ------------------------------------------------------------------
- */
-#if defined(FS_HAVE_STAT)
-
-struct stat stat(const std::string &path)
-{
-    struct stat st;
-
-    if (::stat(path.c_str(), &st) < 0)
-        throw std::runtime_error(std::strerror(errno));
-
-    return st;
-}
-
-#endif
-
-/*
- * readdir.
- * ------------------------------------------------------------------
- */
-std::vector<Entry> readdir(const std::string &path, int flags)
-{
-    std::vector<Entry> entries;
-
-#if defined(_WIN32)
-    std::ostringstream oss;
-    HANDLE handle;
-    WIN32_FIND_DATA fdata;
-
-    oss << path << "\\*";
-    handle = FindFirstFile(oss.str().c_str(), &fdata);
-
-    if (handle == nullptr)
-        throw std::runtime_error(error());
-
-    do {
-        Entry entry;
-
-        entry.name = fdata.cFileName;
-
-        if (entry.name == "." && !(flags & Dot))
-            continue;
-        if (entry.name == ".." && !(flags & DotDot))
-            continue;
-
-        switch (fdata.dwFileAttributes) {
-        case FILE_ATTRIBUTE_DIRECTORY:
-            entry.type = Entry::Dir;
-            break;
-        case FILE_ATTRIBUTE_NORMAL:
-            entry.type = Entry::File;
-            break;
-        case FILE_ATTRIBUTE_REPARSE_POINT:
-            entry.type = Entry::Link;
-            break;
-        default:
-            break;
-        }
-
-        entries.push_back(std::move(entry));
-    } while (FindNextFile(handle, &fdata) != 0);
-
-    FindClose(handle);
-#else
-    DIR *dp;
-    struct dirent *ent;
-
-    if ((dp = opendir(path.c_str())) == nullptr)
-        throw std::runtime_error(std::strerror(errno));
-
-    while ((ent = readdir(dp)) != nullptr) {
-        Entry entry;
-
-        entry.name = ent->d_name;
-        if (entry.name == "." && !(flags & Dot))
-            continue;
-        if (entry.name == ".." && !(flags & DotDot))
-            continue;
-
-        switch (ent->d_type) {
-        case DT_DIR:
-            entry.type = Entry::Dir;
-            break;
-        case DT_REG:
-            entry.type = Entry::File;
-            break;
-        case DT_LNK:
-            entry.type = Entry::Link;
-            break;
-        default:
-            break;
-        }
-
-        entries.push_back(std::move(entry));
-    }
-
-    closedir(dp);
-#endif
-
-    return entries;
-}
-
-/*
- * mkdir.
- * ------------------------------------------------------------------
- */
-void mkdir(const std::string &path, int mode)
-{
-    std::string::size_type next = 0;
-    std::string part;
-
-    for (;;) {
-        next = path.find_first_of("\\/", next);
-        part = path.substr(0, next);
-
-        if (!part.empty()) {
-#if defined(_WIN32)
-            (void)mode;
-
-            if (::_mkdir(part.c_str()) < 0 && errno != EEXIST)
-                throw std::runtime_error(std::strerror(errno));
-#else
-            if (::mkdir(part.c_str(), mode) < 0 && errno != EEXIST)
-                throw std::runtime_error(std::strerror(errno));
-#endif
-        }
-
-        if (next++ == std::string::npos)
-            break;
-    }
-}
-
-/*
- * rmdir.
- * ------------------------------------------------------------------
- */
-void rmdir(const std::string &base) noexcept
-{
-    try {
-        for (const auto &entry : readdir(base)) {
-            std::string path = base + separator() + entry.name;
-
-            if (entry.type == Entry::Dir)
-                rmdir(path);
-            else
-                ::remove(path.c_str());
-        }
-    } catch (...) {
-        // Silently discard to remove as much as possible.
-    }
-
-#if defined(_WIN32)
-    ::RemoveDirectoryA(base.c_str());
-#else
-    ::remove(base.c_str());
-#endif
-}
-
-/*
- * cwd.
- * ------------------------------------------------------------------
- */
-std::string cwd()
-{
-#if defined(_WIN32)
-    char path[MAX_PATH];
-
-    if (!::GetCurrentDirectoryA(sizeof (path), path))
-        throw std::runtime_error("failed to get current working directory");
-
-    return path;
-#else
-    char path[PATH_MAX];
-
-    if (::getcwd(path, sizeof (path)) == nullptr)
-        throw std::runtime_error{std::strerror(errno)};
-
-    return path;
-#endif
-}
-
-} // !irccd
-
-} // !fs
--- a/libcommon/irccd/fs.hpp	Mon Oct 23 21:30:17 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,277 +0,0 @@
-/*
- * fs.hpp -- filesystem operations
- *
- * Copyright (c) 2013-2017 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 IRCCD_FS_HPP
-#define IRCCD_FS_HPP
-
-/**
- * \file fs.hpp
- * \brief Filesystem operations made easy.
- */
-
-/**
- * \cond FS_HIDDEN_SYMBOLS
- */
-
-#if !defined(FS_HAVE_STAT)
-#   if defined(_WIN32)
-#       define FS_HAVE_STAT
-#   elif defined(__linux__)
-#       define FS_HAVE_STAT
-#   elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
-#       define FS_HAVE_STAT
-#   elif defined(__APPLE__)
-#       define FS_HAVE_STAT
-#   endif
-#endif
-
-/**
- * \endcond
- */
-
-#if defined(FS_HAVE_STAT)
-#   include <sys/stat.h>
-#endif
-
-#include <regex>
-#include <string>
-#include <vector>
-
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-/**
- * \brief Filesystem namespace.
- */
-namespace fs {
-
-/**
- * \enum Flags
- * \brief Flags for readdir.
- */
-enum Flags {
-    Dot     = (1 << 0),         //!< if set, also lists "."
-    DotDot  = (1 << 1)          //!< if set, also lists ".."
-};
-
-/**
- * \brief Entry in the directory list.
- */
-class Entry {
-public:
-    /**
-     * \brief Describe the type of an entry
-     */
-    enum Type : char {
-        Unknown,                //!< File type is unknown,
-        File,                   //!< File is regular type,
-        Dir,                    //!< File is directory,
-        Link                    //!< File is link
-    };
-
-    std::string name;           //!< name of entry (base name)
-    Type type{Unknown};         //!< type of file
-};
-
-/**
- * Check if two entries are identical.
- *
- * \param e1 the first entry
- * \param e2 the second entry
- * \return true if they are identical
- */
-inline bool operator==(const Entry &e1, const Entry &e2) noexcept
-{
-    return e1.name == e2.name && e1.type == e2.type;
-}
-
-/**
- * Check if two entries are different.
- *
- * \param e1 the first entry
- * \param e2 the second entry
- * \return true if they are different
- */
-inline bool operator!=(const Entry &e1, const Entry &e2) noexcept
-{
-    return !(e1 == e2);
-}
-
-/**
- * Get the separator for that system.
- *
- * \return \ on Windows and / otherwise
- */
-inline char separator() noexcept
-{
-#if defined(_WIN32)
-    return '\\';
-#else
-    return '/';
-#endif
-}
-
-/**
- * Clean a path by removing any extra / or \ and add a trailing one.
- *
- * \param path the path
- * \return the updated path
- */
-IRCCD_EXPORT std::string clean(std::string path);
-
-/**
- * Get the base name from a path.
- *
- * Example, baseName("/etc/foo.conf") // foo.conf
- *
- * \param path the path
- * \return the base name
- */
-IRCCD_EXPORT std::string baseName(std::string path);
-
-/**
- * Get the parent directory from a path.
- *
- * Example, dirName("/etc/foo.conf") // /etc
- *
- * \param path the path
- * \return the parent directory
- */
-IRCCD_EXPORT std::string dirName(std::string path);
-
-#if defined(FS_HAVE_STAT)
-
-/**
- * Get stat information.
- *
- * \param path the path
- * \return the stat information
- * \throw std::runtime_error on failure
- */
-IRCCD_EXPORT struct stat stat(const std::string &path);
-
-#endif // !HAVE_STAT
-
-/**
- * Check if the file is readable.
- *
- * \param path the path
- * \return true if has read access
- */
-IRCCD_EXPORT bool isReadable(const std::string &path) noexcept;
-
-/**
- * Check if the file is writable.
- *
- * \param path the path
- * \return true if has write access
- */
-IRCCD_EXPORT bool isWritable(const std::string &path) noexcept;
-
-/**
- * Read a directory and return a list of entries (not recursive).
- *
- * \param path the directory path
- * \param flags the optional flags (see Flags)
- * \return the list of entries
- * \throw std::runtime_error on failure
- */
-IRCCD_EXPORT std::vector<Entry> readdir(const std::string &path, int flags = 0);
-
-/**
- * Search an item recursively.
- *
- * The predicate must have the following signature:
- *  void f(const std::string &base, const Entry &entry)
- *
- * Where:
- *   - base is the current parent directory in the tree
- *   - entry is the current entry
- *
- * \param base the base directory
- * \param predicate the predicate
- * \return the full path name to the file or empty string if never found
- * \throw std::runtime_error on read errors
- */
-template <typename Predicate>
-std::string findIf(const std::string &base, Predicate &&predicate)
-{
-    /*
-     * Do not go deeply to the tree before testing all files in the current
-     * directory for performances reasons, we iterate this directory to search
-     * for the entry name and iterate again over all sub directories if not
-     * found.
-     */
-    std::string path;
-    std::vector<Entry> entries = readdir(base);
-
-    for (const auto &entry : entries) {
-        if (predicate(base, entry)) {
-            path = base + separator() + entry.name;
-            break;
-        }
-    }
-
-    if (!path.empty())
-        return path;
-
-    for (const auto &entry : entries) {
-        if (entry.type != Entry::Dir)
-            continue;
-
-        path = findIf(base + separator() + entry.name, std::forward<Predicate>(predicate));
-
-        if (!path.empty())
-            break;
-    }
-
-    return path;
-}
-
-/**
- * Find a file by name recursively.
- *
- * \param base the base directory
- * \param name the file name
- * \return the full path name to the file or empty string if never found
- * \throw std::runtime_error on read errors
- */
-inline std::string find(const std::string &base, const std::string &name)
-{
-    return findIf(base, [&] (const auto &, const auto &entry) { return entry.name == name; });
-}
-
-/**
- * Overload by regular expression.
- *
- * \param base the base directory
- * \param regex the regular expression
- * \return the full path name to the file or empty string if never found
- * \throw std::runtime_error on read errors
- */
-inline std::string find(const std::string &base, const std::regex &regex)
-{
-    return findIf(base, [&] (const auto &, const auto &entry) { return std::regex_match(entry.name, regex); });
-}
-
-} // !fs
-
-} // !irccd
-
-#endif // !IRCCD_FS_HPP
--- a/libcommon/irccd/system.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libcommon/irccd/system.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -100,7 +100,6 @@
 #   include <unistd.h>
 #endif
 
-#include "fs.hpp"
 #include "logger.hpp"
 #include "system.hpp"
 #include "util.hpp"
--- a/libcommon/irccd/util.hpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libcommon/irccd/util.hpp	Tue Oct 24 12:52:20 2017 +0200
@@ -37,6 +37,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <boost/filesystem.hpp>
 #include <boost/format.hpp>
 
 #include <json.hpp>
@@ -328,7 +329,7 @@
  * \return true if integer
  */
 IRCCD_EXPORT bool is_int(const std::string& value, int base = 10) noexcept;
- 
+
 /**
  * Check if the string is real.
  *
@@ -848,6 +849,101 @@
 
 } // !poller
 
+namespace fs {
+
+/**
+ * Get the base name from a path.
+ *
+ * Example, baseName("/etc/foo.conf") // foo.conf
+ *
+ * \param path the path
+ * \return the base name
+ */
+inline std::string base_name(const std::string& path)
+{
+    return boost::filesystem::path(path).filename().string();
+}
+
+/**
+ * Get the parent directory from a path.
+ *
+ * Example, dirName("/etc/foo.conf") // /etc
+ *
+ * \param path the path
+ * \return the parent directory
+ */
+inline std::string dir_name(std::string path)
+{
+    return boost::filesystem::path(path).parent_path().string();
+}
+
+/**
+ * Search an item recursively.
+ *
+ * The predicate must have the following signature:
+ *  void f(const boost::filesystem::directory_entry& entry)
+ *
+ * Where:
+ *   - base is the current parent directory in the tree
+ *   - entry is the current entry
+ *
+ * \param base the base directory
+ * \param predicate the predicate
+ * \param recursive true to do recursive search
+ * \return the full path name to the file or empty string if never found
+ * \throw std::runtime_error on read errors
+ */
+template <typename Predicate>
+std::string find_if(const std::string& base, bool recursive, Predicate&& predicate)
+{
+    auto find = [&] (auto it) -> std::string {
+        for (const auto& entry : it)
+            if (predicate(entry))
+                return entry.path().string();
+
+        return "";
+    };
+
+    if (recursive)
+        return find(boost::filesystem::recursive_directory_iterator(base));
+
+    return find(boost::filesystem::directory_iterator(base));
+}
+
+/**
+ * Find a file by name recursively.
+ *
+ * \param base the base directory
+ * \param name the file name
+ * \param recursive true to do recursive search
+ * \return the full path name to the file or empty string if never found
+ * \throw std::runtime_error on read errors
+ */
+inline std::string find(const std::string& base, const std::string& name, bool recursive = false)
+{
+    return find_if(base, recursive, [&] (const auto& entry) {
+        return entry.path().filename().string() == name;
+    });
+}
+
+/**
+ * Overload by regular expression.
+ *
+ * \param base the base directory
+ * \param regex the regular expression
+ * \param recursive true to do recursive search
+ * \return the full path name to the file or empty string if never found
+ * \throw std::runtime_error on read errors
+ */
+inline std::string find(const std::string& base, const std::regex& regex, bool recursive = false)
+{
+    return find_if(base, recursive, [&] (const auto& entry) {
+        return std::regex_match(entry.path().filename().string(), regex);
+    });
+}
+
+} // !fs
+
 } // !util
 
 } // !irccd
--- a/libirccd-js/irccd/js_directory_module.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libirccd-js/irccd/js_directory_module.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -27,12 +27,13 @@
 #include <boost/filesystem.hpp>
 
 #include "duktape.hpp"
-#include "fs.hpp"
 #include "js_directory_module.hpp"
 #include "js_irccd_module.hpp"
 #include "js_plugin.hpp"
 #include "sysconfig.hpp"
 
+namespace fs = boost::filesystem;
+
 namespace irccd {
 
 namespace {
@@ -56,68 +57,6 @@
 }
 
 /*
- * Find an entry recursively (or not) in a directory using a predicate which can
- * be used to test for regular expression, equality.
- *
- * Do not use this function directly, use:
- *
- * - find_name
- * - find_regex
- */
-template <typename Pred>
-std::string find_path(const std::string& base, bool recursive, Pred&& pred)
-{
-    /*
-     * For performance reason, we first iterate over all entries that are
-     * not directories to avoid going deeper recursively if the requested
-     * file is in the current directory.
-     */
-    auto entries = fs::readdir(base);
-
-    for (const auto& entry : entries)
-        if (entry.type != fs::Entry::Dir && pred(entry.name))
-            return base + entry.name;
-
-    if (!recursive)
-        return "";
-
-    for (const auto& entry : entries) {
-        if (entry.type == fs::Entry::Dir) {
-            std::string next = base + entry.name + fs::separator();
-            std::string path = find_path(next, true, pred);
-
-            if (!path.empty())
-                return path;
-        }
-    }
-
-    return "";
-}
-
-/*
- * Helper for finding by equality.
- */
-std::string find_name(std::string base, const std::string& pattern, bool recursive)
-{
-    return find_path(base, recursive, [&] (const auto& entryname) -> bool {
-        return pattern == entryname;
-    });
-}
-
-/*
- * Helper for finding by regular expression
- */
-std::string find_regex(const std::string& base, std::string pattern, bool recursive)
-{
-    std::regex regexp(pattern, std::regex::ECMAScript);
-    std::smatch smatch;
-
-    return find_path(base, recursive, [&] (const auto& entryname) -> bool {
-        return std::regex_match(entryname, smatch, regexp);
-    });
-}
-
-/*
  * Generic find function for:
  *
  * - Directory.find
@@ -132,7 +71,7 @@
         std::string path;
 
         if (duk_is_string(ctx, pattern_index))
-            path = find_name(base, duk_get_string(ctx, pattern_index), recursive);
+            path = util::fs::find(base, dukx_get_std_string(ctx, pattern_index), recursive);
         else {
             // Check if it's a valid RegExp object.
             duk_get_global_string(ctx, "RegExp");
@@ -144,7 +83,7 @@
                 auto pattern = duk_to_string(ctx, -1);
                 duk_pop(ctx);
 
-                path = find_regex(base, pattern, recursive);
+                path = util::fs::find(base, std::regex(pattern), recursive);
             } else
                 duk_error(ctx, DUK_ERR_TYPE_ERROR, "pattern must be a string or a regex expression");
         }
@@ -230,14 +169,13 @@
  */
 
 /*
- * Function: irccd.Directory(path, flags) [constructor]
+ * Function: Irccd.Directory(path) [constructor]
  * --------------------------------------------------------
  *
  * Opens and read the directory at the specified path.
  *
  * Arguments:
  *   - path, the path to the directory,
- *   - flags, the optional flags (default: 0).
  * Throws:
  *   - Any exception on error
  */
@@ -248,33 +186,32 @@
 
     try {
         auto path = duk_require_string(ctx, 0);
-        auto flags = duk_get_uint(ctx, 1);
 
         if (!boost::filesystem::is_directory(path))
             dukx_throw(ctx, system_error(EINVAL, "not a directory"));
 
-        auto list = fs::readdir(path, flags);
+        duk_push_this(ctx);
 
-        duk_push_this(ctx);
-        duk_push_string(ctx, "count");
-        duk_push_int(ctx, list.size());
-        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-        duk_push_string(ctx, "path");
-        dukx_push_std_string(ctx, path);
-        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+        // 'entries' property.
         duk_push_string(ctx, "entries");
         duk_push_array(ctx);
 
-        for (unsigned i = 0; i < list.size(); ++i) {
+        unsigned i = 0;
+        for (const auto& entry : boost::filesystem::directory_iterator(path)) {
             duk_push_object(ctx);
-            dukx_push_std_string(ctx, list[i].name);
+            dukx_push_std_string(ctx, entry.path().filename().string());
             duk_put_prop_string(ctx, -2, "name");
-            duk_push_int(ctx, list[i].type);
+            duk_push_int(ctx, entry.status().type());
             duk_put_prop_string(ctx, -2, "type");
-            duk_put_prop_index(ctx, -2, i);
+            duk_put_prop_index(ctx, -2, i++);
         }
 
         duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+
+        // 'path' property.
+        duk_push_string(ctx, "path");
+        dukx_push_std_string(ctx, path);
+        duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
     } catch (const std::exception& ex) {
         dukx_throw(ctx, system_error(errno, ex.what()));
     }
@@ -348,13 +285,15 @@
 };
 
 const duk_number_list_entry constants[] = {
-    { "Dot",            static_cast<int>(fs::Dot)               },
-    { "DotDot",         static_cast<int>(fs::DotDot)            },
-    { "TypeUnknown",    static_cast<int>(fs::Entry::Unknown)    },
-    { "TypeDir",        static_cast<int>(fs::Entry::Dir)        },
-    { "TypeFile",       static_cast<int>(fs::Entry::File)       },
-    { "TypeLink",       static_cast<int>(fs::Entry::Link)       },
-    { nullptr,          0                                       }
+    { "TypeFile",       static_cast<int>(fs::regular_file)    },
+    { "TypeDir",        static_cast<int>(fs::directory_file)  },
+    { "TypeLink",       static_cast<int>(fs::symlink_file)    },
+    { "TypeBlock",      static_cast<int>(fs::block_file)      },
+    { "TypeCharacter",  static_cast<int>(fs::character_file)  },
+    { "TypeFifo",       static_cast<int>(fs::fifo_file)       },
+    { "TypeSocket",     static_cast<int>(fs::socket_file)     },
+    { "TypeUnknown",    static_cast<int>(fs::type_unknown)    },
+    { nullptr,          0                                           }
 };
 
 } // !namespace
@@ -372,8 +311,15 @@
     duk_push_c_function(plugin->context(), constructor, 2);
     duk_put_number_list(plugin->context(), -1, constants);
     duk_put_function_list(plugin->context(), -1, functions);
-    dukx_push_std_string(plugin->context(), std::string{fs::separator()});
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+    duk_push_string(plugin->context(), "\\");
+#else
+    duk_push_string(plugin->context(), "/");
+#endif
+
     duk_put_prop_string(plugin->context(), -2, "separator");
+
     duk_push_object(plugin->context());
     duk_put_function_list(plugin->context(), -1, methods);
     duk_put_prop_string(plugin->context(), -2, "prototype");
--- a/libirccd-js/irccd/js_file_module.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libirccd-js/irccd/js_file_module.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -31,7 +31,6 @@
 #  include <sys/stat.h>
 #endif
 
-#include <irccd/fs.hpp>
 #include <irccd/js_plugin.hpp>
 
 #include "js_file_module.hpp"
@@ -153,7 +152,7 @@
  */
 duk_ret_t method_basename(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, fs::baseName(self(ctx)->path()));
+    dukx_push_std_string(ctx, util::fs::base_name(self(ctx)->path()));
 
     return 1;
 }
@@ -182,7 +181,7 @@
  */
 duk_ret_t method_dirname(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, fs::dirName(self(ctx)->path()));
+    dukx_push_std_string(ctx, util::fs::dir_name(self(ctx)->path()));
 
     return 1;
 }
@@ -519,7 +518,7 @@
  */
 duk_ret_t function_basename(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, fs::baseName(duk_require_string(ctx, 0)));
+    dukx_push_std_string(ctx, util::fs::base_name(duk_require_string(ctx, 0)));
 
     return 1;
 }
@@ -537,7 +536,7 @@
  */
 duk_ret_t function_dirname(duk_context* ctx)
 {
-    dukx_push_std_string(ctx, fs::dirName(duk_require_string(ctx, 0)));
+    dukx_push_std_string(ctx, util::fs::dir_name(duk_require_string(ctx, 0)));
 
     return 1;
 }
--- a/libirccd-js/irccd/js_plugin.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libirccd-js/irccd/js_plugin.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -26,7 +26,6 @@
 #  include <cstring>
 #endif
 
-#include "fs.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "js_plugin_module.hpp"
--- a/libirccd-test/irccd/js_test.hpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libirccd-test/irccd/js_test.hpp	Tue Oct 24 12:52:20 2017 +0200
@@ -70,6 +70,10 @@
         add<Modules...>();
 
         plugin_->on_load(irccd_);
+
+        // Add some CMake variables.
+        duk_push_string(plugin_->context(), TESTS_BINARY_DIR);
+        duk_put_global_string(plugin_->context(), "TESTS_BINARY_DIR");
     }
 };
 
--- a/libirccd/irccd/config.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/libirccd/irccd/config.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -21,7 +21,6 @@
 #include <boost/filesystem.hpp>
 
 #include "config.hpp"
-#include "fs.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "plugin.hpp"
--- a/tests/CMakeLists.txt	Mon Oct 23 21:30:17 2017 +0200
+++ b/tests/CMakeLists.txt	Tue Oct 24 12:52:20 2017 +0200
@@ -20,6 +20,13 @@
 project(tests)
 
 if (WITH_TESTS)
+    # Create some hierarchy.
+    file(MAKE_DIRECTORY ${tests_BINARY_DIR}/root)
+    file(MAKE_DIRECTORY ${tests_BINARY_DIR}/root/level-a)
+    file(MAKE_DIRECTORY ${tests_BINARY_DIR}/root/level-a/level-1)
+    file(WRITE ${tests_BINARY_DIR}/root/file-1.txt "\n")
+    file(WRITE ${tests_BINARY_DIR}/root/level-a/level-1/file-2.txt)
+
     add_subdirectory(cmd-plugin-config)
 #    add_subdirectory(cmd-plugin-info)
 #    add_subdirectory(cmd-plugin-list)
@@ -63,6 +70,7 @@
     if (HAVE_JS)
         add_subdirectory(js)
         add_subdirectory(js-elapsedtimer)
+        add_subdirectory(js-directory)
         add_subdirectory(js-file)
         add_subdirectory(js-irccd)
         add_subdirectory(js-logger)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/js-directory/CMakeLists.txt	Tue Oct 24 12:52:20 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 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.
+#
+
+irccd_define_test(
+    NAME js-directory
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/js-directory/main.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -0,0 +1,51 @@
+/*
+ * main.cpp -- test Irccd.Directory API
+ *
+ * Copyright (c) 2013-2017 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.
+ */
+
+#define BOOST_TEST_MODULE "Directory Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/js_directory_module.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+class directory_test : public js_test<js_directory_module> {
+public:
+};
+
+BOOST_FIXTURE_TEST_SUITE(js_directory_suite, directory_test)
+
+BOOST_AUTO_TEST_CASE(constructor)
+{
+    const std::string script(
+        "d = new Irccd.Directory(TESTS_BINARY_DIR + \"/level-1\");"
+        "p = d.path;"
+        "e = d.entries;"
+    );
+
+    if (duk_peval_string(plugin_->context(), script.c_str()) != 0)
+        throw dukx_exception(plugin_->context(), -1);
+
+    duk_get_global_string(plugin_->context(), "l");
+    BOOST_TEST(duk_get_int(plugin_->context(), -1) == 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- a/tests/js/main.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/tests/js/main.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -20,7 +20,8 @@
 #include <boost/test/unit_test.hpp>
 
 #include <duktape.hpp>
-#include <fs.hpp>
+
+#include <irccd/util.hpp>
 
 namespace irccd {
 
@@ -56,7 +57,7 @@
         dukx_peval_file(ctx_, SOURCEDIR "/syntax-error.js");
     } catch (const Exception& ex) {
         BOOST_REQUIRE_EQUAL("SyntaxError", ex.name);
-        BOOST_REQUIRE_EQUAL("syntax-error.js", fs::baseName(ex.fileName));
+        BOOST_REQUIRE_EQUAL("syntax-error.js", util::fs::base_name(ex.fileName));
         BOOST_REQUIRE_EQUAL(6, ex.lineNumber);
         BOOST_REQUIRE_EQUAL("empty expression not allowed (line 6)", ex.message);
     }
--- a/tests/util/main.cpp	Mon Oct 23 21:30:17 2017 +0200
+++ b/tests/util/main.cpp	Tue Oct 24 12:52:20 2017 +0200
@@ -271,7 +271,7 @@
     std::string result = util::strip(value);
 
     BOOST_REQUIRE_EQUAL("", result);
-} 
+}
 
 BOOST_AUTO_TEST_SUITE_END()
 
@@ -460,4 +460,58 @@
 
 BOOST_AUTO_TEST_SUITE_END()
 
+/*
+ * util::fs::find function (name)
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(fs_find_name)
+
+BOOST_AUTO_TEST_CASE(not_recursive)
+{
+    auto file1 = util::fs::find(CMAKE_CURRENT_BINARY_DIR "/root", "file-1.txt", false);
+    auto file2 = util::fs::find(CMAKE_CURRENT_BINARY_DIR "/root", "file-2.txt", false);
+
+    BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
+    BOOST_TEST(file2.empty());
+}
+
+BOOST_AUTO_TEST_CASE(recursive)
+{
+    auto file1 = util::fs::find(CMAKE_CURRENT_BINARY_DIR "/root", "file-1.txt", true);
+    auto file2 = util::fs::find(CMAKE_CURRENT_BINARY_DIR "/root", "file-2.txt", true);
+
+    BOOST_TEST(file1.find("file-1.txt") != std::string::npos);
+    BOOST_TEST(file2.find("file-2.txt") != std::string::npos);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+/*
+ * util::fs::find function (regex)
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_SUITE(fs_find_regex)
+
+BOOST_AUTO_TEST_CASE(not_recursive)
+{
+    const std::regex regex("file-[12]\\.txt");
+
+    auto file = util::fs::find(TESTS_BINARY_DIR "/root", regex, false);
+
+    BOOST_TEST(file.find("file-1.txt") != std::string::npos);
+}
+
+BOOST_AUTO_TEST_CASE(recursive)
+{
+    const std::regex regex("file-[12]\\.txt");
+
+    auto file = util::fs::find(TESTS_BINARY_DIR "/root/level-a", regex, true);
+
+    BOOST_TEST(file.find("file-2.txt") != std::string::npos);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 } // !irccd