view lib/irccd/mod-directory.cpp @ 160:c1acfacc46bd

Irccd: dll export and style
author David Demelier <markand@malikania.fr>
date Tue, 24 May 2016 13:00:35 +0200
parents dc7d6ba08122
children ef527409e638
line wrap: on
line source

/*
 * js-directory.cpp -- Irccd.Directory API
 *
 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <regex>
#include <stdexcept>
#include <string>

#include "fs.hpp"
#include "js.hpp"
#include "mod-directory.hpp"
#include "mod-irccd.hpp"
#include "path.hpp"
#include "plugin-js.hpp"
#include "sysconfig.hpp"

namespace irccd {

namespace {

std::string path(duk::ContextPtr ctx)
{
	duk::push(ctx, duk::This{});
	duk::getProperty<void>(ctx, -1, "path");

	if (duk::type(ctx, -1) != DUK_TYPE_STRING)
		duk::raise(ctx, duk::TypeError("invalid this binding"));

	std::string ret = duk::get<std::string>(ctx, -1);

	if (ret.empty())
		duk::raise(ctx, duk::TypeError("invalid directory with empty path"));

	duk::pop(ctx, 2);

	return ret;
}

/*
 * 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:
 *
 * - findName
 * - findRegex
 */
template <typename Pred>
std::string findPath(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 = findPath(next, true, pred);

			if (!path.empty())
				return path;
		}
	}

	return "";
}

/*
 * Helper for finding by equality.
 */
std::string findName(std::string base, const std::string &pattern, bool recursive)
{
	return findPath(base, recursive, [&] (const std::string &entryname) -> bool {
		return pattern == entryname;
	});
}

/*
 * Helper for finding by regular expression
 */
std::string findRegex(const std::string &base, std::string pattern, bool recursive)
{
	std::regex regexp(pattern, std::regex::ECMAScript);
	std::smatch smatch;

	return findPath(base, recursive, [&] (const std::string &entryname) -> bool {
		return std::regex_match(entryname, smatch, regexp);
	});
}

/*
 * Generic find function for:
 *
 * - Directory.find
 * - Directory.prototype.find
 *
 * The patternIndex is the argument where to test if the argument is a regex or a string.
 */
duk::Ret find(duk::ContextPtr ctx, std::string base, bool recursive, int patternIndex)
{
	base = path::clean(base);

	try {
		std::string path;

		if (duk::is<std::string>(ctx, patternIndex))
			path = findName(base, duk::get<std::string>(ctx, patternIndex), recursive);
		else {
			// Check if it's a valid RegExp object.
			duk::getGlobal<void>(ctx, "RegExp");

			bool isRegex = duk::instanceof(ctx, patternIndex, -1);

			duk::pop(ctx);

			if (isRegex)
				path = findRegex(base, duk::getProperty<std::string>(ctx, patternIndex, "source"), recursive);
			else
				duk::raise(ctx, duk::TypeError("pattern must be a string or a regex expression"));
		}

		if (path.empty())
			return 0;

		duk::push(ctx, path);
	} catch (const std::exception &ex) {
		duk::raise(ctx, duk::Error(ex.what()));
	}

	return 1;
}

/*
 * Generic remove function for:
 *
 * - Directory.remove
 * - Directory.prototype.remove
 */
duk::Ret remove(duk::ContextPtr ctx, const std::string &path, bool recursive)
{
	if (!fs::isDirectory(path))
		duk::raise(ctx, SystemError(EINVAL, "not a directory"));

	if (!recursive) {
#if defined(_WIN32)
		::RemoveDirectory(path.c_str());
#else
		::remove(path.c_str());
#endif
	} else
		fs::rmdir(path.c_str());

	return 0;
}

/*
 * Method: Directory.find(pattern, recursive)
 * --------------------------------------------------------
 *
 * Synonym of Directory.find(path, pattern, recursive) but the path is taken
 * from the directory object.
 *
 * Arguments:
 *   - pattern, the regular expression or file name,
 *   - recursive, set to true to search recursively (default: false).
 * Returns:
 *   The path to the file or undefined if not found.
 * Throws:
 *   - Any exception on error.
 */
duk::Ret methodFind(duk::ContextPtr ctx)
{
	return find(ctx, path(ctx), duk::optional<bool>(ctx, 1, false), 0);
}

/*
 * Method: Directory.remove(recursive)
 * --------------------------------------------------------
 *
 * Synonym of Directory.remove(recursive) but the path is taken from the
 * directory object.
 *
 * Arguments:
 *   - recursive, recursively or not (default: false).
 * Throws:
 *   - Any exception on error.
 */
