"""
   Functions for doing shellish things in Python interactive mode.

   Meant to be imported at startup via environment:
     setenv PYTHONSTARTUP $HOME/easy.py
     or
     export PYTHONSTARTUP=$HOME/easy.py

   - Robin Friedrich
"""
import shutil
import glob
import os
from types import *

# Added code for command completion Janko Hauser
import readline
import rlcompleter

readline.parse_and_bind("tab: complete")
SYS = os.system
# ende of added code

home = os.path.expandvars('$HOME')
system = os.system

def _glob(filenames):
    """
    Expand a filename or sequence of filenames with possible
    shell metacharacters to a list of valid filenames.
    Ex:  _glob(('*.py*',)) == ['able.py','baker.py','charlie.py']
    """
    if type(filenames) is StringType:
        return glob.glob(filename)
    flist = []
    for filename in filenames:
        globbed = glob.glob(filename)
        if globbed:
            for file in globbed:
                flist.append(file)
        else:
            flist.append(filename)
    return flist

def _expandpath(d):
    """
    Convert a relative path to an absolute path.
    """
    return os.path.join(os.getcwd(), os.path.expandvars(d))

def ls(directory=os.curdir):
    """
    Same as 'ls -aF'
    Usage:  >>> ls(['dirname'])   (brackets mean [optional] argument)
    """
    os.system('ls -aF %s' % directory)

def ll(directory=os.curdir):
    """
    Same as 'ls -alF'
    Usage:  >>> ll(['dirname'])   (brackets mean [optional] argument)
    """
    os.system('ls -alF %s' % directory)

def lr(directory=os.curdir):
    """
    Recursive listing. same as 'ls -aRF'
    Usage:  >>> lr(['dirname'])   (brackets mean [optional] argument)
    """
    os.system('ls -aRF %s' % directory)

mkdir = os.mkdir

def rm(*args):
    """
    Delete a file or files.
    Usage:  >>> rm('file.c' [, 'file.h'])  (brackets mean [optional] argument)
    Alias: delete
    """
    filenames = _glob(args)
    for item in filenames:
        try:
            os.remove(item)
        except os.error, detail:
            print "%s: %s" % (detail[1], item)
delete = rm

def rmdir(directory):
    """
    Remove a directory.
    Usage:  >>> rmdir('dirname')
    If the directory isn't empty, can recursively delete all sub-files.
    """
    try:
        os.rmdir(directory)
    except os.error:
        #directory wasn't empty
        answer = raw_input(directory+" isn't empty. Delete anyway?[n] ")
        if answer and answer[0] in 'Yy':
            os.system('rm -rf %s' % directory)
            print directory + ' Deleted.'
        else:
            print directory + ' Unharmed.'

def mv(*args):
    """
    Move files within a filesystem.
    Usage:  >>> mv('file1', ['fileN',] 'fileordir')
    If two arguments - both must be files
    If more arguments - last argument must be a directory
    """
    filenames = _glob(args)
    nfilenames = len(filenames)
    if nfilenames < 2:
        print 'Need at least two arguments'
    elif nfilenames == 2:
        try:
            os.rename(filenames[0], filenames[1])
        except os.error, detail:
            print "%s: %s" % (detail[1], filenames[1])
    else:
        for filename in filenames[:-1]:
            try:
                dest = filenames[-1]+'/'+filename
                if not os.path.isdir(filenames[-1]):
                    print 'Last argument needs to be a directory'
                    return
                os.rename(filename, dest)
            except os.error, detail:
                print "%s: %s" % (detail[1], filename)

def cp(*args):
    """
    Copy files along with their mode bits.
    Usage:  >>> cp('file1', ['fileN',] 'fileordir')
    If two arguments - both must be files
    If more arguments - last argument must be a directory
    """
    filenames = _glob(args)
    nfilenames = len(filenames)
    if nfilenames < 2:
        print 'Need at least two arguments'
    elif nfilenames == 2:
        try:
            shutil.copy(filenames[0], filenames[1])
        except os.error, detail:
            print "%s: %s" % (detail[1], filenames[1])
    else:
        for filename in filenames[:-1]:
            try:
                dest = filenames[-1]+'/'+filename
                if not os.path.isdir(filenames[-1]):
                    print 'Last argument needs to be a directory'
                    return
                shutil.copy(filename, dest)
            except os.error, detail:
                print "%s: %s" % (detail[1], filename)

