Skip to content

Commit 030ee62

Browse files
committed
add mkdir & rmdir
1 parent 7d2186a commit 030ee62

File tree

2 files changed

+178
-7
lines changed

2 files changed

+178
-7
lines changed

lua/plenary/path2.lua

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
--- `../../Users/test/test_file` and there's no home directory absolute path
5050
--- in this.
5151

52+
local bit = require "plenary.bit"
5253
local uv = vim.loop
5354
local iswin = uv.os_uname().sysname == "Windows_NT"
5455
local hasshellslash = vim.fn.exists "+shellslash" == 1
@@ -524,6 +525,40 @@ function Path:is_absolute()
524525
return not self._flavor.has_drv or self.drv ~= ""
525526
end
526527

528+
--- Get file status.
529+
--- Will throw error if path doesn't exist.
530+
---@return uv.aliases.fs_stat_table
531+
function Path:stat()
532+
local res, _, err_msg = uv.fs_stat(self:absolute())
533+
if res == nil then
534+
error(err_msg)
535+
end
536+
return res
537+
end
538+
539+
--- Get file status. Like `Path:stat` but if the path points to a symbolic
540+
--- link, returns the symbolic link's information.
541+
--- Will throw error if path doesn't exist.
542+
---@return uv.aliases.fs_stat_table
543+
function Path:lstat()
544+
local res, _, err_msg = uv.fs_lstat(self:absolute())
545+
if res == nil then
546+
error(err_msg)
547+
end
548+
return res
549+
end
550+
551+
---@return integer
552+
function Path:permission()
553+
local stat = self:stat()
554+
local perm = bit.band(stat.mode, 0x1FF)
555+
local owner = bit.rshift(perm, 6)
556+
local group = bit.rshift(perm, 3) % 8
557+
local user = perm % 8
558+
559+
return owner * 100 + group * 10 + user
560+
end
561+
527562
---@return boolean
528563
function Path:exists()
529564
local stat = uv.fs_stat(self:absolute())
@@ -727,7 +762,6 @@ function Path:make_relative(to, walk_up)
727762
return Path:new(steps).filename
728763
end
729764

730-
731765
--- Shorten path parts.
732766
--- By default, shortens all part except the last tail part to a length of 1.
733767
--- eg.
@@ -756,11 +790,84 @@ function Path:shorten(len, excludes)
756790
return self:_filename(nil, nil, new_parts)
757791
end
758792

