# -*- coding: iso-8859-1 -*-

# ==============================================================================
# COPYRIGHT (C) 1991 - 2003  EDF R&D                  WWW.CODE-ASTER.ORG
# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
# (AT YOUR OPTION) ANY LATER VERSION.
#
# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
#
# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
# ==============================================================================

"""Definition of ASTER_SYSTEM class.
"""

__revision__ = "$Id: as_system.py 2657 2006-12-19 15:55:44Z courtois $"


# ----- differ messages translation
def _(mesg):
   return mesg

import os
import sys
import re
import popen2
import shutil
import glob
import getpass
import resource
from types import StringType, ListType, TupleType, IntType, LongType, \
                  InstanceType
EnumTypes = (ListType, TupleType)

# colors and tty...
label = { 'OK' : _('  OK  '), 'FAILED' : _('FAILED'), 'SKIP' : _(' SKIP ') }
if sys.stdout.isatty():
   label['OK']     = '\033[1;32m%s\033[m\017' % label['OK']
   label['FAILED'] = '\033[1;31m%s\033[m\017' % label['FAILED']
#   label['SKIP']   = '\033[1;34m%s\033[m\017' % label['SKIP']
for k in label:
   label[k] = '['+label[k]+']'

#-------------------------------------------------------------------------------
def _exitcode(status, default=0):
   """Extrait le code retour du status. Retourne `default` si le process
   n'a pas fini pas exit.
   """
   iret = default
   if os.WIFEXITED(status):
      iret = os.WEXITSTATUS(status)
   return iret

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class ASTER_SYSTEM:
   """Class to encapsultate "system" commands on local or remote machine.
   Available methods :
      'Copy', 'Shell', 'IsRemote', 'MkDir', 'Delete', 'Exists', 'IsDir',
      'Gzip', 'Gunzip', 'AddToEnv', 'GetHostName', 'PathOnly'
      'VerbStart, 'VerbEnd', 'VerbIgnore', 'SetLimit', 'Which'
   Supported protocols :
   - for copy :
      LOCAL, RCP, SCP, RSYNC, HTTP
   - for shell execution :
      LOCAL, RSH, SSH
   """
   # this value must be set during installation step.
   MaxCmdLen = 1024
   # line length -9
   _LineLen = 80-9
   # required parameters for initialisation
   RequiredParam = ['remote_copy_protocol', 'remote_shell_protocol']
#-------------------------------------------------------------------------------
   def __init__(self, run, **kargs):
      """run : ASTER_RUN object
      OR just a dictionnary to define 'RequiredParam' + 'verbose', 'debug'
      """
      self.verbose = run['verbose']
      self.debug = run['debug']
      if kargs.has_key('maxcmdlen'):
         self.MaxCmdLen = kargs['maxcmdlen']
      # fill param
      self.param = {}
      for key in self.RequiredParam:
         self.param[key] = run[key]
      # ----- reference to ASTER_RUN object which manages the execution
      self.run = run
      # informations
      self.info =  {
         # store local user (will be the default for remote machines)
         'local_user' : getpass.getuser(),
         'local_host' : os.uname()[1],
      }
      # check if protocols are supported
      if not self.param['remote_shell_protocol'] in ('LOCAL', 'RSH', 'SSH'):
         self._mess(_('unknown protocol : %s') % self.param['remote_shell_protocol'],
               _('<A>_ALARM'))
      if not self.param['remote_copy_protocol'] in ('LOCAL', 'RCP', 'SCP', 'RSYNC', 'HTTP'):
         self._mess(_('unknown protocol : %s') % self.param['remote_copy_protocol'],
               _('<A>_ALARM'))
      self._dbg('ASTER_SYSTEM param :', self.param)
      # add shortcuts - example: run.Copy instead of run.system.Copy
      if type(run) is InstanceType:
         for meth in ASTER_SYSTEM.__dict__.keys():
            if meth[0] == meth[0].upper() and not hasattr(run, meth) \
                  and hasattr(self, meth) and callable(getattr(self, meth)):
               setattr(run, meth, getattr(self, meth))

#-------------------------------------------------------------------------------
   def _mess(self, msg, cod='', store=False):
      """Just print a message
      """
      if hasattr(self.run, 'Mess'):
         self.run.Mess(msg, cod, store)
      else:
         print '%-18s %s' % (cod, msg)

#-------------------------------------------------------------------------------
   def _dbg(self, *args, **kargs):
      """Print debug informations
      """
      if hasattr(self.run, 'DBG'):
         self.run.DBG(*args, **kargs)
      elif self.debug:
         print '<DBG> %s // %s' % (str(args), str(kargs))

