Skip to content

Commit 6e2f699

Browse files
committed
feat(utils): add function utils.fs.make_relative()
1 parent 8849592 commit 6e2f699

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

lua/orgmode/utils/fs.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,37 @@ function M.trim_common_root(paths)
6363
return result
6464
end
6565

66+
---@param filepath string an absolute path
67+
---@param base string an absolute path to an ancestor of filepath;
68+
--- here, `'.'` represents the current working directory, and
69+
--- *not* the current file's directory.
70+
---@return string filepath_relative_to_base
71+
function M.make_relative(filepath, base)
72+
vim.validate({
73+
filepath = { filepath, 'string', false },
74+
base = { base, 'string', false },
75+
})
76+
filepath = vim.fn.fnamemodify(filepath, ':p')
77+
base = vim.fn.fnamemodify(base, ':p')
78+
if base:sub(-1) ~= '/' then
79+
base = base .. '/'
80+
end
81+
local levels_up = 0
82+
for parent in vim.fs.parents(base) do
83+
if parent:sub(-1) ~= '/' then
84+
parent = parent .. '/'
85+
end
86+
if vim.startswith(filepath, parent) then
87+
filepath = filepath:sub(parent:len() + 1)
88+
if levels_up > 0 then
89+
return vim.fs.joinpath(string.rep('..', levels_up, '/'), filepath)
90+
end
91+
return vim.fs.joinpath('.', filepath)
92+
end
93+
levels_up = levels_up + 1
94+
end
95+
-- No common root, just return the absolute path.
96+
return filepath
97+
end
98+
6699
return M

tests/plenary/utils/fs_spec.lua

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,51 @@ describe('get_real_path', function()
8686
assert.is.False(fs_utils.get_real_path('.'))
8787
end)
8888
end)
89+
90+
describe('make_relative', function()
91+
local path = fs_utils.get_real_path(file.filename) ---@cast path string
92+
local basename = vim.fs.basename(path)
93+
local dirname = vim.fs.dirname(path)
94+
local dirname_slash = vim.fs.joinpath(dirname, '')
95+
local root = path
96+
for parent in vim.fs.parents(path) do
97+
root = parent
98+
end
99+
100+
it('gets the basename', function()
101+
local expected = vim.fs.joinpath('.', basename)
102+
local actual = fs_utils.make_relative(path, dirname)
103+
assert.are.same(expected, actual)
104+
end)
105+
106+
it('gets the basename with trailing slash', function()
107+
local expected = vim.fs.joinpath('.', basename)
108+
local actual = fs_utils.make_relative(path, dirname_slash)
109+
assert.are.same(expected, actual)
110+
end)
111+
112+
it('works one level up', function()
113+
local parent_name = vim.fs.basename(dirname)
114+
local expected = vim.fs.joinpath('.', parent_name, basename)
115+
local actual = fs_utils.make_relative(path, vim.fs.dirname(dirname))
116+
assert.are.same(expected, actual)
117+
end)
118+
119+
it('works one level up with trailing slash', function()
120+
local parent_name = vim.fs.basename(dirname)
121+
local expected = vim.fs.joinpath('.', parent_name, basename)
122+
local actual = fs_utils.make_relative(path, vim.fs.dirname(dirname) .. '/')
123+
assert.are.same(expected, actual)
124+
end)
125+
126+
it('produces a relative path even at the root', function()
127+
local relpath = fs_utils.make_relative(path, root)
128+
assert(vim.endswith(path, relpath))
129+
end)
130+
131+
it('climbs up via ..', function()
132+
local relpath = fs_utils.make_relative(root, path)
133+
local only_cdup = vim.regex('\\V\\(../\\)\\+')
134+
assert(only_cdup:match_str(relpath))
135+
end)
136+
end)

0 commit comments

Comments
 (0)