# recipes.py - Here are some general purpose convenience functions
# 
# Author: Stefan Fuertinger [stefan.fuertinger@esi-frankfurt.de]
# Created: June 25 2013
# Last modified: <2017-09-15 16:07:08>
from __future__ import division
import sys
import re
import fnmatch
import os
from numpy.linalg import norm
import numpy as np
from texttable import Texttable
from datetime import datetime, timedelta
from scipy import ndimage
import matplotlib.pyplot as plt
##########################################################################################
[docs]def query_yes_no(question, default=None):
    """
    Ask a yes/no question via `raw_input()` and return the answer.
    Parameters
    ----------
    question : str
        The question to be printed in the prompt
    default : str
        The presumed answer that is used in case the <Return> key is pressed
        (must be either "yes" or "no"). 
        If `default` is `None` then a definitive answer is required 
        (pressing <Return> will re-print `question` in the prompt)
    Returns
    -------
    answer : bool
        Either `True` if the input was "yes"/"y" or `False` otherwise. 
    Notes
    -----
    This code is a slightly modified version of recipe no. 577058 from ActiveState 
    written by Trent Mick. 
    See also
    --------
    ActiveState : ActiveState Code Recipe #577058 currently available 
                  `here <http://code.activestate.com/recipes/577058/>`_
    """
    # Check mandatory input
    if not isinstance(question,(str,unicode)):
        raise TypeError('Input question has to be a string!')
    # Parse optional `default` answer
    valid = {"yes":True,   "y":True,  "ye":True,
             "no":False,     "n":False}
    if default == None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("Invalid default answer: '%s'" % default)
    # Do the actual work
    while True:
        sys.stdout.write(question + prompt)
        choice = raw_input().lower()
        if default is not None and choice == '':
            return valid[default]
        elif choice in valid:
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' "\
                             
"(or 'y' or 'n').\n") 
##########################################################################################
[docs]def natural_sort(lst): 
    """
    Sort a list/NumPy 1darray in a "natural" way
    Parameters
    ----------
    lst : list or NumPy 1darray
        Python list or 1darray of strings
    Returns
    -------
    lst_sort : list or NumPy 1darray
        Lexicographically sorted version of the input list `lst`
    Notes
    -----
    This function was originally intended to perform a natural sorting of a file-listing
    (see Coding Horror's 
    `note <http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html>`_ 
    on this topic for more details). Briefly, an input list `lst` of strings 
    containing digits is sorted such that the actual numerical value of the 
    digits is respected (see Examples for more details). 
    The code below is based on a Stackoverflow submission by Mark Byers, currently available 
    `here <http://stackoverflow.com/questions/4836710/does-python-have-a-built-in-function-for-string-natural-sort>`_. 
    Examples
    --------
    Calling `glob` in a directory containing files named `Elm` and `elm` plus 
    two-digit suffixes will result in a file listing sorted as follows:
    >>> lst = ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
    Using `natural_sort` to order `lst` yields
    >>> natural_sort(lst)
    ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
    See also
    --------
    None
    """
    # Check our single mandatory input argument
    if not isinstance(lst,(list,np.ndarray)):
        raise TypeError('Input has to be a Python list or NumPy 1darray, not '+type(list).__name__+'!')
    # Convert all list entries to strings to avoid any trouble below
    try:
        lst = np.array(lst,dtype=str).flatten()
    except:
        raise ValueError("Input must be a list/NumPy 1darray of strings!")
    # Do the actual sorting
    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(lst, key = alphanum_key) 
##########################################################################################
[docs]def get_numlines(fname):
    """
    Get number of lines of an text file
    Parameters
    ----------
    fname : str
        File to be read
    Returns
    -------
    lineno : int
        Number of lines in the file
    Notes
    -----
    This routine is based on this 
    `Stackoverflow submission <http://stackoverflow.com/questions/845058/how-to-get-line-count-cheaply-in-python>`_
    See also
    --------
    None
    """
    # Check if input makes sense
    if not isinstance(fname,(str,unicode)):
        raise TypeError('Filename has to be a string!')
    fname = str(fname)
    if fname.find("~") == 0:
        fname = os.path.expanduser('~') + fname[1:]
    slash = fname.rfind(os.sep)
    if slash >= 0 and not os.path.isdir(fname[:fname.rfind(os.sep)]):
        raise ValueError('Invalid path to file: '+fname+'!')
    # Cycle through lines of the file and do exactly nothing
    with open(fname) as f:
        for lineno, l in enumerate(f):
            pass
    return lineno + 1 
