# Copyright (c) 2009 ActiveState Software Inc.
# See http://www.activestate.com/activepython/license/ for licensing
# information.

"""
    Console functions
    ~~~~~~~~~~~~~~~~~
    
    This module implements helper functions than can be used in printing
    stuff to the console, or performing shell commands.
"""

import os
import sys
import shutil
import logging
from datetime import datetime
from contextlib import contextmanager
try:
    from cStringIO import StringIO
except:
    from StringIO import StringIO


_SHOUTCOLOR = 'BLUE'
LOG = logging.getLogger(__name__)


#
# typical shell operations
# 

def mkdirs(directory):
    if os.path.isdir(directory):
        return
    LOG.debug('> console:mkdirs %s', directory)
    os.makedirs(directory)
    
def mv(srcfile, destfile):
    LOG.debug("> console:mv %s %s", srcfile, destfile)
    shutil.move(srcfile, destfile)

def mv_path(srcfile, destfile):
    """A variant of ``mv`` that also creates non-existing target directories."""
    mkdirs(os.path.dirname(destfile))
    mv(srcfile, destfile)
    
def cp(srcfile, destfile):
    LOG.debug("> console:cp %s %s", srcfile, destfile)
    shutil.copyfile(srcfile, destfile)
    return destfile

def cp_path(srcfile, destfile):
    """A variant of ``cp`` that also creates non-existing target directories."""
    mkdirs(os.path.dirname(destfile))
    cp(srcfile, destfile)

def cp_tree(srcdir, dstdir, ignore=None, ignore_permissions=False):
    """Copy the entire ``srcdir`` recursively

    ``ignore`` must be an instance of shutil.ignore_patterns that represent a
    pattern of files/directories to be ignored.
    """
    LOG.debug("> console:cp -r %s %s", srcdir, dstdir)
    _copytree(srcdir, dstdir,
                    ignore=ignore,
                    ignore_permissions=ignore_permissions)
def _copytree(src, dst, symlinks=False, ignore=None, ignore_permissions=False):
    """Forked shutil.copytree to support ignoring permissions"""
    names = os.listdir(src)
    if ignore is not None:
        ignored_names = ignore(src, names)
    else:
        ignored_names = set()

    os.makedirs(dst)
    errors = []
    for name in names:
        if name in ignored_names:
            continue
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        try:
            if symlinks and os.path.islink(srcname):
                linkto = os.readlink(srcname)
                os.symlink(linkto, dstname)
            elif os.path.isdir(srcname):
                _copytree(srcname, dstname, symlinks, ignore, ignore_permissions)
            else:
                shutil.copy(srcname, dstname)
            # XXX What about devices, sockets etc.?
        except (IOError, os.error), why:
            raise
            errors.append((srcname, dstname, str(why)))
        # catch the Error from the recursive copytree so that we can
        # continue with other files
        except shutil.Error, err:
            errors.extend(err.args[0])
    if not ignore_permissions:
        try:
            shutil.copystat(src, dst)
        except WindowsError:
            # can't copy file access times on Windows
            pass
        except OSError, why:
            errors.extend((src, dst, str(why)))
    if errors:
        raise shutil.Error, errors    
    
    
    
def mv_children(srcdir, destdir):
    "Move contents of `srcdir` to `destdir`"
    for child in os.listdir(srcdir):
        srcfile = os.path.join(srcdir, child)
        destfile = os.path.join(destdir, child)
        mv(srcfile, destfile)
    
def rm(file):
    """Remove everything under `file` including `file` itself"""
    if not os.path.exists(file):
        return
    LOG.debug("> console:rm %s", file)
    if os.path.isdir(file):
        shutil.rmtree(file)
    else:
        os.remove(file)

@contextmanager
def cd(directory_path):
    """cd to the directory storing the old pwd"""
    assert isinstance(directory_path, (basestring)), \
        'Not a basestring: %s' % directory_path
    assert os.path.isdir(directory_path)
    
    cwd = os.getcwd()
    os.chdir(directory_path)
    try:
        yield
    finally:
        os.chdir(cwd)


# 
# common patterns of execution
#

@contextmanager
def long_process(log, desc, begin_info, final_info=None, finalfn=None):
    """Print information related to running a long process

    1) Dump information in `begin_info` before excution

    2) Dump information in `final_info` after execution or during unhandled
    exception

    3) Catch KeyboardInterrupt's appropriately. (exits with return code 1)

    4) Finally, print total time elapsed at the end of this long process

    Use this function, in the with context, when you going to process something
    expensive and doing the same in a console app (not GUI app)

    This pattern is helpful in pypm.builder and pypm.infinitude
    """
    def log_dict(d):
        for k, v in d.items():
            if callable(v):
                v = v()
            log.info('{0} = {1}'.format(k, v))
                     
    if desc: log.info(desc)
    log_dict(begin_info)
    log.info('***')
    log.info('')

    begin_time = datetime.now()
    try:
        yield
    except KeyboardInterrupt:
        log.info('** interrupted by user - Ctrl-c **')
        raise SystemExit, 1
    finally:
        if finalfn: finalfn()
        end_time = datetime.now()
        log.info('')
        log.info('***')
        if final_info: log_dict(final_info)
        log.info('Total time elapsed: %s', end_time - begin_time)


# how lame that WindowsError is not available on other platforms
try:
    WindowsError
except NameError:
    class WindowsError(OSError): pass
