Skip to content

Commit 7d2186a

Browse files
committed
add shorten
1 parent 53587fe commit 7d2186a

File tree

2 files changed

+123
-4
lines changed

2 files changed

+123
-4
lines changed

lua/plenary/path2.lua

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
--- including 'shellslash' support.
44
--- Effort to improve performance made (notably `:absolue` ~2x faster).
55
---
6-
--- Some finiky behaviors ironed out
7-
--- eg. `:normalize`
8-
--- TODO: demonstrate
96
---
107
--- BREAKING CHANGES:
118
--- - `Path.new` no longer supported (think it's more confusing that helpful
@@ -29,6 +26,28 @@
2926
---
3027
--- eg. `Path:new("foo/bar_baz"):make_relative("foo/bar", true)` => returns
3128
--- "../bar_baz"
29+
---
30+
--- - remove `Path:normalize`. It doesn't make any sense. eg. this test case
31+
--- ```lua
32+
--- it("can normalize ~ when file is within home directory (trailing slash)", function()
33+
--- local home = "/home/test/"
34+
--- local p = Path:new { home, "./test_file" }
35+
--- p.path.home = home
36+
--- p._cwd = "/tmp/lua"
37+
--- assert.are.same("~/test_file", p:normalize())
38+
--- end)
39+
--- ```
40+
--- if the idea is to make `/home/test/test_file` relative to `/tmp/lua`, the result
41+
--- should be `../../home/test/test_file`, only then can you substitue the
42+
--- home directory for `~`.
43+
--- So should really be `../../~/test_file`. But using `~` in a relative path
44+
--- like that looks weird to me. And as this function first makes paths
45+
--- relative, you will never get a leading `~` (since `~` literally
46+
--- represents the absolute path of the home directory).
47+
--- To top it off, something like `../../~/test_file` is impossible on Windows.
48+
--- `C:/Users/test/test_file` relative to `C:/Windows/temp` is
49+
--- `../../Users/test/test_file` and there's no home directory absolute path
50+
--- in this.
3251

3352
local uv = vim.loop
3453
local iswin = uv.os_uname().sysname == "Windows_NT"
@@ -482,8 +501,12 @@ function Path:_filename(drv, root, relparts)
482501

483502
relparts = vim.F.if_nil(relparts, self.relparts)
484503
local relpath = table.concat(relparts, self.sep)
504+
local res = drv .. root .. relpath
485505

486-
return drv .. root .. relpath
506+
if res ~= "" then
507+
return res
508+
end
509+
return "."
487510
end
488511

489512
---@param x any
@@ -704,4 +727,40 @@ function Path:make_relative(to, walk_up)
704727
return Path:new(steps).filename
705728
end
706729

730+
731+
--- Shorten path parts.
732+
--- By default, shortens all part except the last tail part to a length of 1.
733+
--- eg.
734+
--- ```lua
735+
--- local p = Path:new("this/is/a/long/path")
736+
--- p:shorten() -- Output: "t/i/a/l/path"
737+
--- ```
738+
---@param len integer? length to shorthen path parts to (default: `1`)
739+
--- indices of path parts to exclude from being shortened, supports negative index
740+
---@param excludes integer[]?
741+
---@return string
742+
function Path:shorten(len, excludes)
743+
len = vim.F.if_nil(len, 1)
744+
excludes = vim.F.if_nil(excludes, { #self.relparts })
745+
746+
local new_parts = {}
747+
748+
for i, part in ipairs(self.relparts) do
749+
local neg_i = -(#self.relparts + 1) + i
750+
if #part > len and not vim.list_contains(excludes, i) and not vim.list_contains(excludes, neg_i) then
751+
part = part:sub(1, len)
752+
end
753+
table.insert(new_parts, part)
754+
end
755+
756+
return self:_filename(nil, nil, new_parts)
757+
end
758+
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
765+
707766
return Path

tests/plenary/path2_spec.lua

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe("Path2", function()
5858
{ "foo/bar/", "foo/bar" },
5959
{ { readme_path }, readme_path },
6060
{ { readme_path, license_path }, license_path }, -- takes only the last abs path
61+
{ ".", "." },
6162
}
6263

6364
return paths
@@ -334,6 +335,65 @@ describe("Path2", function()
334335
end)
335336
end)
336337

338+
describe(":shorten", function()
339+
it_cross_plat("can shorten a path", function()
340+
local long_path = "this/is/a/long/path"
341+
local short_path = Path:new(long_path):shorten()
342+
assert.are.same(short_path, plat_path "t/i/a/l/path")
343+
end)
344+
345+
it_cross_plat("can shorten a path's components to a given length", function()
346+
local long_path = "this/is/a/long/path"
347+
local short_path = Path:new(long_path):shorten(2)
348+
assert.are.same(short_path, plat_path "th/is/a/lo/path")
349+
350+
-- without the leading /
351+
long_path = "this/is/a/long/path"
352+
short_path = Path:new(long_path):shorten(3)
353+
assert.are.same(short_path, plat_path "thi/is/a/lon/path")
354+
355+
-- where len is greater than the length of the final component
356+
long_path = "this/is/an/extremely/long/path"
357+
short_path = Path:new(long_path):shorten(5)
358+
assert.are.same(short_path, plat_path "this/is/an/extre/long/path")
359+
end)
360+
361+
it_cross_plat("can shorten a path's components when excluding parts", function()
362+
local long_path = "this/is/a/long/path"
363+
local short_path = Path:new(long_path):shorten(nil, { 1, -1 })
364+
assert.are.same(short_path, plat_path "this/i/a/l/path")
365+
366+
-- without the leading /
367+
long_path = "this/is/a/long/path"
368+
short_path = Path:new(long_path):shorten(nil, { 1, -1 })
369+
assert.are.same(short_path, plat_path "this/i/a/l/path")
370+
371+
-- where excluding positions greater than the number of parts
372+
long_path = "this/is/an/extremely/long/path"
373+
short_path = Path:new(long_path):shorten(nil, { 2, 4, 6, 8 })
374+
assert.are.same(short_path, plat_path "t/is/a/extremely/l/path")
375+
376+
-- where excluding positions less than the negation of the number of parts
377+
long_path = "this/is/an/extremely/long/path"
378+
short_path = Path:new(long_path):shorten(nil, { -2, -4, -6, -8 })
379+
assert.are.same(short_path, plat_path "this/i/an/e/long/p")
380+
end)
381+
382+
it_cross_plat("can shorten a path's components to a given length and exclude positions", function()
383+
local long_path = "this/is/a/long/path"
384+
local short_path = Path:new(long_path):shorten(2, { 1, -1 })
385+
assert.are.same(short_path, plat_path "this/is/a/lo/path")
386+
387+
long_path = "this/is/a/long/path"
388+
short_path = Path:new(long_path):shorten(3, { 2, -2 })
389+
assert.are.same(short_path, plat_path "thi/is/a/long/pat")
390+
391+
long_path = "this/is/an/extremely/long/path"
392+
short_path = Path:new(long_path):shorten(5, { 3, -3 })
393+
assert.are.same(short_path, plat_path "this/is/an/extremely/long/path")
394+
end)
395+
end)
396+
337397
describe("parents", function()
338398
it_cross_plat("should extract the ancestors of the path", function()
339399
local p = Path:new(vim.fn.getcwd())

0 commit comments

Comments
 (0)