user1798951
user1798951

Reputation: 19

How to convert absolute path in relative path using LUA?

I have two absolute file path (A and B). I want to convert A in relative path of B. How can i do it using Lua script?

Upvotes: 1

Views: 3900

Answers (3)

Jongwook Choi
Jongwook Choi

Reputation: 8914

It's not for general Lua users, but for neovim users, there is a useful plenary.nvim utility for this:

local Path = require "plenary.path"
local abspath = "/a/b/c"
local cwd = "/a/"
    
local relpath = Path:new(abspath):make_relative(cwd)
relpath  -- "b/c"

Upvotes: 2

millimoose
millimoose

Reputation: 39950

An algorithm to do this would be:

  1. Convert both paths to a canonical form. (I.e. starting at the filesystem root, no trailing slashes, no double slashes, etc.)
  2. From both paths, remove their common prefix. (E.g. go from A="/a/b/c/d.txt" B="/a/b/e/f.txt" to A="c/d.txt" B="e/f.txt")
  3. Split the remaining paths by the path separator. (So you'll end up with A={"c", "d.txt"} B={"e", "f.txt"}
  4. If the original path B points to a regular file, remove the last element of B. If it points to a directory, leave it as is. (For our example, you'd get B={"e"})
  5. For every item left in B, insert a .. at the beginning of A. (Resulting with A={"..", "c", "d.txt"})
  6. Join the contents of A using the path separator to get the final result: "../c/d.txt"

Here is a very rough implementation that works with no libraries. It doesn't handle any edge cases like from and to being the same, or one being a prefix of the other. (By this I mean the function will certainly break in those cases because mismatch will be equal to 0.) This is mainly because I'm lazy; also because the code is already getting a little long and this would harm readability even more.

-- to and from have to be in a canonical absolute form at 
-- this point
to = "/a/b/c/d.txt"
from = "/a/b/e/f.txt"

min_len = math.min(to:len(), from:len())
mismatch = 0
print(min_len)

for i = 1, min_len do
    if to:sub(i, i) ~= from:sub(i, i) then
        mismatch = i
        break
    end
end

-- handle edge cases here

-- the parts of `a` and `b` that differ
to_diff = to:sub(mismatch)
from_diff = from:sub(mismatch)

from_file = io.open(from)
from_is_dir = false
if (from_file) then
    -- check if `from` is a directory
    result, err_msg, err_no = from_file:read(0)
    if (err_no == 21) then -- EISDIR - `from` is a directory

end

result = ""
for slash in from_diff:gmatch("/") do
    result = result .. "../"
end
if from_is_dir then
    result = result .. "../"
end
result = result .. to_diff

print(result) --> ../c/d.txt

Upvotes: 5

catwell
catwell

Reputation: 7046

Use pl.path.relpath from the Penlight library (doc / source).

Upvotes: 3

Related Questions