I implemented `relativePath` that Converts input path to a path relative to 
baseDir. 
[https://github.com/nim-lang/Nim/pull/8166](https://github.com/nim-lang/Nim/pull/8166)

Is my `relativePath` wrong? It should be implemented like one in other 
language? Is it worth to be in the Nim Standard Library?

This `relativePath` has 3 paramters but similar functions in other language 
have only 2 parameters. And this pull request was created on 1 Jul, but still 
it is `S: Waiting on review` status. I don't want to solve conflicts everytime 
`lib/pure/ospaths.nim` or `tests/stdlib/tospaths.nim` was changed.

I wrote why & when my implementation require 3 parameters in the comment:
    
    
    proc relativePath*(path, baseDir: string; curDir: string = ""): string {.
      noSideEffect, rtl, extern: "nos$1".} =
      ## Convert `path` to a path relative to baseDir.
      ##
      ## `path` and `baseDir` must be absolute paths or relative paths from 
`curDir`.
      ## When one of `path` and `baseDir` is relative and other one is 
absolute, `curDir` must be absolute.
      ##
      ## On DOS like filesystem, when a drive of `path` is different from 
`baseDir`,
      ## this proc just return the `path` as is because no way to calculate the 
relative path.
      ##
      ## Following pseudo code looks like Nim explains requirements of 
parameters.
      ##
      ## .. code-block:: nim
      ##
      ##   if isAbsolute(path) and isAbsolute(baseDir):
      ##     # `curDir` is ignored
      ##   else not (isAbsolute(path) or isAbsolute(baseDir)):
      ##     # Both `path` and `baseDir` must be relative to a same path.
      ##     # Suppose ".." is only in front of path, not in middle of path.
      ##     let numParDirsInPath = number of ".." in path
      ##     let numParDirsInBaseDir = number of ".." in baseDir
      ##     if numParDirsInBaseDir > numParDirsInPath:
      ##       # `curDir` can be relative or absolute path.
      ##       # Both `path` and `baseDir` must be relative paths from `curDir`.
      ##       # `curDir` must has (numParDirsInBaseDir - numParDirsInPath) 
directories or raise ValueError.
      ##     else:
      ##       # `curDir` is ignored
      ##   else:
      ##     # `curDir` must be a absolute path.
      ##     # `curDir` is used to convert `path` or `base` to a absolute path.
      ##
      ## For example, relativePath("a", "b") returns "../a", but 
relativePath("a", "..") raise exception.
      ## Because result of relativePath("a", "..") requires the parent 
directory name of "a".
      ##
      ## This proc never read filesystem.
      ## `baseDir` is always assumed to be a directory even if that path is 
actually a file.
      ##
      ## You can find more examples in tests/stdlib/tospaths.nim
      runnableExamples:
        doAssert relativePath("/home/abc".unixToNativePath, 
"/home/abc/x".unixToNativePath) == "..".unixToNativePath
        doAssert relativePath("abc".unixToNativePath, "xyz".unixToNativePath, 
"".unixToNativePath) == "../abc".unixToNativePath
        doAssert relativePath(".".unixToNativePath, "..".unixToNativePath, 
"/abc".unixToNativePath) == "abc".unixToNativePath
        doAssert relativePath("/home/xyz/d".unixToNativePath, 
"xyz".unixToNativePath, "/home".unixToNativePath) == "d".unixToNativePath
        doAssert relativePath("../d".unixToNativePath, "/usr".unixToNativePath, 
"/home/xyz".unixToNativePath) == "../home/d".unixToNativePath
    
    Run

I researched relativePath in other langauges: 
[https://github.com/nim-lang/Nim/pull/8166#issuecomment-430974787](https://github.com/nim-lang/Nim/pull/8166#issuecomment-430974787)

When `path` and `baseDir` are absolute paths, my implementation and other 
implementation works almost same way. When one of `path` xor `baseDir` are 
absolute paths, relative path is converted to absolute path using 3rd parameter 
(NOT using `getCurrentDir`) and a result is calculated from these 2 absolute 
paths. When both `path` and `baseDir` are relative paths, my implementation 
works differently from other implementation. My implementation doesn't call 
procedures that has side effect like getCurrentDir() or setCurrentDir(). When 
converting (path X relative to A) to (path Y relative to B) where X, Y, A and B 
are relative path, A and B are directories and B is relative to A, 3rd 
parameter is required if path Y includes parent directory of path X (In other 
words, path B has more ".." than path X). For example, if path X = ".", A = "a" 
and B = ".." then path Y = "a". If path X = "x", A = "a/b" and B = "../.." then 
path Y = "a/b/x". `relative` function in Boost filesystem takes 2 parameters 
and they are converted to absolute paths inside the function if they are 
relative path. 
[https://www.boost.org/doc/libs/1_68_0/libs/filesystem/src/operations.cpp](https://www.boost.org/doc/libs/1_68_0/libs/filesystem/src/operations.cpp)
 Converting a relative path to a absolute path requires getting current working 
directory that cannot be done from noSideEffect procedure. It cannot convert 
relative paths that does not exists in filesystem. When converting a relative 
path to a absolute path, that could prepend directory names that don't affect 
resulting path. For example, if current working directroy was "/somedir/a/b", 
path X = "x" become "/somedir/a/b/x" and path B = "../../" become "/somedir". 
Then, `relative` function in Boost filesystem convert "/somedir/a/b/x" to 
"a/b/x" which is relative to "/somedir". So it need to allocate more memory to 
store string "/somedir" and require more comparsion to calcute relative path. 
My `relativePath` works without converting path to absolute path and doesn't 
needs to store unnecessary string "/somedir". It only requires 3rd parameter 
"a/b". 

Reply via email to