#-------------------------------------------------------------------------------
   def VerbStart(self, cmd, verbose=None):
      """Start message in verbose mode
      """
      Lm = self._LineLen
      if verbose == None:
         verbose = self.verbose
      if verbose:
         pcmd = cmd
         if len(cmd) > Lm-2 or cmd.count(os.linesep) > 0:
            pcmd = pcmd+os.linesep+' '*Lm
         print ('%-'+str(Lm)+'s') % (pcmd, ),
         sys.stdout.flush()

#-------------------------------------------------------------------------------
   def VerbEnd(self, iret, output='', verbose=None):
      """Ends message in verbose mode
      """
      if verbose == None:
         verbose = self.verbose
      if verbose:
         if iret == 0:
            print label['OK']
         else:
            print label['FAILED']
            print _('Exit code : %d') % iret
         if (iret != 0 or self.debug) and output:
            print output

#-------------------------------------------------------------------------------
   def VerbIgnore(self, verbose=None):
      """Ends message in verbose mode
      """
      if verbose == None:
         verbose = self.verbose
      if verbose:
         print label['SKIP']

#-------------------------------------------------------------------------------
   def local_shell(self, cmd, bg=False, verbose=False, follow_output=False,
         alt_comment=None, interact=False, capturestderr=True):
      """Execute a command shell
         cmd      : command
         bg       : put command in background if True
         verbose  : print status messages during execution if True
         follow_output : follow interactively output of command
         alt_comment : print this "alternative comment" instead of "cmd"
         interact : allow the user to interact with the process
            (don't close stdin). bg=True implies interact=False.
      Return :
         iret     : exit code if bg = False,
                    process id if bg = True
         output   : output lines (as string)
      """
      if not alt_comment:
         alt_comment = cmd
      if bg:
         interact = False
      if len(cmd)>self.MaxCmdLen:
         self._mess((_('length of command shell greater '\
               'than %d characters.') % self.MaxCmdLen), _('<A>_ALARM'))
      self._dbg('cmd :', cmd, 'background : %s' % bg)
      # exec
      self.VerbStart(alt_comment, verbose=verbose)
      if follow_output and verbose:
         print os.linesep+_('Command output :')
#MC A REVOIR : il faudrait connecter tochild à stdin et fromchild à stdout
# faut-il le faire dans un thread séparé....? plus tard ;-)
      # run interactive command
      if interact:
         iret = os.system(cmd)
         return _exitcode(iret), ''
      # use popen to manipulate stdout/stderr
      output = []
      if capturestderr:
         p = popen2.Popen4(cmd)
      else:
         p = popen2.Popen3(cmd, capturestderr=False)
      p.tochild.close()
      if not bg:
         if not follow_output:
            output = p.fromchild.readlines()
         else:
            while p.poll() == -1:
               stmp = p.fromchild.readline()
               if stmp != '':
                  # `os.linesep` already here...
                  print stmp,
                  output.append(stmp)
               sys.stdout.flush()
            # to be sure to empty the buffer
            end = p.fromchild.readlines()
            print ''.join(end)
            output.extend(end)
         try:
            iret = _exitcode(p.wait())
         except OSError, msg:
            self._dbg("OSError = %s" % str(msg))
            iret = 4
      else:
         iret = 0
      p.fromchild.close()
      output = ''.join(output)

      # repeat header message
      if follow_output:
         self.VerbStart(alt_comment, verbose=verbose)
      mat = re.search('EXIT_CODE=([0-9]+)', output)
      if mat != None:
         try:
            iret = int(mat.group(1))
         except ValueError:
            pass
      self._dbg("iret = %d\noutput = %s" % (iret, output))
      self.VerbEnd(iret, output, verbose=verbose)
      if bg:
         iret = p.pid
         if verbose:
            print 'Process ID : ', iret
      return iret, output