def cpr(src, dst):
    """
    Recursively copy a directory tree to a new location
    Usage:  >>> cpr('directory0', 'newdirectory')
    Symbolic links are copied as links not source files.
    """
    shutil.copytree(src, dst)

def ln(src, dst):
    """
    Create a symbolic link.
    Usage:  >>> ln('existingfile', 'newlink')
    """
    os.symlink(src, dst)

def lnh(src, dst):
    """
    Create a hard file system link.
    Usage:  >>> ln('existingfile', 'newlink')
    """
    os.link(src, dst)

def pwd():
    """
    Print current working directory path.
    Usage:  >>> pwd()
    """
    print os.getcwd()

cdlist = [home]
def cd(directory = -1):
    """
    Change directory. Environment variables are expanded.
    Usage:
    cd('rel/$work/dir') change to a directory relative to your own
    cd('/abs/path')     change to an absolute directory path
    cd()                list directories you've been in
    cd(int)             integer from cd() listing, jump to that directory
    """
    global cdlist
    if type(directory) is IntType:
        if directory in range(len(cdlist)):
            cd(cdlist[directory])
            return
        else:
            pprint(cdlist)
            return
    if not os.path.isdir(directory):
        return `directory`+' is not a directory'
    directory = _expandpath(directory)
    if directory not in cdlist:
        cdlist.append(directory)
    os.chdir(directory)

def env():
    """
    List environment variables.
    Usage:  >>> env()
    """
    #unfortunately environ is an instance not a dictionary
    envdict = {}
    for key, value in os.environ.items():
        envdict[key] = value
    pprint(envdict)

stack = []
def pushd(directory=home):
    """
    Place the current dir on stack and change directory.
    Usage:  >>> pushd(['dirname'])   (brackets mean [optional] argument)
                pushd()  goes home.
    """
    global stack
    stack.append(os.getcwd())
    cd(directory)

def popd():
    """
    Change to directory popped off the top of the stack.
    Usage:  >>> popd()
    """
    global stack
    try:
        cd(stack[-1])
        print stack[-1]
        del stack[-1]
    except IndexError:
        print 'Stack is empty'

def syspath():
    """
    Print the Python path.
    Usage:  >>> syspath()
    """
    import sys
    pprint(sys.path)

def which(object):
    """
    Print the source file from which a module or function was imported.
    Usage:  >>> which(mysteryObject)
    Alias:  whence
    """
    object_type = type(object)
    if object_type is ModuleType:
        print 'Module from', object.__file__
    elif object_type is FunctionType:
        print 'Function from', object.func_code.co_filename
    elif object_type is BuiltinFunctionType:
        print "argument is a built-in function."
    else:
        print "argument is not a module or function."
whence = which

def pprint(obj):
    """
    Pretty printer for lists and dictionaries.
    Usage:  >>> pprint(python_object)
    """
    objecttype = type(obj)
    if objecttype is ListType:
        ct = 0
        for item in obj:
            print "%3d : %s" % (ct, item)
            ct = ct + 1
    elif objecttype is DictType:
        keys = obj.keys()
        keys.sort()
        for key in keys:
            print "%-12s: %s" % (key, obj[key])
    else:
        print obj

def doc(*objects):
    """
    Print doc strings for object(s).
    Usage:  >>> doc(object, [obj2, objN])  (brackets mean [optional] argument)
    """
    for obj in objects:
        try:
            print '**', obj.__name__ , '**'
            print obj.__doc__
        except AttributeError:
            print `obj`, 'has no __doc__ attribute'
            print

def help():
    """
    Print the doc strings for the helper functions.
    Usage:  >>> help()
    """
    funcs = ( cd, cp, cpr, doc, env, help,
              ll, ln, lnh, lr, ls, mkdir, mv,
              popd, pprint, pushd, pwd, rm,
              rmdir, syspath, which )
    print '<<< HELPER FUNCTIONS >>>'
    apply(doc, funcs)
