Path: blob/main/contrib/kyua/utils/fs/lua_module.cpp
48081 views
// Copyright 2011 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/fs/lua_module.hpp"2930extern "C" {31#include <dirent.h>32}3334#include <cerrno>35#include <cstring>36#include <stdexcept>37#include <string>3839#include <lutok/operations.hpp>40#include <lutok/stack_cleaner.hpp>41#include <lutok/state.ipp>4243#include "utils/format/macros.hpp"44#include "utils/fs/operations.hpp"45#include "utils/fs/path.hpp"46#include "utils/sanity.hpp"4748namespace fs = utils::fs;495051namespace {525354/// Given a path, qualifies it with the module's start directory if necessary.55///56/// \param state The Lua state.57/// \param path The path to qualify.58///59/// \return The original path if it was absolute; otherwise the original path60/// appended to the module's start directory.61///62/// \throw std::runtime_error If the module's state has been corrupted.63static fs::path64qualify_path(lutok::state& state, const fs::path& path)65{66lutok::stack_cleaner cleaner(state);6768if (path.is_absolute()) {69return path;70} else {71state.get_global("_fs_start_dir");72if (!state.is_string(-1))73throw std::runtime_error("Missing _fs_start_dir global variable; "74"state corrupted?");75return fs::path(state.to_string(-1)) / path;76}77}787980/// Safely gets a path from the Lua state.81///82/// \param state The Lua state.83/// \param index The position in the Lua stack that contains the path to query.84///85/// \return The queried path.86///87/// \throw fs::error If the value is not a valid path.88/// \throw std::runtime_error If the value on the Lua stack is not convertible89/// to a path.90static fs::path91to_path(lutok::state& state, const int index)92{93if (!state.is_string(index))94throw std::runtime_error("Need a string parameter");95return fs::path(state.to_string(index));96}979899/// Lua binding for fs::path::basename.100///101/// \pre stack(-1) The input path.102/// \post stack(-1) The basename of the input path.103///104/// \param state The Lua state.105///106/// \return The number of result values, i.e. 1.107static int108lua_fs_basename(lutok::state& state)109{110lutok::stack_cleaner cleaner(state);111112const fs::path path = to_path(state, -1);113state.push_string(path.leaf_name().c_str());114cleaner.forget();115return 1;116}117118119/// Lua binding for fs::path::dirname.120///121/// \pre stack(-1) The input path.122/// \post stack(-1) The directory part of the input path.123///124/// \param state The Lua state.125///126/// \return The number of result values, i.e. 1.127static int128lua_fs_dirname(lutok::state& state)129{130lutok::stack_cleaner cleaner(state);131132const fs::path path = to_path(state, -1);133state.push_string(path.branch_path().c_str());134cleaner.forget();135return 1;136}137138139/// Lua binding for fs::path::exists.140///141/// \pre stack(-1) The input path.142/// \post stack(-1) Whether the input path exists or not.143///144/// \param state The Lua state.145///146/// \return The number of result values, i.e. 1.147static int148lua_fs_exists(lutok::state& state)149{150lutok::stack_cleaner cleaner(state);151152const fs::path path = qualify_path(state, to_path(state, -1));153state.push_boolean(fs::exists(path));154cleaner.forget();155return 1;156}157158159/// Lua binding for the files iterator.160///161/// This function takes an open directory from the closure of the iterator and162/// returns the next entry. See lua_fs_files() for the iterator generator163/// function.164///165/// \pre upvalue(1) The userdata containing an open DIR* object.166///167/// \param state The lua state.168///169/// \return The number of result values, i.e. 0 if there are no more entries or170/// 1 if an entry has been read.171static int172files_iterator(lutok::state& state)173{174lutok::stack_cleaner cleaner(state);175176DIR** dirp = state.to_userdata< DIR* >(state.upvalue_index(1));177const struct dirent* entry = ::readdir(*dirp);178if (entry == NULL)179return 0;180else {181state.push_string(entry->d_name);182cleaner.forget();183return 1;184}185}186187188/// Lua binding for the destruction of the files iterator.189///190/// This function takes an open directory and closes it. See lua_fs_files() for191/// the iterator generator function.192///193/// \pre stack(-1) The userdata containing an open DIR* object.194/// \post The DIR* object is closed.195///196/// \param state The lua state.197///198/// \return The number of result values, i.e. 0.199static int200files_gc(lutok::state& state)201{202lutok::stack_cleaner cleaner(state);203204PRE(state.is_userdata(-1));205206DIR** dirp = state.to_userdata< DIR* >(-1);207// For some reason, this may be called more than once. I don't know why208// this happens, but we must protect against it.209if (*dirp != NULL) {210::closedir(*dirp);211*dirp = NULL;212}213214return 0;215}216217218/// Lua binding to create an iterator to scan the contents of a directory.219///220/// \pre stack(-1) The input path.221/// \post stack(-1) The iterator function.222///223/// \param state The Lua state.224///225/// \return The number of result values, i.e. 1.226static int227lua_fs_files(lutok::state& state)228{229lutok::stack_cleaner cleaner(state);230231const fs::path path = qualify_path(state, to_path(state, -1));232233DIR** dirp = state.new_userdata< DIR* >();234235state.new_table();236state.push_string("__gc");237state.push_cxx_function(files_gc);238state.set_table(-3);239240state.set_metatable(-2);241242*dirp = ::opendir(path.c_str());243if (*dirp == NULL) {244const int original_errno = errno;245throw std::runtime_error(F("Failed to open directory: %s") %246std::strerror(original_errno));247}248249state.push_cxx_closure(files_iterator, 1);250251cleaner.forget();252return 1;253}254255256/// Lua binding for fs::path::is_absolute.257///258/// \pre stack(-1) The input path.259/// \post stack(-1) Whether the input path is absolute or not.260///261/// \param state The Lua state.262///263/// \return The number of result values, i.e. 1.264static int265lua_fs_is_absolute(lutok::state& state)266{267lutok::stack_cleaner cleaner(state);268269const fs::path path = to_path(state, -1);270271state.push_boolean(path.is_absolute());272cleaner.forget();273return 1;274}275276277/// Lua binding for fs::path::operator/.278///279/// \pre stack(-2) The first input path.280/// \pre stack(-1) The second input path.281/// \post stack(-1) The concatenation of the two paths.282///283/// \param state The Lua state.284///285/// \return The number of result values, i.e. 1.286static int287lua_fs_join(lutok::state& state)288{289lutok::stack_cleaner cleaner(state);290291const fs::path path1 = to_path(state, -2);292const fs::path path2 = to_path(state, -1);293state.push_string((path1 / path2).c_str());294cleaner.forget();295return 1;296}297298299} // anonymous namespace300301302/// Creates a Lua 'fs' module with a default start directory of ".".303///304/// \post The global 'fs' symbol is set to a table that contains functions to a305/// variety of utilites from the fs C++ module.306///307/// \param s The Lua state.308void309fs::open_fs(lutok::state& s)310{311open_fs(s, fs::current_path());312}313314315/// Creates a Lua 'fs' module with an explicit start directory.316///317/// \post The global 'fs' symbol is set to a table that contains functions to a318/// variety of utilites from the fs C++ module.319///320/// \param s The Lua state.321/// \param start_dir The start directory to use in all operations that reference322/// the underlying file sytem.323void324fs::open_fs(lutok::state& s, const fs::path& start_dir)325{326lutok::stack_cleaner cleaner(s);327328s.push_string(start_dir.str());329s.set_global("_fs_start_dir");330331std::map< std::string, lutok::cxx_function > members;332members["basename"] = lua_fs_basename;333members["dirname"] = lua_fs_dirname;334members["exists"] = lua_fs_exists;335members["files"] = lua_fs_files;336members["is_absolute"] = lua_fs_is_absolute;337members["join"] = lua_fs_join;338lutok::create_module(s, "fs", members);339}340341342