From b6f9c5d62bcc494db1fb3061a0b2aa5df55bc72b Mon Sep 17 00:00:00 2001 From: pysan3 Date: Thu, 16 Nov 2023 17:47:59 +0900 Subject: [PATCH 1/9] feat(base): Implement posix methods. WIP --- lua/pathlib/base.lua | 394 ++++++++++++++++++++++++++++++++++- lua/pathlib/const.lua | 105 ++++++++++ lua/pathlib/posix.lua | 18 ++ lua/pathlib/utils/errors.lua | 26 ++- 4 files changed, 531 insertions(+), 12 deletions(-) diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index c9a0011..1d9b4a9 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -20,11 +20,9 @@ setmetatable(Path, { end, }) ----Create a new Path object +---Private init method to create a new Path object ---@param ... string | PathlibPath # List of string and Path objects ----@return PathlibPath -function Path.new(...) - local self = setmetatable({}, Path) +function Path:_init(...) self._raw_paths = utils.lists.str_list.new() self._drive_name = "" self.__windows_panic = false @@ -55,15 +53,53 @@ function Path.new(...) ::continue:: end self:__clean_paths_list() +end + +---Create a new Path object +---@param ... string | PathlibPath # List of string and Path objects +---@return PathlibPath +function Path.new(...) + local self = setmetatable({}, Path) + self:_init(...) return self end +---Create a new Path object as self's child. +---@param ... string +---@return PathlibPath +function Path:new_child(...) + local new = Path.new_all_from(self) + new._raw_paths:extend({ ... }) + return new +end + +---Unpack name and return a new self's child +---@param name string +---@return PathlibPath +function Path:new_child_unpack(name) + local new = Path.new_all_from(self) + for sub in name:gmatch("[/\\]") do + new._raw_paths:append(sub) + end + return new +end + ---Return `vim.fn.getcwd` in Path object ---@return PathlibPath function Path.cwd() return Path(vim.fn.getcwd()) end +---Calculate permission integer from "rwxrwxrwx" notation. +---@param mode_string string +---@return integer +function Path.permission(mode_string) + err.assert_function("Path.permission", function() + return const.check_permission_string(mode_string) + end, "mode_string must be in the form of `rwxrwxrwx` or `-` otherwise.") + return const.permission_from_string(mode_string) +end + function Path:__clean_paths_list() self._raw_paths:filter_internal(nil, 2) if #self._raw_paths > 1 and self._raw_paths[1] == "." then @@ -123,7 +159,7 @@ function Path:__le(other) return (self < other) or (self == other) end ----Concat paths. `Path.cwd() / "foo" / "bar.txt" == "./foo/bar.txt"` +---Concatenate paths. `Path.cwd() / "foo" / "bar.txt" == "./foo/bar.txt"` ---@param other PathlibPath ---@return PathlibPath function Path:__div(other) @@ -134,8 +170,8 @@ function Path:__div(other) return self.new(self, other) end ----Concat paths with the parent of lhs. `Path("./foo/foo.txt") .. "bar.txt" == "./foo/bar.txt"` ----@param other PathlibPath +---Concatenate paths with the parent of lhs. `Path("./foo/foo.txt") .. "bar.txt" == "./foo/bar.txt"` +---@param other PathlibPath | string ---@return PathlibPath -- Path.__concat = function(self, other) function Path:__concat(other) @@ -205,12 +241,21 @@ function Path:copy_all_from(path) self._raw_paths:filter_internal(nil, 2) end +---Copy all attributes from `path` to self +---@param path PathlibPath +function Path.new_all_from(path) + local self = setmetatable({}, Path) + self.mytype = path.mytype + self._drive_name = path._drive_name + self._raw_paths:extend(path._raw_paths) + return self +end + ---Inherit from `path` and trim `_raw_paths` if specified. ---@param path PathlibPath ---@param trim_num number? # 1 will trim the last entry in `_raw_paths`, 2 will trim 2. function Path.new_from(path, trim_num) - local self = Path.new() - self:copy_all_from(path) + local self = Path.new_all_from(path) if not trim_num or trim_num < 1 then return self end @@ -263,4 +308,335 @@ function Path:modify(mods) return vim.fn.fnamemodify(tostring(self), mods) end +---Call `fs_stat` with callback. This plugin will not help you here. +---@param follow_symlinks boolean? # Whether to resolve symlinks +---@param callback fun(err: string?, stat: uv.aliases.fs_stat_table?) +function Path:stat_async(follow_symlinks, callback) + err.check_and_raise_typeerror("Path:stat_async", callback, "function") + if follow_symlinks then + luv.fs_stat(tostring(self), callback) + else + luv.fs_lstat(tostring(self), callback) ---@diagnostic disable-line + end +end + +---Return result of `luv.fs_stat`. Use `self:stat_async` to use with callback. +---Returns: `fs_stat_table | (nil, err_name: string, err_msg: string)` +---@param follow_symlinks? boolean # Whether to resolve symlinks +---@return uv.aliases.fs_stat_table|nil stat, string? err_name, string? err_msg +---@nodiscard +function Path:stat(follow_symlinks) + if follow_symlinks then + return luv.fs_stat(tostring(self)) + else + return luv.fs_lstat(tostring(self)) ---@diagnostic disable-line + end +end + +function Path:lstat() + return self:stat(false) +end + +function Path:exists(follow_symlinks) + local stat = self:stat(follow_symlinks) + return stat and true or false +end + +function Path:is_dir(follow_symlinks) + local stat = self:stat(follow_symlinks) + return stat and stat.type == "directory" +end + +function Path:is_file(follow_symlinks) + local stat = self:stat(follow_symlinks) + return stat and stat.type == "file" +end + +function Path:is_symlink() + local stat = self:lstat() + return stat and stat.type == "link" +end + +---Get mode of path object. Use `self:get_type` to get type description in string instead. +---@param follow_symlinks boolean # Whether to resolve symlinks +---@return PathlibModeEnum? +function Path:get_mode(follow_symlinks) + local stat = self:stat(follow_symlinks) + return stat and stat.mode +end + +---Get type description of path object. Use `self:get_mode` to get mode instead. +---@param follow_symlinks boolean # Whether to resolve symlinks +---@return string? +function Path:get_type(follow_symlinks) + local stat = self:stat(follow_symlinks) + return stat and stat.type +end + +---Return whether `other` is the same file or not. +---@param other PathlibPath +---@return boolean +function Path:samefile(other) + local stat = self:stat() + local other_stat = other:stat() + return (stat and other_stat) and (stat.ino == other_stat.ino and stat.dev == stat.dev) or false +end + +function Path:is_mount() + if not self:exists() or not self:is_dir() then + return false + end + local stat = self:stat() + if not stat then + return false + end + local parent_stat = self:parent():stat() + if not parent_stat then + return false + end + if stat.dev ~= parent_stat.dev then + return false + end + return stat.ino and stat.ino == parent_stat.ino +end + +---Make directory. When `recursive` is true, will create parent dirs like shell command `mkdir -p` +---@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx" +---@param recursive boolean # if true, creates parent directories as well +function Path:mkdir(mode, recursive) + if recursive then + for parent in self:parents() do + if not parent:exists(true) then + parent:mkdir(mode, true) + else + break + end + end + end + luv.fs_mkdir(tostring(self), mode) +end + +---Make file. When `recursive` is true, will create parent dirs like shell command `mkdir -p` +---@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx" +---@param recursive boolean # if true, creates parent directories as well +---@return boolean success, string? err_name, string? err_msg # true if successfully created. +function Path:touch(mode, recursive) + local fd, err_name, err_msg = self:open("w", mode, recursive) + if fd == nil then + return false, err_name, err_msg + else + luv.fs_close(fd) + return true + end +end + +---Create a simlink named `self` pointing to `target` +---@param target PathlibPath +function Path:symlink_to(target) + -- TODO: Not Implemented + error("Not Implemented") +end + +---Create a hardlink named `self` pointing to `target` +---@param target PathlibPath +function Path:hardlink_to(target) + -- TODO: Not Implemented + error("Not Implemented") +end + +---Rename `self` to `target`. If `target` exists, fails with false. Ref: `Path:move` +---@param target PathlibPath +function Path:rename(target) + -- TODO: Not Implemented + error("Not Implemented") +end + +---Move `self` to `target`. Overwrites `target` if exists. Ref: `Path:rename` +---@param target PathlibPath +function Path:move(target) + -- TODO: Not Implemented + error("Not Implemented") +end + +---@deprecated Use `Path:move` instead. +---@param target PathlibPath +function Path:replace(target) + return self:move(target) +end + +---Resolves path. Eliminates `../` representation. +---Changes internal. (See `Path:resolve_copy` to create new object) +function Path:resolve() + -- TODO: Not Implemented + error("Not Implemented") +end + +---Resolves path. Eliminates `../` representation and returns a new object. `self` is not changed. +---@return PathlibPath +function Path:resolve_copy() + -- TODO: Not Implemented + error("Not Implemented") +end + +---Change the permission of the path to `mode`. +---@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx" +---@param follow_symlinks boolean # Whether to resolve symlinks +---@return boolean|nil success, string? err_name, string? err_msg +function Path:chmod(mode, follow_symlinks) + if follow_symlinks then + return luv.fs_chmod(tostring(self:resolve()), mode) + else + return luv.fs_chmod(tostring(self), mode) + end +end + +---Remove this file or link. If the path is a directory, use `Path:rmdir()` instead. +---@param missing_ok boolean +function Path:unlink(missing_ok) + -- TODO: Not Implemented + error("Not Implemented") +end + +---Remove this directory. The directory must be empty. +function Path:rmdir() + -- TODO: Not Implemented + error("Not Implemented") +end + +---Return the login name of the file owner. +function Path:owner() + -- TODO: Not Implemented + error("Not Implemented") +end + +---Return the group name of the file GID. +function Path:group() + -- TODO: Not Implemented + error("Not Implemented") +end + +---Call `luv.fs_open`. Use `self:open_async` to use with callback. +---@param flags uv.aliases.fs_access_flags|integer +---@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx". +---@param ensure_dir integer|boolean|nil # if not nil, runs `mkdir -p self:parent()` with permission to ensure parent exists. +--- `true` will default to 755. +---@return integer|nil fd, string? err_name, string? err_msg +---@nodiscard +function Path:open(flags, mode, ensure_dir) + if ensure_dir == true then + ensure_dir = const.permission_from_string("rwxr-xr-x") + end + if type(ensure_dir) == "integer" then + self:parent():mkdir(ensure_dir, true) + end + return luv.fs_open(tostring(self), flags, mode) +end + +---Call `luv.fs_open` with callback. Use `self:open` for sync version. +---@param flags uv.aliases.fs_access_flags|integer +---@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx". +---@param ensure_dir integer|boolean|nil # if not nil, runs `mkdir -p self:parent()` with permission to ensure parent exists. +--- `true` will default to 755. +---@param callback fun(err: nil|string, fd: integer|nil) +---@return uv_fs_t +function Path:open_async(flags, mode, ensure_dir, callback) + if ensure_dir == true then + ensure_dir = const.permission_from_string("rwxr-xr-x") + end + if type(ensure_dir) == "integer" then + self:parent():mkdir(ensure_dir, true) + end + return luv.fs_open(tostring(self), flags, mode, callback) +end + +---Call `luv.fs_read`. Use `self:open_async` and `luv.read` to use with callback. +---@param size integer +---@param offset integer|nil +---@return string|nil data, string? err_name, string? err_msg +---@nodiscard +function Path:read(size, offset) + local flags = luv.constants.O_RDONLY + local fd, open_err, open_err_msg = self:open(flags, 0) + if fd == nil then + return nil, open_err, open_err_msg + end + local data, err_name, err_msg = luv.fs_read(fd, size, offset) + luv.fs_close(fd) + return data, err_name, err_msg ---@diagnostic disable-line +end + +---Call `luv.fs_write`. Use `self:open_async` and `luv.write` to use with callback. If failed, returns nil +---@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx". Overwrites S_IWRITE to true. +---@param data uv.aliases.buffer +---@param offset integer|nil +---@return integer|nil bytes, string? err_name, string? err_msg +---@nodiscard +function Path:write(mode, data, offset) + local flags = luv.constants.O_CREAT + luv.constants.O_RDWR + luv.constants.O_TRUNC + local fd, open_err, open_err_msg = + self:open(flags, const.bitoper(mode, const.fs_permission_enum.S_IWRITE, const.bitops.OR)) + if fd == nil then + return nil, open_err, open_err_msg + end + local bytes, err_name, err_msg = luv.fs_write(fd, data, offset) + luv.fs_close(fd) + return bytes, err_name, err_msg ---@diagnostic disable-line +end + +---Alias to `vim.fs.dir` but returns PathlibPath objects. +---@param opts table|nil Optional keyword arguments: +--- - depth: integer|nil How deep the traverse (default 1) +--- - skip: (fun(dir_name: string): boolean)|nil Predicate +--- to control traversal. Return false to stop searching the current directory. +--- Only useful when depth > 1 +--- +---@return fun(): PathlibPath?, string? # items in {self}. Each iteration yields two values: "path" and "type". +--- "path" is the PathlibPath object. +--- "type" is one of the following: +--- "file", "directory", "link", "fifo", "socket", "char", "block", "unknown". +function Path:iterdir(opts) + local generator = vim.fs.dir(tostring(self), opts) + return function() + local name, fs_type = generator() + if name ~= nil then + return self:new_child(unpack(vim.split(name:gsub("\\", "/"), "/", { plain = true, trimempty = false }))), fs_type + end + end +end + +---Iterate directory with callback receiving PathlibPath objects +---@param callback fun(path: PathlibPath, fs_type: uv.aliases.fs_stat_types): boolean? # function called for each child in directory +--- When `callback` returns `false` the iteration will break out. +---@param on_error? fun(err: string) # function called when `luv.fs_scandir` fails +---@param on_exit? fun(count: integer) # function called after the scan has finished. `count` gives the number of children +function Path:iterdir_async(callback, on_error, on_exit) + luv.fs_scandir(tostring(self), function(e, handler) + if e or not handler then + if on_error and e then + on_error(e) + end + return + end + local counter = 0 + while true do + local name, fs_type = luv.fs_scandir_next(handler) + if not name or not fs_type then + break + end + counter = counter + 1 + if callback(self:new_child_unpack(name), fs_type) == false then + break + end + end + if on_exit then + on_exit(counter) + end + end) +end + +function Path:glob(pattern, follow_symlinks) + -- TODO: Implement glob + error("Not Implemented") +end + return Path diff --git a/lua/pathlib/const.lua b/lua/pathlib/const.lua index b8a2509..60b41e7 100644 --- a/lua/pathlib/const.lua +++ b/lua/pathlib/const.lua @@ -1,5 +1,27 @@ local M = {} +---@enum PathlibBitOps +M.bitops = { + OR = 1, + XOR = 3, + AND = 4, +} + +---Bitwise operator for uint32 +---@param a integer +---@param b integer +---@param oper PathlibBitOps +---@return integer +M.bitoper = function(a, b, oper) + local s + local r, m = 0, 2 ^ 31 + repeat + s, a, b = a + b + m, a % m, b % m + r, m = r + m * oper % (s - a - b), m / 2 + until m < 1 + return r +end + ---@enum PathlibPathEnum M.path_module_enum = { PathlibPath = "PathlibPath", @@ -7,4 +29,87 @@ M.path_module_enum = { PathlibWindows = "PathlibWindows", } +---Return the portion of the file's mode that can be set by os.chmod() +---@param mode integer +---@return integer +M.fs_imode = function(mode) + return M.bitoper(mode, tonumber("0o7777", 8), M.bitops.AND) +end + +---Return the portion of the file's mode that can be set by os.chmod() +---@param mode integer +---@return integer +M.fs_ifmt = function(mode) + return M.bitoper(mode, tonumber("0o170000", 8), M.bitops.AND) +end + +---@enum PathlibModeEnum +M.fs_mode_enum = { + S_IFDIR = 16384, -- 0o040000 # directory + S_IFCHR = 8192, -- 0o020000 # character device + S_IFBLK = 24576, -- 0o060000 # block device + S_IFREG = 32768, -- 0o100000 # regular file + S_IFIFO = 4096, -- 0o010000 # fifo (named pipe) + S_IFLNK = 40960, -- 0o120000 # symbolic link + S_IFSOCK = 49152, -- 0o140000 # socket file +} + +---@enum PathlibPermissionEnum +M.fs_permission_enum = { + S_ISUID = 2048, -- 0o4000 # set UID bit + S_ISGID = 1024, -- 0o2000 # set GID bit + S_ENFMT = 1024, -- S_ISGID # file locking enforcement + S_ISVTX = 512, -- 0o1000 # sticky bit + S_IREAD = 256, -- 0o0400 # Unix V7 synonym for S_IRUSR + S_IWRITE = 128, -- 0o0200 # Unix V7 synonym for S_IWUSR + S_IEXEC = 64, -- 0o0100 # Unix V7 synonym for S_IXUSR + S_IRWXU = 448, -- 0o0700 # mask for owner permissions + S_IRUSR = 256, -- 0o0400 # read by owner + S_IWUSR = 128, -- 0o0200 # write by owner + S_IXUSR = 64, -- 0o0100 # execute by owner + S_IRWXG = 56, -- 0o0070 # mask for group permissions + S_IRGRP = 32, -- 0o0040 # read by group + S_IWGRP = 16, -- 0o0020 # write by group + S_IXGRP = 8, -- 0o0010 # execute by group + S_IRWXO = 7, -- 0o0007 # mask for others (not in group) permissions + S_IROTH = 4, -- 0o0004 # read by others + S_IWOTH = 2, -- 0o0002 # write by others + S_IXOTH = 1, -- 0o0001 # execute by others +} + +---Check if `mode_string` is a valid representation of permission string. (Eg `rwxrwxrwx`) +---@param mode_string string # "rwxrwxrwx" or '-' where permission not allowed +---@return boolean +M.check_permission_string = function(mode_string) + if type(mode_string) ~= "string" then + return false + end + if #mode_string ~= 9 then + return false + end + local modes = { "r", "w", "x" } + local index = 1 + for value in mode_string:gmatch(".") do + if value ~= "-" or modes[index % 3] ~= value then + return false + end + index = index + 1 + end + return true +end + +---Return integer of permission representing. Assert `M.check_permission_string` beforehand or this function will not work as expected. +---@param mode_string string +---@return integer +M.permission_from_string = function(mode_string) + local result = 0 + for value in mode_string:gmatch(".") do + result = result * 2 + if value ~= "-" then + result = result + 1 + end + end + return result +end + return M diff --git a/lua/pathlib/posix.lua b/lua/pathlib/posix.lua index e69de29..38d4c28 100644 --- a/lua/pathlib/posix.lua +++ b/lua/pathlib/posix.lua @@ -0,0 +1,18 @@ +local Path = require("pathlib.base") + +---@class PathlibPosixPath +local PosixPath = {} +PosixPath.__index = PosixPath +setmetatable(PosixPath, { + __index = Path, + __call = function(cls, ...) + local self = setmetatable({}, cls) + self:_init(...) + return self + end, +}) + +function PosixPath:_init(...) + Path._init(self, ...) + -- TODO: PosixPath specific init procs +end diff --git a/lua/pathlib/utils/errors.lua b/lua/pathlib/utils/errors.lua index 023aef5..0fa54b4 100644 --- a/lua/pathlib/utils/errors.lua +++ b/lua/pathlib/utils/errors.lua @@ -1,14 +1,34 @@ local M = {} ---Raise ValueError ----@param func_name string # function name where error is raised +---@param annotation string # function name where error is raised ---@param object any # Object with wrong value. -M.value_error = function(func_name, object) +M.value_error = function(annotation, object) local type_msg = type(object) if type(object) == "table" then type_msg = type_msg .. (" (mytype=%s)"):format(object.mytype) end - error(("PathlibPath: ValueError: %s called against unknown type: %s"):format(func_name, type_msg), 2) + error(("PathlibPath: ValueError (%s): called against unknown type: %s"):format(annotation, type_msg), 2) +end + +---Raise TypeError +---@param annotation string # function name where error is raised +---@param object any # Object with wrong value. +M.check_and_raise_typeerror = function(annotation, object, expected_type) + local type_msg = type(object) + if type(object) ~= expected_type then + error(("PathlibPath: TypeError (%s). Expected type %s but got %s"):format(annotation, type_msg, expected_type), 2) + end +end + +---Run assert function and raise error. +---@param annotation string # function name where error is raised +---@param assert_func fun(): boolean # assert function to run +---@param description string # description of the error +M.assert_function = function(annotation, assert_func, description) + if not assert_func() then + error(("PathlibPath: AssertionError (%s). %s"):format(annotation, description), 2) + end end return M From 062a8b125a38e1ebf5e54eca6a528401be687019 Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 00:45:33 +0900 Subject: [PATCH 2/9] feat(base): resolve file path with `../` on new --- lua/pathlib/base.lua | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index 1d9b4a9..6483a11 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -26,6 +26,7 @@ function Path:_init(...) self._raw_paths = utils.lists.str_list.new() self._drive_name = "" self.__windows_panic = false + local run_resolve = false for i, s in ipairs({ ... }) do if utils.tables.is_type_of(s, const.path_module_enum.PathlibPath) then ---@cast s PathlibPath @@ -44,8 +45,9 @@ function Path:_init(...) local splits = vim.split(path, "/", { plain = true, trimempty = false }) if #splits == 0 then goto continue + elseif vim.tbl_contains(splits, "..") then -- deal with '../' later in `self:resolve()` + run_resolve = true end - -- elseif -- TODO: deal with `../` self._raw_paths:extend(splits) else error("PathlibPath(new): ValueError: Invalid type as argument: " .. ("%s (%s: %s)"):format(type(s), i, s)) @@ -53,6 +55,9 @@ function Path:_init(...) ::continue:: end self:__clean_paths_list() + if run_resolve then + self:resolve() + end end ---Create a new Path object @@ -467,15 +472,42 @@ end ---Resolves path. Eliminates `../` representation. ---Changes internal. (See `Path:resolve_copy` to create new object) function Path:resolve() - -- TODO: Not Implemented - error("Not Implemented") + local accum, length = 1, self:len() + for _, value in ipairs(self._raw_paths) do + if value == ".." and accum > 1 then + accum = accum - 1 + else + self._raw_paths[accum] = value + accum = accum + 1 + end + end + for i = accum, length do + self._raw_paths[i] = nil + end end ---Resolves path. Eliminates `../` representation and returns a new object. `self` is not changed. ---@return PathlibPath function Path:resolve_copy() - -- TODO: Not Implemented - error("Not Implemented") + local accum, length, new = 1, self:len(), self:new_all_from() + for _, value in ipairs(self._raw_paths) do + if value == ".." and accum > 1 then + accum = accum - 1 + else + new._raw_paths[accum] = value + accum = accum + 1 + end + end + for i = accum, length do + new._raw_paths[i] = nil + end + return new +end + +---Get length of `self._raw_paths`. `/foo/bar.txt ==> 3: { "", "foo", "bar.txt" } (root dir counts as 1!!)` +---@return integer +function Path:len() + return #self._raw_paths end ---Change the permission of the path to `mode`. From 3ccd8283cac32f1388772f211af39fe2de681d2d Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 00:46:03 +0900 Subject: [PATCH 3/9] fix(base): implement luv and vim.fs wrappers --- lua/pathlib/base.lua | 90 +++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index 6483a11..f4c545a 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -207,6 +207,25 @@ function Path:__tostring() return path_str end +---Return the group name of the file GID. +function Path:basename() + return fs.basename(tostring(self)) +end + +---Return the group name of the file GID. Same as `str(self) minus self:modify(":r")`. +---@return string # extension of path including the dot (`.`): `.py`, `.lua` etc +function Path:suffix() + local path_str = tostring(self) + local without_ext = vim.fn.fnamemodify(path_str, ":r") + return path_str:sub(without_ext:len() + 1) or "" +end + +---Return the group name of the file GID. Same as `self:modify(":t:r")`. +---@return string # stem of path. (src/version.c -> "version") +function Path:stem() + return vim.fn.fnamemodify(tostring(self), ":t:r") +end + ---Return parent directory of itself. If parent does not exist, returns nil. ---@return PathlibPath? function Path:parent() @@ -277,6 +296,15 @@ function Path.stdpath(what) return Path.new(vim.fn.stdpath(what)) end +---Shorthand to `vim.fn.stdpath` and specify child path in later args. +---Mason bin path: `Path.stdpath("data", "mason", "bin")` or `Path.stdpath("data", "mason/bin")` +---@param what string # See `:h stdpath` for information +---@param ... string|PathlibPath # child path after the result of stdpath +---@return PathlibPath +function Path.stdpath_child(what, ...) + return Path.new(vim.fn.stdpath(what), ...) +end + ---Returns whether registered path is absolute ---@return boolean function Path:is_absolute() @@ -437,30 +465,35 @@ end ---Create a simlink named `self` pointing to `target` ---@param target PathlibPath +---@return boolean|nil success, string? err_name, string? err_msg function Path:symlink_to(target) - -- TODO: Not Implemented - error("Not Implemented") + utils.tables.is_path_module(target) + return luv.fs_symlink(tostring(self), tostring(target)) end ---Create a hardlink named `self` pointing to `target` ---@param target PathlibPath +---@return boolean|nil success, string? err_name, string? err_msg function Path:hardlink_to(target) - -- TODO: Not Implemented - error("Not Implemented") + utils.tables.is_path_module(target) + return luv.fs_link(tostring(self), tostring(target)) end ---Rename `self` to `target`. If `target` exists, fails with false. Ref: `Path:move` ---@param target PathlibPath +---@return boolean|nil success, string? err_name, string? err_msg function Path:rename(target) - -- TODO: Not Implemented - error("Not Implemented") + utils.tables.is_path_module(target) + return luv.fs_rename(tostring(self), tostring(target)) end ---Move `self` to `target`. Overwrites `target` if exists. Ref: `Path:rename` ---@param target PathlibPath +---@return boolean|nil success, string? err_name, string? err_msg function Path:move(target) - -- TODO: Not Implemented - error("Not Implemented") + utils.tables.is_path_module(target) + target:unlink() + return luv.fs_rename(tostring(self), tostring(target)) end ---@deprecated Use `Path:move` instead. @@ -523,28 +556,15 @@ function Path:chmod(mode, follow_symlinks) end ---Remove this file or link. If the path is a directory, use `Path:rmdir()` instead. ----@param missing_ok boolean -function Path:unlink(missing_ok) - -- TODO: Not Implemented - error("Not Implemented") +---@return boolean|nil success, string? err_name, string? err_msg +function Path:unlink() + return luv.fs_unlink(tostring(self)) end ---Remove this directory. The directory must be empty. +---@return boolean|nil success, string? err_name, string? err_msg function Path:rmdir() - -- TODO: Not Implemented - error("Not Implemented") -end - ----Return the login name of the file owner. -function Path:owner() - -- TODO: Not Implemented - error("Not Implemented") -end - ----Return the group name of the file GID. -function Path:group() - -- TODO: Not Implemented - error("Not Implemented") + return luv.fs_rmdir(tostring(self)) end ---Call `luv.fs_open`. Use `self:open_async` to use with callback. @@ -627,7 +647,7 @@ end --- "type" is one of the following: --- "file", "directory", "link", "fifo", "socket", "char", "block", "unknown". function Path:iterdir(opts) - local generator = vim.fs.dir(tostring(self), opts) + local generator = fs.dir(tostring(self), opts) return function() local name, fs_type = generator() if name ~= nil then @@ -666,9 +686,19 @@ function Path:iterdir_async(callback, on_error, on_exit) end) end -function Path:glob(pattern, follow_symlinks) - -- TODO: Implement glob - error("Not Implemented") +---Run `vim.fn.globpath` on this path. +---@param pattern string # glob pattern expression +---@return fun(): PathlibPath # iterator of results. +function Path:glob(pattern) + local str = tostring(self) + err.assert_function("Path:glob", function() + return not (str:find([[,]])) + end, "Path:glob cannot work on path that contains `,` (comma).") + local result, i = vim.fn.globpath(str, pattern, false, true), 0 + return function() + i = i + 1 + return Path.new(result[i]) + end end return Path From 9ef175bd046672b7b69e795388f1107b451b1bc0 Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 01:13:46 +0900 Subject: [PATCH 4/9] docs(README): update README with more examples --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++++- README.norg | 41 ++++++++++++++++++++++++++++++++++++++++- lua/pathlib/base.lua | 26 ++++++++++++++++++++++---- 3 files changed, 105 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 858b2ff..724e73b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,9 @@ earliest.** Status](https://img.shields.io/github/actions/workflow/status/pysan3/pathlib.nvim/lua_ls-typecheck.yml?style=for-the-badge)](https://github.com/pysan3/pathlib.nvim/actions/workflows/lua_ls-typecheck.yml) [![LuaRocks](https://img.shields.io/luarocks/v/pysan3/pathlib.nvim?logo=lua&color=purple&style=for-the-badge)](https://luarocks.org/modules/pysan3/pathlib.nvim) -# TL;DR +# Usage Example + +## Create Path Object ``` lua local Path = require("pathlib.base") @@ -44,6 +46,46 @@ local bar = foo .. "bar.txt" -- create siblings (just like `.//../bar.txt`) assert(tostring(bar) == "folder/bar.txt") ``` +## Create and Manipulate Files / Directories + +``` lua +local luv = vim.loop +local Path = require("pathlib.base") + +local new_file = Path.new("./new/folder/foo.txt") +new_file:parent():mkdir(Path.permission("rwxr-xr-x"), true) -- (permission, recursive) + +-- You don't need above line if you specify recursive = true in `open`; all parents will be created +local fd, err_name, err_msg = new_file:open("w", Path.permission("rw-r--r--"), true) +assert(fd ~= nil, "File creation failed. " .. err_name .. err_msg) +luv.fs_write(fd, "File Content\n") +luv.fs_close(fd) + +local content = new_file:read(0) +assert(content == "File Content\n") + +new_file:copy(new_file .. "bar.txt") +new_file:symlink_to(new_file .. "baz.txt") +``` + +## Scan Directories + +``` lua +-- Continue from above +for path in new_file:parent():iterdir() do + -- path will be [Path("./new/folder/foo.txt"), Path("./new/folder/bar.txt"), Path("./new/folder/baz.txt")] +end + +-- fs_scandir-like usage +new_file:parent():iterdir_async(function(path, fs_type) -- callback on all files + vim.print(tostring(path), fs_type) +end, function(error) -- on error + vim.print("Error: " .. error) +end, function(count) -- on exit + vim.print("Scan Finished. " .. count .. " files found.") +end) +``` + # TODO - API documentation diff --git a/README.norg b/README.norg index 4c72356..df515b1 100644 --- a/README.norg +++ b/README.norg @@ -35,7 +35,8 @@ version: 1.1.1 {https://github.com/pysan3/pathlib.nvim/actions/workflows/lua_ls-typecheck.yml}[!{https://img.shields.io/github/actions/workflow/status/pysan3/pathlib.nvim/lua_ls-typecheck.yml?style=for-the-badge}[Build Status]] {https://luarocks.org/modules/pysan3/pathlib.nvim}[!{https://img.shields.io/luarocks/v/pysan3/pathlib.nvim?logo=lua&color=purple&style=for-the-badge}[LuaRocks]] -* TL;DR +* Usage Example +** Create Path Object @code lua local Path = require("pathlib.base") @@ -51,6 +52,44 @@ version: 1.1.1 assert(tostring(bar) == "folder/bar.txt") @end +** Create and Manipulate Files / Directories + @code lua + local luv = vim.loop + local Path = require("pathlib.base") + + local new_file = Path.new("./new/folder/foo.txt") + new_file:parent():mkdir(Path.permission("rwxr-xr-x"), true) -- (permission, recursive) + + -- You don't need above line if you specify recursive = true in `open`; all parents will be created + local fd, err_name, err_msg = new_file:open("w", Path.permission("rw-r--r--"), true) + assert(fd ~= nil, "File creation failed. " .. err_name .. err_msg) + luv.fs_write(fd, "File Content\n") + luv.fs_close(fd) + + local content = new_file:read(0) + assert(content == "File Content\n") + + new_file:copy(new_file .. "bar.txt") + new_file:symlink_to(new_file .. "baz.txt") + @end + +** Scan Directories + @code lua + -- Continue from above + for path in new_file:parent():iterdir() do + -- path will be [Path("./new/folder/foo.txt"), Path("./new/folder/bar.txt"), Path("./new/folder/baz.txt")] + end + + -- fs_scandir-like usage + new_file:parent():iterdir_async(function(path, fs_type) -- callback on all files + vim.print(tostring(path), fs_type) + end, function(error) -- on error + vim.print("Error: " .. error) + end, function(count) -- on exit + vim.print("Scan Finished. " .. count .. " files found.") + end) + @end + * TODO - API documentation - Windows implementation, test environment. diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index f4c545a..f188a77 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -463,11 +463,23 @@ function Path:touch(mode, recursive) end end +---Copy file to `target` +---@param target PathlibPath # `self` will be copied to `target` +---@return boolean|nil success, string? err_name, string? err_msg # true if successfully created. +function Path:copy(target) + err.assert_function("Path:copy", function() + return utils.tables.is_path_module(target) + end, "target is not a Path object.") + return luv.fs_copyfile(tostring(self), tostring(target)) +end + ---Create a simlink named `self` pointing to `target` ---@param target PathlibPath ---@return boolean|nil success, string? err_name, string? err_msg function Path:symlink_to(target) - utils.tables.is_path_module(target) + err.assert_function("Path:symlink_to", function() + return utils.tables.is_path_module(target) + end, "target is not a Path object.") return luv.fs_symlink(tostring(self), tostring(target)) end @@ -475,7 +487,9 @@ end ---@param target PathlibPath ---@return boolean|nil success, string? err_name, string? err_msg function Path:hardlink_to(target) - utils.tables.is_path_module(target) + err.assert_function("Path:hardlink_to", function() + return utils.tables.is_path_module(target) + end, "target is not a Path object.") return luv.fs_link(tostring(self), tostring(target)) end @@ -483,7 +497,9 @@ end ---@param target PathlibPath ---@return boolean|nil success, string? err_name, string? err_msg function Path:rename(target) - utils.tables.is_path_module(target) + err.assert_function("Path:rename", function() + return utils.tables.is_path_module(target) + end, "target is not a Path object.") return luv.fs_rename(tostring(self), tostring(target)) end @@ -491,7 +507,9 @@ end ---@param target PathlibPath ---@return boolean|nil success, string? err_name, string? err_msg function Path:move(target) - utils.tables.is_path_module(target) + err.assert_function("Path:move", function() + return utils.tables.is_path_module(target) + end, "target is not a Path object.") target:unlink() return luv.fs_rename(tostring(self), tostring(target)) end From 969d89c716be89fd3dd17ef0c1ddef594702fe5b Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 02:41:25 +0900 Subject: [PATCH 5/9] test(posix): add posix file manipulation test --- spec/posix_manipulate_spec.lua | 84 ++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 spec/posix_manipulate_spec.lua diff --git a/spec/posix_manipulate_spec.lua b/spec/posix_manipulate_spec.lua new file mode 100644 index 0000000..1968250 --- /dev/null +++ b/spec/posix_manipulate_spec.lua @@ -0,0 +1,84 @@ +local luv = vim.loop +local _ = require("pathlib") +local const = require("pathlib.const") +local file_content = "File Content\n" + +describe("Posix File Manipulation;", function() + if const.IS_WINDOWS then + return + end + + local Path = require("pathlib.base") + local foo = Path.new("./folder/foo.txt") + local parent = foo:parent() + ---@cast parent PathlibPath + describe("parent", function() + it("()", function() + assert.is_equal("folder", tostring(parent)) + assert.is_not.is_nil(parent) + end) + end) + + describe("mkdir.", function() + parent:mkdir(Path.permission("rwxr-xr-x"), true) + it("exists()", function() + assert.is_true(parent:exists()) + assert.is_not.is_nil(luv.fs_stat("./folder")) + end) + it("is_dir()", function() + assert.is_true(parent:is_dir()) + local stat = luv.fs_stat("./folder") + assert.is_not.is_nil(stat) + ---@cast stat uv.aliases.fs_stat_table + assert.is_equal("directory", stat.type) + end) + end) + + describe("foo:open", function() + local fd, err_name, err_msg = foo:fs_open("w", Path.permission("rw-r--r--"), true) + ---@cast fd integer + vim.print(string.format([[fd: %s]], vim.inspect(fd))) + vim.print(string.format([[err_name: %s]], vim.inspect(err_name))) + vim.print(string.format([[err_msg: %s]], vim.inspect(err_msg))) + it("()", function() + assert.is_not.is_nil(fd) + assert.is_nil(err_name) + assert.is_nil(err_msg) + end) + it("exists()", function() + assert.is_true(foo:is_file()) + local stat = luv.fs_stat("./folder/foo.txt") + assert.is_not.is_nil(stat) + ---@cast stat uv.aliases.fs_stat_table + assert.is_equal("file", stat.type) + end) + it("write()", function() + assert.is_equal(string.len(file_content), luv.fs_write(fd, file_content)) + assert.is_truthy(luv.fs_close(fd)) + end) + it("read ()", function() + assert.is_equal(file_content, foo:io_read()) + end) + end) + + describe("io read / write", function() + it("()", function() + local suc, err_msg = foo:io_write(file_content) + assert.is_true(suc) + assert.is_nil(err_msg) + assert.is_equal(file_content, foo:io_read()) + end) + end) + + describe("iterdir", function() + it("()", function() + foo:copy(foo .. "bar.txt") + foo:symlink_to(foo .. "baz.txt") + local accum = {} + for path in parent:iterdir() do + table.insert(accum, path) + end + assert.is_equal(3, #accum) + end) + end) +end) From ba2c1769c5a86002e29ce67e4dbd15e1074bb2a1 Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 02:42:15 +0900 Subject: [PATCH 6/9] fix(base): use `io` methods to read, write to files --- lua/pathlib/base.lua | 92 +++++++++++++++++++++++++++---------------- lua/pathlib/const.lua | 6 ++- lua/pathlib/init.lua | 4 +- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index f188a77..2736bac 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -23,9 +23,6 @@ setmetatable(Path, { ---Private init method to create a new Path object ---@param ... string | PathlibPath # List of string and Path objects function Path:_init(...) - self._raw_paths = utils.lists.str_list.new() - self._drive_name = "" - self.__windows_panic = false local run_resolve = false for i, s in ipairs({ ... }) do if utils.tables.is_type_of(s, const.path_module_enum.PathlibPath) then @@ -64,11 +61,19 @@ end ---@param ... string | PathlibPath # List of string and Path objects ---@return PathlibPath function Path.new(...) - local self = setmetatable({}, Path) + local self = Path.new_empty() self:_init(...) return self end +function Path.new_empty() + local self = setmetatable({}, Path) + self._raw_paths = utils.lists.str_list.new() + self._drive_name = "" + self.__windows_panic = false + return self +end + ---Create a new Path object as self's child. ---@param ... string ---@return PathlibPath @@ -268,7 +273,7 @@ end ---Copy all attributes from `path` to self ---@param path PathlibPath function Path.new_all_from(path) - local self = setmetatable({}, Path) + local self = Path.new_empty() self.mytype = path.mytype self._drive_name = path._drive_name self._raw_paths:extend(path._raw_paths) @@ -382,6 +387,7 @@ end function Path:is_file(follow_symlinks) local stat = self:stat(follow_symlinks) + vim.print(string.format([[stat: %s]], vim.inspect(stat))) return stat and stat.type == "file" end @@ -454,7 +460,7 @@ end ---@param recursive boolean # if true, creates parent directories as well ---@return boolean success, string? err_name, string? err_msg # true if successfully created. function Path:touch(mode, recursive) - local fd, err_name, err_msg = self:open("w", mode, recursive) + local fd, err_name, err_msg = self:fs_open("w", mode, recursive) if fd == nil then return false, err_name, err_msg else @@ -592,7 +598,7 @@ end --- `true` will default to 755. ---@return integer|nil fd, string? err_name, string? err_msg ---@nodiscard -function Path:open(flags, mode, ensure_dir) +function Path:fs_open(flags, mode, ensure_dir) if ensure_dir == true then ensure_dir = const.permission_from_string("rwxr-xr-x") end @@ -609,7 +615,7 @@ end --- `true` will default to 755. ---@param callback fun(err: nil|string, fd: integer|nil) ---@return uv_fs_t -function Path:open_async(flags, mode, ensure_dir, callback) +function Path:fs_open_async(flags, mode, ensure_dir, callback) if ensure_dir == true then ensure_dir = const.permission_from_string("rwxr-xr-x") end @@ -619,38 +625,56 @@ function Path:open_async(flags, mode, ensure_dir, callback) return luv.fs_open(tostring(self), flags, mode, callback) end ----Call `luv.fs_read`. Use `self:open_async` and `luv.read` to use with callback. ----@param size integer ----@param offset integer|nil ----@return string|nil data, string? err_name, string? err_msg +---Call `io.read`. Use `self:open_async` and `luv.read` to use with callback. +---@return string|nil data, string? err_msg ---@nodiscard -function Path:read(size, offset) - local flags = luv.constants.O_RDONLY - local fd, open_err, open_err_msg = self:open(flags, 0) - if fd == nil then - return nil, open_err, open_err_msg +function Path:io_read() + local file, err_msg = io.open(tostring(self), "r") + if not file then + return nil, err_msg end - local data, err_name, err_msg = luv.fs_read(fd, size, offset) - luv.fs_close(fd) - return data, err_name, err_msg ---@diagnostic disable-line + return file:read("*a") end ----Call `luv.fs_write`. Use `self:open_async` and `luv.write` to use with callback. If failed, returns nil ----@param mode integer # permission. You may use `Path.permission()` to convert from "rwxrwxrwx". Overwrites S_IWRITE to true. ----@param data uv.aliases.buffer ----@param offset integer|nil ----@return integer|nil bytes, string? err_name, string? err_msg +---Call `io.read` with byte read mode. Use `self:open_async` and `luv.read` to use with callback. +---@return string|nil data, string? err_msg ---@nodiscard -function Path:write(mode, data, offset) - local flags = luv.constants.O_CREAT + luv.constants.O_RDWR + luv.constants.O_TRUNC - local fd, open_err, open_err_msg = - self:open(flags, const.bitoper(mode, const.fs_permission_enum.S_IWRITE, const.bitops.OR)) - if fd == nil then - return nil, open_err, open_err_msg +function Path:io_read_bytes() + local file, err_msg = io.open(tostring(self), "rb") + if not file then + return nil, err_msg + end + return file:read("*a") +end + +---Call `io.write`. Use `self:open_async` and `luv.write` to use with callback. If failed, returns nil +---@param data string # content +---@return boolean success, string? err_msg +---@nodiscard +function Path:io_write(data) + local file, err_msg = io.open(tostring(self), "w") + if not file then + return false, err_msg end - local bytes, err_name, err_msg = luv.fs_write(fd, data, offset) - luv.fs_close(fd) - return bytes, err_name, err_msg ---@diagnostic disable-line + local result = file:write(data) + file:flush() + file:close() + return result ---@diagnostic disable-line +end + +---Call `io.write` with byte write mode. Use `self:open_async` and `luv.write` to use with callback. If failed, returns nil +---@param data string # content +---@return boolean success, string? err_msg +---@nodiscard +function Path:io_write_bytes(data) + local file, err_msg = io.open(tostring(self), "w") + if not file then + return false, err_msg + end + local result = file:write(tostring(data)) + file:flush() + file:close() + return result ---@diagnostic disable-line end ---Alias to `vim.fs.dir` but returns PathlibPath objects. diff --git a/lua/pathlib/const.lua b/lua/pathlib/const.lua index 60b41e7..ed07b41 100644 --- a/lua/pathlib/const.lua +++ b/lua/pathlib/const.lua @@ -1,5 +1,7 @@ local M = {} +M.IS_WINDOWS = vim.fn.has("win32") == 1 or vim.fn.has("win32unix") == 1 + ---@enum PathlibBitOps M.bitops = { OR = 1, @@ -88,9 +90,9 @@ M.check_permission_string = function(mode_string) return false end local modes = { "r", "w", "x" } - local index = 1 + local index = 0 for value in mode_string:gmatch(".") do - if value ~= "-" or modes[index % 3] ~= value then + if value ~= "-" and modes[index % 3 + 1] ~= value then return false end index = index + 1 diff --git a/lua/pathlib/init.lua b/lua/pathlib/init.lua index 043c773..bc6241a 100644 --- a/lua/pathlib/init.lua +++ b/lua/pathlib/init.lua @@ -20,9 +20,9 @@ ---< ---@brief ]] -IS_WINDOWS = vim.fn.has("win32") == 1 or vim.fn.has("win32unix") == 1 +local const = require("pathlib.const") -if IS_WINDOWS then +if const.IS_WINDOWS then return require("pathlib.windows") else return require("pathlib.posix") From 6609a2594e265c6441c830005c251d84584c5f55 Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 02:46:08 +0900 Subject: [PATCH 7/9] fix: remove debug print msgs --- lua/pathlib/base.lua | 1 - spec/posix_manipulate_spec.lua | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index 2736bac..c1c964f 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -387,7 +387,6 @@ end function Path:is_file(follow_symlinks) local stat = self:stat(follow_symlinks) - vim.print(string.format([[stat: %s]], vim.inspect(stat))) return stat and stat.type == "file" end diff --git a/spec/posix_manipulate_spec.lua b/spec/posix_manipulate_spec.lua index 1968250..aafaf34 100644 --- a/spec/posix_manipulate_spec.lua +++ b/spec/posix_manipulate_spec.lua @@ -9,12 +9,12 @@ describe("Posix File Manipulation;", function() end local Path = require("pathlib.base") - local foo = Path.new("./folder/foo.txt") + local foo = Path.new("./tmp/test_folder/foo.txt") local parent = foo:parent() ---@cast parent PathlibPath describe("parent", function() it("()", function() - assert.is_equal("folder", tostring(parent)) + assert.is_equal("tmp/test_folder", tostring(parent)) assert.is_not.is_nil(parent) end) end) @@ -23,11 +23,11 @@ describe("Posix File Manipulation;", function() parent:mkdir(Path.permission("rwxr-xr-x"), true) it("exists()", function() assert.is_true(parent:exists()) - assert.is_not.is_nil(luv.fs_stat("./folder")) + assert.is_not.is_nil(luv.fs_stat("./tmp/test_folder")) end) it("is_dir()", function() assert.is_true(parent:is_dir()) - local stat = luv.fs_stat("./folder") + local stat = luv.fs_stat("./tmp/test_folder") assert.is_not.is_nil(stat) ---@cast stat uv.aliases.fs_stat_table assert.is_equal("directory", stat.type) @@ -37,9 +37,6 @@ describe("Posix File Manipulation;", function() describe("foo:open", function() local fd, err_name, err_msg = foo:fs_open("w", Path.permission("rw-r--r--"), true) ---@cast fd integer - vim.print(string.format([[fd: %s]], vim.inspect(fd))) - vim.print(string.format([[err_name: %s]], vim.inspect(err_name))) - vim.print(string.format([[err_msg: %s]], vim.inspect(err_msg))) it("()", function() assert.is_not.is_nil(fd) assert.is_nil(err_name) @@ -47,7 +44,7 @@ describe("Posix File Manipulation;", function() end) it("exists()", function() assert.is_true(foo:is_file()) - local stat = luv.fs_stat("./folder/foo.txt") + local stat = luv.fs_stat("./tmp/test_folder/foo.txt") assert.is_not.is_nil(stat) ---@cast stat uv.aliases.fs_stat_table assert.is_equal("file", stat.type) From 80f1e7f9c4aa1f8ff4c54d6f6c2e9ba84bd68e53 Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 02:51:38 +0900 Subject: [PATCH 8/9] fix: type issues fixed --- lua/pathlib/base.lua | 2 +- spec/posix_manipulate_spec.lua | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/pathlib/base.lua b/lua/pathlib/base.lua index c1c964f..381996c 100644 --- a/lua/pathlib/base.lua +++ b/lua/pathlib/base.lua @@ -735,7 +735,7 @@ function Path:glob(pattern) err.assert_function("Path:glob", function() return not (str:find([[,]])) end, "Path:glob cannot work on path that contains `,` (comma).") - local result, i = vim.fn.globpath(str, pattern, false, true), 0 + local result, i = vim.fn.globpath(str, pattern, false, true), 0 ---@diagnostic disable-line return function() i = i + 1 return Path.new(result[i]) diff --git a/spec/posix_manipulate_spec.lua b/spec/posix_manipulate_spec.lua index aafaf34..b67d2ef 100644 --- a/spec/posix_manipulate_spec.lua +++ b/spec/posix_manipulate_spec.lua @@ -11,7 +11,9 @@ describe("Posix File Manipulation;", function() local Path = require("pathlib.base") local foo = Path.new("./tmp/test_folder/foo.txt") local parent = foo:parent() - ---@cast parent PathlibPath + if parent == nil then + return + end describe("parent", function() it("()", function() assert.is_equal("tmp/test_folder", tostring(parent)) From d808f30ee006f43ba860a43b292cde9854650c33 Mon Sep 17 00:00:00 2001 From: pysan3 Date: Fri, 17 Nov 2023 02:53:13 +0900 Subject: [PATCH 9/9] ci(luacheck): typecheck only on PRs --- .github/workflows/lua_ls-typecheck.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lua_ls-typecheck.yml b/.github/workflows/lua_ls-typecheck.yml index 76d95ba..aa1541b 100644 --- a/.github/workflows/lua_ls-typecheck.yml +++ b/.github/workflows/lua_ls-typecheck.yml @@ -3,7 +3,8 @@ on: pull_request: ~ push: branches: - - '*' + - 'main' + - 'v*' jobs: build: