"""

Miscellaneous functions written by Martin Preishuber

"""

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

import signal
import os
import string
import copy
import sys

# 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

"""

# Extended split (doesn't split within special characters)
def split(s, sep = None, special = None):
    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

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

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

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

# Merge two strings byte-by-byte
def merge(string1, string2):
    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

"""

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

# Converts a String value to the corresponding Boolean
def str2bool(value):
    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

# Returns a string representation of a number (with a pretty-print option)
def lng2str(number, prettyprint = TRUE):
    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

"""

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

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

# Get the difference of two lists
def listdiff(list1, list2):
    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


"""

Time functions

"""

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


"""

File and directory operations

"""

# Creates a directory listing, optional including full filenames and recursive
def ls(directory, recursive = FALSE, includepath = FALSE, onlydirectories = FALSE):
    longlist = []
    allfiles = os.listdir(directory)
    if (directory == "."): 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

# Remove a file or directory (recursive) and return the number of directories, files, bytes & links deleted
def rm(file, maximum = -1, deleted = 0):
    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]

# Creates a whole direcotry tree
def mkdirtree(directory):
    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)

# Calculate the disk usage of a given directory
def du(file):
    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

# Check the path to find some specified program
def which(file):
    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 ""

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

# Include a file into a already open file
def includefile(source, target):
    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):
    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

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

"""

Pipe functions

"""

def _readlines(opendescriptor):
    line = None
    output = []
    while (line != ""):
        line = opendescriptor.readline()
        if (line != ""):
            output.append(line)
    return output
        
# Read the complete output of a command
def cmdoutput(command, strip = FALSE, waitpid = FALSE):
    pipe = popen3(command)
    error = pipe[2].readline()
    sys.stdout.write(error)

    # Note: this is a workaround, since pipe.readlines() doesn't work with python 2.x and
    # gnome-python (it throws an IOError: (0, 'Error')), so check this back somewhen !
    # output = pipe[0].readlines()
    
    output = _readlines(pipe[0])
    if (strip == TRUE):
        output = striplist(output)
    pipe[0].close()
    pipe[1].close()
    pipe[2].close()
    del pipe
    pipe = None
    if (waitpid == TRUE):
        os.wait()
    return output

"""

General functions

"""

# Handler, which waits for children to be closed
def SigChldHandler(signal_number, stack_frame):
    # 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