#-------------------------------------------------------------------------------
   def filename2dict(self, s):
      """Convert file name 's' given in format [[user[:passwd]@]machine:]name
      into a dictionnary with keys = user, passwd, mach, name
      """
      d = {
         'user'   : '',
         'passwd' : '',
         'mach'   : '',
         'name'   : ''
      }
      if not type(s) is StringType:
         self._mess(_('unexpected type'), _('<F>_PROGRAM_ERROR'))
      mat = re.search('(.*):(.*)@(.*):(.*)', s)
      n = 4
      if mat == None:
         mat = re.search('(.*)@(.*):(.*)', s)
         n = 3
         if mat == None:
            mat = re.search('(.*):(.*)', s)
            n = 2
            if mat == None:
               d['name'] = s
               n = 1
      if n >= 2:
         g = list(mat.groups())
         d['name'] = g.pop()
         d['mach'] = g.pop()
      if n >= 3:
         d['user'] = g.pop()
      if n >= 4:
         d['passwd'] = d['user']
         d['user'] = g.pop()
      if d['mach'] != '' and d['user'] == '':
         d['user'] = self.info['local_user']
      return d

#-------------------------------------------------------------------------------
   def PathOnly(self, filename):
      """Return only the path of file given as [[user[:passwd]@]machine:]name.
      Very useful when `filename` contains 'user@mach:' string but refers to a
      local filename.
      """
      return self.filename2dict(filename)['name']

#-------------------------------------------------------------------------------
   def Copy(self, dest, *src, **opts):
      """Copy files/directories (from 'src' list) to 'dest'
      Remote names format :
         [[user[:passwd]@]machine:]name
      where name is an absolute or a relative path.
      optionnal arguments : verbose, follow_output passed to local_shell,
         niverr : default is set to <F>_COPY_ERROR, but may be '<A>' or less !
         protocol : to use a different protocol just for this copy.
      Note : if src[i] is a directory and dest doesn't exist
             dest is created and the content of src[i] is copied into dest.
             This is not a default behavior of rsync which would create
             `basename src[i]` in dest even if dest doesn't exist.
      """
      # cp command
      if sys.platform in ('linux2', 'x86_64', 'cygwin'):
         cpcmd = 'cp -L -r'
      else:
         cpcmd = 'cp -r'
      
      iret = 0
      kargs = {}
      # take values from opts if exist
      kargs['verbose'] = opts.get('verbose', self.verbose)
      kargs['follow_output'] = opts.get('follow_output', False)
      if opts.has_key('niverr'):
         niverr = opts['niverr']
      else:
         niverr = _('<F>_COPY_ERROR')
      ddest = self.filename2dict(dest)
      if self.IsRemote(dest) \
            or (ddest['user'] != '' and ddest['user'] != self.info['local_user']):
         fdest = ddest['user']+'@'+ddest['mach']+':'+ddest['name']
      else:
         fdest = ddest['name']
      self._dbg('source list : %s' % (src,), 'destination : %s' % fdest)

      if len(src) < 1:
         self._mess(_('no source file to copy'), _('<A>_ALARM'))

      for f in src:
         # here because we can change 'proto' if necessary
         proto = opts.get('protocol', self.param['remote_copy_protocol'])
         jret = 0
         df = self.filename2dict(f)
         if self.IsRemote(f):
            fsrc = df['user']+'@'+df['mach']+':'+df['name']
         else:
            fsrc = df['name']
         cmd = ''
         tail = '.../'+'/'.join(f.split('/')[-2:])
         kargs['alt_comment'] = 'copying %s' % tail
         if df['mach'] == '' and ddest['mach'] == '':
            cmd = cpcmd+' '+fsrc+' '+fdest
         else:
            if proto == 'RSYNC' and df['mach'] != '' and ddest['mach'] != '':
               proto = 'RCP'
               self._mess(_("copying a remote file to another remote server " \
                     "isn't allowed through RSYNC, trying with RCP."))
            if proto == 'RCP':
               cmd = 'rcp -r '+fsrc+' '+fdest
            elif proto == 'SCP':
               cmd = 'scp -rBCq '+fsrc+' '+fdest
            elif proto == 'RSYNC':
               if self.IsDir(f) and not self.Exists(dest):
                  self.MkDir(dest)
                  cmd = 'rsync -rz '+os.path.join(fsrc, '*')+' '+fdest
               else:
                  cmd = 'rsync -rz '+fsrc+' '+fdest
            elif proto == 'HTTP':
               str_user = ''
               if not df['user'] in ('', 'anonymous'):
                  str_user = df['user']+'@'
               # dest must be local
               if ddest['mach'] == '':
                  cmd = 'wget http://'+str_user+df['mach']+df['name']+' -O '+fdest
               else:
                  cmd = ''
                  self._mess(_('remote destination not allowed through %s' \
                        ' : %s') % (proto, fdest), niverr)
         if cmd != '':
            jret, out = self.local_shell(cmd, **kargs)
            if jret != 0 and niverr != 'SILENT':
               self._mess(_('error during copying %s to %s') % (f, fdest) \
                  + os.linesep + _('message : %s') % out, niverr)
         else:
            self._mess(_('unexpected error or unknown protocol : %s') \
                   % proto, niverr)
         iret = max(jret, iret)
      return iret

