|
1 # Module 'riscospath' -- common operations on RISC OS pathnames. |
|
2 |
|
3 # contributed by Andrew Clover ( andrew@oaktree.co.uk ) |
|
4 |
|
5 # The "os.path" name is an alias for this module on RISC OS systems; |
|
6 # on other systems (e.g. Mac, Windows), os.path provides the same |
|
7 # operations in a manner specific to that platform, and is an alias |
|
8 # to another module (e.g. macpath, ntpath). |
|
9 |
|
10 """ |
|
11 Instead of importing this module directly, import os and refer to this module |
|
12 as os.path. |
|
13 """ |
|
14 |
|
15 # strings representing various path-related bits and pieces |
|
16 curdir = '@' |
|
17 pardir = '^' |
|
18 extsep = '/' |
|
19 sep = '.' |
|
20 pathsep = ',' |
|
21 defpath = '<Run$Dir>' |
|
22 altsep = None |
|
23 |
|
24 # Imports - make an error-generating swi object if the swi module is not |
|
25 # available (ie. we are not running on RISC OS Python) |
|
26 |
|
27 import os, stat, string |
|
28 |
|
29 try: |
|
30 import swi |
|
31 except ImportError: |
|
32 class _swi: |
|
33 def swi(*a): |
|
34 raise AttributeError, 'This function only available under RISC OS' |
|
35 block= swi |
|
36 swi= _swi() |
|
37 |
|
38 [_false, _true]= range(2) |
|
39 |
|
40 _roots= ['$', '&', '%', '@', '\\'] |
|
41 |
|
42 |
|
43 # _allowMOSFSNames |
|
44 # After importing riscospath, set _allowMOSFSNames true if you want the module |
|
45 # to understand the "-SomeFS-" notation left over from the old BBC Master MOS, |
|
46 # as well as the standard "SomeFS:" notation. Set this to be fully backwards |
|
47 # compatible but remember that "-SomeFS-" can also be a perfectly valid file |
|
48 # name so care must be taken when splitting and joining paths. |
|
49 |
|
50 _allowMOSFSNames= _false |
|
51 |
|
52 |
|
53 ## Path manipulation, RISC OS stylee. |
|
54 |
|
55 def _split(p): |
|
56 """ |
|
57 split filing system name (including special field) and drive specifier from rest |
|
58 of path. This is needed by many riscospath functions. |
|
59 """ |
|
60 dash= _allowMOSFSNames and p[:1]=='-' |
|
61 if dash: |
|
62 q= string.find(p, '-', 1)+1 |
|
63 else: |
|
64 if p[:1]==':': |
|
65 q= 0 |
|
66 else: |
|
67 q= string.find(p, ':')+1 # q= index of start of non-FS portion of path |
|
68 s= string.find(p, '#') |
|
69 if s==-1 or s>q: |
|
70 s= q # find end of main FS name, not including special field |
|
71 else: |
|
72 for c in p[dash:s]: |
|
73 if c not in string.ascii_letters: |
|
74 q= 0 |
|
75 break # disallow invalid non-special-field characters in FS name |
|
76 r= q |
|
77 if p[q:q+1]==':': |
|
78 r= string.find(p, '.', q+1)+1 |
|
79 if r==0: |
|
80 r= len(p) # find end of drive name (if any) following FS name (if any) |
|
81 return (p[:q], p[q:r], p[r:]) |
|
82 |
|
83 |
|
84 def normcase(p): |
|
85 """ |
|
86 Normalize the case of a pathname. This converts to lowercase as the native RISC |
|
87 OS filesystems are case-insensitive. However, not all filesystems have to be, |
|
88 and there's no simple way to find out what type an FS is argh. |
|
89 """ |
|
90 return string.lower(p) |
|
91 |
|
92 |
|
93 def isabs(p): |
|
94 """ |
|
95 Return whether a path is absolute. Under RISC OS, a file system specifier does |
|
96 not make a path absolute, but a drive name or number does, and so does using the |
|
97 symbol for root, URD, library, CSD or PSD. This means it is perfectly possible |
|
98 to have an "absolute" URL dependent on the current working directory, and |
|
99 equally you can have a "relative" URL that's on a completely different device to |
|
100 the current one argh. |
|
101 """ |
|
102 (fs, drive, path)= _split(p) |
|
103 return drive!='' or path[:1] in _roots |
|
104 |
|
105 |
|
106 def join(a, *p): |
|
107 """ |
|
108 Join path elements with the directory separator, replacing the entire path when |
|
109 an absolute or FS-changing path part is found. |
|
110 """ |
|
111 j= a |
|
112 for b in p: |
|
113 (fs, drive, path)= _split(b) |
|
114 if j=='' or fs!='' or drive!='' or path[:1] in _roots: |
|
115 j= b |
|
116 elif j[-1]==':': |
|
117 j= j+b |
|
118 else: |
|
119 j= j+'.'+b |
|
120 return j |
|
121 |
|
122 |
|
123 def split(p): |
|
124 """ |
|
125 Split a path in head (everything up to the last '.') and tail (the rest). FS |
|
126 name must still be dealt with separately since special field may contain '.'. |
|
127 """ |
|
128 (fs, drive, path)= _split(p) |
|
129 q= string.rfind(path, '.') |
|
130 if q!=-1: |
|
131 return (fs+drive+path[:q], path[q+1:]) |
|
132 return ('', p) |
|
133 |
|
134 |
|
135 def splitext(p): |
|
136 """ |
|
137 Split a path in root and extension. This assumes the 'using slash for dot and |
|
138 dot for slash with foreign files' convention common in RISC OS is in force. |
|
139 """ |
|
140 (tail, head)= split(p) |
|
141 if '/' in head: |
|
142 q= len(head)-string.rfind(head, '/') |
|
143 return (p[:-q], p[-q:]) |
|
144 return (p, '') |
|
145 |
|
146 |
|
147 def splitdrive(p): |
|
148 """ |
|
149 Split a pathname into a drive specification (including FS name) and the rest of |
|
150 the path. The terminating dot of the drive name is included in the drive |
|
151 specification. |
|
152 """ |
|
153 (fs, drive, path)= _split(p) |
|
154 return (fs+drive, p) |
|
155 |
|
156 |
|
157 def basename(p): |
|
158 """ |
|
159 Return the tail (basename) part of a path. |
|
160 """ |
|
161 return split(p)[1] |
|
162 |
|
163 |
|
164 def dirname(p): |
|
165 """ |
|
166 Return the head (dirname) part of a path. |
|
167 """ |
|
168 return split(p)[0] |
|
169 |
|
170 |
|
171 def commonprefix(m): |
|
172 "Given a list of pathnames, returns the longest common leading component" |
|
173 if not m: return '' |
|
174 s1 = min(m) |
|
175 s2 = max(m) |
|
176 n = min(len(s1), len(s2)) |
|
177 for i in xrange(n): |
|
178 if s1[i] != s2[i]: |
|
179 return s1[:i] |
|
180 return s1[:n] |
|
181 |
|
182 |
|
183 ## File access functions. Why are we in os.path? |
|
184 |
|
185 def getsize(p): |
|
186 """ |
|
187 Return the size of a file, reported by os.stat(). |
|
188 """ |
|
189 st= os.stat(p) |
|
190 return st[stat.ST_SIZE] |
|
191 |
|
192 |
|
193 def getmtime(p): |
|
194 """ |
|
195 Return the last modification time of a file, reported by os.stat(). |
|
196 """ |
|
197 st = os.stat(p) |
|
198 return st[stat.ST_MTIME] |
|
199 |
|
200 getatime= getmtime |
|
201 |
|
202 |
|
203 # RISC OS-specific file access functions |
|
204 |
|
205 def exists(p): |
|
206 """ |
|
207 Test whether a path exists. |
|
208 """ |
|
209 try: |
|
210 return swi.swi('OS_File', '5s;i', p)!=0 |
|
211 except swi.error: |
|
212 return 0 |
|
213 |
|
214 lexists = exists |
|
215 |
|
216 |
|
217 def isdir(p): |
|
218 """ |
|
219 Is a path a directory? Includes image files. |
|
220 """ |
|
221 try: |
|
222 return swi.swi('OS_File', '5s;i', p) in [2, 3] |
|
223 except swi.error: |
|
224 return 0 |
|
225 |
|
226 |
|
227 def isfile(p): |
|
228 """ |
|
229 Test whether a path is a file, including image files. |
|
230 """ |
|
231 try: |
|
232 return swi.swi('OS_File', '5s;i', p) in [1, 3] |
|
233 except swi.error: |
|
234 return 0 |
|
235 |
|
236 |
|
237 def islink(p): |
|
238 """ |
|
239 RISC OS has no links or mounts. |
|
240 """ |
|
241 return _false |
|
242 |
|
243 ismount= islink |
|
244 |
|
245 |
|
246 # Same-file testing. |
|
247 |
|
248 # samefile works on filename comparison since there is no ST_DEV and ST_INO is |
|
249 # not reliably unique (esp. directories). First it has to normalise the |
|
250 # pathnames, which it can do 'properly' using OS_FSControl since samefile can |
|
251 # assume it's running on RISC OS (unlike normpath). |
|
252 |
|
253 def samefile(fa, fb): |
|
254 """ |
|
255 Test whether two pathnames reference the same actual file. |
|
256 """ |
|
257 l= 512 |
|
258 b= swi.block(l) |
|
259 swi.swi('OS_FSControl', 'isb..i', 37, fa, b, l) |
|
260 fa= b.ctrlstring() |
|
261 swi.swi('OS_FSControl', 'isb..i', 37, fb, b, l) |
|
262 fb= b.ctrlstring() |
|
263 return fa==fb |
|
264 |
|
265 |
|
266 def sameopenfile(a, b): |
|
267 """ |
|
268 Test whether two open file objects reference the same file. |
|
269 """ |
|
270 return os.fstat(a)[stat.ST_INO]==os.fstat(b)[stat.ST_INO] |
|
271 |
|
272 |
|
273 ## Path canonicalisation |
|
274 |
|
275 # 'user directory' is taken as meaning the User Root Directory, which is in |
|
276 # practice never used, for anything. |
|
277 |
|
278 def expanduser(p): |
|
279 (fs, drive, path)= _split(p) |
|
280 l= 512 |
|
281 b= swi.block(l) |
|
282 |
|
283 if path[:1]!='@': |
|
284 return p |
|
285 if fs=='': |
|
286 fsno= swi.swi('OS_Args', '00;i') |
|
287 swi.swi('OS_FSControl', 'iibi', 33, fsno, b, l) |
|
288 fsname= b.ctrlstring() |
|
289 else: |
|
290 if fs[:1]=='-': |
|
291 fsname= fs[1:-1] |
|
292 else: |
|
293 fsname= fs[:-1] |
|
294 fsname= string.split(fsname, '#', 1)[0] # remove special field from fs |
|
295 x= swi.swi('OS_FSControl', 'ib2s.i;.....i', 54, b, fsname, l) |
|
296 if x<l: |
|
297 urd= b.tostring(0, l-x-1) |
|
298 else: # no URD! try CSD |
|
299 x= swi.swi('OS_FSControl', 'ib0s.i;.....i', 54, b, fsname, l) |
|
300 if x<l: |
|
301 urd= b.tostring(0, l-x-1) |
|
302 else: # no CSD! use root |
|
303 urd= '$' |
|
304 return fsname+':'+urd+path[1:] |
|
305 |
|
306 # Environment variables are in angle brackets. |
|
307 |
|
308 def expandvars(p): |
|
309 """ |
|
310 Expand environment variables using OS_GSTrans. |
|
311 """ |
|
312 l= 512 |
|
313 b= swi.block(l) |
|
314 return b.tostring(0, swi.swi('OS_GSTrans', 'sbi;..i', p, b, l)) |
|
315 |
|
316 |
|
317 # Return an absolute path. RISC OS' osfscontrol_canonicalise_path does this among others |
|
318 abspath = os.expand |
|
319 |
|
320 |
|
321 # realpath is a no-op on systems without islink support |
|
322 realpath = abspath |
|
323 |
|
324 |
|
325 # Normalize a path. Only special path element under RISC OS is "^" for "..". |
|
326 |
|
327 def normpath(p): |
|
328 """ |
|
329 Normalize path, eliminating up-directory ^s. |
|
330 """ |
|
331 (fs, drive, path)= _split(p) |
|
332 rhs= '' |
|
333 ups= 0 |
|
334 while path!='': |
|
335 (path, el)= split(path) |
|
336 if el=='^': |
|
337 ups= ups+1 |
|
338 else: |
|
339 if ups>0: |
|
340 ups= ups-1 |
|
341 else: |
|
342 if rhs=='': |
|
343 rhs= el |
|
344 else: |
|
345 rhs= el+'.'+rhs |
|
346 while ups>0: |
|
347 ups= ups-1 |
|
348 rhs= '^.'+rhs |
|
349 return fs+drive+rhs |
|
350 |
|
351 |
|
352 # Directory tree walk. |
|
353 # Independent of host system. Why am I in os.path? |
|
354 |
|
355 def walk(top, func, arg): |
|
356 """Directory tree walk with callback function. |
|
357 |
|
358 For each directory in the directory tree rooted at top (including top |
|
359 itself, but excluding '.' and '..'), call func(arg, dirname, fnames). |
|
360 dirname is the name of the directory, and fnames a list of the names of |
|
361 the files and subdirectories in dirname (excluding '.' and '..'). func |
|
362 may modify the fnames list in-place (e.g. via del or slice assignment), |
|
363 and walk will only recurse into the subdirectories whose names remain in |
|
364 fnames; this can be used to implement a filter, or to impose a specific |
|
365 order of visiting. No semantics are defined for, or required of, arg, |
|
366 beyond that arg is always passed to func. It can be used, e.g., to pass |
|
367 a filename pattern, or a mutable object designed to accumulate |
|
368 statistics. Passing None for arg is common.""" |
|
369 |
|
370 try: |
|
371 names= os.listdir(top) |
|
372 except os.error: |
|
373 return |
|
374 func(arg, top, names) |
|
375 for name in names: |
|
376 name= join(top, name) |
|
377 if isdir(name) and not islink(name): |
|
378 walk(name, func, arg) |