Source code for oyProjectManager.utils

# -*- coding: utf-8 -*-
# Copyright (c) 2009-2012, Erkan Ozgur Yilmaz
# 
# This module is part of oyProjectManager and is released under the BSD 2
# License: http://www.opensource.org/licenses/BSD-2-Clause

import glob

import os, itertools, re



# lower letters
import shutil

trChars = dict()

trChars[u'\xc3\xa7'] = 'c'
trChars[u'\xc4\x9f'] = 'g'
trChars[u'\xc4\xb1'] = 'i'
trChars[u'\xc3\xb6'] = 'o'
trChars[u'\xc5\x9f'] = 's'
trChars[u'\xc3\xbc'] = 'u'

# upper letters
trChars[u'\xc3\x87'] = 'C'
trChars[u'\xc4\x9e'] = 'G'
trChars[u'\xc4\xb0'] = 'I'
trChars[u'\xc3\x96'] = 'O'
trChars[u'\xc5\x9e'] = 'S'
trChars[u'\xc3\x9c'] = 'U'

validFileNameChars = r' abcdefghijklmnoprqstuvwxyzABCDEFGHIJKLMNOPRQSTUVWXYZ0123456789._-'
validTextChars = validFileNameChars + r'!^+%&(){}[]:;?,|@`\'"/=*~$#'

validFileNameCharsPattern = re.compile(r'[\w\.\-\ ]+')
validTextCharsPattern = re.compile( r'[\w\.\-\ !\^+%&\(\)\{\}\[\]:;?,|\\@`\'"/=*~$#]+' )

def all_equal(elements):
    """return True if all the elements are equal, otherwise False.
    """
    first_element = elements[0]
    
    for other_element in elements[1:]:
        if other_element != first_element: return False
    
    return True

def common_prefix(*sequences):
    """return a list of common elements at the start of all sequences, then a
    list of lists that are the unique tails of each sequence.
    """
    
    # if there are no sequences at all, we're done
    if not sequences: return[], []
    # loop in parallel on the sequences
    common = []
    for elements in itertools.izip(*sequences):
        # unless all elements are equal, bail out of the loop
        if not all_equal(elements): break
        
        # got one more common element, append it and keep looping
        common.append(elements[0])
    
    # return the common prefix and unique tails
    return common, [sequence[len(common):] for sequence in sequences]

def relpath(p1, p2, sep=os.path.sep, pardir=os.path.pardir):
    """return a relative path from p1 equivalent to path p2.
    
    In particular:
    
        the empty string, if p1 == p2;
        p2, if p1 and p2 have no common prefix.
    
    """
    
    # replace any trailing slashes at the end
    p1 = re.sub(r"[/]+$", "" , p1)
    p1 = re.sub(r"[\\]+$", "",  p1)
    
    common, (u1, u2) = common_prefix(p1.split(sep), p2.split(sep))
    if not common:
        return p2 # leave path absolute if nothing at all in common
    
    return sep.join([pardir]*len(u1) + u2 )

def abspath(p1, p2):
    """Converts the p2 to abspath by joining it with p1
    
    The output is always normalized, so for windows all the path separators
    will be backslashes where on other systems it will be forward slashes. 
    """

    if not os.path.isabs(p2):
        return os.path.normpath(os.path.join(p1, p2))
    
    return p2

def createFolder(folderPath):
    """utility method that creates a folder if it doesn't exists
    """
    
    exists = os.path.exists(folderPath)
    
    if not exists:
        os.makedirs(folderPath)
    
    return exists

def mkdir(path):
    """Creates a directory in the given path
    """
    
    try:
        os.makedirs(path)
    except OSError:
        pass

def embedded_numbers(s):
    re_digits = re.compile(r'(\d+)')
    pieces = re_digits.split(str(s))
    pieces[1::2] = map(int, pieces[1::2])
    return pieces
 
def sort_string_numbers(str_list):
    """sorts strings with embedded numbers
    """
    return sorted(str_list, key=embedded_numbers)