#-------------------------------------------------------------------------------
   def Shell(self, cmd, mach='', user='', **opts):
      """Execute a command shell on local or remote machine.
      Options : see local_shell
      """
      iret = 1
      # valeurs par défaut
      kargs = {
         'verbose'         : self.verbose,
         'bg'              : False,
         'interact'        : False,
         'follow_output'   : False,
         'alt_comment'     : cmd,
         'capturestderr'   : True,
      }
      # surcharge par opts
      kargs.update(opts)

      proto = self.param['remote_shell_protocol']
      #if cmd.find('"') != -1:
      #      self._mess(_("""an error may occurs because of '"' character ' \
      #            'in command"""), _('<A>_ALARM'))
      #cmd = "sh -c '"+cmd+"'"
      if mach == '':
         iret, out = self.local_shell(cmd, **kargs)
      else:
         if user == '':
            user = getpass.getuser()
         action = ''
         if proto == 'RSH':
            action = 'rsh -n -l '+user+' '+mach+' '+cmd
         elif proto == 'SSH':
            action = 'ssh -n -l '+user+' '+mach+' '+cmd
         if action != '':
            iret, out = self.local_shell(action, **kargs)
         else:
            self._mess(_('unknown protocol : %s') % proto, _('<A>_ALARM'))
      return iret, out

#-------------------------------------------------------------------------------
   def Gzip(self, src, **opts):
      """Compress file or content of directory 'src'.
      optionnal arguments : verbose, niverr, cmd (to override gzip command).
      Only local files/directories supported !
      """
      return self._gzip_gunzip('gzip', src, **opts)

#-------------------------------------------------------------------------------
   def Gunzip(self, src, **opts):
      """Decompress file or content of directory 'src'.
      optionnal arguments : verbose, niverr, cmd (to override gzip -d command).
      Only local files/directories supported !
      """
      return self._gzip_gunzip('gunzip', src, **opts)

#-------------------------------------------------------------------------------
   def _gzip_gunzip(self, mode, src, **opts):
      """Called by Gzip (mode=gzip) and Gunzip (mode=gunzip) methods.
      Return status and filename of src after de/compression.
      Only local files/directories supported !
      """
      para = {
         'gzip' : {
            'cmd'     : 'gzip ',
            'niverr'  : _('<F>_COMPRES_ERROR'),
            'msg0'    : _('no file/directory to compress'),
            'comment' : _('compressing %s'),
            'msgerr'  : _('error during compressing %s'),
         },
         'gunzip' : {
            # sometimes gunzip doesn't exist, gzip seems safer
            'cmd'     : 'gzip -d ',
            'niverr'  : _('<F>_DECOMPRESSION'),
            'msg0'    : _('no file/directory to decompress'),
            'comment' : _('decompressing %s'),
            'msgerr'  : _('error during decompressing %s'),
         },
      }
      if not mode in para.keys():
         self._mess(_('unknown mode : %s') % mode, _('<F>_PROGRAM_ERROR'))
      iret = 0
      if opts.has_key('cmd'):
         cmd = opts['cmd']
      else:
         cmd = para[mode]['cmd']
      if opts.has_key('verbose'):
         verbose = opts['verbose']
      else:
         verbose = self.verbose
      if opts.has_key('niverr'):
         niverr = opts['niverr']
      else:
         niverr = para[mode]['niverr']

      if len(src) < 1:
         self._mess(para[mode]['msg0'], _('<A>_ALARM'))
         return iret

      if not os.path.isdir(src):
         if mode == 'gzip':
            name = src+'.gz'
         else:
            name = re.sub('\.gz$', '', src)
         lf = [src]
      else:
         name = src
         lf = glob.glob(os.path.join(src, '*'))
      for f in lf:
         jret = 0
         comment = para[mode]['comment'] % f
         if os.path.isdir(f):
            self.VerbStart(comment, verbose=verbose)
            self.VerbIgnore(verbose=verbose)
         else:
            jret, out = self.local_shell(cmd+f, verbose=verbose, alt_comment=comment)
         if jret != 0:
            self._mess(para[mode]['msgerr'] % f, niverr)
         iret = max(iret, jret)
      return iret, name

