Home | History | Annotate | Download | only in plat-riscos
      1 # Module 'riscospath' -- common operations on RISC OS pathnames.
      2 
      3 # contributed by Andrew Clover  ( andrew (at] 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)
    379