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'
ffi = require 'ffi'
ffi.cdef[[
void free(void *ptr);
]]
ffi_util = require 'aegisub.ffi'
transfer_string = (cdata) ->
return nil if cdata == nil
str = ffi.string cdata
ffi.C.free cdata
str
conv_func = (f) ->
err = ffi.new 'char *[1]'
(str) ->
err[0] = nil
result = f str, err
errmsg = transfer_string err[0]
err_buff = ffi.new 'char *[1]'
conv_func = (f) -> (str) ->
err_buff[0] = nil
result = f str, err_buff
errmsg = ffi_util.string err_buff[0]
if errmsg
error errmsg, 2
transfer_string result
ffi_util.string result
local 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
-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
lfs = require 'lfs'
lfs = require 'aegisub.lfs'
uuid = require 'uuid'
uuid.randomseed os.time()

View file

@ -21,15 +21,19 @@ namespace agi {
// Currently supports primitives, pointers, references, const, and function pointers
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; }}
AGI_TYPE_NAME_PRIMITIVE(bool);
AGI_TYPE_NAME_PRIMITIVE(char);
AGI_TYPE_NAME_PRIMITIVE(double);
AGI_TYPE_NAME_PRIMITIVE(float);
AGI_TYPE_NAME_PRIMITIVE(int);
AGI_TYPE_NAME_PRIMITIVE(void);
AGI_DEFINE_TYPE_NAME(bool);
AGI_DEFINE_TYPE_NAME(char);
AGI_DEFINE_TYPE_NAME(double);
AGI_DEFINE_TYPE_NAME(float);
AGI_DEFINE_TYPE_NAME(int);
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

View file

@ -21,7 +21,7 @@
extern "C" int luaopen_luabins(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_lfs(lua_State *L);
extern "C" int luaopen_lfs_impl(lua_State *L);
extern "C" int luaopen_lpeg(lua_State *L);
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.__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, "luabins", luaopen_luabins);

View file

