1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import posixpath
22 import os
23 from urllib import url2pathname, pathname2url
24
25 __all__ = [
26 'nativejoin',
27 'pathjoin',
28 'relpathto',
29 'tslash',
30 'relpath'
31 ]
32
34 """
35 Join paths to a base, observing pardir.
36
37 If base doesn't *end* with '/' we assume it's a file rather than a directory.
38 (so we get rid of it)
39 """
40
41 if base and not base.endswith('/'):
42
43 base = '/'.join(base.split('/')[:-1])
44 base = tslash(base)
45 path = (base,) + paths
46 return posixpath.normpath(posixpath.join(*path))
47
49 """
50 Joins two paths - returning a native file path.
51
52 Given a base path and a relative location, (in posix format)
53 return a file path in a (relatively) OS native way.
54 """
55 return url2pathname(pathjoin(base, path))
56
58 """
59 Given two paths relative to a directory, work out a path from origin
60 to destination.
61
62 Assumes UNIX/URL type relative paths.
63 If origin doesn't *end* with '/' we assume it's a file rather than a
64 directory.
65
66 If the same paths are passed in :
67 if the path ends with ('/') then we return ''
68 else we return the last part of the path (presumably a filename)
69
70 If thisdir doesn't start with '/' then we add one
71 (this makes the top level of thisdir our root directory)
72 """
73 orig_thisdir = thisdir
74 if not thisdir.startswith('/'):
75 thisdir = '/' + thisdir
76 orig_abs = posixpath.normpath(posixpath.join(thisdir, origin))
77 dest_abs = posixpath.normpath(posixpath.join(thisdir, dest))
78 if origin.endswith('/') and not orig_abs.endswith('/'):
79 orig_abs = orig_abs + '/'
80 if dest.endswith('/') and not dest_abs.endswith('/'):
81 dest_abs = dest_abs + '/'
82
83
84
85 orig_list = orig_abs.split('/')[:-1]
86 dest_list = dest_abs.split('/')
87
88
89 if orig_list[0] != dest_list[0]:
90
91
92 return dest
93
94
95 i = 0
96 for start_seg, dest_seg in zip(orig_list, dest_list):
97 if start_seg != dest_seg:
98 break
99 i += 1
100
101
102
103
104 segments = ['..'] * (len(orig_list) - i)
105
106 segments += dest_list[i:]
107 if len(segments) == 0:
108
109
110 if dest.endswith('/'):
111 return ''
112
113 return dest_list[-1]
114 else:
115 return '/'.join(segments)
116
118 """Given two absolute paths, work out a path from origin to destination.
119
120 Assumes UNIX/URL type relative paths.
121 If origin doesn't *end* with '/' we assume it's a file rather than
122 a directory.
123
124 If the same paths are passed in :
125 if the path ends with ('/') then we return ''
126 else we return the last part of the path (presumably a filename)
127
128 If origin or dest don't start with '/' then we add it.
129
130 We are *assuming* relative paths on the same device
131 (i.e. same top level directory)
132 """
133 if not origin.startswith('/'):
134 origin = '/' + origin
135 if not dest.startswith('/'):
136 dest = '/' + dest
137
138
139 orig_list = origin.split('/')[:-1]
140 dest_list = dest.split('/')
141
142
143 i = 0
144 for start_seg, dest_seg in zip(orig_list, dest_list):
145 if start_seg != dest_seg:
146 break
147 i += 1
148
149
150
151
152 segments = ['..'] * (len(orig_list) - i)
153
154 segments += dest_list[i:]
155 if len(segments) == 0:
156
157
158 if dest.endswith('/'):
159 return ''
160
161 return dest_list[-1]
162 else:
163 return '/'.join(segments)
164
166 """Add a trailing slash to a path if it needs one.
167
168 Doesn't use os.sep because you end up jiggered on windoze - when you
169 want separators for URLs.
170 """
171 if (apath and
172 apath != '.' and
173 not apath.endswith('/') and
174 not apath.endswith('\\')):
175 return apath + '/'
176 else:
177 return apath
178
179
180
182 thelist = [
183 ('/', 'fish.html'),
184 ('/dir/dir/', '../file'),
185 ('dir/dir/', '../file'),
186 ('dir/dir/', '../../file'),
187 ('dir/dir/', '../../../file'),
188 ('/dir/dir/', '../notherdir/file'),
189 ('/dir/dir/', '../../notherdir/file'),
190 ('dir/dir/', '../../notherdir/file'),
191 ('dir/dir/', '../../../notherdir/file'),
192 ('', '../path'),
193 ]
194 for entry in thelist:
195 print entry, ' :: ', pathjoin(*entry)
196 print entry, ' :: ', nativejoin(*entry)
197 print '\n'
198
200 thedir = '//toplevel/dirone/dirtwo/dirthree'
201 thelist = [
202 ('file1.html', 'file2.html'),
203 ('file1.html', '../file2.html'),
204 ('../file1.html', '../file2.html'),
205 ('../file1.html', 'file2.html'),
206 ('../fish1/fish2/', '../../sub1/sub2/'),
207 ('../fish1/fish2/', 'sub1/sub2'),
208 ('../../../fish1/fish2/', 'sub1/sub2/'),
209 ('../../../fish1/fish2/', 'sub1/sub2/file1.html'),
210 ]
211 for orig, dest in thelist:
212 print '(%s, %s) : ' % (orig, dest), relpathto(thedir, orig, dest)
213
215 thedir = 'section3/'
216 thelist = [
217 ('../archive/strangeindex1.html', 'article2.html'),
218 ]
219 for orig, dest in thelist:
220 answer = relpathto(thedir, orig, dest)
221 print '(%s, %s) : ' % (orig, dest), answer
222
224 thelist = [
225 ('/hello/fish/', 'bungles'),
226 ]
227 for orig, dest in thelist:
228 answer = relpath(orig, dest)
229 print '(%s, %s) : ' % (orig, dest), answer
230
231
232 if __name__ == '__main__':
233 testJoin()
234 testRelpathto()
235 testRelpath()
236
237
238 """
239 TODO
240 ====
241
242 More comprehensive tests.
243
244 CHANGELOG
245 2005/07/31
246 Can now pass mulitple args to ``pathjoin``.
247 Finalised as version 0.1.0
248
249 2005/06/18
250 Changes by Nicola Larosa
251 Code cleanup
252 lines shortened
253 comments on line above code
254 empty comments in empty lines
255
256 2005/05/28
257 Added relpath to __all__
258
259
260 TODO
261 Move into pythonutils
262 relpathto could call relpath (and so be shorter)
263 nativejoin could accept multiple paths
264 Could tslash be more elegant ?
265 """
266