#!/usr/local/bin/python
"""
$Id: Os_path.py,v 1.3 1997/07/14 11:31:26 arcege Exp $"""

import os, string

def split(str):
  """Split a search path string into a list.  Platform independant."""
  return string.splitfields(str, os.pathsep)
def join(list):
  """Make a search path string from a list.  Platform independant."""
  return string.joinfields(list, os.pathsep)

class Path:
  """Base interface into file search path mechanism.
Platform-independant."""
  ev_name = ''
  list = []
  result_list = 0

  def __init__(self, path=''):
    """Can be given an optional path to override the default."""
    if path:
      if type(path) is type(''):
	self.list = split(path)
      else:
	self.list = map(None, path[:])
    else:
      self.list = self.__class__.list
    self.using_sys = not path
    self.inithook()

  def inithook(self):
    """Perform any subclass specific initializations."""
    pass

  def __str__(self):
    """Return the list."""
    return join(self.list)

  def __len__(self):
    return len(self.list)

  def __cmp__(self, other):
    return cmp(self.list, other)
  def __rcmp__(self, other):
    return -cmp(self.list, other)

  def __getslice__(self, low, high):
    return self.__class__(self.list[low:high])

  def __getitem__(self, index):
    """The search engine: determine if the given file is in the path.
This is determined by the "get_file" method.
If the "result_list" member is true, return a tuple.
If the type of the argument given is not a string,
return as for self.list[index]."""
    import types
    if type(index) is not types.StringType:
      return self.list[index]

    filename = index
    if self.result_list:
      result = ()
    else:
      result = None
    for dir in self.list:
      if os.path.exists(dir) and (os.path.isdir(dir) or os.path.islink(dir)):
        file = self.get_file(dir, filename)
        if file:
	  if self.result_list:
	    for entry in file:
	      result = result + (os.path.normpath(entry),)
	  else:
	    return os.path.normpath(file)
    return result

  def get_file(self, dir, filename):
    """Return a pathname (or list of paths if result_list is set) if
it passes the _filetest."""
    file = os.path.join(dir, filename)
    if self._filetest(file):
      if self.result_list:
	return (file,)
      else:
	return file
    return None

  def _filetest(self, file):
    """Does the file exists?
Subclasses will have other tests, like executability, writability, etc."""
    return os.path.exists(file)

  def use_attr(self, attrname, *args):
    """Apply the changes to the instance list and if using the class
list, then apply the changes to the environment as well."""

    qualattr = getattr(self.list, attrname)
    value = apply(qualattr, args)
    if self.using_sys and self.ev_name:
      os.environ[self.ev_name] = join(self.list)
    return value

  # "append", "insert" and "remove" methods are wrappers for "use_attr"
  def append(self, dir):
    return self.use_attr('append', dir)
  def insert(self, pos, dir):
    return self.use_attr('insert', pos, dir)
  def remove(self, pos):
    return self.use_attr('remove', pos)

  # if the method is not defined here, then "inherit" from the list
  # attribute
  def __getattr__(self, attr):
    if attr not in ('reverse', 'sort'):
      return getattr(self.list, attr)

class Binpath(Path):
  """Provide an interface to a PATH search engine.
Allows for searching for an executable file in the given path:
  p = Binpath()
  p['cat'] ==> '/usr/bin/cat'
"""

  ev_name = 'PATH'
  if os.environ.has_key(ev_name):
    list = split(os.environ[ev_name])
  else:
    list = []

  def inithook(self):
    """Determine the user and group ids.  Platform-dependant;
defaults to 0 on systems with no concept of a user."""
    if hasattr(os, 'getuid'):
      self.uid = apply(getattr(os, 'getuid'), ())
    else:
      self.uid = 0
    if hasattr(os, 'getgid'):
      self.gid = apply(getattr(os, 'getgid'), ())
    else:
      self.gid = 0

  def get_file(self, dir, filename):
    file = os.path.join(dir, filename)
    # if the file doesn't pass the test, then attempt platform specific
    # filenames
    if self._filetest(file):
      return file
    elif os.name in ('posix', 'mac'):
      return None
    elif os.name in ('dos', 'nt'):
      if self._filetest(file + '.exe'):
	return file + '.exe'
      elif self._filetest(file + '.com'):
	return file + '.com'
      elif self._filetest(file + '.bat'):
	return file + '.bat'
    return None

  def _filetest(self, file):
    """Determine if the file is executable from it's mode."""
    if not os.path.exists(file):
      return 0
    stat = os.path.stat
    statinfo = os.stat(file)
    mode = stat.S_IMODE(statinfo[stat.ST_MODE])
    if ((statinfo[stat.ST_UID] == self.uid and stat.S_IXUSR & mode) or
	(statinfo[stat.ST_GID] == self.gid and stat.S_IXGRP & mode) or
	stat.S_IXOTH & mode):
      return 1
    return 0

class Manpath(Path):
  ev_name = 'MANPATH'
  if os.environ.has_key(ev_name):
    list = split(os.environ[ev_name])
  else:
    list = []
  result_list = 1

  def get_file(self, dir, filename):
    """Search in the subdirectories for the manpage."""
    result = ()
    for subdir in os.listdir(dir):
      page = os.path.splitext(subdir)[0][3:]
      testfile = os.path.join(dir, os.path.join(subdir, filename + '.' + page))

      if self._filetest(testfile):
	result = result + (testfile,)
    return result

class Cdpath(Path):
  """Basic subclass of Path with the following exceptions:
The key must be a directory or symbolic link in the path, and
There is the added method "chdir", which acts like a UNIX shell "ch"
when CDPATH exists:
  If the given argument is an absolute pathname, just use that,
  If it passes the _filetest as is, then use it;
  Otherwise, pass the argument thru the search and use that.
Return os.error if the pathname doesn't exist."""

  ev_name = 'CDPATH'
  if os.environ.has_key(ev_name):
    list = split(os.environ[ev_name])
  else:
    list = []

  def _filetest(self, file):
    if os.path.exists(file) and (os.path.isdir(file) or os.path.islink(file)):
      return 1
    return 0

  def chdir(self, key):
    if os.path.isabs(key):
      os.chdir(key)
    elif self._filetest(key):
      os.chdir(key)
    else:
      dir = self[key]
      if dir:
        os.chdir(dir)
      raise os.error, (2, "no found")

def _test():
  import sys
  p = Path('/etc:/usr/lib:/usr/etc:/etc/mail')
  print 'sendmail.cf -->', p['sendmail.cf']
  bp = Binpath()
  print 'cat -->', bp['cat']
  print '%s -->' % sys.argv[0], bp[sys.argv[0]]
  mp = Manpath()
  print 'index -->', mp['index']
  print 'Path:', p
  p.append('/tmp')
  print 'Path with /tmp (1)', p
  p.insert(1, '/var/tmp')
  print 'Path with /var/tmp (2)', p

if __name__ == '__main__':
  _test()

