blob: a38b22c62ab90bc9d3577f3ef1a5062c36300786 [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"""
Guido van Rossumbceccf52001-04-10 22:07:43 +0000206 try:
207 return swi.swi('OS_File', '5s;i', p)!=0
208 except swi.error:
209 return 0
Guido van Rossum228d8072001-03-02 05:58:11 +0000210
211
212def isdir(p):
213 """
214Is a path a directory? Includes image files.
215"""
Guido van Rossumbceccf52001-04-10 22:07:43 +0000216 try:
217 return swi.swi('OS_File', '5s;i', p) in [2, 3]
218 except swi.error:
219 return 0
Guido van Rossum228d8072001-03-02 05:58:11 +0000220
221
222def isfile(p):
223 """
224Test whether a path is a file, including image files.
225"""
Guido van Rossumbceccf52001-04-10 22:07:43 +0000226 try:
227 return swi.swi('OS_File', '5s;i', p) in [1, 3]
228 except swi.error:
229 return 0
Guido van Rossum228d8072001-03-02 05:58:11 +0000230
231
232def islink(p):
233 """
234RISC OS has no links or mounts.
235"""
236 return _false
237
238ismount= islink
239
240
241# Same-file testing.
242
243# samefile works on filename comparison since there is no ST_DEV and ST_INO is
244# not reliably unique (esp. directories). First it has to normalise the
245# pathnames, which it can do 'properly' using OS_FSControl since samefile can
246# assume it's running on RISC OS (unlike normpath).
247
248def samefile(fa, fb):
249 """
250Test whether two pathnames reference the same actual file.
251"""
252 l= 512
253 b= swi.block(l)
254 swi.swi('OS_FSControl', 'isb..i', 37, fa, b, l)
255 fa= b.ctrlstring()
256 swi.swi('OS_FSControl', 'isb..i', 37, fb, b, l)
257 fb= b.ctrlstring()
258 return fa==fb
259
260
261def sameopenfile(a, b):
262 """
263Test whether two open file objects reference the same file.
264"""
265 return os.fstat(a)[stat.ST_INO]==os.fstat(b)[stat.ST_INO]
266
267
268## Path canonicalisation
269
270# 'user directory' is taken as meaning the User Root Directory, which is in
271# practice never used, for anything.
272
273def expanduser(p):
274 (fs, drive, path)= _split(p)
275 l= 512
276 b= swi.block(l)
277
278 if path[:1]!='@':
279 return p
280 if fs=='':
281 fsno= swi.swi('OS_Args', '00;i')
282 swi.swi('OS_FSControl', 'iibi', 33, fsno, b, l)
283 fsname= b.ctrlstring()
284 else:
285 if fs[:1]=='-':
286 fsname= fs[1:-1]
287 else:
288 fsname= fs[:-1]
289 fsname= string.split(fsname, '#', 1)[0] # remove special field from fs
290 x= swi.swi('OS_FSControl', 'ib2s.i;.....i', 54, b, fsname, l)
291 if x<l:
292 urd= b.tostring(0, l-x-1)
293 else: # no URD! try CSD
294 x= swi.swi('OS_FSControl', 'ib0s.i;.....i', 54, b, fsname, l)
295 if x<l:
296 urd= b.tostring(0, l-x-1)
297 else: # no CSD! use root
298 urd= '$'
299 return fsname+':'+urd+path[1:]
300
301# Environment variables are in angle brackets.
302
303def expandvars(p):
304 """
305Expand environment variables using OS_GSTrans.
306"""
307 l= 512
308 b= swi.block(l)
309 return b.tostring(0, swi.swi('OS_GSTrans', 'sbi;..i', p, b, l))
310
311
312# Return an absolute path.
313
314def abspath(p):
315 return normpath(join(os.getcwd(), p))
316
317
318# Normalize a path. Only special path element under RISC OS is "^" for "..".
319
320def normpath(p):
321 """
322Normalize path, eliminating up-directory ^s.
323"""
324 (fs, drive, path)= _split(p)
325 rhs= ''
326 ups= 0
327 while path!='':
328 (path, el)= split(path)
329 if el=='^':
330 ups= ups+1
331 else:
332 if ups>0:
333 ups= ups-1
334 else:
335 if rhs=='':
336 rhs= el
337 else:
338 rhs= el+'.'+rhs
339 while ups>0:
340 ups= ups-1
341 rhs= '^.'+rhs
342 return fs+drive+rhs
343
344
345# Directory tree walk.
346# Independent of host system. Why am I in os.path?
347
348def walk(top, func, arg):
349 """
350walk(top,func,args) calls func(arg, d, files) for each directory "d" in the tree
351rooted at "top" (including "top" itself). "files" is a list of all the files and
352subdirs in directory "d".
353"""
354 try:
355 names= os.listdir(top)
356 except os.error:
357 return
358 func(arg, top, names)
359 for name in names:
360 name= join(top, name)
361 if isdir(name) and not islink(name):
362 walk(name, func, arg)
363