views:

171

answers:

1

This problem should be a no-brainer, but I haven't yet been able to nail it.

I need a function that takes two parameters, each a file path, relative or absolute, and returns a filepath which is the first path (target) resolved relative to the second path (start). The resolved path may be relative to the current directory or may be absolute (I don't care).

Here as an attempted implementation, complete with several doc tests, that exercises some sample uses cases (and demonstrates where it fails). A runnable script is also available on my source code repository, but it may change. The runnable script will run the doctest if no parameters are supplied or will pass one or two parameters to findpath if supplied.

def findpath(target, start=os.path.curdir):
 r"""
 Find a path from start to target where target is relative to start.

 >>> orig_wd = os.getcwd()
 >>> os.chdir('c:\\windows') # so we know what the working directory is

 >>> findpath('d:\\')
 'd:\\'

 >>> findpath('d:\\', 'c:\\windows')
 'd:\\'

 >>> findpath('\\bar', 'd:\\')
 'd:\\bar'

 >>> findpath('\\bar', 'd:\\foo') # fails with '\\bar'
 'd:\\bar'

 >>> findpath('bar', 'd:\\foo')
 'd:\\foo\\bar'

 >>> findpath('bar\\baz', 'd:\\foo')
 'd:\\foo\\bar\\baz'

 >>> findpath('\\baz', 'd:\\foo\\bar') # fails with '\\baz'
 'd:\\baz'

 Since we're on the C drive, findpath may be allowed to return
 relative paths for targets on the same drive. I use abspath to
 confirm that the ultimate target is what we expect.
 >>> os.path.abspath(findpath('\\bar'))
 'c:\\bar'

 >>> os.path.abspath(findpath('bar'))
 'c:\\windows\\bar'

 >>> findpath('..', 'd:\\foo\\bar')
 'd:\\foo'

 >>> findpath('..\\bar', 'd:\\foo')
 'd:\\bar'

 The parent of the root directory is the root directory.
 >>> findpath('..', 'd:\\')
 'd:\\'

 restore the original working directory
 >>> os.chdir(orig_wd)
 """
 return os.path.normpath(os.path.join(start, target))

As you can see from the comments in the doctest, this implementation fails when the start specifies a drive letter and the target is relative to the root of the drive.

This brings up a few questions

  1. Is this behavior a limitation of os.path.join? In other words, should os.path.join('d:\foo', '\bar') resolve to 'd:\bar'? As a Windows user, I tend to think so, but I hate to think that a mature function like path.join would need alteration to handle this use case.
  2. Is there an example of an existing target path resolver such as findpath that will work in all of these test cases?
  3. If 'no' to the above questions, how would you implement this desired behavior?
+1  A: 

I agree with you: this seems like a deficiency in os.path.join. Looks like you have to deal with the drives separately. This code passes all your tests:

def findpath(target, start=os.path.curdir):
    sdrive, start = os.path.splitdrive(start)
    tdrive, target = os.path.splitdrive(target)
    rdrive = tdrive or sdrive
    return os.path.normpath(os.path.join(rdrive, os.path.join(start, target)))

(and yes, I had to nest two os.path.join's to get it to work...)

Ned Batchelder