"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/Utilities/__init__.py" (4 Jul 2020, 64222 Bytes) of package /linux/misc/eric6-20.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "__init__.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2003 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Package implementing various functions/classes needed everywhere within eric6.
    8 """
    9 
   10 
   11 import os
   12 import sys
   13 import codecs
   14 import re
   15 import fnmatch
   16 import glob
   17 import getpass
   18 import ctypes
   19 import subprocess           # secok
   20 
   21 
   22 def __showwarning(message, category, filename, lineno, file=None, line=""):
   23     """
   24     Module function to raise a SyntaxError for a SyntaxWarning.
   25     
   26     @param message warning object
   27     @param category type object of the warning
   28     @param filename name of the file causing the warning (string)
   29     @param lineno line number causing the warning (integer)
   30     @param file file to write the warning message to (ignored)
   31     @param line line causing the warning (ignored)
   32     @raise err exception of type SyntaxError
   33     """
   34     if category is SyntaxWarning:
   35         err = SyntaxError(str(message))
   36         err.filename = filename
   37         err.lineno = lineno
   38         raise err
   39     
   40 import warnings
   41 warnings.showwarning = __showwarning
   42 
   43 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32
   44 
   45 from PyQt5.QtCore import (
   46     QRegExp, QDir, QProcess, Qt, QByteArray, qVersion, PYQT_VERSION_STR,
   47     QCoreApplication, QCryptographicHash
   48 )
   49 from PyQt5.Qsci import QSCINTILLA_VERSION_STR, QsciScintilla
   50 
   51 # import these methods into the Utilities namespace
   52 from Globals import (  # __IGNORE_WARNING__
   53     isWindowsPlatform, isLinuxPlatform, isMacPlatform, desktopName,
   54     getConfigDir, setConfigDir, getPythonModulesDirectory,
   55     getPyQt5ModulesDirectory, getQtBinariesPath, getPyQtToolsPath,
   56     qVersionTuple)
   57 
   58 from E5Gui.E5Application import e5App
   59 
   60 from UI.Info import Program, Version
   61 
   62 import Preferences
   63 from Plugins.CheckerPlugins.SyntaxChecker.SyntaxCheck import (
   64     # __IGNORE_WARNING__
   65     normalizeCode)
   66 
   67 from eric6config import getConfig
   68 
   69 configDir = None
   70 
   71 codingBytes_regexps = [
   72     (5, re.compile(br'''coding[:=]\s*([-\w_.]+)''')),
   73     (1, re.compile(br'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')),
   74 ]
   75 coding_regexps = [
   76     (5, re.compile(r'''coding[:=]\s*([-\w_.]+)''')),
   77     (1, re.compile(r'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')),
   78 ]
   79 
   80 supportedCodecs = [
   81     'utf-8',
   82     'iso8859-1', 'iso8859-15', 'iso8859-2', 'iso8859-3',
   83     'iso8859-4', 'iso8859-5', 'iso8859-6', 'iso8859-7',
   84     'iso8859-8', 'iso8859-9', 'iso8859-10', 'iso8859-11',
   85     'iso8859-13', 'iso8859-14', 'iso8859-16', 'latin-1',
   86     'koi8-r', 'koi8-u',
   87     'utf-16', 'utf-32',
   88     'cp037', 'cp424', 'cp437', 'cp500', 'cp737', 'cp775',
   89     'cp850', 'cp852', 'cp855', 'cp856', 'cp857', 'cp860',
   90     'cp861', 'cp862', 'cp863', 'cp864', 'cp865', 'cp866',
   91     'cp869', 'cp874', 'cp875', 'cp932', 'cp949', 'cp950',
   92     'cp1006', 'cp1026', 'cp1140', 'cp1250', 'cp1251',
   93     'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256',
   94     'cp1257', 'cp1258',
   95     'gb2312', 'gb18030',
   96     'ascii'
   97 ]
   98 
   99 
  100 class CodingError(Exception):
  101     """
  102     Class implementing an exception, which is raised, if a given coding is
  103     incorrect.
  104     """
  105     def __init__(self, coding):
  106         """
  107         Constructor
  108         
  109         @param coding coding to include in the message (string)
  110         """
  111         self.errorMessage = QCoreApplication.translate(
  112             "CodingError",
  113             "The coding '{0}' is wrong for the given text.").format(coding)
  114         
  115     def __repr__(self):
  116         """
  117         Special method returning a representation of the exception.
  118         
  119         @return string representing the error message
  120         """
  121         return str(self.errorMessage)
  122         
  123     def __str__(self):
  124         """
  125         Special method returning a string representation of the exception.
  126         
  127         @return string representing the error message
  128         """
  129         return str(self.errorMessage)
  130     
  131 
  132 def get_codingBytes(text):
  133     """
  134     Function to get the coding of a bytes text.
  135     
  136     @param text bytes text to inspect (bytes)
  137     @return coding string
  138     """
  139     lines = text.splitlines()
  140     for coding in codingBytes_regexps:
  141         coding_re = coding[1]
  142         head = lines[:coding[0]]
  143         for line in head:
  144             m = coding_re.search(line)
  145             if m:
  146                 return str(m.group(1), "ascii").lower()
  147     return None
  148 
  149 
  150 def get_coding(text):
  151     """
  152     Function to get the coding of a text.
  153     
  154     @param text text to inspect (string)
  155     @return coding string
  156     """
  157     lines = text.splitlines()
  158     for coding in coding_regexps:
  159         coding_re = coding[1]
  160         head = lines[:coding[0]]
  161         for line in head:
  162             m = coding_re.search(line)
  163             if m:
  164                 return m.group(1).lower()
  165     return None
  166 
  167 
  168 def readEncodedFile(filename):
  169     """
  170     Function to read a file and decode its contents into proper text.
  171     
  172     @param filename name of the file to read (string)
  173     @return tuple of decoded text and encoding (string, string)
  174     """
  175     f = open(filename, "rb")
  176     text = f.read()
  177     f.close()
  178     return decode(text)
  179 
  180 
  181 def readEncodedFileWithHash(filename):
  182     """
  183     Function to read a file, calculate a hash value and decode its contents
  184     into proper text.
  185     
  186     @param filename name of the file to read (string)
  187     @return tuple of decoded text, encoding and hash value (string, string,
  188         string)
  189     """
  190     f = open(filename, "rb")
  191     text = f.read()
  192     f.close()
  193     hashStr = str(QCryptographicHash.hash(
  194         QByteArray(text), QCryptographicHash.Md5).toHex(), encoding="ASCII")
  195     return decode(text) + (hashStr, )
  196 
  197 
  198 def decode(text):
  199     """
  200     Function to decode some byte text into a string.
  201     
  202     @param text byte text to decode (bytes)
  203     @return tuple of decoded text and encoding (string, string)
  204     """
  205     try:
  206         if text.startswith(BOM_UTF8):
  207             # UTF-8 with BOM
  208             return str(text[len(BOM_UTF8):], 'utf-8'), 'utf-8-bom'
  209         elif text.startswith(BOM_UTF16):
  210             # UTF-16 with BOM
  211             return str(text[len(BOM_UTF16):], 'utf-16'), 'utf-16'
  212         elif text.startswith(BOM_UTF32):
  213             # UTF-32 with BOM
  214             return str(text[len(BOM_UTF32):], 'utf-32'), 'utf-32'
  215         coding = get_codingBytes(text)
  216         if coding:
  217             return str(text, coding), coding
  218     except (UnicodeError, LookupError):
  219         pass
  220     
  221     # Assume UTF-8
  222     try:
  223         return str(text, 'utf-8'), 'utf-8-guessed'
  224     except (UnicodeError, LookupError):
  225         pass
  226     
  227     guess = None
  228     if Preferences.getEditor("AdvancedEncodingDetection"):
  229         # Try the universal character encoding detector
  230         try:
  231             import ThirdParty.CharDet.chardet
  232             guess = ThirdParty.CharDet.chardet.detect(text)
  233             if (
  234                 guess and
  235                 guess['confidence'] > 0.95 and
  236                 guess['encoding'] is not None
  237             ):
  238                 codec = guess['encoding'].lower()
  239                 return str(text, codec), '{0}-guessed'.format(codec)
  240         except (UnicodeError, LookupError):
  241             pass
  242         except ImportError:
  243             pass
  244     
  245     # Try default encoding
  246     try:
  247         codec = Preferences.getEditor("DefaultEncoding")
  248         return str(text, codec), '{0}-default'.format(codec)
  249     except (UnicodeError, LookupError):
  250         pass
  251     
  252     if Preferences.getEditor("AdvancedEncodingDetection"):
  253         # Use the guessed one even if confifence level is low
  254         if guess and guess['encoding'] is not None:
  255             try:
  256                 codec = guess['encoding'].lower()
  257                 return str(text, codec), '{0}-guessed'.format(codec)
  258             except (UnicodeError, LookupError):
  259                 pass
  260     
  261     # Assume UTF-8 loosing information
  262     return str(text, "utf-8", "ignore"), 'utf-8-ignore'
  263 
  264 
  265 def readEncodedFileWithEncoding(filename, encoding):
  266     """
  267     Function to read a file and decode its contents into proper text.
  268     
  269     @param filename name of the file to read (string)
  270     @keyparam encoding encoding to be used to read the file (string)
  271     @return tuple of decoded text and encoding (string, string)
  272     """
  273     f = open(filename, "rb")
  274     text = f.read()
  275     f.close()
  276     if encoding:
  277         try:
  278             return str(text, encoding), '{0}-selected'.format(encoding)
  279         except (UnicodeError, LookupError):
  280             pass
  281         # Try default encoding
  282         try:
  283             codec = Preferences.getEditor("DefaultEncoding")
  284             return str(text, codec), '{0}-default'.format(codec)
  285         except (UnicodeError, LookupError):
  286             pass
  287         # Assume UTF-8 loosing information
  288         return str(text, "utf-8", "ignore"), 'utf-8-ignore'
  289     else:
  290         return decode(text)
  291 
  292 
  293 def writeEncodedFile(filename, text, origEncoding, forcedEncoding=""):
  294     """
  295     Function to write a file with properly encoded text.
  296     
  297     @param filename name of the file to read
  298     @type str
  299     @param text text to be written
  300     @type str
  301     @param origEncoding type of the original encoding
  302     @type str
  303     @param forcedEncoding encoding to be used for writing, if no coding
  304         line is present
  305     @type str
  306     @return encoding used for writing the file
  307     @rtype str
  308     """
  309     etext, encoding = encode(text, origEncoding, forcedEncoding=forcedEncoding)
  310     
  311     f = open(filename, "wb")
  312     f.write(etext)
  313     f.close()
  314     
  315     return encoding
  316 
  317 
  318 def encode(text, origEncoding, forcedEncoding=""):
  319     """
  320     Function to encode text into a byte text.
  321     
  322     @param text text to be encoded
  323     @type str
  324     @param origEncoding type of the original encoding
  325     @type str
  326     @param forcedEncoding encoding to be used for writing, if no coding line
  327         is present
  328     @type str
  329     @return tuple of encoded text and encoding used
  330     @rtype tuple of (bytes, str)
  331     @exception CodingError raised to indicate an invalid encoding
  332     """
  333     encoding = None
  334     if origEncoding == 'utf-8-bom':
  335         etext, encoding = BOM_UTF8 + text.encode("utf-8"), 'utf-8-bom'
  336     else:
  337         # Try declared coding spec
  338         coding = get_coding(text)
  339         if coding:
  340             try:
  341                 etext, encoding = text.encode(coding), coding
  342             except (UnicodeError, LookupError):
  343                 # Error: Declared encoding is incorrect
  344                 raise CodingError(coding)
  345         else:
  346             if forcedEncoding:
  347                 try:
  348                     etext, encoding = (
  349                         text.encode(forcedEncoding), forcedEncoding)
  350                 except (UnicodeError, LookupError):
  351                     # Error: Forced encoding is incorrect, ignore it
  352                     pass
  353             
  354             if encoding is None:
  355                 # Try the original encoding
  356                 if origEncoding and origEncoding.endswith(
  357                         ('-selected', '-default', '-guessed', '-ignore')):
  358                     coding = (
  359                         origEncoding
  360                         .replace("-selected", "")
  361                         .replace("-default", "")
  362                         .replace("-guessed", "")
  363                         .replace("-ignore", "")
  364                     )
  365                     try:
  366                         etext, encoding = text.encode(coding), coding
  367                     except (UnicodeError, LookupError):
  368                         pass
  369                 
  370                 if encoding is None:
  371                     # Try configured default
  372                     try:
  373                         codec = Preferences.getEditor("DefaultEncoding")
  374                         etext, encoding = text.encode(codec), codec
  375                     except (UnicodeError, LookupError):
  376                         pass
  377                     
  378                     if encoding is None:
  379                         # Try saving as ASCII
  380                         try:
  381                             etext, encoding = text.encode('ascii'), 'ascii'
  382                         except UnicodeError:
  383                             pass
  384                         
  385                         if encoding is None:
  386                             # Save as UTF-8 without BOM
  387                             etext, encoding = text.encode('utf-8'), 'utf-8'
  388     
  389     return etext, encoding
  390 
  391 
  392 def decodeString(text):
  393     """
  394     Function to decode a string containing Unicode encoded characters.
  395     
  396     @param text text containing encoded chars (string)
  397     @return decoded text (string)
  398     """
  399     buf = b""
  400     index = 0
  401     while index < len(text):
  402         if text[index] == "\\":
  403             qb = QByteArray.fromHex(text[index:index + 4].encode())
  404             buf += bytes(qb)
  405             index += 4
  406         else:
  407             buf += codecs.encode(text[index], "utf-8")
  408             index += 1
  409     buf = buf.replace(b"\x00", b"")
  410     return decodeBytes(buf)
  411     
  412 
  413 def decodeBytes(buffer):
  414     """
  415     Function to decode some byte text into a string.
  416     
  417     @param buffer byte buffer to decode (bytes)
  418     @return decoded text (string)
  419     """
  420     # try UTF with BOM
  421     try:
  422         if buffer.startswith(BOM_UTF8):
  423             # UTF-8 with BOM
  424             return str(buffer[len(BOM_UTF8):], encoding='utf-8')
  425         elif buffer.startswith(BOM_UTF16):
  426             # UTF-16 with BOM
  427             return str(buffer[len(BOM_UTF16):], encoding='utf-16')
  428         elif buffer.startswith(BOM_UTF32):
  429             # UTF-32 with BOM
  430             return str(buffer[len(BOM_UTF32):], encoding='utf-32')
  431     except (UnicodeError, LookupError):
  432         pass
  433     
  434     # try UTF-8
  435     try:
  436         return str(buffer, encoding="utf-8")
  437     except UnicodeError:
  438         pass
  439     
  440     # try codec detection
  441     try:
  442         import ThirdParty.CharDet.chardet
  443         guess = ThirdParty.CharDet.chardet.detect(buffer)
  444         if guess and guess['encoding'] is not None:
  445             codec = guess['encoding'].lower()
  446             return str(buffer, encoding=codec)
  447     except (UnicodeError, LookupError):
  448         pass
  449     except ImportError:
  450         pass
  451     
  452     return str(buffer, encoding="utf-8", errors="ignore")
  453 
  454 
  455 def readStringFromStream(stream):
  456     """
  457     Module function to read a string from the given stream.
  458     
  459     @param stream data stream opened for reading (QDataStream)
  460     @return string read from the stream (string)
  461     """
  462     data = stream.readString()
  463     if data is None:
  464         data = b""
  465     return data.decode('utf-8')
  466 
  467 
  468 _escape = re.compile("[&<>\"'\u0080-\uffff]")
  469 
  470 _escape_map = {
  471     "&": "&amp;",
  472     "<": "&lt;",
  473     ">": "&gt;",
  474     '"': "&quot;",
  475     "'": "&#x27;",
  476 }
  477 
  478 
  479 def escape_entities(m, escmap=_escape_map):
  480     """
  481     Function to encode html entities.
  482     
  483     @param m the match object
  484     @param escmap the map of entities to encode
  485     @return the converted text (string)
  486     """
  487     char = m.group()
  488     text = escmap.get(char)
  489     if text is None:
  490         text = "&#{0:d};".format(ord(char))
  491     return text
  492     
  493 
  494 def html_encode(text, pattern=_escape):
  495     """
  496     Function to correctly encode a text for html.
  497     
  498     @param text text to be encoded (string)
  499     @param pattern search pattern for text to be encoded (string)
  500     @return the encoded text (string)
  501     """
  502     if not text:
  503         return ""
  504     text = pattern.sub(escape_entities, text)
  505     return text
  506 
  507 _uescape = re.compile('[\u0080-\uffff]')
  508 
  509 
  510 def escape_uentities(m):
  511     """
  512     Function to encode html entities.
  513     
  514     @param m the match object
  515     @return the converted text (string)
  516     """
  517     char = m.group()
  518     text = "&#{0:d};".format(ord(char))
  519     return text
  520     
  521 
  522 def html_uencode(text, pattern=_uescape):
  523     """
  524     Function to correctly encode a unicode text for html.
  525     
  526     @param text text to be encoded (string)
  527     @param pattern search pattern for text to be encoded (string)
  528     @return the encoded text (string)
  529     """
  530     if not text:
  531         return ""
  532     text = pattern.sub(escape_uentities, text)
  533     return text
  534 
  535 _uunescape = re.compile(r'&#\d+;')
  536 
  537 
  538 def unescape_uentities(m):
  539     """
  540     Function to decode html entities.
  541     
  542     @param m the match object
  543     @return the converted text (string)
  544     """
  545     char = m.group()
  546     ordinal = int(char[2:-1])
  547     return chr(ordinal)
  548 
  549 
  550 def html_udecode(text, pattern=_uunescape):
  551     """
  552     Function to correctly decode a html text to a unicode text.
  553     
  554     @param text text to be decoded (string)
  555     @param pattern search pattern for text to be decoded (string)
  556     @return the decoded text (string)
  557     """
  558     if not text:
  559         return ""
  560     text = pattern.sub(unescape_uentities, text)
  561     return text
  562 
  563 
  564 def convertLineEnds(text, eol):
  565     """
  566     Function to convert the end of line characters.
  567     
  568     @param text text to be converted (string)
  569     @param eol new eol setting (string)
  570     @return text with converted eols (string)
  571     """
  572     if eol == '\r\n':
  573         regexp = re.compile(r"""(\r(?!\n)|(?<!\r)\n)""")
  574         return regexp.sub(lambda m, eol='\r\n': eol, text)
  575     elif eol == '\n':
  576         regexp = re.compile(r"""(\r\n|\r)""")
  577         return regexp.sub(lambda m, eol='\n': eol, text)
  578     elif eol == '\r':
  579         regexp = re.compile(r"""(\r\n|\n)""")
  580         return regexp.sub(lambda m, eol='\r': eol, text)
  581     else:
  582         return text
  583 
  584 
  585 def linesep():
  586     """
  587     Function to return the line separator used by the editor.
  588     
  589     @return line separator used by the editor (string)
  590     """
  591     eolMode = Preferences.getEditor("EOLMode")
  592     if eolMode == QsciScintilla.EolUnix:
  593         return "\n"
  594     elif eolMode == QsciScintilla.EolMac:
  595         return "\r"
  596     else:
  597         return "\r\n"
  598 
  599 
  600 def extractFlags(text):
  601     """
  602     Function to extract eric specific flags out of the given text.
  603     
  604     Flags are contained in comments and are introduced by 'eflag:'.
  605     The rest of the line is interpreted as 'key = value'. value is
  606     analyzed for being an integer or float value. If that fails, it
  607     is assumed to be a string. If a key does not contain a '='
  608     character, it is assumed to be a boolean flag. Flags are expected
  609     at the very end of a file. The search is ended, if a line without
  610     the 'eflag:' marker is found.
  611     
  612     @param text text to be scanned (string)
  613     @return dictionary of string, boolean, complex, float and int
  614     """
  615     flags = {}
  616     if isinstance(text, str):
  617         lines = text.rstrip().splitlines()
  618     else:
  619         lines = text
  620     for line in reversed(lines):
  621         try:
  622             index = line.index("eflag:")
  623         except ValueError:
  624             # no flag found, don't look any further
  625             break
  626         
  627         flag = line[index + 6:].strip()
  628         if "=" in flag:
  629             key, value = flag.split("=", 1)
  630             key = key.strip()
  631             value = value.strip()
  632             
  633             if value.lower() in ["true", "false", "yes", "no", "ok"]:
  634                 # it is a flag
  635                 flags[key] = value.lower() in ["true", "yes", "ok"]
  636                 continue
  637             
  638             try:
  639                 # interpret as int first
  640                 value = int(value)
  641             except ValueError:
  642                 try:
  643                     # interpret as float next
  644                     value = float(value)
  645                 except ValueError:
  646                     pass
  647             
  648             flags[key] = value
  649         else:
  650             # treat it as a boolean
  651             if flag[0] == "-":
  652                 # false flags start with '-'
  653                 flags[flag[1:]] = False
  654             else:
  655                 flags[flag] = True
  656     
  657     return flags
  658 
  659 
  660 def extractFlagsFromFile(filename):
  661     """
  662     Function to extract eric specific flags out of the given file.
  663     
  664     @param filename name of the file to be scanned (string)
  665     @return dictionary of string, boolean, complex, float and int
  666     """
  667     try:
  668         source, encoding = readEncodedFile(filename)
  669     except (UnicodeError, IOError):
  670         return {}
  671     
  672     return extractFlags(source)
  673 
  674 
  675 def extractLineFlags(line, startComment="#", endComment="", flagsLine=False):
  676     """
  677     Function to extract flags starting and ending with '__' from a line
  678     comment.
  679     
  680     @param line line to extract flags from (string)
  681     @keyparam startComment string identifying the start of the comment (string)
  682     @keyparam endComment string identifying the end of a comment (string)
  683     @keyparam flagsLine flag indicating to check for a flags only line (bool)
  684     @return list containing the extracted flags (list of strings)
  685     """
  686     flags = []
  687     
  688     if not flagsLine or (
  689        flagsLine and line.strip().startswith(startComment)):
  690         pos = line.rfind(startComment)
  691         if pos >= 0:
  692             comment = line[pos + len(startComment):].strip()
  693             if endComment:
  694                 endPos = line.rfind(endComment)
  695                 if endPos >= 0:
  696                     comment = comment[:endPos]
  697             flags = [f.strip() for f in comment.split()
  698                      if (f.startswith("__") and f.endswith("__"))]
  699     return flags
  700 
  701 
  702 def filterAnsiSequences(txt):
  703     """
  704     Function to filter out ANSI escape sequences (color only).
  705     
  706     @param txt text to be filtered
  707     @type str
  708     @return text without ANSI escape sequences
  709     @rtype str
  710     """
  711     ntxt = txt[:]
  712     while True:
  713         start = ntxt.find("\33[")    # find escape character
  714         if start == -1:
  715             break
  716         end = ntxt.find("m", start)
  717         if end == -1:
  718             break
  719         ntxt = ntxt[:start] + ntxt[end + 1:]
  720     
  721     return ntxt
  722 
  723 
  724 def toNativeSeparators(path):
  725     """
  726     Function returning a path, that is using native separator characters.
  727     
  728     @param path path to be converted (string)
  729     @return path with converted separator characters (string)
  730     """
  731     return QDir.toNativeSeparators(path)
  732     
  733 
  734 def fromNativeSeparators(path):
  735     """
  736     Function returning a path, that is using "/" separator characters.
  737     
  738     @param path path to be converted (string)
  739     @return path with converted separator characters (string)
  740     """
  741     return QDir.fromNativeSeparators(path)
  742     
  743 
  744 def normcasepath(path):
  745     """
  746     Function returning a path, that is normalized with respect to its case
  747     and references.
  748     
  749     @param path file path (string)
  750     @return case normalized path (string)
  751     """
  752     return os.path.normcase(os.path.normpath(path))
  753     
  754 
  755 def normabspath(path):
  756     """
  757     Function returning a normalized, absolute path.
  758     
  759     @param path file path (string)
  760     @return absolute, normalized path (string)
  761     """
  762     return os.path.abspath(path)
  763     
  764 
  765 def normcaseabspath(path):
  766     """
  767     Function returning an absolute path, that is normalized with respect to
  768     its case and references.
  769     
  770     @param path file path (string)
  771     @return absolute, normalized path (string)
  772     """
  773     return os.path.normcase(os.path.abspath(path))
  774     
  775 
  776 def normjoinpath(a, *p):
  777     """
  778     Function returning a normalized path of the joined parts passed into it.
  779     
  780     @param a first path to be joined (string)
  781     @param p variable number of path parts to be joind (string)
  782     @return normalized path (string)
  783     """
  784     return os.path.normpath(os.path.join(a, *p))
  785     
  786 
  787 def normabsjoinpath(a, *p):
  788     """
  789     Function returning a normalized, absolute path of the joined parts passed
  790     into it.
  791     
  792     @param a first path to be joined (string)
  793     @param p variable number of path parts to be joind (string)
  794     @return absolute, normalized path (string)
  795     """
  796     return os.path.abspath(os.path.join(a, *p))
  797     
  798 
  799 def relpath(path, start=os.path.curdir):
  800     """
  801     Return a relative version of a path.
  802     
  803     @param path path to make relative (string)
  804     @param start path to make relative from (string)
  805     @return relative path (string)
  806     @exception ValueError raised to indicate an invalid path
  807     """
  808     if not path:
  809         raise ValueError("no path specified")
  810 
  811     start_list = os.path.abspath(start).split(os.path.sep)
  812     path_list = os.path.abspath(path).split(os.path.sep)
  813 
  814     # Work out how much of the filepath is shared by start and path.
  815     i = len(os.path.commonprefix([start_list, path_list]))
  816 
  817     rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:]
  818     if not rel_list:
  819         return os.path.curdir
  820     return os.path.join(*rel_list)
  821 
  822 
  823 def isinpath(file):
  824     """
  825     Function to check for an executable file.
  826     
  827     @param file filename of the executable to check (string)
  828     @return flag to indicate, if the executable file is accessible
  829         via the searchpath defined by the PATH environment variable.
  830     """
  831     if os.path.isabs(file):
  832         return os.access(file, os.X_OK)
  833     
  834     if os.path.exists(os.path.join(os.curdir, file)):
  835         return os.access(os.path.join(os.curdir, file), os.X_OK)
  836     
  837     path = getEnvironmentEntry('PATH')
  838     
  839     # environment variable not defined
  840     if path is None:
  841         return False
  842     
  843     dirs = path.split(os.pathsep)
  844     for directory in dirs:
  845         if os.access(os.path.join(directory, file), os.X_OK):
  846             return True
  847     
  848     return False
  849 
  850 
  851 def startswithPath(path, start):
  852     """
  853     Function to check, if a path starts with a given start path.
  854     
  855     @param path path to be checked (string)
  856     @param start start path (string)
  857     @return flag indicating that the path starts with the given start
  858         path (boolean)
  859     """
  860     if start:
  861         if path == start:
  862             return True
  863         elif normcasepath(toNativeSeparators(path)).startswith(
  864                 normcasepath(toNativeSeparators(start + "/"))):
  865             return True
  866         else:
  867             return False
  868     else:
  869         return False
  870 
  871 
  872 def relativePath(path, start):
  873     """
  874     Function to convert a file path to a path relative to a start path.
  875     
  876     @param path file or directory name to convert (string)
  877     @param start start path (string)
  878     @return relative path or unchanged path, if path does not start with
  879         the start path (string)
  880     """
  881     if startswithPath(path, start):
  882         if path == start:
  883             return ""
  884         else:
  885             if start.endswith(("/", "\\")):
  886                 return path[len(start):]
  887             else:
  888                 return path[len(start) + 1:]
  889     else:
  890         return path
  891 
  892 
  893 def relativeUniversalPath(path, start):
  894     """
  895     Function to convert a file path to a path relative to a start path
  896     with universal separators.
  897     
  898     @param path file or directory name to convert (string)
  899     @param start start path (string)
  900     @return relative path or unchanged path, if path does not start with
  901         the start path with universal separators (string)
  902     """
  903     return fromNativeSeparators(relativePath(path, start))
  904 
  905 
  906 def absolutePath(path, start):
  907     """
  908     Public method to convert a path relative to a start path to an
  909     absolute path.
  910     
  911     @param path file or directory name to convert (string)
  912     @param start start path (string)
  913     @return absolute path (string)
  914     """
  915     if not os.path.isabs(path):
  916         path = os.path.join(start, path)
  917     return path
  918 
  919 
  920 def absoluteUniversalPath(path, start):
  921     """
  922     Public method to convert a path relative to a start path with
  923     universal separators to an absolute path.
  924     
  925     @param path file or directory name to convert (string)
  926     @param start start path (string)
  927     @return absolute path with native separators (string)
  928     """
  929     if not os.path.isabs(path):
  930         path = toNativeSeparators(os.path.join(start, path))
  931     return path
  932 
  933 
  934 def getExecutablePath(file):
  935     """
  936     Function to build the full path of an executable file from the environment.
  937     
  938     @param file filename of the executable to check (string)
  939     @return full executable name, if the executable file is accessible
  940         via the searchpath defined by the PATH environment variable, or an
  941         empty string otherwise.
  942     """
  943     if os.path.isabs(file):
  944         if os.access(file, os.X_OK):
  945             return file
  946         else:
  947             return ""
  948         
  949     cur_path = os.path.join(os.curdir, file)
  950     if os.path.exists(cur_path):
  951         if os.access(cur_path, os.X_OK):
  952             return cur_path
  953 
  954     path = os.getenv('PATH')
  955     
  956     # environment variable not defined
  957     if path is None:
  958         return ""
  959         
  960     dirs = path.split(os.pathsep)
  961     for directory in dirs:
  962         exe = os.path.join(directory, file)
  963         if os.access(exe, os.X_OK):
  964             return exe
  965             
  966     return ""
  967     
  968 
  969 def getExecutablePaths(file):
  970     """
  971     Function to build all full path of an executable file from the environment.
  972     
  973     @param file filename of the executable (string)
  974     @return list of full executable names (list of strings), if the executable
  975         file is accessible via the searchpath defined by the PATH environment
  976         variable, or an empty list otherwise.
  977     """
  978     paths = []
  979     
  980     if os.path.isabs(file):
  981         if os.access(file, os.X_OK):
  982             return [file]
  983         else:
  984             return []
  985         
  986     cur_path = os.path.join(os.curdir, file)
  987     if os.path.exists(cur_path):
  988         if os.access(cur_path, os.X_OK):
  989             paths.append(cur_path)
  990 
  991     path = os.getenv('PATH')
  992     
  993     # environment variable not defined
  994     if path is not None:
  995         dirs = path.split(os.pathsep)
  996         for directory in dirs:
  997             exe = os.path.join(directory, file)
  998             if os.access(exe, os.X_OK) and exe not in paths:
  999                 paths.append(exe)
 1000     
 1001     return paths
 1002     
 1003 
 1004 def getWindowsExecutablePath(file):
 1005     """
 1006     Function to build the full path of an executable file from the environment
 1007     on Windows platforms.
 1008     
 1009     First an executable with the extension .exe is searched for, thereafter
 1010     such with the extensions .cmd or .bat and finally the given file name as
 1011     is. The first match is returned.
 1012     
 1013     @param file filename of the executable to check (string)
 1014     @return full executable name, if the executable file is accessible
 1015         via the searchpath defined by the PATH environment variable, or an
 1016         empty string otherwise.
 1017     """
 1018     if os.path.isabs(file):
 1019         if os.access(file, os.X_OK):
 1020             return file
 1021         else:
 1022             return ""
 1023     
 1024     filenames = [file + ".exe", file + ".cmd", file + ".bat", file]
 1025     
 1026     for filename in filenames:
 1027         cur_path = os.path.join(os.curdir, filename)
 1028         if os.path.exists(cur_path):
 1029             if os.access(cur_path, os.X_OK):
 1030                 return os.path.abspath(cur_path)
 1031 
 1032     path = os.getenv('PATH')
 1033     
 1034     # environment variable not defined
 1035     if path is None:
 1036         return ""
 1037         
 1038     dirs = path.split(os.pathsep)
 1039     for directory in dirs:
 1040         for filename in filenames:
 1041             exe = os.path.join(directory, filename)
 1042             if os.access(exe, os.X_OK):
 1043                 return exe
 1044     
 1045     return ""
 1046     
 1047 
 1048 def isExecutable(exe):
 1049     """
 1050     Function to check, if a file is executable.
 1051     
 1052     @param exe filename of the executable to check (string)
 1053     @return flag indicating executable status (boolean)
 1054     """
 1055     return os.access(exe, os.X_OK)
 1056 
 1057 
 1058 def isDrive(path):
 1059     """
 1060     Function to check, if a path is a Windows drive.
 1061     
 1062     @param path path name to be checked
 1063     @type str
 1064     @return flag indicating a Windows drive
 1065     @rtype bool
 1066     """
 1067     isDrive = False
 1068     drive, directory = os.path.splitdrive(path)
 1069     if (
 1070         drive and
 1071         len(drive) == 2 and
 1072         drive.endswith(":") and
 1073         directory in ["", "\\", "/"]
 1074     ):
 1075         isDrive = True
 1076     
 1077     return isDrive
 1078     
 1079 
 1080 def samepath(f1, f2):
 1081     """
 1082     Function to compare two paths.
 1083     
 1084     @param f1 first path for the compare (string)
 1085     @param f2 second path for the compare (string)
 1086     @return flag indicating whether the two paths represent the
 1087         same path on disk.
 1088     """
 1089     if f1 is None or f2 is None:
 1090         return False
 1091     
 1092     if (
 1093         normcaseabspath(os.path.realpath(f1)) ==
 1094         normcaseabspath(os.path.realpath(f2))
 1095     ):
 1096         return True
 1097     
 1098     return False
 1099 
 1100 
 1101 def samefilepath(f1, f2):
 1102     """
 1103     Function to compare two paths. Strips the filename.
 1104     
 1105     @param f1 first filepath for the compare (string)
 1106     @param f2 second filepath for the compare (string)
 1107     @return flag indicating whether the two paths represent the
 1108         same path on disk.
 1109     """
 1110     if f1 is None or f2 is None:
 1111         return False
 1112     
 1113     if (normcaseabspath(os.path.dirname(os.path.realpath(f1))) ==
 1114             normcaseabspath(os.path.dirname(os.path.realpath(f2)))):
 1115         return True
 1116     
 1117     return False
 1118 
 1119 try:
 1120     EXTSEP = os.extsep
 1121 except AttributeError:
 1122     EXTSEP = "."
 1123 
 1124 
 1125 def splitPath(name):
 1126     """
 1127     Function to split a pathname into a directory part and a file part.
 1128     
 1129     @param name path name (string)
 1130     @return a tuple of 2 strings (dirname, filename).
 1131     """
 1132     if os.path.isdir(name):
 1133         dn = os.path.abspath(name)
 1134         fn = "."
 1135     else:
 1136         dn, fn = os.path.split(name)
 1137     return (dn, fn)
 1138 
 1139 
 1140 def joinext(prefix, ext):
 1141     """
 1142     Function to join a file extension to a path.
 1143     
 1144     The leading "." of ext is replaced by a platform specific extension
 1145     separator if necessary.
 1146     
 1147     @param prefix the basepart of the filename (string)
 1148     @param ext the extension part (string)
 1149     @return the complete filename (string)
 1150     """
 1151     if ext[0] != ".":
 1152         ext = ".{0}".format(ext)
 1153         # require leading separator to match os.path.splitext
 1154     return prefix + EXTSEP + ext[1:]
 1155 
 1156 
 1157 def compactPath(path, width, measure=len):
 1158     """
 1159     Function to return a compacted path fitting inside the given width.
 1160     
 1161     @param path path to be compacted (string)
 1162     @param width width for the compacted path (integer)
 1163     @param measure reference to a function used to measure the length of the
 1164         string
 1165     @return compacted path (string)
 1166     """
 1167     if measure(path) <= width:
 1168         return path
 1169     
 1170     ellipsis = '...'
 1171     
 1172     head, tail = os.path.split(path)
 1173     mid = len(head) // 2
 1174     head1 = head[:mid]
 1175     head2 = head[mid:]
 1176     while head1:
 1177         # head1 is same size as head2 or one shorter
 1178         path = os.path.join("{0}{1}{2}".format(head1, ellipsis, head2), tail)
 1179         if measure(path) <= width:
 1180             return path
 1181         head1 = head1[:-1]
 1182         head2 = head2[1:]
 1183     path = os.path.join(ellipsis, tail)
 1184     if measure(path) <= width:
 1185         return path
 1186     while tail:
 1187         path = "{0}{1}".format(ellipsis, tail)
 1188         if measure(path) <= width:
 1189             return path
 1190         tail = tail[1:]
 1191     return ""
 1192     
 1193 
 1194 def direntries(path, filesonly=False, pattern=None, followsymlinks=True,
 1195                checkStop=None):
 1196     """
 1197     Function returning a list of all files and directories.
 1198     
 1199     @param path root of the tree to check
 1200     @param filesonly flag indicating that only files are wanted
 1201     @param pattern a filename pattern to check against
 1202     @param followsymlinks flag indicating whether symbolic links
 1203             should be followed
 1204     @param checkStop function to be called to check for a stop
 1205     @return list of all files and directories in the tree rooted
 1206         at path. The names are expanded to start with path.
 1207     """
 1208     if filesonly:
 1209         files = []
 1210     else:
 1211         files = [path]
 1212     try:
 1213         entries = os.listdir(path)
 1214         for entry in entries:
 1215             if checkStop and checkStop():
 1216                 break
 1217             
 1218             if entry in ['.svn',
 1219                          '.hg',
 1220                          '.git',
 1221                          '.ropeproject',
 1222                          '.eric6project']:
 1223                 continue
 1224             
 1225             fentry = os.path.join(path, entry)
 1226             if (
 1227                 pattern and
 1228                 not os.path.isdir(fentry) and
 1229                 not fnmatch.fnmatch(entry, pattern)
 1230             ):
 1231                 # entry doesn't fit the given pattern
 1232                 continue
 1233                 
 1234             if os.path.isdir(fentry):
 1235                 if os.path.islink(fentry) and not followsymlinks:
 1236                     continue
 1237                 files += direntries(
 1238                     fentry, filesonly, pattern, followsymlinks, checkStop)
 1239             else:
 1240                 files.append(fentry)
 1241     except OSError:
 1242         pass
 1243     except UnicodeDecodeError:
 1244         pass
 1245     return files
 1246 
 1247 
 1248 def getDirs(path, excludeDirs):
 1249     """
 1250     Function returning a list of all directories below path.
 1251     
 1252     @param path root of the tree to check
 1253     @param excludeDirs basename of directories to ignore
 1254     @return list of all directories found
 1255     """
 1256     try:
 1257         names = os.listdir(path)
 1258     except EnvironmentError:
 1259         return []
 1260 
 1261     dirs = []
 1262     for name in names:
 1263         if (
 1264             os.path.isdir(os.path.join(path, name)) and
 1265             not os.path.islink(os.path.join(path, name))
 1266         ):
 1267             exclude = 0
 1268             for e in excludeDirs:
 1269                 if name.split(os.sep, 1)[0] == e:
 1270                     exclude = 1
 1271                     break
 1272             if not exclude:
 1273                 dirs.append(os.path.join(path, name))
 1274 
 1275     for name in dirs[:]:
 1276         if not os.path.islink(name):
 1277             dirs = dirs + getDirs(name, excludeDirs)
 1278 
 1279     return dirs
 1280 
 1281 
 1282 def findVolume(volumeName):
 1283     """
 1284     Function to find the directory belonging to a given volume name.
 1285     
 1286     @param volumeName name of the volume to search for
 1287     @type str
 1288     @return directory path of the given volume name
 1289     @rtype str
 1290     """
 1291     volumeDirectory = None
 1292     
 1293     if isWindowsPlatform():
 1294         # we are on a Windows platform
 1295         def getVolumeName(diskName):
 1296             """
 1297             Local function to determine the volume of a disk or device.
 1298             
 1299             Each disk or external device connected to windows has an
 1300             attribute called "volume name". This function returns the
 1301             volume name for the given disk/device.
 1302 
 1303             Code from http://stackoverflow.com/a/12056414
 1304             """
 1305             volumeNameBuffer = ctypes.create_unicode_buffer(1024)
 1306             ctypes.windll.kernel32.GetVolumeInformationW(
 1307                 ctypes.c_wchar_p(diskName), volumeNameBuffer,
 1308                 ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0)
 1309             return volumeNameBuffer.value
 1310         
 1311         #
 1312         # In certain circumstances, volumes are allocated to USB
 1313         # storage devices which cause a Windows popup to raise if their
 1314         # volume contains no media. Wrapping the check in SetErrorMode
 1315         # with SEM_FAILCRITICALERRORS (1) prevents this popup.
 1316         #
 1317         oldMode = ctypes.windll.kernel32.SetErrorMode(1)
 1318         try:
 1319             for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
 1320                 dirpath = "{0}:\\".format(disk)
 1321                 if (os.path.exists(dirpath) and
 1322                         getVolumeName(dirpath) == volumeName):
 1323                     volumeDirectory = dirpath
 1324                     break
 1325         finally:
 1326             ctypes.windll.kernel32.SetErrorMode(oldMode)
 1327     else:
 1328         # we are on a Linux or macOS platform
 1329         for mountCommand in ["mount", "/sbin/mount", "/usr/sbin/mount"]:
 1330             try:
 1331                 mountOutput = (
 1332                     subprocess.check_output(mountCommand).splitlines()  # secok
 1333                 )
 1334                 mountedVolumes = [x.split()[2] for x in mountOutput]
 1335                 for volume in mountedVolumes:
 1336                     if volume.decode("utf-8").endswith(volumeName):
 1337                         volumeDirectory = volume.decode("utf-8")
 1338                         break
 1339                 if volumeDirectory:
 1340                     break
 1341             except FileNotFoundError:
 1342                 pass
 1343     
 1344     return volumeDirectory
 1345 
 1346 
 1347 def getTestFileName(fn):
 1348     """
 1349     Function to build the filename of a unittest file.
 1350     
 1351     The filename for the unittest file is built by prepending
 1352     the string "test" to the filename passed into this function.
 1353     
 1354     @param fn filename basis to be used for the unittest filename (string)
 1355     @return filename of the corresponding unittest file (string)
 1356     """
 1357     dn, fn = os.path.split(fn)
 1358     return os.path.join(dn, "test{0}".format(fn))
 1359 
 1360 
 1361 def parseOptionString(s):
 1362     """
 1363     Function used to convert an option string into a list of options.
 1364     
 1365     @param s option string (string or string)
 1366     @return list of options (list of strings)
 1367     """
 1368     rx = QRegExp(r"""\s([\w=/-]*"[^"]+"|[\w=/-]*'[^']+'|[^\s]+)""")
 1369     s = re.sub(r"%[A-Z%]", _percentReplacementFunc, s)
 1370     return parseString(s, rx)
 1371     
 1372 
 1373 def parseEnvironmentString(s):
 1374     """
 1375     Function used to convert an environment string into a list of environment
 1376     settings.
 1377     
 1378     @param s environment string (string)
 1379     @return list of environment settings (list of strings)
 1380     """
 1381     rx = QRegExp(r"""\s(\w+\+?=[^\s]+|\w+="[^"]+"|\w+='[^']+')""")
 1382     return parseString(s, rx)
 1383 
 1384 
 1385 def parseString(s, rx):
 1386     """
 1387     Function used to convert a string into a list.
 1388     
 1389     @param s string to be parsed (string)
 1390     @param rx regex defining the parse pattern (QRegExp)
 1391     @return list of parsed data (list of strings)
 1392     """
 1393     olist = []
 1394     if not s.startswith(' '):
 1395         # prepare the  string to fit our pattern
 1396         s = ' ' + s
 1397         
 1398     pos = rx.indexIn(s)
 1399     while pos != -1:
 1400         cs = rx.cap(1)
 1401         if cs.startswith('"') or cs.startswith("'"):
 1402             cs = cs[1:-1]
 1403         olist.append(cs)
 1404         pos += rx.matchedLength()
 1405         pos = rx.indexIn(s, pos)
 1406         
 1407     return olist
 1408 
 1409 
 1410 def _percentReplacementFunc(matchobj):
 1411     """
 1412     Protected function called for replacing % codes.
 1413     
 1414     @param matchobj matchobject for the code
 1415     @return replacement string
 1416     """
 1417     return getPercentReplacement(matchobj.group(0))
 1418     
 1419 
 1420 def getPercentReplacement(code):
 1421     """
 1422     Function to get the replacement for code.
 1423     
 1424     @param code code indicator (string)
 1425     @return replacement string (string)
 1426     """
 1427     if code in ["C", "%C"]:
 1428         # column of the cursor of the current editor
 1429         aw = e5App().getObject("ViewManager").activeWindow()
 1430         if aw is None:
 1431             column = -1
 1432         else:
 1433             column = aw.getCursorPosition()[1]
 1434         return "{0:d}".format(column)
 1435     elif code in ["D", "%D"]:
 1436         # directory of active editor
 1437         aw = e5App().getObject("ViewManager").activeWindow()
 1438         if aw is None:
 1439             dn = "not_available"
 1440         else:
 1441             fn = aw.getFileName()
 1442             if fn is None:
 1443                 dn = "not_available"
 1444             else:
 1445                 dn = os.path.dirname(fn)
 1446         return dn
 1447     elif code in ["F", "%F"]:
 1448         # filename (complete) of active editor
 1449         aw = e5App().getObject("ViewManager").activeWindow()
 1450         if aw is None:
 1451             fn = "not_available"
 1452         else:
 1453             fn = aw.getFileName()
 1454             if fn is None:
 1455                 fn = "not_available"
 1456         return fn
 1457     elif code in ["H", "%H"]:
 1458         # home directory
 1459         return getHomeDir()
 1460     elif code in ["L", "%L"]:
 1461         # line of the cursor of the current editor
 1462         aw = e5App().getObject("ViewManager").activeWindow()
 1463         if aw is None:
 1464             line = 0
 1465         else:
 1466             line = aw.getCursorPosition()[0] + 1
 1467         return "{0:d}".format(line)
 1468     elif code in ["P", "%P"]:
 1469         # project path
 1470         projectPath = e5App().getObject("Project").getProjectPath()
 1471         if not projectPath:
 1472             projectPath = "not_available"
 1473         return projectPath
 1474     elif code in ["S", "%S"]:
 1475         # selected text of the current editor
 1476         aw = e5App().getObject("ViewManager").activeWindow()
 1477         if aw is None:
 1478             text = "not_available"
 1479         else:
 1480             text = aw.selectedText()
 1481         return text
 1482     elif code in ["U", "%U"]:
 1483         # username
 1484         un = getUserName()
 1485         if un is None:
 1486             return code
 1487         else:
 1488             return un
 1489     elif code in ["%", "%%"]:
 1490         # the percent sign
 1491         return "%"
 1492     else:
 1493         # unknown code, just return it
 1494         return code
 1495     
 1496 
 1497 def getPercentReplacementHelp():
 1498     """
 1499     Function to get the help text for the supported %-codes.
 1500     
 1501     @returns help text (string)
 1502     """
 1503     return QCoreApplication.translate(
 1504         "Utilities",
 1505         """<p>You may use %-codes as placeholders in the string."""
 1506         """ Supported codes are:"""
 1507         """<table>"""
 1508         """<tr><td>%C</td><td>column of the cursor of the current editor"""
 1509         """</td></tr>"""
 1510         """<tr><td>%D</td><td>directory of the current editor</td></tr>"""
 1511         """<tr><td>%F</td><td>filename of the current editor</td></tr>"""
 1512         """<tr><td>%H</td><td>home directory of the current user</td></tr>"""
 1513         """<tr><td>%L</td><td>line of the cursor of the current editor"""
 1514         """</td></tr>"""
 1515         """<tr><td>%P</td><td>path of the current project</td></tr>"""
 1516         """<tr><td>%S</td><td>selected text of the current editor</td></tr>"""
 1517         """<tr><td>%U</td><td>username of the current user</td></tr>"""
 1518         """<tr><td>%%</td><td>the percent sign</td></tr>"""
 1519         """</table>"""
 1520         """</p>""")
 1521 
 1522 
 1523 def getUserName():
 1524     """
 1525     Function to get the user name.
 1526     
 1527     @return user name (string)
 1528     """
 1529     user = getpass.getuser()
 1530     
 1531     if isWindowsPlatform():
 1532         if not user:
 1533             return win32_GetUserName()
 1534     
 1535     return user
 1536 
 1537 
 1538 def getRealName():
 1539     """
 1540     Function to get the real name of the user.
 1541     
 1542     @return real name of the user (string)
 1543     """
 1544     if isWindowsPlatform():
 1545         return win32_getRealName()
 1546     else:
 1547         import pwd
 1548         user = getpass.getuser()
 1549         return pwd.getpwnam(user).pw_gecos
 1550 
 1551 
 1552 def getHomeDir():
 1553     """
 1554     Function to get a users home directory.
 1555     
 1556     @return home directory (string)
 1557     """
 1558     return QDir.homePath()
 1559     
 1560 
 1561 def getPythonLibPath():
 1562     """
 1563     Function to determine the path to Python's library.
 1564     
 1565     @return path to the Python library (string)
 1566     """
 1567     pyFullVers = sys.version.split()[0]
 1568 
 1569     vl = re.findall("[0-9.]*", pyFullVers)[0].split(".")
 1570     major = vl[0]
 1571     minor = vl[1]
 1572 
 1573     pyVers = major + "." + minor
 1574 
 1575     if isWindowsPlatform():
 1576         libDir = sys.prefix + "\\Lib"
 1577     else:
 1578         try:
 1579             syslib = sys.lib
 1580         except AttributeError:
 1581             syslib = "lib"
 1582         libDir = sys.prefix + "/" + syslib + "/python" + pyVers
 1583         
 1584     return libDir
 1585     
 1586 
 1587 def getPythonVersion():
 1588     """
 1589     Function to get the Python version (major, minor) as an integer value.
 1590     
 1591     @return An integer representing major and minor version number (integer)
 1592     """
 1593     return sys.hexversion >> 16
 1594 
 1595 
 1596 def determinePythonVersion(filename, source, editor=None):
 1597     """
 1598     Function to determine the python version of a given file.
 1599     
 1600     @param filename name of the file with extension (str)
 1601     @param source of the file (str)
 1602     @keyparam editor reference to the editor, if the file is opened
 1603         already (Editor object)
 1604     @return Python version if file is Python3 (int)
 1605     """
 1606     pyAssignment = {
 1607         "Python3": 3,
 1608         "MicroPython": 3,
 1609     }
 1610     
 1611     if not editor:
 1612         viewManager = e5App().getObject('ViewManager')
 1613         editor = viewManager.getOpenEditor(filename)
 1614     
 1615     # Maybe the user has changed the language
 1616     if editor and editor.getFileType() in pyAssignment:
 1617         return pyAssignment[editor.getFileType()]
 1618 
 1619     pyVer = 0
 1620     if filename:
 1621         if not source:
 1622             source = readEncodedFile(filename)[0]
 1623         flags = extractFlags(source)
 1624         ext = os.path.splitext(filename)[1]
 1625         py3Ext = Preferences.getPython("Python3Extensions")
 1626         project = e5App().getObject('Project')
 1627         basename = os.path.basename(filename)
 1628         
 1629         if "FileType" in flags:
 1630             pyVer = pyAssignment.get(flags["FileType"], 0)
 1631         elif project.isOpen() and project.isProjectFile(filename):
 1632             language = project.getEditorLexerAssoc(basename)
 1633             if not language:
 1634                 language = Preferences.getEditorLexerAssoc(basename)
 1635             if language == 'Python3':
 1636                 pyVer = pyAssignment[language]
 1637         
 1638         if pyVer:
 1639             # Skip the next tests
 1640             pass
 1641         elif (Preferences.getProject("DeterminePyFromProject") and
 1642               project.isOpen() and
 1643               project.isProjectFile(filename) and
 1644               ext in py3Ext):
 1645             pyVer = pyAssignment.get(project.getProjectLanguage(), 0)
 1646         elif ext in py3Ext:
 1647             pyVer = 3
 1648         elif source:
 1649             if isinstance(source, str):
 1650                 line0 = source.splitlines()[0]
 1651             else:
 1652                 line0 = source[0]
 1653             if line0.startswith("#!"):
 1654                 if "python3" in line0:
 1655                     pyVer = 3
 1656                 elif "python" in line0:
 1657                     pyVer = 3
 1658         
 1659         if pyVer == 0 and ext in py3Ext:
 1660             pyVer = 3
 1661     
 1662     return pyVer
 1663 
 1664 
 1665 ###############################################################################
 1666 # functions for environment handling
 1667 ###############################################################################
 1668 
 1669 
 1670 def getEnvironmentEntry(key, default=None):
 1671     """
 1672     Module function to get an environment entry.
 1673     
 1674     @param key key of the requested environment entry (string)
 1675     @param default value to be returned, if the environment doesn't contain
 1676         the requested entry (string)
 1677     @return the requested entry or the default value, if the entry wasn't
 1678         found (string or None)
 1679     """
 1680     filterRe = QRegExp("^{0}[ \t]*=".format(key))
 1681     if isWindowsPlatform():
 1682         filterRe.setCaseSensitivity(Qt.CaseInsensitive)
 1683     
 1684     entries = [e for e in QProcess.systemEnvironment()
 1685                if filterRe.indexIn(e) != -1]
 1686     if not entries:
 1687         return default
 1688     
 1689     # if there are multiple entries, just consider the first one
 1690     ename, val = entries[0].split("=", 1)
 1691     return val.strip()
 1692 
 1693 
 1694 def hasEnvironmentEntry(key):
 1695     """
 1696     Module function to check, if the environment contains an entry.
 1697     
 1698     @param key key of the requested environment entry (string)
 1699     @return flag indicating the presence of the requested entry (boolean)
 1700     """
 1701     filterRe = QRegExp("^{0}[ \t]*=".format(key))
 1702     if isWindowsPlatform():
 1703         filterRe.setCaseSensitivity(Qt.CaseInsensitive)
 1704     
 1705     entries = [e for e in QProcess.systemEnvironment()
 1706                if filterRe.indexIn(e) != -1]
 1707     return len(entries) > 0
 1708 
 1709 ###############################################################################
 1710 # Qt utility functions below
 1711 ###############################################################################
 1712 
 1713 
 1714 def generateQtToolName(toolname):
 1715     """
 1716     Module function to generate the executable name for a Qt tool like
 1717     designer.
 1718     
 1719     @param toolname base name of the tool (string)
 1720     @return the Qt tool name without extension (string)
 1721     """
 1722     return "{0}{1}{2}".format(Preferences.getQt("QtToolsPrefix"),
 1723                               toolname,
 1724                               Preferences.getQt("QtToolsPostfix")
 1725                               )
 1726 
 1727 
 1728 def getQtMacBundle(toolname):
 1729     """
 1730     Module function to determine the correct Mac OS X bundle name for Qt tools.
 1731     
 1732     @param toolname  plain name of the tool (e.g. "designer") (string)
 1733     @return bundle name of the Qt tool (string)
 1734     """
 1735     qtDir = getQtBinariesPath()
 1736     bundles = [
 1737         os.path.join(
 1738             qtDir, 'bin', generateQtToolName(toolname.capitalize())) + ".app",
 1739         os.path.join(qtDir, 'bin', generateQtToolName(toolname)) + ".app",
 1740         os.path.join(
 1741             qtDir, generateQtToolName(toolname.capitalize())) + ".app",
 1742         os.path.join(qtDir, generateQtToolName(toolname)) + ".app",
 1743     ]
 1744     if toolname == "designer":
 1745         # support the standalone Qt Designer installer from
 1746         # https://build-system.fman.io/qt-designer-download
 1747         designer = "Qt Designer.app"
 1748         bundles.extend([
 1749             os.path.join(qtDir, 'bin', designer),
 1750             os.path.join(qtDir, designer),
 1751         ])
 1752     for bundle in bundles:
 1753         if os.path.exists(bundle):
 1754             return bundle
 1755     return ""
 1756 
 1757 
 1758 def prepareQtMacBundle(toolname, args):
 1759     """
 1760     Module function for starting Qt tools that are Mac OS X bundles.
 1761 
 1762     @param toolname  plain name of the tool (e.g. "designer")
 1763     @type str
 1764     @param args    name of input file for tool, if any
 1765     @type list of str
 1766     @return command-name and args for QProcess
 1767     @rtype tuple of (str, list of str)
 1768     """
 1769     fullBundle = getQtMacBundle(toolname)
 1770     if fullBundle == "":
 1771         return ("", [])
 1772 
 1773     newArgs = []
 1774     newArgs.append("-a")
 1775     newArgs.append(fullBundle)
 1776     if args:
 1777         newArgs.append("--args")
 1778         newArgs += args
 1779 
 1780     return ("open", newArgs)
 1781 
 1782 ###############################################################################
 1783 # PyQt utility functions below
 1784 ###############################################################################
 1785 
 1786 
 1787 def generatePyQtToolPath(toolname, alternatives=None):
 1788     """
 1789     Module function to generate the executable path for a PyQt tool.
 1790     
 1791     @param toolname base name of the tool
 1792     @type str
 1793     @param alternatives list of alternative tool names to try
 1794     @type list of str
 1795     @return executable path name of the tool
 1796     @rtype str
 1797     """
 1798     pyqtVariant = int(toolname[-1])
 1799     pyqtToolsPath = getPyQtToolsPath(pyqtVariant)
 1800     if pyqtToolsPath:
 1801         exe = os.path.join(pyqtToolsPath, toolname)
 1802         if isWindowsPlatform():
 1803             exe += ".exe"
 1804     else:
 1805         if isWindowsPlatform():
 1806             exe = getWindowsExecutablePath(toolname)
 1807         else:
 1808             exe = toolname
 1809     
 1810     if not isinpath(exe) and alternatives:
 1811         ex_ = generatePyQtToolPath(alternatives[0], alternatives[1:])
 1812         if isinpath(ex_):
 1813             exe = ex_
 1814     
 1815     return exe
 1816 
 1817 ###############################################################################
 1818 # PySide2 utility functions below
 1819 ###############################################################################
 1820 
 1821 
 1822 def generatePySideToolPath(toolname, variant="2"):
 1823     """
 1824     Module function to generate the executable path for a PySide2 tool.
 1825     
 1826     @param toolname base name of the tool
 1827     @type str
 1828     @param variant indicator for the PySide variant (not used)
 1829     @type str
 1830     @return the PySide2 tool path with extension
 1831     @rtype str
 1832     """
 1833     if variant == 1:
 1834         # no PySide support anymore
 1835         return ""
 1836     
 1837     if isWindowsPlatform():
 1838         hasPyside = checkPyside(variant)
 1839         if not hasPyside:
 1840             return ""
 1841         
 1842         venvName = Preferences.getDebugger("Python3VirtualEnv")
 1843         interpreter = e5App().getObject(
 1844             "VirtualEnvManager").getVirtualenvInterpreter(venvName)
 1845         prefix = os.path.dirname(interpreter)
 1846         if toolname in ["pyside2-uic"]:
 1847             return os.path.join(prefix, "Scripts", toolname + '.exe')
 1848         else:
 1849             path = os.path.join(prefix, "Scripts", toolname + '.exe')
 1850             if os.path.exists(path):
 1851                 return path
 1852             
 1853             # report it the old style
 1854             return os.path.join(
 1855                 prefix, "Lib", "site-packages",
 1856                 "PySide{0}".format(variant),
 1857                 toolname + ".exe")
 1858     else:
 1859         # step 1: check, if the user has configured a tools path
 1860         path = Preferences.getQt("PySide2ToolsDir")
 1861         if path:
 1862             return os.path.join(path, toolname)
 1863         
 1864         # step 2: determine from used Python interpreter
 1865         dirName = os.path.dirname(sys.executable)
 1866         if os.path.exists(os.path.join(dirName, toolname)):
 1867             return os.path.join(dirName, toolname)
 1868         
 1869         # step 3: if it is not 'pyside2-uic' look in the package directory
 1870         if toolname == "pyside2-uic":
 1871             return toolname
 1872         else:
 1873             import distutils.sysconfig
 1874             return os.path.join(distutils.sysconfig.get_python_lib(True),
 1875                                 "PySide2", toolname)
 1876 
 1877 
 1878 def checkPyside(variant="2"):
 1879     """
 1880     Module function to check the presence of PySide2.
 1881     
 1882     @param variant indicator for the PySide variant (not used)
 1883     @type str
 1884     @return flags indicating the presence of PySide2
 1885     @rtype bool
 1886     """
 1887     if variant == 1:
 1888         # no longer supported PySide
 1889         return [False, False]
 1890     
 1891     venvName = Preferences.getDebugger("Python3VirtualEnv")
 1892     interpreter = e5App().getObject(
 1893         "VirtualEnvManager").getVirtualenvInterpreter(venvName)
 1894     if interpreter == "" or not isinpath(interpreter):
 1895         return False
 1896     else:
 1897         checker = os.path.join(
 1898             getConfig('ericDir'), "Utilities", "PySideImporter.py")
 1899         args = [checker, "-" + variant]
 1900         proc = QProcess()
 1901         proc.setProcessChannelMode(QProcess.MergedChannels)
 1902         proc.start(interpreter, args)
 1903         finished = proc.waitForFinished(30000)
 1904         if finished:
 1905             if proc.exitCode() == 0:
 1906                 return True
 1907     
 1908     return False
 1909 
 1910 ###############################################################################
 1911 # Other utility functions below
 1912 ###############################################################################
 1913 
 1914 
 1915 def generateVersionInfo(linesep='\n'):
 1916     """
 1917     Module function to generate a string with various version infos.
 1918     
 1919     @param linesep string to be used to separate lines (string)
 1920     @return string with version infos (string)
 1921     """
 1922     try:
 1923         try:
 1924             from PyQt5 import sip
 1925         except ImportError:
 1926             import sip
 1927         sip_version_str = sip.SIP_VERSION_STR
 1928     except (ImportError, AttributeError):
 1929         sip_version_str = "sip version not available"
 1930     
 1931     if sys.maxsize > 2**32:
 1932         sizeStr = "64-Bit"
 1933     else:
 1934         sizeStr = "32-Bit"
 1935     
 1936     info = "Version Numbers:{0}  Python {1}, {2}{3}".format(
 1937         linesep, sys.version.split()[0], sizeStr, linesep)
 1938     info += "  Qt {0}{1}  PyQt {2}{3}".format(
 1939         qVersion(), linesep, PYQT_VERSION_STR, linesep)
 1940     try:
 1941         from PyQt5 import QtChart
 1942         info += "  PyQtChart {0}".format(QtChart.PYQT_CHART_VERSION_STR)
 1943     except (ImportError, AttributeError):
 1944         pass
 1945     try:
 1946         from PyQt5 import QtWebEngine
 1947         info += "  PyQtWebEngine {0}".format(
 1948             QtWebEngine.PYQT_WEBENGINE_VERSION_STR)
 1949     except (ImportError, AttributeError):
 1950         pass
 1951     info += "  QScintilla {2}{3}  sip {0}{1}".format(
 1952         sip_version_str, linesep, QSCINTILLA_VERSION_STR, linesep)
 1953     try:
 1954         from PyQt5 import QtWebEngineWidgets    # __IGNORE_WARNING__
 1955         from WebBrowser.Tools import WebBrowserTools
 1956         chromeVersion = WebBrowserTools.getWebEngineVersions()[0]
 1957         info += "  WebEngine {0}{1}".format(chromeVersion, linesep)
 1958     except ImportError:
 1959         pass
 1960     info += "  {0} {1}{2}".format(
 1961         Program, Version, linesep * 2)
 1962     info += "Platform: {0}{1}{2}{3}".format(
 1963         sys.platform, linesep, sys.version, linesep)
 1964     desktop = desktopName()
 1965     if desktop:
 1966         info += linesep
 1967         info += "Desktop: {0}{1}".format(desktop, linesep)
 1968     
 1969     return info
 1970 
 1971 
 1972 def generatePluginsVersionInfo(linesep='\n'):
 1973     """
 1974     Module function to generate a string with plugins version infos.
 1975     
 1976     @param linesep string to be used to separate lines (string)
 1977     @return string with plugins version infos (string)
 1978     """
 1979     infoStr = ""
 1980     app = e5App()
 1981     if app is not None:
 1982         try:
 1983             pm = app.getObject("PluginManager")
 1984             versions = {}
 1985             for info in pm.getPluginInfos():
 1986                 versions[info["module_name"]] = info["version"]
 1987             
 1988             infoStr = "Plugins Version Numbers:{0}".format(linesep)
 1989             for pluginModuleName in sorted(versions.keys()):
 1990                 infoStr += "  {0} {1}{2}".format(
 1991                     pluginModuleName, versions[pluginModuleName], linesep)
 1992         except KeyError:
 1993             pass
 1994     
 1995     return infoStr
 1996 
 1997 
 1998 def generateDistroInfo(linesep='\n'):
 1999     """
 2000     Module function to generate a string with distribution infos.
 2001     
 2002     @param linesep string to be used to separate lines (string)
 2003     @return string with plugins version infos (string)
 2004     """
 2005     infoStr = ""
 2006     if isLinuxPlatform():
 2007         releaseList = glob.glob("/etc/*-release")
 2008         if releaseList:
 2009             infoStr = "Distribution Info:{0}".format(linesep)
 2010             infoParas = []
 2011             for rfile in releaseList:
 2012                 try:
 2013                     f = open(rfile, "r")
 2014                     lines = f.read().splitlines()
 2015                     f.close
 2016                 except IOError:
 2017                     continue
 2018                 
 2019                 lines.insert(0, rfile)
 2020                 infoParas.append('  ' + (linesep + '  ').join(lines))
 2021             infoStr += (linesep + linesep).join(infoParas)
 2022     
 2023     return infoStr
 2024 
 2025 
 2026 def toBool(dataStr):
 2027     """
 2028     Module function to convert a string to a boolean value.
 2029     
 2030     @param dataStr string to be converted (string)
 2031     @return converted boolean value (boolean)
 2032     """
 2033     if dataStr in ["True", "true", "1", "Yes", "yes"]:
 2034         return True
 2035     elif dataStr in ["False", "false", "0", "No", "no"]:
 2036         return False
 2037     else:
 2038         return bool(dataStr)
 2039 
 2040 
 2041 def getSysPath(interpreter):
 2042     """
 2043     Module function to get the Python path (sys.path) of a specific
 2044     interpreter.
 2045     
 2046     @param interpreter Python interpreter executable to get sys.path for
 2047     @type str
 2048     @return list containing sys.path of the interpreter; an empty list
 2049         is returned, if the interpreter is the one used to run eric itself
 2050     @rtype list of str
 2051     """
 2052     import json
 2053     
 2054     sysPath = []
 2055     
 2056     getSysPath = os.path.join(
 2057         getConfig('ericDir'), "Utilities", "GetSysPath.py")
 2058     args = [getSysPath]
 2059     proc = QProcess()
 2060     proc.setProcessChannelMode(QProcess.MergedChannels)
 2061     proc.start(interpreter, args)
 2062     finished = proc.waitForFinished(30000)
 2063     if finished:
 2064         if proc.exitCode() == 0:
 2065             text = proc.readAllStandardOutput()
 2066             sysPathResult = str(text, "utf-8", "replace").strip()
 2067             try:
 2068                 sysPath = json.loads(sysPathResult)
 2069                 if "" in sysPath:
 2070                     sysPath.remove("")
 2071             except (TypeError, ValueError):
 2072                 # ignore faulty results and return empty list
 2073                 pass
 2074     
 2075     return sysPath
 2076 
 2077 ###############################################################################
 2078 # posix compatibility functions below
 2079 ###############################################################################
 2080 
 2081 # None right now
 2082 
 2083 ###############################################################################
 2084 # win32 compatibility functions below
 2085 ###############################################################################
 2086 
 2087 
 2088 def win32_Kill(pid):
 2089     """
 2090     Function to provide an os.kill equivalent for Win32.
 2091     
 2092     @param pid process id (integer)
 2093     @return result of the kill (boolean)
 2094     """
 2095     import win32api
 2096     handle = win32api.OpenProcess(1, 0, pid)
 2097     return (0 != win32api.TerminateProcess(handle, 0))
 2098 
 2099 
 2100 def win32_GetUserName():
 2101     """
 2102     Function to get the user name under Win32.
 2103     
 2104     @return user name (string)
 2105     """
 2106     try:
 2107         import win32api
 2108         return win32api.GetUserName()
 2109     except ImportError:
 2110         try:
 2111             u = getEnvironmentEntry('USERNAME')
 2112         except KeyError:
 2113             u = getEnvironmentEntry('username', None)
 2114         return u
 2115 
 2116 
 2117 def win32_getRealName():
 2118     """
 2119     Function to get the user's real name (aka. display name) under Win32.
 2120     
 2121     @return real name of the current user (string)
 2122     """
 2123     import ctypes
 2124     
 2125     GetUserNameEx = ctypes.windll.secur32.GetUserNameExW
 2126     NameDisplay = 3
 2127 
 2128     size = ctypes.pointer(ctypes.c_ulong(0))
 2129     GetUserNameEx(NameDisplay, None, size)
 2130 
 2131     nameBuffer = ctypes.create_unicode_buffer(size.contents.value)
 2132     GetUserNameEx(NameDisplay, nameBuffer, size)
 2133     return nameBuffer.value