Convert the lfs module to using the ffi

This commit is contained in:
Thomas Goyne 2014-07-18 10:33:57 -07:00
parent 4f08afd808
commit 0cf35894e1
8 changed files with 260 additions and 133 deletions

View file

@ -0,0 +1,48 @@
-- Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
--
-- Permission to use, copy, modify, and 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.
--
-- Aegisub Project http://www.aegisub.org/
ffi = require 'ffi'
ffi.cdef[[
void free(void *ptr);
]]
char_ptr = ffi.typeof 'char *'
-- Convert a const char * to a lua string, returning nil rather than crashing
-- if it's NULL and freeing the input if it's non-const
string = (cdata) ->
return nil if cdata == nil
str = ffi.string cdata
if type(cdata) == char_ptr
ffi.C.free cdata
str
err_buff = ffi.new 'char *[1]'
-- Convert a function which has an error out parameter to one which returns
-- the original return value and the error as a string
err_arg_to_multiple_return = (f) -> (arg) ->
err_buff[0] = nil
result = if arg != nil
f arg, err_buff
else
f err_buff
errmsg = string err_buff[0]
if errmsg
return nil, errmsg
return result
{:string, :err_arg_to_multiple_return}

View file

@ -0,0 +1,80 @@
-- Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
--
-- Permission to use, copy, modify, and 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.
--
-- Aegisub Project http://www.aegisub.org/
impl = require 'aegisub.__lfs_impl'
ffi = require 'ffi'
ffi_util = require 'aegisub.ffi'
for k, v in pairs impl
impl[k] = ffi_util.err_arg_to_multiple_return v
string_ret = (f) -> (...) ->
res, err = f ...
ffi_util.string(res), err
number_ret = (f) -> (...) ->
res, err = f ...
tonumber(res), err
attributes = (path, field) ->
switch field
when 'mode'
res, err = impl.get_mode path
ffi_util.string(res), err
when 'modification'
res, err = impl.get_mtime path
tonumber(res), err
when 'size'
res, err = impl.get_size path
tonumber(res), err
else
mode, err = impl.get_mode path
if err or mode == nil then return nil, err
mod, err = impl.get_mtime path
if err then return nil, err
size, err = impl.get_size path
if err then return nil, err
mode: ffi_util.string(mode), modification: tonumber(mod), size: tonumber(size)
class dir_iter
new: (iter) =>
@iter = ffi.gc iter, -> impl.dir_free iter
close: =>
impl.dir_close @iter
next: =>
str, err = impl.dir_next @iter
if err then error err, 2
ffi_util.string str
dir = (path) ->
obj, err = impl.dir_new path
if err
error 2, err
iter = dir_iter obj
iter.next, iter
return {
:attributes
chdir: number_ret impl.chdir
currentdir: string_ret impl.currentdir
:dir
mkdir: number_ret impl.mkdir
rmdir: number_ret impl.rmdir
touch: number_ret impl.touch
}

View file

@ -30,25 +30,16 @@
impl = require 'aegisub.__unicode_impl' impl = require 'aegisub.__unicode_impl'
ffi = require 'ffi' ffi = require 'ffi'
ffi.cdef[[ ffi_util = require 'aegisub.ffi'
void free(void *ptr);
]]
transfer_string = (cdata) -> err_buff = ffi.new 'char *[1]'
return nil if cdata == nil conv_func = (f) -> (str) ->
str = ffi.string cdata err_buff[0] = nil
ffi.C.free cdata result = f str, err_buff
str errmsg = ffi_util.string err_buff[0]
if errmsg
conv_func = (f) -> error errmsg, 2
err = ffi.new 'char *[1]' ffi_util.string result
(str) ->
err[0] = nil
result = f str, err
errmsg = transfer_string err[0]
if errmsg
error errmsg, 2
transfer_string result
local unicode local unicode
unicode = unicode =

View file

@ -0,0 +1,2 @@
lfs = require 'aegisub.lfs'
return lfs

View file

@ -12,7 +12,7 @@
-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
lfs = require 'lfs' lfs = require 'aegisub.lfs'
uuid = require 'uuid' uuid = require 'uuid'
uuid.randomseed os.time() uuid.randomseed os.time()

View file

