"""

Miscellaneous functions written by Martin Preishuber

"""

from string import strip, lower, zfill, find, atoi, join, replace
from os import getcwd, chdir, mkdir, lstat, unlink, rmdir, utime, wait
from urlparse import urlparse
from time import time
from popen2 import popen3
from re import match

import signal
import os
import string
import copy

# Boolean constants
TRUE = 1
FALSE = 0

# Color constants
BLACK = "30"
RED = "31"
GREEN = "32"
YELLOW = "33"
BLUE = "34"
PURPLE = "35"
AQUA = "36"
WHITE = "37"


"""

String operations

"""

def split(s, sep = None, special = None):
    """ Extended split (doesn't split within special characters). """
    if (special == None):
        if (sep == None):
            return string.split(s)
        else:
            return string.split(s, sep)
    else:
        list = []
        elem = ""
        mode = 0
        for i in range(len(s)):
            if (s[i] == sep):
                if (mode == 0) and (elem != ""):
                    list.append(elem)
                    elem = ""
                elif (mode == 1):
                    elem = "%s%s" % (elem, s[i])
            elif (s[i] == special):
                if (mode == 0):
                    mode = 1
                elif (mode == 1):
                    mode = 0
            else:
                elem = "%s%s" % (elem, s[i])
        list.append(elem)
        return list

def cfill(source, char, width):
    """ Fill a string with a character up to a given width. """
    while len(source) < width:
        source = "%s%s" % (source, char)
    return source

def pfill(source, char, width):
    """ Pad a string with a character up to a given width. """
    while len(source) < width:
        source = char + source
    return source

def lstrip(string):
    """ Strip blanks (NOT whitespaces) on the left side of a string. """
    while (string[0] == " ") and (len(string) > 0):
        string = string[1:]
    return string

def merge(string1, string2):
    """ Merge two strings byte-by-byte. """
    pos1 = 0
    pos2 = 0
    result = ""
    while (len(string1) >= (pos1 + 1)) and (len(string2) >= (pos2 + 1)):
        result = "%s%s%s" % (result, string1[pos1], string2[pos2])
        pos1 = pos1 + 1
        pos2 = pos2 + 1
    if (len(string1) >= (pos1 + 1)):
        result = "%s%s" % (result, string1[pos1:])
    if (len(string2) >= (pos2 + 1)):
        result = "%s%s" % (result, string2[pos2:])
    return result


"""

RE module functions

"""

def unfold(regexp):
    splitted = split(regexp, "\n")
    for i in range(len(splitted)):
        splitted[i] = strip(splitted[i])
    return join(splitted, "")


"""

Type conversion functions

"""

def bool2str(value):
    """ Converts a Boolean value to the corresponding String. """
    if value == FALSE:
        return "False"
    elif value == TRUE:
        return "True"

def str2bool(value):
    """ Converts a String value to the corresponding Boolean. """
    if (lower(value) == "true") or (lower(value) == "yes") or (value == "1"):
        return TRUE
    elif (lower(value) == "false") or (lower(value) == "no") or (value == "0"):
        return FALSE
    else:
        return None

def lng2str(number, prettyprint = TRUE):
    """ Returns a string representation of a number (with a pretty-print option). """
    if (prettyprint == TRUE):
        number = list(str(number))
        if number[-1] == "L":
            number = number[:-1]
        number.reverse()
        dots = divmod(len(number) - 1, 3)[0]
        for i in range(dots):
            number.insert(3 + (i * 3) + i, ".")
        number.reverse()
        return join(number, "")
    else:
        return str(number)

"""

List operations

"""

def striplist(list):
    """ Strip each value of a list. """
    for i in range(len(list)):
        list[i] = strip(list[i])
    return list

def cleanlist(list):
    """ Removes empty entries from a list. """
    newlist = []
    for i in range(len(list)):
        if (list[i] <> ""):
            newlist.append(list[i])
    return newlist

def listdiff(list1, list2):
    """ Get the difference of two lists. """
    difflist = []
    for i in range(len(list1)):
        if (not list1[i] in list2):
            difflist.append(list1[i])
    for i in range(len(list2)):
        if (not list2[i] in list1):
            difflist.append(list2[i])
    return difflist

def listmatch(list, expression):
    """ Returns the matching element number and match object. """
    number = -1
    element = None
    for i in range(len(list)):
        if (match(expression, list[i])):
            number = i
            element = list[i]
            break
    return (number, element)

"""

Time functions

"""

def nicetime(seconds):
    """ Converts time in seconds to a nicely formatted string. """
    minutes, seconds = divmod(seconds, 60)
    return "%s:%s" % (zfill(minutes, 2), zfill(seconds, 2))


"""

File and directory operations

"""

