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