def unique(s):
    """ Return a list of elements in s in arbitrary order, but without
    duplicates.
    """
    
    # Try using a set first, because it's the fastest and will usually work
    try:
        return list(set(s))
    except TypeError:
        pass # Move on to the next method
    
    # Since you can't hash all elements, try sorting, to bring equal items
    # together and then weed them out in a single pass
    t = list(s)
    try:
        t.sort()
    except TypeError:
        del t # Move on to the next method
    else:
        # the sort worked, so we are fine
        # do weeding
        return [x for i, x in enumerate(t) if not i or x != t[i-1]]
    # Brute force is all that's left
    u = []
    for x in s:
        if x not in u:
            u.append(x)
    
    return u

[docs]def uncompress_range(_range): """a shotRange is a string that contains numeric data with "," and "-" characters 1-4 expands to 1,2,3,4 10-5 expands to 5,6,7,8,9,10 1,4-7 expands to 1,4,5,6,7 1,4-7,11-4 expands to 1,4,5,6,7,8,9,10,11 """ shotList = [] * 0 assert(isinstance(_range, (str, unicode) ) ) # first split for "," groups = _range.split(",") for group in groups: # try to split for "-" ranges = group.split("-") if len(ranges) > 1: if ranges[0] != '' and ranges[1] != '': minRange = min( int(ranges[0]), int(ranges[1])) maxRange = max( int(ranges[0]), int(ranges[1])) for number in range(minRange, maxRange+1): if number not in shotList: shotList.append( number ) else: number = int(ranges[0]) if number not in shotList: shotList.append(number) shotList.sort() return shotList
def matchRange(_range): """validates the range string """ assert(isinstance(_range, (str, unicode))) pattern = re.compile('[0-9\-,]+') matchObj = re.match( pattern, _range ) if matchObj: _range = matchObj.group() else: _range = '' return _range def stringConditioner( text, allowSpaces=False, allowNumbers=True, allowNumbersAtBeginning=False, allowUnderScores=False, upperCaseOnly=False, capitalize=True ): """removes any spaces, underscores, and turkish characters from the name """ textFixed = unicode( text ) textFixed = multiple_replace( textFixed, trChars ) # remove all the white spaces and slashes patternString = '\t\n\r\f\v\\\/,\.;~!"\'^+%&()=?*{}\-' if not allowSpaces: patternString += ' ' if not allowNumbers: patternString += '0-9' if not allowUnderScores: patternString += "_" if allowNumbers and not allowNumbersAtBeginning: preMatchString = re.compile( '^[0-9]+') textFixed = preMatchString.sub("",textFixed) matchString = '[' + patternString + ']+' pattern = re.compile(matchString) textFixed = pattern.sub("",textFixed) if capitalize: textFixed = textFixed.capitalize() if upperCaseOnly: textFixed = textFixed.upper() return textFixed def multiple_replace( text, adict ): rx = re.compile('|'.join(map(re.escape, adict))) def one_xlat(match): return adict[match.group(0)] return rx.sub(one_xlat, text) def fixWindowsPath(path): """replaces / with \ for windows """ return unicode(path).replace(u'/',u'\\') def findFiles(pattern, search_path, pathsep=os.pathsep): return glob.glob(os.path.join(path,pattern)) def getChildFolders(path, return_full_path=False): """returns the child folders for the given path """ childFolders = os.listdir( path ) childFolders = filter( os.path.isdir, map( os.path.join, [path] * len(childFolders), childFolders ) ) if return_full_path: return childFolders else: return map( os.path.basename, childFolders ) def getChildFiles(path, return_full_path=False): """returns the child files for the given path """ childFiles = os.listdir( path ) childFiles = filter( os.path.isfile, map( os.path.join, [path] * len(childFiles), childFiles ) ) if return_full_path: return childFiles else: return map( os.path.basename, childFiles ) def backupFile(full_path, maximum_backupCount=None): """backups a file by copying it and then renaming it by adding .#.bak to the end of the file name so a file called myText.txt will be backed up as myText.txt.1.bak if there is a file with that name than it will increase the backup number """ # check if the file exists exists = os.path.exists(full_path) if not exists: # just return without doing anything return # get the base name of the file baseName = os.path.basename(full_path) # start the backup number from 1 backup_no = 1 backup_extension = '.bak' backup_file_full_path = '' # try to find maximum backup number # get the files backup_no = getMaximumBackupNumber(full_path) + 1 # now try to get the maximum backup number while True: backup_file_full_path = full_path + '.' + str(backup_no) + backup_extension if os.path.exists( full_path + '.' + str(backup_no) + backup_extension ): backup_no += 1 else: break # now copy the file with the new name shutil.copy( full_path, backup_file_full_path ) if maximum_backupCount is not None: maintainMaximumBackupCount( full_path, maximum_backupCount ) def getBackupFiles(full_path): """returns the backup files of the given file, returns None if couldn't find any """ # for a file lets say .settings.xml the backup file should be names as # .settings.xml.1.bak # so our search pattern should be # .settings.xml.*.bak backUpExtension = '.bak' pattern = full_path + '.*' + backUpExtension return sort_strings_with_embedded_numbers( glob.glob(pattern) ) def getBackupNumber(full_path): """returns the backup number of the file """ backupExtension = '.bak' # remove the backupExtension # and split the remaining # and use the last one as the backupVersion backupNumber = 0 try: backupNumber = int(full_path[0:-len(backupExtension)].split('.')[-1]) except (IndexError, ValueError): backupNumber = 0 return backupNumber def getMaximumBackupNumber(full_path): """returns the maximum backup number of the file """ backupFiles = getBackupFiles(full_path) maximumBackupNumber = 0 if len(backupFiles): maximumBackupNumber = getBackupNumber(backupFiles[-1]) return maximumBackupNumber def maintainMaximumBackupCount(full_path, maximum_backup_count): """keeps maximum of given number of backups for the given file """ if maximum_backup_count is None: return # get the backup files backupFiles = getBackupFiles(full_path) if len(backupFiles) > maximum_backup_count: # delete the older backups for backupFile in backupFiles[:-maximum_backup_count]: os.remove( backupFile ) def embedded_numbers(s): re_digits = re.compile(r'(\d+)') pieces = re_digits.split(str(s)) pieces[1::2] = map(int, pieces[1::2]) return pieces def sort_strings_with_embedded_numbers(alist): """sorts a string with embedded numbers """ return sorted(alist, key=embedded_numbers) def padNumber(number, pad): """pads a number with zeros """ return ("%0" + str(pad) + "d") % number def invalidCharacterRemover( text, validChars ): """its a more stupid way to condition a text """ conditionedText = '' for char in text: if char in validChars: conditionedText += char return conditionedText def file_name_conditioner(fileName): """ conditions the file name by replacing the whitespaces and slashes and back slashes with underscore ("_") characters """ assert(isinstance(fileName, (str, unicode) ) ) fileName = multiple_replace( fileName, trChars ) # make all uppercase fileName = fileName.upper() # replace all the white spaces and slashes # with underscore ("_") character pattern = re.compile('[\t\n\r\f\v\\\/ ]+') fileName = pattern.sub("_",fileName) return fileName def open_browser_in_location(path): """Opens the os native browser at the given path :param path: The path that the browser should be opened at. """ import os import subprocess import platform command = [] platform_info = platform.platform() if platform_info.startswith('Linux'): command = 'nautilus ' + path elif platform_info.startswith('Windows'): command = 'explorer ' + path.replace('/', '\\') elif platform_info.startswith('Darwin'): command = 'open -a /System/Library/CoreServices/Finder.app ' + path if os.path.exists(path): subprocess.call(command, shell=True) else: raise IOError("%s doesn't exists!" % path)