#-------------------------------------------------------------------------------
   def IsRemote(self, path):
      """Return True if 'path' seems to be on a remote host.
      NB : we suppose that host and host.domain are identical.
      """
      dico  = self.filename2dict(path)
      mach = dico['mach'].split('.')[0]
      diff_host = ( mach != '' and mach != self.info['local_host'])
      return diff_host

#-------------------------------------------------------------------------------
   def Exists(self, path):
      """Return True if 'path' exists (file or directory).
      """
      iret = 0
      exists = True
      dico = self.filename2dict(path)
      if self.IsRemote(path):
         cmd = "\"sh -c 'if test -f "+dico['name']+' ; then echo FILE_EXISTS; ' \
                         "elif test -d "+dico['name']+' ; then echo DIR_EXISTS ; ' \
                                                         "else echo FALSE ; fi'\""
         iret, out = self.Shell(cmd, dico['mach'], dico['user'], verbose=self.verbose)
         if out.find('FALSE') > -1:
            exists = False
      else:
         exists = os.path.exists(dico['name'])
      return exists

#-------------------------------------------------------------------------------
   def IsDir(self, path):
      """Return True if 'path' is a directory.
      """
      iret = 0
      isdir = False
      dico = self.filename2dict(path)
      if self.IsRemote(path):
         cmd = "\"sh -c 'if test -d "+dico['name']+' ; then echo IS_DIRECTORY; ' \
                                             "else echo FALSE ; fi'\""
         iret, out = self.Shell(cmd, dico['mach'], dico['user'], verbose=self.verbose)
         if out.find('IS_DIRECTORY') > -1:
            isdir = True
      else:
         isdir = os.path.isdir(dico['name'])
      return isdir

#-------------------------------------------------------------------------------
   def IsText(self, path):
      """Return True if `path` is a text file.
      Warning : return False if `path` is remote
      """
      iret = 0
      istext = False
      dico = self.filename2dict(path)
      if not self.IsRemote(path):
         cmd = "file %s" % path
         iret, out = self.Shell(cmd, dico['mach'], dico['user'], verbose=self.verbose)
         if out.lower().find('text') > -1:
            istext = True
      return istext

#-------------------------------------------------------------------------------
   def MkDir(self, rep, niverr=None, verbose=None):
      """Create the directory 'rep' (mkdir -p ...)
      """
      if niverr == None:
         niverr = _('<F>_MKDIR_ERROR')
      if verbose == None:
         verbose = self.verbose
      iret = 0
      dico = self.filename2dict(rep)
      if self.IsRemote(rep):
         cmd = 'mkdir -p '+dico['name']
         iret, out = self.Shell(cmd, dico['mach'], dico['user'], verbose=verbose)
      else:
         self.VerbStart(_('creating directory %s') % dico['name'], verbose)
         # ----- it's not tested in remote
         if self.IsDir(rep):
            iret = 0
            self.VerbIgnore(verbose)
         else:
            s = ''
            try:
               os.makedirs(dico['name'])
            except OSError, s:
               iret = 4
            self.VerbEnd(iret, s, verbose)
      if iret != 0:
         self._mess(_('can not create directory %s') % rep, niverr)
      return iret

#-------------------------------------------------------------------------------
   def Delete(self, rep, remove_dirs=True, verbose=None):
      """Delete a file or a directory tree (rm -rf ...).
      Set 'remove_dirs' to False to be sure to delete 'rep' only if it's a file.
      """
      if verbose == None:
         verbose = self.verbose
      iret = 0
      dico = self.filename2dict(rep)
      # preventing to delete first level directories (as /, /home, /usr...)
      if dico['name'][0] == '/' and len(dico['name'][:-1].split(os.sep)) <= 2:
         self._mess(_('deleting this directory seems too dangerous. ' \
               '%s has not been removed') % dico['name'], _('<A>_ALARM'))
         return 1
      
      if remove_dirs:
         cmd = 'rm -rf '+dico['name']
      else:
         cmd = 'rm -f '+dico['name']
      
      comment = _('deleting %s') % rep
      if self.IsRemote(rep):
         iret, out = self.Shell(cmd, dico['mach'], dico['user'],
                                alt_comment=comment, verbose=verbose)
      else:
         iret, out = self.Shell(cmd, alt_comment=comment, verbose=verbose)
      return

