From 2e86539114d04818a5074dd4c07308fc27eb34bf Mon Sep 17 00:00:00 2001 From: James Trew Date: Sat, 8 Jan 2022 22:50:05 -0500 Subject: [PATCH 1/4] initial implementation TODO: refactor the similarities between copy/move/rename --- lua/plenary/path.lua | 70 ++++++++++++++ tests/plenary/path_spec.lua | 184 ++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua index a8152f17..be8fdf44 100644 --- a/lua/plenary/path.lua +++ b/lua/plenary/path.lua @@ -502,6 +502,76 @@ function Path:rename(opts) return status end +function Path:move(opts) + opts = opts or {} + opts.recursive = F.if_nil(opts.recursive, false, opts.recursive) + opts.override = F.if_nil(opts.override, true, opts.override) + + local dest = opts.destination + -- handles `.`, `..`, `./`, and `../` + -- TODO: similar stuff repeated `copy` and `rename` - move into `new`? + if not Path.is_path(dest) then + if type(dest) == "string" and dest:match "^%.%.?/?\\?.+" then + dest = { + uv.fs_realpath(dest:sub(1, 3)), + dest:sub(4, #dest), + } + end + dest = Path:new(dest) + end + + local success = {} + if not self:is_dir() then + if opts.interactive and dest:exists() then + opts.override = vim.ui.input({ + string.format("%s already exists. Overwrite? [y/N] ", dest.filename), + }, function(input) + return string.lower(input or "") == "y" + end) + end + + if dest:exists() then + if opts.override then + dest:rm() + else + return success + end + end + success[dest] = uv.fs_rename(self:absolute(), dest:absolute()) or false + self.filename = dest.filename + return success + end + + if opts.recursive then + dest:mkdir { + parents = F.if_nil(opts.parents, false, opts.parents), + exists_ok = F.if_nil(opts.exists_ok, true, opts.exists_ok), + } + local scan = require "plenary.scandir" + local data = scan.scan_dir(self.filename, { + respect_gitignore = F.if_nil(opts.respect_gitignore, false, opts.respect_gitignore), + hidden = F.if_nil(opts.hidden, true, opts.hidden), + depth = 1, + add_dirs = true, + }) + for _, entry in ipairs(data) do + local entry_path = Path:new(entry) + local suffix = table.remove(entry_path:_split()) + local new_dest = dest:joinpath(suffix) + -- clear destination as it might be Path table otherwise failing w/ extend + opts.destination = nil + local new_opts = vim.tbl_deep_extend("force", opts, { destination = new_dest }) + -- nil: not overriden if `override = false` + success[new_dest] = entry_path:move(new_opts) or false + end + self:rmdir() + return success + else + error(string.format("Warning: %s was not copied as `recursive=false`", self:absolute())) + end + +end + --- Copy files or folders with defaults akin to GNU's `cp`. ---@param opts table: options to pass to toggling registered actions ---@field destination string|Path: target file path to copy to diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua index be9cae1c..5c43ebdc 100644 --- a/tests/plenary/path_spec.lua +++ b/tests/plenary/path_spec.lua @@ -568,6 +568,190 @@ describe("Path", function() end) end) + describe(":move", function() + it("can move file", function() + local p1 = Path:new "a_random_filename.rs" + local p2 = Path:new "a_random_filename.rs" + local dest = "not_a_random_filename.rs" + assert(pcall(p1.touch, p1)) + + assert(pcall(p1.move, p1, { destination = dest })) + assert.are.same(p1.filename, dest) + assert.False(p2:exists()) + p1:rm() + end) + + it("can move to parent dir", function() + local p1 = Path:new "a_random_filename.rs" + local p2 = Path:new "a_random_filename.rs" + assert(pcall(p1.touch, p1)) + + assert(pcall(p1.move, p1, { destination = "../not_a_random_filename.rs" })) + assert(pcall(p1.exists, p1)) + assert.False(p2:exists()) + + p1:rm() + end) + + it("cannot move an existing file if override false", function() + local orig = "a_random_filename.rs" + local dest = "not_a_random_filename.rs" + local p1 = Path:new(orig) + local p2 = Path:new(dest) + assert(pcall(p1.touch, p1)) + assert(pcall(p2.touch, p2)) + + assert(pcall(p1.move, p1, { destination = dest, override = false })) + assert.are.same(p1.filename, orig) + assert.are.same(p2.filename, dest) + + p1:rm() + p2:rm() + end) + + it("fails when moving folders non-recursively", function() + local src_dir = Path:new "src" + src_dir:mkdir() + src_dir:joinpath("file1.lua"):touch() + local trg_dir = Path:new "trg" + + -- error out as intended + assert.has.errors(function() + return src_dir:move { destination = trg_dir, recursive = false } + end, string.format( + "Warning: %s was not copied as `recursive=false`", + src_dir:absolute() + )) + src_dir:rm { recursive = true } + end) + + describe("recursively", function() + local scan = require "plenary.scandir" + local scan_opts = { + hidden = true, + depth = 3, + add_dirs = true, + } + + local src_dir, src_dirs, ovr_dir, ovr_dirs, oth_dir + local file_prefixes = { "file1", "file2", ".file3" } + + local flatten + flatten = function(ret, t) + for _, v in pairs(t) do + if type(v) == "table" then + flatten(ret, v) + else + table.insert(ret, v) + end + end + end + + local generate_children = function(files, dirs) + for _, file in ipairs(files) do + for level, dir in ipairs(dirs) do + local p = dir:joinpath(file .. "_" .. level .. ".lua") + p:touch { parents = true, exists_ok = true } + end + end + end + + before_each(function() + -- vim.tbl_flatten doesn't work here as copy doesn't return a list + + -- setup directories + src_dir = Path:new "src" + ovr_dir = Path:new "ovr" + oth_dir = Path:new "oth" + src_dir:mkdir() + + -- set up sub directory paths for creation and testing + local sub_dirs = { "sub_dir1", "sub_dir1/sub_dir2" } + src_dirs = { src_dir } + ovr_dirs = { ovr_dir } + -- {src, trg}_dirs is a table with all directory levels by {src, trg} + for _, dir in ipairs(sub_dirs) do + table.insert(src_dirs, src_dir:joinpath(dir)) + table.insert(ovr_dirs, ovr_dir:joinpath(dir)) + end + + -- generate {file}_{level}.lua on every directory level in src + -- src + -- ├── file1_1.lua + -- ├── file2_1.lua + -- ├── .file3_1.lua + -- └── sub_dir1 + -- ├── file1_2.lua + -- ├── file2_2.lua + -- ├── .file3_2.lua + -- └── sub_dir2 + -- ├── file1_3.lua + -- ├── file2_3.lua + -- └── .file3_3.lua + generate_children(file_prefixes, src_dirs) + end) + + after_each(function() + if src_dir:exists() then + src_dir:rm { recursive = true } + end + if ovr_dir:exists() then + ovr_dir:rm { recursive = true } + end + if oth_dir:exists() then + oth_dir:rm { recursive = true } + end + end) + + it("no override needed, hidden = true", function() + local success = src_dir:move { destination = oth_dir, recursive = true, hidden = true } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 9) + assert.False(src_dir:exists()) + + local data = scan.scan_dir(oth_dir.filename, scan_opts) + assert.is.equal(#data, 11) + end) + + it("no override needed, hidden = false", function() + local success = src_dir:move { destination = oth_dir, recursive = true, hidden = false } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 6) + assert.True(src_dir:exists()) + + local src_data = scan.scan_dir(src_dir.filename, scan_opts) + local oth_data = scan.scan_dir(oth_dir.filename, scan_opts) + assert.is.equal(#src_data, 5) + assert.is.equal(#oth_data, 8) + end) + + it("full overriding", function() + generate_children(file_prefixes, ovr_dirs) + local success = src_dir:move { destination = ovr_dir, recursive = true, override = true } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 9) + + local data = scan.scan_dir(ovr_dir.filename, scan_opts) + assert.is.equal(#data, 11) + end) + + it("partial overriding while keeping non overlapping files", function() + table.insert(file_prefixes, 1, "keep") + generate_children(file_prefixes, ovr_dirs) + local success = src_dir:move { destination = ovr_dir, recursive = true, override = true } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 9) + + local data = scan.scan_dir(ovr_dir.filename, scan_opts) + assert.is.equal(#data, 14) + end) + end) + end) + describe("parents", function() it("should extract the ancestors of the path", function() local p = Path:new(vim.loop.cwd()) From 825535ba3b227e7c4867966e035b668c533d2bbb Mon Sep 17 00:00:00 2001 From: James Trew Date: Sun, 9 Jan 2022 17:39:45 -0500 Subject: [PATCH 2/4] feat(path): add recursive overwriting to `rename` --- lua/plenary/path.lua | 68 +++---- tests/plenary/path_spec.lua | 395 ++++++++++++++++-------------------- 2 files changed, 210 insertions(+), 253 deletions(-) diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua index be8fdf44..8256149b 100644 --- a/lua/plenary/path.lua +++ b/lua/plenary/path.lua @@ -476,49 +476,41 @@ function Path:rmdir() uv.fs_rmdir(self:absolute()) end +-- function Path:rename(opts) +-- opts = opts or {} +-- if not opts.new_name or opts.new_name == "" then +-- error "Please provide the new name!" +-- end +-- +-- -- handles `.`, `..`, `./`, and `../` +-- if opts.new_name:match "^%.%.?/?\\?.+" then +-- opts.new_name = { +-- uv.fs_realpath(opts.new_name:sub(1, 3)), +-- opts.new_name:sub(4, #opts.new_name), +-- } +-- end +-- +-- local new_path = Path:new(opts.new_name) +-- +-- if new_path:exists() then +-- error "File or directory already exists!" +-- end +-- +-- local status = uv.fs_rename(self:absolute(), new_path:absolute()) +-- self.filename = new_path.filename +-- +-- return status +-- end + function Path:rename(opts) opts = opts or {} if not opts.new_name or opts.new_name == "" then error "Please provide the new name!" end - - -- handles `.`, `..`, `./`, and `../` - if opts.new_name:match "^%.%.?/?\\?.+" then - opts.new_name = { - uv.fs_realpath(opts.new_name:sub(1, 3)), - opts.new_name:sub(4, #opts.new_name), - } - end - - local new_path = Path:new(opts.new_name) - - if new_path:exists() then - error "File or directory already exists!" - end - - local status = uv.fs_rename(self:absolute(), new_path:absolute()) - self.filename = new_path.filename - - return status -end - -function Path:move(opts) - opts = opts or {} opts.recursive = F.if_nil(opts.recursive, false, opts.recursive) opts.override = F.if_nil(opts.override, true, opts.override) - local dest = opts.destination - -- handles `.`, `..`, `./`, and `../` - -- TODO: similar stuff repeated `copy` and `rename` - move into `new`? - if not Path.is_path(dest) then - if type(dest) == "string" and dest:match "^%.%.?/?\\?.+" then - dest = { - uv.fs_realpath(dest:sub(1, 3)), - dest:sub(4, #dest), - } - end - dest = Path:new(dest) - end + local dest = Path:new(opts.new_name) local success = {} if not self:is_dir() then @@ -559,10 +551,10 @@ function Path:move(opts) local suffix = table.remove(entry_path:_split()) local new_dest = dest:joinpath(suffix) -- clear destination as it might be Path table otherwise failing w/ extend - opts.destination = nil - local new_opts = vim.tbl_deep_extend("force", opts, { destination = new_dest }) + opts.new_name = nil + local new_opts = vim.tbl_deep_extend("force", opts, { new_name = new_dest }) -- nil: not overriden if `override = false` - success[new_dest] = entry_path:move(new_opts) or false + success[new_dest] = entry_path:rename(new_opts) or false end self:rmdir() return success diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua index 5c43ebdc..3fb15620 100644 --- a/tests/plenary/path_spec.lua +++ b/tests/plenary/path_spec.lua @@ -370,53 +370,200 @@ describe("Path", function() end) describe("rename", function() - it("can rename a file", function() - local p = Path:new "a_random_filename.lua" - assert(pcall(p.touch, p)) - assert(p:exists()) + local test_file + local test_name = "a_random_filename.lua" - assert(pcall(p.rename, p, { new_name = "not_a_random_filename.lua" })) - assert.are.same("not_a_random_filename.lua", p.filename) + before_each(function() + test_file = Path:new(test_name) + test_file:touch() + end) - p:rm() + after_each(function() + if test_file:exists() then + test_file:rm() + end + end) + + it("can rename a file", function() + local new_name = "not_a_random_filename.lua" + assert(pcall(test_file.rename, test_file, { new_name = new_name })) + assert.are.same(new_name, test_file.filename) end) it("can handle an invalid filename", function() - local p = Path:new "some_random_filename.lua" - assert(pcall(p.touch, p)) - assert(p:exists()) + assert.has.errors(function() + test_file:rename { new_name = "" } + end, "Please provide the new name!") + assert.has.errors(function() + test_file:rename() + end, "Please provide the new name!") + assert.is.equal(test_file.filename, test_name) + end) + + it("can move to parent dir", function() + local new_name = "../some_random_filename.lua" + assert(pcall(test_file.rename, test_file, { new_name = new_name })) + assert.are.same(vim.loop.fs_realpath(Path:new(new_name):absolute()), test_file:absolute()) + end) - assert(not pcall(p.rename, p, { new_name = "" })) - assert(not pcall(p.rename, p)) - assert.are.same("some_random_filename.lua", p.filename) + it("cannot rename to an existing filename if override false", function() + local ovr_file = Path:new "not_a_random_filename.lua" + ovr_file:touch() - p:rm() + assert(pcall(test_file.rename, test_file, { new_name = ovr_file, override = false })) + assert.are.same(test_file.filename, test_name) + ovr_file:rm() end) - it("can move to parent dir", function() - local p = Path:new "some_random_filename.lua" - assert(pcall(p.touch, p)) - assert(p:exists()) + it("can rename to an existing file if override true", function() + local ovr_file = Path:new "not_a_random_filename.lua" + ovr_file:touch() - assert(pcall(p.rename, p, { new_name = "../some_random_filename.lua" })) - assert.are.same(vim.loop.fs_realpath(Path:new("../some_random_filename.lua"):absolute()), p:absolute()) + assert(pcall(test_file.rename, test_file, { new_name = ovr_file, override = true })) + assert.are.same(test_file.filename, ovr_file.filename) + ovr_file:rm() + end) - p:rm() + it("fails when moving folders non-recursively", function() + local src_dir = Path:new "src" + src_dir:mkdir() + src_dir:joinpath("file1.lua"):touch() + local trg_dir = Path:new "trg" + + -- error out as intended + assert.has.errors(function() + return src_dir:rename { new_name = trg_dir, recursive = false } + end, string.format( + "Warning: %s was not copied as `recursive=false`", + src_dir:absolute() + )) + src_dir:rm { recursive = true } end) - it("cannot rename to an existing filename", function() - local p1 = Path:new "a_random_filename.lua" - local p2 = Path:new "not_a_random_filename.lua" - assert(pcall(p1.touch, p1)) - assert(pcall(p2.touch, p2)) - assert(p1:exists()) - assert(p2:exists()) + describe("recursively", function() + local scan = require "plenary.scandir" + local scan_opts = { + hidden = true, + depth = 3, + add_dirs = true, + } - assert(not pcall(p1.rename, p1, { new_name = "not_a_random_filename.lua" })) - assert.are.same(p1.filename, "a_random_filename.lua") + local src_dir, src_dirs, ovr_dir, ovr_dirs, oth_dir + local file_prefixes = { "file1", "file2", ".file3" } - p1:rm() - p2:rm() + local flatten + flatten = function(ret, t) + for _, v in pairs(t) do + if type(v) == "table" then + flatten(ret, v) + else + table.insert(ret, v) + end + end + end + + local generate_children = function(files, dirs) + for _, file in ipairs(files) do + for level, dir in ipairs(dirs) do + local p = dir:joinpath(file .. "_" .. level .. ".lua") + p:touch { parents = true, exists_ok = true } + end + end + end + + before_each(function() + -- vim.tbl_flatten doesn't work here as copy doesn't return a list + + -- setup directories + src_dir = Path:new "src" + ovr_dir = Path:new "ovr" + oth_dir = Path:new "oth" + src_dir:mkdir() + + -- set up sub directory paths for creation and testing + local sub_dirs = { "sub_dir1", "sub_dir1/sub_dir2" } + src_dirs = { src_dir } + ovr_dirs = { ovr_dir } + -- {src, trg}_dirs is a table with all directory levels by {src, trg} + for _, dir in ipairs(sub_dirs) do + table.insert(src_dirs, src_dir:joinpath(dir)) + table.insert(ovr_dirs, ovr_dir:joinpath(dir)) + end + + -- generate {file}_{level}.lua on every directory level in src + -- src + -- ├── file1_1.lua + -- ├── file2_1.lua + -- ├── .file3_1.lua + -- └── sub_dir1 + -- ├── file1_2.lua + -- ├── file2_2.lua + -- ├── .file3_2.lua + -- └── sub_dir2 + -- ├── file1_3.lua + -- ├── file2_3.lua + -- └── .file3_3.lua + generate_children(file_prefixes, src_dirs) + end) + + after_each(function() + if src_dir:exists() then + src_dir:rm { recursive = true } + end + if ovr_dir:exists() then + ovr_dir:rm { recursive = true } + end + if oth_dir:exists() then + oth_dir:rm { recursive = true } + end + end) + + it("no override needed, hidden = true", function() + local success = src_dir:rename { new_name = oth_dir, recursive = true, hidden = true } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 9) + assert.False(src_dir:exists()) + + local data = scan.scan_dir(oth_dir.filename, scan_opts) + assert.is.equal(#data, 11) + end) + + it("no override needed, hidden = false", function() + local success = src_dir:rename { new_name = oth_dir, recursive = true, hidden = false } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 6) + assert.True(src_dir:exists()) + + local src_data = scan.scan_dir(src_dir.filename, scan_opts) + local oth_data = scan.scan_dir(oth_dir.filename, scan_opts) + assert.is.equal(#src_data, 5) + assert.is.equal(#oth_data, 8) + end) + + it("full overriding", function() + generate_children(file_prefixes, ovr_dirs) + local success = src_dir:rename { new_name = ovr_dir, recursive = true, override = true } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 9) + + local data = scan.scan_dir(ovr_dir.filename, scan_opts) + assert.is.equal(#data, 11) + end) + + it("partial overriding while keeping non overlapping files", function() + table.insert(file_prefixes, 1, "keep") + generate_children(file_prefixes, ovr_dirs) + local success = src_dir:rename { new_name = ovr_dir, recursive = true, override = true } + local file_ops = {} + flatten(file_ops, success) + assert.is.equal(#file_ops, 9) + + local data = scan.scan_dir(ovr_dir.filename, scan_opts) + assert.is.equal(#data, 14) + end) end) end) @@ -568,189 +715,7 @@ describe("Path", function() end) end) - describe(":move", function() - it("can move file", function() - local p1 = Path:new "a_random_filename.rs" - local p2 = Path:new "a_random_filename.rs" - local dest = "not_a_random_filename.rs" - assert(pcall(p1.touch, p1)) - - assert(pcall(p1.move, p1, { destination = dest })) - assert.are.same(p1.filename, dest) - assert.False(p2:exists()) - p1:rm() - end) - - it("can move to parent dir", function() - local p1 = Path:new "a_random_filename.rs" - local p2 = Path:new "a_random_filename.rs" - assert(pcall(p1.touch, p1)) - - assert(pcall(p1.move, p1, { destination = "../not_a_random_filename.rs" })) - assert(pcall(p1.exists, p1)) - assert.False(p2:exists()) - - p1:rm() - end) - - it("cannot move an existing file if override false", function() - local orig = "a_random_filename.rs" - local dest = "not_a_random_filename.rs" - local p1 = Path:new(orig) - local p2 = Path:new(dest) - assert(pcall(p1.touch, p1)) - assert(pcall(p2.touch, p2)) - - assert(pcall(p1.move, p1, { destination = dest, override = false })) - assert.are.same(p1.filename, orig) - assert.are.same(p2.filename, dest) - - p1:rm() - p2:rm() - end) - - it("fails when moving folders non-recursively", function() - local src_dir = Path:new "src" - src_dir:mkdir() - src_dir:joinpath("file1.lua"):touch() - local trg_dir = Path:new "trg" - - -- error out as intended - assert.has.errors(function() - return src_dir:move { destination = trg_dir, recursive = false } - end, string.format( - "Warning: %s was not copied as `recursive=false`", - src_dir:absolute() - )) - src_dir:rm { recursive = true } - end) - - describe("recursively", function() - local scan = require "plenary.scandir" - local scan_opts = { - hidden = true, - depth = 3, - add_dirs = true, - } - - local src_dir, src_dirs, ovr_dir, ovr_dirs, oth_dir - local file_prefixes = { "file1", "file2", ".file3" } - - local flatten - flatten = function(ret, t) - for _, v in pairs(t) do - if type(v) == "table" then - flatten(ret, v) - else - table.insert(ret, v) - end - end - end - - local generate_children = function(files, dirs) - for _, file in ipairs(files) do - for level, dir in ipairs(dirs) do - local p = dir:joinpath(file .. "_" .. level .. ".lua") - p:touch { parents = true, exists_ok = true } - end - end - end - - before_each(function() - -- vim.tbl_flatten doesn't work here as copy doesn't return a list - - -- setup directories - src_dir = Path:new "src" - ovr_dir = Path:new "ovr" - oth_dir = Path:new "oth" - src_dir:mkdir() - - -- set up sub directory paths for creation and testing - local sub_dirs = { "sub_dir1", "sub_dir1/sub_dir2" } - src_dirs = { src_dir } - ovr_dirs = { ovr_dir } - -- {src, trg}_dirs is a table with all directory levels by {src, trg} - for _, dir in ipairs(sub_dirs) do - table.insert(src_dirs, src_dir:joinpath(dir)) - table.insert(ovr_dirs, ovr_dir:joinpath(dir)) - end - - -- generate {file}_{level}.lua on every directory level in src - -- src - -- ├── file1_1.lua - -- ├── file2_1.lua - -- ├── .file3_1.lua - -- └── sub_dir1 - -- ├── file1_2.lua - -- ├── file2_2.lua - -- ├── .file3_2.lua - -- └── sub_dir2 - -- ├── file1_3.lua - -- ├── file2_3.lua - -- └── .file3_3.lua - generate_children(file_prefixes, src_dirs) - end) - - after_each(function() - if src_dir:exists() then - src_dir:rm { recursive = true } - end - if ovr_dir:exists() then - ovr_dir:rm { recursive = true } - end - if oth_dir:exists() then - oth_dir:rm { recursive = true } - end - end) - - it("no override needed, hidden = true", function() - local success = src_dir:move { destination = oth_dir, recursive = true, hidden = true } - local file_ops = {} - flatten(file_ops, success) - assert.is.equal(#file_ops, 9) - assert.False(src_dir:exists()) - - local data = scan.scan_dir(oth_dir.filename, scan_opts) - assert.is.equal(#data, 11) - end) - - it("no override needed, hidden = false", function() - local success = src_dir:move { destination = oth_dir, recursive = true, hidden = false } - local file_ops = {} - flatten(file_ops, success) - assert.is.equal(#file_ops, 6) - assert.True(src_dir:exists()) - - local src_data = scan.scan_dir(src_dir.filename, scan_opts) - local oth_data = scan.scan_dir(oth_dir.filename, scan_opts) - assert.is.equal(#src_data, 5) - assert.is.equal(#oth_data, 8) - end) - - it("full overriding", function() - generate_children(file_prefixes, ovr_dirs) - local success = src_dir:move { destination = ovr_dir, recursive = true, override = true } - local file_ops = {} - flatten(file_ops, success) - assert.is.equal(#file_ops, 9) - - local data = scan.scan_dir(ovr_dir.filename, scan_opts) - assert.is.equal(#data, 11) - end) - - it("partial overriding while keeping non overlapping files", function() - table.insert(file_prefixes, 1, "keep") - generate_children(file_prefixes, ovr_dirs) - local success = src_dir:move { destination = ovr_dir, recursive = true, override = true } - local file_ops = {} - flatten(file_ops, success) - assert.is.equal(#file_ops, 9) - - local data = scan.scan_dir(ovr_dir.filename, scan_opts) - assert.is.equal(#data, 14) - end) - end) - end) + describe(":rename", function() end) describe("parents", function() it("should extract the ancestors of the path", function() From 5afd70430e2c09c032a5ad185651f458a48d2f96 Mon Sep 17 00:00:00 2001 From: James Trew Date: Fri, 14 Jan 2022 21:08:26 -0500 Subject: [PATCH 3/4] refactor and clean up test --- lua/plenary/path.lua | 168 ++++++++++++++---------------------- tests/plenary/path_spec.lua | 14 +-- 2 files changed, 73 insertions(+), 109 deletions(-) diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua index 8256149b..1203c2a7 100644 --- a/lua/plenary/path.lua +++ b/lua/plenary/path.lua @@ -476,37 +476,65 @@ function Path:rmdir() uv.fs_rmdir(self:absolute()) end --- function Path:rename(opts) --- opts = opts or {} --- if not opts.new_name or opts.new_name == "" then --- error "Please provide the new name!" --- end --- --- -- handles `.`, `..`, `./`, and `../` --- if opts.new_name:match "^%.%.?/?\\?.+" then --- opts.new_name = { --- uv.fs_realpath(opts.new_name:sub(1, 3)), --- opts.new_name:sub(4, #opts.new_name), --- } --- end --- --- local new_path = Path:new(opts.new_name) --- --- if new_path:exists() then --- error "File or directory already exists!" --- end --- --- local status = uv.fs_rename(self:absolute(), new_path:absolute()) --- self.filename = new_path.filename --- --- return status --- end +local function _check_path_empty(input_path) + if input_path == nil or (type(input_path) == "string" and string.match(input_path, "^%s*$")) then + error "Empty path input given. Please provide valid path." + end +end +function Path:_get_override_input(dest_path, opts) + if opts.interactive and dest_path:exists() then + vim.ui.input( + { prompt = string.format("%s already exists. Overwrite with %s? [y/N] ", dest_path.filename, self.filename) }, + function(input) + opts.override = string.lower(input or "") == "y" + end + ) + end + return opts.override +end + +function Path:_do_recursion(dest_path, opts, field, callback) + local success = {} + dest_path:mkdir { + parents = F.if_nil(opts.parents, false, opts.parents), + exists_ok = F.if_nil(opts.exists_ok, true, opts.exists_ok), + } + local scan = require "plenary.scandir" + local data = scan.scan_dir(self.filename, { + respect_gitignore = F.if_nil(opts.respect_gitignore, false, opts.respect_gitignore), + hidden = F.if_nil(opts.hidden, true, opts.hidden), + depth = 1, + add_dirs = true, + }) + for _, entry in ipairs(data) do + local entry_path = Path:new(entry) + local suffix = table.remove(entry_path:_split()) + local new_dest = dest_path:joinpath(suffix) + -- clear destination as it might be Path table otherwise failing w/ extend + opts[field] = nil + local new_opts = vim.tbl_deep_extend("force", opts, { [field] = new_dest }) + -- nil: not overriden if `override = false` + _, success[new_dest] = pcall(callback, entry_path, new_opts) + end + return success +end + +--- Rename/move files or folders with defaults akin to GNU's `mv`. +---@class Path +---@param opts table: options to pass to toggling registered actions +---@field new_name string|Path: target file path to rename/move to +---@field recursive boolean: whether to rename/move folders recursively (default: true) +---@field override boolean: whether to override files (default: true) +---@field interactive boolean: confirm if rename/move would override; precedes `override` (default: false) +---@field respect_gitignore boolean: skip folders ignored by all detected `gitignore`s (default: false) +---@field hidden boolean: whether to add hidden files in recursively renaming/moving folders (default: true) +---@field parents boolean: whether to create possibly non-existing parent dirs of `opts.destination` (default: false) +---@field exists_ok boolean: whether ok if `opts.destination` exists, if so folders are merged (default: true) +---@return table {[Path of destination]: bool} indicating success of rename/move; nested tables constitute sub dirs function Path:rename(opts) opts = opts or {} - if not opts.new_name or opts.new_name == "" then - error "Please provide the new name!" - end + _check_path_empty(opts.new_name) opts.recursive = F.if_nil(opts.recursive, false, opts.recursive) opts.override = F.if_nil(opts.override, true, opts.override) @@ -514,13 +542,7 @@ function Path:rename(opts) local success = {} if not self:is_dir() then - if opts.interactive and dest:exists() then - opts.override = vim.ui.input({ - string.format("%s already exists. Overwrite? [y/N] ", dest.filename), - }, function(input) - return string.lower(input or "") == "y" - end) - end + opts.override = self:_get_override_input(dest, opts) if dest:exists() then if opts.override then @@ -535,33 +557,12 @@ function Path:rename(opts) end if opts.recursive then - dest:mkdir { - parents = F.if_nil(opts.parents, false, opts.parents), - exists_ok = F.if_nil(opts.exists_ok, true, opts.exists_ok), - } - local scan = require "plenary.scandir" - local data = scan.scan_dir(self.filename, { - respect_gitignore = F.if_nil(opts.respect_gitignore, false, opts.respect_gitignore), - hidden = F.if_nil(opts.hidden, true, opts.hidden), - depth = 1, - add_dirs = true, - }) - for _, entry in ipairs(data) do - local entry_path = Path:new(entry) - local suffix = table.remove(entry_path:_split()) - local new_dest = dest:joinpath(suffix) - -- clear destination as it might be Path table otherwise failing w/ extend - opts.new_name = nil - local new_opts = vim.tbl_deep_extend("force", opts, { new_name = new_dest }) - -- nil: not overriden if `override = false` - success[new_dest] = entry_path:rename(new_opts) or false - end + success = self:_do_recursion(dest, opts, "new_name", Path.rename) self:rmdir() return success else error(string.format("Warning: %s was not copied as `recursive=false`", self:absolute())) end - end --- Copy files or folders with defaults akin to GNU's `cp`. @@ -576,62 +577,23 @@ end ---@field exists_ok bool: whether ok if `opts.destination` exists, if so folders are merged (default: true) ---@return table {[Path of destination]: bool} indicating success of copy; nested tables constitute sub dirs function Path:copy(opts) - opts = opts or {} + _check_path_empty(opts.destination) opts.recursive = F.if_nil(opts.recursive, false, opts.recursive) opts.override = F.if_nil(opts.override, true, opts.override) - local dest = opts.destination - -- handles `.`, `..`, `./`, and `../` - if not Path.is_path(dest) then - if type(dest) == "string" and dest:match "^%.%.?/?\\?.+" then - dest = { - uv.fs_realpath(dest:sub(1, 3)), - dest:sub(4, #dest), - } - end - dest = Path:new(dest) - end + local dest = Path:new(opts.destination) + -- success is true in case file is copied, false otherwise local success = {} if not self:is_dir() then - if opts.interactive and dest:exists() then - vim.ui.select( - { "Yes", "No" }, - { prompt = string.format("Overwrite existing %s?", dest:absolute()) }, - function(_, idx) - success[dest] = uv.fs_copyfile(self:absolute(), dest:absolute(), { excl = not (idx == 1) }) or false - end - ) - else - -- nil: not overriden if `override = false` - success[dest] = uv.fs_copyfile(self:absolute(), dest:absolute(), { excl = not opts.override }) or false - end + opts.override = self:_get_override_input(dest, opts) + -- nil: not overriden if `override = false` + success[dest] = uv.fs_copyfile(self:absolute(), dest:absolute(), { excl = not opts.override }) or false return success end -- dir if opts.recursive then - dest:mkdir { - parents = F.if_nil(opts.parents, false, opts.parents), - exists_ok = F.if_nil(opts.exists_ok, true, opts.exists_ok), - } - local scan = require "plenary.scandir" - local data = scan.scan_dir(self.filename, { - respect_gitignore = F.if_nil(opts.respect_gitignore, false, opts.respect_gitignore), - hidden = F.if_nil(opts.hidden, true, opts.hidden), - depth = 1, - add_dirs = true, - }) - for _, entry in ipairs(data) do - local entry_path = Path:new(entry) - local suffix = table.remove(entry_path:_split()) - local new_dest = dest:joinpath(suffix) - -- clear destination as it might be Path table otherwise failing w/ extend - opts.destination = nil - local new_opts = vim.tbl_deep_extend("force", opts, { destination = new_dest }) - -- nil: not overriden if `override = false` - success[new_dest] = entry_path:copy(new_opts) or false - end - return success + return self:_do_recursion(dest, opts, "destination", Path.copy) else error(string.format("Warning: %s was not copied as `recursive=false`", self:absolute())) end diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua index 3fb15620..2016470f 100644 --- a/tests/plenary/path_spec.lua +++ b/tests/plenary/path_spec.lua @@ -391,12 +391,16 @@ describe("Path", function() end) it("can handle an invalid filename", function() + local error_msg = "Empty path input given. Please provide valid path." assert.has.errors(function() test_file:rename { new_name = "" } - end, "Please provide the new name!") + end, error_msg) + assert.has.errors(function() + test_file:rename { new_name = " " } + end, error_msg) assert.has.errors(function() test_file:rename() - end, "Please provide the new name!") + end, error_msg) assert.is.equal(test_file.filename, test_name) end) @@ -451,8 +455,8 @@ describe("Path", function() local src_dir, src_dirs, ovr_dir, ovr_dirs, oth_dir local file_prefixes = { "file1", "file2", ".file3" } - local flatten - flatten = function(ret, t) + -- vim.tbl_flatten doesn't work here as copy doesn't return a list + local function flatten(ret, t) for _, v in pairs(t) do if type(v) == "table" then flatten(ret, v) @@ -472,8 +476,6 @@ describe("Path", function() end before_each(function() - -- vim.tbl_flatten doesn't work here as copy doesn't return a list - -- setup directories src_dir = Path:new "src" ovr_dir = Path:new "ovr" From 88c489ffc827433c3a2363592d151bd8bc0857a0 Mon Sep 17 00:00:00 2001 From: James Trew Date: Fri, 14 Jan 2022 21:18:04 -0500 Subject: [PATCH 4/4] correct defaults --- lua/plenary/path.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua index 1203c2a7..ff7b6566 100644 --- a/lua/plenary/path.lua +++ b/lua/plenary/path.lua @@ -535,7 +535,7 @@ end function Path:rename(opts) opts = opts or {} _check_path_empty(opts.new_name) - opts.recursive = F.if_nil(opts.recursive, false, opts.recursive) + opts.recursive = F.if_nil(opts.recursive, true, opts.recursive) opts.override = F.if_nil(opts.override, true, opts.override) local dest = Path:new(opts.new_name)