====== lua_fs - std::filesystem for Lua ====== `lua_fs` is a Lua loadable shared library that exposes the C++17 `std::filesystem` library through [sol2](https://github.com/ThePhD/sol2). Paths can be passed as ordinary Lua strings or as `fs.path` objects. Filesystem failures raise a Lua error containing the operating-system error message. ===== API Reference ===== All examples assume: ```lua local fs = require("lua_fs") ``` Paths may be Lua strings or `fs.path` objects. A function that encounters an operating-system error raises a Lua error. Boolean query functions return `false` for a missing path unless the underlying operation itself fails. ===== fs.path ===== ''fs.path(value)'' constructs a path object. `value` is optional for an empty path or can be a string/path value. Path objects are useful when several lexical operations are chained; filesystem functions accept them anywhere a string is accepted. ```lua local path = fs.path("logs/app.2026.txt") print(path:parent_path(), path:stem(), path:extension()) ``` ==== Path methods ==== ^ Method ^ Description and result ^ Example ^ | `path:string()` | Returns the platform-native path representation as a string. | `print(path:string())` | | `path:generic_string()` | Returns a portable representation using `/` separators. | `print(path:generic_string())` | | `path:filename()` | Returns the final path component, or an empty string. | `local name = path:filename()` | | `path:parent_path()` | Returns the lexical parent path. | `local folder = path:parent_path()` | | `path:root_name()` | Returns a root name such as `C:` on Windows. | `print(path:root_name())` | | `path:root_directory()` | Returns the root directory component, such as `/` or `\\`. | `print(path:root_directory())` | | `path:relative_path()` | Returns the path with its root components removed. | `print(path:relative_path())` | | `path:stem()` | Returns the filename without its final extension. | `print(path:stem())` | | `path:extension()` | Returns the final extension, including its dot, or an empty string. | `assert(path:extension() == ".txt")` | | `path:lexically_normal()` | Returns a normalized path without accessing the filesystem. | `local clean = path:lexically_normal()` | | `path:lexically_relative(base)` | Returns the lexical path from `base` to `path`; it can be empty when no relative form exists. | `print(path:lexically_relative("logs"))` | | `path:has_root_name()` | Returns whether the path has a root name. | `if path:has_root_name() then ... end` | | `path:has_root_directory()` | Returns whether the path has a root directory. | `print(path:has_root_directory())` | | `path:has_parent_path()` | Returns whether a parent component exists. | `print(path:has_parent_path())` | | `path:has_filename()` | Returns whether a filename component exists. | `print(path:has_filename())` | | `path:has_stem()` | Returns whether a stem component exists. | `print(path:has_stem())` | | `path:has_extension()` | Returns whether an extension component exists. | `print(path:has_extension())` | | `path:is_absolute()` | Returns whether the path is absolute according to the host platform. | `print(path:is_absolute())` | | `path:is_relative()` | Returns whether the path is not absolute. | `print(path:is_relative())` | | `path:empty()` | Returns whether the path contains no characters. | `assert(not path:empty())` | | `path:append(value)` | Appends a path component using the platform separator and returns the same path object. | `path:append("archive")` | | `path:concat(value)` | Concatenates text without inserting a separator and returns the same path object. | `path:concat(".bak")` | ==== Path and environment functions ==== ^ Function ^ Description and result ^ Example ^ | `fs.absolute(path)` | Returns an absolute path without requiring the target to exist. | `print(fs.absolute("data/input.txt"))` | | `fs.canonical(path)` | Returns the fully resolved absolute path. Every component must exist. | `print(fs.canonical("data"))` | | `fs.weakly_canonical(path)` | Resolves existing components and permits a non-existing final portion. | `print(fs.weakly_canonical("data/new.txt"))` | | `fs.relative(path, base)` | Returns the path from `base` to `path`, using the current directory for omitted filesystem context. | `print(fs.relative("data/a.txt", "data"))` | | `fs.proximate(path, base)` | Returns `relative(path, base)`, or an absolute path when a relative result is not possible. | `print(fs.proximate("data/a.txt", "data"))` | | `fs.current_path()` | Returns the process working directory. | `print(fs.current_path())` | | `fs.set_current_path(path)` | Changes the process working directory. Returns no value. | `fs.set_current_path("work")` | | `fs.temp_directory_path()` | Returns the operating system temporary directory. | `local temp = fs.temp_directory_path()` | | `fs.directory(path)` | Returns a 1-based Lua array of immediate child paths. | for _, p in ipairs(fs.directory("data")) do print(p) end | | `fs.recursive_directory(path)` | Returns a 1-based Lua array containing all descendants recursively. | for _, p in ipairs(fs.recursive_directory("data")) do print(p) end | ==== Queries and metadata ==== ^ Function ^ Description and result ^ Example ^ | `fs.exists(path)` | Returns `true` when the path exists, including a symlink target check. | `if fs.exists("config.lua") then ... end` | | `fs.equivalent(left, right)` | Returns whether two existing paths refer to the same filesystem object. | `print(fs.equivalent("a", "b"))` | | `fs.status(path)` | Returns metadata after following symlinks. | `local info = fs.status("file.txt")` | | `fs.symlink_status(path)` | Returns metadata for the link itself without following a symlink. | `local info = fs.symlink_status("link")` | | `fs.is_block_file(path)` | Returns whether the path is a block device. | `print(fs.is_block_file(path))` | | `fs.is_character_file(path)` | Returns whether the path is a character device. | `print(fs.is_character_file(path))` | | `fs.is_fifo(path)` | Returns whether the path is a named pipe/FIFO. | `print(fs.is_fifo(path))` | | `fs.is_other(path)` | Returns whether the path exists but is not a regular file, directory, or symlink. | `print(fs.is_other(path))` | | `fs.is_socket(path)` | Returns whether the path is a socket where the platform supports that type. | `print(fs.is_socket(path))` | | `fs.is_symlink(path)` | Returns whether the path is a symbolic link. | `print(fs.is_symlink(path))` | | `fs.is_regular_file(path)` | Returns whether the path is a regular file. | `assert(fs.is_regular_file("file.txt"))` | | `fs.is_directory(path)` | Returns whether the path is a directory. | `if fs.is_directory(path) then ... end` | | `fs.file_size(path)` | Returns the file size in bytes. The path must refer to a regular file. | `print(fs.file_size("file.txt"))` | | `fs.hard_link_count(path)` | Returns the number of hard links to the filesystem object. | `print(fs.hard_link_count("file.txt"))` | | `fs.last_write_time(path)` | Returns the last modification time as Unix seconds. | `print(os.date("%F", fs.last_write_time(path)))` | | `fs.set_last_write_time(path, seconds)` | Sets the last modification time from Unix seconds. Returns no value. | `fs.set_last_write_time(path, os.time())` | | `fs.space(path)` | Returns `{ capacity, free, available }`, measured in bytes, for the filesystem containing `path`. | `print(fs.space(".").available)` | `status` and `symlink_status` return a table with these fields: ```lua local info = fs.status("file.txt") print(info.type, info.permissions) print(info.exists, info.is_regular_file, info.is_directory, info.is_symlink) ``` `type` is the numeric C++ file-type value and `permissions` is the numeric permission bitmask. Use the `fs.perms` constants below when interpreting or changing that mask. ==== Filesystem mutations ==== ^ Function ^ Description and result ^ Example ^ | `fs.create_directory(path)` | Creates one directory. Returns `true` if created and `false` if it already existed. | `fs.create_directory("output")` | | `fs.create_directories(path)` | Creates a directory and any missing parents. Returns `true` if anything was created. | `fs.create_directories("output/images")` | | `fs.remove(path)` | Removes one file, empty directory, or symlink. Returns `true` if removed. | `fs.remove("output/old.txt")` | | `fs.remove_all(path)` | Recursively removes a path and its contents. Returns the number of removed filesystem objects. | `print(fs.remove_all("output"))` | | `fs.rename(from, to)` | Renames or moves a filesystem object. Returns no value. | `fs.rename("draft.txt", "final.txt")` | | `fs.copy(from, to, options)` | Copies a file or directory according to `fs.copy_options`. Returns no value. | `fs.copy("assets", "backup", fs.copy_options.recursive)` | | `fs.copy_file(from, to, options)` | Copies one file and returns `true` when the copy succeeds. | `fs.copy_file("a.txt", "b.txt", fs.copy_options.overwrite_existing)` | | `fs.resize_file(path, size)` | Changes a regular file to `size` bytes. Returns no value. | `fs.resize_file("cache.bin", 0)` | | `fs.create_hard_link(existing, link)` | Creates a hard link named `link` to `existing`. Returns no value. | `fs.create_hard_link("a.txt", "a-link.txt")` | | `fs.create_symlink(existing, link)` | Creates a symbolic link. On Windows, the process may need symlink privileges. Returns no value. | `fs.create_symlink("a.txt", "a-link.txt")` | | `fs.create_directory_symlink(existing, link)` | Creates a symbolic link specifically for a directory. Returns no value. | `fs.create_directory_symlink("assets", "assets-link")` | | `fs.permissions(path, bits, options)` | Replaces, adds, or removes permission bits. Use `fs.perms` and `fs.perm_options`. Returns no value. | `fs.permissions("private.txt", fs.perms.owner_read, fs.perm_options.replace)` | ### Option and permission constants `fs.copy_options` contains the flags accepted by `copy` and `copy_file`: ^ Constant ^ Meaning ^ | `none` | Use default copy behavior. | | `skip_existing` | Keep an existing destination. | | `overwrite_existing` | Replace an existing destination. | | `update_existing` | Replace only when the source is newer. | | `recursive` | Copy directory contents recursively. | | `copy_symlinks` | Copy symlinks as symlinks. | | `skip_symlinks` | Do not copy symlinks. | | `directories_only` | Copy directory structure without regular files. | | `create_symlinks` | Create symlinks instead of copies. | | `create_hard_links` | Create hard links instead of copies. | `fs.perms` contains the standard permission bits: `owner_read`, `owner_write`, `owner_exec`, `group_read`, `group_write`, `group_exec`, `others_read`, `others_write`, `others_exec`, `all`, `set_uid`, `set_gid`, and `sticky_bit`. `fs.perm_options` contains `replace`, `add`, and `remove`: ```lua fs.permissions("script.sh", fs.perms.owner_exec, fs.perm_options.add) fs.permissions("script.sh", fs.perms.others_write, fs.perm_options.remove) ```