changeset 519:d6dad57e9e6b

Fs: import
author David Demelier <markand@malikania.fr>
date Wed, 01 Jun 2016 16:35:01 +0200
parents 78f296a7b2e5
children b698e591b43a
files CMakeLists.txt modules/fs/CMakeLists.txt modules/fs/doc/mainpage.cpp modules/fs/fs.cpp modules/fs/fs.hpp modules/fs/test/main.cpp
diffstat 6 files changed, 1151 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Jun 01 16:26:20 2016 +0200
+++ b/CMakeLists.txt	Wed Jun 01 16:35:01 2016 +0200
@@ -44,4 +44,5 @@
 
 add_subdirectory(modules/base64)
 add_subdirectory(modules/dynlib)
+add_subdirectory(modules/fs)
 add_subdirectory(modules/options)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fs/CMakeLists.txt	Wed Jun 01 16:35:01 2016 +0200
@@ -0,0 +1,32 @@
+#
+# CMakeLists.txt -- code building for common code
+#
+# Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+#
+# Create fake hierarchy for tests
+#
+file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/root)
+file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/root/level-1)
+file(WRITE ${CMAKE_BINARY_DIR}/root/file-1.txt "Hello")
+file(WRITE ${CMAKE_BINARY_DIR}/root/level-1/file-2.txt "World")
+
+code_define_module(
+	NAME fs
+	SOURCES fs.cpp fs.hpp
+	LIBRARIES $<$<BOOL:${WIN32}>:shlwapi>
+	FLAGS DIRECTORY=\"${CMAKE_BINARY_DIR}/root/\"
+)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fs/doc/mainpage.cpp	Wed Jun 01 16:35:01 2016 +0200
@@ -0,0 +1,67 @@
+/**
+ * \mainpage
+ *
+ * Welcome to the fs library.
+ *
+ * ## Introduction
+ *
+ * Various free functions for filesystem operations. No dependencies.
+ *
+ * Including:
+ *
+ *   - Directory manipulation,
+ *   - Basic path management (clean, baseName, dirName),
+ *   - Checking access to files,
+ *   - Convenient stat wrapper.
+ *
+ * ## Operating system support
+ *
+ * - Linux,
+ * - All BSD variants,
+ * - Windows (must link to shlwapi library).
+ *
+ * ## Installation
+ *
+ * Just copy the two files fs.hpp and fs.cpp and add them to your project.
+ *
+ * ## Overview
+ *
+ * ### Reading a directory
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "fs.hpp"
+ *
+ * int main()
+ * {
+ * 	try {
+ * 		for (const fs::Entry &e : fs::readdir("jokes"))
+ * 			std::cout << "entry: " << e.name << std::endl;
+ * 	} catch (const std::exception &error) {
+ * 		std::cerr << error.what() << std::endl;
+ * 	}
+ *
+ * 	return 0;
+ * }
+ * ````
+ *
+ * ### Creating a directory
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "fs.hpp"
+ *
+ * int main()
+ * {
+ * 	try {
+ * 		fs::mkdir("tmp");
+ * 	} catch (const std::exception &error) {
+ * 		std::cerr << error.what() << std::endl;
+ * 	}
+ * 
+ * 	return 0;
+ * }
+ * ````
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fs/fs.cpp	Wed Jun 01 16:35:01 2016 +0200
@@ -0,0 +1,493 @@
+/*
+ * fs.cpp -- filesystem operations
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if defined(_WIN32)
+#  define _CRT_SECURE_NO_WARNINGS
+#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>
+#endif
+
+#include "fs.hpp"
+
+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;
+}
+
+/*
+ * isAbsolute.
+ * ------------------------------------------------------------------
+ */
+bool isAbsolute(const std::string &path) noexcept
+{
+#if defined(_WIN32)
+	return !isRelative(path);
+#else
+	return path.size() > 0 && path[0] == '/';
+#endif
+}
+
+/*
+ * isRelative.
+ * ------------------------------------------------------------------
+ */
+bool isRelative(const std::string &path) noexcept
+{
+#if defined(_WIN32)
+	return PathIsRelativeA(path.c_str()) == 1;
+#else
+	return !isAbsolute(path);
+#endif
+}
+
+/*
+ * 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
+	});
+}
+
+/*
+ * isSymlink.
+ * ------------------------------------------------------------------
+ */
+bool isSymlink(const std::string &path)
+{
+	return typeOf(path, [] (const auto &object) {
+#if defined(_WIN32)
+		return (object & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT;
+#elif defined(FS_HAVE_STAT)
+		return S_ISLNK(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
+
+/*
+ * exists.
+ * ------------------------------------------------------------------
+ */
+bool exists(const std::string &path) noexcept
+{
+#if defined(FS_HAVE_STAT)
+	struct stat st;
+
+	return ::stat(path.c_str(), &st) == 0;
+#else
+	return  hasAccess(path, "r");
+#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
+}
+
+} // !fs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fs/fs.hpp	Wed Jun 01 16:35:01 2016 +0200
@@ -0,0 +1,364 @@
+/*
+ * fs.hpp -- filesystem operations
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef FS_HPP
+#define FS_HPP
+
+/**
+ * \file fs.hpp
+ * \brief Filesystem operations made easy.
+ */
+
+/**
+ * \page filesystem Filesystem
+ * \brief Filesystem support
+ *
+ * The following options can be set by the user:
+ *
+ *   - **FS_HAVE_STAT**: (bool) Set to true if sys/stat.h and stat function are available, automatically detected.
+ *   - **FS_BUILDING_DLL**: (bool) Set to true if building this code as DLL, see also FS_EXPORT,
+ *   - **FS_EXPORT**: (unspecified) Attribute to export symbols, __declspec(dllexport) on Windows if FS_BUILDING_DLL is set.
+ */
+
+#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
+
+/**
+ * \brief DLL export.
+ */
+#if !defined(FS_EXPORT)
+#  if defined(FS_BUILDING_DLL) && defined(_WIN32)
+#    define FS_EXPORT __declspec(dllexport)
+#  else
+#    define FS_EXPORT
+#  endif
+#endif
+
+#if defined(FS_HAVE_STAT)
+#  include <sys/stat.h>
+#endif
+
+#include <regex>
+#include <string>
+#include <vector>
+
+/**
+ * \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 ".."
+};
+
+/**
+ * \class Entry
+ * \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
+ */
+FS_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
+ */
+FS_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
+ */
+FS_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
+ */
+FS_EXPORT struct stat stat(const std::string &path);
+
+#endif // !HAVE_STAT
+
+/**
+ * Check if a file exists.
+ *
+ * If HAVE_ACCESS is defined, the function access is used, otherwise stat is used.
+ *
+ * \param path the path to check
+ * \return true if the path exists
+ */
+FS_EXPORT bool exists(const std::string &path) noexcept;
+
+/**
+ * Check if the path is absolute.
+ *
+ * \param path the path
+ * \return true if the path is absolute
+ */
+FS_EXPORT bool isAbsolute(const std::string &path) noexcept;
+
+/**
+ * Check if the path is relative.
+ *
+ * \param path the path
+ * \return true if the path is absolute
+ */
+FS_EXPORT bool isRelative(const std::string &path) noexcept;
+
+/**
+ * Check if the file is readable.
+ *
+ * \param path the path
+ * \return true if has read access
+ */
+FS_EXPORT bool isReadable(const std::string &path) noexcept;
+
+/**
+ * Check if the file is writable.
+ *
+ * \param path the path
+ * \return true if has write access
+ */
+FS_EXPORT bool isWritable(const std::string &path) noexcept;
+
+/**
+ * Check if the file is a regular file.
+ *
+ * \param path the path
+ * \return true if it is a file and false if not or not readable
+ * \throw std::runtime_error if the operation is not supported
+ */
+FS_EXPORT bool isFile(const std::string &path);
+
+/**
+ * Check if the file is a directory.
+ *
+ * \param path the path
+ * \return true if it is a directory and false if not or not readable
+ * \throw std::runtime_error if the operation is not supported
+ */
+FS_EXPORT bool isDirectory(const std::string &path);
+
+/**
+ * Check if the file is a symbolic link.
+ *
+ * \param path the path
+ * \return true if it is a symbolic link and false if not or not readable
+ * \throw std::runtime_error if the operation is not supported
+ */
+FS_EXPORT bool isSymlink(const std::string &path);
+
+/**
+ * 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
+ */
+FS_EXPORT std::vector<Entry> readdir(const std::string &path, int flags = 0);
+
+/**
+ * Create a directory recursively.
+ *
+ * \param path the path
+ * \param mode the optional mode (not always supported)
+ * \throw std::runtime_error on failure
+ * \post all intermediate directories are created
+ */
+FS_EXPORT void mkdir(const std::string &path, int mode = 0700);
+
+/**
+ * Remove a directory recursively.
+ *
+ * If errors happens, they are silently discarded to remove as much as possible.
+ *
+ * \param path the path
+ */
+FS_EXPORT void rmdir(const std::string &path) noexcept;
+
+/**
+ * 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); });
+}
+
+/**
+ * Get the current working directory.
+ *
+ * \return the current working directory
+ * \throw std::runtime_error on failure
+ */
+FS_EXPORT std::string cwd();
+
+} // !fs
+
+#endif // !FS_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fs/test/main.cpp	Wed Jun 01 16:35:01 2016 +0200
@@ -0,0 +1,194 @@
+/*
+ * main.cpp -- test directory
+ *
+ * Copyright (c) 2013-2015 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 <sstream>
+
+#include <gtest/gtest.h>
+
+#include <fs.hpp>
+
+TEST(Symbols, entry)
+{
+	return;
+
+	FAIL() << "must not go here";
+
+	fs::Entry e1, e2;
+
+	(void)(e1 == e2);
+	(void)(e1 != e2);
+}
+
+TEST(Symbols, all)
+{
+	return;
+
+	FAIL() << "must not go here";
+
+	(void)fs::separator();
+	(void)fs::clean("");
+	(void)fs::baseName("");
+	(void)fs::dirName("");
+	(void)fs::stat("");
+	(void)fs::exists("");
+	(void)fs::isAbsolute("");
+	(void)fs::isRelative("");
+	(void)fs::isReadable("");
+	(void)fs::isWritable("");
+	(void)fs::isFile("");
+	(void)fs::isDirectory("");
+	(void)fs::isSymlink("");
+	(void)fs::readdir("");
+	(void)fs::mkdir("");
+	(void)fs::rmdir("");
+	(void)fs::cwd();
+}
+
+TEST(Filter, withDot)
+{
+	try {
+		bool dot(false);
+		bool dotdot(false);
+
+		for (const auto &entry : fs::readdir(".", fs::Dot)) {
+			if (entry.name == ".")
+				dot = true;
+			if (entry.name == "..")
+				dotdot = true;
+		}
+
+		ASSERT_TRUE(dot);
+		ASSERT_FALSE(dotdot);
+	} catch (const std::exception &error) {
+		FAIL() << error.what();
+	}
+}
+
+TEST(Filter, withDotDot)
+{
+	try {
+		bool dot(false);
+		bool dotdot(false);
+
+		for (const auto &entry : fs::readdir(".", fs::DotDot)) {
+			if (entry.name == ".")
+				dot = true;
+			if (entry.name == "..")
+				dotdot = true;
+		}
+
+		ASSERT_FALSE(dot);
+		ASSERT_TRUE(dotdot);
+	} catch (const std::exception &error) {
+		FAIL() << error.what();
+	}
+}
+
+TEST(Filter, withBothDots)
+{
+	try {
+		bool dot(false);
+		bool dotdot(false);
+
+		for (const auto &entry : fs::readdir(".", fs::Dot | fs::DotDot)) {
+			if (entry.name == ".")
+				dot = true;
+			if (entry.name == "..")
+				dotdot = true;
+		}
+
+		ASSERT_TRUE(dot);
+		ASSERT_TRUE(dotdot);
+	} catch (const std::exception &error) {
+		FAIL() << error.what();
+	}
+}
+
+TEST(Filter, withoutDots)
+{
+	try {
+		bool dot(false);
+		bool dotdot(false);
+
+		for (const auto &entry : fs::readdir(".")) {
+			if (entry.name == ".")
+				dot = true;
+			if (entry.name == "..")
+				dotdot = true;
+		}
+
+		ASSERT_FALSE(dot);
+		ASSERT_FALSE(dotdot);
+	} catch (const std::exception &error) {
+		FAIL() << error.what();
+	}
+}
+
+TEST(Exists, yes)
+{
+	ASSERT_TRUE(fs::exists(DIRECTORY "file-1.txt"));
+}
+
+TEST(Exists, no)
+{
+	ASSERT_FALSE(fs::exists(DIRECTORY "does not exists"));
+}
+
+TEST(IsFile, yes)
+{
+	ASSERT_TRUE(fs::isFile(__FILE__));
+}
+
+TEST(IsFile, no)
+{
+	ASSERT_FALSE(fs::isFile(DIRECTORY));
+}
+
+TEST(IsDirectory, yes)
+{
+	ASSERT_TRUE(fs::isDirectory(DIRECTORY));
+}
+
+TEST(IsDirectory, no)
+{
+	ASSERT_FALSE(fs::isDirectory(__FILE__));
+}
+
+TEST(RemoveDirectory, basic)
+{
+	try {
+		std::string path = DIRECTORY "mkdir-test";
+
+		fs::mkdir(path);
+
+		ASSERT_TRUE(fs::isDirectory(path));
+
+		fs::rmdir(path);
+
+		ASSERT_FALSE(fs::exists(path));
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+int main(int argc, char **argv)
+{
+	testing::InitGoogleTest(&argc, argv);
+
+	return RUN_ALL_TESTS();
+}