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