Mercurial > irccd
view libirccd-js/irccd/js/file_js_api.cpp @ 811:2d71cf931c79
js: use DUK_HIDDEN_SYMBOL, closes #955 @10m
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 16 Nov 2018 12:20:54 +0100 |
parents | 8460b4a34191 |
children |
line wrap: on
line source
/* * file_js_api.cpp -- Irccd.File API * * 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 <algorithm> #include <array> #include <cassert> #include <iterator> #include <vector> #include <boost/filesystem.hpp> #include <irccd/fs_util.hpp> #include "file_js_api.hpp" #include "irccd_js_api.hpp" #include "js_plugin.hpp" using irccd::daemon::bot; namespace irccd::js { namespace { const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.File")); const std::string_view prototype(DUK_HIDDEN_SYMBOL("Irccd.File.prototype")); // {{{ clear_crlf auto clear_crlf(std::string input) noexcept -> std::string { if (input.length() > 0 && input.back() == '\r') input.pop_back(); return input; } // }}} // {{{ from_errno auto from_errno() noexcept -> std::system_error { return std::system_error(make_error_code(static_cast<std::errc>(errno))); } // }}} // {{{ self auto self(duk_context* ctx) -> std::shared_ptr<file> { duk::stack_guard sa(ctx); duk_push_this(ctx); duk_get_prop_string(ctx, -1, signature.data()); auto ptr = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1)); duk_pop_2(ctx); if (!ptr) duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object"); return *ptr; } // }}} // {{{ wrap template <typename Handler> auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t { try { return handler(); } catch (const boost::system::system_error& ex) { duk::raise(ctx, ex); } catch (const std::system_error& ex) { duk::raise(ctx, ex); } catch (const std::exception& ex) { duk::raise(ctx, ex); } return 0; } // }}} // {{{ Irccd.File.prototype.basename /* * Method: Irccd.File.prototype.basename() * -------------------------------------------------------- * * Synonym of `Irccd.File.basename(path)` but with the path from the file. * * Returns: * The base name. */ auto File_prototype_basename(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { return duk::push(ctx, fs_util::base_name(self(ctx)->get_path())); }); } // }}} // {{{ Irccd.File.prototype.close /* * Method: Irccd.File.prototype.close() * -------------------------------------------------------- * * Force close of the file, automatically called when object is collected. */ auto File_prototype_close(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { self(ctx)->close(); return 0; }); } // }}} // {{{ Irccd.File.prototype.dirname /* * Method: Irccd.File.prototype.dirname() * -------------------------------------------------------- * * Synonym of `Irccd.File.dirname(path)` but with the path from the file. * * Returns: * The directory name. */ auto File_prototype_dirname(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { return duk::push(ctx, fs_util::dir_name(self(ctx)->get_path())); }); } // }}} // {{{ Irccd.File.prototype.lines /* * Method: Irccd.File.prototype.lines() * -------------------------------------------------------- * * Read all lines and return an array. * * Returns: * An array with all lines. * Throws * - Any exception on error. */ auto File_prototype_lines(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { duk_push_array(ctx); std::FILE* fp = self(ctx)->get_handle(); std::string buffer; std::array<char, 128> data; std::int32_t i = 0; while (std::fgets(&data[0], data.size(), fp) != nullptr) { buffer += data.data(); const auto pos = buffer.find('\n'); if (pos != std::string::npos) { duk::push(ctx, clear_crlf(buffer.substr(0, pos))); duk_put_prop_index(ctx, -2, i++); buffer.erase(0, pos + 1); } } // Maybe an error in the stream. if (std::ferror(fp)) throw from_errno(); // Missing '\n' in end of file. if (!buffer.empty()) { duk::push(ctx, clear_crlf(buffer)); duk_put_prop_index(ctx, -2, i++); } return 1; }); } // }}} // {{{ Irccd.File.prototype.read /* * Method: Irccd.File.prototype.read(amount) * -------------------------------------------------------- * * Read the specified amount of characters or the whole file. * * Arguments: * - amount, the amount of characters or -1 to read all (Optional, default: -1). * Returns: * The string. * Throws: * - Irccd.SystemError on errors */ auto File_prototype_read(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { const auto fp = self(ctx)->get_handle(); const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1; if (amount == 0 || !fp) return 0; std::string data; std::size_t total = 0; if (amount < 0) { std::array<char, 128> buffer; std::size_t nread; while ((nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), fp)) > 0) { if (std::ferror(fp)) throw from_errno(); std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data)); total += nread; } } else { data.resize(static_cast<std::size_t>(amount)); total = std::fread(&data[0], sizeof (data[0]), static_cast<std::size_t>(amount), fp); if (std::ferror(fp)) throw from_errno(); data.resize(total); } return duk::push(ctx, data); }); } // }}} // {{{ Irccd.File.prototype.readline /* * Method: Irccd.File.prototype.readline() * -------------------------------------------------------- * * Read the next line available. * * Returns: * The next line or undefined if eof. * Throws: * - Irccd.SystemError on errors */ auto File_prototype_readline(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { auto fp = self(ctx)->get_handle(); if (fp == nullptr || std::feof(fp)) return 0; std::string result; for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; ) result += (char)ch; if (std::ferror(fp)) throw from_errno(); return duk::push(ctx, clear_crlf(result)); }); } // }}} // {{{ Irccd.File.prototype.remove /* * Method: Irccd.File.prototype.remove() * -------------------------------------------------------- * * Synonym of Irccd.File.prototype.remove(path) but with the path from the file. * * Throws: * - Irccd.SystemError on errors */ auto File_prototype_remove(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { boost::filesystem::remove(self(ctx)->get_path()); return 0; }); } // }}} // {{{ Irccd.File.prototype.seek /* * Method: Irccd.File.prototype.seek(type, amount) * -------------------------------------------------------- * * Sets the position in the file. * * Arguments: * - type, the type of setting (File.SeekSet, File.SeekCur, File.SeekSet), * - amount, the new offset. * Throws: * - Irccd.SystemError on errors */ auto File_prototype_seek(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { auto fp = self(ctx)->get_handle(); auto type = duk_require_int(ctx, 0); auto amount = duk_require_int(ctx, 1); if (fp != nullptr && std::fseek(fp, amount, type) != 0) throw from_errno(); return 0; }); } // }}} // {{{ Irccd.File.prototype.stat #if defined(IRCCD_HAVE_STAT) /* * Method: Irccd.File.prototype.stat() [optional] * -------------------------------------------------------- * * Synonym of File.stat(path) but with the path from the file. * * Returns: * The stat information. * Throws: * - Irccd.SystemError on errors */ auto File_prototype_stat(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { auto file = self(ctx); struct stat st; if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0) throw from_errno(); duk::push(ctx, st); return 1; }); } #endif // !IRCCD_HAVE_STAT // }}} // {{{ Irccd.File.prototype.tell /* * Method: Irccd.File.prototype.tell() * -------------------------------------------------------- * * Get the actual position in the file. * * Returns: * The position. * Throws: * - Irccd.SystemError on errors */ auto File_prototype_tell(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { auto fp = self(ctx)->get_handle(); long pos; if (fp == nullptr) return 0; if ((pos = std::ftell(fp)) == -1L) throw from_errno(); duk_push_int(ctx, pos); return 1; }); } // }}} // {{{ Irccd.File.prototype.write /* * Method: Irccd.File.prototype.write(data) * -------------------------------------------------------- * * Write some characters to the file. * * Arguments: * - data, the character to write. * Returns: * The number of bytes written. * Throws: * - Irccd.SystemError on errors */ auto File_prototype_write(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { auto fp = self(ctx)->get_handle(); auto data = duk::require<std::string>(ctx, 0); if (fp == nullptr) return 0; const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp); if (std::ferror(fp)) throw from_errno(); duk_push_uint(ctx, nwritten); return 1; }); } // }}} // {{{ Irccd.File [constructor] /* * Function: Irccd.File(path, mode) [constructor] * -------------------------------------------------------- * * Open a file specified by path with the specified mode. * * Arguments: * - path, the path to the file, * - mode, the mode string. * Throws: * - Irccd.SystemError on errors */ auto File_constructor(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { if (!duk_is_constructor_call(ctx)) return 0; const auto path = duk::require<std::string>(ctx, 0); const auto mode = duk::require<std::string>(ctx, 1); duk_push_this(ctx); duk_push_pointer(ctx, new std::shared_ptr<file>(new file(path, mode))); duk_put_prop_string(ctx, -2, signature.data()); duk_pop(ctx); return 0; }); } // }}} // {{{ Irccd.File [destructor] /* * Function: Irccd.File() [destructor] * ------------------------------------------------------------------ * * Delete the property. */ auto File_destructor(duk_context* ctx) -> duk_ret_t { duk_get_prop_string(ctx, 0, signature.data()); delete static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1)); duk_pop(ctx); duk_del_prop_string(ctx, 0, signature.data()); return 0; } // }}} // {{{ Irccd.File.basename /* * Function: Irccd.File.basename(path) * -------------------------------------------------------- * * duk_ret_turn the file basename as specified in `basename(3)` C function. * * Arguments: * - path, the path to the file. * Returns: * The base name. * Throws: * - Irccd.SystemError on errors */ auto File_basename(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { return duk::push(ctx, fs_util::base_name(duk_require_string(ctx, 0))); }); } // }}} // {{{ Irccd.File.dirname /* * Function: Irccd.File.dirname(path) * -------------------------------------------------------- * * duk_ret_turn the file directory name as specified in `dirname(3)` C function. * * Arguments: * - path, the path to the file. * Throws: * - Irccd.SystemError on errors */ auto File_dirname(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { return duk::push(ctx, fs_util::dir_name(duk_require_string(ctx, 0))); }); } // }}} // {{{ Irccd.File.exists /* * Function: Irccd.File.exists(path) * -------------------------------------------------------- * * Check if the file exists. * * Arguments: * - path, the path to the file. * Returns: * True if exists. * Throws: * - Irccd.SystemError on errors */ auto File_exists(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { return duk::push(ctx, boost::filesystem::exists(duk_require_string(ctx, 0))); }); } // }}} // {{{ Irccd.File.remove /* * Function Irccd.File.remove(path) * -------------------------------------------------------- * * Remove the file at the specified path. * * Arguments: * - path, the path to the file. * Throws: * - Irccd.SystemError on errors */ auto File_remove(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { boost::filesystem::remove(duk::require<std::string>(ctx, 0)); return 0; }); } // }}} // {{{ Irccd.File.stat #if defined(IRCCD_HAVE_STAT) /* * Function Irccd.File.stat(path) [optional] * -------------------------------------------------------- * * Get file information at the specified path. * * Arguments: * - path, the path to the file. * Returns: * The stat information. * Throws: * - Irccd.SystemError on errors */ auto File_stat(duk_context* ctx) -> duk_ret_t { return wrap(ctx, [&] { struct stat st; if (::stat(duk_require_string(ctx, 0), &st) < 0) throw from_errno(); return duk::push(ctx, st); }); } #endif // !IRCCD_HAVE_STAT // }}} // {{{ definitions const duk_function_list_entry methods[] = { { "basename", File_prototype_basename, 0 }, { "close", File_prototype_close, 0 }, { "dirname", File_prototype_dirname, 0 }, { "lines", File_prototype_lines, 0 }, { "read", File_prototype_read, 1 }, { "readline", File_prototype_readline, 0 }, { "remove", File_prototype_remove, 0 }, { "seek", File_prototype_seek, 2 }, #if defined(IRCCD_HAVE_STAT) { "stat", File_prototype_stat, 0 }, #endif { "tell", File_prototype_tell, 0 }, { "write", File_prototype_write, 1 }, { nullptr, nullptr, 0 } }; const duk_function_list_entry functions[] = { { "basename", File_basename, 1 }, { "dirname", File_dirname, 1 }, { "exists", File_exists, 1 }, { "remove", File_remove, 1 }, #if defined(IRCCD_HAVE_STAT) { "stat", File_stat, 1 }, #endif { nullptr, nullptr, 0 } }; const duk_number_list_entry constants[] = { { "SeekCur", SEEK_CUR }, { "SeekEnd", SEEK_END }, { "SeekSet", SEEK_SET }, { nullptr, 0 } }; // }}} } // !namespace // {{{ file_js_api auto file_js_api::get_name() const noexcept -> std::string_view { return "Irccd.File"; } void file_js_api::load(bot&, std::shared_ptr<js_plugin> plugin) { duk::stack_guard sa(plugin->get_context()); duk_get_global_string(plugin->get_context(), "Irccd"); duk_push_c_function(plugin->get_context(), File_constructor, 2); duk_put_number_list(plugin->get_context(), -1, constants); duk_put_function_list(plugin->get_context(), -1, functions); duk_push_object(plugin->get_context()); duk_put_function_list(plugin->get_context(), -1, methods); duk_push_c_function(plugin->get_context(), File_destructor, 1); duk_set_finalizer(plugin->get_context(), -2); duk_dup(plugin->get_context(), -1); duk_put_global_string(plugin->get_context(), prototype.data()); duk_put_prop_string(plugin->get_context(), -2, "prototype"); duk_put_prop_string(plugin->get_context(), -2, "File"); duk_pop(plugin->get_context()); } // }}} // {{{ duk::type_traits<std::shared_ptr<file>> using file_traits = duk::type_traits<std::shared_ptr<file>>; void file_traits::push(duk_context* ctx, std::shared_ptr<file> fp) { assert(ctx); assert(fp); duk::stack_guard sa(ctx, 1); duk_push_object(ctx); duk_push_pointer(ctx, new std::shared_ptr<file>(std::move(fp))); duk_put_prop_string(ctx, -2, signature.data()); duk_get_global_string(ctx, prototype.data()); duk_set_prototype(ctx, -2); } auto file_traits::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file> { if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data())) duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object"); duk_get_prop_string(ctx, index, signature.data()); const auto fp = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1)); duk_pop(ctx); return *fp; } // }}} // {{{ duk::type_traits<struct stat> #if defined(IRCCD_HAVE_STAT) void duk::type_traits<struct stat>::push(duk_context* ctx, const struct stat& st) { duk::stack_guard sa(ctx, 1); duk_push_object(ctx); #if defined(IRCCD_HAVE_STAT_ST_ATIME) duk_push_int(ctx, st.st_atime); duk_put_prop_string(ctx, -2, "atime"); #endif #if defined(IRCCD_HAVE_STAT_ST_BLKSIZE) duk_push_int(ctx, st.st_blksize); duk_put_prop_string(ctx, -2, "blksize"); #endif #if defined(IRCCD_HAVE_STAT_ST_BLOCKS) duk_push_int(ctx, st.st_blocks); duk_put_prop_string(ctx, -2, "blocks"); #endif #if defined(IRCCD_HAVE_STAT_ST_CTIME) duk_push_int(ctx, st.st_ctime); duk_put_prop_string(ctx, -2, "ctime"); #endif #if defined(IRCCD_HAVE_STAT_ST_DEV) duk_push_int(ctx, st.st_dev); duk_put_prop_string(ctx, -2, "dev"); #endif #if defined(IRCCD_HAVE_STAT_ST_GID) duk_push_int(ctx, st.st_gid); duk_put_prop_string(ctx, -2, "gid"); #endif #if defined(IRCCD_HAVE_STAT_ST_INO) duk_push_int(ctx, st.st_ino); duk_put_prop_string(ctx, -2, "ino"); #endif #if defined(IRCCD_HAVE_STAT_ST_MODE) duk_push_int(ctx, st.st_mode); duk_put_prop_string(ctx, -2, "mode"); #endif #if defined(IRCCD_HAVE_STAT_ST_MTIME) duk_push_int(ctx, st.st_mtime); duk_put_prop_string(ctx, -2, "mtime"); #endif #if defined(IRCCD_HAVE_STAT_ST_NLINK) duk_push_int(ctx, st.st_nlink); duk_put_prop_string(ctx, -2, "nlink"); #endif #if defined(IRCCD_HAVE_STAT_ST_RDEV) duk_push_int(ctx, st.st_rdev); duk_put_prop_string(ctx, -2, "rdev"); #endif #if defined(IRCCD_HAVE_STAT_ST_SIZE) duk_push_int(ctx, st.st_size); duk_put_prop_string(ctx, -2, "size"); #endif #if defined(IRCCD_HAVE_STAT_ST_UID) duk_push_int(ctx, st.st_uid); duk_put_prop_string(ctx, -2, "uid"); #endif } #endif // !IRCCD_HAVE_STAT // }}} } // !irccd::js