def ls(directory, recursive = FALSE, includepath = FALSE, onlydirectories = FALSE):
    """ Creates a directory listing, optional including full filenames and recursive. """
    longlist = []
    allfiles = os.listdir(directory)
    if (directory == "."): directory = ""
    if ((len(directory) > 0) and (directory[-1] != "/")): directory = "%s/" % directory
    for i in range(len(allfiles)):
        filename = "%s%s" % (directory, allfiles[i])
        longfilename = "%s/%s" % (os.path.abspath(directory), filename)
        if (os.path.isdir(filename)):
            if (includepath == TRUE):
                longlist.append(longfilename)
            else:
                longlist.append(filename)
            if (recursive == TRUE):
                subdirlist = ls("%s/" % filename, recursive, includepath, onlydirectories)
                if (len(subdirlist) > 0):
                    longlist = longlist + subdirlist
        else:
            if (onlydirectories == FALSE):
                if (includepath == TRUE):
                    longlist.append(longfilename)
                else:
                    longlist.append(filename)
    return longlist

def rm(file, maximum = -1, deleted = 0):
    """ Remove a file or directory (recursive) and return the number of directories, files, bytes & links deleted. """
    directories = files = bytes = links = 0L
    if (os.path.islink(file)):
        if (maximum == -1) or ((maximum != -1) and (deleted < maximum)):
            links = links + 1
            unlink(file)
    elif (os.path.isfile(file)):
        filesize = lstat(file)[6]
        if (maximum == -1) or ((maximum != -1) and ((deleted + filesize) < maximum)):
            bytes = bytes + filesize
            files = files + 1
            unlink(file)
    else:
        filelist = os.listdir(file)
        for i in range(len(filelist)):
            result = rm(file + "/" + filelist[i], maximum, bytes)
            directories = directories + result[0]
            files = files + result[1]
            bytes = bytes + result[2]
            links = links + result[3]
        filelist = listdir(file)
        if (maximum == -1) or ((len(filelist) == 0) and (maximum != -1) and (deleted < maximum)):
            rmdir(file)
            directories = directories + 1
    return [directories, files, bytes, links]

def mkdirtree(directory):
    """ Creates a whole direcotry tree. """
    if (directory[-1:] == "/"):
        directory = directory[:-1]
    if (not os.path.exists(directory)):
        pwd = getcwd()
        splitted = split(directory, "/")
        path = "/"
        for i in range(len(splitted)):
            path = "%s/%s" % (path, splitted[i])
            if not os.path.exists(path):
                mkdir(path)
        chdir(pwd)

def du(file):
    """ Calculate the disk usage of a given directory. """
    if (os.path.islink(file)) or (os.path.isfile(file)):
        size = lstat(file)[6]
    else:
        filelist = listdir(file)
        size = 0
        for i in range(len(filelist)):
            size = size + du(file + "/" + filelist[i])
    return size

def which(file):
    """ Check the path to find some specified program. """
    if file == "":
        return ""
    for item in split(os.environ["PATH"], os.pathsep):
        filename="%s%s%s" % (item, os.sep, file)
        if os.path.exists(filename):
            return filename
    return ""

def filecopy(source, target):
    """ Copy a source file to a target file. """
    sourcefile = open(source, "r")
    targetfile = open(target, "w")
    targetfile.write(sourcefile.read())
    targetfile.close()
    sourcefile.close()

def includefile(source, target):
    """ Include a file into a already open file. """
    sourcefile = open(source, "r")
    line = "line"
    while (line != ""):
        line = sourcefile.readline()
        if (line != ""):
            target.write(line)
    sourcefile.close()

def listdir(path, incfiles = TRUE, incdirs = TRUE, inclinks = TRUE):
    """ A modified listdir with some optional parameters. """
    files = os.listdir(path)
    if (incfiles == TRUE) and (incdirs == TRUE) and (inclinks == TRUE):
        cleanfiles = files
    else:
        cleanfiles = []
        if path[-1] != "/":
            path = "%s/" % path
        for i in range(len(files)):
            file = "%s%s" % (path, files[i])
            addFile = TRUE
            if (incfiles == FALSE) and (os.path.isfile(file)):
                addFile = FALSE
            if (incdirs == FALSE) and (os.path.isdir(file)):
                addFile = FALSE
            if (inclinks == FALSE) and (os.path.islink(file)):
                addFile = FALSE
            if (addFile == TRUE):
                cleanfiles.append(files[i])
    cleanfiles.sort()
    return cleanfiles

def touch(filename):
    """ Implements the functionality of the unix command "touch". """
    if (os.path.exists(filename)):
        now = time()
        utime(filename, (now, now))
    else:
        file = open(filename, "w")
        file.close()

