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)
|
---|