759-
-- vim.o.shellslash = false
760-
local long_path = "/this/is/a/long/path"
761-
local p = Path:new(long_path)
762-
local short_path = p:shorten()
763-
print(p.filename, short_path)
764-
vim.o.shellslash = true
793+
---@class plenary.Path2.mkdirOpts
794+
---@field mode integer? permission to give to the directory, no umask effect will be applied (default: `o777`)
795+
---@field parents boolean? creates parent directories if true and necessary (default: `false`)
796+
---@field exists_ok boolean? ignores error if true and target directory exists (default: `false`)
797+
798+
--- Create directory
799+
---@param opts plenary.Path2.mkdirOpts?
800+
---@return boolean success
801+
function Path:mkdir(opts)
802+
opts = opts or {}
803+
opts.mode = vim.F.if_nil(opts.mode, 511)
804+
opts.parents = vim.F.if_nil(opts.parents, false)
805+
opts.exists_ok = vim.F.if_nil(opts.exists_ok, false)
806+
807+
local abs_path = self:absolute()
808+
809+
if not opts.exists_ok and self:exists() then
810+
error(string.format("FileExistsError: %s", abs_path))
811+
end
812+
813+
local ok, err_msg, err_code = uv.fs_mkdir(abs_path, opts.mode)
814+
if ok then
815+
return true
816+
end
817+
if err_code == "EEXIST" then
818+
return true
819+
end
820+
if err_code == "ENOENT" then
821+
if not opts.parents or self.parent == self then
822+
error(err_msg)
823+
end
824+
self:parent():mkdir { mode = opts.mode }
825+
uv.fs_mkdir(abs_path, opts.mode)
826+
return true
827+
end
828+
829+
error(err_msg)
830+
end
831+
832+
--- Delete directory
833+
function Path:rmdir()
834+
if not self:exists() then
835+
return
836+
end
837+
838+
local ok, err_msg = uv.fs_rmdir(self:absolute())
839+
if not ok then
840+
error(err_msg)
841+
end
842+
end
843+
844+
---@class plenary.Path2.touchOpts
845+
---@field mode integer? permissions to give to the file if created (default: `o666`)
846+
--- create parent directories if true and necessary. can optionally take a mode value
847+
--- for the mkdir function (default: `false`)
848+
---@field parents boolean|integer?
849+
850+
--- 'touch' file.
851+
--- If it doesn't exist, creates it including optionally, the parent directories
852+
---@param opts plenary.Path2.touchOpts?
853+
---@return boolean success
854+
function Path:touch(opts)
855+
opts = opts or {}
856+
opts.mode = vim.F.if_nil(opts.mode, 438)
857+
opts.parents = vim.F.if_nil(opts.parents, false)
858+
859+
local abs_path = self:absolute()
860+
861+
if self:exists() then
862+
local new_time = os.time()
863+
uv.fs_utime(abs_path, new_time, new_time)
864+
return true
865+
end
866+
867+
if not not opts.parents then
868+
local mode = type(opts.parents) == "number" and opts.parents ---@cast mode number?
869+
_ = Path:new(self:parent()):mkdir { mode = mode, parents = true }
870+
end
871+
end
765872

766873
return Path

tests/plenary/path2_spec.lua

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,70 @@ describe("Path2", function()
394394
end)
395395
end)
396396

397+
local function assert_permission(expect, actual)
398+
if iswin then
399+
return
400+
end
401+
assert.equal(expect, actual)
402+
end
403+
404+
describe("mkdir / rmdir", function()
405+
it_cross_plat("can create and delete directories", function()
406+
local p = Path:new "_dir_not_exist"
407+
408+
p:rmdir()
409+
assert.is_false(p:exists())
410+
411+
p:mkdir()
412+
assert.is_true(p:exists())
413+
assert.is_true(p:is_dir())
414+
assert_permission(0777, p:permission())
415+
416+
p:rmdir()
417+
assert.is_false(p:exists())
418+
end)
419+
420+
it_cross_plat("fails when exists_ok is false", function()
421+
local p = Path:new "lua"
422+
assert.has_error(function()
423+
p:mkdir { exists_ok = false }
424+
end)
425+
end)
426+
427+
it_cross_plat("fails when parents is not passed", function()
428+
local p = Path:new("impossible", "dir")
429+
assert.has_error(function()
430+
p:mkdir { parents = false }
431+
end)
432+
assert.is_false(p:exists())
433+
end)
434+
435+
it_cross_plat("can create nested directories", function()
436+
local p = Path:new("impossible", "dir")
437+
assert.has_no_error(function()
438+
p:mkdir { parents = true }
439+
end)
440+
assert.is_true(p:exists())
441+
442+
p:rmdir()
443+
Path:new("impossible"):rmdir()
444+
assert.is_false(p:exists())
445+
assert.is_false(Path:new("impossible"):exists())
446+
end)
447+
448+
-- it_cross_plat("can set different modes", function()
449+
-- local p = Path:new "_dir_not_exist"
450+
-- assert.has_no_error(function()
451+
-- p:mkdir { mode = 0755 }
452+
-- end)
453+
-- print(vim.uv.fs_stat(p:absolute()).mode)
454+
-- assert_permission(0755, p:permission())
455+
456+
-- p:rmdir()
457+
-- assert.is_false(p:exists())
458+
-- end)
459+
end)
460+
397461
describe("parents", function()
398462
it_cross_plat("should extract the ancestors of the path", function()
399463
local p = Path:new(vim.fn.getcwd())

0 commit comments

Comments
 (0)