relativepath

function relativepath( from, to ) --> path

Description

Returns a relative path between the paths from and to.

Parameters

from

The source path.

to

The destination path.

Return Values

path

The relative path between from and to.

Code

local lfs = require( "lfs" ) --ZREQ-lfs

--ZFUNC-unixpath-v1
local function unixpath( path )
   return path:gsub( "\\", "/" )
end
--ZFUNC-currentdir-v1
local function currentdir()
   local path = lfs.currentdir()
   return unixpath( path )
end
--ZFUNC-rootprefix-v1
local function rootprefix( path )
   local remote = path:match[[^//%w+/]] or path:match[[^\\%w+\]]
   if remote then return remote end

   local unix = path:sub( 1, 1 )
   if unix == "/" then return unix end

   local win = path:match[=[^[a-zA-Z]:[\/]]=]
   if win then return win end

   return ""
end
--ZFUNC-isabsolute-v1
local function isabsolute( path ) --> res
   return rootprefix( path ) ~= ""
end
--ZFUNC-firstchar-v1
local function firstchar( str )
   return string.sub( str, 1, 1 )
end
--ZFUNC-lastchar-v1
local function lastchar( str )
   return string.sub( str, #str )
end
--ZFUNC-joinpath-v1
local function joinpath( tab )
   local rooted = false
   local tmptab = {}
   for k, s in ipairs( tab ) do
      if k == 1 and firstchar( s ) == "/" then
         rooted = true
      end

      if firstchar( s ) == "/" then
         s = s:sub( 2 )
      end
      if lastchar( s ) == "/" then
         s = s:sub( 1, #s - 1 )
      end

      if #s > 0 then
         table.insert( tmptab, s )
      end
   end

   if rooted then
      return "/"..table.concat( tmptab, "/" )
   end

   return table.concat( tmptab, "/" )
end
--ZFUNC-abspath-v2
local function abspath( path ) --> abs
   if isabsolute( path ) then
      return path
   end

   return joinpath{ currentdir(), path }
end
--ZFUNC-splitpath-v1
local function splitpath( path ) --> tab
   local tab = {}
   for token in string.gmatch( path, "[^/]+" ) do
      if #token > 0 then
         table.insert( tab, token )
      end
   end
   return tab
end

--ZFUNC-relativepath-v1
local function relativepath( from, to ) --> path
   from = abspath( from )
   to = abspath( to )

   if from == to then return "" end

   local fromtab = splitpath( from )
   local totab = splitpath( to )

   local f = 1
   local t = 1
   while f <= #fromtab and
         t <= #totab and
         fromtab[ f ] == totab[ t ] do
      f = f+1
      t = t+1
   end

   local pathtab = {}
   for i = f, #fromtab do
      table.insert( pathtab, ".." )
   end
   for i = t, #totab do
      table.insert( pathtab, totab[ i ] )
   end

   return table.concat( pathtab, "/" )
end

return relativepath

Examples

local t = require( "tapered" )
local relativepath = require( "relativepath" )

t.is( "../../impl/bbb", relativepath( "data/dir/test/aaa", "data/dir/impl/bbb" ) )

--https://github.com/nodejs/node/blob/master/test/parallel/test-path.js
t.is( "..", relativepath( "/var/lib", "/var" ) )
t.is( "../../bin", relativepath( "/var/lib", "/bin" ) )
t.is( "", relativepath( "/var/lib", "/var/lib" ) )
t.is( "../apache", relativepath( "/var/lib", "/var/apache" ) )
t.is( "lib", relativepath( "/var/", "/var/lib" ) )
t.is( "var/lib", relativepath( "/", "/var/lib" ) )
t.is( "bar/package.json", relativepath( "/foo/test", "/foo/test/bar/package.json" ) )
t.is( "../..", relativepath( "/Users/a/web/b/test/mails", "/Users/a/web/b" ) )
t.is( "../baz", relativepath( "/foo/bar/baz-quux", "/foo/bar/baz" ) )
t.is( "../baz-quux", relativepath( "/foo/bar/baz", "/foo/bar/baz-quux" ) )
t.is( "../baz", relativepath( "/baz-quux", "/baz" ) )
t.is( "../baz-quux", relativepath( "/baz", "/baz-quux" ) )

t.done()

See also