xref: /src/contrib/kyua/utils/fs/lua_module.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
108334c51SBrooks Davis // Copyright 2011 The Kyua Authors.
208334c51SBrooks Davis // All rights reserved.
308334c51SBrooks Davis //
408334c51SBrooks Davis // Redistribution and use in source and binary forms, with or without
508334c51SBrooks Davis // modification, are permitted provided that the following conditions are
608334c51SBrooks Davis // met:
708334c51SBrooks Davis //
808334c51SBrooks Davis // * Redistributions of source code must retain the above copyright
908334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer.
1008334c51SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
1108334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
1208334c51SBrooks Davis //   documentation and/or other materials provided with the distribution.
1308334c51SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
1408334c51SBrooks Davis //   may be used to endorse or promote products derived from this software
1508334c51SBrooks Davis //   without specific prior written permission.
1608334c51SBrooks Davis //
1708334c51SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1808334c51SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1908334c51SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2008334c51SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2108334c51SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2208334c51SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2308334c51SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2408334c51SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2508334c51SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2608334c51SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2708334c51SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2808334c51SBrooks Davis 
2908334c51SBrooks Davis #include "utils/fs/lua_module.hpp"
3008334c51SBrooks Davis 
3108334c51SBrooks Davis extern "C" {
3208334c51SBrooks Davis #include <dirent.h>
3308334c51SBrooks Davis }
3408334c51SBrooks Davis 
3508334c51SBrooks Davis #include <cerrno>
3608334c51SBrooks Davis #include <cstring>
3708334c51SBrooks Davis #include <stdexcept>
3808334c51SBrooks Davis #include <string>
3908334c51SBrooks Davis 
4008334c51SBrooks Davis #include <lutok/operations.hpp>
4108334c51SBrooks Davis #include <lutok/stack_cleaner.hpp>
4208334c51SBrooks Davis #include <lutok/state.ipp>
4308334c51SBrooks Davis 
4408334c51SBrooks Davis #include "utils/format/macros.hpp"
4508334c51SBrooks Davis #include "utils/fs/operations.hpp"
4608334c51SBrooks Davis #include "utils/fs/path.hpp"
4708334c51SBrooks Davis #include "utils/sanity.hpp"
4808334c51SBrooks Davis 
4908334c51SBrooks Davis namespace fs = utils::fs;
5008334c51SBrooks Davis 
5108334c51SBrooks Davis 
5208334c51SBrooks Davis namespace {
5308334c51SBrooks Davis 
5408334c51SBrooks Davis 
5508334c51SBrooks Davis /// Given a path, qualifies it with the module's start directory if necessary.
5608334c51SBrooks Davis ///
5708334c51SBrooks Davis /// \param state The Lua state.
5808334c51SBrooks Davis /// \param path The path to qualify.
5908334c51SBrooks Davis ///
6008334c51SBrooks Davis /// \return The original path if it was absolute; otherwise the original path
6108334c51SBrooks Davis /// appended to the module's start directory.
6208334c51SBrooks Davis ///
6308334c51SBrooks Davis /// \throw std::runtime_error If the module's state has been corrupted.
6408334c51SBrooks Davis static fs::path
qualify_path(lutok::state & state,const fs::path & path)6508334c51SBrooks Davis qualify_path(lutok::state& state, const fs::path& path)
6608334c51SBrooks Davis {
6708334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
6808334c51SBrooks Davis 
6908334c51SBrooks Davis     if (path.is_absolute()) {
7008334c51SBrooks Davis         return path;
7108334c51SBrooks Davis     } else {
7208334c51SBrooks Davis         state.get_global("_fs_start_dir");
7308334c51SBrooks Davis         if (!state.is_string(-1))
7408334c51SBrooks Davis             throw std::runtime_error("Missing _fs_start_dir global variable; "
7508334c51SBrooks Davis                                      "state corrupted?");
7608334c51SBrooks Davis         return fs::path(state.to_string(-1)) / path;
7708334c51SBrooks Davis     }
7808334c51SBrooks Davis }
7908334c51SBrooks Davis 
8008334c51SBrooks Davis 
8108334c51SBrooks Davis /// Safely gets a path from the Lua state.
8208334c51SBrooks Davis ///
8308334c51SBrooks Davis /// \param state The Lua state.
8408334c51SBrooks Davis /// \param index The position in the Lua stack that contains the path to query.
8508334c51SBrooks Davis ///
8608334c51SBrooks Davis /// \return The queried path.
8708334c51SBrooks Davis ///
8808334c51SBrooks Davis /// \throw fs::error If the value is not a valid path.
8908334c51SBrooks Davis /// \throw std::runtime_error If the value on the Lua stack is not convertible
9008334c51SBrooks Davis ///     to a path.
9108334c51SBrooks Davis static fs::path
to_path(lutok::state & state,const int index)9208334c51SBrooks Davis to_path(lutok::state& state, const int index)
9308334c51SBrooks Davis {
9408334c51SBrooks Davis     if (!state.is_string(index))
9508334c51SBrooks Davis         throw std::runtime_error("Need a string parameter");
9608334c51SBrooks Davis     return fs::path(state.to_string(index));
9708334c51SBrooks Davis }
9808334c51SBrooks Davis 
9908334c51SBrooks Davis 
10008334c51SBrooks Davis /// Lua binding for fs::path::basename.
10108334c51SBrooks Davis ///
10208334c51SBrooks Davis /// \pre stack(-1) The input path.
10308334c51SBrooks Davis /// \post stack(-1) The basename of the input path.
10408334c51SBrooks Davis ///
10508334c51SBrooks Davis /// \param state The Lua state.
10608334c51SBrooks Davis ///
10708334c51SBrooks Davis /// \return The number of result values, i.e. 1.
10808334c51SBrooks Davis static int
lua_fs_basename(lutok::state & state)10908334c51SBrooks Davis lua_fs_basename(lutok::state& state)
11008334c51SBrooks Davis {
11108334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
11208334c51SBrooks Davis 
11308334c51SBrooks Davis     const fs::path path = to_path(state, -1);
11408334c51SBrooks Davis     state.push_string(path.leaf_name().c_str());
11508334c51SBrooks Davis     cleaner.forget();
11608334c51SBrooks Davis     return 1;
11708334c51SBrooks Davis }
11808334c51SBrooks Davis 
11908334c51SBrooks Davis 
12008334c51SBrooks Davis /// Lua binding for fs::path::dirname.
12108334c51SBrooks Davis ///
12208334c51SBrooks Davis /// \pre stack(-1) The input path.
12308334c51SBrooks Davis /// \post stack(-1) The directory part of the input path.
12408334c51SBrooks Davis ///
12508334c51SBrooks Davis /// \param state The Lua state.
12608334c51SBrooks Davis ///
12708334c51SBrooks Davis /// \return The number of result values, i.e. 1.
12808334c51SBrooks Davis static int
lua_fs_dirname(lutok::state & state)12908334c51SBrooks Davis lua_fs_dirname(lutok::state& state)
13008334c51SBrooks Davis {
13108334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
13208334c51SBrooks Davis 
13308334c51SBrooks Davis     const fs::path path = to_path(state, -1);
13408334c51SBrooks Davis     state.push_string(path.branch_path().c_str());
13508334c51SBrooks Davis     cleaner.forget();
13608334c51SBrooks Davis     return 1;
13708334c51SBrooks Davis }
13808334c51SBrooks Davis 
13908334c51SBrooks Davis 
14008334c51SBrooks Davis /// Lua binding for fs::path::exists.
14108334c51SBrooks Davis ///
14208334c51SBrooks Davis /// \pre stack(-1) The input path.
14308334c51SBrooks Davis /// \post stack(-1) Whether the input path exists or not.
14408334c51SBrooks Davis ///
14508334c51SBrooks Davis /// \param state The Lua state.
14608334c51SBrooks Davis ///
14708334c51SBrooks Davis /// \return The number of result values, i.e. 1.
14808334c51SBrooks Davis static int
lua_fs_exists(lutok::state & state)14908334c51SBrooks Davis lua_fs_exists(lutok::state& state)
15008334c51SBrooks Davis {
15108334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
15208334c51SBrooks Davis 
15308334c51SBrooks Davis     const fs::path path = qualify_path(state, to_path(state, -1));
15408334c51SBrooks Davis     state.push_boolean(fs::exists(path));
15508334c51SBrooks Davis     cleaner.forget();
15608334c51SBrooks Davis     return 1;
15708334c51SBrooks Davis }
15808334c51SBrooks Davis 
15908334c51SBrooks Davis 
16008334c51SBrooks Davis /// Lua binding for the files iterator.
16108334c51SBrooks Davis ///
16208334c51SBrooks Davis /// This function takes an open directory from the closure of the iterator and
16308334c51SBrooks Davis /// returns the next entry.  See lua_fs_files() for the iterator generator
16408334c51SBrooks Davis /// function.
16508334c51SBrooks Davis ///
16608334c51SBrooks Davis /// \pre upvalue(1) The userdata containing an open DIR* object.
16708334c51SBrooks Davis ///
16808334c51SBrooks Davis /// \param state The lua state.
16908334c51SBrooks Davis ///
17008334c51SBrooks Davis /// \return The number of result values, i.e. 0 if there are no more entries or
17108334c51SBrooks Davis /// 1 if an entry has been read.
17208334c51SBrooks Davis static int
files_iterator(lutok::state & state)17308334c51SBrooks Davis files_iterator(lutok::state& state)
17408334c51SBrooks Davis {
17508334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
17608334c51SBrooks Davis 
17708334c51SBrooks Davis     DIR** dirp = state.to_userdata< DIR* >(state.upvalue_index(1));
17808334c51SBrooks Davis     const struct dirent* entry = ::readdir(*dirp);
17908334c51SBrooks Davis     if (entry == NULL)
18008334c51SBrooks Davis         return 0;
18108334c51SBrooks Davis     else {
18208334c51SBrooks Davis         state.push_string(entry->d_name);
18308334c51SBrooks Davis         cleaner.forget();
18408334c51SBrooks Davis         return 1;
18508334c51SBrooks Davis     }
18608334c51SBrooks Davis }
18708334c51SBrooks Davis 
18808334c51SBrooks Davis 
18908334c51SBrooks Davis /// Lua binding for the destruction of the files iterator.
19008334c51SBrooks Davis ///
19108334c51SBrooks Davis /// This function takes an open directory and closes it.  See lua_fs_files() for
19208334c51SBrooks Davis /// the iterator generator function.
19308334c51SBrooks Davis ///
19408334c51SBrooks Davis /// \pre stack(-1) The userdata containing an open DIR* object.
19508334c51SBrooks Davis /// \post The DIR* object is closed.
19608334c51SBrooks Davis ///
19708334c51SBrooks Davis /// \param state The lua state.
19808334c51SBrooks Davis ///
19908334c51SBrooks Davis /// \return The number of result values, i.e. 0.
20008334c51SBrooks Davis static int
files_gc(lutok::state & state)20108334c51SBrooks Davis files_gc(lutok::state& state)
20208334c51SBrooks Davis {
20308334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
20408334c51SBrooks Davis 
20508334c51SBrooks Davis     PRE(state.is_userdata(-1));
20608334c51SBrooks Davis 
20708334c51SBrooks Davis     DIR** dirp = state.to_userdata< DIR* >(-1);
20808334c51SBrooks Davis     // For some reason, this may be called more than once.  I don't know why
20908334c51SBrooks Davis     // this happens, but we must protect against it.
21008334c51SBrooks Davis     if (*dirp != NULL) {
21108334c51SBrooks Davis         ::closedir(*dirp);
21208334c51SBrooks Davis         *dirp = NULL;
21308334c51SBrooks Davis     }
21408334c51SBrooks Davis 
21508334c51SBrooks Davis     return 0;
21608334c51SBrooks Davis }
21708334c51SBrooks Davis 
21808334c51SBrooks Davis 
21908334c51SBrooks Davis /// Lua binding to create an iterator to scan the contents of a directory.
22008334c51SBrooks Davis ///
22108334c51SBrooks Davis /// \pre stack(-1) The input path.
22208334c51SBrooks Davis /// \post stack(-1) The iterator function.
22308334c51SBrooks Davis ///
22408334c51SBrooks Davis /// \param state The Lua state.
22508334c51SBrooks Davis ///
22608334c51SBrooks Davis /// \return The number of result values, i.e. 1.
22708334c51SBrooks Davis static int
lua_fs_files(lutok::state & state)22808334c51SBrooks Davis lua_fs_files(lutok::state& state)
22908334c51SBrooks Davis {
23008334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
23108334c51SBrooks Davis 
23208334c51SBrooks Davis     const fs::path path = qualify_path(state, to_path(state, -1));
23308334c51SBrooks Davis 
23408334c51SBrooks Davis     DIR** dirp = state.new_userdata< DIR* >();
23508334c51SBrooks Davis 
23608334c51SBrooks Davis     state.new_table();
23708334c51SBrooks Davis     state.push_string("__gc");
23808334c51SBrooks Davis     state.push_cxx_function(files_gc);
23908334c51SBrooks Davis     state.set_table(-3);
24008334c51SBrooks Davis 
24108334c51SBrooks Davis     state.set_metatable(-2);
24208334c51SBrooks Davis 
24308334c51SBrooks Davis     *dirp = ::opendir(path.c_str());
24408334c51SBrooks Davis     if (*dirp == NULL) {
24508334c51SBrooks Davis         const int original_errno = errno;
24608334c51SBrooks Davis         throw std::runtime_error(F("Failed to open directory: %s") %
24708334c51SBrooks Davis                                  std::strerror(original_errno));
24808334c51SBrooks Davis     }
24908334c51SBrooks Davis 
25008334c51SBrooks Davis     state.push_cxx_closure(files_iterator, 1);
25108334c51SBrooks Davis 
25208334c51SBrooks Davis     cleaner.forget();
25308334c51SBrooks Davis     return 1;
25408334c51SBrooks Davis }
25508334c51SBrooks Davis 
25608334c51SBrooks Davis 
25708334c51SBrooks Davis /// Lua binding for fs::path::is_absolute.
25808334c51SBrooks Davis ///
25908334c51SBrooks Davis /// \pre stack(-1) The input path.
26008334c51SBrooks Davis /// \post stack(-1) Whether the input path is absolute or not.
26108334c51SBrooks Davis ///
26208334c51SBrooks Davis /// \param state The Lua state.
26308334c51SBrooks Davis ///
26408334c51SBrooks Davis /// \return The number of result values, i.e. 1.
26508334c51SBrooks Davis static int
lua_fs_is_absolute(lutok::state & state)26608334c51SBrooks Davis lua_fs_is_absolute(lutok::state& state)
26708334c51SBrooks Davis {
26808334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
26908334c51SBrooks Davis 
27008334c51SBrooks Davis     const fs::path path = to_path(state, -1);
27108334c51SBrooks Davis 
27208334c51SBrooks Davis     state.push_boolean(path.is_absolute());
27308334c51SBrooks Davis     cleaner.forget();
27408334c51SBrooks Davis     return 1;
27508334c51SBrooks Davis }
27608334c51SBrooks Davis 
27708334c51SBrooks Davis 
27808334c51SBrooks Davis /// Lua binding for fs::path::operator/.
27908334c51SBrooks Davis ///
28008334c51SBrooks Davis /// \pre stack(-2) The first input path.
28108334c51SBrooks Davis /// \pre stack(-1) The second input path.
28208334c51SBrooks Davis /// \post stack(-1) The concatenation of the two paths.
28308334c51SBrooks Davis ///
28408334c51SBrooks Davis /// \param state The Lua state.
28508334c51SBrooks Davis ///
28608334c51SBrooks Davis /// \return The number of result values, i.e. 1.
28708334c51SBrooks Davis static int
lua_fs_join(lutok::state & state)28808334c51SBrooks Davis lua_fs_join(lutok::state& state)
28908334c51SBrooks Davis {
29008334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
29108334c51SBrooks Davis 
29208334c51SBrooks Davis     const fs::path path1 = to_path(state, -2);
29308334c51SBrooks Davis     const fs::path path2 = to_path(state, -1);
29408334c51SBrooks Davis     state.push_string((path1 / path2).c_str());
29508334c51SBrooks Davis     cleaner.forget();
29608334c51SBrooks Davis     return 1;
29708334c51SBrooks Davis }
29808334c51SBrooks Davis 
29908334c51SBrooks Davis 
30008334c51SBrooks Davis }  // anonymous namespace
30108334c51SBrooks Davis 
30208334c51SBrooks Davis 
30308334c51SBrooks Davis /// Creates a Lua 'fs' module with a default start directory of ".".
30408334c51SBrooks Davis ///
30508334c51SBrooks Davis /// \post The global 'fs' symbol is set to a table that contains functions to a
30608334c51SBrooks Davis /// variety of utilites from the fs C++ module.
30708334c51SBrooks Davis ///
30808334c51SBrooks Davis /// \param s The Lua state.
30908334c51SBrooks Davis void
open_fs(lutok::state & s)31008334c51SBrooks Davis fs::open_fs(lutok::state& s)
31108334c51SBrooks Davis {
31208334c51SBrooks Davis     open_fs(s, fs::current_path());
31308334c51SBrooks Davis }
31408334c51SBrooks Davis 
31508334c51SBrooks Davis 
31608334c51SBrooks Davis /// Creates a Lua 'fs' module with an explicit start directory.
31708334c51SBrooks Davis ///
31808334c51SBrooks Davis /// \post The global 'fs' symbol is set to a table that contains functions to a
31908334c51SBrooks Davis /// variety of utilites from the fs C++ module.
32008334c51SBrooks Davis ///
32108334c51SBrooks Davis /// \param s The Lua state.
32208334c51SBrooks Davis /// \param start_dir The start directory to use in all operations that reference
32308334c51SBrooks Davis ///     the underlying file sytem.
32408334c51SBrooks Davis void
open_fs(lutok::state & s,const fs::path & start_dir)32508334c51SBrooks Davis fs::open_fs(lutok::state& s, const fs::path& start_dir)
32608334c51SBrooks Davis {
32708334c51SBrooks Davis     lutok::stack_cleaner cleaner(s);
32808334c51SBrooks Davis 
32908334c51SBrooks Davis     s.push_string(start_dir.str());
33008334c51SBrooks Davis     s.set_global("_fs_start_dir");
33108334c51SBrooks Davis 
33208334c51SBrooks Davis     std::map< std::string, lutok::cxx_function > members;
33308334c51SBrooks Davis     members["basename"] = lua_fs_basename;
33408334c51SBrooks Davis     members["dirname"] = lua_fs_dirname;
33508334c51SBrooks Davis     members["exists"] = lua_fs_exists;
33608334c51SBrooks Davis     members["files"] = lua_fs_files;
33708334c51SBrooks Davis     members["is_absolute"] = lua_fs_is_absolute;
33808334c51SBrooks Davis     members["join"] = lua_fs_join;
33908334c51SBrooks Davis     lutok::create_module(s, "fs", members);
34008334c51SBrooks Davis }
341