Sat, 04 Jan 2014 22:12:42 +0100
Interface to add user-defined services, e.g. in plugins. Auto syntax check working. Little cleanup.
Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py | file | annotate | diff | comparison | revisions | |
Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py | file | annotate | diff | comparison | revisions | |
QScintilla/Editor.py | file | annotate | diff | comparison | revisions | |
UI/UserInterface.py | file | annotate | diff | comparison | revisions | |
Utilities/BackgroundClient.py | file | annotate | diff | comparison | revisions | |
Utilities/BackgroundService.py | file | annotate | diff | comparison | revisions | |
Utilities/InternalServices.py | file | annotate | diff | comparison | revisions | |
Utilities/__init__.py | file | annotate | diff | comparison | revisions | |
Utilities/py3flakes/__init__.py | file | annotate | diff | comparison | revisions | |
Utilities/py3flakes/checker.py | file | annotate | diff | comparison | revisions | |
Utilities/py3flakes/messages.py | file | annotate | diff | comparison | revisions | |
eric5.e4p | file | annotate | diff | comparison | revisions |
--- a/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py Wed Jan 01 22:59:10 2014 +0100 +++ b/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py Sat Jan 04 22:12:42 2014 +0100 @@ -2,6 +2,7 @@ # Copyright (c) 2011 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> # +# pylint: disable=C0103 """ Module implementing the syntax check for Python 2/3. @@ -12,8 +13,20 @@ import sys import traceback -from .pyflakes.checker import Checker -from .pyflakes.messages import ImportStarUsed +try: + from pyflakes.checker import Checker + from pyflakes.messages import ImportStarUsed +except ImportError: + pass + + +def initService(): + """ + Initialize the service and return the entry point. + + @return the entry point for the background client (function) + """ + return syntaxAndPyflakesCheck def normalizeCode(codestring):
--- a/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py Wed Jan 01 22:59:10 2014 +0100 +++ b/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py Sat Jan 04 22:12:42 2014 +0100 @@ -66,8 +66,8 @@ self.checkProgressLabel.setVisible(False) self.checkProgressLabel.setMaximumWidth(600) - self.backgroundService = e5App().getObject('BackgroundService') - self.backgroundService.syntaxChecked.connect(self.processResult) + self.internalServices = e5App().getObject('InternalServices') + self.internalServices.syntaxChecked.connect(self.__processResult) def __resort(self): """ @@ -191,7 +191,7 @@ QApplication.processEvents() self.__resort() - if self.cancelled: # ??? + if self.cancelled: return self.__lastFileItem = None @@ -213,14 +213,14 @@ self.check() return - self.backgroundService.syntaxCheck( + self.internalServices.syntaxCheck( self.filename, self.source, self.checkFlakes, self.ignoreStarImportWarnings) - def processResult( + def __processResult( self, fn, nok, fname, line, index, code, error, warnings): """ - Slot which reports the resulting messages. + Slot to display the reported messages. If checkFlakes is True, warnings contains a list of strings containing the warnings (marker, file name, line number, message) @@ -247,16 +247,6 @@ else: source = self.source.splitlines() for warning in warnings: - # TODO: Move to BackgroundService - # Translate messages - msg_args = warning.pop() - translated = QApplication.translate( - 'py3Flakes', warning[-1]).format(*msg_args) - # Avoid leading "u" at Python2 unicode strings - if translated.startswith("u'"): - translated = translated[1:] - warning[3] = translated.replace(" u'", " '") - self.noResults = False scr_line = source[warning[2] - 1].strip() self.__createResultItem(
--- a/QScintilla/Editor.py Wed Jan 01 22:59:10 2014 +0100 +++ b/QScintilla/Editor.py Sat Jan 04 22:12:42 2014 +0100 @@ -312,6 +312,8 @@ self.__setTextDisplay() # initialize the online syntax check timer + self.internalServices = e5App().getObject('InternalServices') + self.internalServices.syntaxChecked.connect(self.__processResult) self.__initOnlineSyntaxCheck() self.isResourcesFile = False @@ -1763,13 +1765,13 @@ if isProjectPy2: self.filetype = "Python2" return isProjectPy2 - else: - # 3) determine by compiling the sources - syntaxError = Utilities.compile( - self.fileName, self.text(), True)[0] - if not syntaxError: - self.filetype = "Python2" - return True +# else: +# # 3) determine by compiling the sources +# syntaxError = Utilities.compile( +# self.fileName, self.text(), True)[0] +# if not syntaxError: +# self.filetype = "Python2" +# return True if ext in self.dbs.getExtensions('Python2'): self.filetype = "Python2" @@ -1806,13 +1808,13 @@ if isProjectPy3: self.filetype = "Python3" return isProjectPy3 - else: - # 3) determine by compiling the sources - syntaxError = Utilities.compile( - self.fileName, self.text(), False)[0] - if not syntaxError: - self.filetype = "Python3" - return True +# else: +# # 3) determine by compiling the sources +# syntaxError = Utilities.compile( +# self.fileName, self.text(), False)[0] +# if not syntaxError: +# self.filetype = "Python3" +# return True if ext in self.dbs.getExtensions('Python3'): self.filetype = "Python3" @@ -4960,25 +4962,53 @@ """ Private method to perform an automatic syntax check of the file. """ - isPy2 = self.isPy2File() - if (isPy2 or self.isPy3File()) is False: + if (self.isPy2File() or self.isPy3File()) is False: return if Preferences.getEditor("AutoCheckSyntax"): if Preferences.getEditor("OnlineSyntaxCheck"): self.__onlineSyntaxCheckTimer.stop() - self.clearSyntaxError() - self.clearFlakesWarnings() + + checkFlakes = Preferences.getFlakes("IncludeInSyntaxCheck") + ignoreStarImportWarnings = Preferences.getFlakes( + "IgnoreStarImportWarnings") + + self.internalServices.syntaxCheck( + self.fileName or "(Unnamed)", self.text(), checkFlakes, + ignoreStarImportWarnings, editor=self) - syntaxError, _fn, errorline, errorindex, _code, _error, warnings =\ - Utilities.compile( - self.fileName or "(Unnamed)", self.text(), isPy2) - if syntaxError: - self.toggleSyntaxError(errorline, errorindex, True, _error) - else: - for warning in warnings: - self.toggleWarning( - warning[2], True, warning[3]) + def __processResult( + self, fn, nok, fname, line, index, code, error, warnings): + """ + Slot to report the resulting messages. + + If checkFlakes is True, warnings contains a list of strings containing + the warnings (marker, file name, line number, message) + The values are only valid, if nok is False. + + @param fn filename of the checked file (str) + @param nok flag if an error in the source was found (boolean) + @param fname filename of the checked file (str) # TODO: remove dubl. + @param line number where the error occured (int) + @param index the column where the error occured (int) + @param code the part of the code where the error occured (str) + @param error the name of the error (str) + @param warnings a list of strings containing the warnings + (marker, file name, line number, message) + """ + # Check if it's the requested file, otherwise ignore signal + if fn != self.fileName and ( + self.fileName is not None or fn != "(Unnamed)"): + return + + self.clearSyntaxError() + self.clearFlakesWarnings() + + if nok: + self.toggleSyntaxError(line, index, True, error) + else: + for warning in warnings: + self.toggleWarning(warning[2], True, warning[3]) def __initOnlineSyntaxCheck(self): """ @@ -5855,6 +5885,8 @@ self.breakpointModel.rowsInserted.disconnect( self.__addBreakPoints) + self.internalServices.syntaxChecked.disconnect(self.__processResult) + if self.spell: self.spell.stopIncrementalCheck()
--- a/UI/UserInterface.py Wed Jan 01 22:59:10 2014 +0100 +++ b/UI/UserInterface.py Sat Jan 04 22:12:42 2014 +0100 @@ -205,6 +205,9 @@ # Create the background service object from Utilities.BackgroundService import BackgroundService self.backgroundService = BackgroundService() + # And initialize the standard services + from Utilities.InternalServices import InternalServices + self.internalServices = InternalServices(self.backgroundService) # Generate an empty project object and multi project object from Project.Project import Project @@ -455,6 +458,7 @@ e5App().registerObject("DebugUI", self.debuggerUI) e5App().registerObject("DebugServer", debugServer) e5App().registerObject("BackgroundService", self.backgroundService) + e5App().registerObject("InternalServices", self.internalServices) e5App().registerObject("ViewManager", self.viewmanager) e5App().registerObject("Project", self.project) e5App().registerObject("ProjectBrowser", self.projectBrowser)
--- a/Utilities/BackgroundClient.py Wed Jan 01 22:59:10 2014 +0100 +++ b/Utilities/BackgroundClient.py Sat Jan 04 22:12:42 2014 +0100 @@ -2,6 +2,7 @@ # Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # +# pylint: disable=C0103 """ Module implementing a Qt free version of a background client for the various @@ -15,21 +16,11 @@ pass import json -import os import socket import struct import sys from zlib import adler32 -if __name__ == '__main__': - # Add Eric basepath to sys.path to be able to import modules which are - # laying not only below Utilities - path = os.path.dirname(sys.argv[0]) - path = os.path.dirname(path) - sys.path.append(path) - -from Plugins.CheckerPlugins.SyntaxChecker import SyntaxCheck - class BackgroundClient(object): """ @@ -42,11 +33,30 @@ @param host ip address the background service is listening @param port port of the background service """ + self.services = {} + self.connection = socket.create_connection((host, port)) ver = b'2' if sys.version_info[0] == 2 else b'3' self.connection.sendall(ver) self.connection.settimeout(0.25) + def __initClientService(self, fn, path, module): + """ + Import the given module and register it as service. + + @param fn service name to register (str) + @param path contains the path to the module (str) + @param module name to import (str) + @return text result of the import action (str) + """ + sys.path.append(path) + try: + importedModule = __import__(module, globals(), locals(), [], 0) + self.services[fn] = importedModule.initService() + return 'ok' + except ImportError: + return 'Import Error' + def __send(self, fx, fn, data): """ Private method to send a job response back to the BackgroundService. @@ -80,7 +90,6 @@ break length, datahash = struct.unpack(b'!II', header) - packedData = b'' while len(packedData) < length: packedData += self.connection.recv(length - len(packedData)) @@ -89,15 +98,16 @@ 'Hashes not equal' if sys.version_info[0] == 3: packedData = packedData.decode('utf-8') + fx, fn, data = json.loads(packedData) - if fx == 'syntax': - ret = SyntaxCheck.syntaxAndPyflakesCheck(fn, *data) - elif fx == 'style': - print(data) - elif fx == 'indent': - pass + if fx == 'INIT': + ret = self.__initClientService(fn, *data) else: - continue + callback = self.services.get(fx) + if callback: + ret = callback(fn, *data) + else: + ret = 'Unknown service.' self.__send(fx, fn, ret)
--- a/Utilities/BackgroundService.py Wed Jan 01 22:59:10 2014 +0100 +++ b/Utilities/BackgroundService.py Sat Jan 04 22:12:42 2014 +0100 @@ -2,6 +2,7 @@ # Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # +# pylint: disable=C0103 """ Module implementing a background service for the various checkers and other @@ -18,11 +19,8 @@ from zlib import adler32 from PyQt4.QtCore import QProcess, pyqtSignal -from PyQt4.QtGui import QApplication from PyQt4.QtNetwork import QTcpServer, QHostAddress -from E5Gui.E5Application import e5App - import Preferences import Utilities from Utilities.BackgroundClient import BackgroundClient @@ -34,9 +32,7 @@ """ Class implementing the main part of the background service. """ - syntaxChecked = pyqtSignal(str, bool, str, int, int, str, str, list) - #styleChecked = pyqtSignal(TBD) - #indentChecked = pyqtSignal(TBD) + serviceNotAvailable = pyqtSignal(str, str, int, str) def __init__(self): """ @@ -44,6 +40,9 @@ """ self.processes = [None, None] self.connections = [None, None] + self.isWorking = False + self.__queue = [] + self.services = {} super(BackgroundService, self).__init__() @@ -73,34 +72,6 @@ process = self.__startExternalClient(interpreter, port) self.processes[pyIdx] = process - def on_newConnection(self): - """ - Slot for new incomming connections from the clients. - """ - connection = self.nextPendingConnection() - if not connection.waitForReadyRead(1000): - return - ch = 0 if connection.read(1) == b'2' else 1 - self.connections[ch] = connection - connection.readyRead.connect( - lambda x=ch: self.__receive(x)) - - def shutdown(self): - """ - Cleanup the connections and processes when Eric is shuting down. - """ - for connection in self.connections: - if connection: - connection.close() - - for process in self.processes: - if isinstance(process, QProcess): - process.close() - process = None - elif isinstance(process, threading.Thread): - process.join(0.1) - process = None - def __startExternalClient(self, interpreter, port): """ Private method to start the background client as external process. @@ -129,35 +100,43 @@ @param port socket port to which the interpreter should connect (int) @return the thread object (Thread) or None """ - self.backgroundClient = BackgroundClient( + backgroundClient = BackgroundClient( self.hostAddress, port) - thread = threading.Thread(target=self.backgroundClient.run) + thread = threading.Thread(target=backgroundClient.run) thread.start() return thread - - # TODO: Implement a queued processing of incomming events. Dublicate file - # checks should update an older request to avoid overrun or starving of - # the check. - def __send(self, fx, fn, data, isPy3): + + def __processQueue(self): + """ + Private method to take the next service request and send it to the + client. + """ + if self.__queue and self.isWorking is False: + self.isWorking = True + fx, fn, pyVer, data = self.__queue.pop(0) + self.__send(fx, fn, pyVer, data) + + def __send(self, fx, fn, pyVer, data): """ Private method to send a job request to one of the clients. @param fx remote function name to execute (str) @param fn filename for identification (str) + @param pyVer version for the required interpreter (int) @param data function argument(s) (any basic datatype) - @param isPy3 flag for the required interpreter (boolean) """ packedData = json.dumps([fx, fn, data]) if sys.version_info[0] == 3: packedData = bytes(packedData, 'utf-8') - connection = self.connections[int(isPy3)] + connection = self.connections[pyVer - 2] if connection is None: - self.__postResult( - fx, fn, [ - True, fn, 0, 0, '', - 'No connection to Python{0} interpreter. ' - 'Check your debugger settings.'.format(int(isPy3) + 2), - []]) + if fx != 'INIT': + self.serviceNotAvailable.emit( + fx, fn, pyVer, self.trUtf8( + 'Python{0} interpreter not configured.').format(pyVer)) + # Reset flag and continue processing queue + self.isWorking = False + self.__processQueue() else: header = struct.pack( b'!II', len(packedData), adler32(packedData) & 0xffffffff) @@ -183,12 +162,9 @@ if sys.version_info[0] == 3: packedData = packedData.decode('utf-8') # "check" if is's a tuple of 3 values - try: - fx, fn, data = json.loads(packedData) - self.__postResult(fx, fn, data) - except: - pass - + fx, fn, data = json.loads(packedData) + self.__postResult(fx, fn, data) + def __postResult(self, fx, fn, data): """ Private method to emit the correspondig signal for the returned @@ -198,57 +174,96 @@ @param fn filename for identification (str) @param data function argument(s) (any basic datatype) """ - if fx == 'syntax': - self.syntaxChecked.emit(fn, *data) - elif fx == 'style': - pass - elif fx == 'indent': + if fx == 'INIT': pass elif fx == 'exception': # Call sys.excepthook(type, value, traceback) to emulate the # exception which was caught on the client - sys.excepthook(*data) - - #QApplication.translate(packedData) - - # ggf. nach Utilities verschieben - def determinePythonVersion(self, filename, source): - """ - Determine the python version of a given file. + #sys.excepthook(*data) + print(data) + else: + callback = self.services.get(fx) + if callback: + callback[2](fn, *data) - @param filename name of the file with extension (str) - @param source of the file (str) - @return flag if file is Python2 or Python3 (boolean) + self.isWorking = False + self.__processQueue() + + def enqueueRequest(self, fx, fn, pyVer, data): """ - flags = Utilities.extractFlags(source) - ext = os.path.splitext(filename)[1] - project = e5App().getObject('Project') - if "FileType" in flags: - isPy3 = flags["FileType"] not in ["Python", "Python2"] - elif (Preferences.getProject("DeterminePyFromProject") and - project.isOpen() and - project.isProjectFile(filename)): - isPy3 = project.getProjectLanguage() == "Python3" + Implement a queued processing of incomming events. + + Dublicate file checks update an older request to avoid overrun or + starving of the check. + @param fx function name of the service (str) + @param fn filename for identification (str) + @param pyVer version for the required interpreter (int) + @param data function argument(s) (any basic datatype) + """ + args = [fx, fn, pyVer, data] + if fx == 'INIT': + self.__queue.insert(0, args) else: - isPy3 = ext in Preferences.getPython("PythonExtensions") - return isPy3 - - def syntaxCheck(self, filename, source="", checkFlakes=True, - ignoreStarImportWarnings=False, isPy3=None): + for pendingArg in self.__queue: + if pendingArg[:3] == args[:3]: + pendingArg[3] = args[3] + break + else: + self.__queue.append(args) + self.__processQueue() + + def serviceConnect( + self, fx, modulepath, module, callback, onErrorCallback=None): """ - Function to compile one Python source file to Python bytecode - and to perform a pyflakes check. + Announce a new service to the background service/ client. + + @param fx function name of the service (str) + @param modulepath full path to the module (str) + @param module name to import (str) + @param callback function on service response (function) + @param onErrorCallback function if client isn't available (function) + """ + self.services[fx] = modulepath, module, callback, onErrorCallback + self.enqueueRequest('INIT', fx, 0, [modulepath, module]) + self.enqueueRequest('INIT', fx, 1, [modulepath, module]) + if onErrorCallback: + self.serviceNotAvailable.connect(onErrorCallback) + + def serviceDisconnect(self, fx): + """ + Remove the service from the service list. - @param filename source filename (string) - @keyparam source string containing the code to check (string) - @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) - @keyparam ignoreStarImportWarnings flag indicating to - ignore 'star import' warnings (boolean) - @keyparam isPy3 flag sets the interpreter to use or None for autodetect - corresponding interpreter (boolean or None) + @param fx function name of the service + """ + self.services.pop(fx, None) + + def on_newConnection(self): + """ + Slot for new incomming connections from the clients. """ - if isPy3 is None: - isPy3 = self.determinePythonVersion(filename, source) + connection = self.nextPendingConnection() + if not connection.waitForReadyRead(1000): + return + ch = 0 if connection.read(1) == b'2' else 1 + self.connections[ch] = connection + connection.readyRead.connect( + lambda x=ch: self.__receive(x)) - data = [source, checkFlakes, ignoreStarImportWarnings] - self.__send('syntax', filename, data, isPy3) + for fx, args in self.services.items(): + self.enqueueRequest('INIT', fx, ch, args[:2]) + + def shutdown(self): + """ + Cleanup the connections and processes when Eric is shuting down. + """ + for connection in self.connections: + if connection: + connection.close() + + for process in self.processes: + if isinstance(process, QProcess): + process.close() + process = None + elif isinstance(process, threading.Thread): + process.join(0.1) + process = None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Utilities/InternalServices.py Sat Jan 04 22:12:42 2014 +0100 @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# +# pylint: disable=C0103 + +""" +Module implementing a Qt free version of a background client for the various +checkers and other python interpreter dependent functions. +""" + +from __future__ import unicode_literals + +import os + +from PyQt4.QtCore import QObject, pyqtSignal +from PyQt4.QtGui import QApplication + +from eric5config import getConfig +from Utilities import determinePythonVersion + + +class InternalServices(QObject): + """ + Implement the standard services (syntax with flakes and the style check). + """ + syntaxChecked = pyqtSignal(str, bool, str, int, int, str, str, list) + #styleChecked = pyqtSignal(TBD) + #indentChecked = pyqtSignal(TBD) + + def __init__(self, backgroundService): + """ + Contructor of InternalServices. + + @param backgroundService to connect to + """ + super(InternalServices, self).__init__() + self.backgroundService = backgroundService + + path = os.path.join( + getConfig('ericDir'), 'Plugins', 'CheckerPlugins', 'SyntaxChecker') + self.backgroundService.serviceConnect( + 'syntax', path, 'SyntaxCheck', + self.__translateSyntaxCheck, + lambda fx, fn, ver, msg: self.syntaxChecked.emit( + fn, True, fn, 0, 0, '', msg, [])) + + def syntaxCheck(self, filename, source="", checkFlakes=True, + ignoreStarImportWarnings=False, pyVer=None, editor=None): + """ + Function to compile one Python source file to Python bytecode + and to perform a pyflakes check. + + @param filename source filename (string) + @keyparam source string containing the code to check (string) + @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) + @keyparam ignoreStarImportWarnings flag indicating to + ignore 'star import' warnings (boolean) + @keyparam pyVer version of the interpreter to use or None for + autodetect corresponding interpreter (int or None) + @keyparam editor if the file is opened already (Editor object) + """ + if pyVer is None: + pyVer = determinePythonVersion(filename, source, editor) + + data = [source, checkFlakes, ignoreStarImportWarnings] + self.backgroundService.enqueueRequest('syntax', filename, pyVer, data) + + def __translateSyntaxCheck( + self, fn, nok, fname, line, index, code, error, warnings): + """ + Slot to translate the resulting messages. + + If checkFlakes is True, warnings contains a list of strings containing + the warnings (marker, file name, line number, message) + The values are only valid, if nok is False. + + @param fn filename of the checked file (str) + @param nok flag if an error in the source was found (boolean) + @param fname filename of the checked file (str) # TODO: remove dubl. + @param line number where the error occured (int) + @param index the column where the error occured (int) + @param code the part of the code where the error occured (str) + @param error the name of the error (str) + @param warnings a list of strings containing the warnings + (marker, file name, line number, message) + """ + for warning in warnings: + # Translate messages + msg_args = warning.pop() + translated = QApplication.translate( + 'py3Flakes', warning[3]).format(*msg_args) + # Avoid leading "u" at Python2 unicode strings + if translated.startswith("u'"): + translated = translated[1:] + warning[3] = translated.replace(" u'", " '") + + self.syntaxChecked.emit( + fn, nok, fname, line, index, code, error, warnings)
--- a/Utilities/__init__.py Wed Jan 01 22:59:10 2014 +0100 +++ b/Utilities/__init__.py Sat Jan 04 22:12:42 2014 +0100 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2003 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# Copyright (c) 2003 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # """ @@ -70,13 +70,16 @@ from UI.Info import Program, Version import Preferences -from .SyntaxCheck import (readEncodedFile, decode, # __IGNORE_WARNING__ - extractLineFlags, normalizeCode, compile_and_check) +from Plugins.CheckerPlugins.SyntaxChecker.SyntaxCheck import normalizeCode from eric5config import getConfig configDir = None +codingBytes_regexps = [ + (2, re.compile(br'''coding[:=]\s*([-\w_.]+)''')), + (1, re.compile(br'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')), +] coding_regexps = [ (2, re.compile(r'''coding[:=]\s*([-\w_.]+)''')), (1, re.compile(r'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')), @@ -134,6 +137,24 @@ 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. @@ -152,6 +173,19 @@ 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 @@ -170,6 +204,70 @@ return decode(text) + (hash, ) +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 writeEncodedFile(filename, text, orig_coding): """ Function to write a file with properly encoded text. @@ -523,6 +621,28 @@ return extractFlags(source) +def extractLineFlags(line, startComment="#", endComment=""): + """ + 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) + @return list containing the extracted flags (list of strings) + """ + flags = [] + + pos = line.rfind(startComment) + if pos >= 0: + comment = line[pos + len(startComment):].strip() + if endComment: + comment = comment.replace("endComment", "") + flags = [f.strip() for f in comment.split() + if (f.startswith("__") and f.endswith("__"))] + return flags + + def toNativeSeparators(path): """ Function returning a path, that is using native separator characters. @@ -1173,127 +1293,49 @@ @return An integer representing major and minor version number (integer) """ return sys.hexversion >> 16 - - -def compile(file, codestring="", isPy2=False): - """ - Function to compile one Python source file to Python bytecode. - - @param file source filename (string) - @param codestring string containing the code to compile (string) - @param isPy2 shows which interperter to use (boolean) - @return A tuple indicating status (True = an error was found), the - file name, the line number, the index number, the code string - and the error message (boolean, string, string, string, string, - string). The values are only valid, if the status is True. - """ - from PyQt4.QtCore import QCoreApplication - - interpreter_name = 'Python' if isPy2 else 'Python3' - interpreter = Preferences.getDebugger( - interpreter_name + "Interpreter") - checkFlakes = Preferences.getFlakes("IncludeInSyntaxCheck") - ignoreStarImportWarnings = Preferences.getFlakes( - "IgnoreStarImportWarnings") - if samefilepath(interpreter, sys.executable): - ret = compile_and_check( - file, codestring, checkFlakes, ignoreStarImportWarnings) - else: - #TODO: create temporary file if only a codestring is given - ret = compile_extern( - file, isPy2, checkFlakes, ignoreStarImportWarnings) - - # Translate messages - for warning in ret[6]: - msg_args = warning.pop() - translated = QCoreApplication.translate( - 'py3Flakes', warning[-1]).format(*msg_args) - # Avoid leading "u" at Python2 unicode strings - if translated.startswith("u'"): - translated = translated[1:] - warning[3] = translated.replace(" u'", " '") - - return ret -def compile_extern( - file, isPy2, checkFlakes=True, ignoreStarImportWarnings=False): +def determinePythonVersion(filename, source, editor=None): """ - Function to compile one Python source file to Python bytecode. + Determine the python version of a given file. - @param file source filename (string) - @param isPy2 flag indicating if it's a Python 2 or 3 file (boolean) - @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) - @keyparam ignoreStarImportWarnings flag if star import warnings should be - suppressed (boolean) - @return A tuple indicating status (True = an error was found), the - file name, the line number, the index number, the code string, - the error message and a list of tuples of pyflakes warnings indicating - file name, line number and message (boolean, string, string, string, - string, string, list of (string, string, string)). The syntax error - values are only valid, if the status is True. The pyflakes list will - be empty, if a syntax error was detected by the syntax checker. + @param filename name of the file with extension (str) + @param source of the file (str) + @keyparam editor if the file is opened already (Editor object) + @return flag if file is Python2 or Python3 (int) """ - interpreter_name = 'Python' if isPy2 else 'Python3' - interpreter = Preferences.getDebugger(interpreter_name + "Interpreter") - if interpreter == "" or not isinpath(interpreter): - return (True, file, 1, 0, "", - QCoreApplication.translate( - "Utilities", - "{0} interpreter not configured.") - .format(interpreter_name), []) - syntaxChecker = os.path.join(getConfig('ericDir'), - "Utilities", "SyntaxCheck.py") - args = [syntaxChecker] - if checkFlakes: - if ignoreStarImportWarnings: - args.append("-fi") - else: - args.append("-fs") - args.append(file) - proc = QProcess() - proc.setProcessChannelMode(QProcess.MergedChannels) - proc.start(interpreter, args) - finished = proc.waitForFinished(30000) - if finished: - output = codecs.decode( - proc.readAllStandardOutput(), - sys.getfilesystemencoding(), 'strict').splitlines() + pyAssignment = {"Python": 2, "Python2": 2, "Python3": 3} + + flags = extractFlags(source) + ext = os.path.splitext(filename)[1] + py2Ext = Preferences.getPython("PythonExtensions") + py3Ext = Preferences.getPython("Python3Extensions") + project = e5App().getObject('Project') + + pyVer = 0 + if editor and editor.getLanguage() in pyAssignment: + pyVer = pyAssignment.get(editor.getLanguage()) + elif "FileType" in flags: + pyVer = pyAssignment.get(flags["FileType"], 0) + elif (Preferences.getProject("DeterminePyFromProject") and + project.isOpen() and + project.isProjectFile(filename)): + 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.startswith("#!"): + line0 = source.splitlines()[0] + 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] - if output: - syntaxerror = output[0] == "ERROR" - if syntaxerror: - fn = output[1] - line = int(output[2]) - index = int(output[3]) - code = output[4] - error = output[5] - return (True, fn, line, index, code, error, []) - else: - index = 6 - warnings = [] - while len(output) - index > 3: - if output[index] == "FLAKES_ERROR": - return (True, output[index + 1], - int(output[index + 2]), -1, - '', output[index + 3], []) - else: - msg_args = output[index + 4].split('#') - warnings.append([ - output[index], output[index + 1], - int(output[index + 2]), output[index + 3], - msg_args]) - index += 5 - - return (False, None, None, None, None, None, warnings) - else: - return (False, "", -1, -1, "", "", []) - - return (True, file, 1, 0, "", - QCoreApplication.translate( - "Utilities", - "{0} interpreter did not finish within 30s.").format( - interpreter_name), []) + return pyVer ###############################################################################
--- a/Utilities/py3flakes/__init__.py Wed Jan 01 22:59:10 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Package containg the pyflakes Python3 port adapted for Qt. -""" - -__version__ = '0.5.0'
--- a/Utilities/py3flakes/checker.py Wed Jan 01 22:59:10 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,680 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# -# Original (c) 2005-2010 Divmod, Inc. -# -# This module is based on pyflakes for Python2 but was heavily hacked to -# work with Python3 and eric5 - -import builtins -import os.path -import ast - -from . import messages - - -class Binding(object): - """ - Represents the binding of a value to a name. - - The checker uses this to keep track of which names have been bound and - which names have not. See Assignment for a special type of binding that - is checked with stricter rules. - """ - def __init__(self, name, source): - self.name = name - self.source = source - self.used = False - - def __str__(self): - return self.name - - def __repr__(self): - return '<{0} object {1!r} from line {2!r} at 0x{3:x}>'.format( - self.__class__.__name__, - self.name, - self.source.lineno, - id(self)) - - -class UnBinding(Binding): - ''' - Created by the 'del' operator. - ''' - pass - - -class Importation(Binding): - """ - A binding created by an import statement. - """ - def __init__(self, name, source): - self.fullName = name - name = name.split('.')[0] - super(Importation, self).__init__(name, source) - - -class Argument(Binding): - """ - Represents binding a name as an argument. - """ - pass - - -class Assignment(Binding): - """ - Represents binding a name with an explicit assignment. - - The checker will raise warnings for any Assignment that isn't used. Also, - the checker does not consider assignments in tuple/list unpacking to be - Assignments, rather it treats them as simple Bindings. - """ - pass - - -class FunctionDefinition(Binding): - """ - Represents a function definition. - """ - is_property = False - - -class ExportBinding(Binding): - """ - A binding created by an __all__ assignment. If the names in the list - can be determined statically, they will be treated as names for export and - additional checking applied to them. - - The only __all__ assignment that can be recognized is one which takes - the value of a literal list containing literal strings. For example:: - - __all__ = ["foo", "bar"] - - Names which are imported and not otherwise used but appear in the value of - __all__ will not have an unused import warning reported for them. - """ - def names(self): - """ - Return a list of the names referenced by this binding. - """ - names = [] - if isinstance(self.source, ast.List): - for node in self.source.elts: - if isinstance(node, (ast.Str, ast.Bytes)): - names.append(node.s) - elif isinstance(node, ast.Num): - names.append(node.n) - return names - - -class Scope(dict): - """ - Class defining the scope base class. - """ - importStarred = False # set to True when import * is found - - def __repr__(self): - return '<{0} at 0x{1:x} {2}>'.format( - self.__class__.__name__, id(self), dict.__repr__(self)) - - def __init__(self): - super(Scope, self).__init__() - - -class ClassScope(Scope): - """ - Class representing a name scope for a class. - """ - pass - - -class FunctionScope(Scope): - """ - Class representing a name scope for a function. - """ - def __init__(self): - super(FunctionScope, self).__init__() - self.globals = {} - - -class ModuleScope(Scope): - """ - Class representing a name scope for a module. - """ - pass - -# Globally defined names which are not attributes of the builtins module. -_MAGIC_GLOBALS = ['__file__', '__builtins__'] - - -class Checker(object): - """ - Class to check the cleanliness and sanity of Python code. - """ - nodeDepth = 0 - traceTree = False - - def __init__(self, module, filename='(none)'): - """ - Constructor - - @param module parsed module tree or module source code - @param filename name of the module file (string) - """ - self._deferredFunctions = [] - self._deferredAssignments = [] - self.dead_scopes = [] - self.messages = [] - self.filename = filename - self.scopeStack = [ModuleScope()] - self.futuresAllowed = True - - if isinstance(module, str): - module = ast.parse(module, filename, "exec") - self.handleBody(module) - self._runDeferred(self._deferredFunctions) - # Set _deferredFunctions to None so that deferFunction will fail - # noisily if called after we've run through the deferred functions. - self._deferredFunctions = None - self._runDeferred(self._deferredAssignments) - # Set _deferredAssignments to None so that deferAssignment will fail - # noisly if called after we've run through the deferred assignments. - self._deferredAssignments = None - del self.scopeStack[1:] - self.popScope() - self.check_dead_scopes() - - def deferFunction(self, callable): - ''' - Schedule a function handler to be called just before completion. - - This is used for handling function bodies, which must be deferred - because code later in the file might modify the global scope. When - `callable` is called, the scope at the time this is called will be - restored, however it will contain any new bindings added to it. - ''' - self._deferredFunctions.append((callable, self.scopeStack[:])) - - def deferAssignment(self, callable): - """ - Schedule an assignment handler to be called just after deferred - function handlers. - """ - self._deferredAssignments.append((callable, self.scopeStack[:])) - - def _runDeferred(self, deferred): - """ - Run the callables in deferred using their associated scope stack. - """ - for handler, scope in deferred: - self.scopeStack = scope - handler() - - def scope(self): - return self.scopeStack[-1] - scope = property(scope) - - def popScope(self): - self.dead_scopes.append(self.scopeStack.pop()) - - def check_dead_scopes(self): - """ - Look at scopes which have been fully examined and report names in them - which were imported but unused. - """ - for scope in self.dead_scopes: - export = isinstance(scope.get('__all__'), ExportBinding) - if export: - all = scope['__all__'].names() - if os.path.split(self.filename)[1] != '__init__.py': - # Look for possible mistakes in the export list - undefined = set(all) - set(scope) - for name in undefined: - self.report( - messages.UndefinedExport, - scope['__all__'].source.lineno, - name) - else: - all = [] - - # Look for imported names that aren't used. - for importation in scope.values(): - if isinstance(importation, Importation): - if not importation.used and importation.name not in all: - self.report( - messages.UnusedImport, - importation.source.lineno, - importation.name) - - def pushFunctionScope(self): - self.scopeStack.append(FunctionScope()) - - def pushClassScope(self): - self.scopeStack.append(ClassScope()) - - def report(self, messageClass, *args, **kwargs): - self.messages.append(messageClass(self.filename, *args, **kwargs)) - - def handleBody(self, tree): - for node in tree.body: - self.handleNode(node, tree) - - def handleChildren(self, tree): - for node in ast.iter_child_nodes(tree): - self.handleNode(node, tree) - - def isDocstring(self, node): - """ - Determine if the given node is a docstring, as long as it is at the - correct place in the node tree. - """ - return isinstance(node, ast.Str) or \ - (isinstance(node, ast.Expr) and - isinstance(node.value, ast.Str)) - - def handleNode(self, node, parent): - if node: - node.parent = parent - if self.traceTree: - print(' ' * self.nodeDepth + node.__class__.__name__) - self.nodeDepth += 1 - if self.futuresAllowed and \ - not (isinstance(node, ast.ImportFrom) or - self.isDocstring(node)): - self.futuresAllowed = False - nodeType = node.__class__.__name__.upper() - try: - handler = getattr(self, nodeType) - handler(node) - except AttributeError: - print(nodeType, "not supported yet. Please report this.") - finally: - self.nodeDepth -= 1 - if self.traceTree: - print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) - - def ignore(self, node): - pass - - # ast nodes to be ignored - PASS = CONTINUE = BREAK = ELLIPSIS = NUM = STR = BYTES = \ - LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = \ - ATTRIBUTES = AND = OR = ADD = SUB = MULT = DIV = \ - MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = \ - INVERT = NOT = UADD = USUB = EQ = NOTEQ = LT = LTE = GT = GTE = IS = \ - ISNOT = IN = NOTIN = ignore - - # "stmt" type nodes - RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \ - TRY = TRYEXCEPT = TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren - - # "expr" type nodes - BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ - CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren - - # "slice" type nodes - SLICE = EXTSLICE = INDEX = handleChildren - - # additional node types - COMPREHENSION = KEYWORD = handleChildren - - def addBinding(self, lineno, value, reportRedef=True): - ''' - Called when a binding is altered. - - @param lineno line of the statement responsible for the change - (integer) - @param value the optional new value, a Binding instance, associated - with the binding; if None, the binding is deleted if it exists - @param reportRedef flag indicating if rebinding while unused will be - reported (boolean) - ''' - if (isinstance(self.scope.get(value.name), FunctionDefinition) - and isinstance(value, FunctionDefinition) - and not self.scope.get(value.name).is_property - and not value.is_property): - self.report(messages.RedefinedFunction, - lineno, value.name, - self.scope[value.name].source.lineno) - - if not isinstance(self.scope, ClassScope): - for scope in self.scopeStack[::-1]: - existing = scope.get(value.name) - if isinstance(existing, Importation) and \ - not existing.used and \ - not isinstance(value, UnBinding) and \ - (not isinstance(value, Importation) or \ - value.fullName == existing.fullName) and \ - reportRedef: - self.report(messages.RedefinedWhileUnused, - lineno, value.name, - scope[value.name].source.lineno) - - if isinstance(value, UnBinding): - try: - del self.scope[value.name] - except KeyError: - self.report(messages.UndefinedName, lineno, value.name) - else: - self.scope[value.name] = value - - ############################################################ - ## individual handler methods below - ############################################################ - - def GLOBAL(self, node): - """ - Keep track of globals declarations. - """ - if isinstance(self.scope, FunctionScope): - self.scope.globals.update(dict.fromkeys(node.names)) - - NONLOCAL = GLOBAL - - def LISTCOMP(self, node): - for generator in node.generators: - self.handleNode(generator, node) - self.handleNode(node.elt, node) - - SETCOMP = GENERATOREXP = LISTCOMP - - def DICTCOMP(self, node): - for generator in node.generators: - self.handleNode(generator, node) - self.handleNode(node.key, node) - self.handleNode(node.value, node) - - def FOR(self, node): - """ - Process bindings for loop variables. - """ - vars = [] - - def collectLoopVars(n): - if isinstance(n, ast.Name): - vars.append(n.id) - elif isinstance(n, ast.expr_context): - return - else: - for c in ast.iter_child_nodes(n): - collectLoopVars(c) - - collectLoopVars(node.target) - for varn in vars: - if (isinstance(self.scope.get(varn), Importation) - # unused ones will get an unused import warning - and self.scope[varn].used): - self.report(messages.ImportShadowedByLoopVar, - node.lineno, varn, self.scope[varn].source.lineno) - - self.handleChildren(node) - - def NAME(self, node): - """ - Handle occurrence of Name (which can be a load/store/delete access.) - """ - # Locate the name in locals / function / globals scopes. - if isinstance(node.ctx, (ast.Load, ast.AugLoad)): - # try local scope - importStarred = self.scope.importStarred - try: - self.scope[node.id].used = (self.scope, node.lineno) - except KeyError: - pass - else: - return - - # try enclosing function scopes - for scope in self.scopeStack[-2:0:-1]: - importStarred = importStarred or scope.importStarred - if not isinstance(scope, FunctionScope): - continue - try: - scope[node.id].used = (self.scope, node.lineno) - except KeyError: - pass - else: - return - - # try global scope - importStarred = importStarred or self.scopeStack[0].importStarred - try: - self.scopeStack[0][node.id].used = (self.scope, node.lineno) - except KeyError: - if ((not hasattr(builtins, node.id)) - and node.id not in _MAGIC_GLOBALS - and not importStarred): - if (os.path.basename(self.filename) == '__init__.py' and - node.id == '__path__'): - # the special name __path__ is valid only in packages - pass - else: - self.report(messages.UndefinedName, - node.lineno, node.id) - elif isinstance(node.ctx, (ast.Store, ast.AugStore)): - # if the name hasn't already been defined in the current scope - if isinstance(self.scope, FunctionScope) and \ - node.id not in self.scope: - # for each function or module scope above us - for scope in self.scopeStack[:-1]: - if not isinstance(scope, (FunctionScope, ModuleScope)): - continue - # if the name was defined in that scope, and the name has - # been accessed already in the current scope, and hasn't - # been declared global - if (node.id in scope - and scope[node.id].used - and scope[node.id].used[0] is self.scope - and node.id not in self.scope.globals): - # then it's probably a mistake - self.report(messages.UndefinedLocal, - scope[node.id].used[1], - node.id, - scope[node.id].source.lineno) - break - - if isinstance(node.parent, - (ast.For, ast.comprehension, ast.Tuple, ast.List)): - binding = Binding(node.id, node) - elif (node.id == '__all__' and - isinstance(self.scope, ModuleScope)): - binding = ExportBinding(node.id, node.parent.value) - else: - binding = Assignment(node.id, node) - if node.id in self.scope: - binding.used = self.scope[node.id].used - self.addBinding(node.lineno, binding) - elif isinstance(node.ctx, ast.Del): - if isinstance(self.scope, FunctionScope) and \ - node.id in self.scope.globals: - del self.scope.globals[node.id] - else: - self.addBinding(node.lineno, UnBinding(node.id, node)) - else: - # must be a Param context -- this only happens for names in - # function arguments, but these aren't dispatched through here - raise RuntimeError( - "Got impossible expression context: {0:r}".format(node.ctx,)) - - def FUNCTIONDEF(self, node): - is_property = False - if hasattr(node, "decorator_list"): - for decorator in node.decorator_list: - self.handleNode(decorator, node) - if getattr(decorator, 'id', None) == 'property': - is_property = True - if getattr(decorator, 'attr', None) in ('setter', 'deleter'): - is_property = True - funcdef = FunctionDefinition(node.name, node) - funcdef.is_property = is_property - self.addBinding(node.lineno, funcdef) - self.LAMBDA(node) - - def LAMBDA(self, node): - for default in node.args.defaults + node.args.kw_defaults: - self.handleNode(default, node) - - def runFunction(): - args = [] - - def addArgs(arglist): - for arg in arglist: - if isinstance(arg.arg, tuple): - addArgs(arg.arg) - else: - if arg.arg in args: - self.report(messages.DuplicateArgument, - node.lineno, arg.arg) - args.append(arg.arg) - - def checkUnusedAssignments(): - """ - Check to see if any assignments have not been used. - """ - for name, binding in self.scope.items(): - if (not binding.used and not name in self.scope.globals - and isinstance(binding, Assignment)): - self.report(messages.UnusedVariable, - binding.source.lineno, name) - - self.pushFunctionScope() - addArgs(node.args.args) - addArgs(node.args.kwonlyargs) - # vararg/kwarg identifiers are not Name nodes - if node.args.vararg: - args.append(node.args.vararg) - if node.args.kwarg: - args.append(node.args.kwarg) - for name in args: - self.addBinding(node.lineno, Argument(name, node), - reportRedef=False) - if isinstance(node.body, list): - self.handleBody(node) - else: - self.handleNode(node.body, node) - self.deferAssignment(checkUnusedAssignments) - self.popScope() - - self.deferFunction(runFunction) - - def CLASSDEF(self, node): - """ - Check names used in a class definition, including its decorators, base - classes, and the body of its definition. Additionally, add its name to - the current scope. - """ - for decorator in getattr(node, "decorator_list", []): - self.handleNode(decorator, node) - for baseNode in node.bases: - self.handleNode(baseNode, node) - self.addBinding(node.lineno, Binding(node.name, node)) - self.pushClassScope() - self.handleBody(node) - self.popScope() - - def handleAssignName(self, node): - # special handling for ast.Subscript and ast.Starred - if isinstance(node, (ast.Subscript, ast.Starred)): - node.value.parent = node - self.handleAssignName(node.value) - if isinstance(node, ast.Subscript): - if isinstance(node.slice, ast.Slice): - self.handleNode(node.slice.lower, node) - self.handleNode(node.slice.upper, node) - else: - self.handleNode(node.slice.value, node) - return - - # if the name hasn't already been defined in the current scope - if isinstance(node, (ast.Tuple, ast.List)): - for elt in node.elts: - elt.parent = node - self.handleAssignName(elt) - return - - if isinstance(node, ast.Attribute): - self.handleNode(node.value, node) - return - - if isinstance(self.scope, FunctionScope) and node.id not in self.scope: - # for each function or module scope above us - for scope in self.scopeStack[:-1]: - if not isinstance(scope, (FunctionScope, ModuleScope)): - continue - # if the name was defined in that scope, and the name has - # been accessed already in the current scope, and hasn't - # been declared global - if (node.id in scope - and scope[node.id].used - and scope[node.id].used[0] is self.scope - and node.id not in self.scope.globals): - # then it's probably a mistake - self.report(messages.UndefinedLocal, - scope[node.id].used[1], - node.id, - scope[node.id].source.lineno) - break - - if isinstance(node.parent, - (ast.For, ast.ListComp, ast.GeneratorExp, - ast.Tuple, ast.List)): - binding = Binding(node.id, node) - elif (node.id == '__all__' and - isinstance(self.scope, ModuleScope) and - isinstance(node.parent, ast.Assign)): - binding = ExportBinding(node.id, node.parent.value) - else: - binding = Assignment(node.id, node) - if node.id in self.scope: - binding.used = self.scope[node.id].used - self.addBinding(node.lineno, binding) - - def ASSIGN(self, node): - self.handleNode(node.value, node) - for target in node.targets: - self.handleNode(target, node) - - def AUGASSIGN(self, node): - # AugAssign is awkward: must set the context explicitly and - # visit twice, once with AugLoad context, once with AugStore context - node.target.ctx = ast.AugLoad() - self.handleNode(node.target, node) - self.handleNode(node.value, node) - node.target.ctx = ast.AugStore() - self.handleNode(node.target, node) - - def IMPORT(self, node): - for alias in node.names: - name = alias.asname or alias.name - importation = Importation(name, node) - self.addBinding(node.lineno, importation) - - def IMPORTFROM(self, node): - if node.module == '__future__': - if not self.futuresAllowed: - self.report(messages.LateFutureImport, node.lineno, - [n.name for n in node.names]) - else: - self.futuresAllowed = False - - for alias in node.names: - if alias.name == '*': - self.scope.importStarred = True - self.report(messages.ImportStarUsed, node.lineno, node.module) - continue - name = alias.asname or alias.name - importation = Importation(name, node) - if node.module == '__future__': - importation.used = (self.scope, node.lineno) - self.addBinding(node.lineno, importation) - - def EXCEPTHANDLER(self, node): - node.type and self.handleNode(node.type, node) - if node.name: - node.id = node.name - self.handleAssignName(node) - self.handleBody(node) - - def STARRED(self, node): - self.handleNode(node.value, node)
--- a/Utilities/py3flakes/messages.py Wed Jan 01 22:59:10 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,287 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# -# Original (c) 2005 Divmod, Inc. See LICENSE file for details -# -# This module is based on pyflakes for Python2 but was heavily hacked to -# work with Python3 and Qt (translatable messages) - -""" -Module implementing the messages for py3flakes. -""" - - -def QT_TRANSLATE_NOOP(mod, txt): - """ - Function to tell 'lupdate' which strings to keep for translation. - - @param mod module name - @param txt translatable string - @return the untranslated! string - """ - return txt - - -class Message(object): - """ - Class defining the base for all specific message classes. - """ - message = '' - message_args = () - - def __init__(self, filename, lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - """ - self.filename = filename - self.lineno = lineno - - def __str__(self): - """ - Special method return a string representation of the instance object. - - @return string representation of the object (string) - """ - return '{0}:{1} {2}'.format( - self.filename, self.lineno, - self.message.format(*self.message_args)) - - def getMessageData(self): - """ - Public method to get the individual message data elements. - - @return tuple containing file name, line number and message - (string, integer, string) - """ - return (self.filename, self.lineno, - self.message, self.message_args) - - -class UnusedImport(Message): - """ - Class defining the "Unused Import" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - '{0!r} imported but unused.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the unused import (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class RedefinedWhileUnused(Message): - """ - Class defining the "Redefined While Unused" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Redefinition of unused {0!r} from line {1!r}.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the redefined object (string) - @param orig_lineno line number of the original definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class ImportShadowedByLoopVar(Message): - """ - Class defining the "Import Shadowed By Loop Var" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Import {0!r} from line {1!r} shadowed by loop variable.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the shadowed import (string) - @param orig_lineno line number of the import (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class ImportStarUsed(Message): - """ - Class defining the "Import Star Used" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - "'from {0} import *' used; unable to detect undefined names.") - - def __init__(self, filename, lineno, modname): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param modname name of the module imported using star import (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (modname,) - - -class UndefinedName(Message): - """ - Class defining the "Undefined Name" message. - """ - message = QT_TRANSLATE_NOOP('py3Flakes', 'Undefined name {0!r}.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name undefined name (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class UndefinedExport(Message): - """ - Class defining the "Undefined Export" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Undefined name {0!r} in __all__.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name undefined exported name (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class UndefinedLocal(Message): - """ - Class defining the "Undefined Local Variable" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - "Local variable {0!r} (defined in enclosing scope on line {1!r})" - " referenced before assignment.") - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the prematurely referenced variable (string) - @param orig_lineno line number of the variable definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class DuplicateArgument(Message): - """ - Class defining the "Duplicate Argument" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Duplicate argument {0!r} in function definition.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the duplicate argument (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class RedefinedFunction(Message): - """ - Class defining the "Redefined Function" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Redefinition of function {0!r} from line {1!r}.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the redefined function (string) - @param orig_lineno line number of the original definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class LateFutureImport(Message): - """ - Class defining the "Late Future Import" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Future import(s) {0!r} after other statements.') - - def __init__(self, filename, lineno, names): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param names names of the imported futures (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (names,) - - -class UnusedVariable(Message): - """ - Class defining the "Unused Variable" message. - - Indicates that a variable has been explicitly assigned to but not actually - used. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Local variable {0!r} is assigned to but never used.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the unused variable (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,)
--- a/eric5.e4p Wed Jan 01 22:59:10 2014 +0100 +++ b/eric5.e4p Sat Jan 04 22:12:42 2014 +0100 @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Project SYSTEM "Project-5.1.dtd"> <!-- eric5 project file for project eric5 --> -<!-- Saved: 2014-01-01, 22:56:51 --> +<!-- Saved: 2014-01-04, 21:59:29 --> <!-- Copyright (C) 2014 Detlev Offenbach, detlev@die-offenbachs.de --> <Project version="5.1"> <Language>en_US</Language> @@ -706,9 +706,6 @@ <Source>E5Gui/E5ToolBox.py</Source> <Source>E5Gui/E5TreeSortFilterProxyModel.py</Source> <Source>E5Gui/E5TreeView.py</Source> - <Source>Utilities/py3flakes/__init__.py</Source> - <Source>Utilities/py3flakes/checker.py</Source> - <Source>Utilities/py3flakes/messages.py</Source> <Source>Preferences/ConfigurationPages/EditorKeywordsPage.py</Source> <Source>Cooperation/__init__.py</Source> <Source>Cooperation/Connection.py</Source> @@ -1053,10 +1050,6 @@ <Source>QScintilla/SortOptionsDialog.py</Source> <Source>Debugger/CallStackViewer.py</Source> <Source>Utilities/compatibility_fixes.py</Source> - <Source>Utilities/SyntaxCheck.py</Source> - <Source>Utilities/py2flakes/checker.py</Source> - <Source>Utilities/py2flakes/messages.py</Source> - <Source>Utilities/py2flakes/__init__.py</Source> <Source>Examples/hallo.py</Source> <Source>Examples/modpython.py</Source> <Source>Examples/modpython_dbg.py</Source> @@ -1114,14 +1107,12 @@ <Source>UtilitiesPython2/__init__.py</Source> <Source>UtilitiesPython2/Tools.py</Source> <Source>Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py</Source> - <Source>Plugins/CheckerPlugins/SyntaxChecker/py3flakes/checker.py</Source> - <Source>Plugins/CheckerPlugins/SyntaxChecker/py3flakes/messages.py</Source> <Source>Utilities/BackgroundClient.py</Source> <Source>Utilities/BackgroundService.py</Source> <Source>Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py</Source> <Source>Plugins/CheckerPlugins/SyntaxChecker/pyflakes/messages.py</Source> <Source>Plugins/CheckerPlugins/SyntaxChecker/pyflakes/__init__.py</Source> - <Source>Plugins/CheckerPlugins/SyntaxChecker/py3flakes/__init__.py</Source> + <Source>Utilities/InternalServices.py</Source> </Sources> <Forms> <Form>PyUnit/UnittestDialog.ui</Form>