##########################################################################################
[docs]def myglob(flpath,spattern):
    """
    Return a glob-like list of paths matching a regular expression 
    Parameters
    ----------
    flpath : str
        Path to search (to search current directory use `flpath=''` or `flpath='.'`)
    spattern : str
        Pattern to search for in `flpath`
    Returns
    -------
    flist : list
        A Python list of all files found in `flpath` that match the input pattern `spattern`
    Examples
    --------
    List all png/PNG files in the folder `MyHolidayFun` found under `Documents`
    >>> myglob('Documents/MyHolidayFun','*.[Pp][Nn][Gg]')
    ['Documents/MyHolidayFun/img1.PNG','Documents/MyHolidayFun/img1.png']
        
    See also
    --------
    glob : Unix-style path-name and pattern expansion in Python
    """
    # Make sure provided path is a string and makes sense
    if not isinstance(flpath,(str,unicode)):
        raise TypeError('Filepath has to be a string!')
    flpath = str(flpath)
    if flpath.find("~") == 0:
        flpath = os.path.expanduser('~') + flpath[1:]
    slash = flpath.rfind(os.sep)
    if slash >= 0 and not os.path.isdir(flpath[:flpath.rfind(os.sep)]):
        raise ValueError('Invalid path: '+flpath+'!')
    if not isinstance(spattern,(str,unicode)):
        raise TypeError('Pattern has to be a string!')
    # If user wants to search current directory, make sure that works as expected
    if (flpath == '') or (flpath.count(' ') == len(flpath)):
        flpath = '.'
    # Append trailing slash to filepath
    else:
        if flpath[-1] != os.sep: flpath = flpath + os.sep
    # Return glob-like list
    return [os.path.join(flpath, fnm) for fnm in fnmatch.filter(os.listdir(flpath),spattern)] 
##########################################################################################
[docs]def moveit(fname):
    """
    Check if a file/directory exists, if yes, rename it
    Parameters
    ----------
    fname : str
        A string specifying (the path to) the file/directory to be renamed (if existing)
    Returns
    -------
    Nothing : None
    Notes
    -----
    None
    See also
    --------
    None
    """
    # Check if input makes sense
    if not isinstance(fname,(str,unicode)):
        raise TypeError("File-/Directory-name has to be a string!")
    fname = str(fname)
    if fname.find("~") == 0:
        fname = os.path.expanduser('~') + fname[1:]
    # If file already exists, rename it
    if os.path.isfile(fname):
        now     = datetime.now()
        dot     = fname.rfind('.')
        idx     = len(fname)
        if dot > 0: idx = dot
        newname = fname[:idx] + "_bak_"+\
                  
str(now.year)+"_"+\
                  
str(now.month)+"_"+\
                  
str(now.day)+"_"+\
                  
str(now.hour)+"_"+\
                  
str(now.minute)+"_"+\
                  
str(now.second)+\
                  
fname[idx::]
        print "WARNING: File "+fname+" already exists, renaming it to: "+newname+"!"
        os.rename(fname,newname)
    # If directory already exists, rename it
    elif os.path.isdir(fname):
        now     = datetime.now()
        slash   = fname.rfind(os.sep)
        if slash == (len(fname) - 1): fname = fname[:slash]
        newname = fname + "_bak_"+\
                  
str(now.year)+"_"+\
                  
str(now.month)+"_"+\
                  
str(now.day)+"_"+\
                  
str(now.hour)+"_"+\
                  
str(now.minute)+"_"+\
                  
str(now.second)
        print "WARNING: Directory "+fname+" already exists, renaming it to: "+newname+"!"
        shutil.move(fname,newname) 
    
##########################################################################################
[docs]def regexfind(arr,expr):
    """
    Find regular expression in a NumPy array
    Parameters
    ----------
    arr : NumPy 1darray
        Array of strings to search 
    expr : str
        Regular expression to search for in the components of `arr`
    Returns
    -------
    ind : NumPy 1darray
        Index array of elements in `arr` that contain expression `expr`. If `expr` was not found
        anywhere in `arr` an empty array is returned
    Examples
    --------
    Suppose the array `arr` is given by
    >>> arr
    array(['L_a', 'L_b', 'R_a', 'R_b'], 
      dtype='|S3')
    If we want to find all elements of `arr` starting with `l_` or `L_` we could use
    >>> regexfind(arr,"[Ll]_*")
    array([0, 1])
    See also
    --------
    None
    """
    # Sanity checks
    try:
        arr = np.array(arr)
    except:
        raise TypeError("Input must be a NumPy array/Python list, not "+type(arr).__name__+"!")
    sha = arr.squeeze().shape
    if len(sha) != 1:
        raise ValueError("Input must be a NumPy 1darray or Python list!")
    for el in arr:
        if not isinstance(el,(str,unicode)):
            raise ValueError("Every element in the input array has to be a string!")
    if not isinstance(expr,(str,unicode)):
        raise TypeError("Input expression has to be a string, not "+type(expr).__name__+"!")
    # Now do something: start by compiling the input expression
    regex = re.compile(expr)
    # Create a generalized function to find matches
    match = np.vectorize(lambda x:bool(regex.match(x)))(arr)
    # Get matching indices and return
    return np.where(match == True)[0]