Skip to content

Commit 049335c

Browse files
committed
exist and is_file
1 parent 9895af2 commit 049335c

File tree

2 files changed

+185
-3
lines changed

2 files changed

+185
-3
lines changed

lua/plenary/path2.lua

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
--- NOTES:
2+
--- Rework on plenary.Path with a focus on better cross-platform support
3+
--- including 'shellslash' support.
4+
--- Effort to improve performance made (notably `:absolue` ~2x faster).
5+
---
6+
--- Some finiky behaviors ironed out
7+
--- eg. `:normalize`
8+
--- TODO: demonstrate
9+
---
10+
--- BREAKING CHANGES:
11+
--- - `Path.new` no longer supported (think it's more confusing that helpful
12+
--- and not really used as far as I can tell)
13+
---
14+
--- - drop `__concat` metamethod? it was untested, not sure how functional it is
15+
---
16+
--- - `Path` objects are now "read-only", I don't think people were ever doing
17+
--- things like `path.filename = 'foo'` but now explicitly adding some barrier
18+
--- to this. Allows us to compute `filename` from "metadata" parsed once on
19+
--- instantiation.
20+
21+
--- TODO: rework `split_root` logic according to python 3.12
22+
123
local uv = vim.loop
224
local iswin = uv.os_uname().sysname == "Windows_NT"
325
local hasshellslash = vim.fn.exists "+shellslash" == 1
@@ -264,19 +286,32 @@ Path.__index = function(t, k)
264286
end
265287
end
266288

289+
---@param self plenary.Path2
290+
---@param other string|plenary.Path2
291+
---@return plenary.Path2
267292
Path.__div = function(self, other)
268293
assert(Path.is_path(self))
269294
assert(Path.is_path(other) or type(other) == "string")
270295

271296
return self:joinpath(other)
272297
end
273298

299+
---@param self plenary.Path2
300+
---@return string
274301
Path.__tostring = function(self)
275302
return self.filename
276303
end
277304

278-
Path.__concat = function(self, other)
279-
return self.filename .. other
305+
---@param self plenary.Path2
306+
---@param other string|plenary.Path2
307+
---@return boolean
308+
Path.__eq = function(self, other)
309+
assert(Path.is_path(self))
310+
assert(Path.is_path(other) or type(other) == "string")
311+
-- TODO
312+
if true then
313+
error "not yet implemented"
314+
end
280315
end
281316

282317
---@alias plenary.Path2Args string|plenary.Path2|(string|plenary.Path2)[]
@@ -335,6 +370,7 @@ function Path:new(...)
335370
__div = function(t, other) return Path.__div(t, other) end,
336371
__concat = function(t, other) return Path.__concat(t, other) end,
337372
__tostring = function(t) return Path.__tostring(t) end,
373+
__eq = function(t, other) return Path.__eq(t, other) end,
338374
__metatable = Path,
339375
-- stylua: ignore end
340376
})
@@ -379,6 +415,12 @@ function Path:is_absolute()
379415
return self._path.has_drv and self.drv ~= ""
380416
end
381417

418+
---@return boolean
419+
function Path:exists()
420+
local stat = uv.fs_stat(self:absolute())
421+
return stat ~= nil and not vim.tbl_isempty(stat)
422+
end
423+
382424
--- if path doesn't exists, returns false
383425
---@return boolean
384426
function Path:is_dir()
@@ -389,6 +431,16 @@ function Path:is_dir()
389431
return false
390432
end
391433

434+
--- if path doesn't exists, returns false
435+
---@return boolean
436+
function Path:is_file()
437+
local stat = uv.fs_stat(self:absolute())
438+
if stat then
439+
return stat.type == "file"
440+
end
441+
return false
442+
end
443+
392444
---@param parts string[] path parts
393445
---@return string[]
394446
local function resolve_dots(parts)
@@ -443,8 +495,46 @@ function Path:joinpath(...)
443495
return Path:new { self, ... }
444496
end
445497

498+
--- makes a path relative to another (by default the cwd).
499+
--- if path is already a relative path
500+
---@param to string|plenary.Path2? absolute path to make relative to (default: cwd)
501+
---@return string
502+
function Path:make_relative(to)
503+
to = vim.F.if_nil(to, self.cwd)
504+
if type(to) == "string" then
505+
to = Path:new(to)
506+
end
507+
508+
if self:is_absolute() then
509+
local to_abs = to:absolute()
510+
511+
if to_abs == self:absolute() then
512+
return "."
513+
else
514+
-- TODO
515+
end
516+
else
517+
end
518+
519+
-- SEE: `Path.relative_to` implementation (3.12) specifically `walk_up` param
520+
521+
local matches = true
522+
for i = 1, #to.parts do
523+
if to.parts[i] ~= self.parts[i] then
524+
matches = false
525+
break
526+
end
527+
end
528+
529+
if matches then
530+
return "."
531+
end
532+
533+
-- /home/jt/foo/bar/baz
534+
-- /home/jt
535+
end
536+
446537
-- vim.o.shellslash = false
447-
local p = Path:new("lua"):joinpath "plenary"
448538
-- vim.print(p)
449539
-- print(p.filename, p:is_absolute(), p:absolute())
450540
-- vim.o.shellslash = true

