1 |
bh |
339 |
# Copyright (c) 2001, 2002 by Intevation GmbH |
2 |
bh |
6 |
# Authors: |
3 |
|
|
# Bernhard Herzog <[email protected]> |
4 |
|
|
# |
5 |
|
|
# This program is free software under the GPL (>=v2) |
6 |
|
|
# Read the file COPYING coming with Thuban for details. |
7 |
|
|
|
8 |
|
|
""" |
9 |
|
|
Functions to deal with filenames |
10 |
|
|
""" |
11 |
|
|
|
12 |
|
|
__version__ = "$Revision$" |
13 |
|
|
|
14 |
|
|
import os.path |
15 |
|
|
from string import split, join |
16 |
|
|
|
17 |
|
|
|
18 |
|
|
def relative_filename_common(dir, absname, sep): |
19 |
|
|
"""Return a filename relative to dir for the absolute file name absname. |
20 |
|
|
This is part the posix and nt versions have in common. Both dir and |
21 |
|
|
absname are assumed to be normalized (as done with os.normpath) |
22 |
|
|
absolute filenames without drive letters. sep is the platform |
23 |
|
|
specific directory separator. |
24 |
|
|
""" |
25 |
|
|
|
26 |
|
|
# split the filenames into their components. remove the first item |
27 |
|
|
# since it will be always empty because both names are absolute. |
28 |
|
|
dir_parts = split(dir, sep)[1:] |
29 |
|
|
absname_parts = split(absname, sep)[1:] |
30 |
|
|
|
31 |
|
|
# count the number of common parts at the start of dir_parts and |
32 |
|
|
# absname_parts |
33 |
|
|
max_common = min(len(dir_parts), len(absname_parts)) |
34 |
|
|
common = 0 |
35 |
|
|
while common < max_common and dir_parts[common] == absname_parts[common]: |
36 |
|
|
common = common + 1 |
37 |
|
|
|
38 |
|
|
# If the common part is the root directory, return absname |
39 |
|
|
if common == 0: |
40 |
|
|
return absname |
41 |
|
|
|
42 |
|
|
# for each directory under the common part prepend a '..'. |
43 |
|
|
rel_parts = (len(dir_parts) - common) * ['..'] + absname_parts[common:] |
44 |
|
|
return join(rel_parts, sep) |
45 |
|
|
|
46 |
|
|
|
47 |
|
|
def relative_filename_posix(dir, absname): |
48 |
|
|
"""Return a filename relative to dir for the absolute file name absname. |
49 |
|
|
|
50 |
|
|
If absname is already a relative filename, return it unchanged. If |
51 |
|
|
the common directory of dir and absname is /, return absname |
52 |
|
|
unchanged. If dir is not an absolute name, raise TypeError. |
53 |
|
|
|
54 |
|
|
This is the posix specific version of relative_filename. |
55 |
|
|
|
56 |
|
|
Example: |
57 |
|
|
>>> from fileutil import relative_filename_posix |
58 |
|
|
>>> relative_filename_posix("/usr/local/lib/", "/usr/local/lib/python") |
59 |
|
|
'python' |
60 |
|
|
>>> relative_filename_posix("/usr/local/lib/", "/usr/local/bin/python") |
61 |
|
|
'../bin/python' |
62 |
|
|
>>> relative_filename_posix("/usr/local/lib/", "/usr/bin/python") |
63 |
|
|
'../../bin/python' |
64 |
|
|
>>> relative_filename_posix("/usr/local/lib/", "/var/spool/mail") |
65 |
|
|
'/var/spool/mail' |
66 |
|
|
>>> relative_filename_posix("/home/", "xyzzy") |
67 |
|
|
'xyzzy' |
68 |
|
|
>>> relative_filename_posix("home/", "/xyzzy") |
69 |
|
|
Traceback (most recent call last): |
70 |
|
|
File "<stdin>", line 1, in ? |
71 |
|
|
File "fileutil.py", line 42, in relative_filename_posix |
72 |
|
|
raise TypeError("first argument must be an absolute filename") |
73 |
|
|
TypeError: first argument must be an absolute filename |
74 |
|
|
""" |
75 |
|
|
# for posix, the common part does exactly what we need, except for |
76 |
|
|
# the special cases and input checking. Import posixpath explicitly |
77 |
|
|
# for that to faciliate testing |
78 |
|
|
import posixpath |
79 |
|
|
if not posixpath.isabs(absname): |
80 |
bh |
339 |
return absname |
81 |
bh |
6 |
|
82 |
|
|
if not posixpath.isabs(dir): |
83 |
|
|
raise TypeError("first argument must be an absolute filename") |
84 |
|
|
|
85 |
|
|
dir = posixpath.normpath(dir) |
86 |
|
|
absname = posixpath.normpath(absname) |
87 |
|
|
|
88 |
|
|
return relative_filename_common(dir, absname, "/") |
89 |
|
|
|
90 |
|
|
def relative_filename_nt(dir, absname): |
91 |
|
|
r"""Return a filename relative to dir for the absolute file name absname. |
92 |
|
|
|
93 |
|
|
If absname is already a relative filename or if dir and absname are |
94 |
|
|
on different drives, return absname. If the common directory of dir |
95 |
|
|
and absname is the drive's root directory, return absname. If dir is |
96 |
|
|
not an absolute name or either name doesn't have a drive letter, |
97 |
|
|
raise TypeError. |
98 |
|
|
|
99 |
|
|
This is the nt specific version of relative_filename. |
100 |
|
|
|
101 |
|
|
Example: |
102 |
|
|
>>> from fileutil import relative_filename_nt |
103 |
|
|
>>> relative_filename_nt(r"C:\Programme\Python", r"C:\Programme\Thuban") |
104 |
|
|
'..\\Thuban' |
105 |
|
|
>>> relative_filename_nt(r"C:\Programme\Python", r"D:\Programme\Thuban") |
106 |
|
|
'D:\\Programme\\Thuban' |
107 |
|
|
>>> relative_filename_nt(r"C:\Programme\Python", r"C:Programme") |
108 |
|
|
'C:Programme' |
109 |
|
|
>>> relative_filename_nt(r"C:Programme\Python", r"C:Programme") |
110 |
|
|
Traceback (most recent call last): |
111 |
|
|
File "<stdin>", line 1, in ? |
112 |
|
|
File "fileutil.py", line 123, in relative_filename_nt |
113 |
|
|
raise TypeError("first argument must be an absolute filename") |
114 |
|
|
TypeError: first argument must be an absolute filename |
115 |
|
|
>>> relative_filename_nt(r"\Programme\Python", r"\Programme") |
116 |
|
|
Traceback (most recent call last): |
117 |
|
|
File "<stdin>", line 1, in ? |
118 |
|
|
File "fileutil.py", line 120, in relative_filename_nt |
119 |
|
|
raise TypeError("Both parameters must have a drive letter") |
120 |
|
|
TypeError: Both parameters must have a drive letter |
121 |
|
|
""" |
122 |
|
|
# first check the parameters. Imort ntpath directly to facilitate |
123 |
|
|
# testing on non-nt systems. |
124 |
|
|
import ntpath |
125 |
|
|
|
126 |
|
|
dir = ntpath.normpath(dir) |
127 |
|
|
absname = ntpath.normpath(absname) |
128 |
|
|
|
129 |
|
|
dir_drive, dir_rest = ntpath.splitdrive(dir) |
130 |
|
|
absname_drive, absname_rest = ntpath.splitdrive(absname) |
131 |
|
|
#print dir_drive, dir_rest |
132 |
|
|
#print absname_drive, absname_rest |
133 |
|
|
if not dir_drive or not absname_drive: |
134 |
|
|
raise TypeError("Both parameters must have a drive letter") |
135 |
|
|
|
136 |
|
|
if not ntpath.isabs(dir_rest): |
137 |
|
|
raise TypeError("first argument must be an absolute filename") |
138 |
|
|
|
139 |
|
|
# handle some special cases |
140 |
|
|
if not ntpath.isabs(absname_rest): |
141 |
bh |
339 |
return absname |
142 |
bh |
6 |
|
143 |
|
|
if dir_drive != absname_drive: |
144 |
|
|
return absname |
145 |
|
|
|
146 |
|
|
# Now both dir_rest and absname_rest are absolute filenames without |
147 |
|
|
# drive letter. We can now use the common part to determine the |
148 |
|
|
# relative name |
149 |
|
|
return relative_filename_common(dir_rest, absname_rest, "\\") |
150 |
|
|
|
151 |
|
|
|
152 |
bh |
339 |
# bind the appropriate version of relative_filename for the platform |
153 |
|
|
# we're currently running on. |
154 |
bh |
6 |
if os.name == "posix": |
155 |
|
|
relative_filename = relative_filename_posix |
156 |
|
|
elif os.name == "nt": |
157 |
|
|
relative_filename = relative_filename_nt |
158 |
|
|
else: |
159 |
|
|
raise RuntimeError("No implementation of relative_filename" |
160 |
|
|
" available for platform" + os.name) |
161 |
|
|
|
162 |
|
|
__test__ = {"relative_filename_posix": relative_filename_posix, |
163 |
|
|
"relative_filename_nt": relative_filename_nt} |
164 |
|
|
|
165 |
|
|
# if run as a script, run doctest |
166 |
|
|
def _test(): |
167 |
|
|
import doctest, fileutil |
168 |
|
|
# Pass an isprivate function that always returns true so that only |
169 |
|
|
# items in __test__ are tested |
170 |
|
|
return doctest.testmod(fileutil, isprivate = lambda *args: 1) |
171 |
|
|
|
172 |
|
|
if __name__ == "__main__": |
173 |
|
|
_test() |