eric6/Utilities/__init__.py

changeset 6942
2602857055c5
parent 6891
93f82da09f22
child 7098
fbdf76af88ed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Utilities/__init__.py	Sun Apr 14 15:09:21 2019 +0200
@@ -0,0 +1,2050 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2003 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing various functions/classes needed everywhere within eric6.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode
+    import locale
+    import urllib
+
+    def quote(url):
+        """
+        Replacement for the urllib.quote function because of unicode problems.
+        
+        @param url text to quote (string)
+        @return quoted url (string)
+        """
+        return urllib.quote(url.encode('utf-8'))
+except NameError:
+    basestring = str
+    from urllib.parse import quote    # __IGNORE_WARNING__
+
+import os
+import sys
+import codecs
+import re
+import fnmatch
+import glob
+import getpass
+
+
+def __showwarning(message, category, filename, lineno, file=None, line=""):
+    """
+    Module function to raise a SyntaxError for a SyntaxWarning.
+    
+    @param message warning object
+    @param category type object of the warning
+    @param filename name of the file causing the warning (string)
+    @param lineno line number causing the warning (integer)
+    @param file file to write the warning message to (ignored)
+    @param line line causing the warning (ignored)
+    @raise SyntaxError
+    """     # __IGNORE_WARNING_D252__ __IGNORE_WARNING_D253__
+    if category is SyntaxWarning:
+        err = SyntaxError(str(message))
+        err.filename = filename
+        err.lineno = lineno
+        raise err
+    
+import warnings
+warnings.showwarning = __showwarning
+
+from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32
+
+from PyQt5.QtCore import QRegExp, QDir, QProcess, Qt, QByteArray, \
+    qVersion, PYQT_VERSION_STR, QCoreApplication, QCryptographicHash
+from PyQt5.Qsci import QSCINTILLA_VERSION_STR, QsciScintilla
+
+# import these methods into the Utilities namespace
+from Globals import (  # __IGNORE_WARNING__
+    isWindowsPlatform, isLinuxPlatform, isMacPlatform, desktopName,
+    getConfigDir, setConfigDir, getPythonModulesDirectory,
+    getPyQt5ModulesDirectory, getQtBinariesPath, getPyQtToolsPath,
+    qVersionTuple)
+
+from E5Gui.E5Application import e5App
+
+from UI.Info import Program, Version
+
+import Preferences
+from Plugins.CheckerPlugins.SyntaxChecker.SyntaxCheck import (
+    # __IGNORE_WARNING__
+    normalizeCode)
+
+from eric6config import getConfig
+
+configDir = None
+
+codingBytes_regexps = [
+    (5, re.compile(br'''coding[:=]\s*([-\w_.]+)''')),
+    (1, re.compile(br'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')),
+]
+coding_regexps = [
+    (5, re.compile(r'''coding[:=]\s*([-\w_.]+)''')),
+    (1, re.compile(r'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')),
+]
+
+supportedCodecs = [
+    'utf-8',
+    'iso8859-1', 'iso8859-15', 'iso8859-2', 'iso8859-3',
+    'iso8859-4', 'iso8859-5', 'iso8859-6', 'iso8859-7',
+    'iso8859-8', 'iso8859-9', 'iso8859-10', 'iso8859-11',
+    'iso8859-13', 'iso8859-14', 'iso8859-16', 'latin-1',
+    'koi8-r', 'koi8-u',
+    'utf-16', 'utf-32',
+    'cp037', 'cp424', 'cp437', 'cp500', 'cp737', 'cp775',
+    'cp850', 'cp852', 'cp855', 'cp856', 'cp857', 'cp860',
+    'cp861', 'cp862', 'cp863', 'cp864', 'cp865', 'cp866',
+    'cp869', 'cp874', 'cp875', 'cp932', 'cp949', 'cp950',
+    'cp1006', 'cp1026', 'cp1140', 'cp1250', 'cp1251',
+    'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256',
+    'cp1257', 'cp1258',
+    'gb2312', 'gb18030',
+    'ascii'
+]
+
+
+class CodingError(Exception):
+    """
+    Class implementing an exception, which is raised, if a given coding is
+    incorrect.
+    """
+    def __init__(self, coding):
+        """
+        Constructor
+        
+        @param coding coding to include in the message (string)
+        """
+        self.errorMessage = QCoreApplication.translate(
+            "CodingError",
+            "The coding '{0}' is wrong for the given text.").format(coding)
+        
+    def __repr__(self):
+        """
+        Special method returning a representation of the exception.
+        
+        @return string representing the error message
+        """
+        return str(self.errorMessage)
+        
+    def __str__(self):
+        """
+        Special method returning a string representation of the exception.
+        
+        @return string representing the error message
+        """
+        return str(self.errorMessage)
+    
+
+def get_codingBytes(text):
+    """
+    Function to get the coding of a bytes text.
+    
+    @param text bytes text to inspect (bytes)
+    @return coding string
+    """
+    lines = text.splitlines()
+    for coding in codingBytes_regexps:
+        coding_re = coding[1]
+        head = lines[:coding[0]]
+        for l in head:
+            m = coding_re.search(l)
+            if m:
+                return str(m.group(1), "ascii").lower()
+    return None
+
+
+def get_coding(text):
+    """
+    Function to get the coding of a text.
+    
+    @param text text to inspect (string)
+    @return coding string
+    """
+    lines = text.splitlines()
+    for coding in coding_regexps:
+        coding_re = coding[1]
+        head = lines[:coding[0]]
+        for l in head:
+            m = coding_re.search(l)
+            if m:
+                return m.group(1).lower()
+    return None
+
+
+def readEncodedFile(filename):
+    """
+    Function to read a file and decode its contents into proper text.
+    
+    @param filename name of the file to read (string)
+    @return tuple of decoded text and encoding (string, string)
+    """
+    f = open(filename, "rb")
+    text = f.read()
+    f.close()
+    return decode(text)
+
+
+def readEncodedFileWithHash(filename):
+    """
+    Function to read a file, calculate a hash value and decode its contents
+    into proper text.
+    
+    @param filename name of the file to read (string)
+    @return tuple of decoded text, encoding and hash value (string, string,
+        string)
+    """
+    f = open(filename, "rb")
+    text = f.read()
+    f.close()
+    hashStr = str(QCryptographicHash.hash(
+        QByteArray(text), QCryptographicHash.Md5).toHex(), encoding="ASCII")
+    return decode(text) + (hashStr, )
+
+
+def decode(text):
+    """
+    Function to decode some byte text into a string.
+    
+    @param text byte text to decode (bytes)
+    @return tuple of decoded text and encoding (string, string)
+    """
+    try:
+        if text.startswith(BOM_UTF8):
+            # UTF-8 with BOM
+            return str(text[len(BOM_UTF8):], 'utf-8'), 'utf-8-bom'
+        elif text.startswith(BOM_UTF16):
+            # UTF-16 with BOM
+            return str(text[len(BOM_UTF16):], 'utf-16'), 'utf-16'
+        elif text.startswith(BOM_UTF32):
+            # UTF-32 with BOM
+            return str(text[len(BOM_UTF32):], 'utf-32'), 'utf-32'
+        coding = get_codingBytes(text)
+        if coding:
+            return str(text, coding), coding
+    except (UnicodeError, LookupError):
+        pass
+    
+    # Assume UTF-8
+    try:
+        return str(text, 'utf-8'), 'utf-8-guessed'
+    except (UnicodeError, LookupError):
+        pass
+    
+    guess = None
+    if Preferences.getEditor("AdvancedEncodingDetection"):
+        # Try the universal character encoding detector
+        try:
+            import ThirdParty.CharDet.chardet
+            guess = ThirdParty.CharDet.chardet.detect(text)
+            if guess and guess['confidence'] > 0.95 and \
+                    guess['encoding'] is not None:
+                codec = guess['encoding'].lower()
+                return str(text, codec), '{0}-guessed'.format(codec)
+        except (UnicodeError, LookupError):
+            pass
+        except ImportError:
+            pass
+    
+    # Try default encoding
+    try:
+        codec = Preferences.getEditor("DefaultEncoding")
+        return str(text, codec), '{0}-default'.format(codec)
+    except (UnicodeError, LookupError):
+        pass
+    
+    if Preferences.getEditor("AdvancedEncodingDetection"):
+        # Use the guessed one even if confifence level is low
+        if guess and guess['encoding'] is not None:
+            try:
+                codec = guess['encoding'].lower()
+                return str(text, codec), '{0}-guessed'.format(codec)
+            except (UnicodeError, LookupError):
+                pass
+    
+    # Assume UTF-8 loosing information
+    return str(text, "utf-8", "ignore"), 'utf-8-ignore'
+
+
+def readEncodedFileWithEncoding(filename, encoding):
+    """
+    Function to read a file and decode its contents into proper text.
+    
+    @param filename name of the file to read (string)
+    @keyparam encoding encoding to be used to read the file (string)
+    @return tuple of decoded text and encoding (string, string)
+    """
+    f = open(filename, "rb")
+    text = f.read()
+    f.close()
+    if encoding:
+        try:
+            return str(text, encoding), '{0}-selected'.format(encoding)
+        except (UnicodeError, LookupError):
+            pass
+        # Try default encoding
+        try:
+            codec = Preferences.getEditor("DefaultEncoding")
+            return str(text, codec), '{0}-default'.format(codec)
+        except (UnicodeError, LookupError):
+            pass
+        # Assume UTF-8 loosing information
+        return str(text, "utf-8", "ignore"), 'utf-8-ignore'
+    else:
+        return decode(text)
+
+
+def writeEncodedFile(filename, text, origEncoding, forcedEncoding=""):
+    """
+    Function to write a file with properly encoded text.
+    
+    @param filename name of the file to read
+    @type str
+    @param text text to be written
+    @type str
+    @param origEncoding type of the original encoding
+    @type str
+    @param forcedEncoding encoding to be used for writing, if no coding
+        line is present
+    @type str
+    @return encoding used for writing the file
+    @rtype str
+    """
+    etext, encoding = encode(text, origEncoding, forcedEncoding=forcedEncoding)
+    
+    f = open(filename, "wb")
+    f.write(etext)
+    f.close()
+    
+    return encoding
+
+
+def encode(text, origEncoding, forcedEncoding=""):
+    """
+    Function to encode text into a byte text.
+    
+    @param text text to be encoded
+    @type str
+    @param origEncoding type of the original encoding
+    @type str
+    @param forcedEncoding encoding to be used for writing, if no coding line
+        is present
+    @type str
+    @return tuple of encoded text and encoding used
+    @rtype tuple of (bytes, str)
+    @exception CodingError raised to indicate an invalid encoding
+    """
+    encoding = None
+    if origEncoding == 'utf-8-bom':
+        etext, encoding = BOM_UTF8 + text.encode("utf-8"), 'utf-8-bom'
+    else:
+        # Try declared coding spec
+        coding = get_coding(text)
+        if coding:
+            try:
+                etext, encoding = text.encode(coding), coding
+            except (UnicodeError, LookupError):
+                # Error: Declared encoding is incorrect
+                raise CodingError(coding)
+        else:
+            if forcedEncoding:
+                try:
+                    etext, encoding = (
+                        text.encode(forcedEncoding), forcedEncoding)
+                except (UnicodeError, LookupError):
+                    # Error: Forced encoding is incorrect, ignore it
+                    pass
+            
+            if encoding is None:
+                # Try the original encoding
+                if origEncoding and origEncoding.endswith(
+                        ('-selected', '-default', '-guessed', '-ignore')):
+                    coding = origEncoding\
+                        .replace("-selected", "")\
+                        .replace("-default", "")\
+                        .replace("-guessed", "")\
+                        .replace("-ignore", "")
+                    try:
+                        etext, encoding = text.encode(coding), coding
+                    except (UnicodeError, LookupError):
+                        pass
+                
+                if encoding is None:
+                    # Try configured default
+                    try:
+                        codec = Preferences.getEditor("DefaultEncoding")
+                        etext, encoding = text.encode(codec), codec
+                    except (UnicodeError, LookupError):
+                        pass
+                    
+                    if encoding is None:
+                        # Try saving as ASCII
+                        try:
+                            etext, encoding = text.encode('ascii'), 'ascii'
+                        except UnicodeError:
+                            pass
+                        
+                        if encoding is None:
+                            # Save as UTF-8 without BOM
+                            etext, encoding = text.encode('utf-8'), 'utf-8'
+    
+    return etext, encoding
+
+
+def decodeString(text):
+    """
+    Function to decode a string containing Unicode encoded characters.
+    
+    @param text text containing encoded chars (string)
+    @return decoded text (string)
+    """
+    buf = b""
+    index = 0
+    while index < len(text):
+        if text[index] == "\\":
+            qb = QByteArray.fromHex(text[index:index + 4].encode())
+            buf += bytes(qb)
+            index += 4
+        else:
+            buf += codecs.encode(text[index], "utf-8")
+            index += 1
+    buf = buf.replace(b"\x00", b"")
+    return decodeBytes(buf)
+    
+
+def decodeBytes(buffer):
+    """
+    Function to decode some byte text into a string.
+    
+    @param buffer byte buffer to decode (bytes)
+    @return decoded text (string)
+    """
+    # try UTF with BOM
+    try:
+        if buffer.startswith(BOM_UTF8):
+            # UTF-8 with BOM
+            return str(buffer[len(BOM_UTF8):], encoding='utf-8')
+        elif buffer.startswith(BOM_UTF16):
+            # UTF-16 with BOM
+            return str(buffer[len(BOM_UTF16):], encoding='utf-16')
+        elif buffer.startswith(BOM_UTF32):
+            # UTF-32 with BOM
+            return str(buffer[len(BOM_UTF32):], encoding='utf-32')
+    except (UnicodeError, LookupError):
+        pass
+    
+    # try UTF-8
+    try:
+        return str(buffer, encoding="utf-8")
+    except UnicodeError:
+        pass
+    
+    # try codec detection
+    try:
+        import ThirdParty.CharDet.chardet
+        guess = ThirdParty.CharDet.chardet.detect(buffer)
+        if guess and guess['encoding'] is not None:
+            codec = guess['encoding'].lower()
+            return str(buffer, encoding=codec)
+    except (UnicodeError, LookupError):
+        pass
+    except ImportError:
+        pass
+    
+    return str(buffer, encoding="utf-8", errors="ignore")
+
+
+def readStringFromStream(stream):
+    """
+    Module function to read a string from the given stream.
+    
+    @param stream data stream opened for reading (QDataStream)
+    @return string read from the stream (string)
+    """
+    data = stream.readString()
+    if data is None:
+        data = b""
+    return data.decode('utf-8')
+
+
+_escape = re.compile("[&<>\"'\u0080-\uffff]")
+
+_escape_map = {
+    "&": "&amp;",
+    "<": "&lt;",
+    ">": "&gt;",
+    '"': "&quot;",
+    "'": "&#x27;",
+}
+
+
+def escape_entities(m, escmap=_escape_map):
+    """
+    Function to encode html entities.
+    
+    @param m the match object
+    @param escmap the map of entities to encode
+    @return the converted text (string)
+    """
+    char = m.group()
+    text = escmap.get(char)
+    if text is None:
+        text = "&#{0:d};".format(ord(char))
+    return text
+    
+
+def html_encode(text, pattern=_escape):
+    """
+    Function to correctly encode a text for html.
+    
+    @param text text to be encoded (string)
+    @param pattern search pattern for text to be encoded (string)
+    @return the encoded text (string)
+    """
+    if not text:
+        return ""
+    text = pattern.sub(escape_entities, text)
+    return text
+
+_uescape = re.compile('[\u0080-\uffff]')
+
+
+def escape_uentities(m):
+    """
+    Function to encode html entities.
+    
+    @param m the match object
+    @return the converted text (string)
+    """
+    char = m.group()
+    text = "&#{0:d};".format(ord(char))
+    return text
+    
+
+def html_uencode(text, pattern=_uescape):
+    """
+    Function to correctly encode a unicode text for html.
+    
+    @param text text to be encoded (string)
+    @param pattern search pattern for text to be encoded (string)
+    @return the encoded text (string)
+    """
+    if not text:
+        return ""
+    text = pattern.sub(escape_uentities, text)
+    return text
+
+_uunescape = re.compile(r'&#\d+;')
+
+
+def unescape_uentities(m):
+    """
+    Function to decode html entities.
+    
+    @param m the match object
+    @return the converted text (string)
+    """
+    char = m.group()
+    ordinal = int(char[2:-1])
+    return chr(ordinal)
+
+
+def html_udecode(text, pattern=_uunescape):
+    """
+    Function to correctly decode a html text to a unicode text.
+    
+    @param text text to be decoded (string)
+    @param pattern search pattern for text to be decoded (string)
+    @return the decoded text (string)
+    """
+    if not text:
+        return ""
+    text = pattern.sub(unescape_uentities, text)
+    return text
+
+
+def convertLineEnds(text, eol):
+    """
+    Function to convert the end of line characters.
+    
+    @param text text to be converted (string)
+    @param eol new eol setting (string)
+    @return text with converted eols (string)
+    """
+    if eol == '\r\n':
+        regexp = re.compile(r"""(\r(?!\n)|(?<!\r)\n)""")
+        return regexp.sub(lambda m, eol='\r\n': eol, text)
+    elif eol == '\n':
+        regexp = re.compile(r"""(\r\n|\r)""")
+        return regexp.sub(lambda m, eol='\n': eol, text)
+    elif eol == '\r':
+        regexp = re.compile(r"""(\r\n|\n)""")
+        return regexp.sub(lambda m, eol='\r': eol, text)
+    else:
+        return text
+
+
+def linesep():
+    """
+    Function to return the line separator used by the editor.
+    
+    @return line separator used by the editor (string)
+    """
+    eolMode = Preferences.getEditor("EOLMode")
+    if eolMode == QsciScintilla.EolUnix:
+        return "\n"
+    elif eolMode == QsciScintilla.EolMac:
+        return "\r"
+    else:
+        return "\r\n"
+
+
+def extractFlags(text):
+    """
+    Function to extract eric specific flags out of the given text.
+    
+    Flags are contained in comments and are introduced by 'eflag:'.
+    The rest of the line is interpreted as 'key = value'. value is
+    analyzed for being an integer or float value. If that fails, it
+    is assumed to be a string. If a key does not contain a '='
+    character, it is assumed to be a boolean flag. Flags are expected
+    at the very end of a file. The search is ended, if a line without
+    the 'eflag:' marker is found.
+    
+    @param text text to be scanned (string)
+    @return dictionary of string, boolean, complex, float and int
+    """
+    flags = {}
+    if isinstance(text, basestring):
+        lines = text.rstrip().splitlines()
+    else:
+        lines = text
+    for line in reversed(lines):
+        try:
+            index = line.index("eflag:")
+        except ValueError:
+            # no flag found, don't look any further
+            break
+        
+        flag = line[index + 6:].strip()
+        if "=" in flag:
+            key, value = flag.split("=", 1)
+            key = key.strip()
+            value = value.strip()
+            
+            if value.lower() in ["true", "false", "yes", "no", "ok"]:
+                # it is a flag
+                flags[key] = value.lower() in ["true", "yes", "ok"]
+                continue
+            
+            try:
+                # interpret as int first
+                value = int(value)
+            except ValueError:
+                try:
+                    # interpret as float next
+                    value = float(value)
+                except ValueError:
+                    pass
+            
+            flags[key] = value
+        else:
+            # treat it as a boolean
+            if flag[0] == "-":
+                # false flags start with '-'
+                flags[flag[1:]] = False
+            else:
+                flags[flag] = True
+    
+    return flags
+
+
+def extractFlagsFromFile(filename):
+    """
+    Function to extract eric specific flags out of the given file.
+    
+    @param filename name of the file to be scanned (string)
+    @return dictionary of string, boolean, complex, float and int
+    """
+    try:
+        source, encoding = readEncodedFile(filename)
+    except (UnicodeError, IOError):
+        return {}
+    
+    return extractFlags(source)
+
+
+def extractLineFlags(line, startComment="#", endComment="", flagsLine=False):
+    """
+    Function to extract flags starting and ending with '__' from a line
+    comment.
+    
+    @param line line to extract flags from (string)
+    @keyparam startComment string identifying the start of the comment (string)
+    @keyparam endComment string identifying the end of a comment (string)
+    @keyparam flagsLine flag indicating to check for a flags only line (bool)
+    @return list containing the extracted flags (list of strings)
+    """
+    flags = []
+    
+    if not flagsLine or (
+       flagsLine and line.strip().startswith(startComment)):
+        pos = line.rfind(startComment)
+        if pos >= 0:
+            comment = line[pos + len(startComment):].strip()
+            if endComment:
+                endPos = line.rfind(endComment)
+                if endPos >= 0:
+                    comment = comment[:endPos]
+            flags = [f.strip() for f in comment.split()
+                     if (f.startswith("__") and f.endswith("__"))]
+    return flags
+
+
+def filterAnsiSequences(txt):
+    """
+    Function to filter out ANSI escape sequences (color only).
+    
+    @param txt text to be filtered
+    @type str
+    @return text without ANSI escape sequences
+    @rtype str
+    """
+    ntxt = txt[:]
+    while True:
+        start = ntxt.find("\33[")    # find escape character
+        if start == -1:
+            break
+        end = ntxt.find("m", start)
+        if end == -1:
+            break
+        ntxt = ntxt[:start] + ntxt[end + 1:]
+    
+    return ntxt
+
+
+def toNativeSeparators(path):
+    """
+    Function returning a path, that is using native separator characters.
+    
+    @param path path to be converted (string)
+    @return path with converted separator characters (string)
+    """
+    return QDir.toNativeSeparators(path)
+    
+
+def fromNativeSeparators(path):
+    """
+    Function returning a path, that is using "/" separator characters.
+    
+    @param path path to be converted (string)
+    @return path with converted separator characters (string)
+    """
+    return QDir.fromNativeSeparators(path)
+    
+
+def normcasepath(path):
+    """
+    Function returning a path, that is normalized with respect to its case
+    and references.
+    
+    @param path file path (string)
+    @return case normalized path (string)
+    """
+    return os.path.normcase(os.path.normpath(path))
+    
+
+def normabspath(path):
+    """
+    Function returning a normalized, absolute path.
+    
+    @param path file path (string)
+    @return absolute, normalized path (string)
+    """
+    return os.path.abspath(path)
+    
+
+def normcaseabspath(path):
+    """
+    Function returning an absolute path, that is normalized with respect to
+    its case and references.
+    
+    @param path file path (string)
+    @return absolute, normalized path (string)
+    """
+    return os.path.normcase(os.path.abspath(path))
+    
+
+def normjoinpath(a, *p):
+    """
+    Function returning a normalized path of the joined parts passed into it.
+    
+    @param a first path to be joined (string)
+    @param p variable number of path parts to be joind (string)
+    @return normalized path (string)
+    """
+    return os.path.normpath(os.path.join(a, *p))
+    
+
+def normabsjoinpath(a, *p):
+    """
+    Function returning a normalized, absolute path of the joined parts passed
+    into it.
+    
+    @param a first path to be joined (string)
+    @param p variable number of path parts to be joind (string)
+    @return absolute, normalized path (string)
+    """
+    return os.path.abspath(os.path.join(a, *p))
+    
+
+def relpath(path, start=os.path.curdir):
+    """
+    Return a relative version of a path.
+    
+    @param path path to make relative (string)
+    @param start path to make relative from (string)
+    @return relative path (string)
+    @exception ValueError raised to indicate an invalid path
+    """
+    if not path:
+        raise ValueError("no path specified")
+
+    start_list = os.path.abspath(start).split(os.path.sep)
+    path_list = os.path.abspath(path).split(os.path.sep)
+
+    # Work out how much of the filepath is shared by start and path.
+    i = len(os.path.commonprefix([start_list, path_list]))
+
+    rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:]
+    if not rel_list:
+        return os.path.curdir
+    return os.path.join(*rel_list)
+
+
+def isinpath(file):
+    """
+    Function to check for an executable file.
+    
+    @param file filename of the executable to check (string)
+    @return flag to indicate, if the executable file is accessible
+        via the searchpath defined by the PATH environment variable.
+    """
+    if os.path.isabs(file):
+        return os.access(file, os.X_OK)
+    
+    if os.path.exists(os.path.join(os.curdir, file)):
+        return os.access(os.path.join(os.curdir, file), os.X_OK)
+    
+    path = getEnvironmentEntry('PATH')
+    
+    # environment variable not defined
+    if path is None:
+        return False
+    
+    dirs = path.split(os.pathsep)
+    for directory in dirs:
+        if os.access(os.path.join(directory, file), os.X_OK):
+            return True
+    
+    return False
+
+
+def startswithPath(path, start):
+    """
+    Function to check, if a path starts with a given start path.
+    
+    @param path path to be checked (string)
+    @param start start path (string)
+    @return flag indicating that the path starts with the given start
+        path (boolean)
+    """
+    if start:
+        if path == start:
+            return True
+        elif normcasepath(toNativeSeparators(path)).startswith(
+                normcasepath(toNativeSeparators(start + "/"))):
+            return True
+        else:
+            return False
+    else:
+        return False
+
+
+def relativePath(path, start):
+    """
+    Function to convert a file path to a path relative to a start path.
+    
+    @param path file or directory name to convert (string)
+    @param start start path (string)
+    @return relative path or unchanged path, if path does not start with
+        the start path (string)
+    """
+    if startswithPath(path, start):
+        if path == start:
+            return ""
+        else:
+            if start.endswith(("/", "\\")):
+                return path[len(start):]
+            else:
+                return path[len(start) + 1:]
+    else:
+        return path
+
+
+def relativeUniversalPath(path, start):
+    """
+    Function to convert a file path to a path relative to a start path
+    with universal separators.
+    
+    @param path file or directory name to convert (string)
+    @param start start path (string)
+    @return relative path or unchanged path, if path does not start with
+        the start path with universal separators (string)
+    """
+    return fromNativeSeparators(relativePath(path, start))
+
+
+def absolutePath(path, start):
+    """
+    Public method to convert a path relative to a start path to an
+    absolute path.
+    
+    @param path file or directory name to convert (string)
+    @param start start path (string)
+    @return absolute path (string)
+    """
+    if not os.path.isabs(path):
+        path = os.path.join(start, path)
+    return path
+
+
+def absoluteUniversalPath(path, start):
+    """
+    Public method to convert a path relative to a start path with
+    universal separators to an absolute path.
+    
+    @param path file or directory name to convert (string)
+    @param start start path (string)
+    @return absolute path with native separators (string)
+    """
+    if not os.path.isabs(path):
+        path = toNativeSeparators(os.path.join(start, path))
+    return path
+
+
+def getExecutablePath(file):
+    """
+    Function to build the full path of an executable file from the environment.
+    
+    @param file filename of the executable to check (string)
+    @return full executable name, if the executable file is accessible
+        via the searchpath defined by the PATH environment variable, or an
+        empty string otherwise.
+    """
+    if os.path.isabs(file):
+        if os.access(file, os.X_OK):
+            return file
+        else:
+            return ""
+        
+    cur_path = os.path.join(os.curdir, file)
+    if os.path.exists(cur_path):
+        if os.access(cur_path, os.X_OK):
+            return cur_path
+
+    path = os.getenv('PATH')
+    
+    # environment variable not defined
+    if path is None:
+        return ""
+        
+    dirs = path.split(os.pathsep)
+    for directory in dirs:
+        exe = os.path.join(directory, file)
+        if os.access(exe, os.X_OK):
+            return exe
+            
+    return ""
+    
+
+def getExecutablePaths(file):
+    """
+    Function to build all full path of an executable file from the environment.
+    
+    @param file filename of the executable (string)
+    @return list of full executable names (list of strings), if the executable
+        file is accessible via the searchpath defined by the PATH environment
+        variable, or an empty list otherwise.
+    """
+    paths = []
+    
+    if os.path.isabs(file):
+        if os.access(file, os.X_OK):
+            return [file]
+        else:
+            return []
+        
+    cur_path = os.path.join(os.curdir, file)
+    if os.path.exists(cur_path):
+        if os.access(cur_path, os.X_OK):
+            paths.append(cur_path)
+
+    path = os.getenv('PATH')
+    
+    # environment variable not defined
+    if path is not None:
+        dirs = path.split(os.pathsep)
+        for directory in dirs:
+            exe = os.path.join(directory, file)
+            if os.access(exe, os.X_OK) and exe not in paths:
+                paths.append(exe)
+    
+    return paths
+    
+
+def getWindowsExecutablePath(file):
+    """
+    Function to build the full path of an executable file from the environment
+    on Windows platforms.
+    
+    First an executable with the extension .exe is searched for, thereafter
+    such with the extensions .cmd or .bat and finally the given file name as
+    is. The first match is returned.
+    
+    @param file filename of the executable to check (string)
+    @return full executable name, if the executable file is accessible
+        via the searchpath defined by the PATH environment variable, or an
+        empty string otherwise.
+    """
+    if os.path.isabs(file):
+        if os.access(file, os.X_OK):
+            return file
+        else:
+            return ""
+    
+    filenames = [file + ".exe", file + ".cmd", file + ".bat", file]
+    
+    for filename in filenames:
+        cur_path = os.path.join(os.curdir, filename)
+        if os.path.exists(cur_path):
+            if os.access(cur_path, os.X_OK):
+                return os.path.abspath(cur_path)
+
+    path = os.getenv('PATH')
+    
+    # environment variable not defined
+    if path is None:
+        return ""
+        
+    dirs = path.split(os.pathsep)
+    for directory in dirs:
+        for filename in filenames:
+            exe = os.path.join(directory, filename)
+            if os.access(exe, os.X_OK):
+                return exe
+    
+    return ""
+    
+
+def isExecutable(exe):
+    """
+    Function to check, if a file is executable.
+    
+    @param exe filename of the executable to check (string)
+    @return flag indicating executable status (boolean)
+    """
+    return os.access(exe, os.X_OK)
+
+
+def isDrive(path):
+    """
+    Function to check, if a path is a Windows drive.
+    
+    @param path path name to be checked
+    @type str
+    @return flag indicating a Windows drive
+    @rtype bool
+    """
+    isDrive = False
+    drive, directory = os.path.splitdrive(path)
+    if drive and len(drive) == 2 and drive.endswith(":") and \
+       directory in ["", "\\", "/"]:
+        isDrive = True
+    
+    return isDrive
+    
+
+def samepath(f1, f2):
+    """
+    Function to compare two paths.
+    
+    @param f1 first path for the compare (string)
+    @param f2 second path for the compare (string)
+    @return flag indicating whether the two paths represent the
+        same path on disk.
+    """
+    if f1 is None or f2 is None:
+        return False
+    
+    if normcaseabspath(os.path.realpath(f1)) == \
+            normcaseabspath(os.path.realpath(f2)):
+        return True
+    
+    return False
+
+
+def samefilepath(f1, f2):
+    """
+    Function to compare two paths. Strips the filename.
+    
+    @param f1 first filepath for the compare (string)
+    @param f2 second filepath for the compare (string)
+    @return flag indicating whether the two paths represent the
+        same path on disk.
+    """
+    if f1 is None or f2 is None:
+        return False
+    
+    if (normcaseabspath(os.path.dirname(os.path.realpath(f1))) ==
+            normcaseabspath(os.path.dirname(os.path.realpath(f2)))):
+        return True
+    
+    return False
+
+try:
+    EXTSEP = os.extsep
+except AttributeError:
+    EXTSEP = "."
+
+
+def splitPath(name):
+    """
+    Function to split a pathname into a directory part and a file part.
+    
+    @param name path name (string)
+    @return a tuple of 2 strings (dirname, filename).
+    """
+    if os.path.isdir(name):
+        dn = os.path.abspath(name)
+        fn = "."
+    else:
+        dn, fn = os.path.split(name)
+    return (dn, fn)
+
+
+def joinext(prefix, ext):
+    """
+    Function to join a file extension to a path.
+    
+    The leading "." of ext is replaced by a platform specific extension
+    separator if necessary.
+    
+    @param prefix the basepart of the filename (string)
+    @param ext the extension part (string)
+    @return the complete filename (string)
+    """
+    if ext[0] != ".":
+        ext = ".{0}".format(ext)
+        # require leading separator to match os.path.splitext
+    return prefix + EXTSEP + ext[1:]
+
+
+def compactPath(path, width, measure=len):
+    """
+    Function to return a compacted path fitting inside the given width.
+    
+    @param path path to be compacted (string)
+    @param width width for the compacted path (integer)
+    @param measure reference to a function used to measure the length of the
+        string
+    @return compacted path (string)
+    """
+    if measure(path) <= width:
+        return path
+    
+    ellipsis = '...'
+    
+    head, tail = os.path.split(path)
+    mid = len(head) // 2
+    head1 = head[:mid]
+    head2 = head[mid:]
+    while head1:
+        # head1 is same size as head2 or one shorter
+        path = os.path.join("{0}{1}{2}".format(head1, ellipsis, head2), tail)
+        if measure(path) <= width:
+            return path
+        head1 = head1[:-1]
+        head2 = head2[1:]
+    path = os.path.join(ellipsis, tail)
+    if measure(path) <= width:
+        return path
+    while tail:
+        path = "{0}{1}".format(ellipsis, tail)
+        if measure(path) <= width:
+            return path
+        tail = tail[1:]
+    return ""
+    
+
+def direntries(path, filesonly=False, pattern=None, followsymlinks=True,
+               checkStop=None):
+    """
+    Function returning a list of all files and directories.
+    
+    @param path root of the tree to check
+    @param filesonly flag indicating that only files are wanted
+    @param pattern a filename pattern to check against
+    @param followsymlinks flag indicating whether symbolic links
+            should be followed
+    @param checkStop function to be called to check for a stop
+    @return list of all files and directories in the tree rooted
+        at path. The names are expanded to start with path.
+    """
+    if filesonly:
+        files = []
+    else:
+        files = [path]
+    try:
+        entries = os.listdir(path)
+        for entry in entries:
+            if checkStop and checkStop():
+                break
+            
+            if entry in ['.svn',
+                         '.hg',
+                         '.git',
+                         '.ropeproject',
+                         '.eric6project']:
+                continue
+            
+            fentry = os.path.join(path, entry)
+            if pattern and \
+                not os.path.isdir(fentry) and \
+                    not fnmatch.fnmatch(entry, pattern):
+                # entry doesn't fit the given pattern
+                continue
+                
+            if os.path.isdir(fentry):
+                if os.path.islink(fentry) and not followsymlinks:
+                    continue
+                files += direntries(
+                    fentry, filesonly, pattern, followsymlinks, checkStop)
+            else:
+                files.append(fentry)
+    except OSError:
+        pass
+    except UnicodeDecodeError:
+        pass
+    return files
+
+
+def getDirs(path, excludeDirs):
+    """
+    Function returning a list of all directories below path.
+    
+    @param path root of the tree to check
+    @param excludeDirs basename of directories to ignore
+    @return list of all directories found
+    """
+    try:
+        names = os.listdir(path)
+    except EnvironmentError:
+        return []
+
+    dirs = []
+    for name in names:
+        if os.path.isdir(os.path.join(path, name)) and \
+                not os.path.islink(os.path.join(path, name)):
+            exclude = 0
+            for e in excludeDirs:
+                if name.split(os.sep, 1)[0] == e:
+                    exclude = 1
+                    break
+            if not exclude:
+                dirs.append(os.path.join(path, name))
+
+    for name in dirs[:]:
+        if not os.path.islink(name):
+            dirs = dirs + getDirs(name, excludeDirs)
+
+    return dirs
+
+
+def getTestFileName(fn):
+    """
+    Function to build the filename of a unittest file.
+    
+    The filename for the unittest file is built by prepending
+    the string "test" to the filename passed into this function.
+    
+    @param fn filename basis to be used for the unittest filename (string)
+    @return filename of the corresponding unittest file (string)
+    """
+    dn, fn = os.path.split(fn)
+    return os.path.join(dn, "test{0}".format(fn))
+
+
+def parseOptionString(s):
+    """
+    Function used to convert an option string into a list of options.
+    
+    @param s option string (string or string)
+    @return list of options (list of strings)
+    """
+    rx = QRegExp(r"""\s([\w=/-]*"[^"]+"|[\w=/-]*'[^']+'|[^\s]+)""")
+    s = re.sub(r"%[A-Z%]", _percentReplacementFunc, s)
+    return parseString(s, rx)
+    
+
+def parseEnvironmentString(s):
+    """
+    Function used to convert an environment string into a list of environment
+    settings.
+    
+    @param s environment string (string)
+    @return list of environment settings (list of strings)
+    """
+    rx = QRegExp(r"""\s(\w+\+?=[^\s]+|\w+="[^"]+"|\w+='[^']+')""")
+    return parseString(s, rx)
+
+
+def parseString(s, rx):
+    """
+    Function used to convert a string into a list.
+    
+    @param s string to be parsed (string)
+    @param rx regex defining the parse pattern (QRegExp)
+    @return list of parsed data (list of strings)
+    """
+    olist = []
+    if not s.startswith(' '):
+        # prepare the  string to fit our pattern
+        s = ' ' + s
+        
+    pos = rx.indexIn(s)
+    while pos != -1:
+        cs = rx.cap(1)
+        if cs.startswith('"') or cs.startswith("'"):
+            cs = cs[1:-1]
+        olist.append(cs)
+        pos += rx.matchedLength()
+        pos = rx.indexIn(s, pos)
+        
+    return olist
+
+
+def _percentReplacementFunc(matchobj):
+    """
+    Protected function called for replacing % codes.
+    
+    @param matchobj matchobject for the code
+    @return replacement string
+    """
+    return getPercentReplacement(matchobj.group(0))
+    
+
+def getPercentReplacement(code):
+    """
+    Function to get the replacement for code.
+    
+    @param code code indicator (string)
+    @return replacement string (string)
+    """
+    if code in ["C", "%C"]:
+        # column of the cursor of the current editor
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw is None:
+            column = -1
+        else:
+            column = aw.getCursorPosition()[1]
+        return "{0:d}".format(column)
+    elif code in ["D", "%D"]:
+        # directory of active editor
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw is None:
+            dn = "not_available"
+        else:
+            fn = aw.getFileName()
+            if fn is None:
+                dn = "not_available"
+            else:
+                dn = os.path.dirname(fn)
+        return dn
+    elif code in ["F", "%F"]:
+        # filename (complete) of active editor
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw is None:
+            fn = "not_available"
+        else:
+            fn = aw.getFileName()
+            if fn is None:
+                fn = "not_available"
+        return fn
+    elif code in ["H", "%H"]:
+        # home directory
+        return getHomeDir()
+    elif code in ["L", "%L"]:
+        # line of the cursor of the current editor
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw is None:
+            line = 0
+        else:
+            line = aw.getCursorPosition()[0] + 1
+        return "{0:d}".format(line)
+    elif code in ["P", "%P"]:
+        # project path
+        projectPath = e5App().getObject("Project").getProjectPath()
+        if not projectPath:
+            projectPath = "not_available"
+        return projectPath
+    elif code in ["S", "%S"]:
+        # selected text of the current editor
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw is None:
+            text = "not_available"
+        else:
+            text = aw.selectedText()
+        return text
+    elif code in ["U", "%U"]:
+        # username
+        un = getUserName()
+        if un is None:
+            return code
+        else:
+            return un
+    elif code in ["%", "%%"]:
+        # the percent sign
+        return "%"
+    else:
+        # unknown code, just return it
+        return code
+    
+
+def getPercentReplacementHelp():
+    """
+    Function to get the help text for the supported %-codes.
+    
+    @returns help text (string)
+    """
+    return QCoreApplication.translate(
+        "Utilities",
+        """<p>You may use %-codes as placeholders in the string."""
+        """ Supported codes are:"""
+        """<table>"""
+        """<tr><td>%C</td><td>column of the cursor of the current editor"""
+        """</td></tr>"""
+        """<tr><td>%D</td><td>directory of the current editor</td></tr>"""
+        """<tr><td>%F</td><td>filename of the current editor</td></tr>"""
+        """<tr><td>%H</td><td>home directory of the current user</td></tr>"""
+        """<tr><td>%L</td><td>line of the cursor of the current editor"""
+        """</td></tr>"""
+        """<tr><td>%P</td><td>path of the current project</td></tr>"""
+        """<tr><td>%S</td><td>selected text of the current editor</td></tr>"""
+        """<tr><td>%U</td><td>username of the current user</td></tr>"""
+        """<tr><td>%%</td><td>the percent sign</td></tr>"""
+        """</table>"""
+        """</p>""")
+
+
+def getUserName():
+    """
+    Function to get the user name.
+    
+    @return user name (string)
+    """
+    user = getpass.getuser()
+    
+    if isWindowsPlatform():
+        if not user:
+            return win32_GetUserName()
+        else:
+            if sys.version_info[0] == 2:
+                user = user.decode(locale.getpreferredencoding())
+    
+    return user
+
+
+def getRealName():
+    """
+    Function to get the real name of the user.
+    
+    @return real name of the user (string)
+    """
+    if isWindowsPlatform():
+        return win32_getRealName()
+    else:
+        import pwd
+        user = getpass.getuser()
+        return pwd.getpwnam(user).pw_gecos
+
+
+def getHomeDir():
+    """
+    Function to get a users home directory.
+    
+    @return home directory (string)
+    """
+    return QDir.homePath()
+    
+
+def getPythonLibPath():
+    """
+    Function to determine the path to Python's library.
+    
+    @return path to the Python library (string)
+    """
+    pyFullVers = sys.version.split()[0]
+
+    vl = re.findall("[0-9.]*", pyFullVers)[0].split(".")
+    major = vl[0]
+    minor = vl[1]
+
+    pyVers = major + "." + minor
+
+    if isWindowsPlatform():
+        libDir = sys.prefix + "\\Lib"
+    else:
+        try:
+            syslib = sys.lib
+        except AttributeError:
+            syslib = "lib"
+        libDir = sys.prefix + "/" + syslib + "/python" + pyVers
+        
+    return libDir
+    
+
+def getPythonVersion():
+    """
+    Function to get the Python version (major, minor) as an integer value.
+    
+    @return An integer representing major and minor version number (integer)
+    """
+    return sys.hexversion >> 16
+
+
+def determinePythonVersion(filename, source, editor=None):
+    """
+    Function to determine the python version of a given file.
+    
+    @param filename name of the file with extension (str)
+    @param source of the file (str)
+    @keyparam editor reference to the editor, if the file is opened
+        already (Editor object)
+    @return Python version if file is Python2 or Python3 (int)
+    """
+    pyAssignment = {"Python": 2, "Python2": 2, "Python3": 3}
+    
+    if not editor:
+        viewManager = e5App().getObject('ViewManager')
+        editor = viewManager.getOpenEditor(filename)
+    
+    # Maybe the user has changed the language
+    if editor and editor.getFileType() in pyAssignment:
+        return pyAssignment[editor.getFileType()]
+
+    pyVer = 0
+    if filename:
+        if not source:
+            source = readEncodedFile(filename)[0]
+        flags = extractFlags(source)
+        ext = os.path.splitext(filename)[1]
+        py2Ext = Preferences.getPython("PythonExtensions")
+        py3Ext = Preferences.getPython("Python3Extensions")
+        project = e5App().getObject('Project')
+        basename = os.path.basename(filename)
+        
+        if "FileType" in flags:
+            pyVer = pyAssignment.get(flags["FileType"], 0)
+        elif project.isOpen() and project.isProjectFile(filename):
+            language = project.getEditorLexerAssoc(basename)
+            if not language:
+                language = Preferences.getEditorLexerAssoc(basename)
+            if language in ['Python2', 'Python3']:
+                pyVer = pyAssignment[language]
+        
+        if pyVer:
+            # Skip the next tests
+            pass
+        elif (Preferences.getProject("DeterminePyFromProject") and
+              project.isOpen() and
+              project.isProjectFile(filename) and
+              ext in py2Ext + py3Ext):
+            pyVer = pyAssignment.get(project.getProjectLanguage(), 0)
+        elif ext in py2Ext and ext not in py3Ext:
+            pyVer = 2
+        elif ext in py3Ext and ext not in py2Ext:
+            pyVer = 3
+        elif source:
+            if isinstance(source, basestring):
+                line0 = source.splitlines()[0]
+            else:
+                line0 = source[0]
+            if line0.startswith("#!"):
+                if "python3" in line0:
+                    pyVer = 3
+                elif "python" in line0:
+                    pyVer = 2
+        
+        if pyVer == 0 and ext in py2Ext + py3Ext:
+            pyVer = sys.version_info[0]
+    
+    return pyVer
+
+
+###############################################################################
+# functions for environment handling
+###############################################################################
+
+
+def getEnvironmentEntry(key, default=None):
+    """
+    Module function to get an environment entry.
+    
+    @param key key of the requested environment entry (string)
+    @param default value to be returned, if the environment doesn't contain
+        the requested entry (string)
+    @return the requested entry or the default value, if the entry wasn't
+        found (string or None)
+    """
+    filterRe = QRegExp("^{0}[ \t]*=".format(key))
+    if isWindowsPlatform():
+        filterRe.setCaseSensitivity(Qt.CaseInsensitive)
+    
+    entries = [e for e in QProcess.systemEnvironment()
+               if filterRe.indexIn(e) != -1]
+    if not entries:
+        return default
+    
+    # if there are multiple entries, just consider the first one
+    ename, val = entries[0].split("=", 1)
+    return val.strip()
+
+
+def hasEnvironmentEntry(key):
+    """
+    Module function to check, if the environment contains an entry.
+    
+    @param key key of the requested environment entry (string)
+    @return flag indicating the presence of the requested entry (boolean)
+    """
+    filterRe = QRegExp("^{0}[ \t]*=".format(key))
+    if isWindowsPlatform():
+        filterRe.setCaseSensitivity(Qt.CaseInsensitive)
+    
+    entries = [e for e in QProcess.systemEnvironment()
+               if filterRe.indexIn(e) != -1]
+    return len(entries) > 0
+
+###############################################################################
+# Qt utility functions below
+###############################################################################
+
+
+def generateQtToolName(toolname):
+    """
+    Module function to generate the executable name for a Qt tool like
+    designer.
+    
+    @param toolname base name of the tool (string)
+    @return the Qt tool name without extension (string)
+    """
+    return "{0}{1}{2}".format(Preferences.getQt("QtToolsPrefix4"),
+                              toolname,
+                              Preferences.getQt("QtToolsPostfix4")
+                              )
+
+
+def getQtMacBundle(toolname):
+    """
+    Module function to determine the correct Mac OS X bundle name for Qt tools.
+    
+    @param toolname  plain name of the tool (e.g. "designer") (string)
+    @return bundle name of the Qt tool (string)
+    """
+    qtDir = getQtBinariesPath()
+    bundles = [
+        os.path.join(
+            qtDir, 'bin', generateQtToolName(toolname.capitalize())) + ".app",
+        os.path.join(qtDir, 'bin', generateQtToolName(toolname)) + ".app",
+        os.path.join(
+            qtDir, generateQtToolName(toolname.capitalize())) + ".app",
+        os.path.join(qtDir, generateQtToolName(toolname)) + ".app",
+    ]
+    for bundle in bundles:
+        if os.path.exists(bundle):
+            return bundle
+    return ""
+
+
+def prepareQtMacBundle(toolname, version, args):
+    """
+    Module function for starting Qt tools that are Mac OS X bundles.
+
+    @param toolname  plain name of the tool (e.g. "designer") (string)
+    @param version indication for the requested version (Qt 4) (integer)
+    @param args    name of input file for tool, if any (list of strings)
+    @return command-name and args for QProcess (tuple)
+    """
+    if version != 4:
+        return ("", [])
+    
+    fullBundle = getQtMacBundle(toolname)
+    if fullBundle == "":
+        return ("", [])
+
+    newArgs = []
+    newArgs.append("-a")
+    newArgs.append(fullBundle)
+    if args:
+        newArgs.append("--args")
+        newArgs += args
+
+    return ("open", newArgs)
+
+###############################################################################
+# PyQt utility functions below
+###############################################################################
+
+
+def generatePyQtToolPath(toolname, alternatives=None):
+    """
+    Module function to generate the executable path for a PyQt tool.
+    
+    @param toolname base name of the tool
+    @type str
+    @param alternatives list of alternative tool names to try
+    @type list of str
+    @return executable path name of the tool
+    @rtype str
+    """
+    pyqtVariant = int(toolname[-1])
+    pyqtToolsPath = getPyQtToolsPath(pyqtVariant)
+    if pyqtToolsPath:
+        exe = os.path.join(pyqtToolsPath, toolname)
+        if isWindowsPlatform():
+            exe += ".exe"
+    else:
+        if isWindowsPlatform():
+            exe = getWindowsExecutablePath(toolname)
+        else:
+            exe = toolname
+    
+    if not isinpath(exe) and alternatives:
+        ex_ = generatePyQtToolPath(alternatives[0], alternatives[1:])
+        if isinpath(ex_):
+            exe = ex_
+    
+    return exe
+
+###############################################################################
+# PySide/PySide2 utility functions below
+###############################################################################
+
+
+def generatePySideToolPath(toolname, variant="2"):
+    """
+    Module function to generate the executable path for a PySide/PySide2 tool.
+    
+    @param toolname base name of the tool
+    @type str
+    @param variant indicator for the PySide variant
+    @type str
+    @return the PySide/PySide2 tool path with extension
+    @rtype str
+    """
+    assert variant in ("1", "2")
+    
+    if isWindowsPlatform():
+        pysideInterpreters = checkPyside(variant)
+        interpreterIndex = sys.version_info[0] - 2
+        hasPyside = pysideInterpreters[interpreterIndex]
+        # if it isn't the internal interpreter, it has to be the external one
+        if not hasPyside:
+            interpreterIndex = not interpreterIndex
+        if interpreterIndex:
+            venvName = Preferences.getDebugger("Python3VirtualEnv")
+        else:
+            venvName = Preferences.getDebugger("Python2VirtualEnv")
+        interpreter = e5App().getObject("VirtualEnvManager")\
+            .getVirtualenvInterpreter(venvName)
+        prefix = os.path.dirname(interpreter)
+        if toolname in ["pyside-uic", "pyside2-uic"]:
+            return os.path.join(prefix, "Scripts", toolname + '.exe')
+        else:
+            return os.path.join(
+                prefix, "Lib", "site-packages",
+                "PySide{0}".format("" if variant == "1" else variant),
+                toolname + ".exe")
+    else:
+        if variant == "1":
+            return toolname
+        
+        elif toolname == "pyside2-uic":
+            return toolname
+        else:
+            import distutils.sysconfig
+            return os.path.join(distutils.sysconfig.get_python_lib(True),
+                                "PySide2", toolname)
+
+
+def checkPyside(variant="2"):
+    """
+    Module function to check the presence of PySide/PySide2.
+    
+    @param variant indicator for the PySide variant
+    @type str
+    @return list of two flags indicating the presence of PySide/PySide2 for
+        Python2 and PySide/PySide2 for Python3
+    @rtype tuple of (bool, bool)
+    """
+    assert variant in ("1", "2")
+    
+    pysideInformation = []
+    for venvNameKey in ["Python2VirtualEnv", "Python3VirtualEnv"]:
+        venvName = Preferences.getDebugger(venvNameKey)
+        interpreter = e5App().getObject("VirtualEnvManager")\
+            .getVirtualenvInterpreter(venvName)
+        if interpreter == "" or not isinpath(interpreter):
+            hasPyside = False
+        else:
+            hasPyside = False
+            checker = os.path.join(getConfig('ericDir'),
+                                   "Utilities", "PySideImporter.py")
+            args = [checker, "-" + variant]
+            proc = QProcess()
+            proc.setProcessChannelMode(QProcess.MergedChannels)
+            proc.start(interpreter, args)
+            finished = proc.waitForFinished(30000)
+            if finished:
+                if proc.exitCode() == 0:
+                    hasPyside = True
+        pysideInformation.append(hasPyside)
+    
+    return pysideInformation
+
+###############################################################################
+# Other utility functions below
+###############################################################################
+
+
+def generateVersionInfo(linesep='\n'):
+    """
+    Module function to generate a string with various version infos.
+    
+    @param linesep string to be used to separate lines (string)
+    @return string with version infos (string)
+    """
+    try:
+        try:
+            from PyQt5 import sip
+        except ImportError:
+            import sip
+        sip_version_str = sip.SIP_VERSION_STR
+    except (ImportError, AttributeError):
+        sip_version_str = "sip version not available"
+    
+    if sys.maxsize > 2**32:
+        sizeStr = "64-Bit"
+    else:
+        sizeStr = "32-Bit"
+    
+    info = "Version Numbers:{0}  Python {1}, {2}{3}".format(
+        linesep, sys.version.split()[0], sizeStr, linesep)
+    info += "  Qt {0}{1}  PyQt {2}{3}".format(
+        qVersion(), linesep, PYQT_VERSION_STR, linesep)
+    info += "  sip {0}{1}  QScintilla {2}{3}".format(
+        sip_version_str, linesep, QSCINTILLA_VERSION_STR, linesep)
+    if qVersionTuple() >= (5, 6, 0):
+        try:
+            from PyQt5 import QtWebEngineWidgets    # __IGNORE_WARNING__
+            from WebBrowser.Tools import WebBrowserTools
+            chromeVersion = WebBrowserTools.getWebEngineVersions()[0]
+            info += "  WebEngine {0}{1}".format(chromeVersion, linesep)
+        except ImportError:
+            pass
+    try:
+        from PyQt5.QtWebKit import qWebKitVersion
+        info += "  WebKit {0}{1}".format(qWebKitVersion(), linesep)
+    except ImportError:
+        pass
+    info += "  {0} {1}{2}".format(
+        Program, Version, linesep * 2)
+    info += "Platform: {0}{1}{2}{3}".format(
+        sys.platform, linesep, sys.version, linesep)
+    desktop = desktopName()
+    if desktop:
+        info += linesep
+        info += "Desktop: {0}{1}".format(desktop, linesep)
+    
+    return info
+
+
+def generatePluginsVersionInfo(linesep='\n'):
+    """
+    Module function to generate a string with plugins version infos.
+    
+    @param linesep string to be used to separate lines (string)
+    @return string with plugins version infos (string)
+    """
+    infoStr = ""
+    app = e5App()
+    if app is not None:
+        try:
+            pm = app.getObject("PluginManager")
+            versions = {}
+            for info in pm.getPluginInfos():
+                versions[info["module_name"]] = info["version"]
+            
+            infoStr = "Plugins Version Numbers:{0}".format(linesep)
+            for pluginModuleName in sorted(versions.keys()):
+                infoStr += "  {0} {1}{2}".format(
+                    pluginModuleName, versions[pluginModuleName], linesep)
+        except KeyError:
+            pass
+    
+    return infoStr
+
+
+def generateDistroInfo(linesep='\n'):
+    """
+    Module function to generate a string with distribution infos.
+    
+    @param linesep string to be used to separate lines (string)
+    @return string with plugins version infos (string)
+    """
+    infoStr = ""
+    if isLinuxPlatform():
+        releaseList = glob.glob("/etc/*-release")
+        if releaseList:
+            infoStr = "Distribution Info:{0}".format(linesep)
+            infoParas = []
+            for rfile in releaseList:
+                try:
+                    f = open(rfile, "r")
+                    lines = f.read().splitlines()
+                    f.close
+                except IOError:
+                    continue
+                
+                lines.insert(0, rfile)
+                infoParas.append('  ' + (linesep + '  ').join(lines))
+            infoStr += (linesep + linesep).join(infoParas)
+    
+    return infoStr
+
+
+def toBool(dataStr):
+    """
+    Module function to convert a string to a boolean value.
+    
+    @param dataStr string to be converted (string)
+    @return converted boolean value (boolean)
+    """
+    if dataStr in ["True", "true", "1", "Yes", "yes"]:
+        return True
+    elif dataStr in ["False", "false", "0", "No", "no"]:
+        return False
+    else:
+        return bool(dataStr)
+
+
+def getSysPath(interpreter):
+    """
+    Module function to get the Python path (sys.path) of a specific
+    interpreter.
+    
+    @param interpreter Python interpreter executable to get sys.path for
+    @type str
+    @return list containing sys.path of the interpreter; an empty list
+        is returned, if the interpreter is the one used to run eric itself
+    @rtype list of str
+    """
+    import json
+    
+    sysPath = []
+    
+    getSysPath = os.path.join(
+        getConfig('ericDir'), "Utilities", "GetSysPath.py")
+    args = [getSysPath]
+    proc = QProcess()
+    proc.setProcessChannelMode(QProcess.MergedChannels)
+    proc.start(interpreter, args)
+    finished = proc.waitForFinished(30000)
+    if finished:
+        if proc.exitCode() == 0:
+            text = proc.readAllStandardOutput()
+            sysPathResult = str(text, "utf-8", "replace").strip()
+            try:
+                sysPath = json.loads(sysPathResult)
+                if "" in sysPath:
+                    sysPath.remove("")
+            except (TypeError, ValueError):
+                # ignore faulty results and return empty list
+                pass
+    
+    return sysPath
+
+###############################################################################
+# posix compatibility functions below
+###############################################################################
+
+# None right now
+
+###############################################################################
+# win32 compatibility functions below
+###############################################################################
+
+
+def win32_Kill(pid):
+    """
+    Function to provide an os.kill equivalent for Win32.
+    
+    @param pid process id (integer)
+    @return result of the kill (boolean)
+    """
+    import win32api
+    handle = win32api.OpenProcess(1, 0, pid)
+    return (0 != win32api.TerminateProcess(handle, 0))
+
+
+def win32_GetUserName():
+    """
+    Function to get the user name under Win32.
+    
+    @return user name (string)
+    """
+    try:
+        import win32api
+        return win32api.GetUserName()
+    except ImportError:
+        try:
+            u = getEnvironmentEntry('USERNAME')
+        except KeyError:
+            u = getEnvironmentEntry('username', None)
+        return u
+
+
+def win32_getRealName():
+    """
+    Function to get the user's real name (aka. display name) under Win32.
+    
+    @return real name of the current user (string)
+    """
+    import ctypes
+    
+    GetUserNameEx = ctypes.windll.secur32.GetUserNameExW
+    NameDisplay = 3
+
+    size = ctypes.pointer(ctypes.c_ulong(0))
+    GetUserNameEx(NameDisplay, None, size)
+
+    nameBuffer = ctypes.create_unicode_buffer(size.contents.value)
+    GetUserNameEx(NameDisplay, nameBuffer, size)
+    return nameBuffer.value

eric ide

mercurial