#-------------------------------------------------------------------------------
   def AddToEnv(self, profile):
      """Read 'profile' file (with sh/bash/ksh syntax) and add updated
      variables to os.environ.
      """
      def env2dict(s):
         """Convert output to a dictionnary."""
         l = s.split(os.linesep)
         d = {}
         for line in l:
            mat = re.search('^([-a-zA-Z_0-9@\+]+)=(.*$)', line)
            if mat != None:
               d[mat.group(1)] = mat.group(2)
         return d
      if not os.path.isfile(profile):
         self._mess(_('file not found : %s') % profile, '<A>_FILE_NOT_FOUND')
         return
      # read initial environment
      iret, out = self.Shell('sh -c env')
      env_init = env2dict(out)
      if iret != 0:
         self._mess(_('error getting environment'), '<E>_ABNORMAL_ABORT')
         return
      # read profile and dump modified environment
      iret, out = self.Shell('sh -c ". %s ; env"' % profile)
      env_prof = env2dict(out)
      if iret != 0:
         self._mess(_('error reading profile : %s') % profile,
               '<E>_ABNORMAL_ABORT')
         return
      # "diff"
      for k, v in env_prof.items():
         if env_init.get(k, None) != v:
            if self.debug:
               print 'AddToEnv adding : %s=%s' % (k, v)
            os.environ[k] = v

#-------------------------------------------------------------------------------
   def GetHostName(self, host=None):
      """Return hostname of the machine 'host' or current machine if None.
      """
      from socket import gethostname, gethostbyaddr
      if host == None:
         host = gethostname()
      fqn, alias, ip = gethostbyaddr(host)
      if fqn.find('localhost')>-1:
         alias = [a for a in alias if a.find('localhost')<0]
         if len(alias)>0:
            fqn = alias[0]
         for a in alias:
            if a.find('.')>-1:
               fqn = a
               break
      return fqn

#-------------------------------------------------------------------------------
   def SendMail(self, dest, author=None, subject='no subject', text=''):
      """Send a message by mail.
      Use ", " in `dest` to separate multiple recipients.
      """
      sign = 'sent by as_run/SendMail from %s' % self.GetHostName()
      sign = os.linesep*2 + '-'*len(sign) + os.linesep \
           + sign \
           + os.linesep   + '-'*len(sign) + os.linesep
      try:
         import smtplib
         from email.MIMEText import MIMEText
      except ImportError:
         self._mess(_('Can not send mail from this machine'),
            _('<A>_NO_MAILER'))
         return
      dest = [s.strip() for s in dest.split(',')]
      msg = MIMEText(text+sign)
      msg['Subject'] = subject
      if author == None:
         author = '%s@%s' % (getpass.getuser(), self.GetHostName())
      msg['From'] = author
      msg['To'] = ', '.join(dest)
      s = smtplib.SMTP()
      s.connect()
      s.sendmail(msg['From'], dest, msg.as_string())
      s.close()

#-------------------------------------------------------------------------------
   def SetLimit(self, what, *l_limit):
      """Set a system limit.
      `what` is one of CORE, CPU... (see help of resource module).
      If provided `l_limit` contains (soft limit, hard limit).
      """
      nomp = 'RLIMIT_%s' % what.upper()
      param = getattr(resource, nomp)
      if param != None:
         if len(l_limit) == 1:
            l_limit = (l_limit[0], l_limit[0])
         elif len(l_limit) != 2:
            l_limit = (-1, -1)
         l_limit = list(l_limit)
         for i, lim in enumerate(l_limit):
            if type(lim) not in (IntType, LongType):
               l_limit[i] = -1
         try:
            self._dbg([what, nomp, l_limit])
            resource.setrlimit(param, l_limit)
         except Exception, msg:
            self._mess(_('unable to set %s limit to %s') % (nomp, l_limit))

#-------------------------------------------------------------------------------
   def Which(self, cmd):
      """Same as `which cmd`. Returns the path found or None.
      """
      ldirs = os.environ.get('PATH').split(':')
      for d in ldirs:
         path = os.path.join(d, cmd)
         if os.path.isfile(path) and os.access(path, os.X_OK):
            return path
      return None

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class ASTER_SYSTEM_MINIMAL(ASTER_SYSTEM):
   """Fake ASTER_RUN to use easily ASTER_SYSTEM outside of as_run.
   """
#-------------------------------------------------------------------------------
   def __init__(self, **kargs):
      """run : ASTER_RUN object
      OR just a dictionnary to define 'RequiredParam' + 'verbose', 'debug'
      """
      opts = { 
         'debug'   : False,
         'verbose' : False,
         'remote_copy_protocol' : 'SCP',
         'remote_shell_protocol' : 'SSH',
      }
      opts.update(kargs)
      ASTER_SYSTEM.__init__(self, run=opts)