def df(directory = ""):
    """ Returns the values of "df" in list form. """
    df_command = which("df")
    df_command = strip("%s --block-size=1 %s" % (df_command, directory))
    output = striplist(cmdoutput(df_command)[1:])
    entry_list = []
    for i in range(len(output)):
        splitted = cleanlist(split(output[i], " "))
        entry = {}
        entry["FILESYSTEM"] = splitted[0]
        entry["SIZE"] = atoi(splitted[1])
        entry["USED"] = atoi(splitted[2])
        entry["AVAILABLE"] = atoi(splitted[3])
        entry["PERCENTAGE"] = atoi(splitted[4][:-1])
        entry["MOUNT_POINT"] = splitted[5]
        entry_list.append(entry)
    return entry_list

def cmpdirs(dict1, dict2):
    """ Compares two dictionaries of lstatfiles and returns a tuple ( {added}, {deleted}, {modified]) """
    added = {} 
    deleted = {}
    modified = {}

    for i in range(len(dict2.keys())):
        key = dict2.keys()[i]
        value = dict2[key]
        if (not key in dict1.keys()):
            added[key] = value
        else:
            if (value[8] != dict1[key][8]):
                modified[key] = value
    for i in range(len(dict1.keys())):
        key = dict1.keys()[i]
        value = dict1[key]
        if (not key in dict2.keys()):
            deleted[key] = value
    return (added, deleted, modified)

def lstatfiles(filelist):
    """ Returns a dictionary {filename: lstat} for a given [filelist]. """
    statdict = {}
    for i in range(len(filelist)):
        statinfo = lstat(filelist[i])
        statdict[filelist[i]] = statinfo
    return statdict

def escapedfilename(filename):
    """ escapes special characters in filenames (currently "`") """
    filename = replace(filename, "`", "\`")
    return filename

"""

Pipe functions

"""

def cmdoutput(command, strip = FALSE, waitpid = TRUE, returnerror = FALSE):
    """ Read the complete output of a command. """
    pipe = popen3(command)

    if (returnerror == TRUE):
        output = pipe[2].readlines()
    else:
        output = pipe[0].readlines()

    if (strip == TRUE):
        output = striplist(output)

    if (pipe[0] != None):
        pipe[0].close()
    if (pipe[1] != None):
        pipe[1].close()
    if (pipe[2] != None):
        pipe[2].close()
    if (waitpid == TRUE):
        os.wait()
    del pipe
    pipe = None
    return output

"""

General functions

"""

def SigChldHandler(signal_number, stack_frame):
    """ Handler, which waits for children to be closed. """

    # Reinstall the sigchild handler, because it gets deleted on libc5/sysv machines
    signal.signal(signal.SIGCHLD, SigChldHandler)
    pid = -1
    while (pid != 0):
        try:
            pid = os.waitpid(0, os.WNOHANG)[0]
        except:
            pid = 0

# ANSI text (fore- & background, bold font)
def ansi(text, fg = WHITE, bg = BLACK, bold = FALSE):
    if (bold == TRUE):
        fg = "%s;1" % fg
    bg = str(atoi(bg) + 10)
    text = "\033[%s;%sm%s\033[0m" % (bg, fg, text)
    return text


"""

Extended version of urlparse (includes username & password)

"""

# extended urlparse function (including username, password)
# returns: <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
# returns: <scheme>://<user>:<password>@<netloc>:<port>/<path>;<params>?<query>#<fragment>

urlparseorg = urlparse
_services_cache = {}

def readservices():
    global _services_cache
    services = {}
    if (len(_services_cache) == 0):
        if (os.path.exists("/etc/services")):
            file = open("/etc/services", "r")
            lines = file.readlines()
            file.close()
            for i in range(len(lines)):
                line = strip(lines[i])
                if (find(line, "#") != -1):
                    line = strip(line[:find(line, "#")])
                if (len(line) > 0):
                    nameportpair = split(line)
                    if (len(nameportpair) > 1):
                        name, port = nameportpair[:2]
                        if find(port, "/") != -1:
                            port, protocol = split(port, "/")
                            services[name] = port
        else:
            services["ftp"] = 21
            services["http"] = 80
        _services_cache = copy.copy(services)
    else:
        services = copy.copy(_services_cache)
    return services

def urlparse(url):
    scheme, netloc, path, params, query, fragment = urlparseorg(url)
    services = readservices()
    user = password = ""
    if services.has_key(scheme):
        port = atoi(services[scheme])
    else:
        port = 0
    if (find(netloc, "@") != -1):
        user, netloc = split(netloc, "@")
    if (find(user, ":") != -1):
        user, password = split(user, ":")
    if (find(netloc, ":") != -1):
        netloc, port = split(netloc, ":")
        port = atoi(port)
    if (path == ""):
        path = "/"
    tuple = scheme, user, password, netloc, port, path, params, query, fragment
    return tuple

"""

Database functions

"""

def db_null_string(s):
    if (s == "") or (s == None):
        return "NULL"
    else:
        return s