tests/plenary/path2_spec.lua

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ describe("Path2", function()
229229
assert.are.same("lua" .. path.sep .. "plenary", Path:new("lua", "plenary").filename)
230230
end)
231231

232+
describe(".exists()", function()
233+
it_cross_plat("finds files that exist", function()
234+
assert.are.same(true, Path:new("README.md"):exists())
235+
end)
236+
237+
it_cross_plat("returns false for files that do not exist", function()
238+
assert.are.same(false, Path:new("asdf.md"):exists())
239+
end)
240+
end)
241+
232242
describe(".is_dir()", function()
233243
it_cross_plat("should find directories that exist", function()
234244
assert.are.same(true, Path:new("lua"):is_dir())
@@ -242,4 +252,86 @@ describe("Path2", function()
242252
assert.are.same(false, Path:new("README.md"):is_dir())
243253
end)
244254
end)
255+
256+
describe(".is_file()", function()
257+
it_cross_plat("should not allow directories", function()
258+
assert.are.same(true, not Path:new("lua"):is_file())
259+
end)
260+
261+
it_cross_plat("should return false when the file does not exist", function()
262+
assert.are.same(true, not Path:new("asdf"):is_file())
263+
end)
264+
265+
it_cross_plat("should show files as file", function()
266+
assert.are.same(true, Path:new("README.md"):is_file())
267+
end)
268+
end)
269+
270+
-- describe(":make_relative", function()
271+
-- local root = iswin and "c:\\" or "/"
272+
-- it_cross_plat("can take absolute paths and make them relative to the cwd", function()
273+
-- local p = Path:new { "lua", "plenary", "path.lua" }
274+
-- local absolute = vim.loop.cwd() .. path.sep .. p.filename
275+
-- local relative = Path:new(absolute):make_relative()
276+
-- assert.are.same(p.filename, relative)
277+
-- end)
278+
279+
-- it_cross_plat("can take absolute paths and make them relative to a given path", function()
280+
-- local r = Path:new { root, "home", "prime" }
281+
-- local p = Path:new { "aoeu", "agen.lua" }
282+
-- local absolute = r.filename .. path.sep .. p.filename
283+
-- local relative = Path:new(absolute):make_relative(r.filename)
284+
-- assert.are.same(relative, p.filename)
285+
-- end)
286+
287+
-- it_cross_plat("can take double separator absolute paths and make them relative to the cwd", function()
288+
-- local p = Path:new { "lua", "plenary", "path.lua" }
289+
-- local absolute = vim.loop.cwd() .. path.sep .. path.sep .. p.filename
290+
-- local relative = Path:new(absolute):make_relative()
291+
-- assert.are.same(relative, p.filename)
292+
-- end)
293+
294+
-- it_cross_plat("can take double separator absolute paths and make them relative to a given path", function()
295+
-- local r = Path:new { root, "home", "prime" }
296+
-- local p = Path:new { "aoeu", "agen.lua" }
297+
-- local absolute = r.filename .. path.sep .. path.sep .. p.filename
298+
-- local relative = Path:new(absolute):make_relative(r.filename)
299+
-- assert.are.same(relative, p.filename)
300+
-- end)
301+
302+
-- it_cross_plat("can take absolute paths and make them relative to a given path with trailing separator", function()
303+
-- local r = Path:new { root, "home", "prime" }
304+
-- local p = Path:new { "aoeu", "agen.lua" }
305+
-- local absolute = r.filename .. path.sep .. p.filename
306+
-- local relative = Path:new(absolute):make_relative(r.filename .. path.sep)
307+
-- assert.are.same(relative, p.filename)
308+
-- end)
309+
310+
-- it_cross_plat("can take absolute paths and make them relative to the root directory", function()
311+
-- local p = Path:new { "home", "prime", "aoeu", "agen.lua" }
312+
-- local absolute = root .. p.filename
313+
-- local relative = Path:new(absolute):make_relative(root)
314+
-- assert.are.same(relative, p.filename)
315+
-- end)
316+
317+
-- it_cross_plat("can take absolute paths and make them relative to themselves", function()
318+
-- local p = Path:new { root, "home", "prime", "aoeu", "agen.lua" }
319+
-- local relative = Path:new(p.filename):make_relative(p.filename)
320+
-- assert.are.same(relative, ".")
321+
-- end)
322+
323+
-- it_cross_plat("should not truncate if path separator is not present after cwd", function()
324+
-- local cwd = "tmp" .. path.sep .. "foo"
325+
-- local p = Path:new { "tmp", "foo_bar", "fileb.lua" }
326+
-- local relative = Path:new(p.filename):make_relative(cwd)
327+
-- assert.are.same(p.filename, relative)
328+
-- end)
329+
330+
-- it_cross_plat("should not truncate if path separator is not present after cwd and cwd ends in path sep", function()
331+
-- local cwd = "tmp" .. path.sep .. "foo" .. path.sep
332+
-- local p = Path:new { "tmp", "foo_bar", "fileb.lua" }
333+
-- local relative = Path:new(p.filename):make_relative(cwd)
334+
-- assert.are.same(p.filename, relative)
335+
-- end)
336+
-- end)
245337
end)

0 commit comments

Comments
 (0)