@ -21,15 +21,19 @@ namespace agi {
// Currently supports primitives, pointers, references, const, and function pointers // Currently supports primitives, pointers, references, const, and function pointers
template<typename T> struct type_name; template<typename T> struct type_name;
#define AGI_TYPE_NAME_PRIMITIVE(type) \ #define AGI_DEFINE_TYPE_NAME(type) \
template<> struct type_name<type> { static const char *name() { return #type; }} template<> struct type_name<type> { static const char *name() { return #type; }}
AGI_TYPE_NAME_PRIMITIVE(bool); AGI_DEFINE_TYPE_NAME(bool);
AGI_TYPE_NAME_PRIMITIVE(char); AGI_DEFINE_TYPE_NAME(char);
AGI_TYPE_NAME_PRIMITIVE(double); AGI_DEFINE_TYPE_NAME(double);
AGI_TYPE_NAME_PRIMITIVE(float); AGI_DEFINE_TYPE_NAME(float);
AGI_TYPE_NAME_PRIMITIVE(int); AGI_DEFINE_TYPE_NAME(int);
AGI_TYPE_NAME_PRIMITIVE(void); AGI_DEFINE_TYPE_NAME(long long);
AGI_DEFINE_TYPE_NAME(long);
AGI_DEFINE_TYPE_NAME(size_t);
AGI_DEFINE_TYPE_NAME(unsigned long long);
AGI_DEFINE_TYPE_NAME(void);
#undef AGI_TYPE_NAME_PRIMITIVE #undef AGI_TYPE_NAME_PRIMITIVE

View file

@ -21,7 +21,7 @@
extern "C" int luaopen_luabins(lua_State *L); extern "C" int luaopen_luabins(lua_State *L);
extern "C" int luaopen_re_impl(lua_State *L); extern "C" int luaopen_re_impl(lua_State *L);
extern "C" int luaopen_unicode_impl(lua_State *L); extern "C" int luaopen_unicode_impl(lua_State *L);
extern "C" int luaopen_lfs(lua_State *L); extern "C" int luaopen_lfs_impl(lua_State *L);
extern "C" int luaopen_lpeg(lua_State *L); extern "C" int luaopen_lpeg(lua_State *L);
namespace agi { namespace lua { namespace agi { namespace lua {
@ -35,7 +35,7 @@ void preload_modules(lua_State *L) {
set_field(L, "aegisub.__re_impl", luaopen_re_impl); set_field(L, "aegisub.__re_impl", luaopen_re_impl);
set_field(L, "aegisub.__unicode_impl", luaopen_unicode_impl); set_field(L, "aegisub.__unicode_impl", luaopen_unicode_impl);
set_field(L, "lfs", luaopen_lfs); set_field(L, "aegisub.__lfs_impl", luaopen_lfs_impl);
set_field(L, "lpeg", luaopen_lpeg); set_field(L, "lpeg", luaopen_lpeg);
set_field(L, "luabins", luaopen_luabins); set_field(L, "luabins", luaopen_luabins);

View file

@ -14,150 +14,152 @@
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
#include "libaegisub/lua/utils.h" #include "libaegisub/fs.h"
#include "libaegisub/type_name.h"
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/range/size.hpp> #include <lua.hpp>
namespace {
using namespace agi::lua;
using namespace agi::fs; using namespace agi::fs;
namespace bfs = boost::filesystem; namespace bfs = boost::filesystem;
template<void (*func)(lua_State *L)> namespace agi {
int wrap(lua_State *L) { AGI_DEFINE_TYPE_NAME(DirectoryIterator);
}
namespace {
template<typename Func>
auto wrap(char **err, Func f) -> decltype(f()) {
try { try {
func(L); return f();
return 1;
} }
catch (bfs::filesystem_error const& e) { catch (bfs::filesystem_error const& e) {
lua_pushnil(L); *err = strdup(e.what());
push_value(L, e.what()); return 0;
return 2;
} }
catch (agi::Exception const& e) { catch (agi::Exception const& e) {
lua_pushnil(L); *err = strdup(e.GetMessage().c_str());
push_value(L, e.GetMessage()); return 0;
return 2;
}
catch (error_tag) {
return lua_error(L);
} }
} }
void chdir(lua_State *L) { template<typename Ret>
bfs::current_path(check_string(L, 1)); bool setter(const char *path, char **err, Ret (*f)(bfs::path const&)) {
push_value(L, true); return wrap(err, [=]{
f(path);
return true;
});
} }
void currentdir(lua_State *L) { bool lfs_chdir(const char *dir, char **err) {
push_value(L, bfs::current_path()); return setter(dir, err, &bfs::current_path);
} }
void mkdir(lua_State *L) { char *currentdir(char **err) {
CreateDirectory(check_string(L, 1)); return wrap(err, []{
push_value(L, true); return strdup(bfs::current_path().string().c_str());
});
} }
void rmdir(lua_State *L) { bool mkdir(const char *dir, char **err) {
Remove(check_string(L, 1)); return setter(dir, err, &CreateDirectory);
push_value(L, true);
} }
void touch(lua_State *L) { bool lfs_rmdir(const char *dir, char **err) {
Touch(check_string(L, 1)); return setter(dir, err, &Remove);
push_value(L, true);
} }
int dir_next(lua_State *L) { bool touch(const char *path, char **err) {
auto& it = get<agi::fs::DirectoryIterator>(L, 1, "aegisub.lfs.dir"); return setter(path, err, &Touch);
if (it == end(it)) return 0;
push_value(L, *it);
++it;
return 1;
} }
int dir_close(lua_State *L) { char *dir_next(DirectoryIterator &it, char **err) {
auto& it = get<agi::fs::DirectoryIterator>(L, 1, "aegisub.lfs.dir"); if (it == end(it)) return nullptr;
// Convert to end iterator rather than destroying to avoid crashes if this return wrap(err, [&]{
// is called multiple times auto str = strdup((*it).c_str());
++it;
return str;
});
}
void dir_close(DirectoryIterator &it) {
it = DirectoryIterator(); it = DirectoryIterator();
return 0;
} }
int dir(lua_State *L) { void dir_free(DirectoryIterator *it) {
const path p = check_string(L, 1); delete it;
push_value(L, dir_next);
make<agi::fs::DirectoryIterator>(L, "aegisub.lfs.dir", check_string(L, 1), "");
return 2;
} }
void attributes(lua_State *L) { DirectoryIterator *dir_new(const char *path, char **err) {
static std::pair<const char *, void (*)(lua_State *, path const&)> fields[] = { return wrap(err, [=]{
{"mode", [](lua_State *L, path const& p) { return new DirectoryIterator(path, "");
switch (status(p).type()) { });
case bfs::file_not_found: lua_pushnil(L); break; }
case bfs::regular_file: push_value(L, "file"); break;
case bfs::directory_file: push_value(L, "directory"); break;
case bfs::symlink_file: push_value(L, "link"); break;
case bfs::block_file: push_value(L, "block device"); break;
case bfs::character_file: push_value(L, "char device"); break;
case bfs::fifo_file: push_value(L, "fifo"); break;
case bfs::socket_file: push_value(L, "socket"); break;
case bfs::reparse_file: push_value(L, "reparse point"); break;
default: push_value(L, "other"); break;
}
}},
{"modification", [](lua_State *L, path const& p) { push_value(L, ModifiedTime(p)); }},
{"size", [](lua_State *L, path const& p) { push_value(L, Size(p)); }}
};
const path p = check_string(L, 1); const char *get_mode(const char *path, char **err) {
return wrap(err, [=]() -> const char * {
const auto field = get_string(L, 2); switch (bfs::status(path).type()) {
if (!field.empty()) { case bfs::file_not_found: return nullptr; break;
for (const auto getter : fields) { case bfs::regular_file: return "file"; break;
if (field == getter.first) { case bfs::directory_file: return "directory"; break;
getter.second(L, p); case bfs::symlink_file: return "link"; break;
return; case bfs::block_file: return "block device"; break;
} case bfs::character_file: return "char device"; break;
case bfs::fifo_file: return "fifo"; break;
case bfs::socket_file: return "socket"; break;
case bfs::reparse_file: return "reparse point"; break;
default: return "other"; break;
} }
error(L, "Invalid attribute name: %s", field.c_str()); });
} }
lua_createtable(L, 0, boost::size(fields)); time_t get_mtime(const char *path, char **err) {
for (const auto getter : fields) { return wrap(err, [=] { return ModifiedTime(path); });
getter.second(L, p); }
lua_setfield(L, -2, getter.first);
} uintmax_t get_size(const char *path, char **err) {
return wrap(err, [=] { return Size(path); });
}
template<typename T>
void push_ffi_function(lua_State *L, const char *name, T *func) {
lua_pushvalue(L, -2); // push cast function
lua_pushstring(L, agi::type_name<T*>::name().c_str());
// This cast isn't legal, but LuaJIT internally requires that it work
lua_pushlightuserdata(L, (void *)func);
lua_call(L, 2, 1);
lua_setfield(L, -2, name);
} }
} }
extern "C" int luaopen_lfs(lua_State *L) { extern "C" int luaopen_lfs_impl(lua_State *L) {
if (luaL_newmetatable(L, "aegisub.lfs.dir")) { lua_getglobal(L, "require");
set_field<dir_close>(L, "__gc"); lua_pushstring(L, "ffi");
lua_call(L, 1, 1);
lua_createtable(L, 0, 2); lua_getfield(L, -1, "cdef");
set_field<dir_next>(L, "next"); lua_pushstring(L, "typedef struct DirectoryIterator DirectoryIterator;");
set_field<dir_close>(L, "close"); lua_call(L, 1, 0);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
}
const struct luaL_Reg lib[] = { lua_getfield(L, -1, "cast");
{"attributes", wrap<attributes>}, lua_remove(L, -2); // ffi table
{"chdir", wrap<chdir>},
{"currentdir", wrap<currentdir>}, lua_createtable(L, 0, 12);
{"dir", exception_wrapper<dir>}, push_ffi_function(L, "chdir", lfs_chdir);
{"mkdir", wrap<mkdir>}, push_ffi_function(L, "currentdir", currentdir);
{"rmdir", wrap<rmdir>}, push_ffi_function(L, "mkdir", mkdir);
{"touch", wrap<touch>}, push_ffi_function(L, "rmdir", lfs_rmdir);
{nullptr, nullptr}, push_ffi_function(L, "touch", touch);
}; push_ffi_function(L, "get_mtime", get_mtime);
lua_createtable(L, 0, boost::size(lib) - 1); push_ffi_function(L, "get_mode", get_mode);
luaL_register(L, nullptr, lib); push_ffi_function(L, "get_size", get_size);
lua_pushvalue(L, -1);
lua_setglobal(L, "lfs"); push_ffi_function(L, "dir_new", dir_new);
push_ffi_function(L, "dir_free", dir_free);
push_ffi_function(L, "dir_next", dir_next);
push_ffi_function(L, "dir_close", dir_close);
lua_remove(L, -2); // ffi.cast function
return 1; return 1;
} }