@ -14,150 +14,152 @@
//
// 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/path.hpp>
#include <boost/range/size.hpp>
#include <lua.hpp>
namespace {
using namespace agi::lua;
using namespace agi::fs;
namespace bfs = boost::filesystem;
template<void (*func)(lua_State *L)>
int wrap(lua_State *L) {
namespace agi {
AGI_DEFINE_TYPE_NAME(DirectoryIterator);
}
namespace {
template<typename Func>
auto wrap(char **err, Func f) -> decltype(f()) {
try {
func(L);
return 1;
return f();
}
catch (bfs::filesystem_error const& e) {
lua_pushnil(L);
push_value(L, e.what());
return 2;
}
catch (agi::Exception const& e) {
lua_pushnil(L);
push_value(L, e.GetMessage());
return 2;
}
catch (error_tag) {
return lua_error(L);
}
}
void chdir(lua_State *L) {
bfs::current_path(check_string(L, 1));
push_value(L, true);
}
void currentdir(lua_State *L) {
push_value(L, bfs::current_path());
}
void mkdir(lua_State *L) {
CreateDirectory(check_string(L, 1));
push_value(L, true);
}
void rmdir(lua_State *L) {
Remove(check_string(L, 1));
push_value(L, true);
}
void touch(lua_State *L) {
Touch(check_string(L, 1));
push_value(L, true);
}
int dir_next(lua_State *L) {
auto& it = get<agi::fs::DirectoryIterator>(L, 1, "aegisub.lfs.dir");
if (it == end(it)) return 0;
push_value(L, *it);
++it;
return 1;
}
int dir_close(lua_State *L) {
auto& it = get<agi::fs::DirectoryIterator>(L, 1, "aegisub.lfs.dir");
// Convert to end iterator rather than destroying to avoid crashes if this
// is called multiple times
it = DirectoryIterator();
*err = strdup(e.what());
return 0;
}
int dir(lua_State *L) {
const path p = check_string(L, 1);
push_value(L, dir_next);
make<agi::fs::DirectoryIterator>(L, "aegisub.lfs.dir", check_string(L, 1), "");
return 2;
}
void attributes(lua_State *L) {
static std::pair<const char *, void (*)(lua_State *, path const&)> fields[] = {
{"mode", [](lua_State *L, path const& p) {
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 auto field = get_string(L, 2);
if (!field.empty()) {
for (const auto getter : fields) {
if (field == getter.first) {
getter.second(L, p);
return;
}
}
error(L, "Invalid attribute name: %s", field.c_str());
}
lua_createtable(L, 0, boost::size(fields));
for (const auto getter : fields) {
getter.second(L, p);
lua_setfield(L, -2, getter.first);
}
catch (agi::Exception const& e) {
*err = strdup(e.GetMessage().c_str());
return 0;
}
}
extern "C" int luaopen_lfs(lua_State *L) {
if (luaL_newmetatable(L, "aegisub.lfs.dir")) {
set_field<dir_close>(L, "__gc");
lua_createtable(L, 0, 2);
set_field<dir_next>(L, "next");
set_field<dir_close>(L, "close");
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
template<typename Ret>
bool setter(const char *path, char **err, Ret (*f)(bfs::path const&)) {
return wrap(err, [=]{
f(path);
return true;
});
}
const struct luaL_Reg lib[] = {
{"attributes", wrap<attributes>},
{"chdir", wrap<chdir>},
{"currentdir", wrap<currentdir>},
{"dir", exception_wrapper<dir>},
{"mkdir", wrap<mkdir>},
{"rmdir", wrap<rmdir>},
{"touch", wrap<touch>},
{nullptr, nullptr},
};
lua_createtable(L, 0, boost::size(lib) - 1);
luaL_register(L, nullptr, lib);
lua_pushvalue(L, -1);
lua_setglobal(L, "lfs");
bool lfs_chdir(const char *dir, char **err) {
return setter(dir, err, &bfs::current_path);
}
char *currentdir(char **err) {
return wrap(err, []{
return strdup(bfs::current_path().string().c_str());
});
}
bool mkdir(const char *dir, char **err) {
return setter(dir, err, &CreateDirectory);
}
bool lfs_rmdir(const char *dir, char **err) {
return setter(dir, err, &Remove);
}
bool touch(const char *path, char **err) {
return setter(path, err, &Touch);
}
char *dir_next(DirectoryIterator &it, char **err) {
if (it == end(it)) return nullptr;
return wrap(err, [&]{
auto str = strdup((*it).c_str());
++it;
return str;
});
}
void dir_close(DirectoryIterator &it) {
it = DirectoryIterator();
}
void dir_free(DirectoryIterator *it) {
delete it;
}
DirectoryIterator *dir_new(const char *path, char **err) {
return wrap(err, [=]{
return new DirectoryIterator(path, "");
});
}
const char *get_mode(const char *path, char **err) {
return wrap(err, [=]() -> const char * {
switch (bfs::status(path).type()) {
case bfs::file_not_found: return nullptr; break;
case bfs::regular_file: return "file"; break;
case bfs::directory_file: return "directory"; break;
case bfs::symlink_file: return "link"; break;
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;
}
});
}
time_t get_mtime(const char *path, char **err) {
return wrap(err, [=] { return ModifiedTime(path); });
}
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_impl(lua_State *L) {
lua_getglobal(L, "require");
lua_pushstring(L, "ffi");
lua_call(L, 1, 1);
lua_getfield(L, -1, "cdef");
lua_pushstring(L, "typedef struct DirectoryIterator DirectoryIterator;");
lua_call(L, 1, 0);
lua_getfield(L, -1, "cast");
lua_remove(L, -2); // ffi table
lua_createtable(L, 0, 12);
push_ffi_function(L, "chdir", lfs_chdir);
push_ffi_function(L, "currentdir", currentdir);
push_ffi_function(L, "mkdir", mkdir);
push_ffi_function(L, "rmdir", lfs_rmdir);
push_ffi_function(L, "touch", touch);
push_ffi_function(L, "get_mtime", get_mtime);
push_ffi_function(L, "get_mode", get_mode);
push_ffi_function(L, "get_size", get_size);
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;
}