duk::Ret methodRemove(duk::ContextPtr ctx)
{
	return remove(ctx, path(ctx), duk::optional<bool>(ctx, 0, false));
}

const duk::FunctionMap methods{
	{ "find",		{ methodFind,		DUK_VARARGS	} },
	{ "remove",		{ methodRemove,		1		} }
};

/* --------------------------------------------------------
 * Directory "static" functions
 * -------------------------------------------------------- */

/*
 * Function: Irccd.Directory(path, flags) [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
 */
duk::Ret constructor(duk::ContextPtr ctx)
{
	if (!duk_is_constructor_call(ctx))
		return 0;

	try {
		std::string path = duk::require<std::string>(ctx, 0);
		std::int8_t flags = duk::optional<int>(ctx, 1, 0);

		if (!fs::isDirectory(path))
			duk::raise(ctx, SystemError(EINVAL, "not a directory"));

		std::vector<fs::Entry> list = fs::readdir(path, flags);

		duk::push(ctx, duk::This{});
		duk::push(ctx, "count");
		duk::push(ctx, (int)list.size());
		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
		duk::push(ctx, "path");
		duk::push(ctx, path);
		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
		duk::push(ctx, "entries");
		duk::push(ctx, duk::Array{});

		for (unsigned i = 0; i < list.size(); ++i) {
			duk::push(ctx, duk::Object{});
			duk::putProperty(ctx, -1, "name", list[i].name);
			duk::putProperty(ctx, -1, "type", static_cast<int>(list[i].type));
			duk::putProperty(ctx, -2, (int)i);
		}

		duk::defineProperty(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
	} catch (const std::exception &ex) {
		duk::raise(ctx, SystemError(errno, ex.what()));
	}

	return 0;
}

/*
 * Function: Irccd.Directory.find(path, pattern, recursive)
 * --------------------------------------------------------
 *
 * Find an entry by a pattern or a regular expression.
 *
 * Arguments:
 *   - path, the base path,
 *   - pattern, the regular expression or file name,
 *   - recursive, set to true to search recursively (default: false).
 * Returns:
 *   The path to the file or undefined on errors or not found.
 */
duk::Ret funcFind(duk::ContextPtr ctx)
{
	return find(ctx, duk::require<std::string>(ctx, 0), duk::optional<bool>(ctx, 2, false), 1);
}

/*
 * Function: Irccd.Directory.remove(path, recursive)
 * --------------------------------------------------------
 *
 * Remove the directory optionally recursively.
 *
 * Arguments:
 *   - path, the path to the directory,
 *   - recursive, recursively or not (default: false).
 * Throws:
 *   - Any exception on error.
 */
duk::Ret funcRemove(duk::ContextPtr ctx)
{
	return remove(ctx, duk::require<std::string>(ctx, 0), duk::optional<bool>(ctx, 1, false));
}

/*
 * Function: Irccd.Directory.mkdir(path, mode = 0700)
 * --------------------------------------------------------
 *
 * Create a directory specified by path. It will create needed subdirectories
 * just like you have invoked mkdir -p.
 *
 * Arguments:
 *   - path, the path to the directory,
 *   - mode, the mode, not available on all platforms.
 * Throws:
 *   - Any exception on error.
 */
duk::Ret funcMkdir(duk::ContextPtr ctx)
{
	try {
		fs::mkdir(duk::require<std::string>(ctx, 0), duk::optional<int>(ctx, 1, 0700));
	} catch (const std::exception &ex) {
		duk::raise(ctx, SystemError(errno, ex.what()));
	}

	return 0;
}

const duk::FunctionMap functions{
	{ "find",		{ funcFind,	DUK_VARARGS } },
	{ "mkdir",		{ funcMkdir,	DUK_VARARGS } },
	{ "remove",		{ funcRemove,	DUK_VARARGS } }
};

const duk::Map<int> 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)		}
};

} // !namespace

DirectoryModule::DirectoryModule() noexcept
	: Module("Irccd.Directory")
{
}

void DirectoryModule::load(Irccd &, JsPlugin &plugin)
{
	duk::StackAssert sa(plugin.context());

	duk::getGlobal<void>(plugin.context(), "Irccd");
	duk::push(plugin.context(), duk::Function{constructor, 2});
	duk::push(plugin.context(), constants);
	duk::push(plugin.context(), functions);
	duk::putProperty(plugin.context(), -1, "separator", std::string{fs::separator()});
	duk::push(plugin.context(), duk::Object{});
	duk::push(plugin.context(), methods);
	duk::putProperty(plugin.context(), -2, "prototype");
	duk::putProperty(plugin.context(), -2, "Directory");
	duk::pop(plugin.context());
}

} // !irccd