diff --git a/CadQuery/Libs/cadquery/CQ.py b/CadQuery/Libs/cadquery/CQ.py index e98854b..f0d62e1 100644 --- a/CadQuery/Libs/cadquery/CQ.py +++ b/CadQuery/Libs/cadquery/CQ.py @@ -2271,3 +2271,27 @@ class Workplane(CQ): #combine everything return self.union(boxes) + def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, centered=(True, True, True), combine=True): + # Convert the direction tuple to a vector, if needed + if isinstance(direct, tuple): + direct = Vector(direct) + + def _makesphere(pnt): + (xp, yp, zp) = pnt.toTuple() + + if centered[0]: + xp = xp - radius + if centered[1]: + yp = yp - radius + if centered[2]: + zp = zp - radius + + return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3) + + spheres = self.eachpoint(_makesphere, True) + + # If we don't need to combine everything, just return the created spheres + if not combine: + return spheres + else: + return self.union(spheres) diff --git a/CadQuery/Libs/cadquery/freecad_impl/shapes.py b/CadQuery/Libs/cadquery/freecad_impl/shapes.py index be88361..b58a67b 100644 --- a/CadQuery/Libs/cadquery/freecad_impl/shapes.py +++ b/CadQuery/Libs/cadquery/freecad_impl/shapes.py @@ -638,13 +638,13 @@ class Solid(Shape): FreeCADPart.makeWedge(xmin, ymin, zmin, z2min, x2min, xmax, ymax, zmax, z2max, x2max, pnt, dir)) @classmethod - def makeSphere(cls, radius, pnt=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None): + def makeSphere(cls, radius, pnt=None, dir=None, angleDegrees1=None, angleDegrees2=None, angleDegrees3=None): """ 'makeSphere(radius,[pnt, dir, angle1,angle2,angle3]) -- Make a sphere with a giv en radius\nBy default pnt=Vector(0,0,0), dir=Vector(0,0,1), angle1=0, angle2=90 and angle3=360' """ - return Solid(FreeCADPart.makeSphere(radius, pnt, angleDegrees1, angleDegrees2, angleDegrees3)) + return Shape.cast(FreeCADPart.makeSphere(radius, pnt.wrapped, dir.wrapped, angleDegrees1, angleDegrees2, angleDegrees3)) @classmethod def extrudeLinearWithRotation(cls, outerWire, innerWires, vecCenter, vecNormal, angleDegrees): diff --git a/CadQuery/Libs/frosted/__init__.py b/CadQuery/Libs/frosted/__init__.py deleted file mode 100644 index 8e3c933..0000000 --- a/CadQuery/Libs/frosted/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '1.4.1' diff --git a/CadQuery/Libs/frosted/api.py b/CadQuery/Libs/frosted/api.py deleted file mode 100644 index b5f603d..0000000 --- a/CadQuery/Libs/frosted/api.py +++ /dev/null @@ -1,153 +0,0 @@ -"""frosted/api.py. - -Defines the api for the command-line frosted utility - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - -""" -import os -import re -import sys -import tokenize -from io import StringIO -from token import N_TOKENS - -from pies.overrides import * - -import _ast -from frosted import reporter as modReporter -from frosted import checker, settings -from frosted.messages import FileSkipped, PythonSyntaxError - -__all__ = ['check', 'check_path', 'check_recursive', 'iter_source_code'] - -_re_noqa = re.compile(r'((frosted)[:=]\s*noqa)|(#\s*noqa)', re.I) - - -def _noqa_lines(codeString): - line_nums = [] - g = tokenize.generate_tokens(StringIO(str(codeString)).readline) # tokenize the string - for toknum, tokval, begins, _, _ in g: - lineno = begins[0] - # not sure what N_TOKENS really means, but in testing, that was what comments were - # tokenized as - if toknum == N_TOKENS: - if _re_noqa.search(tokval): - line_nums.append(lineno) - return line_nums - - -def _should_skip(filename, skip): - if filename in skip: - return True - - position = os.path.split(filename) - while position[1]: - if position[1] in skip: - return True - position = os.path.split(position[0]) - - -def check(codeString, filename, reporter=modReporter.Default, settings_path=None, **setting_overrides): - """Check the Python source given by codeString for unfrosted flakes.""" - - if not settings_path and filename: - settings_path = os.path.dirname(os.path.abspath(filename)) - settings_path = settings_path or os.getcwd() - - active_settings = settings.from_path(settings_path).copy() - for key, value in itemsview(setting_overrides): - access_key = key.replace('not_', '').lower() - if type(active_settings.get(access_key)) in (list, tuple): - if key.startswith('not_'): - active_settings[access_key] = list(set(active_settings[access_key]).difference(value)) - else: - active_settings[access_key] = list(set(active_settings[access_key]).union(value)) - else: - active_settings[key] = value - active_settings.update(setting_overrides) - - if _should_skip(filename, active_settings.get('skip', [])): - if active_settings.get('directly_being_checked', None) == 1: - reporter.flake(FileSkipped(filename)) - return 1 - elif active_settings.get('verbose', False): - ignore = active_settings.get('ignore_frosted_errors', []) - if(not "W200" in ignore and not "W201" in ignore): - reporter.flake(FileSkipped(filename, None, verbose=active_settings.get('verbose'))) - return 0 - - # First, compile into an AST and handle syntax errors. - try: - tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) - except SyntaxError: - value = sys.exc_info()[1] - msg = value.args[0] - - (lineno, offset, text) = value.lineno, value.offset, value.text - - # If there's an encoding problem with the file, the text is None. - if text is None: - # Avoid using msg, since for the only known case, it contains a - # bogus message that claims the encoding the file declared was - # unknown. - reporter.unexpected_error(filename, 'problem decoding source') - else: - reporter.flake(PythonSyntaxError(filename, msg, lineno, offset, text, - verbose=active_settings.get('verbose'))) - return 1 - except Exception: - reporter.unexpected_error(filename, 'problem decoding source') - return 1 - # Okay, it's syntactically valid. Now check it. - w = checker.Checker(tree, filename, None, ignore_lines=_noqa_lines(codeString), **active_settings) - w.messages.sort(key=lambda m: m.lineno) - for warning in w.messages: - reporter.flake(warning) - return len(w.messages) - - -def check_path(filename, reporter=modReporter.Default, settings_path=None, **setting_overrides): - """Check the given path, printing out any warnings detected.""" - try: - with open(filename, 'U') as f: - codestr = f.read() + '\n' - except UnicodeError: - reporter.unexpected_error(filename, 'problem decoding source') - return 1 - except IOError: - msg = sys.exc_info()[1] - reporter.unexpected_error(filename, msg.args[1]) - return 1 - return check(codestr, filename, reporter, settings_path, **setting_overrides) - - -def iter_source_code(paths): - """Iterate over all Python source files defined in paths.""" - for path in paths: - if os.path.isdir(path): - for dirpath, dirnames, filenames in os.walk(path): - for filename in filenames: - if filename.endswith('.py'): - yield os.path.join(dirpath, filename) - else: - yield path - - -def check_recursive(paths, reporter=modReporter.Default, settings_path=None, **setting_overrides): - """Recursively check all source files defined in paths.""" - warnings = 0 - for source_path in iter_source_code(paths): - warnings += check_path(source_path, reporter, settings_path=None, **setting_overrides) - return warnings diff --git a/CadQuery/Libs/frosted/checker.py b/CadQuery/Libs/frosted/checker.py deleted file mode 100644 index 239bd12..0000000 --- a/CadQuery/Libs/frosted/checker.py +++ /dev/null @@ -1,905 +0,0 @@ -"""frosted/checker.py. - -The core functionality of frosted lives here. Implements the core checking capability models Bindings and Scopes - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import builtins -import doctest -import itertools -import os -import sys - -from pies import ast -from pies.overrides import * - -from frosted import messages - -PY34_GTE = sys.version_info >= (3, 4) -FROSTED_BUILTINS = set(dir(builtins) + ['__file__', '__builtins__', '__debug__', '__name__', 'WindowsError', - '__import__'] + - os.environ.get('PYFLAKES_BUILTINS', '').split(',')) - -def node_name(node): - """ - Convenience function: Returns node.id, or node.name, or None - """ - return hasattr(node, 'id') and node.id or hasattr(node, 'name') and node.name - - -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. - - """ - __slots__ = ('name', 'source', 'used') - - def __init__(self, name, source): - self.name = name - self.source = source - self.used = False - - def __str__(self): - return self.name - - def __repr__(self): - return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, - self.name, - self.source.lineno, - id(self)) - - -class Importation(Binding): - """A binding created by an import statement.""" - __slots__ = ('fullName', ) - - 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.""" - __slots__ = () - - -class Definition(Binding): - """A binding that defines a function or a class.""" - __slots__ = () - - -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. - - """ - __slots__ = () - - -class FunctionDefinition(Definition): - __slots__ = ('signature', ) - - def __init__(self, name, source): - super(FunctionDefinition, self).__init__(name, source) - self.signature = FunctionSignature(source) - - -class ClassDefinition(Definition): - __slots__ = () - - -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. - - """ - __slots__ = () - - 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): - names.append(node.s) - return names - - -class Scope(dict): - importStarred = False # set to True when import * is found - - def __repr__(self): - scope_cls = self.__class__.__name__ - return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) - - -class ClassScope(Scope): - pass - - -class FunctionScope(Scope): - """Represents the name scope for a function.""" - uses_locals = False - always_used = set(['__tracebackhide__', '__traceback_info__', '__traceback_supplement__']) - - def __init__(self): - Scope.__init__(self) - self.globals = self.always_used.copy() - - def unusedAssignments(self): - """Return a generator for the assignments which have not been used.""" - for name, binding in self.items(): - if (not binding.used and name not in self.globals - and not self.uses_locals - and isinstance(binding, Assignment)): - yield name, binding - - -class GeneratorScope(Scope): - pass - - -class ModuleScope(Scope): - pass - - -class FunctionSignature(object): - __slots__ = ('decorated', 'argument_names', 'default_count', 'kw_only_argument_names', 'default_count', - 'kw_only_argument_names', 'kw_only_default_count', 'has_var_arg', 'has_kw_arg') - - def __init__(self, node): - self.decorated = bool(any(node.decorator_list)) - self.argument_names = ast.argument_names(node) - self.default_count = len(node.args.defaults) - self.kw_only_argument_names = ast.kw_only_argument_names(node) - self.kw_only_default_count = ast.kw_only_default_count(node) - self.has_var_arg = node.args.vararg is not None - self.has_kw_arg = node.args.kwarg is not None - - def min_argument_count(self): - return len(self.argument_names) - self.default_count - - def maxArgumentCount(self): - return len(self.argument_names) - - def checkCall(self, call_node, reporter, name): - if self.decorated: - return - - filledSlots = set() - filledKwOnlySlots = set() - for item, arg in enumerate(call_node.args): - if item >= len(self.argument_names): - if not self.has_var_arg: - return reporter.report(messages.TooManyArguments, call_node, name, self.maxArgumentCount()) - break - filledSlots.add(item) - - for kw in call_node.keywords: - slots = None - try: - argIndex = self.argument_names.index(kw.arg) - slots = filledSlots - except ValueError: - try: - argIndex = self.kw_only_argument_names.index(kw.arg) - slots = filledKwOnlySlots - except ValueError: - if self.has_kw_arg: - continue - else: - return reporter.report(messages.UnexpectedArgument, call_node, name, kw.arg) - if argIndex in slots: - return reporter.report(messages.MultipleValuesForArgument, call_node, name, kw.arg) - slots.add(argIndex) - - filledSlots.update(range(len(self.argument_names) - self.default_count, len(self.argument_names))) - filledKwOnlySlots.update(range(len(self.kw_only_argument_names) - self.kw_only_default_count, - len(self.kw_only_argument_names))) - - if (len(filledSlots) < len(self.argument_names) and not call_node.starargs and not call_node.kwargs): - return reporter.report(messages.TooFewArguments, call_node, name, self.min_argument_count()) - if (len(filledKwOnlySlots) < len(self.kw_only_argument_names) and not call_node.kwargs): - missing_arguments = [repr(arg) for i, arg in enumerate(self.kw_only_argument_names) - if i not in filledKwOnlySlots] - return reporter.report(messages.NeedKwOnlyArgument, call_node, name, ', '.join(missing_arguments)) - - -class Checker(object): - """The core of frosted, checks the cleanliness and sanity of Python code.""" - - node_depth = 0 - offset = None - trace_tree = False - frosted_builtins = FROSTED_BUILTINS - - def __init__(self, tree, filename='(none)', builtins=None, ignore_lines=(), **settings): - self.settings = settings - self.ignore_errors = settings.get('ignore_frosted_errors', []) - self.ignore_lines = ignore_lines - file_specifc_ignores = settings.get('ignore_frosted_errors_for_' + (os.path.basename(filename) or ""), None) - if file_specifc_ignores: - self.ignore_errors += file_specifc_ignores - - self._node_handlers = {} - self._deferred_functions = [] - self._deferred_assignments = [] - self.dead_scopes = [] - self.messages = [] - self.filename = filename - if builtins: - self.frosted_builtins = self.frosted_builtins.union(builtins) - self.scope_stack = [ModuleScope()] - self.except_handlers = [()] - self.futures_allowed = True - self.root = tree - self.handle_children(tree) - self.run_deferred(self._deferred_functions) - self._deferred_functions = None - self.run_deferred(self._deferred_assignments) - self._deferred_assignments = None - del self.scope_stack[1:] - self.pop_scope() - self.check_dead_scopes() - - def defer_function(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._deferred_functions.append((callable, self.scope_stack[:], self.offset)) - - def defer_assignment(self, callable): - """Schedule an assignment handler to be called just after deferred - function handlers.""" - self._deferred_assignments.append((callable, self.scope_stack[:], self.offset)) - - def run_deferred(self, deferred): - """Run the callables in deferred using their associated scope stack.""" - for handler, scope, offset in deferred: - self.scope_stack = scope - self.offset = offset - handler() - - @property - def scope(self): - return self.scope_stack[-1] - - def pop_scope(self): - self.dead_scopes.append(self.scope_stack.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() - # Look for possible mistakes in the export list - if not scope.importStarred and os.path.basename(self.filename) != '__init__.py': - undefined = set(all) - set(scope) - for name in undefined: - self.report(messages.UndefinedExport, scope['__all__'].source, name) - else: - all = [] - - # Look for imported names that aren't used without checking imports in namespace definition - for importation in scope.values(): - if isinstance(importation, Importation) and not importation.used and importation.name not in all: - self.report(messages.UnusedImport, importation.source, importation.name) - - def push_scope(self, scope_class=FunctionScope): - self.scope_stack.append(scope_class()) - - def push_function_scope(self): # XXX Deprecated - self.push_scope(FunctionScope) - - def push_class_scope(self): # XXX Deprecated - self.push_scope(ClassScope) - - def report(self, message_class, *args, **kwargs): - error_code = message_class.error_code - - if(not error_code[:2] + "00" in self.ignore_errors and not error_code in self.ignore_errors and not - str(message_class.error_number) in self.ignore_errors): - kwargs['verbose'] = self.settings.get('verbose') - message = message_class(self.filename, *args, **kwargs) - if message.lineno not in self.ignore_lines: - self.messages.append(message) - - def has_parent(self, node, kind): - while hasattr(node, 'parent'): - node = node.parent - if isinstance(node, kind): - return True - - def get_common_ancestor(self, lnode, rnode, stop=None): - stop = stop or self.root - if lnode is rnode: - return lnode - if stop in (lnode, rnode): - return stop - - if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'): - return - if (lnode.level > rnode.level): - return self.get_common_ancestor(lnode.parent, rnode, stop) - if (rnode.level > lnode.level): - return self.get_common_ancestor(lnode, rnode.parent, stop) - return self.get_common_ancestor(lnode.parent, rnode.parent, stop) - - def descendant_of(self, node, ancestors, stop=None): - for ancestor in ancestors: - if self.get_common_ancestor(node, ancestor, stop) not in (stop, None): - return True - return False - - def on_fork(self, parent, lnode, rnode, items): - return (self.descendant_of(lnode, items, parent) ^ self.descendant_of(rnode, items, parent)) - - def different_forks(self, lnode, rnode): - """True, if lnode and rnode are located on different forks of - IF/TRY.""" - ancestor = self.get_common_ancestor(lnode, rnode) - if isinstance(ancestor, ast.If): - for fork in (ancestor.body, ancestor.orelse): - if self.on_fork(ancestor, lnode, rnode, fork): - return True - elif isinstance(ancestor, ast.Try): - body = ancestor.body + ancestor.orelse - for fork in [body] + [[hdl] for hdl in ancestor.handlers]: - if self.on_fork(ancestor, lnode, rnode, fork): - return True - elif isinstance(ancestor, ast.TryFinally): - if self.on_fork(ancestor, lnode, rnode, ancestor.body): - return True - return False - - def add_binding(self, node, value, report_redef=True): - """Called when a binding is altered. - - - `node` is the statement responsible for the change - - `value` is the optional new value, a Binding instance, associated - with the binding; if None, the binding is deleted if it exists. - - if `report_redef` is True (default), rebinding while unused will be - reported. - - """ - redefinedWhileUnused = False - if not isinstance(self.scope, ClassScope): - for scope in self.scope_stack[::-1]: - existing = scope.get(value.name) - if (isinstance(existing, Importation) - and not existing.used - and (not isinstance(value, Importation) or - value.fullName == existing.fullName) - and report_redef - and not self.different_forks(node, existing.source)): - redefinedWhileUnused = True - self.report(messages.RedefinedWhileUnused, - node, value.name, existing.source) - - existing = self.scope.get(value.name) - if not redefinedWhileUnused and self.has_parent(value.source, ast.ListComp): - if (existing and report_redef - and not self.has_parent(existing.source, (ast.For, ast.ListComp)) - and not self.different_forks(node, existing.source)): - self.report(messages.RedefinedInListComp, - node, value.name, existing.source) - - if (isinstance(existing, Definition) - and not existing.used - and not self.different_forks(node, existing.source)): - self.report(messages.RedefinedWhileUnused, - node, value.name, existing.source) - else: - self.scope[value.name] = value - - def get_node_handler(self, node_class): - try: - return self._node_handlers[node_class] - except KeyError: - nodeType = str(node_class.__name__).upper() - self._node_handlers[node_class] = handler = getattr(self, nodeType) - return handler - - def iter_visible_scopes(self): - outerScopes = itertools.islice(self.scope_stack, len(self.scope_stack) - 1) - scopes = [scope for scope in outerScopes - if isinstance(scope, (FunctionScope, ModuleScope))] - if (isinstance(self.scope, GeneratorScope) - and scopes[-1] != self.scope_stack[-2]): - scopes.append(self.scope_stack[-2]) - scopes.append(self.scope_stack[-1]) - return iter(reversed(scopes)) - - def handle_node_load(self, node): - name = node_name(node) - if not name: - return - - importStarred = False - for scope in self.iter_visible_scopes(): - importStarred = importStarred or scope.importStarred - try: - scope[name].used = (self.scope, node) - except KeyError: - pass - else: - return - - # look in the built-ins - if importStarred or name in self.frosted_builtins: - return - if name == '__path__' and os.path.basename(self.filename) == '__init__.py': - # the special name __path__ is valid only in packages - return - - # protected with a NameError handler? - if 'NameError' not in self.except_handlers[-1]: - self.report(messages.UndefinedName, node, name) - - def handle_node_store(self, node): - name = node_name(node) - if not name: - return - # if the name hasn't already been defined in the current scope - if isinstance(self.scope, FunctionScope) and name not in self.scope: - # for each function or module scope above us - for scope in self.scope_stack[:-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 - used = name in scope and scope[name].used - if used and used[0] is self.scope and name not in self.scope.globals: - # then it's probably a mistake - self.report(messages.UndefinedLocal, - scope[name].used[1], name, scope[name].source) - break - - parent = getattr(node, 'parent', None) - if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)): - binding = Binding(name, node) - elif (parent is not None and name == '__all__' and - isinstance(self.scope, ModuleScope)): - binding = ExportBinding(name, parent.value) - else: - binding = Assignment(name, node) - if name in self.scope: - binding.used = self.scope[name].used - self.add_binding(node, binding) - - def handle_node_delete(self, node): - name = node_name(node) - if not name: - return - if isinstance(self.scope, FunctionScope) and name in self.scope.globals: - self.scope.globals.remove(name) - else: - try: - del self.scope[name] - except KeyError: - self.report(messages.UndefinedName, node, name) - - def handle_children(self, tree): - for node in ast.iter_child_nodes(tree): - self.handleNode(node, tree) - - def is_docstring(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 docstring(self, node): - if isinstance(node, ast.Expr): - node = node.value - if not isinstance(node, ast.Str): - return (None, None) - # Computed incorrectly if the docstring has backslash - doctest_lineno = node.lineno - node.s.count('\n') - 1 - return (node.s, doctest_lineno) - - def handleNode(self, node, parent): - if node is None: - return - if self.offset and getattr(node, 'lineno', None) is not None: - node.lineno += self.offset[0] - node.col_offset += self.offset[1] - if self.trace_tree: - print(' ' * self.node_depth + node.__class__.__name__) - if self.futures_allowed and not (isinstance(node, ast.ImportFrom) or - self.is_docstring(node)): - self.futures_allowed = False - self.node_depth += 1 - node.level = self.node_depth - node.parent = parent - try: - handler = self.get_node_handler(node.__class__) - handler(node) - finally: - self.node_depth -= 1 - if self.trace_tree: - print(' ' * self.node_depth + 'end ' + node.__class__.__name__) - - _get_doctest_examples = doctest.DocTestParser().get_examples - - def handle_doctests(self, node): - try: - docstring, node_lineno = self.docstring(node.body[0]) - if not docstring: - return - examples = self._get_doctest_examples(docstring) - except (ValueError, IndexError): - # e.g. line 6 of the docstring for has inconsistent - # leading whitespace: ... - return - node_offset = self.offset or (0, 0) - self.push_scope() - for example in examples: - try: - tree = compile(example.source, "", "exec", ast.PyCF_ONLY_AST) - except SyntaxError: - e = sys.exc_info()[1] - position = (node_lineno + example.lineno + e.lineno, - example.indent + 4 + (e.offset or 0)) - self.report(messages.DoctestSyntaxError, node, position) - else: - self.offset = (node_offset[0] + node_lineno + example.lineno, - node_offset[1] + example.indent + 4) - self.handle_children(tree) - self.offset = node_offset - self.pop_scope() - - def find_return_with_argument(self, node): - """Finds and returns a return statment that has an argument. - - Note that we should use node.returns in Python 3, but this method is never called in Python 3 so we don't bother - checking. - - """ - for item in node.body: - if isinstance(item, ast.Return) and item.value: - return item - elif not isinstance(item, ast.FunctionDef) and hasattr(item, 'body'): - return_with_argument = self.find_return_with_argument(item) - if return_with_argument: - return return_with_argument - - def is_generator(self, node): - """Checks whether a function is a generator by looking for a yield - statement or expression.""" - if not isinstance(node.body, list): - # lambdas can not be generators - return False - for item in node.body: - if isinstance(item, (ast.Assign, ast.Expr)): - if isinstance(item.value, ast.Yield): - return True - elif not isinstance(item, ast.FunctionDef) and hasattr(item, 'body'): - if self.is_generator(item): - return True - return False - - def ignore(self, node): - pass - - # "stmt" type nodes - RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = TRYFINALLY = ASSERT = EXEC = EXPR = handle_children - - CONTINUE = BREAK = PASS = ignore - - # "expr" type nodes - BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = COMPARE = REPR = ATTRIBUTE = SUBSCRIPT = \ - LIST = TUPLE = STARRED = NAMECONSTANT = handle_children - - NUM = STR = BYTES = ELLIPSIS = ignore - - # "slice" type nodes - SLICE = EXTSLICE = INDEX = handle_children - - # expression contexts are node instances too, though being constants - LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore - - # same for operators - 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 - - # additional node types - COMPREHENSION = KEYWORD = handle_children - - def GLOBAL(self, node): - """Keep track of globals declarations.""" - if isinstance(self.scope, FunctionScope): - self.scope.globals.update(node.names) - - NONLOCAL = GLOBAL - - def LISTCOMP(self, node): - # handle generators before element - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.elt, node) - - def GENERATOREXP(self, node): - self.push_scope(GeneratorScope) - # handle generators before element - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.elt, node) - self.pop_scope() - - SETCOMP = GENERATOREXP - - def DICTCOMP(self, node): - self.push_scope(GeneratorScope) - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.key, node) - self.handleNode(node.value, node) - self.pop_scope() - - 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, varn, self.scope[varn].source) - - self.handle_children(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)): - self.handle_node_load(node) - if (node.id == 'locals' and isinstance(self.scope, FunctionScope) - and isinstance(node.parent, ast.Call)): - # we are doing locals() call in current scope - self.scope.uses_locals = True - elif isinstance(node.ctx, (ast.Store, ast.AugStore)): - self.handle_node_store(node) - elif isinstance(node.ctx, ast.Del): - self.handle_node_delete(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: %r" % (node.ctx,)) - - def CALL(self, node): - f = node.func - if isinstance(f, ast.Name): - for scope in self.iter_visible_scopes(): - definition = scope.get(f.id) - if definition: - if isinstance(definition, FunctionDefinition): - definition.signature.checkCall(node, self, f.id) - break - - - self.handle_children(node) - - def FUNCTIONDEF(self, node): - for deco in node.decorator_list: - self.handleNode(deco, node) - self.add_binding(node, FunctionDefinition(node.name, node)) - self.LAMBDA(node) - if self.settings.get('run_doctests', False): - self.defer_function(lambda: self.handle_doctests(node)) - - def LAMBDA(self, node): - args = [] - annotations = [] - - if PY2: - def addArgs(arglist): - for arg in arglist: - if isinstance(arg, ast.Tuple): - addArgs(arg.elts) - else: - if arg.id in args: - self.report(messages.DuplicateArgument, - node, arg.id) - args.append(arg.id) - addArgs(node.args.args) - defaults = node.args.defaults - else: - for arg in node.args.args + node.args.kwonlyargs: - annotations.append(arg.annotation) - args.append(arg.arg) - defaults = node.args.defaults + node.args.kw_defaults - - # Only for Python3 FunctionDefs - is_py3_func = hasattr(node, 'returns') - - for arg_name in ('vararg', 'kwarg'): - wildcard = getattr(node.args, arg_name) - if not wildcard: - continue - args.append(getattr(wildcard, 'arg', wildcard)) - if is_py3_func: - if PY34_GTE: - annotations.append(wildcard.annotation) - else: - argannotation = arg_name + 'annotation' - annotations.append(getattr(node.args, argannotation)) - if is_py3_func: - annotations.append(node.returns) - - if PY3: - if len(set(args)) < len(args): - for (idx, arg) in enumerate(args): - if arg in args[:idx]: - self.report(messages.DuplicateArgument, node, arg) - - for child in annotations + defaults: - if child: - self.handleNode(child, node) - - def runFunction(): - - self.push_scope() - for name in args: - self.add_binding(node, Argument(name, node), report_redef=False) - if isinstance(node.body, list): - # case for FunctionDefs - for stmt in node.body: - self.handleNode(stmt, node) - else: - # case for Lambdas - self.handleNode(node.body, node) - - def checkUnusedAssignments(): - """Check to see if any assignments have not been used.""" - for name, binding in self.scope.unusedAssignments(): - self.report(messages.UnusedVariable, binding.source, name) - self.defer_assignment(checkUnusedAssignments) - - if PY2: - def checkReturnWithArgumentInsideGenerator(): - """Check to see if there are any return statements with - arguments but the function is a generator.""" - if self.is_generator(node): - stmt = self.find_return_with_argument(node) - if stmt is not None: - self.report(messages.ReturnWithArgsInsideGenerator, stmt) - self.defer_assignment(checkReturnWithArgumentInsideGenerator) - self.pop_scope() - - self.defer_function(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 deco in node.decorator_list: - self.handleNode(deco, node) - for baseNode in node.bases: - self.handleNode(baseNode, node) - if not PY2: - for keywordNode in node.keywords: - self.handleNode(keywordNode, node) - self.push_scope(ClassScope) - if self.settings.get('run_doctests', False): - self.defer_function(lambda: self.handle_doctests(node)) - for stmt in node.body: - self.handleNode(stmt, node) - self.pop_scope() - self.add_binding(node, ClassDefinition(node.name, node)) - - def ASSIGN(self, node): - self.handleNode(node.value, node) - for target in node.targets: - self.handleNode(target, node) - - def AUGASSIGN(self, node): - self.handle_node_load(node.target) - self.handleNode(node.value, node) - 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.add_binding(node, importation) - - def IMPORTFROM(self, node): - if node.module == '__future__': - if not self.futures_allowed: - self.report(messages.LateFutureImport, - node, [n.name for n in node.names]) - else: - self.futures_allowed = False - - for alias in node.names: - if alias.name == '*': - self.scope.importStarred = True - self.report(messages.ImportStarUsed, node, node.module) - continue - name = alias.asname or alias.name - importation = Importation(name, node) - if node.module == '__future__': - importation.used = (self.scope, node) - self.add_binding(node, importation) - - def TRY(self, node): - handler_names = [] - # List the exception handlers - for handler in node.handlers: - if isinstance(handler.type, ast.Tuple): - for exc_type in handler.type.elts: - handler_names.append(node_name(exc_type)) - elif handler.type: - handler_names.append(node_name(handler.type)) - # Memorize the except handlers and process the body - self.except_handlers.append(handler_names) - for child in node.body: - self.handleNode(child, node) - self.except_handlers.pop() - # Process the other nodes: "except:", "else:", "finally:" - for child in ast.iter_child_nodes(node): - if child not in node.body: - self.handleNode(child, node) - - TRYEXCEPT = TRY - - def EXCEPTHANDLER(self, node): - # 3.x: in addition to handling children, we must handle the name of - # the exception, which is not a Name node, but a simple string. - if node.type is None: - self.report(messages.BareExcept, node) - if isinstance(node.name, str): - self.handle_node_store(node) - self.handle_children(node) diff --git a/CadQuery/Libs/frosted/main.py b/CadQuery/Libs/frosted/main.py deleted file mode 100644 index 7dd5e08..0000000 --- a/CadQuery/Libs/frosted/main.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python -""" Implementation of the command-line frosted tool. - -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import argparse -import sys - -from pies.overrides import * - -from frosted import __version__ -from frosted.api import check, check_path, check_recursive - - -def main(): - warnings = 0 - - parser = argparse.ArgumentParser(description='Quickly check the correctness of your Python scripts.') - parser.add_argument('files', nargs='+', help='One file or a list of Python source files to check the syntax of.') - parser.add_argument('-r', '--recursive', dest='recursive', action='store_true', - help='Recursively look for Python files to check') - parser.add_argument('-s', '--skip', help='Files that frosted should skip over.', dest='skip', action='append') - parser.add_argument('-d', '--with-doctests', help='Run frosted against doctests', dest='run_doctests', - action='store_true') - parser.add_argument('-i', '--ignore', help='Specify error codes that should be ignored.', - dest='ignore_frosted_errors', action='append') - parser.add_argument('-di', '--dont-ignore', help='Specify error codes that should not be ignored in any case.', - dest='not_ignore_frosted_errors', action='append') - parser.add_argument('-vb', '--verbose', help='Explicitly separate each section of data when displaying errors.', - dest='verbose', action='store_true') - parser.add_argument('-v', '--version', action='version', version='frosted {0}'.format(__version__)) - arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value) - file_names = arguments.pop('files', []) - if file_names == ['-']: - check(sys.stdin.read(), '', **arguments) - elif arguments.get('recursive'): - warnings = check_recursive(file_names, **arguments) - else: - warnings = 0 - for file_path in file_names: - try: - warnings += check_path(file_path, directly_being_checked=len(file_names), **arguments) - except IOError as e: - print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e)) - - raise SystemExit(warnings > 0) - - -if __name__ == "__main__": - main() diff --git a/CadQuery/Libs/frosted/messages.py b/CadQuery/Libs/frosted/messages.py deleted file mode 100644 index eb48381..0000000 --- a/CadQuery/Libs/frosted/messages.py +++ /dev/null @@ -1,116 +0,0 @@ -"""frosted/reporter.py. - -Defines the error messages that frosted can output - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - -""" - -from __future__ import absolute_import, division, print_function, unicode_literals - -import re -from collections import namedtuple - -from pies.overrides import * - -BY_CODE = {} -_ERROR_INDEX = 100 - -AbstractMessageType = namedtuple('AbstractMessageType', ('error_code', 'name', 'template', - 'keyword', 'error_number')) - - -class MessageType(AbstractMessageType): - - class Message(namedtuple('Message', ('message', 'type', 'lineno', 'col'))): - - def __str__(self): - return self.message - - def __new__(cls, error_code, name, template, keyword='{0!s}'): - global _ERROR_INDEX - new_instance = AbstractMessageType.__new__(cls, error_code, name, template, - keyword, _ERROR_INDEX) - _ERROR_INDEX += 1 - BY_CODE[error_code] = new_instance - return new_instance - - def __call__(self, filename, loc=None, *kargs, **kwargs): - values = {'filename': filename, 'lineno': 0, 'col': 0} - if loc: - values['lineno'] = loc.lineno - values['col'] = getattr(loc, 'col_offset', 0) - values.update(kwargs) - - message = self.template.format(*kargs, **values) - if kwargs.get('verbose', False): - keyword = self.keyword.format(*kargs, **values) - return self.Message('{0}:{1}:{2}:{3}:{4}:{5}'.format(filename, values['lineno'], values['col'], - self.error_code, keyword, message), - self, values['lineno'], values['col']) - return self.Message('{0}:{1}: {2}'.format(filename, values['lineno'], message), - self, values['lineno'], values['col']) - - - -class OffsetMessageType(MessageType): - def __call__(self, filename, loc, position=None, *kargs, **kwargs): - if position: - kwargs.update({'lineno': position[0], 'col': position[1]}) - return MessageType.__call__(self, filename, loc, *kargs, **kwargs) - - -class SyntaxErrorType(MessageType): - def __call__(self, filename, msg, lineno, offset, text, *kargs, **kwargs): - kwargs['lineno'] = lineno - line = text.splitlines()[-1] - msg += "\n" + str(line) - if offset is not None: - offset = offset - (len(text) - len(line)) - kwargs['col'] = offset - msg += "\n" + re.sub(r'\S',' ', line[:offset]) + "^" - - return MessageType.__call__(self, filename, None, msg, *kargs, **kwargs) - - -Message = MessageType('I101', 'Generic', '{0}', '') -UnusedImport = MessageType('E101', 'UnusedImport', '{0} imported but unused') -RedefinedWhileUnused = MessageType('E301', 'RedefinedWhileUnused', - 'redefinition of {0!r} from line {1.lineno!r}') -RedefinedInListComp = MessageType('E302', 'RedefinedInListComp', - 'list comprehension redefines {0!r} from line {1.lineno!r}') -ImportShadowedByLoopVar = MessageType('E102', 'ImportShadowedByLoopVar', - 'import {0!r} from line {1.lineno!r} shadowed by loop variable') -ImportStarUsed = MessageType('E103', 'ImportStarUsed', - "'from {0!s} import *' used; unable to detect undefined names", '*') -UndefinedName = MessageType('E303', 'UndefinedName', "undefined name {0!r}") -DoctestSyntaxError = OffsetMessageType('E401', 'DoctestSyntaxError', "syntax error in doctest", '') -UndefinedExport = MessageType('E304', 'UndefinedExport', "undefined name {0!r} in __all__") -UndefinedLocal = MessageType('E305', 'UndefinedLocal', - 'local variable {0!r} (defined in enclosing scope on line {1.lineno!r}) referenced before assignment') -DuplicateArgument = MessageType('E206', 'DuplicateArgument', "duplicate argument {0!r} in function definition") -Redefined = MessageType('E306', 'Redefined', "redefinition of {0!r} from line {1.lineno!r}") -LateFutureImport = MessageType('E207', 'LateFutureImport', "future import(s) {0!r} after other statements") -UnusedVariable = MessageType('E307', 'UnusedVariable', "local variable {0!r} is assigned to but never used") -MultipleValuesForArgument = MessageType('E201', 'MultipleValuesForArgument', - "{0!s}() got multiple values for argument {1!r}") -TooFewArguments = MessageType('E202', 'TooFewArguments', "{0!s}() takes at least {1:d} argument(s)") -TooManyArguments = MessageType('E203', 'TooManyArguments', "{0!s}() takes at most {1:d} argument(s)") -UnexpectedArgument = MessageType('E204', 'UnexpectedArgument', "{0!s}() got unexpected keyword argument: {1!r}") -NeedKwOnlyArgument = MessageType('E205', 'NeedKwOnlyArgument', "{0!s}() needs kw-only argument(s): {1!s}") -ReturnWithArgsInsideGenerator = MessageType('E208', 'ReturnWithArgsInsideGenerator', - "'return' with argument inside generator", 'return') -BareExcept = MessageType('W101', 'BareExcept', "bare except used: this is dangerous and should be avoided", 'except') -FileSkipped = MessageType('W201', 'FileSkipped', "Skipped because of the current configuration", 'skipped') -PythonSyntaxError = SyntaxErrorType('E402', 'PythonSyntaxError', "{0!s}", "") diff --git a/CadQuery/Libs/frosted/reporter.py b/CadQuery/Libs/frosted/reporter.py deleted file mode 100644 index 2aeb8ab..0000000 --- a/CadQuery/Libs/frosted/reporter.py +++ /dev/null @@ -1,39 +0,0 @@ -"""frosted/reporter.py. - -Defines how errors found by frosted should be displayed to the user - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import sys -from collections import namedtuple - -from pies.overrides import * - - -class Reporter(namedtuple('Reporter', ('stdout', 'stderr'))): - """Formats the results of frosted checks and then presents them to the user.""" - - def unexpected_error(self, filename, msg): - """Output an unexpected_error specific to the provided filename.""" - self.stderr.write("%s: %s\n" % (filename, msg)) - - def flake(self, message): - """Print an error message to stdout.""" - self.stdout.write(str(message)) - self.stdout.write('\n') - -Default = Reporter(sys.stdout, sys.stderr) diff --git a/CadQuery/Libs/frosted/settings.py b/CadQuery/Libs/frosted/settings.py deleted file mode 100644 index 3c20799..0000000 --- a/CadQuery/Libs/frosted/settings.py +++ /dev/null @@ -1,118 +0,0 @@ -"""frosted/settings.py. - -Defines how the default settings for frosted should be loaded - -(First from the default setting dictionary at the top of the file, then overridden by any settings - in ~/.frosted.conf if there are any) - -Copyright (C) 2013 Timothy Edmund Crosley - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import os - -from pies.functools import lru_cache -from pies.overrides import * - -try: - import configparser -except ImportError: - import ConfigParser as configparser - -MAX_CONFIG_SEARCH_DEPTH = 25 # The number of parent directories frosted will look for a config file within - -# Note that none of these lists must be complete as they are simply fallbacks for when included auto-detection fails. -default = {'skip': [], - 'ignore_frosted_errors': ['W201'], - 'ignore_frosted_errors_for__init__.py': ['E101', 'E103'], - 'verbose': False, - 'run_doctests': False} - - -@lru_cache() -def from_path(path): - computed_settings = default.copy() - _update_settings_with_config(path, '.editorconfig', '~/.editorconfig', ('*', '*.py', '**.py'), computed_settings) - _update_settings_with_config(path, '.frosted.cfg', '~/.frosted.cfg', ('settings', ), computed_settings) - _update_settings_with_config(path, 'setup.cfg', None, ('frosted', ), computed_settings) - return computed_settings - - -def _update_settings_with_config(path, name, default, sections, computed_settings): - editor_config_file = default and os.path.expanduser(default) - tries = 0 - current_directory = path - while current_directory and tries < MAX_CONFIG_SEARCH_DEPTH: - potential_path = os.path.join(current_directory, native_str(name)) - if os.path.exists(potential_path): - editor_config_file = potential_path - break - - current_directory = os.path.split(current_directory)[0] - tries += 1 - - if editor_config_file and os.path.exists(editor_config_file): - _update_with_config_file(computed_settings, editor_config_file, sections) - - -def _update_with_config_file(computed_settings, file_path, sections): - settings = _get_config_data(file_path, sections) - if not settings: - return - - for key, value in settings.items(): - access_key = key.replace('not_', '').lower() - if key.startswith('ignore_frosted_errors_for'): - existing_value_type = list - else: - existing_value_type = type(default.get(access_key, '')) - if existing_value_type in (list, tuple): - existing_data = set(computed_settings.get(access_key, default.get(access_key)) or ()) - if key.startswith('not_'): - computed_settings[access_key] = list(existing_data.difference(value.split(","))) - else: - computed_settings[access_key] = list(existing_data.union(value.split(","))) - elif existing_value_type == bool and value.lower().strip() == "false": - computed_settings[access_key] = False - else: - computed_settings[access_key] = existing_value_type(value) - - -@lru_cache() -def _get_config_data(file_path, sections): - with open(file_path) as config_file: - if file_path.endswith(".editorconfig"): - line = "\n" - last_position = config_file.tell() - while line: - line = config_file.readline() - if "[" in line: - config_file.seek(last_position) - break - last_position = config_file.tell() - - config = configparser.SafeConfigParser() - config.readfp(config_file) - settings = dict() - for section in sections: - if config.has_section(section): - settings.update(dict(config.items(section))) - - return settings - - return None diff --git a/CadQuery/Libs/frosted/test/__init__.py b/CadQuery/Libs/frosted/test/__init__.py deleted file mode 100644 index 86774f8..0000000 --- a/CadQuery/Libs/frosted/test/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -from pies.overrides import * diff --git a/CadQuery/Libs/frosted/test/test_api.py b/CadQuery/Libs/frosted/test/test_api.py deleted file mode 100644 index 8fe6998..0000000 --- a/CadQuery/Libs/frosted/test/test_api.py +++ /dev/null @@ -1,302 +0,0 @@ -"""frosted/test/test_api.py. - -Tests all major functionality of the Frosted API - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import os -import sys -import tempfile -from io import StringIO - -import pytest -from pies.overrides import * - -from frosted.api import check_path, check_recursive -from frosted.messages import PythonSyntaxError, UnusedImport -from frosted.reporter import Reporter - -from .utils import LoggingReporter, Node - - -def test_syntax_error(): - """syntax_error reports that there was a syntax error in the source file. - - It reports to the error stream and includes the filename, line number, error message, actual line of source and a - caret pointing to where the error is - - """ - err = StringIO() - reporter = Reporter(err, err) - reporter.flake(PythonSyntaxError('foo.py', 'a problem', 3, 7, 'bad line of source', verbose=True)) - assert ("foo.py:3:7:E402::a problem\n" - "bad line of source\n" - " ^\n") == err.getvalue() - - -def test_syntax_errorNoOffset(): - """syntax_error doesn't include a caret pointing to the error if offset is passed as None.""" - err = StringIO() - reporter = Reporter(err, err) - reporter.flake(PythonSyntaxError('foo.py', 'a problem', 3, None, 'bad line of source', verbose=True)) - assert ("foo.py:3:0:E402::a problem\n" - "bad line of source\n") == err.getvalue() - - -def test_multiLineSyntaxError(): - """ If there's a multi-line syntax error, then we only report the last line. - - The offset is adjusted so that it is relative to the start of the last line - - """ - err = StringIO() - lines = ['bad line of source', 'more bad lines of source'] - reporter = Reporter(err, err) - reporter.flake(PythonSyntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, '\n'.join(lines), verbose=True)) - assert ("foo.py:3:6:E402::a problem\n" + - lines[-1] + "\n" + - " ^\n") == err.getvalue() - - -def test_unexpected_error(): - """unexpected_error reports an error processing a source file.""" - err = StringIO() - reporter = Reporter(None, err) - reporter.unexpected_error('source.py', 'error message') - assert 'source.py: error message\n' == err.getvalue() - - -def test_flake(): - """flake reports a code warning from Frosted. - - It is exactly the str() of a frosted.messages.Message - - """ - out = StringIO() - reporter = Reporter(out, None) - message = UnusedImport('foo.py', Node(42), 'bar') - reporter.flake(message) - assert out.getvalue() == "%s\n" % (message,) - - -def make_temp_file(content): - """Make a temporary file containing C{content} and return a path to it.""" - _, fpath = tempfile.mkstemp() - if not hasattr(content, 'decode'): - content = content.encode('ascii') - fd = open(fpath, 'wb') - fd.write(content) - fd.close() - return fpath - - -def assert_contains_output(path, flakeList): - """Assert that provided causes at minimal the errors provided in the error list.""" - out = StringIO() - count = check_path(path, Reporter(out, out), verbose=True) - out_string = out.getvalue() - assert len(flakeList) >= count - for flake in flakeList: - assert flake in out_string - - -def get_errors(path): - """Get any warnings or errors reported by frosted for the file at path.""" - log = [] - reporter = LoggingReporter(log) - count = check_path(path, reporter) - return count, log - - -def test_missingTrailingNewline(): - """Source which doesn't end with a newline shouldn't cause any exception to - - be raised nor an error indicator to be returned by check. - - """ - fName = make_temp_file("def foo():\n\tpass\n\t") - assert_contains_output(fName, []) - - -def test_check_pathNonExisting(): - """check_path handles non-existing files""" - count, errors = get_errors('extremo') - assert count == 1 - assert errors == [('unexpected_error', 'extremo', 'No such file or directory')] - - -def test_multilineSyntaxError(): - """Source which includes a syntax error which results in the raised SyntaxError. - - text containing multiple lines of source are reported with only - the last line of that source. - - """ - source = """\ -def foo(): - ''' - -def bar(): - pass - -def baz(): - '''quux''' -""" - # Sanity check - SyntaxError.text should be multiple lines, if it - # isn't, something this test was unprepared for has happened. - def evaluate(source): - exec(source) - try: - evaluate(source) - except SyntaxError: - e = sys.exc_info()[1] - assert e.text.count('\n') > 1 - else: - assert False - - sourcePath = make_temp_file(source) - assert_contains_output( - sourcePath, - ["""\ -%s:8:10:E402::invalid syntax - '''quux''' - ^ -""" % (sourcePath,)]) - - -def test_eofSyntaxError(): - """The error reported for source files which end prematurely causing a - syntax error reflects the cause for the syntax error. - - """ - sourcePath = make_temp_file("def foo(") - assert_contains_output(sourcePath, ["""\ -%s:1:8:E402::unexpected EOF while parsing -def foo( - ^ -""" % (sourcePath,)]) - - -def test_nonDefaultFollowsDefaultSyntaxError(): - """ Source which has a non-default argument following a default argument - - should include the line number of the syntax error - However these exceptions do not include an offset - - """ - source = """\ -def foo(bar=baz, bax): - pass -""" - sourcePath = make_temp_file(source) - last_line = ' ^\n' if sys.version_info >= (3, 2) else '' - column = '7:' if sys.version_info >= (3, 2) else '0:' - assert_contains_output(sourcePath, ["""\ -%s:1:%sE402::non-default argument follows default argument -def foo(bar=baz, bax): -%s""" % (sourcePath, column, last_line)]) - - -def test_nonKeywordAfterKeywordSyntaxError(): - """Source which has a non-keyword argument after a keyword argument - - should include the line number of the syntax error - However these exceptions do not include an offset - """ - source = """\ -foo(bar=baz, bax) -""" - sourcePath = make_temp_file(source) - last_line = ' ^\n' if sys.version_info >= (3, 2) else '' - column = '12:' if sys.version_info >= (3, 2) else '0:' - assert_contains_output( - sourcePath, - ["""\ -%s:1:%sE402::non-keyword arg after keyword arg -foo(bar=baz, bax) -%s""" % (sourcePath, column, last_line)]) - - -def test_invalidEscape(): - """The invalid escape syntax raises ValueError in Python 2.""" - sourcePath = make_temp_file(r"foo = '\xyz'") - if PY2: - decoding_error = "%s: problem decoding source\n" % (sourcePath,) - else: - decoding_error = "(unicode error) 'unicodeescape' codec can't decode bytes" - assert_contains_output(sourcePath, (decoding_error, )) - - -def test_permissionDenied(): - """If the source file is not readable, this is reported on standard error.""" - sourcePath = make_temp_file('') - os.chmod(sourcePath, 0) - count, errors = get_errors(sourcePath) - assert count == 1 - assert errors == [('unexpected_error', sourcePath, "Permission denied")] - - -def test_frostedWarning(): - """If the source file has a frosted warning, this is reported as a 'flake'.""" - sourcePath = make_temp_file("import foo") - count, errors = get_errors(sourcePath) - assert count == 1 - assert errors == [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))] - - -@pytest.mark.skipif("PY3") -def test_misencodedFileUTF8(): - """If a source file contains bytes which cannot be decoded, this is reported on stderr.""" - SNOWMAN = chr(0x2603) - source = ("""\ -# coding: ascii -x = "%s" -""" % SNOWMAN).encode('utf-8') - sourcePath = make_temp_file(source) - assert_contains_output(sourcePath, ["%s: problem decoding source\n" % (sourcePath, )]) - - -def test_misencodedFileUTF16(): - """If a source file contains bytes which cannot be decoded, this is reported on stderr.""" - SNOWMAN = chr(0x2603) - source = ("""\ -# coding: ascii -x = "%s" -""" % SNOWMAN).encode('utf-16') - sourcePath = make_temp_file(source) - assert_contains_output(sourcePath, ["%s: problem decoding source\n" % (sourcePath,)]) - - -def test_check_recursive(): - """check_recursive descends into each directory, finding Python files and reporting problems.""" - tempdir = tempfile.mkdtemp() - os.mkdir(os.path.join(tempdir, 'foo')) - file1 = os.path.join(tempdir, 'foo', 'bar.py') - fd = open(file1, 'wb') - fd.write("import baz\n".encode('ascii')) - fd.close() - file2 = os.path.join(tempdir, 'baz.py') - fd = open(file2, 'wb') - fd.write("import contraband".encode('ascii')) - fd.close() - log = [] - reporter = LoggingReporter(log) - warnings = check_recursive([tempdir], reporter) - assert warnings == 2 - assert sorted(log) == sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))), - ('flake', str(UnusedImport(file2, Node(1), 'contraband')))]) diff --git a/CadQuery/Libs/frosted/test/test_doctests.py b/CadQuery/Libs/frosted/test/test_doctests.py deleted file mode 100644 index f9a8df3..0000000 --- a/CadQuery/Libs/frosted/test/test_doctests.py +++ /dev/null @@ -1,238 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -import textwrap - -import pytest -from pies.overrides import * - -from frosted import messages as m - -from .utils import flakes - - -def doctestify(input): - lines = [] - for line in textwrap.dedent(input).splitlines(): - if line.strip() == '': - pass - elif (line.startswith(' ') or - line.startswith('except:') or - line.startswith('except ') or - line.startswith('finally:') or - line.startswith('else:') or - line.startswith('elif ')): - line = "... %s" % line - else: - line = ">>> %s" % line - lines.append(line) - doctestificator = textwrap.dedent('''\ - def doctest_something(): - """ - %s - """ - ''') - return doctestificator % "\n ".join(lines) - - -def test_doubleNestingReportsClosestName(): - """Lines in doctest are a bit different so we can't use the test from TestUndefinedNames.""" - exc = flakes(''' - def doctest_stuff(): - """ - >>> def a(): - ... x = 1 - ... def b(): - ... x = 2 # line 7 in the file - ... def c(): - ... x - ... x = 3 - ... return x - ... return x - ... return x - - """ - ''', m.UndefinedLocal, run_doctests=True).messages[0] - - assert "local variable 'x'" in exc.message and 'line 7' in exc.message - - -def test_importBeforeDoctest(): - flakes(""" - import foo - - def doctest_stuff(): - ''' - >>> foo - ''' - """, run_doctests=True) - - -@pytest.mark.skipif("'todo'") -def test_importBeforeAndInDoctest(): - flakes(''' - import foo - - def doctest_stuff(): - """ - >>> import foo - >>> foo - """ - - foo - ''', m.Redefined, run_doctests=True) - - -def test_importInDoctestAndAfter(): - flakes(''' - def doctest_stuff(): - """ - >>> import foo - >>> foo - """ - - import foo - foo() - ''', run_doctests=True) - - -def test_offsetInDoctests(): - exc = flakes(''' - - def doctest_stuff(): - """ - >>> x # line 5 - """ - - ''', m.UndefinedName, run_doctests=True).messages[0] - assert exc.lineno == 5 - assert exc.col == 12 - - -def test_ignoreErrorsByDefault(): - flakes(''' - - def doctest_stuff(): - """ - >>> x # line 5 - """ - - ''') - -def test_offsetInLambdasInDoctests(): - exc = flakes(''' - - def doctest_stuff(): - """ - >>> lambda: x # line 5 - """ - - ''', m.UndefinedName, run_doctests=True).messages[0] - assert exc.lineno == 5 - assert exc.col == 20 - - -def test_offsetAfterDoctests(): - exc = flakes(''' - - def doctest_stuff(): - """ - >>> x = 5 - """ - - x - - ''', m.UndefinedName, run_doctests=True).messages[0] - assert exc.lineno == 8 - assert exc.col == 0 - - -def test_syntax_errorInDoctest(): - exceptions = flakes( - ''' - def doctest_stuff(): - """ - >>> from # line 4 - >>> fortytwo = 42 - >>> except Exception: - """ - ''', - m.DoctestSyntaxError, - m.DoctestSyntaxError, run_doctests=True).messages - exc = exceptions[0] - assert exc.lineno == 4 - assert exc.col == 26 - exc = exceptions[1] - assert exc.lineno == 6 - assert exc.col == 18 - - -def test_indentationErrorInDoctest(): - exc = flakes(''' - def doctest_stuff(): - """ - >>> if True: - ... pass - """ - ''', m.DoctestSyntaxError, run_doctests=True).messages[0] - assert exc.lineno == 5 - assert exc.col == 16 - - -def test_offsetWithMultiLineArgs(): - (exc1, exc2) = flakes( - ''' - def doctest_stuff(arg1, - arg2, - arg3): - """ - >>> assert - >>> this - """ - ''', - m.DoctestSyntaxError, - m.UndefinedName, run_doctests=True).messages - assert exc1.lineno == 6 - assert exc1.col == 19 - assert exc2.lineno == 7 - assert exc2.col == 12 - - -def test_doctestCanReferToFunction(): - flakes(""" - def foo(): - ''' - >>> foo - ''' - """, run_doctests=True) - - -def test_doctestCanReferToClass(): - flakes(""" - class Foo(): - ''' - >>> Foo - ''' - def bar(self): - ''' - >>> Foo - ''' - """, run_doctests=True) - - -def test_noOffsetSyntaxErrorInDoctest(): - exceptions = flakes(''' - def buildurl(base, *args, **kwargs): - """ - >>> buildurl('/blah.php', ('a', '&'), ('b', '=') - '/blah.php?a=%26&b=%3D' - >>> buildurl('/blah.php', a='&', 'b'='=') - '/blah.php?b=%3D&a=%26' - """ - pass - ''', - m.DoctestSyntaxError, - m.DoctestSyntaxError, run_doctests=True).messages - exc = exceptions[0] - assert exc.lineno == 4 - exc = exceptions[1] - assert exc.lineno == 6 diff --git a/CadQuery/Libs/frosted/test/test_function_calls.py b/CadQuery/Libs/frosted/test/test_function_calls.py deleted file mode 100644 index c81aa31..0000000 --- a/CadQuery/Libs/frosted/test/test_function_calls.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function, unicode_literals - -import sys - -from pies.overrides import * - -from frosted import messages as m - -from .utils import flakes - - -def test_ok(): - flakes(''' - def foo(a): - pass - foo(5) - ''') - - flakes(''' - def foo(a, b=2): - pass - foo(5, b=1) - ''') - - -def test_noCheckDecorators(): - flakes(''' - def decorator(f): - return f - @decorator - def foo(): - pass - foo(42) - ''') - - -def test_tooManyArguments(): - flakes(''' - def foo(): - pass - foo(5) - ''', m.TooManyArguments) - flakes(''' - def foo(a, b): - pass - foo(5, 6, 7) - ''', m.TooManyArguments) - - -def test_tooManyArgumentsVarargs(): - flakes(''' - def foo(a, *args): - pass - foo(1, 2, 3) - ''') - - -def test_unexpectedArgument(): - flakes(''' - def foo(a): - pass - foo(1, b=3) - ''', m.UnexpectedArgument) - - flakes(''' - def foo(a, *args): - pass - foo(1, b=3) - ''', m.UnexpectedArgument) - - flakes(''' - def foo(a, **kwargs): - pass - foo(1, b=3) - ''') - - -def test_multipleValuesForArgument(): - flakes(''' - def foo(a): - pass - foo(5, a=5) - ''', m.MultipleValuesForArgument) - - -def test_tooFewArguments(): - flakes(''' - def foo(a): - pass - foo() - ''', m.TooFewArguments) - - flakes(''' - def foo(a): - pass - foo(*[]) - ''') - - flakes(''' - def foo(a): - pass - foo(**{}) - ''') - - -def test_tooFewArgumentsVarArgs(): - flakes(''' - def foo(a, b, *args): - pass - foo(1) - ''', m.TooFewArguments) - - -if PY3: - def test_kwOnlyArguments(): - flakes(''' - def foo(a, *, b=0): - pass - foo(5, b=2) - ''') - - flakes(''' - def foo(a, *, b=0): - pass - foo(5) - ''') - - flakes(''' - def foo(a, *, b): - pass - foo(5, b=2) - ''') - - flakes(''' - def foo(a, *, b): - pass - foo(5, **{}) - ''') - - flakes(''' - def foo(a, *, b): - pass - foo(1) - ''', m.NeedKwOnlyArgument) - - flakes(''' - def foo(a, *args, b): - pass - foo(1, 2, 3, 4) - ''', m.NeedKwOnlyArgument) -elif PY2: - def test_compoundArguments(): - flakes(''' - def foo(a, (b, c)): - pass - foo(1, [])''') - - flakes(''' - def foo(a, (b, c)): - pass - foo(1, 2, 3)''', m.TooManyArguments) - - flakes(''' - def foo(a, (b, c)): - pass - foo(1)''', m.TooFewArguments) - - flakes(''' - def foo(a, (b, c)): - pass - foo(1, b=2, c=3)''', m.UnexpectedArgument) diff --git a/CadQuery/Libs/frosted/test/test_imports.py b/CadQuery/Libs/frosted/test/test_imports.py deleted file mode 100644 index a9b6803..0000000 --- a/CadQuery/Libs/frosted/test/test_imports.py +++ /dev/null @@ -1,836 +0,0 @@ - -from __future__ import absolute_import, division, print_function, unicode_literals - -from sys import version_info - -import pytest -from pies.overrides import * - -from frosted import messages as m - -from .utils import flakes - - -def test_unusedImport(): - flakes('import fu, bar', m.UnusedImport, m.UnusedImport) - flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport) - - -def test_aliasedImport(): - flakes('import fu as FU, bar as FU', - m.RedefinedWhileUnused, m.UnusedImport) - flakes('from moo import fu as FU, bar as FU', - m.RedefinedWhileUnused, m.UnusedImport) - - -def test_usedImport(): - flakes('import fu; print(fu)') - flakes('from baz import fu; print(fu)') - flakes('import fu; del fu') - - -def test_redefinedWhileUnused(): - flakes('import fu; fu = 3', m.RedefinedWhileUnused) - flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) - flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused) - - -def test_redefinedIf(): - """Test that importing a module twice within an if block does raise a warning.""" - flakes(''' - i = 2 - if i==1: - import os - import os - os.path''', m.RedefinedWhileUnused) - - -def test_redefinedIfElse(): - """Test that importing a module twice in if and else blocks does not raise a warning.""" - flakes(''' - i = 2 - if i==1: - import os - else: - import os - os.path''') - - -def test_redefinedTry(): - """Test that importing a module twice in an try block does raise a warning.""" - flakes(''' - try: - import os - import os - except Exception: - pass - os.path''', m.RedefinedWhileUnused) - - -def test_redefinedTryExcept(): - """Test that importing a module twice in an try and except block does not raise a warning.""" - flakes(''' - try: - import os - except Exception: - import os - os.path''') - - -def test_redefinedTryNested(): - """Test that importing a module twice using a nested try/except and if blocks does not issue a warning.""" - flakes(''' - try: - if True: - if True: - import os - except Exception: - import os - os.path''') - - -def test_redefinedTryExceptMulti(): - flakes(""" - try: - from aa import mixer - except AttributeError: - from bb import mixer - except RuntimeError: - from cc import mixer - except Exception: - from dd import mixer - mixer(123) - """) - - -def test_redefinedTryElse(): - flakes(""" - try: - from aa import mixer - except ImportError: - pass - else: - from bb import mixer - mixer(123) - """, m.RedefinedWhileUnused) - - -def test_redefinedTryExceptElse(): - flakes(""" - try: - import funca - except ImportError: - from bb import funca - from bb import funcb - else: - from bbb import funcb - print(funca, funcb) - """) - - -def test_redefinedTryExceptFinally(): - flakes(""" - try: - from aa import a - except ImportError: - from bb import a - finally: - a = 42 - print(a) - """) - - -def test_redefinedTryExceptElseFinally(): - flakes(""" - try: - import b - except ImportError: - b = Ellipsis - from bb import a - else: - from aa import a - finally: - a = 42 - print(a, b) - """) - - -def test_redefinedByFunction(): - flakes(''' - import fu - def fu(): - pass - ''', m.RedefinedWhileUnused) - - -def test_redefinedInNestedFunction(): - """Test that shadowing a global name with a nested function definition generates a warning.""" - flakes(''' - import fu - def bar(): - def baz(): - def fu(): - pass - ''', m.RedefinedWhileUnused, m.UnusedImport) - - -def test_redefinedByClass(): - flakes(''' - import fu - class fu: - pass - ''', m.RedefinedWhileUnused) - - -def test_redefinedBySubclass(): - """If an imported name is redefined by a class statement - - which also uses that name in the bases list, no warning is emitted. - - """ - flakes(''' - from fu import bar - class bar(bar): - pass - ''') - - -def test_redefinedInClass(): - """Test that shadowing a global with a class attribute does not produce a warning.""" - flakes(''' - import fu - class bar: - fu = 1 - print(fu) - ''') - - -def test_usedInFunction(): - flakes(''' - import fu - def fun(): - print(fu) - ''') - - -def test_shadowedByParameter(): - flakes(''' - import fu - def fun(fu): - print(fu) - ''', m.UnusedImport) - - flakes(''' - import fu - def fun(fu): - print(fu) - print(fu) - ''') - - -def test_newAssignment(): - flakes('fu = None') - - -def test_usedInGetattr(): - flakes('import fu; fu.bar.baz') - flakes('import fu; "bar".fu.baz', m.UnusedImport) - - -def test_usedInSlice(): - flakes('import fu; print(fu.bar[1:])') - - -def test_usedInIfBody(): - flakes(''' - import fu - if True: print(fu) - ''') - - -def test_usedInIfConditional(): - flakes(''' - import fu - if fu: pass - ''') - - -def test_usedInElifConditional(): - flakes(''' - import fu - if False: pass - elif fu: pass - ''') - - -def test_usedInElse(): - flakes(''' - import fu - if False: pass - else: print(fu) - ''') - - -def test_usedInCall(): - flakes('import fu; fu.bar()') - - -def test_usedInClass(): - flakes(''' - import fu - class bar: - bar = fu - ''') - - -def test_usedInClassBase(): - flakes(''' - import fu - class bar(object, fu.baz): - pass - ''') - - -def test_notUsedInNestedScope(): - flakes(''' - import fu - def bleh(): - pass - print(fu) - ''') - - -def test_usedInFor(): - flakes(''' - import fu - for bar in range(9): - print(fu) - ''') - - -def test_usedInForElse(): - flakes(''' - import fu - for bar in range(10): - pass - else: - print(fu) - ''') - - -def test_redefinedByFor(): - flakes(''' - import fu - for fu in range(2): - pass - ''', m.RedefinedWhileUnused) - - -def test_shadowedByFor(): - """Test that shadowing a global name with a for loop variable generates a warning.""" - flakes(''' - import fu - fu.bar() - for fu in (): - pass - ''', m.ImportShadowedByLoopVar) - - -def test_shadowedByForDeep(): - """Test that shadowing a global name with a for loop variable nested in a tuple unpack generates a warning.""" - flakes(''' - import fu - fu.bar() - for (x, y, z, (a, b, c, (fu,))) in (): - pass - ''', m.ImportShadowedByLoopVar) - - -def test_usedInReturn(): - flakes(''' - import fu - def fun(): - return fu - ''') - - -def test_usedInOperators(): - flakes('import fu; 3 + fu.bar') - flakes('import fu; 3 % fu.bar') - flakes('import fu; 3 - fu.bar') - flakes('import fu; 3 * fu.bar') - flakes('import fu; 3 ** fu.bar') - flakes('import fu; 3 / fu.bar') - flakes('import fu; 3 // fu.bar') - flakes('import fu; -fu.bar') - flakes('import fu; ~fu.bar') - flakes('import fu; 1 == fu.bar') - flakes('import fu; 1 | fu.bar') - flakes('import fu; 1 & fu.bar') - flakes('import fu; 1 ^ fu.bar') - flakes('import fu; 1 >> fu.bar') - flakes('import fu; 1 << fu.bar') - - -def test_usedInAssert(): - flakes('import fu; assert fu.bar') - - -def test_usedInSubscript(): - flakes('import fu; fu.bar[1]') - - -def test_usedInLogic(): - flakes('import fu; fu and False') - flakes('import fu; fu or False') - flakes('import fu; not fu.bar') - - -def test_usedInList(): - flakes('import fu; [fu]') - - -def test_usedInTuple(): - flakes('import fu; (fu,)') - - -def test_usedInTry(): - flakes(''' - import fu - try: fu - except Exception: pass - ''') - - -def test_usedInExcept(): - flakes(''' - import fu - try: fu - except Exception: pass - ''') - - -def test_redefinedByExcept(): - as_exc = ', ' if version_info < (2, 6) else ' as ' - flakes(''' - import fu - try: pass - except Exception%sfu: pass - ''' % as_exc, m.RedefinedWhileUnused) - - -def test_usedInRaise(): - flakes(''' - import fu - raise fu.bar - ''') - - -def test_usedInYield(): - flakes(''' - import fu - def gen(): - yield fu - ''') - - -def test_usedInDict(): - flakes('import fu; {fu:None}') - flakes('import fu; {1:fu}') - - -def test_usedInParameterDefault(): - flakes(''' - import fu - def f(bar=fu): - pass - ''') - - -def test_usedInAttributeAssign(): - flakes('import fu; fu.bar = 1') - - -def test_usedInKeywordArg(): - flakes('import fu; fu.bar(stuff=fu)') - - -def test_usedInAssignment(): - flakes('import fu; bar=fu') - flakes('import fu; n=0; n+=fu') - - -def test_usedInListComp(): - flakes('import fu; [fu for _ in range(1)]') - flakes('import fu; [1 for _ in range(1) if fu]') - - -def test_redefinedByListComp(): - flakes('import fu; [1 for fu in range(1)]', m.RedefinedWhileUnused) - - -def test_usedInTryFinally(): - flakes(''' - import fu - try: pass - finally: fu - ''') - - flakes(''' - import fu - try: fu - finally: pass - ''') - - -def test_usedInWhile(): - flakes(''' - import fu - while 0: - fu - ''') - - flakes(''' - import fu - while fu: pass - ''') - - -def test_usedInGlobal(): - flakes(''' - import fu - def f(): global fu - ''', m.UnusedImport) - - -@pytest.mark.skipif("version_info >= (3,)") -def test_usedInBackquote(): - flakes('import fu; `fu`') - - -def test_usedInExec(): - if version_info < (3,): - exec_stmt = 'exec "print 1" in fu.bar' - else: - exec_stmt = 'exec("print(1)", fu.bar)' - flakes('import fu; %s' % exec_stmt) - - -def test_usedInLambda(): - flakes('import fu; lambda: fu') - - -def test_shadowedByLambda(): - flakes('import fu; lambda fu: fu', m.UnusedImport) - - -def test_usedInSliceObj(): - flakes('import fu; "meow"[::fu]') - - -def test_unusedInNestedScope(): - flakes(''' - def bar(): - import fu - fu - ''', m.UnusedImport, m.UndefinedName) - - -def test_methodsDontUseClassScope(): - flakes(''' - class bar: - import fu - def fun(): - fu - ''', m.UnusedImport, m.UndefinedName) - - -def test_nestedFunctionsNestScope(): - flakes(''' - def a(): - def b(): - fu - import fu - ''') - - -def test_nestedClassAndFunctionScope(): - flakes(''' - def a(): - import fu - class b: - def c(): - print(fu) - ''') - - -def test_importStar(): - flakes('from fu import *', m.ImportStarUsed, ignore_frosted_errors=[]) - - -def test_packageImport(): - """If a dotted name is imported and used, no warning is reported.""" - flakes(''' - import fu.bar - fu.bar - ''') - - -def test_unusedPackageImport(): - """If a dotted name is imported and not used, an unused import warning is reported.""" - flakes('import fu.bar', m.UnusedImport) - - -def test_duplicateSubmoduleImport(): - """If a submodule of a package is imported twice, an unused - - import warning and a redefined while unused warning are reported. - - """ - flakes(''' - import fu.bar, fu.bar - fu.bar - ''', m.RedefinedWhileUnused) - flakes(''' - import fu.bar - import fu.bar - fu.bar - ''', m.RedefinedWhileUnused) - - -def test_differentSubmoduleImport(): - """If two different submodules of a package are imported, - - no duplicate import warning is reported for the package. - - """ - flakes(''' - import fu.bar, fu.baz - fu.bar, fu.baz - ''') - flakes(''' - import fu.bar - import fu.baz - fu.bar, fu.baz - ''') - - -def test_assignRHSFirst(): - flakes('import fu; fu = fu') - flakes('import fu; fu, bar = fu') - flakes('import fu; [fu, bar] = fu') - flakes('import fu; fu += fu') - - -def test_tryingMultipleImports(): - flakes(''' - try: - import fu - except ImportError: - import bar as fu - fu - ''') - - -def test_nonGlobalDoesNotRedefine(): - flakes(''' - import fu - def a(): - fu = 3 - return fu - fu - ''') - - -def test_functionsRunLater(): - flakes(''' - def a(): - fu - import fu - ''') - - -def test_functionNamesAreBoundNow(): - flakes(''' - import fu - def fu(): - fu - fu - ''', m.RedefinedWhileUnused) - - -def test_ignoreNonImportRedefinitions(): - flakes('a = 1; a = 2') - - -@pytest.mark.skipif("'todo'") -def test_importingForImportError(): - flakes(''' - try: - import fu - except ImportError: - pass - ''') - - -@pytest.mark.skipif("'todo: requires evaluating attribute access'") -def test_importedInClass(): - """Imports in class scope can be used through.""" - flakes(''' - class c: - import i - def __init__(): - i - ''') - - -def test_futureImport(): - """__future__ is special.""" - flakes('from __future__ import division') - flakes(''' - "docstring is allowed before future import" - from __future__ import division - ''') - - -def test_futureImportFirst(): - """__future__ imports must come before anything else.""" - flakes(''' - x = 5 - from __future__ import division - ''', m.LateFutureImport) - flakes(''' - from foo import bar - from __future__ import division - bar - ''', m.LateFutureImport) - - -def test_ignoredInFunction(): - """An C{__all__} definition does not suppress unused import warnings in a function scope.""" - flakes(''' - def foo(): - import bar - __all__ = ["bar"] - ''', m.UnusedImport, m.UnusedVariable) - - -def test_ignoredInClass(): - """An C{__all__} definition does not suppress unused import warnings in a class scope.""" - flakes(''' - class foo: - import bar - __all__ = ["bar"] - ''', m.UnusedImport) - - -def test_warningSuppressed(): - """If a name is imported and unused but is named in C{__all__}, no warning is reported.""" - flakes(''' - import foo - __all__ = ["foo"] - ''') - - -def test_unrecognizable(): - """If C{__all__} is defined in a way that can't be recognized statically, it is ignored.""" - flakes(''' - import foo - __all__ = ["f" + "oo"] - ''', m.UnusedImport) - flakes(''' - import foo - __all__ = [] + ["foo"] - ''', m.UnusedImport) - - -def test_unboundExported(): - """If C{__all__} includes a name which is not bound, a warning is emitted.""" - flakes(''' - __all__ = ["foo"] - ''', m.UndefinedExport) - - # Skip this in __init__.py though, since the rules there are a little - # different. - for filename in ["foo/__init__.py", "__init__.py"]: - flakes(''' - __all__ = ["foo"] - ''', filename=filename, **{'ignore_frosted_errors_for___init__.py': ['E101', 'E103']}) - - -def test_importStarExported(): - """Do not report undefined if import * is used""" - flakes(''' - from foolib import * - __all__ = ["foo"] - ''', m.ImportStarUsed) - - -def test_usedInGenExp(): - """Using a global in a generator expression results in no warnings.""" - flakes('import fu; (fu for _ in range(1))') - flakes('import fu; (1 for _ in range(1) if fu)') - - -def test_redefinedByGenExp(): - """ Re-using a global name as the loop variable for a generator - - expression results in a redefinition warning. - - """ - flakes('import fu; (1 for fu in range(1))', m.RedefinedWhileUnused, m.UnusedImport) - - -def test_usedAsDecorator(): - """Using a global name in a decorator statement results in no warnings, but - - using an undefined name in a decorator statement results in an undefined - name warning. - - """ - flakes(''' - from interior import decorate - @decorate - def f(): - return "hello" - ''') - - flakes(''' - from interior import decorate - @decorate('value') - def f(): - return "hello" - ''') - - flakes(''' - @decorate - def f(): - return "hello" - ''', m.UndefinedName) - - -def test_usedAsClassDecorator(): - """Using an imported name as a class decorator results in no warnings - - but using an undefined name as a class decorator results in an undefined name warning. - - """ - flakes(''' - from interior import decorate - @decorate - class foo: - pass - ''') - - flakes(''' - from interior import decorate - @decorate("foo") - class bar: - pass - ''') - - flakes(''' - @decorate - class foo: - pass - ''', m.UndefinedName) diff --git a/CadQuery/Libs/frosted/test/test_noqa.py b/CadQuery/Libs/frosted/test/test_noqa.py deleted file mode 100644 index d09d4f4..0000000 --- a/CadQuery/Libs/frosted/test/test_noqa.py +++ /dev/null @@ -1,53 +0,0 @@ -from frosted import messages as m -from frosted.api import _noqa_lines, _re_noqa, check -from frosted.reporter import Reporter - -from .utils import LoggingReporter, flakes - - -def test_regex(): - # simple format - assert _re_noqa.search('#noqa') - assert _re_noqa.search('# noqa') - # simple format is strict, must be at start of comment - assert not _re_noqa.search('# foo noqa') - - # verbose format (not strict like simple format) - assert _re_noqa.search('#frosted:noqa') - assert _re_noqa.search('# frosted: noqa') - assert _re_noqa.search('# foo frosted: noqa') - - -def test_checker_ignore_lines(): - # ignore same line - flakes('from fu import *', ignore_lines=[1]) - # don't ignore different line - flakes('from fu import *', m.ImportStarUsed, ignore_lines=[2]) - - -def test_noqa_lines(): - assert _noqa_lines('from fu import bar; bar') == [] - assert _noqa_lines('from fu import * # noqa; bar') == [1] - assert _noqa_lines('from fu import * #noqa\nbar\nfoo # frosted: noqa') == [1, 3] - - -def test_check_integration(): - """ make sure all the above logic comes together correctly in the check() function """ - output = [] - reporter = LoggingReporter(output) - - result = check('from fu import *', 'test', reporter, not_ignore_frosted_errors=['E103']) - - # errors reported - assert result == 1 - assert "unable to detect undefined names" in output.pop(0)[1] - - # same test, but with ignore set - output = [] - reporter = LoggingReporter(output) - - result = check('from fu import * # noqa', 'test', reporter) - - # errors reported - assert result == 0 - assert len(output) == 0 diff --git a/CadQuery/Libs/frosted/test/test_other.py b/CadQuery/Libs/frosted/test/test_other.py deleted file mode 100644 index 752e39c..0000000 --- a/CadQuery/Libs/frosted/test/test_other.py +++ /dev/null @@ -1,886 +0,0 @@ -"""Tests for various Frosted behavior.""" - -from __future__ import absolute_import, division, print_function, unicode_literals - -from sys import version_info - -import pytest -from pies.overrides import * - -from frosted import messages as m - -from .utils import flakes - - -def test_duplicateArgs(): - flakes('def fu(bar, bar): pass', m.DuplicateArgument) - - -@pytest.mark.skipif("'todo: this requires finding all assignments in the function body first'") -def test_localReferencedBeforeAssignment(): - flakes(''' - a = 1 - def f(): - a; a=1 - f() - ''', m.UndefinedName) - - -def test_redefinedInListComp(): - """Test that shadowing a variable in a list comprehension raises a warning.""" - flakes(''' - a = 1 - [1 for a, b in [(1, 2)]] - ''', m.RedefinedInListComp) - flakes(''' - class A: - a = 1 - [1 for a, b in [(1, 2)]] - ''', m.RedefinedInListComp) - flakes(''' - def f(): - a = 1 - [1 for a, b in [(1, 2)]] - ''', m.RedefinedInListComp) - flakes(''' - [1 for a, b in [(1, 2)]] - [1 for a, b in [(1, 2)]] - ''') - flakes(''' - for a, b in [(1, 2)]: - pass - [1 for a, b in [(1, 2)]] - ''') - - -def test_redefinedInGenerator(): - """Test that reusing a variable in a generator does not raise a warning.""" - flakes(''' - a = 1 - (1 for a, b in [(1, 2)]) - ''') - flakes(''' - class A: - a = 1 - list(1 for a, b in [(1, 2)]) - ''') - flakes(''' - def f(): - a = 1 - (1 for a, b in [(1, 2)]) - ''', m.UnusedVariable) - flakes(''' - (1 for a, b in [(1, 2)]) - (1 for a, b in [(1, 2)]) - ''') - flakes(''' - for a, b in [(1, 2)]: - pass - (1 for a, b in [(1, 2)]) - ''') - - -@pytest.mark.skipif('''version_info < (2, 7)''') -def test_redefinedInSetComprehension(): - """Test that reusing a variable in a set comprehension does not raise a warning.""" - flakes(''' - a = 1 - {1 for a, b in [(1, 2)]} - ''') - flakes(''' - class A: - a = 1 - {1 for a, b in [(1, 2)]} - ''') - flakes(''' - def f(): - a = 1 - {1 for a, b in [(1, 2)]} - ''', m.UnusedVariable) - flakes(''' - {1 for a, b in [(1, 2)]} - {1 for a, b in [(1, 2)]} - ''') - flakes(''' - for a, b in [(1, 2)]: - pass - {1 for a, b in [(1, 2)]} - ''') - - -@pytest.mark.skipif('''version_info < (2, 7)''') -def test_redefinedInDictComprehension(): - """Test that reusing a variable in a dict comprehension does not raise a warning.""" - flakes(''' - a = 1 - {1: 42 for a, b in [(1, 2)]} - ''') - flakes(''' - class A: - a = 1 - {1: 42 for a, b in [(1, 2)]} - ''') - flakes(''' - def f(): - a = 1 - {1: 42 for a, b in [(1, 2)]} - ''', m.UnusedVariable) - flakes(''' - {1: 42 for a, b in [(1, 2)]} - {1: 42 for a, b in [(1, 2)]} - ''') - flakes(''' - for a, b in [(1, 2)]: - pass - {1: 42 for a, b in [(1, 2)]} - ''') - - -def test_redefinedFunction(): - """Test that shadowing a function definition with another one raises a warning.""" - flakes(''' - def a(): pass - def a(): pass - ''', m.RedefinedWhileUnused) - - -def test_redefinedClassFunction(): - """Test that shadowing a function definition in a class suite with another one raises a warning.""" - flakes(''' - class A: - def a(): pass - def a(): pass - ''', m.RedefinedWhileUnused) - - -def test_redefinedIfElseFunction(): - """Test that shadowing a function definition twice in an if and else block does not raise a warning.""" - flakes(''' - if True: - def a(): pass - else: - def a(): pass - ''') - - -def test_redefinedIfFunction(): - """Test that shadowing a function definition within an if block raises a warning.""" - flakes(''' - if True: - def a(): pass - def a(): pass - ''', m.RedefinedWhileUnused) - - -def test_redefinedTryExceptFunction(): - """Test that shadowing a function definition twice in try and except block does not raise a warning.""" - flakes(''' - try: - def a(): pass - except Exception: - def a(): pass - ''') - - -def test_redefinedTryFunction(): - """Test that shadowing a function definition within a try block raises a warning.""" - flakes(''' - try: - def a(): pass - def a(): pass - except Exception: - pass - ''', m.RedefinedWhileUnused) - - -def test_redefinedIfElseInListComp(): - """Test that shadowing a variable in a list comprehension in an if and else block does not raise a warning.""" - flakes(''' - if False: - a = 1 - else: - [a for a in '12'] - ''') - - -def test_redefinedElseInListComp(): - """Test that shadowing a variable in a list comprehension in an else (or if) block raises a warning.""" - flakes(''' - if False: - pass - else: - a = 1 - [a for a in '12'] - ''', m.RedefinedInListComp) - - -def test_functionDecorator(): - """Test that shadowing a function definition with a decorated version of that function does not raise a warning.""" - flakes(''' - from somewhere import somedecorator - - def a(): pass - a = somedecorator(a) - ''') - - -def test_classFunctionDecorator(): - """Test that shadowing a function definition in a class suite with a - decorated version of that function does not raise a warning. - - """ - flakes(''' - class A: - def a(): pass - a = classmethod(a) - ''') - - -@pytest.mark.skipif('''version_info < (2, 6)''') -def test_modernProperty(): - flakes(""" - class A: - @property - def t(): - pass - @t.setter - def t(self, value): - pass - @t.deleter - def t(): - pass - """) - - -def test_unaryPlus(): - """Don't die on unary +.""" - flakes('+1') - - -def test_undefinedBaseClass(): - """If a name in the base list of a class definition is undefined, a warning is emitted.""" - flakes(''' - class foo(foo): - pass - ''', m.UndefinedName) - - -def test_classNameUndefinedInClassBody(): - """If a class name is used in the body of that class's definition and the - - name is not already defined, a warning is emitted. - - """ - flakes(''' - class foo: - foo - ''', m.UndefinedName) - - -def test_classNameDefinedPreviously(): - """If a class name is used in the body of that class's definition and the - name was previously defined in some other way, no warning is emitted. - - """ - flakes(''' - foo = None - class foo: - foo - ''') - - -def test_classRedefinition(): - """If a class is defined twice in the same module, a warning is emitted.""" - flakes(''' - class Foo: - pass - class Foo: - pass - ''', m.RedefinedWhileUnused) - - -def test_functionRedefinedAsClass(): - """If a function is redefined as a class, a warning is emitted.""" - flakes(''' - def Foo(): - pass - class Foo: - pass - ''', m.RedefinedWhileUnused) - - -def test_classRedefinedAsFunction(): - """If a class is redefined as a function, a warning is emitted.""" - flakes(''' - class Foo: - pass - def Foo(): - pass - ''', m.RedefinedWhileUnused) - - -@pytest.mark.skipif("'todo: Too hard to make this warn but other cases stay silent'") -def test_doubleAssignment(): - """If a variable is re-assigned to without being used, no warning is emitted.""" - flakes(''' - x = 10 - x = 20 - ''', m.RedefinedWhileUnused) - - -def test_doubleAssignmentConditionally(): - """If a variable is re-assigned within a conditional, no warning is emitted.""" - flakes(''' - x = 10 - if True: - x = 20 - ''') - - -def test_doubleAssignmentWithUse(): - """If a variable is re-assigned to after being used, no warning is emitted.""" - flakes(''' - x = 10 - y = x * 2 - x = 20 - ''') - - -def test_comparison(): - """If a defined name is used on either side of any of the six comparison operators, no warning is emitted.""" - flakes(''' - x = 10 - y = 20 - x < y - x <= y - x == y - x != y - x >= y - x > y - ''') - - -def test_identity(): - """If a defined name is used on either side of an identity test, no warning is emitted.""" - flakes(''' - x = 10 - y = 20 - x is y - x is not y - ''') - - -def test_containment(): - """If a defined name is used on either side of a containment test, no warning is emitted.""" - flakes(''' - x = 10 - y = 20 - x in y - x not in y - ''') - - -def test_loopControl(): - """break and continue statements are supported.""" - flakes(''' - for x in [1, 2]: - break - ''') - flakes(''' - for x in [1, 2]: - continue - ''') - - -def test_ellipsis(): - """Ellipsis in a slice is supported.""" - flakes(''' - [1, 2][...] - ''') - - -def test_extendedSlice(): - """Extended slices are supported.""" - flakes(''' - x = 3 - [1, 2][x,:] - ''') - - -def test_varAugmentedAssignment(): - """Augmented assignment of a variable is supported. - - We don't care about var refs. - - """ - flakes(''' - foo = 0 - foo += 1 - ''') - - -def test_attrAugmentedAssignment(): - """Augmented assignment of attributes is supported. - - We don't care about attr refs. - - """ - flakes(''' - foo = None - foo.bar += foo.baz - ''') - - -def test_unusedVariable(): - """Warn when a variable in a function is assigned a value that's never used.""" - flakes(''' - def a(): - b = 1 - ''', m.UnusedVariable) - - -def test_unusedVariableAsLocals(): - """Using locals() it is perfectly valid to have unused variables.""" - flakes(''' - def a(): - b = 1 - return locals() - ''') - - -def test_unusedVariableNoLocals(): - """Using locals() in wrong scope should not matter.""" - flakes(''' - def a(): - locals() - def a(): - b = 1 - return - ''', m.UnusedVariable) - - -def test_assignToGlobal(): - """Assigning to a global and then not using that global is perfectly - acceptable. - - Do not mistake it for an unused local variable. - - """ - flakes(''' - b = 0 - def a(): - global b - b = 1 - ''') - - -@pytest.mark.skipif('''version_info < (3,)''') -def test_assignToNonlocal(): - """Assigning to a nonlocal and then not using that binding is perfectly - acceptable. - - Do not mistake it for an unused local variable. - - """ - flakes(''' - b = b'0' - def a(): - nonlocal b - b = b'1' - ''') - - -def test_assignToMember(): - """Assigning to a member of another object and then not using that member - variable is perfectly acceptable. - - Do not mistake it for an unused local variable. - - """ - # XXX: Adding this test didn't generate a failure. Maybe not - # necessary? - flakes(''' - class b: - pass - def a(): - b.foo = 1 - ''') - - -def test_assignInForLoop(): - """Don't warn when a variable in a for loop is assigned to but not used.""" - flakes(''' - def f(): - for i in range(10): - pass - ''') - - -def test_assignInListComprehension(): - """Don't warn when a variable in a list comprehension is assigned to but not used.""" - flakes(''' - def f(): - [None for i in range(10)] - ''') - - -def test_generatorExpression(): - """Don't warn when a variable in a generator expression is assigned to but not used.""" - flakes(''' - def f(): - (None for i in range(10)) - ''') - - -def test_assignmentInsideLoop(): - """Don't warn when a variable assignment occurs lexically after its use.""" - flakes(''' - def f(): - x = None - for i in range(10): - if i > 2: - return x - x = i * 2 - ''') - - -def test_tupleUnpacking(): - """Don't warn when a variable included in tuple unpacking is unused. - - It's very common for variables in a tuple unpacking assignment to be unused in good Python code, so warning will - only create false positives. - - """ - flakes(''' - def f(): - (x, y) = 1, 2 - ''') - - -def test_listUnpacking(): - """Don't warn when a variable included in list unpacking is unused.""" - flakes(''' - def f(): - [x, y] = [1, 2] - ''') - - -def test_closedOver(): - """Don't warn when the assignment is used in an inner function.""" - flakes(''' - def barMaker(): - foo = 5 - def bar(): - return foo - return bar - ''') - - -def test_doubleClosedOver(): - """Don't warn when the assignment is used in an inner function, even if - that inner function itself is in an inner function.""" - flakes(''' - def barMaker(): - foo = 5 - def bar(): - def baz(): - return foo - return bar - ''') - - -def test_tracebackhideSpecialVariable(): - """Do not warn about unused local variable __tracebackhide__, which is a - special variable for py.test.""" - flakes(""" - def helper(): - __tracebackhide__ = True - """) - - -def test_ifexp(): - """Test C{foo if bar else baz} statements.""" - flakes("a = 'moo' if True else 'oink'") - flakes("a = foo if True else 'oink'", m.UndefinedName) - flakes("a = 'moo' if True else bar", m.UndefinedName) - - -def test_withStatementNoNames(): - """No warnings are emitted for using inside or after a nameless statement a name defined beforehand.""" - flakes(''' - from __future__ import with_statement - bar = None - with open("foo"): - bar - bar - ''') - - -def test_withStatementSingleName(): - """No warnings are emitted for using a name defined by a statement within the suite or afterwards.""" - flakes(''' - from __future__ import with_statement - with open('foo') as bar: - bar - bar - ''') - - -def test_withStatementAttributeName(): - """No warnings are emitted for using an attribute as the target of a statement.""" - flakes(''' - from __future__ import with_statement - import foo - with open('foo') as foo.bar: - pass - ''') - - -def test_withStatementSubscript(): - """No warnings are emitted for using a subscript as the target of a statement.""" - flakes(''' - from __future__ import with_statement - import foo - with open('foo') as foo[0]: - pass - ''') - - -def test_withStatementSubscriptUndefined(): - """An undefined name warning is emitted if the subscript used as the target of a with statement is not defined.""" - flakes(''' - from __future__ import with_statement - import foo - with open('foo') as foo[bar]: - pass - ''', m.UndefinedName) - - -def test_withStatementTupleNames(): - """No warnings are emitted for using any of the tuple of names defined - - by a statement within the suite or afterwards. - - """ - flakes(''' - from __future__ import with_statement - with open('foo') as (bar, baz): - bar, baz - bar, baz - ''') - - -def test_withStatementListNames(): - """No warnings are emitted for using any of the list of names defined by - - a statement within the suite or afterwards. - - """ - flakes(''' - from __future__ import with_statement - with open('foo') as [bar, baz]: - bar, baz - bar, baz - ''') - - -def test_withStatementComplicatedTarget(): - """ If the target of a statement uses any or all of the valid forms - for that part of the grammar - (See: http://docs.python.org/reference/compound_stmts.html#the-with-statement), - the names involved are checked both for definedness and any bindings - created are respected in the suite of the statement and afterwards. - - """ - flakes(''' - from __future__ import with_statement - c = d = e = g = h = i = None - with open('foo') as [(a, b), c[d], e.f, g[h:i]]: - a, b, c, d, e, g, h, i - a, b, c, d, e, g, h, i - ''') - - -def test_withStatementSingleNameUndefined(): - """An undefined name warning is emitted if the name first defined by a statement is used before the statement.""" - flakes(''' - from __future__ import with_statement - bar - with open('foo') as bar: - pass - ''', m.UndefinedName) - - -def test_withStatementTupleNamesUndefined(): - """ An undefined name warning is emitted if a name first defined by a the - tuple-unpacking form of the statement is used before the statement. - - """ - flakes(''' - from __future__ import with_statement - baz - with open('foo') as (bar, baz): - pass - ''', m.UndefinedName) - - -def test_withStatementSingleNameRedefined(): - """A redefined name warning is emitted if a name bound by an import is - rebound by the name defined by a statement. - - """ - flakes(''' - from __future__ import with_statement - import bar - with open('foo') as bar: - pass - ''', m.RedefinedWhileUnused) - - -def test_withStatementTupleNamesRedefined(): - """ A redefined name warning is emitted if a name bound by an import is - rebound by one of the names defined by the tuple-unpacking form of a - statement. - - """ - flakes(''' - from __future__ import with_statement - import bar - with open('foo') as (bar, baz): - pass - ''', m.RedefinedWhileUnused) - - -def test_withStatementUndefinedInside(): - """An undefined name warning is emitted if a name is used inside the body - of a statement without first being bound. - - """ - flakes(''' - from __future__ import with_statement - with open('foo') as bar: - baz - ''', m.UndefinedName) - - -def test_withStatementNameDefinedInBody(): - """A name defined in the body of a statement can be used after the body ends without warning.""" - flakes(''' - from __future__ import with_statement - with open('foo') as bar: - baz = 10 - baz - ''') - - -def test_withStatementUndefinedInExpression(): - """An undefined name warning is emitted if a name in the I{test} expression of a statement is undefined.""" - flakes(''' - from __future__ import with_statement - with bar as baz: - pass - ''', m.UndefinedName) - - flakes(''' - from __future__ import with_statement - with bar as bar: - pass - ''', m.UndefinedName) - - -@pytest.mark.skipif('''version_info < (2, 7)''') -def test_dictComprehension(): - """Dict comprehensions are properly handled.""" - flakes(''' - a = {1: x for x in range(10)} - ''') - - -@pytest.mark.skipif('''version_info < (2, 7)''') -def test_setComprehensionAndLiteral(): - """Set comprehensions are properly handled.""" - flakes(''' - a = {1, 2, 3} - b = {x for x in range(10)} - ''') - - -def test_exceptionUsedInExcept(): - as_exc = ', ' if version_info < (2, 6) else ' as ' - flakes(''' - try: pass - except Exception%se: e - ''' % as_exc) - - flakes(''' - def download_review(): - try: pass - except Exception%se: e - ''' % as_exc) - - -def test_exceptWithoutNameInFunction(): - """Don't issue false warning when an unnamed exception is used. - - Previously, there would be a false warning, but only when the try..except was in a function - - """ - flakes(''' - import tokenize - def foo(): - try: pass - except tokenize.TokenError: pass - ''') - - -def test_exceptWithoutNameInFunctionTuple(): - """Don't issue false warning when an unnamed exception is used. - - This example catches a tuple of exception types. - - """ - flakes(''' - import tokenize - def foo(): - try: pass - except (tokenize.TokenError, IndentationError): pass - ''') - - -def test_augmentedAssignmentImportedFunctionCall(): - """Consider a function that is called on the right part of an augassign operation to be used.""" - flakes(''' - from foo import bar - baz = 0 - baz += bar() - ''') - - -@pytest.mark.skipif('''version_info < (3, 3)''') -def test_yieldFromUndefined(): - """Test yield from statement.""" - flakes(''' - def bar(): - yield from foo() - ''', m.UndefinedName) - - -def test_bareExcept(): - """ - Issue a warning when using bare except:. - """ - flakes(''' - try: - pass - except: - pass - ''', m.BareExcept) - - -def test_access_debug(): - """Test accessing __debug__ returns no errors""" - flakes(''' - if __debug__: - print("success!") - print(__debug__) - ''') diff --git a/CadQuery/Libs/frosted/test/test_return_with_arguments_inside_generator.py b/CadQuery/Libs/frosted/test/test_return_with_arguments_inside_generator.py deleted file mode 100644 index 7d76c18..0000000 --- a/CadQuery/Libs/frosted/test/test_return_with_arguments_inside_generator.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -from sys import version_info - -import pytest -from pies.overrides import * - -from frosted import messages as m - -from .utils import flakes - - -@pytest.mark.skipif("version_info >= (3,)") -def test_return(): - flakes(''' - class a: - def b(): - for x in a.c: - if x: - yield x - return a - ''', m.ReturnWithArgsInsideGenerator) - - -@pytest.mark.skipif("version_info >= (3,)") -def test_returnNone(): - flakes(''' - def a(): - yield 12 - return None - ''', m.ReturnWithArgsInsideGenerator) - - -@pytest.mark.skipif("version_info >= (3,)") -def test_returnYieldExpression(): - flakes(''' - def a(): - b = yield a - return b - ''', m.ReturnWithArgsInsideGenerator) - - -@pytest.mark.skipif("version_info >= (3,)") -def test_return_with_args_inside_generator_not_duplicated(): - # doubly nested - should still only complain once - flakes(''' - def f0(): - def f1(): - yield None - return None - ''', m.ReturnWithArgsInsideGenerator) - - # triple nested - should still only complain once - flakes(''' - def f0(): - def f1(): - def f2(): - yield None - return None - ''', m.ReturnWithArgsInsideGenerator) - - -@pytest.mark.skipif("version_info >= (3,)") -def test_no_false_positives_for_return_inside_generator(): - # doubly nested - should still only complain once - flakes(''' - def f(): - def g(): - yield None - return g - ''') diff --git a/CadQuery/Libs/frosted/test/test_script.py b/CadQuery/Libs/frosted/test/test_script.py deleted file mode 100644 index 31e509a..0000000 --- a/CadQuery/Libs/frosted/test/test_script.py +++ /dev/null @@ -1,181 +0,0 @@ -"""frosted/test/test_script.py. - -Tests functionality (integration testing) that require starting a full frosted instance against input files - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" - -from __future__ import absolute_import, division, print_function - -import os -import shutil -import subprocess -import sys -import tempfile - -import pytest -from pies.overrides import * - -import frosted -from frosted.api import iter_source_code -from frosted.messages import UnusedImport - -from .utils import Node - -FROSTED_BINARY = os.path.join(os.path.dirname(frosted.__file__), 'main.py') - - -def setup_function(function): - globals()['TEMP_DIR'] = tempfile.mkdtemp() - globals()['TEMP_FILE_PATH'] = os.path.join(TEMP_DIR, 'temp') - - -def teardown_function(function): - shutil.rmtree(TEMP_DIR) - - -def make_empty_file(*parts): - assert parts - fpath = os.path.join(TEMP_DIR, *parts) - fd = open(fpath, 'a') - fd.close() - return fpath - - -def test_emptyDirectory(): - """There are no Python files in an empty directory.""" - assert list(iter_source_code([TEMP_DIR])) == [] - - -def test_singleFile(): - """If the directory contains one Python file - iter_source_code will find it""" - childpath = make_empty_file('foo.py') - assert list(iter_source_code([TEMP_DIR])) == [childpath] - - -def test_onlyPythonSource(): - """Files that are not Python source files are not included.""" - make_empty_file('foo.pyc') - assert list(iter_source_code([TEMP_DIR])) == [] - - -def test_recurses(): - """If the Python files are hidden deep down in child directories, we will find them.""" - os.mkdir(os.path.join(TEMP_DIR, 'foo')) - apath = make_empty_file('foo', 'a.py') - os.mkdir(os.path.join(TEMP_DIR, 'bar')) - bpath = make_empty_file('bar', 'b.py') - cpath = make_empty_file('c.py') - assert sorted(iter_source_code([TEMP_DIR])) == sorted([apath, bpath, cpath]) - - -def test_multipleDirectories(): - """iter_source_code can be given multiple directories - it will recurse into each of them""" - foopath = os.path.join(TEMP_DIR, 'foo') - barpath = os.path.join(TEMP_DIR, 'bar') - os.mkdir(foopath) - apath = make_empty_file('foo', 'a.py') - os.mkdir(barpath) - bpath = make_empty_file('bar', 'b.py') - assert sorted(iter_source_code([foopath, barpath])) == sorted([apath, bpath]) - - -def test_explicitFiles(): - """If one of the paths given to iter_source_code is not a directory but a - file, it will include that in its output.""" - epath = make_empty_file('e.py') - assert list(iter_source_code([epath])) == [epath] - - -def run_frosted(paths, stdin=None): - """Launch a subprocess running frosted.""" - env = native_dict(os.environ) - env['PYTHONPATH'] = os.pathsep.join(sys.path) - command = [sys.executable, FROSTED_BINARY] - command.extend(paths) - if stdin: - p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = p.communicate(stdin) - else: - p = subprocess.Popen(command, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = p.communicate() - rv = p.wait() - if PY3: - stdout = stdout.decode('utf-8') - stderr = stderr.decode('utf-8') - - return (stdout, stderr, rv) - - -def test_goodFile(): - """When a Python source file is all good, the return code is zero and no - - messages are printed to either stdout or stderr. - - """ - fd = open(TEMP_FILE_PATH, 'a') - fd.close() - d = run_frosted([TEMP_FILE_PATH]) - assert d == ('', '', 0) - - -def test_fileWithFlakes(): - """ When a Python source file has warnings, - - the return code is non-zero and the warnings are printed to stdout - - """ - fd = open(TEMP_FILE_PATH, 'wb') - fd.write("import contraband\n".encode('ascii')) - fd.close() - d = run_frosted([TEMP_FILE_PATH]) - expected = UnusedImport(TEMP_FILE_PATH, Node(1), 'contraband') - assert d[0].strip() == expected.message.strip() - - -@pytest.mark.skipif("sys.version_info >= (3,)") -def test_non_unicode_slash_u(): - """ Ensure \ u doesn't cause a unicode decode error """ - fd = open(TEMP_FILE_PATH, 'wb') - fd.write('"""Example: C:\\foobar\\unit-tests\\test.py"""'.encode('ascii')) - fd.close() - d = run_frosted([TEMP_FILE_PATH]) - assert d == ('', '', 0) - - -def test_errors(): - """ When frosted finds errors with the files it's given, (if they don't exist, say), - - then the return code is non-zero and the errors are printed to stderr - - """ - d = run_frosted([TEMP_FILE_PATH, '-r']) - error_msg = '%s: No such file or directory\n' % (TEMP_FILE_PATH,) - assert d == ('', error_msg, 1) - - -def test_readFromStdin(): - """If no arguments are passed to C{frosted} then it reads from stdin.""" - d = run_frosted(['-'], stdin='import contraband'.encode('ascii')) - - expected = UnusedImport('', Node(1), 'contraband') - assert d[0].strip() == expected.message.strip() - - -@pytest.mark.skipif("sys.version_info >= (3,)") -def test_print_statement_python2(): - d = run_frosted(['-'], stdin='print "Hello, Frosted"'.encode('ascii')) - assert d == ('', '', 0) diff --git a/CadQuery/Libs/frosted/test/test_undefined_names.py b/CadQuery/Libs/frosted/test/test_undefined_names.py deleted file mode 100644 index f962b35..0000000 --- a/CadQuery/Libs/frosted/test/test_undefined_names.py +++ /dev/null @@ -1,407 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -from sys import version_info - -import pytest -from pies.overrides import * - -from _ast import PyCF_ONLY_AST -from frosted import messages as m -from frosted import checker - -from.utils import flakes - - -def test_undefined(): - flakes('bar', m.UndefinedName) - - -def test_definedInListComp(): - flakes('[a for a in range(10) if a]') - - -def test_functionsNeedGlobalScope(): - flakes(''' - class a: - def b(): - fu - fu = 1 - ''') - - -def test_builtins(): - flakes('range(10)') - - -def test_builtinWindowsError(): - """WindowsError is sometimes a builtin name, so no warning is emitted for using it.""" - flakes('WindowsError') - - -def test_magicGlobalsFile(): - """Use of the __file magic global should not emit an undefined name - warning.""" - flakes('__file__') - - -def test_magicGlobalsBuiltins(): - """Use of the __builtins magic global should not emit an undefined name warning.""" - flakes('__builtins__') - - -def test_magicGlobalImport(): - """Use of the __import__ magic global should not emit an undefined name warning.""" - flakes('__import__') - -def test_magicGlobalsName(): - """Use of the __name magic global should not emit an undefined name warning.""" - flakes('__name__') - - -def test_magicGlobalsPath(): - """Use of the __path magic global should not emit an undefined name warning, - - if you refer to it from a file called __init__.py. - - """ - flakes('__path__', m.UndefinedName) - flakes('__path__', filename='package/__init__.py') - - -def test_globalImportStar(): - """Can't find undefined names with import *.""" - flakes('from fu import *; bar', m.ImportStarUsed) - - -def test_localImportStar(): - """A local import * still allows undefined names to be found in upper scopes.""" - flakes(''' - def a(): - from fu import * - bar - ''', m.ImportStarUsed, m.UndefinedName) - - -@pytest.mark.skipif("version_info >= (3,)") -def test_unpackedParameter(): - """Unpacked function parameters create bindings.""" - flakes(''' - def a((bar, baz)): - bar; baz - ''') - - -@pytest.mark.skipif("'todo'") -def test_definedByGlobal(): - """"global" can make an otherwise undefined name in another function defined.""" - flakes(''' - def a(): global fu; fu = 1 - def b(): fu - ''') - - -def test_globalInGlobalScope(): - """A global statement in the global scope is ignored.""" - flakes(''' - global x - def foo(): - print(x) - ''', m.UndefinedName) - - -def test_del(): - """Del deletes bindings.""" - flakes('a = 1; del a; a', m.UndefinedName) - - -def test_delGlobal(): - """Del a global binding from a function.""" - flakes(''' - a = 1 - def f(): - global a - del a - a - ''') - - -def test_delUndefined(): - """Del an undefined name.""" - flakes('del a', m.UndefinedName) - - -def test_globalFromNestedScope(): - """Global names are available from nested scopes.""" - flakes(''' - a = 1 - def b(): - def c(): - a - ''') - - -def test_laterRedefinedGlobalFromNestedScope(): - """Test that referencing a local name that shadows a global, before it is - defined, generates a warning.""" - flakes(''' - a = 1 - def fun(): - a - a = 2 - return a - ''', m.UndefinedLocal) - - -def test_laterRedefinedGlobalFromNestedScope2(): - """Test that referencing a local name in a nested scope that shadows a - global declared in an enclosing scope, before it is defined, generates a - warning.""" - flakes(''' - a = 1 - def fun(): - global a - def fun2(): - a - a = 2 - return a - ''', m.UndefinedLocal) - - -def test_intermediateClassScopeIgnored(): - """If a name defined in an enclosing scope is shadowed by a local variable - and the name is used locally before it is bound, an unbound local warning - is emitted, even if there is a class scope between the enclosing scope and - the local scope.""" - flakes(''' - def f(): - x = 1 - class g: - def h(): - a = x - x = None - print(x, a) - print(x) - ''', m.UndefinedLocal) - - -def test_doubleNestingReportsClosestName(): - """Test that referencing a local name in a nested scope that shadows a - variable declared in two different outer scopes before it is defined in the - innermost scope generates an UnboundLocal warning which refers to the - nearest shadowed name.""" - exc = flakes(''' - def a(): - x = 1 - def b(): - x = 2 # line 5 - def c(): - x - x = 3 - return x - return x - return x - ''', m.UndefinedLocal).messages[0] - assert 'x' in exc.message - assert str(5) in exc.message - - -def test_laterRedefinedGlobalFromNestedScope3(): - """Test that referencing a local name in a nested scope that shadows a - global, before it is defined, generates a warning.""" - flakes(''' - def fun(): - a = 1 - def fun2(): - a - a = 1 - return a - return a - ''', m.UndefinedLocal) - - -def test_undefinedAugmentedAssignment(): - flakes( - ''' - def f(seq): - a = 0 - seq[a] += 1 - seq[b] /= 2 - c[0] *= 2 - a -= 3 - d += 4 - e[any] = 5 - ''', - m.UndefinedName, # b - m.UndefinedName, # c - m.UndefinedName, m.UnusedVariable, # d - m.UndefinedName, # e - ) - - -def test_nestedClass(): - """Nested classes can access enclosing scope.""" - flakes(''' - def f(foo): - class C: - bar = foo - def f(): - return foo - return C() - - f(123).f() - ''') - - -def test_badNestedClass(): - """Free variables in nested classes must bind at class creation.""" - flakes(''' - def f(): - class C: - bar = foo - foo = 456 - return foo - f() - ''', m.UndefinedName) - - -def test_definedAsStarArgs(): - """Star and double-star arg names are defined.""" - flakes(''' - def f(a, *b, **c): - print(a, b, c) - ''') - - -@pytest.mark.skipif("version_info < (3,)") -def test_definedAsStarUnpack(): - """Star names in unpack are defined.""" - flakes(''' - a, *b = range(10) - print(a, b) - ''') - flakes(''' - *a, b = range(10) - print(a, b) - ''') - flakes(''' - a, *b, c = range(10) - print(a, b, c) - ''') - - -@pytest.mark.skipif("version_info < (3,)") -def test_keywordOnlyArgs(): - """Keyword-only arg names are defined.""" - flakes(''' - def f(*, a, b=None): - print(a, b) - ''') - - flakes(''' - import default_b - def f(*, a, b=default_b): - print(a, b) - ''') - - -@pytest.mark.skipif("version_info < (3,)") -def test_keywordOnlyArgsUndefined(): - """Typo in kwonly name.""" - flakes(''' - def f(*, a, b=default_c): - print(a, b) - ''', m.UndefinedName) - - -@pytest.mark.skipif("version_info < (3,)") -def test_annotationUndefined(): - """Undefined annotations.""" - flakes(''' - from abc import note1, note2, note3, note4, note5 - def func(a: note1, *args: note2, - b: note3=12, **kw: note4) -> note5: pass - ''') - - flakes(''' - def func(): - d = e = 42 - def func(a: {1, d}) -> (lambda c: e): pass - ''') - - -@pytest.mark.skipif("version_info < (3,)") -def test_metaClassUndefined(): - flakes(''' - from abc import ABCMeta - class A(metaclass=ABCMeta): pass - ''') - - -def test_definedInGenExp(): - """Using the loop variable of a generator expression results in no - warnings.""" - flakes('(a for a in %srange(10) if a)' % - ('x' if version_info < (3,) else '')) - - -def test_undefinedWithErrorHandler(): - """Some compatibility code checks explicitly for NameError. - - It should not trigger warnings. - - """ - flakes(''' - try: - socket_map - except NameError: - socket_map = {} - ''') - flakes(''' - try: - _memoryview.contiguous - except (NameError, AttributeError): - raise RuntimeError("Python >= 3.3 is required") - ''') - # If NameError is not explicitly handled, generate a warning - flakes(''' - try: - socket_map - except Exception: - socket_map = {} - ''', m.UndefinedName) - flakes(''' - try: - socket_map - except Exception: - socket_map = {} - ''', m.UndefinedName) - - -def test_definedInClass(): - """Defined name for generator expressions and dict/set comprehension.""" - flakes(''' - class A: - T = range(10) - - Z = (x for x in T) - L = [x for x in T] - B = dict((i, str(i)) for i in T) - ''') - - if version_info >= (2, 7): - flakes(''' - class A: - T = range(10) - - X = {x for x in T} - Y = {x:x for x in T} - ''') - - -def test_impossibleContext(): - """A Name node with an unrecognized context results in a RuntimeError being raised.""" - tree = compile("x = 10", "", "exec", PyCF_ONLY_AST) - # Make it into something unrecognizable. - tree.body[0].targets[0].ctx = object() - with pytest.raises(RuntimeError): - checker.Checker(tree) diff --git a/CadQuery/Libs/frosted/test/utils.py b/CadQuery/Libs/frosted/test/utils.py deleted file mode 100644 index 747a5a2..0000000 --- a/CadQuery/Libs/frosted/test/utils.py +++ /dev/null @@ -1,49 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -import textwrap -from collections import namedtuple - -from pies.overrides import * - -from frosted import checker - -PyCF_ONLY_AST = 1024 -__all__ = ['flakes', 'Node', 'LoggingReporter'] - - -def flakes(input, *expectedOutputs, **kw): - tree = compile(textwrap.dedent(input), "", "exec", PyCF_ONLY_AST) - results = checker.Checker(tree, **kw) - outputs = [message.type for message in results.messages] - expectedOutputs = list(expectedOutputs) - outputs.sort(key=lambda t: t.name) - expectedOutputs.sort(key=lambda t: t.name) - assert outputs == expectedOutputs, ('\n' - 'for input:\n' - '%s\n' - 'expected outputs:\n' - '%r\n' - 'but got:\n' - '%s') % (input, expectedOutputs, - '\n'.join([str(o) for o in results.messages])) - return results - - -class Node(namedtuple('Node', ['lineno', 'col_offset'])): - """A mock AST Node.""" - - def __new__(cls, lineno, col_offset=0): - return super(Node, cls).__new__(cls, lineno, col_offset) - - -class LoggingReporter(namedtuple('LoggingReporter', ['log'])): - """A mock Reporter implementation.""" - - def flake(self, message): - self.log.append(('flake', str(message))) - - def unexpected_error(self, filename, message): - self.log.append(('unexpected_error', filename, message)) - - def syntax_error(self, filename, msg, lineno, offset, line): - self.log.append(('syntax_error', filename, msg, lineno, offset, line)) diff --git a/CadQuery/Libs/future/__init__.py b/CadQuery/Libs/future/__init__.py index c3cddc7..a93381f 100644 --- a/CadQuery/Libs/future/__init__.py +++ b/CadQuery/Libs/future/__init__.py @@ -1,23 +1,20 @@ """ -future: Easy, safe support for Python 3/2 compatibility +future: Easy, safe support for Python 2/3 compatibility ======================================================= -``future`` is the missing compatibility layer between Python 3 and Python -2. It allows you to use a single, clean Python 3.x-compatible codebase to -support both Python 3 and Python 2 with minimal overhead. - -Notable projects that use ``future`` for Python 2/3 compatibility are -`Mezzanine `_ and `ObsPy `_. +``future`` is the missing compatibility layer between Python 2 and Python +3. It allows you to use a single, clean Python 3.x-compatible codebase to +support both Python 2 and Python 3 with minimal overhead. It is designed to be used as follows:: from __future__ import (absolute_import, division, print_function, unicode_literals) - from future.builtins import ( - bytes, dict, int, list, object, range, str, - ascii, chr, hex, input, next, oct, open, - pow, round, super, - filter, map, zip) + from builtins import ( + bytes, dict, int, list, object, range, str, + ascii, chr, hex, input, next, oct, open, + pow, round, super, + filter, map, zip) followed by predominantly standard, idiomatic Python 3 code that then runs similarly on Python 2.6/2.7 and Python 3.3+. @@ -30,13 +27,31 @@ versus 2, to provide their Python 3 semantics. Standard library reorganization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``from future import standard_library`` provides a context-manager called -``hooks`` that installs import hooks (PEP 3108) to allow renamed and -moved standard library modules to be imported from their new Py3 locations. +``future`` supports the standard library reorganization (PEP 3108) through the +following Py3 interfaces: + + >>> # Top-level packages with Py3 names provided on Py2: + >>> import configparser + >>> import html.parser + >>> import queue + >>> import tkinter.dialog + >>> import xmlrpc.client + >>> # etc. + + >>> # Aliases provided for extensions to existing Py2 module names: + >>> from future.standard_library import install_aliases + >>> install_aliases() + + >>> from collections import Counter, OrderedDict # backported to Py2.6 + >>> from collections import UserDict, UserList, UserString + >>> import urllib.request + >>> from itertools import filterfalse, zip_longest + >>> from subprocess import getoutput, getstatusoutput Automatic conversion -------------------- + An included script called `futurize `_ aids in converting code (from either Python 2 or Python 3) to code compatible with both @@ -62,7 +77,7 @@ Credits Licensing --------- -Copyright 2013-2014 Python Charmers Pty Ltd, Australia. +Copyright 2013-2015 Python Charmers Pty Ltd, Australia. The software is distributed under an MIT licence. See LICENSE.txt. """ @@ -70,10 +85,10 @@ The software is distributed under an MIT licence. See LICENSE.txt. __title__ = 'future' __author__ = 'Ed Schofield' __license__ = 'MIT' -__copyright__ = 'Copyright 2014 Python Charmers Pty Ltd' +__copyright__ = 'Copyright 2013-2015 Python Charmers Pty Ltd' __ver_major__ = 0 __ver_minor__ = 14 -__ver_patch__ = 2 +__ver_patch__ = 3 __ver_sub__ = '' __version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) diff --git a/CadQuery/Libs/future/backports/misc.py b/CadQuery/Libs/future/backports/misc.py index b69547c..03c68c1 100644 --- a/CadQuery/Libs/future/backports/misc.py +++ b/CadQuery/Libs/future/backports/misc.py @@ -11,7 +11,7 @@ collections.Counter (for Python 2.6) from math import ceil as oldceil import subprocess -from future.utils import iteritems, PY26 +from future.utils import iteritems, itervalues, PY26 def ceil(x): diff --git a/CadQuery/Libs/future/types/newint.py b/CadQuery/Libs/future/types/newint.py index 7157f95..1917b87 100644 --- a/CadQuery/Libs/future/types/newint.py +++ b/CadQuery/Libs/future/types/newint.py @@ -299,20 +299,29 @@ class newint(with_metaclass(BaseNewInt, long)): used to represent the integer. If signed is False and a negative integer is given, an OverflowError is raised. """ - if signed: - raise NotImplementedError("Not yet implemented. Please contribute a patch at http://python-future.org") + if length < 0: + raise ValueError("length argument must be non-negative") + if length == 0 and self == 0: + return newbytes() + if signed and self < 0: + bits = length * 8 + num = (2**bits) + self + if num <= 0: + raise OverflowError("int too smal to convert") else: if self < 0: raise OverflowError("can't convert negative int to unsigned") num = self if byteorder not in ('little', 'big'): raise ValueError("byteorder must be either 'little' or 'big'") - if length < 0: - raise ValueError("length argument must be non-negative") - if length == 0 and num == 0: - return newbytes() h = b'%x' % num s = newbytes((b'0'*(len(h) % 2) + h).zfill(length*2).decode('hex')) + if signed: + high_set = s[0] & 0x80 + if self > 0 and high_set: + raise OverflowError("int too big to convert") + if self < 0 and not high_set: + raise OverflowError("int too small to convert") if len(s) > length: raise OverflowError("int too big to convert") return s if byteorder == 'big' else s[::-1] @@ -335,8 +344,6 @@ class newint(with_metaclass(BaseNewInt, long)): The signed keyword-only argument indicates whether two's complement is used to represent the integer. """ - if signed: - raise NotImplementedError("Not yet implemented. Please contribute a patch at http://python-future.org") if byteorder not in ('little', 'big'): raise ValueError("byteorder must be either 'little' or 'big'") if isinstance(mybytes, unicode): @@ -351,6 +358,8 @@ class newint(with_metaclass(BaseNewInt, long)): # The encode() method has been disabled by newbytes, but Py2's # str has it: num = int(native(b).encode('hex'), 16) + if signed and (b[0] & 0x80): + num = num - (2 ** (len(b)*8)) return cls(num) diff --git a/CadQuery/Libs/future/types/newrange.py b/CadQuery/Libs/future/types/newrange.py index 2438d20..432f11a 100644 --- a/CadQuery/Libs/future/types/newrange.py +++ b/CadQuery/Libs/future/types/newrange.py @@ -1,5 +1,5 @@ """ -Nearly identical to xrange.py, by Dan Crosta, from +Nearly identical to xrange.py, by Dan Crosta, from https://github.com/dcrosta/xrange.git @@ -18,10 +18,8 @@ From Dan Crosta's README: https://late.am/post/2012/06/18/what-the-heck-is-an-xrange """ -from math import ceil from collections import Sequence, Iterator - -from future.utils import PY3 +from itertools import islice class newrange(Sequence): @@ -58,16 +56,28 @@ class newrange(Sequence): self._step = step self._len = (stop - start) // step + bool((stop - start) % step) + @property + def start(self): + return self._start + + @property + def stop(self): + return self._stop + + @property + def step(self): + return self._step + def __repr__(self): if self._step == 1: return 'range(%d, %d)' % (self._start, self._stop) return 'range(%d, %d, %d)' % (self._start, self._stop, self._step) def __eq__(self, other): - return isinstance(other, newrange) and \ - self._start == other._start and \ - self._stop == other._stop and \ - self._step == other._step + return (isinstance(other, newrange) and + (self._len == 0 == other._len or + (self._start, self._step, self._len) == + (other._start, other._step, self._len))) def __len__(self): return self._len @@ -97,12 +107,7 @@ class newrange(Sequence): return False def __reversed__(self): - """Return a range which represents a sequence whose - contents are the same as the sequence this range - represents, but in the opposite order.""" - sign = self._step / abs(self._step) - last = self._start + ((self._len - 1) * self._step) - return newrange(last, self._start - sign, -1 * self._step) + return iter(self[::-1]) def __getitem__(self, index): """Return the element at position ``index`` in the sequence @@ -121,56 +126,35 @@ class newrange(Sequence): """Return a range which represents the requested slce of the sequence represented by this range. """ - start, stop, step = slce.start, slce.stop, slce.step - if step == 0: - raise ValueError('slice step cannot be 0') - - start = start or self._start - stop = stop or self._stop - if start < 0: - start = max(0, start + self._len) - if stop < 0: - stop = max(start, stop + self._len) - - if step is None or step > 0: - return newrange(start, stop, step or 1) - else: - rv = reversed(self) - rv._step = step - return rv + start, stop, step = slce.indices(self._len) + return newrange(self._start + self._step*start, + self._start + stop, + self._step * step) def __iter__(self): """Return an iterator which enumerates the elements of the sequence this range represents.""" - return rangeiterator(self) + return range_iterator(self) -class rangeiterator(Iterator): +class range_iterator(Iterator): """An iterator for a :class:`range`. """ - - def __init__(self, rangeobj): - self._range = rangeobj - - # Intialize the "last outputted value" to the value - # just before the first value; this simplifies next() - self._last = self._range._start - self._range._step - self._count = 0 + def __init__(self, range_): + self._stepper = islice(_count(range_.start, range_.step), len(range_)) def __iter__(self): - """An iterator is already an iterator, so return ``self``. - """ return self def next(self): - """Return the next element in the sequence represented - by the range we are iterating, or raise StopIteration - if we have passed the end of the sequence.""" - self._last += self._range._step - self._count += 1 - if self._count > self._range._len: - raise StopIteration() - return self._last + return next(self._stepper) + + +# itertools.count in Py 2.6 doesn't accept a step parameter +def _count(start=0, step=1): + while True: + yield start + start += step __all__ = ['newrange'] diff --git a/CadQuery/Libs/pep8.py b/CadQuery/Libs/pep8.py index f605f18..d7907e5 100644 --- a/CadQuery/Libs/pep8.py +++ b/CadQuery/Libs/pep8.py @@ -2,6 +2,7 @@ # pep8.py - Check Python source code formatting, according to PEP 8 # Copyright (C) 2006-2009 Johann C. Rocholl # Copyright (C) 2009-2014 Florent Xicluna +# Copyright (C) 2014 Ian Lee # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -46,8 +47,6 @@ W warnings """ from __future__ import with_statement -__version__ = '1.5.7' - import os import sys import re @@ -63,13 +62,21 @@ try: except ImportError: from ConfigParser import RawConfigParser -DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__' -DEFAULT_IGNORE = 'E123,E226,E24' -if sys.platform == 'win32': - DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') -else: - DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or - os.path.expanduser('~/.config'), 'pep8') +__version__ = '1.6.2' + +DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' +DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' +try: + if sys.platform == 'win32': + USER_CONFIG = os.path.expanduser(r'~\.pep8') + else: + USER_CONFIG = os.path.join( + os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), + 'pep8' + ) +except ImportError: + USER_CONFIG = None + PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 @@ -101,8 +108,9 @@ ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') DOCSTRING_REGEX = re.compile(r'u?r?["\']') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') -COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)') -COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^[({ ]+\s+(in|is)\s') +COMPARE_SINGLETON_REGEX = re.compile(r'\b(None|False|True)?\s*([=!]=)' + r'\s*(?(1)|(None|False|True))\b') +COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^][)(}{ ]+\s+(in|is)\s') COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type' r'|\s*\(\s*([^)]*[^ )])\s*\))') KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) @@ -353,20 +361,25 @@ def indentation(logical_line, previous_logical, indent_char, Okay: a = 1 Okay: if a == 0:\n a = 1 E111: a = 1 + E114: # a = 1 Okay: for item in items:\n pass E112: for item in items:\npass + E115: for item in items:\n# Hi\n pass Okay: a = 1\nb = 2 E113: a = 1\n b = 2 + E116: a = 1\n # b = 2 """ - if indent_char == ' ' and indent_level % 4: - yield 0, "E111 indentation is not a multiple of four" + c = 0 if logical_line else 3 + tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)" + if indent_level % 4: + yield 0, tmpl % (1 + c, "indentation is not a multiple of four") indent_expect = previous_logical.endswith(':') if indent_expect and indent_level <= previous_indent_level: - yield 0, "E112 expected an indented block" - if indent_level > previous_indent_level and not indent_expect: - yield 0, "E113 unexpected indentation" + yield 0, tmpl % (2 + c, "expected an indented block") + elif not indent_expect and indent_level > previous_indent_level: + yield 0, tmpl % (3 + c, "unexpected indentation") def continued_indentation(logical_line, tokens, indent_level, hang_closing, @@ -422,6 +435,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, indent_chances = {} last_indent = tokens[0][2] visual_indent = None + last_token_multiline = False # for each depth, memorize the visual indent column indent = [last_indent[1]] if verbose >= 3: @@ -501,8 +515,9 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, yield start, "%s continuation line %s" % error # look for visual indenting - if (parens[row] and token_type not in (tokenize.NL, tokenize.COMMENT) - and not indent[depth]): + if (parens[row] and + token_type not in (tokenize.NL, tokenize.COMMENT) and + not indent[depth]): indent[depth] = start[1] indent_chances[start[1]] = True if verbose >= 4: @@ -675,7 +690,7 @@ def missing_whitespace_around_operator(logical_line, tokens): if need_space is True or need_space[1]: # A needed trailing space was not found yield prev_end, "E225 missing whitespace around operator" - else: + elif prev_text != '**': code, optype = 'E226', 'arithmetic' if prev_text == '%': code, optype = 'E228', 'modulo' @@ -743,6 +758,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): Okay: boolean(a != b) Okay: boolean(a <= b) Okay: boolean(a >= b) + Okay: def foo(arg: int = 42): E251: def complex(real, imag = 0.0): E251: return magic(r = real, i = imag) @@ -750,6 +766,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): parens = 0 no_space = False prev_end = None + annotated_func_arg = False + in_def = logical_line.startswith('def') message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: @@ -758,15 +776,22 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = False if start != prev_end: yield (prev_end, message) - elif token_type == tokenize.OP: + if token_type == tokenize.OP: if text == '(': parens += 1 elif text == ')': parens -= 1 - elif parens and text == '=': + elif in_def and text == ':' and parens == 1: + annotated_func_arg = True + elif parens and text == ',' and parens == 1: + annotated_func_arg = False + elif parens and text == '=' and not annotated_func_arg: no_space = True if start != prev_end: yield (prev_end, message) + if not parens: + annotated_func_arg = False + prev_end = end @@ -787,6 +812,7 @@ def whitespace_before_comment(logical_line, tokens): E262: x = x + 1 #Increment x E262: x = x + 1 # Increment x E265: #Block comment + E266: ### Block comment """ prev_end = (0, 0) for token_type, text, start, end, line in tokens: @@ -797,13 +823,15 @@ def whitespace_before_comment(logical_line, tokens): yield (prev_end, "E261 at least two spaces before inline comment") symbol, sp, comment = text.partition(' ') - bad_prefix = symbol not in ('#', '#:') + bad_prefix = symbol not in '#:' and (symbol.lstrip('#')[:1] or '#') if inline_comment: - if bad_prefix or comment[:1].isspace(): + if bad_prefix or comment[:1] in WHITESPACE: yield start, "E262 inline comment should start with '# '" - elif bad_prefix: - if text.rstrip('#') and (start[0] > 1 or symbol[1] != '!'): + elif bad_prefix and (bad_prefix != '!' or start[0] > 1): + if bad_prefix != '#': yield start, "E265 block comment should start with '# '" + elif comment: + yield start, "E266 too many leading '#' for block comment" elif token_type != tokenize.NL: prev_end = end @@ -827,6 +855,56 @@ def imports_on_separate_lines(logical_line): yield found, "E401 multiple imports on one line" +def module_imports_on_top_of_file( + logical_line, indent_level, checker_state, noqa): + r"""Imports are always put at the top of the file, just after any module + comments and docstrings, and before module globals and constants. + + Okay: import os + Okay: # this is a comment\nimport os + Okay: '''this is a module docstring'''\nimport os + Okay: r'''this is a module docstring'''\nimport os + Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y + Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y + E402: a=1\nimport os + E402: 'One string'\n"Two string"\nimport os + E402: a=1\nfrom sys import x + + Okay: if x:\n import os + """ + def is_string_literal(line): + if line[0] in 'uUbB': + line = line[1:] + if line and line[0] in 'rR': + line = line[1:] + return line and (line[0] == '"' or line[0] == "'") + + allowed_try_keywords = ('try', 'except', 'else', 'finally') + + if indent_level: # Allow imports in conditional statements or functions + return + if not logical_line: # Allow empty lines or comments + return + if noqa: + return + line = logical_line + if line.startswith('import ') or line.startswith('from '): + if checker_state.get('seen_non_imports', False): + yield 0, "E402 module level import not at top of file" + elif any(line.startswith(kw) for kw in allowed_try_keywords): + # Allow try, except, else, finally keywords intermixed with imports in + # order to support conditional importing + return + elif is_string_literal(line): + # The first literal is a docstring, allow it. Otherwise, report error. + if checker_state.get('seen_docstring', False): + checker_state['seen_non_imports'] = True + else: + checker_state['seen_docstring'] = True + else: + checker_state['seen_non_imports'] = True + + def compound_statements(logical_line): r"""Compound statements (on the same line) are generally discouraged. @@ -834,6 +912,9 @@ def compound_statements(logical_line): on the same line, never do this for multi-clause statements. Also avoid folding such long lines! + Always use a def statement instead of an assignment statement that + binds a lambda expression directly to a name. + Okay: if foo == 'blah':\n do_blah_thing() Okay: do_one() Okay: do_two() @@ -847,20 +928,30 @@ def compound_statements(logical_line): E701: try: something() E701: finally: cleanup() E701: if foo == 'blah': one(); two(); three() - E702: do_one(); do_two(); do_three() E703: do_four(); # useless semicolon + E704: def f(x): return 2*x + E731: f = lambda x: 2*x """ line = logical_line last_char = len(line) - 1 found = line.find(':') while -1 < found < last_char: before = line[:found] - if (before.count('{') <= before.count('}') and # {'a': 1} (dict) - before.count('[') <= before.count(']') and # [1:2] (slice) - before.count('(') <= before.count(')') and # (Python 3 annotation) - not LAMBDA_REGEX.search(before)): # lambda x: x - yield found, "E701 multiple statements on one line (colon)" + if ((before.count('{') <= before.count('}') and # {'a': 1} (dict) + before.count('[') <= before.count(']') and # [1:2] (slice) + before.count('(') <= before.count(')'))): # (annotation) + lambda_kw = LAMBDA_REGEX.search(before) + if lambda_kw: + before = line[:lambda_kw.start()].rstrip() + if before[-1:] == '=' and isidentifier(before[:-1].strip()): + yield 0, ("E731 do not assign a lambda expression, use a " + "def") + break + if before.startswith('def '): + yield 0, "E704 multiple statements on one line (def)" + else: + yield found, "E701 multiple statements on one line (colon)" found = line.find(':', found + 1) found = line.find(';') while -1 < found: @@ -885,10 +976,15 @@ def explicit_line_join(logical_line, tokens): Okay: aaa = [123,\n 123] Okay: aaa = ("bbb "\n "ccc") Okay: aaa = "bbb " \\n "ccc" + Okay: aaa = 123 # \\ """ prev_start = prev_end = parens = 0 + comment = False + backslash = None for token_type, text, start, end, line in tokens: - if start[0] != prev_start and parens and backslash: + if token_type == tokenize.COMMENT: + comment = True + if start[0] != prev_start and parens and backslash and not comment: yield backslash, "E502 the backslash is redundant between brackets" if end[0] != prev_end: if line.rstrip('\r\n').endswith('\\'): @@ -905,6 +1001,45 @@ def explicit_line_join(logical_line, tokens): parens -= 1 +def break_around_binary_operator(logical_line, tokens): + r""" + Avoid breaks before binary operators. + + The preferred place to break around a binary operator is after the + operator, not before it. + + W503: (width == 0\n + height == 0) + W503: (width == 0\n and height == 0) + + Okay: (width == 0 +\n height == 0) + Okay: foo(\n -x) + Okay: foo(x\n []) + Okay: x = '''\n''' + '' + Okay: foo(x,\n -y) + Okay: foo(x, # comment\n -y) + """ + def is_binary_operator(token_type, text): + # The % character is strictly speaking a binary operator, but the + # common usage seems to be to put it next to the format parameters, + # after a line break. + return ((token_type == tokenize.OP or text in ['and', 'or']) and + text not in "()[]{},:.;@=%") + + line_break = False + unary_context = True + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + continue + if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: + line_break = True + else: + if (is_binary_operator(token_type, text) and line_break and + not unary_context): + yield start, "W503 line break before binary operator" + unary_context = text in '([{,;' + line_break = False + + def comparison_to_singleton(logical_line, noqa): r"""Comparison to singletons should use "is" or "is not". @@ -913,7 +1048,9 @@ def comparison_to_singleton(logical_line, noqa): Okay: if arg is not None: E711: if arg != None: + E711: if None == arg: E712: if arg == True: + E712: if False == arg: Also, beware of writing if x when you really mean if x is not None -- e.g. when testing whether a variable or argument that defaults to None was @@ -922,8 +1059,9 @@ def comparison_to_singleton(logical_line, noqa): """ match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) if match: - same = (match.group(1) == '==') - singleton = match.group(2) + singleton = match.group(1) or match.group(3) + same = (match.group(2) == '==') + msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton) if singleton in ('None',): code = 'E711' @@ -932,7 +1070,7 @@ def comparison_to_singleton(logical_line, noqa): nonzero = ((singleton == 'True' and same) or (singleton == 'False' and not same)) msg += " or 'if %scond:'" % ('' if nonzero else 'not ') - yield match.start(1), ("%s comparison to %s should be %s" % + yield match.start(2), ("%s comparison to %s should be %s" % (code, singleton, msg)) @@ -957,7 +1095,7 @@ def comparison_negative(logical_line): yield pos, "E714 test for object identity should be 'is not'" -def comparison_type(logical_line): +def comparison_type(logical_line, noqa): r"""Object type comparisons should always use isinstance(). Do not compare types directly. @@ -973,7 +1111,7 @@ def comparison_type(logical_line): Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) - if match: + if match and not noqa: inst = match.group(1) if inst and isidentifier(inst) and inst not in SINGLETONS: return # Allow comparison for types which are not obvious @@ -1039,7 +1177,7 @@ if '' == ''.encode(): """Read the source code.""" with open(filename, 'rU') as f: return f.readlines() - isidentifier = re.compile(r'[a-zA-Z_]\w*').match + isidentifier = re.compile(r'[a-zA-Z_]\w*$').match stdin_get_value = sys.stdin.read else: # Python 3 @@ -1138,10 +1276,13 @@ def normalize_paths(value, parent=os.curdir): Return a list of absolute paths. """ - if not value or isinstance(value, list): + if not value: + return [] + if isinstance(value, list): return value paths = [] for path in value.split(','): + path = path.strip() if '/' in path: path = os.path.abspath(os.path.join(parent, path)) paths.append(path.rstrip('/')) @@ -1158,14 +1299,12 @@ def filename_match(filename, patterns, default=True): return any(fnmatch(filename, pattern) for pattern in patterns) +def _is_eol_token(token): + return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' if COMMENT_WITH_NL: - def _is_eol_token(token): - return (token[0] in NEWLINE or - (token[0] == tokenize.COMMENT and token[1] == token[4])) -else: - def _is_eol_token(token): - return token[0] in NEWLINE - + def _is_eol_token(token, _eol_token=_is_eol_token): + return _eol_token(token) or (token[0] == tokenize.COMMENT and + token[1] == token[4]) ############################################################################## # Framework to run all checks @@ -1222,6 +1361,8 @@ class Checker(object): self.hang_closing = options.hang_closing self.verbose = options.verbose self.filename = filename + # Dictionary where a checker can store its custom state. + self._checker_states = {} if filename is None: self.filename = 'stdin' self.lines = lines or [] @@ -1277,10 +1418,16 @@ class Checker(object): arguments.append(getattr(self, name)) return check(*arguments) + def init_checker_state(self, name, argument_names): + """ Prepares a custom state for the specific checker plugin.""" + if 'checker_state' in argument_names: + self.checker_state = self._checker_states.setdefault(name, {}) + def check_physical(self, line): """Run all physical checks on a raw input line.""" self.physical_line = line for name, check, argument_names in self._physical_checks: + self.init_checker_state(name, argument_names) result = self.run_check(check, argument_names) if result is not None: (offset, text) = result @@ -1308,8 +1455,8 @@ class Checker(object): (start_row, start_col) = start if prev_row != start_row: # different row prev_text = self.lines[prev_row - 1][prev_col - 1] - if prev_text == ',' or (prev_text not in '{[(' - and text not in '}])'): + if prev_text == ',' or (prev_text not in '{[(' and + text not in '}])'): text = ' ' + text elif prev_col != start_col: # different column text = line[prev_col:start_col] + text @@ -1325,6 +1472,10 @@ class Checker(object): """Build a line from tokens and run all logical checks on it.""" self.report.increment_logical_line() mapping = self.build_tokens_line() + + if not mapping: + return + (start_row, start_col) = mapping[0][1] start_line = self.lines[start_row - 1] self.indent_level = expand_indent(start_line[:start_col]) @@ -1335,6 +1486,7 @@ class Checker(object): for name, check, argument_names in self._logical_checks: if self.verbose >= 4: print(' ' + name) + self.init_checker_state(name, argument_names) for offset, text in self.run_check(check, argument_names) or (): if not isinstance(offset, tuple): for token_offset, pos in mapping: @@ -1596,6 +1748,14 @@ class StandardReport(BaseReport): print(re.sub(r'\S', ' ', line[:offset]) + '^') if self._show_pep8 and doc: print(' ' + doc.strip()) + + # stdout is block buffered when not stdout.isatty(). + # line can be broken where buffer boundary since other processes + # write to same file. + # flush() after print() to avoid buffer boundary. + # Typical buffer size is 8192. line written safely when + # len(line) < 8192. + sys.stdout.flush() return self.file_errors @@ -1619,7 +1779,7 @@ class StyleGuide(object): # build options from the command line self.checker_class = kwargs.pop('checker_class', Checker) parse_argv = kwargs.pop('parse_argv', False) - config_file = kwargs.pop('config_file', None) + config_file = kwargs.pop('config_file', False) parser = kwargs.pop('parser', None) # build options from dict options_dict = dict(*args, **kwargs) @@ -1772,7 +1932,8 @@ def get_parser(prog='pep8', version=__version__): parser.add_option('--select', metavar='errors', default='', help="select errors and warnings (e.g. E,W6)") parser.add_option('--ignore', metavar='errors', default='', - help="skip errors and warnings (e.g. E4,W)") + help="skip errors and warnings (e.g. E4,W) " + "(default: %s)" % DEFAULT_IGNORE) parser.add_option('--show-source', action='store_true', help="show source code for each error") parser.add_option('--show-pep8', action='store_true', @@ -1808,24 +1969,39 @@ def get_parser(prog='pep8', version=__version__): def read_config(options, args, arglist, parser): - """Read both user configuration and local configuration.""" + """Read and parse configurations + + If a config file is specified on the command line with the "--config" + option, then only it is used for configuration. + + Otherwise, the user configuration (~/.config/pep8) and any local + configurations in the current directory or above will be merged together + (in that order) using the read method of ConfigParser. + """ config = RawConfigParser() - user_conf = options.config - if user_conf and os.path.isfile(user_conf): - if options.verbose: - print('user configuration: %s' % user_conf) - config.read(user_conf) + cli_conf = options.config local_dir = os.curdir - parent = tail = args and os.path.abspath(os.path.commonprefix(args)) - while tail: - if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]): - local_dir = parent + + if cli_conf and os.path.isfile(cli_conf): + if options.verbose: + print('cli configuration: %s' % cli_conf) + config.read(cli_conf) + else: + if USER_CONFIG and os.path.isfile(USER_CONFIG): if options.verbose: - print('local configuration: in %s' % parent) - break - (parent, tail) = os.path.split(parent) + print('user configuration: %s' % USER_CONFIG) + config.read(USER_CONFIG) + + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): + local_dir = parent + if options.verbose: + print('local configuration: in %s' % parent) + break + (parent, tail) = os.path.split(parent) pep8_section = parser.prog if config.has_section(pep8_section): @@ -1863,19 +2039,21 @@ def read_config(options, args, arglist, parser): def process_options(arglist=None, parse_argv=False, config_file=None, parser=None): - """Process options passed either via arglist or via command line args.""" + """Process options passed either via arglist or via command line args. + + Passing in the ``config_file`` parameter allows other tools, such as flake8 + to specify their own options to be processed in pep8. + """ if not parser: parser = get_parser() if not parser.has_option('--config'): - if config_file is True: - config_file = DEFAULT_CONFIG group = parser.add_option_group("Configuration", description=( "The project options are read from the [%s] section of the " "tox.ini file or the setup.cfg file located in any parent folder " "of the path(s) being processed. Allowed options are: %s." % (parser.prog, ', '.join(parser.config_options)))) group.add_option('--config', metavar='path', default=config_file, - help="user config file location (default: %default)") + help="user config file location") # Don't read the command line if the module is used as a library. if not arglist and not parse_argv: arglist = [] @@ -1920,7 +2098,7 @@ def _main(): except AttributeError: pass # not supported on Windows - pep8style = StyleGuide(parse_argv=True, config_file=True) + pep8style = StyleGuide(parse_argv=True) options = pep8style.options if options.doctest or options.testsuite: from testsuite.support import run_tests diff --git a/CadQuery/Libs/pies/StringIO.py b/CadQuery/Libs/pies/StringIO.py deleted file mode 100644 index fd5ce5e..0000000 --- a/CadQuery/Libs/pies/StringIO.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import absolute_import - -from .version_info import PY3 - -if PY3: - from StringIO import * -else: - try: - from cStringIO import * - except ImportError: - from StringIO import * diff --git a/CadQuery/Libs/pies/__init__.py b/CadQuery/Libs/pies/__init__.py deleted file mode 100644 index 2489aeb..0000000 --- a/CadQuery/Libs/pies/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -"""pies/__init__.py. - -Adds necessary hooks to allow Python code to run on multiple major versions of Python at once -(currently 2.6 - 3.x) - -Usage: - Anywhere you want to gain support for multiple versions of Python simply add the following two lines - from __future__ import absolute_import, division, print_function, unicode_literals - from pies.overrides import * - - And for changed stdlibs: - from pies import [libname] - -Copyright (C) 2013 Timothy Edmund Crosley - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" -__version__ = "2.6.1" diff --git a/CadQuery/Libs/pies/_utils.py b/CadQuery/Libs/pies/_utils.py deleted file mode 100644 index 0783183..0000000 --- a/CadQuery/Libs/pies/_utils.py +++ /dev/null @@ -1,88 +0,0 @@ -"""pies/_utils.py. - -Utils internal to the pies library and not meant for direct external usage. - -Copyright (C) 2013 Timothy Edmund Crosley - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" -import abc -import sys - - -def with_metaclass(meta, *bases): - """Enables use of meta classes across Python Versions. taken from jinja2/_compat.py. - - Use it like this:: - - class BaseForm(object): - pass - - class FormType(type): - pass - - class Form(with_metaclass(FormType, BaseForm)): - pass - - """ - class metaclass(meta): - __call__ = type.__call__ - __init__ = type.__init__ - def __new__(cls, name, this_bases, d): - if this_bases is None: - return type.__new__(cls, name, (), d) - return meta(name, bases, d) - return metaclass('temporary_class', None, {}) - - -def unmodified_isinstance(*bases): - """When called in the form - - MyOverrideClass(unmodified_isinstance(BuiltInClass)) - - it allows calls against passed in built in instances to pass even if there not a subclass - - """ - class UnmodifiedIsInstance(type): - if sys.version_info[0] == 2 and sys.version_info[1] <= 6: - - @classmethod - def __instancecheck__(cls, instance): - if cls.__name__ in (str(base.__name__) for base in bases): - return isinstance(instance, bases) - - subclass = getattr(instance, '__class__', None) - subtype = type(instance) - instance_type = getattr(abc, '_InstanceType', None) - if not instance_type: - class test_object: - pass - instance_type = type(test_object) - if subtype is instance_type: - subtype = subclass - if subtype is subclass or subclass is None: - return cls.__subclasscheck__(subtype) - return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype)) - else: - @classmethod - def __instancecheck__(cls, instance): - if cls.__name__ in (str(base.__name__) for base in bases): - return isinstance(instance, bases) - - return type.__instancecheck__(cls, instance) - - return with_metaclass(UnmodifiedIsInstance, *bases) - diff --git a/CadQuery/Libs/pies/ast.py b/CadQuery/Libs/pies/ast.py deleted file mode 100644 index ef576eb..0000000 --- a/CadQuery/Libs/pies/ast.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import absolute_import - -import sys -from ast import * - -from .version_info import PY2 - -if PY2 or sys.version_info[1] <= 2: - Try = TryExcept -else: - TryFinally = () - -if PY2: - def argument_names(node): - return [isinstance(arg, Name) and arg.id or None for arg in node.args.args] - - def kw_only_argument_names(node): - return [] - - def kw_only_default_count(node): - return 0 -else: - def argument_names(node): - return [arg.arg for arg in node.args.args] - - def kw_only_argument_names(node): - return [arg.arg for arg in node.args.kwonlyargs] - - def kw_only_default_count(node): - return sum(1 for n in node.args.kw_defaults if n is not None) diff --git a/CadQuery/Libs/pies/collections.py b/CadQuery/Libs/pies/collections.py deleted file mode 100644 index a8a94cf..0000000 --- a/CadQuery/Libs/pies/collections.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import absolute_import - -from collections import * - -from .version_info import PY2 - -if PY2: - from UserString import * - from UserList import * - - import sys - if sys.version_info < (2, 7): - from ordereddict import OrderedDict diff --git a/CadQuery/Libs/pies/functools.py b/CadQuery/Libs/pies/functools.py deleted file mode 100644 index 2e79a45..0000000 --- a/CadQuery/Libs/pies/functools.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import absolute_import - -import sys -from functools import * - -from .version_info import PY2 - -if PY2: - reduce = reduce - -if sys.version_info < (3, 2): - try: - from threading import Lock - except ImportError: - from dummy_threading import Lock - - from .collections import OrderedDict - - def lru_cache(maxsize=100): - """Least-recently-used cache decorator. - - Taking from: https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py - with slight modifications. - - If *maxsize* is set to None, the LRU features are disabled and the cache - can grow without bound. - - Arguments to the cached function must be hashable. - - View the cache statistics named tuple (hits, misses, maxsize, currsize) with - f.cache_info(). Clear the cache and statistics with f.cache_clear(). - Access the underlying function with f.__wrapped__. - - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used - - """ - def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyError=KeyError): - hits, misses = [0], [0] - kwd_mark = (object(),) # separates positional and keyword args - lock = Lock() - - if maxsize is None: - CACHE = dict() - - @wraps(user_function) - def wrapper(*args, **kwds): - key = args - if kwds: - key += kwd_mark + tuple(sorted(kwds.items())) - try: - result = CACHE[key] - hits[0] += 1 - return result - except KeyError: - pass - result = user_function(*args, **kwds) - CACHE[key] = result - misses[0] += 1 - return result - else: - CACHE = OrderedDict() - - @wraps(user_function) - def wrapper(*args, **kwds): - key = args - if kwds: - key += kwd_mark + tuple(sorted(kwds.items())) - with lock: - cached = CACHE.get(key, None) - if cached: - del CACHE[key] - CACHE[key] = cached - hits[0] += 1 - return cached - result = user_function(*args, **kwds) - with lock: - CACHE[key] = result # record recent use of this key - misses[0] += 1 - while len(CACHE) > maxsize: - CACHE.popitem(last=False) - return result - - def cache_info(): - """Report CACHE statistics.""" - with lock: - return _CacheInfo(hits[0], misses[0], maxsize, len(CACHE)) - - def cache_clear(): - """Clear the CACHE and CACHE statistics.""" - with lock: - CACHE.clear() - hits[0] = misses[0] = 0 - - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return wrapper - - return decorating_function diff --git a/CadQuery/Libs/pies/imp.py b/CadQuery/Libs/pies/imp.py deleted file mode 100644 index 8a85e73..0000000 --- a/CadQuery/Libs/pies/imp.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import absolute_import - -from imp import * - -from .version_info import PY2 - -if PY2: - reload = reload diff --git a/CadQuery/Libs/pies/itertools.py b/CadQuery/Libs/pies/itertools.py deleted file mode 100644 index 40636f1..0000000 --- a/CadQuery/Libs/pies/itertools.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import absolute_import - -from itertools import * - -from .version_info import PY2 - -if PY2: - filterfalse = ifilterfalse diff --git a/CadQuery/Libs/pies/overrides.py b/CadQuery/Libs/pies/overrides.py deleted file mode 100644 index 1d64f66..0000000 --- a/CadQuery/Libs/pies/overrides.py +++ /dev/null @@ -1,244 +0,0 @@ -"""pies/overrides.py. - -Overrides Python syntax to conform to the Python3 version as much as possible using a '*' import - -Copyright (C) 2013 Timothy Edmund Crosley - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -""" -from __future__ import absolute_import - -import abc -import functools -import sys -from numbers import Integral - -from ._utils import unmodified_isinstance, with_metaclass -from .version_info import PY2, PY3, VERSION - -native_dict = dict -native_round = round -native_filter = filter -native_map = map -native_zip = zip -native_range = range -native_str = str -native_chr = chr -native_input = input -native_next = next -native_object = object - -common = ['native_dict', 'native_round', 'native_filter', 'native_map', 'native_range', 'native_str', 'native_chr', - 'native_input', 'PY2', 'PY3', 'u', 'itemsview', 'valuesview', 'keysview', 'execute', 'integer_types', - 'native_next', 'native_object', 'with_metaclass'] - -if PY3: - import urllib - import builtins - from urllib import parse - - from collections import OrderedDict - - integer_types = (int, ) - - def u(string): - return string - - def itemsview(collection): - return collection.items() - - def valuesview(collection): - return collection.values() - - def keysview(collection): - return collection.keys() - - urllib.quote = parse.quote - urllib.quote_plus = parse.quote_plus - urllib.unquote = parse.unquote - urllib.unquote_plus = parse.unquote_plus - urllib.urlencode = parse.urlencode - execute = getattr(builtins, 'exec') - if VERSION[1] < 2: - def callable(entity): - return hasattr(entity, '__call__') - common.append('callable') - - __all__ = common + ['OrderedDict', 'urllib'] -else: - from itertools import ifilter as filter - from itertools import imap as map - from itertools import izip as zip - from decimal import Decimal, ROUND_HALF_EVEN - - - try: - from collections import OrderedDict - except ImportError: - from ordereddict import OrderedDict - - import codecs - str = unicode - chr = unichr - input = raw_input - range = xrange - integer_types = (int, long) - - import sys - reload(sys) - sys.setdefaultencoding('utf-8') - - def _create_not_allowed(name): - def _not_allow(*args, **kwargs): - raise NameError("name '{0}' is not defined".format(name)) - _not_allow.__name__ = name - return _not_allow - - for removed in ('apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks'): - globals()[removed] = _create_not_allowed(removed) - - def u(s): - if isinstance(s, unicode): - return s - else: - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - - def execute(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - class _dict_view_base(object): - __slots__ = ('_dictionary', ) - - def __init__(self, dictionary): - self._dictionary = dictionary - - def __repr__(self): - return "{0}({1})".format(self.__class__.__name__, str(list(self.__iter__()))) - - def __unicode__(self): - return str(self.__repr__()) - - def __str__(self): - return str(self.__unicode__()) - - class dict_keys(_dict_view_base): - __slots__ = () - - def __iter__(self): - return self._dictionary.iterkeys() - - class dict_values(_dict_view_base): - __slots__ = () - - def __iter__(self): - return self._dictionary.itervalues() - - class dict_items(_dict_view_base): - __slots__ = () - - def __iter__(self): - return self._dictionary.iteritems() - - def itemsview(collection): - return dict_items(collection) - - def valuesview(collection): - return dict_values(collection) - - def keysview(collection): - return dict_keys(collection) - - class dict(unmodified_isinstance(native_dict)): - def has_key(self, *args, **kwargs): - return AttributeError("'dict' object has no attribute 'has_key'") - - def items(self): - return dict_items(self) - - def keys(self): - return dict_keys(self) - - def values(self): - return dict_values(self) - - def round(number, ndigits=None): - return_int = False - if ndigits is None: - return_int = True - ndigits = 0 - if hasattr(number, '__round__'): - return number.__round__(ndigits) - - if ndigits < 0: - raise NotImplementedError('negative ndigits not supported yet') - exponent = Decimal('10') ** (-ndigits) - d = Decimal.from_float(number).quantize(exponent, - rounding=ROUND_HALF_EVEN) - if return_int: - return int(d) - else: - return float(d) - - def next(iterator): - try: - iterator.__next__() - except Exception: - native_next(iterator) - - class FixStr(type): - def __new__(cls, name, bases, dct): - if '__str__' in dct: - dct['__unicode__'] = dct['__str__'] - dct['__str__'] = lambda self: self.__unicode__().encode('utf-8') - return type.__new__(cls, name, bases, dct) - - if sys.version_info[1] <= 6: - def __instancecheck__(cls, instance): - if cls.__name__ == "object": - return isinstance(instance, native_object) - - subclass = getattr(instance, '__class__', None) - subtype = type(instance) - instance_type = getattr(abc, '_InstanceType', None) - if not instance_type: - class test_object: - pass - instance_type = type(test_object) - if subtype is instance_type: - subtype = subclass - if subtype is subclass or subclass is None: - return cls.__subclasscheck__(subtype) - return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype)) - else: - def __instancecheck__(cls, instance): - if cls.__name__ == "object": - return isinstance(instance, native_object) - return type.__instancecheck__(cls, instance) - - class object(with_metaclass(FixStr, object)): - pass - - __all__ = common + ['round', 'dict', 'apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks', 'str', 'chr', - 'input', 'range', 'filter', 'map', 'zip', 'object'] diff --git a/CadQuery/Libs/pies/pickle.py b/CadQuery/Libs/pies/pickle.py deleted file mode 100644 index d606ee7..0000000 --- a/CadQuery/Libs/pies/pickle.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import absolute_import - -from .version_info import PY3 - -if PY3: - from pickle import * -else: - try: - from cPickle import * - except ImportError: - from pickle import * diff --git a/CadQuery/Libs/pies/sys.py b/CadQuery/Libs/pies/sys.py deleted file mode 100644 index 64087b0..0000000 --- a/CadQuery/Libs/pies/sys.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import absolute_import - -from sys import * - -if version_info[0] == 2: - intern = intern diff --git a/CadQuery/Libs/pies/unittest.py b/CadQuery/Libs/pies/unittest.py deleted file mode 100644 index a5d0960..0000000 --- a/CadQuery/Libs/pies/unittest.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import absolute_import - -import sys -from unittest import * - -from ._utils import unmodified_isinstance - -NativeTestCase = TestCase - -if sys.version_info < (2, 7): - skip = lambda why: (lambda func: 'skip') - skipIf = lambda cond, why: (skip(why) if cond else lambda func: func) - - class TestCase(unmodified_isinstance(TestCase)): - def assertIs(self, expr1, expr2, msg=None): - if expr1 is not expr2: - self.fail(msg or '%r is not %r' % (expr1, expr2)) diff --git a/CadQuery/Libs/pies/version_info.py b/CadQuery/Libs/pies/version_info.py deleted file mode 100644 index d3547bb..0000000 --- a/CadQuery/Libs/pies/version_info.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import absolute_import - -import sys - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -VERSION = sys.version_info diff --git a/CadQuery/Libs/pyflakes/__init__.py b/CadQuery/Libs/pyflakes/__init__.py new file mode 100644 index 0000000..43619bf --- /dev/null +++ b/CadQuery/Libs/pyflakes/__init__.py @@ -0,0 +1,2 @@ + +__version__ = '0.8.1' diff --git a/CadQuery/Libs/pyflakes/__main__.py b/CadQuery/Libs/pyflakes/__main__.py new file mode 100644 index 0000000..a69e689 --- /dev/null +++ b/CadQuery/Libs/pyflakes/__main__.py @@ -0,0 +1,5 @@ +from pyflakes.api import main + +# python -m pyflakes (with Python >= 2.7) +if __name__ == '__main__': + main(prog='pyflakes') diff --git a/CadQuery/Libs/pyflakes/api.py b/CadQuery/Libs/pyflakes/api.py new file mode 100644 index 0000000..f32881e --- /dev/null +++ b/CadQuery/Libs/pyflakes/api.py @@ -0,0 +1,133 @@ +""" +API for the command-line I{pyflakes} tool. +""" +from __future__ import with_statement + +import sys +import os +import _ast +from optparse import OptionParser + +from pyflakes import checker, __version__ +from pyflakes import reporter as modReporter + +__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main'] + + +def check(codeString, filename, reporter=None): + """ + Check the Python source given by C{codeString} for flakes. + + @param codeString: The Python source to check. + @type codeString: C{str} + + @param filename: The name of the file the source came from, used to report + errors. + @type filename: C{str} + + @param reporter: A L{Reporter} instance, where errors and warnings will be + reported. + + @return: The number of warnings emitted. + @rtype: C{int} + """ + if reporter is None: + reporter = modReporter._makeDefaultReporter() + # First, compile into an AST and handle syntax errors. + try: + tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) + except SyntaxError: + value = sys.exc_info()[1] + msg = value.args[0] + + (lineno, offset, text) = value.lineno, value.offset, value.text + + # If there's an encoding problem with the file, the text is None. + if text is None: + # Avoid using msg, since for the only known case, it contains a + # bogus message that claims the encoding the file declared was + # unknown. + reporter.unexpectedError(filename, 'problem decoding source') + else: + reporter.syntaxError(filename, msg, lineno, offset, text) + return 1 + except Exception: + reporter.unexpectedError(filename, 'problem decoding source') + return 1 + # Okay, it's syntactically valid. Now check it. + w = checker.Checker(tree, filename) + w.messages.sort(key=lambda m: m.lineno) + for warning in w.messages: + reporter.flake(warning) + return len(w.messages) + + +def checkPath(filename, reporter=None): + """ + Check the given path, printing out any warnings detected. + + @param reporter: A L{Reporter} instance, where errors and warnings will be + reported. + + @return: the number of warnings printed + """ + if reporter is None: + reporter = modReporter._makeDefaultReporter() + try: + with open(filename, 'rb') as f: + codestr = f.read() + if sys.version_info < (2, 7): + codestr += '\n' # Work around for Python <= 2.6 + except UnicodeError: + reporter.unexpectedError(filename, 'problem decoding source') + return 1 + except IOError: + msg = sys.exc_info()[1] + reporter.unexpectedError(filename, msg.args[1]) + return 1 + return check(codestr, filename, reporter) + + +def iterSourceCode(paths): + """ + Iterate over all Python source files in C{paths}. + + @param paths: A list of paths. Directories will be recursed into and + any .py files found will be yielded. Any non-directories will be + yielded as-is. + """ + for path in paths: + if os.path.isdir(path): + for dirpath, dirnames, filenames in os.walk(path): + for filename in filenames: + if filename.endswith('.py'): + yield os.path.join(dirpath, filename) + else: + yield path + + +def checkRecursive(paths, reporter): + """ + Recursively check all source files in C{paths}. + + @param paths: A list of paths to Python source files and directories + containing Python source files. + @param reporter: A L{Reporter} where all of the warnings and errors + will be reported to. + @return: The number of warnings found. + """ + warnings = 0 + for sourcePath in iterSourceCode(paths): + warnings += checkPath(sourcePath, reporter) + return warnings + + +def main(prog=None): + parser = OptionParser(prog=prog, version=__version__) + (__, args) = parser.parse_args() + reporter = modReporter._makeDefaultReporter() + if args: + warnings = checkRecursive(args, reporter) + else: + warnings = check(sys.stdin.read(), '', reporter) + raise SystemExit(warnings > 0) diff --git a/CadQuery/Libs/pyflakes/checker.py b/CadQuery/Libs/pyflakes/checker.py new file mode 100644 index 0000000..8f290f6 --- /dev/null +++ b/CadQuery/Libs/pyflakes/checker.py @@ -0,0 +1,870 @@ +""" +Main module. + +Implement the central Checker class. +Also, it models the Bindings and Scopes. +""" +import doctest +import os +import sys + +PY2 = sys.version_info < (3, 0) +PY32 = sys.version_info < (3, 3) # Python 2.5 to 3.2 +PY33 = sys.version_info < (3, 4) # Python 2.5 to 3.3 +builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) + +try: + import ast +except ImportError: # Python 2.5 + import _ast as ast + + if 'decorator_list' not in ast.ClassDef._fields: + # Patch the missing attribute 'decorator_list' + ast.ClassDef.decorator_list = () + ast.FunctionDef.decorator_list = property(lambda s: s.decorators) + +from pyflakes import messages + + +if PY2: + def getNodeType(node_class): + # workaround str.upper() which is locale-dependent + return str(unicode(node_class.__name__).upper()) +else: + def getNodeType(node_class): + return node_class.__name__.upper() + +# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) +if PY32: + def getAlternatives(n): + if isinstance(n, (ast.If, ast.TryFinally)): + return [n.body] + if isinstance(n, ast.TryExcept): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] +else: + def getAlternatives(n): + if isinstance(n, ast.If): + return [n.body] + if isinstance(n, ast.Try): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] + + +class _FieldsOrder(dict): + """Fix order of AST node fields.""" + + def _get_fields(self, node_class): + # handle iter before target, and generators before element + fields = node_class._fields + if 'iter' in fields: + key_first = 'iter'.find + elif 'generators' in fields: + key_first = 'generators'.find + else: + key_first = 'value'.find + return tuple(sorted(fields, key=key_first, reverse=True)) + + def __missing__(self, node_class): + self[node_class] = fields = self._get_fields(node_class) + return fields + + +def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + """ + for name in _fields_order[node.__class__]: + if name == omit: + continue + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + yield item + + +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 L{Assignment} for a special type of binding that + is checked with stricter rules. + + @ivar used: pair of (L{Scope}, line-number) indicating the scope and + line number that this binding was last used + """ + + def __init__(self, name, source): + self.name = name + self.source = source + self.used = False + + def __str__(self): + return self.name + + def __repr__(self): + return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, + self.name, + self.source.lineno, + id(self)) + + def redefines(self, other): + return isinstance(other, Definition) and self.name == other.name + + +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + + +class Importation(Definition): + """ + A binding created by an import statement. + + @ivar fullName: The complete name given to the import statement, + possibly including multiple dotted components. + @type fullName: C{str} + """ + + def __init__(self, name, source): + self.fullName = name + self.redefined = [] + name = name.split('.')[0] + super(Importation, self).__init__(name, source) + + def redefines(self, other): + if isinstance(other, Importation): + return self.fullName == other.fullName + return isinstance(other, Definition) and self.name == other.name + + +class Argument(Binding): + """ + Represents binding a name as an argument. + """ + + +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. + """ + + +class FunctionDefinition(Definition): + pass + + +class ClassDefinition(Definition): + pass + + +class ExportBinding(Binding): + """ + A binding created by an C{__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 C{__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 + C{__all__} will not have an unused import warning reported for them. + """ + + def __init__(self, name, source, scope): + if '__all__' in scope and isinstance(source, ast.AugAssign): + self.names = list(scope['__all__'].names) + else: + self.names = [] + if isinstance(source.value, (ast.List, ast.Tuple)): + for node in source.value.elts: + if isinstance(node, ast.Str): + self.names.append(node.s) + super(ExportBinding, self).__init__(name, source) + + +class Scope(dict): + importStarred = False # set to True when import * is found + + def __repr__(self): + scope_cls = self.__class__.__name__ + return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) + + +class ClassScope(Scope): + pass + + +class FunctionScope(Scope): + """ + I represent a name scope for a function. + + @ivar globals: Names declared 'global' in this function. + """ + usesLocals = False + alwaysUsed = set(['__tracebackhide__', + '__traceback_info__', '__traceback_supplement__']) + + def __init__(self): + super(FunctionScope, self).__init__() + # Simplify: manage the special locals as globals + self.globals = self.alwaysUsed.copy() + self.returnValue = None # First non-empty return + self.isGenerator = False # Detect a generator + + def unusedAssignments(self): + """ + Return a generator for the assignments which have not been used. + """ + for name, binding in self.items(): + if (not binding.used and name not in self.globals + and not self.usesLocals + and isinstance(binding, Assignment)): + yield name, binding + + +class GeneratorScope(Scope): + pass + + +class ModuleScope(Scope): + pass + + +# Globally defined names which are not attributes of the builtins module, or +# are only present on some platforms. +_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] + + +def getNodeName(node): + # Returns node.id, or node.name, or None + if hasattr(node, 'id'): # One of the many nodes with an id + return node.id + if hasattr(node, 'name'): # a ExceptHandler node + return node.name + + +class Checker(object): + """ + I check the cleanliness and sanity of Python code. + + @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements + of the list are two-tuples. The first element is the callable passed + to L{deferFunction}. The second element is a copy of the scope stack + at the time L{deferFunction} was called. + + @ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for + callables which are deferred assignment checks. + """ + + nodeDepth = 0 + offset = None + traceTree = False + + builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) + _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') + if _customBuiltIns: + builtIns.update(_customBuiltIns.split(',')) + del _customBuiltIns + + def __init__(self, tree, filename='(none)', builtins=None, + withDoctest='PYFLAKES_DOCTEST' in os.environ): + self._nodeHandlers = {} + self._deferredFunctions = [] + self._deferredAssignments = [] + self.deadScopes = [] + self.messages = [] + self.filename = filename + if builtins: + self.builtIns = self.builtIns.union(builtins) + self.withDoctest = withDoctest + self.scopeStack = [ModuleScope()] + self.exceptHandlers = [()] + self.futuresAllowed = True + self.root = tree + self.handleChildren(tree) + 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 + # noisily if called after we've run through the deferred assignments. + self._deferredAssignments = None + del self.scopeStack[1:] + self.popScope() + self.checkDeadScopes() + + 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[:], self.offset)) + + def deferAssignment(self, callable): + """ + Schedule an assignment handler to be called just after deferred + function handlers. + """ + self._deferredAssignments.append((callable, self.scopeStack[:], self.offset)) + + def runDeferred(self, deferred): + """ + Run the callables in C{deferred} using their associated scope stack. + """ + for handler, scope, offset in deferred: + self.scopeStack = scope + self.offset = offset + handler() + + @property + def scope(self): + return self.scopeStack[-1] + + def popScope(self): + self.deadScopes.append(self.scopeStack.pop()) + + def checkDeadScopes(self): + """ + Look at scopes which have been fully examined and report names in them + which were imported but unused. + """ + for scope in self.deadScopes: + if isinstance(scope.get('__all__'), ExportBinding): + all_names = set(scope['__all__'].names) + if not scope.importStarred and \ + os.path.basename(self.filename) != '__init__.py': + # Look for possible mistakes in the export list + undefined = all_names.difference(scope) + for name in undefined: + self.report(messages.UndefinedExport, + scope['__all__'].source, name) + else: + all_names = [] + + # Look for imported names that aren't used. + for value in scope.values(): + if isinstance(value, Importation): + used = value.used or value.name in all_names + if not used: + messg = messages.UnusedImport + self.report(messg, value.source, value.name) + for node in value.redefined: + if isinstance(self.getParent(node), ast.For): + messg = messages.ImportShadowedByLoopVar + elif used: + continue + else: + messg = messages.RedefinedWhileUnused + self.report(messg, node, value.name, value.source) + + def pushScope(self, scopeClass=FunctionScope): + self.scopeStack.append(scopeClass()) + + def report(self, messageClass, *args, **kwargs): + self.messages.append(messageClass(self.filename, *args, **kwargs)) + + def getParent(self, node): + # Lookup the first parent which is not Tuple, List or Starred + while True: + node = node.parent + if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): + return node + + def getCommonAncestor(self, lnode, rnode, stop): + if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and + hasattr(rnode, 'parent')): + return None + if lnode is rnode: + return lnode + + if (lnode.depth > rnode.depth): + return self.getCommonAncestor(lnode.parent, rnode, stop) + if (lnode.depth < rnode.depth): + return self.getCommonAncestor(lnode, rnode.parent, stop) + return self.getCommonAncestor(lnode.parent, rnode.parent, stop) + + def descendantOf(self, node, ancestors, stop): + for a in ancestors: + if self.getCommonAncestor(node, a, stop): + return True + return False + + def differentForks(self, lnode, rnode): + """True, if lnode and rnode are located on different forks of IF/TRY""" + ancestor = self.getCommonAncestor(lnode, rnode, self.root) + parts = getAlternatives(ancestor) + if parts: + for items in parts: + if self.descendantOf(lnode, items, ancestor) ^ \ + self.descendantOf(rnode, items, ancestor): + return True + return False + + def addBinding(self, node, value): + """ + Called when a binding is altered. + + - `node` is the statement responsible for the change + - `value` is the new value, a Binding instance + """ + # assert value.source in (node, node.parent): + for scope in self.scopeStack[::-1]: + if value.name in scope: + break + existing = scope.get(value.name) + + if existing and not self.differentForks(node, existing.source): + + parent_stmt = self.getParent(value.source) + if isinstance(existing, Importation) and isinstance(parent_stmt, ast.For): + self.report(messages.ImportShadowedByLoopVar, + node, value.name, existing.source) + + elif scope is self.scope: + if (isinstance(parent_stmt, ast.comprehension) and + not isinstance(self.getParent(existing.source), + (ast.For, ast.comprehension))): + self.report(messages.RedefinedInListComp, + node, value.name, existing.source) + elif not existing.used and value.redefines(existing): + self.report(messages.RedefinedWhileUnused, + node, value.name, existing.source) + + elif isinstance(existing, Importation) and value.redefines(existing): + existing.redefined.append(node) + + self.scope[value.name] = value + + def getNodeHandler(self, node_class): + try: + return self._nodeHandlers[node_class] + except KeyError: + nodeType = getNodeType(node_class) + self._nodeHandlers[node_class] = handler = getattr(self, nodeType) + return handler + + def handleNodeLoad(self, node): + name = getNodeName(node) + if not name: + return + # try local scope + try: + self.scope[name].used = (self.scope, node) + except KeyError: + pass + else: + return + + scopes = [scope for scope in self.scopeStack[:-1] + if isinstance(scope, (FunctionScope, ModuleScope))] + if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]: + scopes.append(self.scopeStack[-2]) + + # try enclosing function scopes and global scope + importStarred = self.scope.importStarred + for scope in reversed(scopes): + importStarred = importStarred or scope.importStarred + try: + scope[name].used = (self.scope, node) + except KeyError: + pass + else: + return + + # look in the built-ins + if importStarred or name in self.builtIns: + return + if name == '__path__' and os.path.basename(self.filename) == '__init__.py': + # the special name __path__ is valid only in packages + return + + # protected with a NameError handler? + if 'NameError' not in self.exceptHandlers[-1]: + self.report(messages.UndefinedName, node, name) + + def handleNodeStore(self, node): + name = getNodeName(node) + if not name: + return + # if the name hasn't already been defined in the current scope + if isinstance(self.scope, FunctionScope) and name 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 + used = name in scope and scope[name].used + if used and used[0] is self.scope and name not in self.scope.globals: + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[name].used[1], name, scope[name].source) + break + + parent_stmt = self.getParent(node) + if isinstance(parent_stmt, (ast.For, ast.comprehension)) or ( + parent_stmt != node.parent and + not self.isLiteralTupleUnpacking(parent_stmt)): + binding = Binding(name, node) + elif name == '__all__' and isinstance(self.scope, ModuleScope): + binding = ExportBinding(name, node.parent, self.scope) + else: + binding = Assignment(name, node) + if name in self.scope: + binding.used = self.scope[name].used + self.addBinding(node, binding) + + def handleNodeDelete(self, node): + name = getNodeName(node) + if not name: + return + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: + self.scope.globals.remove(name) + else: + try: + del self.scope[name] + except KeyError: + self.report(messages.UndefinedName, node, name) + + def handleChildren(self, tree, omit=None): + for node in iter_child_nodes(tree, omit=omit): + self.handleNode(node, tree) + + def isLiteralTupleUnpacking(self, node): + if isinstance(node, ast.Assign): + for child in node.targets + [node.value]: + if not hasattr(child, 'elts'): + return False + return True + + 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 getDocstring(self, node): + if isinstance(node, ast.Expr): + node = node.value + if not isinstance(node, ast.Str): + return (None, None) + # Computed incorrectly if the docstring has backslash + doctest_lineno = node.lineno - node.s.count('\n') - 1 + return (node.s, doctest_lineno) + + def handleNode(self, node, parent): + if node is None: + return + if self.offset and getattr(node, 'lineno', None) is not None: + node.lineno += self.offset[0] + node.col_offset += self.offset[1] + if self.traceTree: + print(' ' * self.nodeDepth + node.__class__.__name__) + if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or + self.isDocstring(node)): + self.futuresAllowed = False + self.nodeDepth += 1 + node.depth = self.nodeDepth + node.parent = parent + try: + handler = self.getNodeHandler(node.__class__) + handler(node) + finally: + self.nodeDepth -= 1 + if self.traceTree: + print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) + + _getDoctestExamples = doctest.DocTestParser().get_examples + + def handleDoctests(self, node): + try: + (docstring, node_lineno) = self.getDocstring(node.body[0]) + examples = docstring and self._getDoctestExamples(docstring) + except (ValueError, IndexError): + # e.g. line 6 of the docstring for has inconsistent + # leading whitespace: ... + return + if not examples: + return + node_offset = self.offset or (0, 0) + self.pushScope() + underscore_in_builtins = '_' in self.builtIns + if not underscore_in_builtins: + self.builtIns.add('_') + for example in examples: + try: + tree = compile(example.source, "", "exec", ast.PyCF_ONLY_AST) + except SyntaxError: + e = sys.exc_info()[1] + position = (node_lineno + example.lineno + e.lineno, + example.indent + 4 + (e.offset or 0)) + self.report(messages.DoctestSyntaxError, node, position) + else: + self.offset = (node_offset[0] + node_lineno + example.lineno, + node_offset[1] + example.indent + 4) + self.handleChildren(tree) + self.offset = node_offset + if not underscore_in_builtins: + self.builtIns.remove('_') + self.popScope() + + def ignore(self, node): + pass + + # "stmt" type nodes + DELETE = PRINT = FOR = WHILE = IF = WITH = WITHITEM = RAISE = \ + TRYFINALLY = ASSERT = EXEC = EXPR = ASSIGN = handleChildren + + CONTINUE = BREAK = PASS = ignore + + # "expr" type nodes + BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = \ + COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \ + STARRED = NAMECONSTANT = handleChildren + + NUM = STR = BYTES = ELLIPSIS = ignore + + # "slice" type nodes + SLICE = EXTSLICE = INDEX = handleChildren + + # expression contexts are node instances too, though being constants + LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore + + # same for operators + 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 + + # additional node types + LISTCOMP = COMPREHENSION = KEYWORD = handleChildren + + def GLOBAL(self, node): + """ + Keep track of globals declarations. + """ + if isinstance(self.scope, FunctionScope): + self.scope.globals.update(node.names) + + NONLOCAL = GLOBAL + + def GENERATOREXP(self, node): + self.pushScope(GeneratorScope) + self.handleChildren(node) + self.popScope() + + DICTCOMP = SETCOMP = GENERATOREXP + + 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)): + self.handleNodeLoad(node) + if (node.id == 'locals' and isinstance(self.scope, FunctionScope) + and isinstance(node.parent, ast.Call)): + # we are doing locals() call in current scope + self.scope.usesLocals = True + elif isinstance(node.ctx, (ast.Store, ast.AugStore)): + self.handleNodeStore(node) + elif isinstance(node.ctx, ast.Del): + self.handleNodeDelete(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: %r" % (node.ctx,)) + + def RETURN(self, node): + if node.value and not self.scope.returnValue: + self.scope.returnValue = node.value + self.handleNode(node.value, node) + + def YIELD(self, node): + self.scope.isGenerator = True + self.handleNode(node.value, node) + + YIELDFROM = YIELD + + def FUNCTIONDEF(self, node): + for deco in node.decorator_list: + self.handleNode(deco, node) + self.LAMBDA(node) + self.addBinding(node, FunctionDefinition(node.name, node)) + if self.withDoctest: + self.deferFunction(lambda: self.handleDoctests(node)) + + def LAMBDA(self, node): + args = [] + annotations = [] + + if PY2: + def addArgs(arglist): + for arg in arglist: + if isinstance(arg, ast.Tuple): + addArgs(arg.elts) + else: + args.append(arg.id) + addArgs(node.args.args) + defaults = node.args.defaults + else: + for arg in node.args.args + node.args.kwonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + defaults = node.args.defaults + node.args.kw_defaults + + # Only for Python3 FunctionDefs + is_py3_func = hasattr(node, 'returns') + + for arg_name in ('vararg', 'kwarg'): + wildcard = getattr(node.args, arg_name) + if not wildcard: + continue + args.append(wildcard if PY33 else wildcard.arg) + if is_py3_func: + if PY33: # Python 2.5 to 3.3 + argannotation = arg_name + 'annotation' + annotations.append(getattr(node.args, argannotation)) + else: # Python >= 3.4 + annotations.append(wildcard.annotation) + + if is_py3_func: + annotations.append(node.returns) + + if len(set(args)) < len(args): + for (idx, arg) in enumerate(args): + if arg in args[:idx]: + self.report(messages.DuplicateArgument, node, arg) + + for child in annotations + defaults: + if child: + self.handleNode(child, node) + + def runFunction(): + + self.pushScope() + for name in args: + self.addBinding(node, Argument(name, node)) + if isinstance(node.body, list): + # case for FunctionDefs + for stmt in node.body: + self.handleNode(stmt, node) + else: + # case for Lambdas + self.handleNode(node.body, node) + + def checkUnusedAssignments(): + """ + Check to see if any assignments have not been used. + """ + for name, binding in self.scope.unusedAssignments(): + self.report(messages.UnusedVariable, binding.source, name) + self.deferAssignment(checkUnusedAssignments) + + if PY32: + def checkReturnWithArgumentInsideGenerator(): + """ + Check to see if there is any return statement with + arguments but the function is a generator. + """ + if self.scope.isGenerator and self.scope.returnValue: + self.report(messages.ReturnWithArgsInsideGenerator, + self.scope.returnValue) + self.deferAssignment(checkReturnWithArgumentInsideGenerator) + 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 deco in node.decorator_list: + self.handleNode(deco, node) + for baseNode in node.bases: + self.handleNode(baseNode, node) + if not PY2: + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) + self.pushScope(ClassScope) + if self.withDoctest: + self.deferFunction(lambda: self.handleDoctests(node)) + for stmt in node.body: + self.handleNode(stmt, node) + self.popScope() + self.addBinding(node, ClassDefinition(node.name, node)) + + def AUGASSIGN(self, node): + self.handleNodeLoad(node.target) + self.handleNode(node.value, node) + 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, importation) + + def IMPORTFROM(self, node): + if node.module == '__future__': + if not self.futuresAllowed: + self.report(messages.LateFutureImport, + node, [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, node.module) + continue + name = alias.asname or alias.name + importation = Importation(name, node) + if node.module == '__future__': + importation.used = (self.scope, node) + self.addBinding(node, importation) + + def TRY(self, node): + handler_names = [] + # List the exception handlers + for handler in node.handlers: + if isinstance(handler.type, ast.Tuple): + for exc_type in handler.type.elts: + handler_names.append(getNodeName(exc_type)) + elif handler.type: + handler_names.append(getNodeName(handler.type)) + # Memorize the except handlers and process the body + self.exceptHandlers.append(handler_names) + for child in node.body: + self.handleNode(child, node) + self.exceptHandlers.pop() + # Process the other nodes: "except:", "else:", "finally:" + self.handleChildren(node, omit='body') + + TRYEXCEPT = TRY + + def EXCEPTHANDLER(self, node): + # 3.x: in addition to handling children, we must handle the name of + # the exception, which is not a Name node, but a simple string. + if isinstance(node.name, str): + self.handleNodeStore(node) + self.handleChildren(node) diff --git a/CadQuery/Libs/pyflakes/messages.py b/CadQuery/Libs/pyflakes/messages.py new file mode 100644 index 0000000..1f799ec --- /dev/null +++ b/CadQuery/Libs/pyflakes/messages.py @@ -0,0 +1,135 @@ +""" +Provide the class Message and its subclasses. +""" + + +class Message(object): + message = '' + message_args = () + + def __init__(self, filename, loc): + self.filename = filename + self.lineno = loc.lineno + self.col = getattr(loc, 'col_offset', 0) + + def __str__(self): + return '%s:%s: %s' % (self.filename, self.lineno, + self.message % self.message_args) + + +class UnusedImport(Message): + message = '%r imported but unused' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class RedefinedWhileUnused(Message): + message = 'redefinition of unused %r from line %r' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class RedefinedInListComp(Message): + message = 'list comprehension redefines %r from line %r' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class ImportShadowedByLoopVar(Message): + message = 'import %r from line %r shadowed by loop variable' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class ImportStarUsed(Message): + message = "'from %s import *' used; unable to detect undefined names" + + def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) + self.message_args = (modname,) + + +class UndefinedName(Message): + message = 'undefined name %r' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class DoctestSyntaxError(Message): + message = 'syntax error in doctest' + + def __init__(self, filename, loc, position=None): + Message.__init__(self, filename, loc) + if position: + (self.lineno, self.col) = position + self.message_args = () + + +class UndefinedExport(Message): + message = 'undefined name %r in __all__' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class UndefinedLocal(Message): + message = ('local variable %r (defined in enclosing scope on line %r) ' + 'referenced before assignment') + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class DuplicateArgument(Message): + message = 'duplicate argument %r in function definition' + + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + +class Redefined(Message): + message = 'redefinition of %r from line %r' + + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) + + +class LateFutureImport(Message): + message = 'future import(s) %r after other statements' + + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = (names,) + + +class UnusedVariable(Message): + """ + Indicates that a variable has been explicity assigned to but not actually + used. + """ + message = 'local variable %r is assigned to but never used' + + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = (names,) + + +class ReturnWithArgsInsideGenerator(Message): + """ + Indicates a return statement with arguments inside a generator. + """ + message = '\'return\' with argument inside generator' diff --git a/CadQuery/Libs/pyflakes/reporter.py b/CadQuery/Libs/pyflakes/reporter.py new file mode 100644 index 0000000..ae645bd --- /dev/null +++ b/CadQuery/Libs/pyflakes/reporter.py @@ -0,0 +1,81 @@ +""" +Provide the Reporter class. +""" + +import re +import sys + + +class Reporter(object): + """ + Formats the results of pyflakes checks to users. + """ + + def __init__(self, warningStream, errorStream): + """ + Construct a L{Reporter}. + + @param warningStream: A file-like object where warnings will be + written to. The stream's C{write} method must accept unicode. + C{sys.stdout} is a good value. + @param errorStream: A file-like object where error output will be + written to. The stream's C{write} method must accept unicode. + C{sys.stderr} is a good value. + """ + self._stdout = warningStream + self._stderr = errorStream + + def unexpectedError(self, filename, msg): + """ + An unexpected error occurred trying to process C{filename}. + + @param filename: The path to a file that we could not process. + @ptype filename: C{unicode} + @param msg: A message explaining the problem. + @ptype msg: C{unicode} + """ + self._stderr.write("%s: %s\n" % (filename, msg)) + + def syntaxError(self, filename, msg, lineno, offset, text): + """ + There was a syntax errror in C{filename}. + + @param filename: The path to the file with the syntax error. + @ptype filename: C{unicode} + @param msg: An explanation of the syntax error. + @ptype msg: C{unicode} + @param lineno: The line number where the syntax error occurred. + @ptype lineno: C{int} + @param offset: The column on which the syntax error occurred, or None. + @ptype offset: C{int} + @param text: The source code containing the syntax error. + @ptype text: C{unicode} + """ + line = text.splitlines()[-1] + if offset is not None: + offset = offset - (len(text) - len(line)) + self._stderr.write('%s:%d:%d: %s\n' % + (filename, lineno, offset + 1, msg)) + else: + self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg)) + self._stderr.write(line) + self._stderr.write('\n') + if offset is not None: + self._stderr.write(re.sub(r'\S', ' ', line[:offset]) + + "^\n") + + def flake(self, message): + """ + pyflakes found something wrong with the code. + + @param: A L{pyflakes.messages.Message}. + """ + self._stdout.write(str(message)) + self._stdout.write('\n') + + +def _makeDefaultReporter(): + """ + Make a reporter that can be used when no reporter is specified. + """ + return Reporter(sys.stdout, sys.stderr) diff --git a/CadQuery/Libs/pyflakes/scripts/__init__.py b/CadQuery/Libs/pyflakes/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/CadQuery/Libs/pyflakes/scripts/pyflakes.py b/CadQuery/Libs/pyflakes/scripts/pyflakes.py new file mode 100644 index 0000000..f0c96d1 --- /dev/null +++ b/CadQuery/Libs/pyflakes/scripts/pyflakes.py @@ -0,0 +1,7 @@ +""" +Implementation of the command-line I{pyflakes} tool. +""" +from __future__ import absolute_import + +# For backward compatibility +from pyflakes.api import check, checkPath, checkRecursive, iterSourceCode, main diff --git a/CadQuery/Libs/pyflakes/test/__init__.py b/CadQuery/Libs/pyflakes/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/CadQuery/Libs/pyflakes/test/harness.py b/CadQuery/Libs/pyflakes/test/harness.py new file mode 100644 index 0000000..a781237 --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/harness.py @@ -0,0 +1,43 @@ + +import sys +import textwrap +import unittest + +from pyflakes import checker + +__all__ = ['TestCase', 'skip', 'skipIf'] + +if sys.version_info < (2, 7): + skip = lambda why: (lambda func: 'skip') # not callable + skipIf = lambda cond, why: (skip(why) if cond else lambda func: func) +else: + skip = unittest.skip + skipIf = unittest.skipIf +PyCF_ONLY_AST = 1024 + + +class TestCase(unittest.TestCase): + + withDoctest = False + + def flakes(self, input, *expectedOutputs, **kw): + tree = compile(textwrap.dedent(input), "", "exec", PyCF_ONLY_AST) + w = checker.Checker(tree, withDoctest=self.withDoctest, **kw) + outputs = [type(o) for o in w.messages] + expectedOutputs = list(expectedOutputs) + outputs.sort(key=lambda t: t.__name__) + expectedOutputs.sort(key=lambda t: t.__name__) + self.assertEqual(outputs, expectedOutputs, '''\ +for input: +%s +expected outputs: +%r +but got: +%s''' % (input, expectedOutputs, '\n'.join([str(o) for o in w.messages]))) + return w + + if sys.version_info < (2, 7): + + def assertIs(self, expr1, expr2, msg=None): + if expr1 is not expr2: + self.fail(msg or '%r is not %r' % (expr1, expr2)) diff --git a/CadQuery/Libs/pyflakes/test/test_api.py b/CadQuery/Libs/pyflakes/test/test_api.py new file mode 100644 index 0000000..f82ac62 --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/test_api.py @@ -0,0 +1,591 @@ +""" +Tests for L{pyflakes.scripts.pyflakes}. +""" + +import os +import sys +import shutil +import subprocess +import tempfile + +from pyflakes.messages import UnusedImport +from pyflakes.reporter import Reporter +from pyflakes.api import ( + checkPath, + checkRecursive, + iterSourceCode, +) +from pyflakes.test.harness import TestCase + +if sys.version_info < (3,): + from cStringIO import StringIO +else: + from io import StringIO + unichr = chr + + +def withStderrTo(stderr, f, *args, **kwargs): + """ + Call C{f} with C{sys.stderr} redirected to C{stderr}. + """ + (outer, sys.stderr) = (sys.stderr, stderr) + try: + return f(*args, **kwargs) + finally: + sys.stderr = outer + + +class Node(object): + """ + Mock an AST node. + """ + def __init__(self, lineno, col_offset=0): + self.lineno = lineno + self.col_offset = col_offset + + +class LoggingReporter(object): + """ + Implementation of Reporter that just appends any error to a list. + """ + + def __init__(self, log): + """ + Construct a C{LoggingReporter}. + + @param log: A list to append log messages to. + """ + self.log = log + + def flake(self, message): + self.log.append(('flake', str(message))) + + def unexpectedError(self, filename, message): + self.log.append(('unexpectedError', filename, message)) + + def syntaxError(self, filename, msg, lineno, offset, line): + self.log.append(('syntaxError', filename, msg, lineno, offset, line)) + + +class TestIterSourceCode(TestCase): + """ + Tests for L{iterSourceCode}. + """ + + def setUp(self): + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def makeEmptyFile(self, *parts): + assert parts + fpath = os.path.join(self.tempdir, *parts) + fd = open(fpath, 'a') + fd.close() + return fpath + + def test_emptyDirectory(self): + """ + There are no Python files in an empty directory. + """ + self.assertEqual(list(iterSourceCode([self.tempdir])), []) + + def test_singleFile(self): + """ + If the directory contains one Python file, C{iterSourceCode} will find + it. + """ + childpath = self.makeEmptyFile('foo.py') + self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath]) + + def test_onlyPythonSource(self): + """ + Files that are not Python source files are not included. + """ + self.makeEmptyFile('foo.pyc') + self.assertEqual(list(iterSourceCode([self.tempdir])), []) + + def test_recurses(self): + """ + If the Python files are hidden deep down in child directories, we will + find them. + """ + os.mkdir(os.path.join(self.tempdir, 'foo')) + apath = self.makeEmptyFile('foo', 'a.py') + os.mkdir(os.path.join(self.tempdir, 'bar')) + bpath = self.makeEmptyFile('bar', 'b.py') + cpath = self.makeEmptyFile('c.py') + self.assertEqual( + sorted(iterSourceCode([self.tempdir])), + sorted([apath, bpath, cpath])) + + def test_multipleDirectories(self): + """ + L{iterSourceCode} can be given multiple directories. It will recurse + into each of them. + """ + foopath = os.path.join(self.tempdir, 'foo') + barpath = os.path.join(self.tempdir, 'bar') + os.mkdir(foopath) + apath = self.makeEmptyFile('foo', 'a.py') + os.mkdir(barpath) + bpath = self.makeEmptyFile('bar', 'b.py') + self.assertEqual( + sorted(iterSourceCode([foopath, barpath])), + sorted([apath, bpath])) + + def test_explicitFiles(self): + """ + If one of the paths given to L{iterSourceCode} is not a directory but + a file, it will include that in its output. + """ + epath = self.makeEmptyFile('e.py') + self.assertEqual(list(iterSourceCode([epath])), + [epath]) + + +class TestReporter(TestCase): + """ + Tests for L{Reporter}. + """ + + def test_syntaxError(self): + """ + C{syntaxError} reports that there was a syntax error in the source + file. It reports to the error stream and includes the filename, line + number, error message, actual line of source and a caret pointing to + where the error is. + """ + err = StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, 7, 'bad line of source') + self.assertEqual( + ("foo.py:3:8: a problem\n" + "bad line of source\n" + " ^\n"), + err.getvalue()) + + def test_syntaxErrorNoOffset(self): + """ + C{syntaxError} doesn't include a caret pointing to the error if + C{offset} is passed as C{None}. + """ + err = StringIO() + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, None, + 'bad line of source') + self.assertEqual( + ("foo.py:3: a problem\n" + "bad line of source\n"), + err.getvalue()) + + def test_multiLineSyntaxError(self): + """ + If there's a multi-line syntax error, then we only report the last + line. The offset is adjusted so that it is relative to the start of + the last line. + """ + err = StringIO() + lines = [ + 'bad line of source', + 'more bad lines of source', + ] + reporter = Reporter(None, err) + reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, + '\n'.join(lines)) + self.assertEqual( + ("foo.py:3:7: a problem\n" + + lines[-1] + "\n" + + " ^\n"), + err.getvalue()) + + def test_unexpectedError(self): + """ + C{unexpectedError} reports an error processing a source file. + """ + err = StringIO() + reporter = Reporter(None, err) + reporter.unexpectedError('source.py', 'error message') + self.assertEqual('source.py: error message\n', err.getvalue()) + + def test_flake(self): + """ + C{flake} reports a code warning from Pyflakes. It is exactly the + str() of a L{pyflakes.messages.Message}. + """ + out = StringIO() + reporter = Reporter(out, None) + message = UnusedImport('foo.py', Node(42), 'bar') + reporter.flake(message) + self.assertEqual(out.getvalue(), "%s\n" % (message,)) + + +class CheckTests(TestCase): + """ + Tests for L{check} and L{checkPath} which check a file for flakes. + """ + + def makeTempFile(self, content): + """ + Make a temporary file containing C{content} and return a path to it. + """ + _, fpath = tempfile.mkstemp() + if not hasattr(content, 'decode'): + content = content.encode('ascii') + fd = open(fpath, 'wb') + fd.write(content) + fd.close() + return fpath + + def assertHasErrors(self, path, errorList): + """ + Assert that C{path} causes errors. + + @param path: A path to a file to check. + @param errorList: A list of errors expected to be printed to stderr. + """ + err = StringIO() + count = withStderrTo(err, checkPath, path) + self.assertEqual( + (count, err.getvalue()), (len(errorList), ''.join(errorList))) + + def getErrors(self, path): + """ + Get any warnings or errors reported by pyflakes for the file at C{path}. + + @param path: The path to a Python file on disk that pyflakes will check. + @return: C{(count, log)}, where C{count} is the number of warnings or + errors generated, and log is a list of those warnings, presented + as structured data. See L{LoggingReporter} for more details. + """ + log = [] + reporter = LoggingReporter(log) + count = checkPath(path, reporter) + return count, log + + def test_legacyScript(self): + from pyflakes.scripts import pyflakes as script_pyflakes + self.assertIs(script_pyflakes.checkPath, checkPath) + + def test_missingTrailingNewline(self): + """ + Source which doesn't end with a newline shouldn't cause any + exception to be raised nor an error indicator to be returned by + L{check}. + """ + fName = self.makeTempFile("def foo():\n\tpass\n\t") + self.assertHasErrors(fName, []) + + def test_checkPathNonExisting(self): + """ + L{checkPath} handles non-existing files. + """ + count, errors = self.getErrors('extremo') + self.assertEqual(count, 1) + self.assertEqual( + errors, + [('unexpectedError', 'extremo', 'No such file or directory')]) + + def test_multilineSyntaxError(self): + """ + Source which includes a syntax error which results in the raised + L{SyntaxError.text} containing multiple lines of source are reported + with only the last line of that source. + """ + source = """\ +def foo(): + ''' + +def bar(): + pass + +def baz(): + '''quux''' +""" + + # Sanity check - SyntaxError.text should be multiple lines, if it + # isn't, something this test was unprepared for has happened. + def evaluate(source): + exec(source) + try: + evaluate(source) + except SyntaxError: + e = sys.exc_info()[1] + self.assertTrue(e.text.count('\n') > 1) + else: + self.fail() + + sourcePath = self.makeTempFile(source) + self.assertHasErrors( + sourcePath, + ["""\ +%s:8:11: invalid syntax + '''quux''' + ^ +""" % (sourcePath,)]) + + def test_eofSyntaxError(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + sourcePath = self.makeTempFile("def foo(") + self.assertHasErrors( + sourcePath, + ["""\ +%s:1:9: unexpected EOF while parsing +def foo( + ^ +""" % (sourcePath,)]) + + def test_eofSyntaxErrorWithTab(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + sourcePath = self.makeTempFile("if True:\n\tfoo =") + self.assertHasErrors( + sourcePath, + ["""\ +%s:2:7: invalid syntax +\tfoo = +\t ^ +""" % (sourcePath,)]) + + def test_nonDefaultFollowsDefaultSyntaxError(self): + """ + Source which has a non-default argument following a default argument + should include the line number of the syntax error. However these + exceptions do not include an offset. + """ + source = """\ +def foo(bar=baz, bax): + pass +""" + sourcePath = self.makeTempFile(source) + last_line = ' ^\n' if sys.version_info >= (3, 2) else '' + column = '8:' if sys.version_info >= (3, 2) else '' + self.assertHasErrors( + sourcePath, + ["""\ +%s:1:%s non-default argument follows default argument +def foo(bar=baz, bax): +%s""" % (sourcePath, column, last_line)]) + + def test_nonKeywordAfterKeywordSyntaxError(self): + """ + Source which has a non-keyword argument after a keyword argument should + include the line number of the syntax error. However these exceptions + do not include an offset. + """ + source = """\ +foo(bar=baz, bax) +""" + sourcePath = self.makeTempFile(source) + last_line = ' ^\n' if sys.version_info >= (3, 2) else '' + column = '13:' if sys.version_info >= (3, 2) else '' + self.assertHasErrors( + sourcePath, + ["""\ +%s:1:%s non-keyword arg after keyword arg +foo(bar=baz, bax) +%s""" % (sourcePath, column, last_line)]) + + def test_invalidEscape(self): + """ + The invalid escape syntax raises ValueError in Python 2 + """ + ver = sys.version_info + # ValueError: invalid \x escape + sourcePath = self.makeTempFile(r"foo = '\xyz'") + if ver < (3,): + decoding_error = "%s: problem decoding source\n" % (sourcePath,) + else: + last_line = ' ^\n' if ver >= (3, 2) else '' + # Column has been "fixed" since 3.2.4 and 3.3.1 + col = 1 if ver >= (3, 3, 1) or ((3, 2, 4) <= ver < (3, 3)) else 2 + decoding_error = """\ +%s:1:7: (unicode error) 'unicodeescape' codec can't decode bytes \ +in position 0-%d: truncated \\xXX escape +foo = '\\xyz' +%s""" % (sourcePath, col, last_line) + self.assertHasErrors( + sourcePath, [decoding_error]) + + def test_permissionDenied(self): + """ + If the source file is not readable, this is reported on standard + error. + """ + sourcePath = self.makeTempFile('') + os.chmod(sourcePath, 0) + count, errors = self.getErrors(sourcePath) + self.assertEqual(count, 1) + self.assertEqual( + errors, + [('unexpectedError', sourcePath, "Permission denied")]) + + def test_pyflakesWarning(self): + """ + If the source file has a pyflakes warning, this is reported as a + 'flake'. + """ + sourcePath = self.makeTempFile("import foo") + count, errors = self.getErrors(sourcePath) + self.assertEqual(count, 1) + self.assertEqual( + errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))]) + + def test_encodedFileUTF8(self): + """ + If source file declares the correct encoding, no error is reported. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: utf-8 +x = "%s" +""" % SNOWMAN).encode('utf-8') + sourcePath = self.makeTempFile(source) + self.assertHasErrors(sourcePath, []) + + def test_misencodedFileUTF8(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: ascii +x = "%s" +""" % SNOWMAN).encode('utf-8') + sourcePath = self.makeTempFile(source) + self.assertHasErrors( + sourcePath, ["%s: problem decoding source\n" % (sourcePath,)]) + + def test_misencodedFileUTF16(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: ascii +x = "%s" +""" % SNOWMAN).encode('utf-16') + sourcePath = self.makeTempFile(source) + self.assertHasErrors( + sourcePath, ["%s: problem decoding source\n" % (sourcePath,)]) + + def test_checkRecursive(self): + """ + L{checkRecursive} descends into each directory, finding Python files + and reporting problems. + """ + tempdir = tempfile.mkdtemp() + os.mkdir(os.path.join(tempdir, 'foo')) + file1 = os.path.join(tempdir, 'foo', 'bar.py') + fd = open(file1, 'wb') + fd.write("import baz\n".encode('ascii')) + fd.close() + file2 = os.path.join(tempdir, 'baz.py') + fd = open(file2, 'wb') + fd.write("import contraband".encode('ascii')) + fd.close() + log = [] + reporter = LoggingReporter(log) + warnings = checkRecursive([tempdir], reporter) + self.assertEqual(warnings, 2) + self.assertEqual( + sorted(log), + sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))), + ('flake', + str(UnusedImport(file2, Node(1), 'contraband')))])) + + +class IntegrationTests(TestCase): + """ + Tests of the pyflakes script that actually spawn the script. + """ + + def setUp(self): + self.tempdir = tempfile.mkdtemp() + self.tempfilepath = os.path.join(self.tempdir, 'temp') + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def getPyflakesBinary(self): + """ + Return the path to the pyflakes binary. + """ + import pyflakes + package_dir = os.path.dirname(pyflakes.__file__) + return os.path.join(package_dir, '..', 'bin', 'pyflakes') + + def runPyflakes(self, paths, stdin=None): + """ + Launch a subprocess running C{pyflakes}. + + @param args: Command-line arguments to pass to pyflakes. + @param kwargs: Options passed on to C{subprocess.Popen}. + @return: C{(returncode, stdout, stderr)} of the completed pyflakes + process. + """ + env = dict(os.environ) + env['PYTHONPATH'] = os.pathsep.join(sys.path) + command = [sys.executable, self.getPyflakesBinary()] + command.extend(paths) + if stdin: + p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = p.communicate(stdin) + else: + p = subprocess.Popen(command, env=env, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = p.communicate() + rv = p.wait() + if sys.version_info >= (3,): + stdout = stdout.decode('utf-8') + stderr = stderr.decode('utf-8') + return (stdout, stderr, rv) + + def test_goodFile(self): + """ + When a Python source file is all good, the return code is zero and no + messages are printed to either stdout or stderr. + """ + fd = open(self.tempfilepath, 'a') + fd.close() + d = self.runPyflakes([self.tempfilepath]) + self.assertEqual(d, ('', '', 0)) + + def test_fileWithFlakes(self): + """ + When a Python source file has warnings, the return code is non-zero + and the warnings are printed to stdout. + """ + fd = open(self.tempfilepath, 'wb') + fd.write("import contraband\n".encode('ascii')) + fd.close() + d = self.runPyflakes([self.tempfilepath]) + expected = UnusedImport(self.tempfilepath, Node(1), 'contraband') + self.assertEqual(d, ("%s\n" % expected, '', 1)) + + def test_errors(self): + """ + When pyflakes finds errors with the files it's given, (if they don't + exist, say), then the return code is non-zero and the errors are + printed to stderr. + """ + d = self.runPyflakes([self.tempfilepath]) + error_msg = '%s: No such file or directory\n' % (self.tempfilepath,) + self.assertEqual(d, ('', error_msg, 1)) + + def test_readFromStdin(self): + """ + If no arguments are passed to C{pyflakes} then it reads from stdin. + """ + d = self.runPyflakes([], stdin='import contraband'.encode('ascii')) + expected = UnusedImport('', Node(1), 'contraband') + self.assertEqual(d, ("%s\n" % expected, '', 1)) diff --git a/CadQuery/Libs/pyflakes/test/test_doctests.py b/CadQuery/Libs/pyflakes/test/test_doctests.py new file mode 100644 index 0000000..3a74304 --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/test_doctests.py @@ -0,0 +1,256 @@ +import textwrap + +from pyflakes import messages as m +from pyflakes.test.test_other import Test as TestOther +from pyflakes.test.test_imports import Test as TestImports +from pyflakes.test.test_undefined_names import Test as TestUndefinedNames +from pyflakes.test.harness import TestCase, skip + + +class _DoctestMixin(object): + + withDoctest = True + + def doctestify(self, input): + lines = [] + for line in textwrap.dedent(input).splitlines(): + if line.strip() == '': + pass + elif (line.startswith(' ') or + line.startswith('except:') or + line.startswith('except ') or + line.startswith('finally:') or + line.startswith('else:') or + line.startswith('elif ')): + line = "... %s" % line + else: + line = ">>> %s" % line + lines.append(line) + doctestificator = textwrap.dedent('''\ + def doctest_something(): + """ + %s + """ + ''') + return doctestificator % "\n ".join(lines) + + def flakes(self, input, *args, **kw): + return super(_DoctestMixin, self).flakes(self.doctestify(input), *args, **kw) + + +class Test(TestCase): + + withDoctest = True + + def test_importBeforeDoctest(self): + self.flakes(""" + import foo + + def doctest_stuff(): + ''' + >>> foo + ''' + """) + + @skip("todo") + def test_importBeforeAndInDoctest(self): + self.flakes(''' + import foo + + def doctest_stuff(): + """ + >>> import foo + >>> foo + """ + + foo + ''', m.Redefined) + + def test_importInDoctestAndAfter(self): + self.flakes(''' + def doctest_stuff(): + """ + >>> import foo + >>> foo + """ + + import foo + foo() + ''') + + def test_offsetInDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> x # line 5 + """ + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 12) + + def test_offsetInLambdasInDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> lambda: x # line 5 + """ + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 20) + + def test_offsetAfterDoctests(self): + exc = self.flakes(''' + + def doctest_stuff(): + """ + >>> x = 5 + """ + + x + + ''', m.UndefinedName).messages[0] + self.assertEqual(exc.lineno, 8) + self.assertEqual(exc.col, 0) + + def test_syntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def doctest_stuff(): + """ + >>> from # line 4 + >>> fortytwo = 42 + >>> except Exception: + """ + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + self.assertEqual(exc.col, 26) + exc = exceptions[1] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 16) + exc = exceptions[2] + self.assertEqual(exc.lineno, 6) + self.assertEqual(exc.col, 18) + + def test_indentationErrorInDoctest(self): + exc = self.flakes(''' + def doctest_stuff(): + """ + >>> if True: + ... pass + """ + ''', m.DoctestSyntaxError).messages[0] + self.assertEqual(exc.lineno, 5) + self.assertEqual(exc.col, 16) + + def test_offsetWithMultiLineArgs(self): + (exc1, exc2) = self.flakes( + ''' + def doctest_stuff(arg1, + arg2, + arg3): + """ + >>> assert + >>> this + """ + ''', + m.DoctestSyntaxError, + m.UndefinedName).messages + self.assertEqual(exc1.lineno, 6) + self.assertEqual(exc1.col, 19) + self.assertEqual(exc2.lineno, 7) + self.assertEqual(exc2.col, 12) + + def test_doctestCanReferToFunction(self): + self.flakes(""" + def foo(): + ''' + >>> foo + ''' + """) + + def test_doctestCanReferToClass(self): + self.flakes(""" + class Foo(): + ''' + >>> Foo + ''' + def bar(self): + ''' + >>> Foo + ''' + """) + + def test_noOffsetSyntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def buildurl(base, *args, **kwargs): + """ + >>> buildurl('/blah.php', ('a', '&'), ('b', '=') + '/blah.php?a=%26&b=%3D' + >>> buildurl('/blah.php', a='&', 'b'='=') + '/blah.php?b=%3D&a=%26' + """ + pass + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + exc = exceptions[1] + self.assertEqual(exc.lineno, 6) + + def test_singleUnderscoreInDoctest(self): + self.flakes(''' + def func(): + """A docstring + + >>> func() + 1 + >>> _ + 1 + """ + return 1 + ''') + + +class TestOther(_DoctestMixin, TestOther): + pass + + +class TestImports(_DoctestMixin, TestImports): + + def test_futureImport(self): + """XXX This test can't work in a doctest""" + + def test_futureImportUsed(self): + """XXX This test can't work in a doctest""" + + +class TestUndefinedNames(_DoctestMixin, TestUndefinedNames): + + def test_doubleNestingReportsClosestName(self): + """ + Lines in doctest are a bit different so we can't use the test + from TestUndefinedNames + """ + exc = self.flakes(''' + def a(): + x = 1 + def b(): + x = 2 # line 7 in the file + def c(): + x + x = 3 + return x + return x + return x + ''', m.UndefinedLocal).messages[0] + self.assertEqual(exc.message_args, ('x', 7)) diff --git a/CadQuery/Libs/pyflakes/test/test_imports.py b/CadQuery/Libs/pyflakes/test/test_imports.py new file mode 100644 index 0000000..8895856 --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/test_imports.py @@ -0,0 +1,881 @@ + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skip, skipIf + + +class Test(TestCase): + + def test_unusedImport(self): + self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport) + self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport) + + def test_aliasedImport(self): + self.flakes('import fu as FU, bar as FU', + m.RedefinedWhileUnused, m.UnusedImport) + self.flakes('from moo import fu as FU, bar as FU', + m.RedefinedWhileUnused, m.UnusedImport) + + def test_usedImport(self): + self.flakes('import fu; print(fu)') + self.flakes('from baz import fu; print(fu)') + self.flakes('import fu; del fu') + + def test_redefinedWhileUnused(self): + self.flakes('import fu; fu = 3', m.RedefinedWhileUnused) + self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) + self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused) + + def test_redefinedIf(self): + """ + Test that importing a module twice within an if + block does raise a warning. + """ + self.flakes(''' + i = 2 + if i==1: + import os + import os + os.path''', m.RedefinedWhileUnused) + + def test_redefinedIfElse(self): + """ + Test that importing a module twice in if + and else blocks does not raise a warning. + """ + self.flakes(''' + i = 2 + if i==1: + import os + else: + import os + os.path''') + + def test_redefinedTry(self): + """ + Test that importing a module twice in an try block + does raise a warning. + """ + self.flakes(''' + try: + import os + import os + except: + pass + os.path''', m.RedefinedWhileUnused) + + def test_redefinedTryExcept(self): + """ + Test that importing a module twice in an try + and except block does not raise a warning. + """ + self.flakes(''' + try: + import os + except: + import os + os.path''') + + def test_redefinedTryNested(self): + """ + Test that importing a module twice using a nested + try/except and if blocks does not issue a warning. + """ + self.flakes(''' + try: + if True: + if True: + import os + except: + import os + os.path''') + + def test_redefinedTryExceptMulti(self): + self.flakes(""" + try: + from aa import mixer + except AttributeError: + from bb import mixer + except RuntimeError: + from cc import mixer + except: + from dd import mixer + mixer(123) + """) + + def test_redefinedTryElse(self): + self.flakes(""" + try: + from aa import mixer + except ImportError: + pass + else: + from bb import mixer + mixer(123) + """, m.RedefinedWhileUnused) + + def test_redefinedTryExceptElse(self): + self.flakes(""" + try: + import funca + except ImportError: + from bb import funca + from bb import funcb + else: + from bbb import funcb + print(funca, funcb) + """) + + def test_redefinedTryExceptFinally(self): + self.flakes(""" + try: + from aa import a + except ImportError: + from bb import a + finally: + a = 42 + print(a) + """) + + def test_redefinedTryExceptElseFinally(self): + self.flakes(""" + try: + import b + except ImportError: + b = Ellipsis + from bb import a + else: + from aa import a + finally: + a = 42 + print(a, b) + """) + + def test_redefinedByFunction(self): + self.flakes(''' + import fu + def fu(): + pass + ''', m.RedefinedWhileUnused) + + def test_redefinedInNestedFunction(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + def baz(): + def fu(): + pass + ''', m.RedefinedWhileUnused, m.UnusedImport) + + def test_redefinedInNestedFunctionTwice(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + import fu + def baz(): + def fu(): + pass + ''', m.RedefinedWhileUnused, m.RedefinedWhileUnused, + m.UnusedImport, m.UnusedImport) + + def test_redefinedButUsedLater(self): + """ + Test that a global import which is redefined locally, + but used later in another scope does not generate a warning. + """ + self.flakes(''' + import unittest, transport + + class GetTransportTestCase(unittest.TestCase): + def test_get_transport(self): + transport = 'transport' + self.assertIsNotNone(transport) + + class TestTransportMethodArgs(unittest.TestCase): + def test_send_defaults(self): + transport.Transport() + ''') + + def test_redefinedByClass(self): + self.flakes(''' + import fu + class fu: + pass + ''', m.RedefinedWhileUnused) + + def test_redefinedBySubclass(self): + """ + If an imported name is redefined by a class statement which also uses + that name in the bases list, no warning is emitted. + """ + self.flakes(''' + from fu import bar + class bar(bar): + pass + ''') + + def test_redefinedInClass(self): + """ + Test that shadowing a global with a class attribute does not produce a + warning. + """ + self.flakes(''' + import fu + class bar: + fu = 1 + print(fu) + ''') + + def test_usedInFunction(self): + self.flakes(''' + import fu + def fun(): + print(fu) + ''') + + def test_shadowedByParameter(self): + self.flakes(''' + import fu + def fun(fu): + print(fu) + ''', m.UnusedImport, m.RedefinedWhileUnused) + + self.flakes(''' + import fu + def fun(fu): + print(fu) + print(fu) + ''') + + def test_newAssignment(self): + self.flakes('fu = None') + + def test_usedInGetattr(self): + self.flakes('import fu; fu.bar.baz') + self.flakes('import fu; "bar".fu.baz', m.UnusedImport) + + def test_usedInSlice(self): + self.flakes('import fu; print(fu.bar[1:])') + + def test_usedInIfBody(self): + self.flakes(''' + import fu + if True: print(fu) + ''') + + def test_usedInIfConditional(self): + self.flakes(''' + import fu + if fu: pass + ''') + + def test_usedInElifConditional(self): + self.flakes(''' + import fu + if False: pass + elif fu: pass + ''') + + def test_usedInElse(self): + self.flakes(''' + import fu + if False: pass + else: print(fu) + ''') + + def test_usedInCall(self): + self.flakes('import fu; fu.bar()') + + def test_usedInClass(self): + self.flakes(''' + import fu + class bar: + bar = fu + ''') + + def test_usedInClassBase(self): + self.flakes(''' + import fu + class bar(object, fu.baz): + pass + ''') + + def test_notUsedInNestedScope(self): + self.flakes(''' + import fu + def bleh(): + pass + print(fu) + ''') + + def test_usedInFor(self): + self.flakes(''' + import fu + for bar in range(9): + print(fu) + ''') + + def test_usedInForElse(self): + self.flakes(''' + import fu + for bar in range(10): + pass + else: + print(fu) + ''') + + def test_redefinedByFor(self): + self.flakes(''' + import fu + for fu in range(2): + pass + ''', m.ImportShadowedByLoopVar) + + def test_shadowedByFor(self): + """ + Test that shadowing a global name with a for loop variable generates a + warning. + """ + self.flakes(''' + import fu + fu.bar() + for fu in (): + pass + ''', m.ImportShadowedByLoopVar) + + def test_shadowedByForDeep(self): + """ + Test that shadowing a global name with a for loop variable nested in a + tuple unpack generates a warning. + """ + self.flakes(''' + import fu + fu.bar() + for (x, y, z, (a, b, c, (fu,))) in (): + pass + ''', m.ImportShadowedByLoopVar) + # Same with a list instead of a tuple + self.flakes(''' + import fu + fu.bar() + for [x, y, z, (a, b, c, (fu,))] in (): + pass + ''', m.ImportShadowedByLoopVar) + + def test_usedInReturn(self): + self.flakes(''' + import fu + def fun(): + return fu + ''') + + def test_usedInOperators(self): + self.flakes('import fu; 3 + fu.bar') + self.flakes('import fu; 3 % fu.bar') + self.flakes('import fu; 3 - fu.bar') + self.flakes('import fu; 3 * fu.bar') + self.flakes('import fu; 3 ** fu.bar') + self.flakes('import fu; 3 / fu.bar') + self.flakes('import fu; 3 // fu.bar') + self.flakes('import fu; -fu.bar') + self.flakes('import fu; ~fu.bar') + self.flakes('import fu; 1 == fu.bar') + self.flakes('import fu; 1 | fu.bar') + self.flakes('import fu; 1 & fu.bar') + self.flakes('import fu; 1 ^ fu.bar') + self.flakes('import fu; 1 >> fu.bar') + self.flakes('import fu; 1 << fu.bar') + + def test_usedInAssert(self): + self.flakes('import fu; assert fu.bar') + + def test_usedInSubscript(self): + self.flakes('import fu; fu.bar[1]') + + def test_usedInLogic(self): + self.flakes('import fu; fu and False') + self.flakes('import fu; fu or False') + self.flakes('import fu; not fu.bar') + + def test_usedInList(self): + self.flakes('import fu; [fu]') + + def test_usedInTuple(self): + self.flakes('import fu; (fu,)') + + def test_usedInTry(self): + self.flakes(''' + import fu + try: fu + except: pass + ''') + + def test_usedInExcept(self): + self.flakes(''' + import fu + try: fu + except: pass + ''') + + def test_redefinedByExcept(self): + as_exc = ', ' if version_info < (2, 6) else ' as ' + self.flakes(''' + import fu + try: pass + except Exception%sfu: pass + ''' % as_exc, m.RedefinedWhileUnused) + + def test_usedInRaise(self): + self.flakes(''' + import fu + raise fu.bar + ''') + + def test_usedInYield(self): + self.flakes(''' + import fu + def gen(): + yield fu + ''') + + def test_usedInDict(self): + self.flakes('import fu; {fu:None}') + self.flakes('import fu; {1:fu}') + + def test_usedInParameterDefault(self): + self.flakes(''' + import fu + def f(bar=fu): + pass + ''') + + def test_usedInAttributeAssign(self): + self.flakes('import fu; fu.bar = 1') + + def test_usedInKeywordArg(self): + self.flakes('import fu; fu.bar(stuff=fu)') + + def test_usedInAssignment(self): + self.flakes('import fu; bar=fu') + self.flakes('import fu; n=0; n+=fu') + + def test_usedInListComp(self): + self.flakes('import fu; [fu for _ in range(1)]') + self.flakes('import fu; [1 for _ in range(1) if fu]') + + def test_redefinedByListComp(self): + self.flakes('import fu; [1 for fu in range(1)]', + m.RedefinedInListComp) + + def test_usedInTryFinally(self): + self.flakes(''' + import fu + try: pass + finally: fu + ''') + + self.flakes(''' + import fu + try: fu + finally: pass + ''') + + def test_usedInWhile(self): + self.flakes(''' + import fu + while 0: + fu + ''') + + self.flakes(''' + import fu + while fu: pass + ''') + + def test_usedInGlobal(self): + self.flakes(''' + import fu + def f(): global fu + ''', m.UnusedImport) + + @skipIf(version_info >= (3,), 'deprecated syntax') + def test_usedInBackquote(self): + self.flakes('import fu; `fu`') + + def test_usedInExec(self): + if version_info < (3,): + exec_stmt = 'exec "print 1" in fu.bar' + else: + exec_stmt = 'exec("print(1)", fu.bar)' + self.flakes('import fu; %s' % exec_stmt) + + def test_usedInLambda(self): + self.flakes('import fu; lambda: fu') + + def test_shadowedByLambda(self): + self.flakes('import fu; lambda fu: fu', + m.UnusedImport, m.RedefinedWhileUnused) + self.flakes('import fu; lambda fu: fu\nfu()') + + def test_usedInSliceObj(self): + self.flakes('import fu; "meow"[::fu]') + + def test_unusedInNestedScope(self): + self.flakes(''' + def bar(): + import fu + fu + ''', m.UnusedImport, m.UndefinedName) + + def test_methodsDontUseClassScope(self): + self.flakes(''' + class bar: + import fu + def fun(self): + fu + ''', m.UnusedImport, m.UndefinedName) + + def test_nestedFunctionsNestScope(self): + self.flakes(''' + def a(): + def b(): + fu + import fu + ''') + + def test_nestedClassAndFunctionScope(self): + self.flakes(''' + def a(): + import fu + class b: + def c(self): + print(fu) + ''') + + def test_importStar(self): + self.flakes('from fu import *', m.ImportStarUsed) + + def test_packageImport(self): + """ + If a dotted name is imported and used, no warning is reported. + """ + self.flakes(''' + import fu.bar + fu.bar + ''') + + def test_unusedPackageImport(self): + """ + If a dotted name is imported and not used, an unused import warning is + reported. + """ + self.flakes('import fu.bar', m.UnusedImport) + + def test_duplicateSubmoduleImport(self): + """ + If a submodule of a package is imported twice, an unused import warning + and a redefined while unused warning are reported. + """ + self.flakes(''' + import fu.bar, fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + self.flakes(''' + import fu.bar + import fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + + def test_differentSubmoduleImport(self): + """ + If two different submodules of a package are imported, no duplicate + import warning is reported for the package. + """ + self.flakes(''' + import fu.bar, fu.baz + fu.bar, fu.baz + ''') + self.flakes(''' + import fu.bar + import fu.baz + fu.bar, fu.baz + ''') + + def test_assignRHSFirst(self): + self.flakes('import fu; fu = fu') + self.flakes('import fu; fu, bar = fu') + self.flakes('import fu; [fu, bar] = fu') + self.flakes('import fu; fu += fu') + + def test_tryingMultipleImports(self): + self.flakes(''' + try: + import fu + except ImportError: + import bar as fu + fu + ''') + + def test_nonGlobalDoesNotRedefine(self): + self.flakes(''' + import fu + def a(): + fu = 3 + return fu + fu + ''') + + def test_functionsRunLater(self): + self.flakes(''' + def a(): + fu + import fu + ''') + + def test_functionNamesAreBoundNow(self): + self.flakes(''' + import fu + def fu(): + fu + fu + ''', m.RedefinedWhileUnused) + + def test_ignoreNonImportRedefinitions(self): + self.flakes('a = 1; a = 2') + + @skip("todo") + def test_importingForImportError(self): + self.flakes(''' + try: + import fu + except ImportError: + pass + ''') + + @skip("todo: requires evaluating attribute access") + def test_importedInClass(self): + """Imports in class scope can be used through self.""" + self.flakes(''' + class c: + import i + def __init__(self): + self.i + ''') + + def test_importUsedInMethodDefinition(self): + """ + Method named 'foo' with default args referring to module named 'foo'. + """ + self.flakes(''' + import foo + + class Thing(object): + def foo(self, parser=foo.parse_foo): + pass + ''') + + def test_futureImport(self): + """__future__ is special.""" + self.flakes('from __future__ import division') + self.flakes(''' + "docstring is allowed before future import" + from __future__ import division + ''') + + def test_futureImportFirst(self): + """ + __future__ imports must come before anything else. + """ + self.flakes(''' + x = 5 + from __future__ import division + ''', m.LateFutureImport) + self.flakes(''' + from foo import bar + from __future__ import division + bar + ''', m.LateFutureImport) + + def test_futureImportUsed(self): + """__future__ is special, but names are injected in the namespace.""" + self.flakes(''' + from __future__ import division + from __future__ import print_function + + assert print_function is not division + ''') + + +class TestSpecialAll(TestCase): + """ + Tests for suppression of unused import warnings by C{__all__}. + """ + def test_ignoredInFunction(self): + """ + An C{__all__} definition does not suppress unused import warnings in a + function scope. + """ + self.flakes(''' + def foo(): + import bar + __all__ = ["bar"] + ''', m.UnusedImport, m.UnusedVariable) + + def test_ignoredInClass(self): + """ + An C{__all__} definition does not suppress unused import warnings in a + class scope. + """ + self.flakes(''' + class foo: + import bar + __all__ = ["bar"] + ''', m.UnusedImport) + + def test_warningSuppressed(self): + """ + If a name is imported and unused but is named in C{__all__}, no warning + is reported. + """ + self.flakes(''' + import foo + __all__ = ["foo"] + ''') + self.flakes(''' + import foo + __all__ = ("foo",) + ''') + + def test_augmentedAssignment(self): + """ + The C{__all__} variable is defined incrementally. + """ + self.flakes(''' + import a + import c + __all__ = ['a'] + __all__ += ['b'] + if 1 < 3: + __all__ += ['c', 'd'] + ''', m.UndefinedExport, m.UndefinedExport) + + def test_unrecognizable(self): + """ + If C{__all__} is defined in a way that can't be recognized statically, + it is ignored. + """ + self.flakes(''' + import foo + __all__ = ["f" + "oo"] + ''', m.UnusedImport) + self.flakes(''' + import foo + __all__ = [] + ["foo"] + ''', m.UnusedImport) + + def test_unboundExported(self): + """ + If C{__all__} includes a name which is not bound, a warning is emitted. + """ + self.flakes(''' + __all__ = ["foo"] + ''', m.UndefinedExport) + + # Skip this in __init__.py though, since the rules there are a little + # different. + for filename in ["foo/__init__.py", "__init__.py"]: + self.flakes(''' + __all__ = ["foo"] + ''', filename=filename) + + def test_importStarExported(self): + """ + Do not report undefined if import * is used + """ + self.flakes(''' + from foolib import * + __all__ = ["foo"] + ''', m.ImportStarUsed) + + def test_usedInGenExp(self): + """ + Using a global in a generator expression results in no warnings. + """ + self.flakes('import fu; (fu for _ in range(1))') + self.flakes('import fu; (1 for _ in range(1) if fu)') + + def test_redefinedByGenExp(self): + """ + Re-using a global name as the loop variable for a generator + expression results in a redefinition warning. + """ + self.flakes('import fu; (1 for fu in range(1))', + m.RedefinedWhileUnused, m.UnusedImport) + + def test_usedAsDecorator(self): + """ + Using a global name in a decorator statement results in no warnings, + but using an undefined name in a decorator statement results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + def f(): + return "hello" + ''') + + self.flakes(''' + from interior import decorate + @decorate('value') + def f(): + return "hello" + ''') + + self.flakes(''' + @decorate + def f(): + return "hello" + ''', m.UndefinedName) + + +class Python26Tests(TestCase): + """ + Tests for checking of syntax which is valid in PYthon 2.6 and newer. + """ + + @skipIf(version_info < (2, 6), "Python >= 2.6 only") + def test_usedAsClassDecorator(self): + """ + Using an imported name as a class decorator results in no warnings, + but using an undefined name as a class decorator results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + class foo: + pass + ''') + + self.flakes(''' + from interior import decorate + @decorate("foo") + class bar: + pass + ''') + + self.flakes(''' + @decorate + class foo: + pass + ''', m.UndefinedName) diff --git a/CadQuery/Libs/pyflakes/test/test_other.py b/CadQuery/Libs/pyflakes/test/test_other.py new file mode 100644 index 0000000..ff8716b --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/test_other.py @@ -0,0 +1,940 @@ +""" +Tests for various Pyflakes behavior. +""" + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skip, skipIf + + +class Test(TestCase): + + def test_duplicateArgs(self): + self.flakes('def fu(bar, bar): pass', m.DuplicateArgument) + + def test_localReferencedBeforeAssignment(self): + self.flakes(''' + a = 1 + def f(): + a; a=1 + f() + ''', m.UndefinedLocal, m.UnusedVariable) + + def test_redefinedInListComp(self): + """ + Test that shadowing a variable in a list comprehension raises + a warning. + """ + self.flakes(''' + a = 1 + [1 for a, b in [(1, 2)]] + ''', m.RedefinedInListComp) + self.flakes(''' + class A: + a = 1 + [1 for a, b in [(1, 2)]] + ''', m.RedefinedInListComp) + self.flakes(''' + def f(): + a = 1 + [1 for a, b in [(1, 2)]] + ''', m.RedefinedInListComp) + self.flakes(''' + [1 for a, b in [(1, 2)]] + [1 for a, b in [(1, 2)]] + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + [1 for a, b in [(1, 2)]] + ''') + + def test_redefinedInGenerator(self): + """ + Test that reusing a variable in a generator does not raise + a warning. + """ + self.flakes(''' + a = 1 + (1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + class A: + a = 1 + list(1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + def f(): + a = 1 + (1 for a, b in [(1, 2)]) + ''', m.UnusedVariable) + self.flakes(''' + (1 for a, b in [(1, 2)]) + (1 for a, b in [(1, 2)]) + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + (1 for a, b in [(1, 2)]) + ''') + + @skipIf(version_info < (2, 7), "Python >= 2.7 only") + def test_redefinedInSetComprehension(self): + """ + Test that reusing a variable in a set comprehension does not raise + a warning. + """ + self.flakes(''' + a = 1 + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + class A: + a = 1 + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + def f(): + a = 1 + {1 for a, b in [(1, 2)]} + ''', m.UnusedVariable) + self.flakes(''' + {1 for a, b in [(1, 2)]} + {1 for a, b in [(1, 2)]} + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + {1 for a, b in [(1, 2)]} + ''') + + @skipIf(version_info < (2, 7), "Python >= 2.7 only") + def test_redefinedInDictComprehension(self): + """ + Test that reusing a variable in a dict comprehension does not raise + a warning. + """ + self.flakes(''' + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + class A: + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + def f(): + a = 1 + {1: 42 for a, b in [(1, 2)]} + ''', m.UnusedVariable) + self.flakes(''' + {1: 42 for a, b in [(1, 2)]} + {1: 42 for a, b in [(1, 2)]} + ''') + self.flakes(''' + for a, b in [(1, 2)]: + pass + {1: 42 for a, b in [(1, 2)]} + ''') + + def test_redefinedFunction(self): + """ + Test that shadowing a function definition with another one raises a + warning. + """ + self.flakes(''' + def a(): pass + def a(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedClassFunction(self): + """ + Test that shadowing a function definition in a class suite with another + one raises a warning. + """ + self.flakes(''' + class A: + def a(): pass + def a(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedIfElseFunction(self): + """ + Test that shadowing a function definition twice in an if + and else block does not raise a warning. + """ + self.flakes(''' + if True: + def a(): pass + else: + def a(): pass + ''') + + def test_redefinedIfFunction(self): + """ + Test that shadowing a function definition within an if block + raises a warning. + """ + self.flakes(''' + if True: + def a(): pass + def a(): pass + ''', m.RedefinedWhileUnused) + + def test_redefinedTryExceptFunction(self): + """ + Test that shadowing a function definition twice in try + and except block does not raise a warning. + """ + self.flakes(''' + try: + def a(): pass + except: + def a(): pass + ''') + + def test_redefinedTryFunction(self): + """ + Test that shadowing a function definition within a try block + raises a warning. + """ + self.flakes(''' + try: + def a(): pass + def a(): pass + except: + pass + ''', m.RedefinedWhileUnused) + + def test_redefinedIfElseInListComp(self): + """ + Test that shadowing a variable in a list comprehension in + an if and else block does not raise a warning. + """ + self.flakes(''' + if False: + a = 1 + else: + [a for a in '12'] + ''') + + def test_redefinedElseInListComp(self): + """ + Test that shadowing a variable in a list comprehension in + an else (or if) block raises a warning. + """ + self.flakes(''' + if False: + pass + else: + a = 1 + [a for a in '12'] + ''', m.RedefinedInListComp) + + def test_functionDecorator(self): + """ + Test that shadowing a function definition with a decorated version of + that function does not raise a warning. + """ + self.flakes(''' + from somewhere import somedecorator + + def a(): pass + a = somedecorator(a) + ''') + + def test_classFunctionDecorator(self): + """ + Test that shadowing a function definition in a class suite with a + decorated version of that function does not raise a warning. + """ + self.flakes(''' + class A: + def a(): pass + a = classmethod(a) + ''') + + @skipIf(version_info < (2, 6), "Python >= 2.6 only") + def test_modernProperty(self): + self.flakes(""" + class A: + @property + def t(self): + pass + @t.setter + def t(self, value): + pass + @t.deleter + def t(self): + pass + """) + + def test_unaryPlus(self): + """Don't die on unary +.""" + self.flakes('+1') + + def test_undefinedBaseClass(self): + """ + If a name in the base list of a class definition is undefined, a + warning is emitted. + """ + self.flakes(''' + class foo(foo): + pass + ''', m.UndefinedName) + + def test_classNameUndefinedInClassBody(self): + """ + If a class name is used in the body of that class's definition and + the name is not already defined, a warning is emitted. + """ + self.flakes(''' + class foo: + foo + ''', m.UndefinedName) + + def test_classNameDefinedPreviously(self): + """ + If a class name is used in the body of that class's definition and + the name was previously defined in some other way, no warning is + emitted. + """ + self.flakes(''' + foo = None + class foo: + foo + ''') + + def test_classRedefinition(self): + """ + If a class is defined twice in the same module, a warning is emitted. + """ + self.flakes(''' + class Foo: + pass + class Foo: + pass + ''', m.RedefinedWhileUnused) + + def test_functionRedefinedAsClass(self): + """ + If a function is redefined as a class, a warning is emitted. + """ + self.flakes(''' + def Foo(): + pass + class Foo: + pass + ''', m.RedefinedWhileUnused) + + def test_classRedefinedAsFunction(self): + """ + If a class is redefined as a function, a warning is emitted. + """ + self.flakes(''' + class Foo: + pass + def Foo(): + pass + ''', m.RedefinedWhileUnused) + + @skip("todo: Too hard to make this warn but other cases stay silent") + def test_doubleAssignment(self): + """ + If a variable is re-assigned to without being used, no warning is + emitted. + """ + self.flakes(''' + x = 10 + x = 20 + ''', m.RedefinedWhileUnused) + + def test_doubleAssignmentConditionally(self): + """ + If a variable is re-assigned within a conditional, no warning is + emitted. + """ + self.flakes(''' + x = 10 + if True: + x = 20 + ''') + + def test_doubleAssignmentWithUse(self): + """ + If a variable is re-assigned to after being used, no warning is + emitted. + """ + self.flakes(''' + x = 10 + y = x * 2 + x = 20 + ''') + + def test_comparison(self): + """ + If a defined name is used on either side of any of the six comparison + operators, no warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x < y + x <= y + x == y + x != y + x >= y + x > y + ''') + + def test_identity(self): + """ + If a defined name is used on either side of an identity test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x is y + x is not y + ''') + + def test_containment(self): + """ + If a defined name is used on either side of a containment test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x in y + x not in y + ''') + + def test_loopControl(self): + """ + break and continue statements are supported. + """ + self.flakes(''' + for x in [1, 2]: + break + ''') + self.flakes(''' + for x in [1, 2]: + continue + ''') + + def test_ellipsis(self): + """ + Ellipsis in a slice is supported. + """ + self.flakes(''' + [1, 2][...] + ''') + + def test_extendedSlice(self): + """ + Extended slices are supported. + """ + self.flakes(''' + x = 3 + [1, 2][x,:] + ''') + + def test_varAugmentedAssignment(self): + """ + Augmented assignment of a variable is supported. + We don't care about var refs. + """ + self.flakes(''' + foo = 0 + foo += 1 + ''') + + def test_attrAugmentedAssignment(self): + """ + Augmented assignment of attributes is supported. + We don't care about attr refs. + """ + self.flakes(''' + foo = None + foo.bar += foo.baz + ''') + + +class TestUnusedAssignment(TestCase): + """ + Tests for warning about unused assignments. + """ + + def test_unusedVariable(self): + """ + Warn when a variable in a function is assigned a value that's never + used. + """ + self.flakes(''' + def a(): + b = 1 + ''', m.UnusedVariable) + + def test_unusedVariableAsLocals(self): + """ + Using locals() it is perfectly valid to have unused variables + """ + self.flakes(''' + def a(): + b = 1 + return locals() + ''') + + def test_unusedVariableNoLocals(self): + """ + Using locals() in wrong scope should not matter + """ + self.flakes(''' + def a(): + locals() + def a(): + b = 1 + return + ''', m.UnusedVariable) + + def test_assignToGlobal(self): + """ + Assigning to a global and then not using that global is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = 0 + def a(): + global b + b = 1 + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_assignToNonlocal(self): + """ + Assigning to a nonlocal and then not using that binding is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = b'0' + def a(): + nonlocal b + b = b'1' + ''') + + def test_assignToMember(self): + """ + Assigning to a member of another object and then not using that member + variable is perfectly acceptable. Do not mistake it for an unused + local variable. + """ + # XXX: Adding this test didn't generate a failure. Maybe not + # necessary? + self.flakes(''' + class b: + pass + def a(): + b.foo = 1 + ''') + + def test_assignInForLoop(self): + """ + Don't warn when a variable in a for loop is assigned to but not used. + """ + self.flakes(''' + def f(): + for i in range(10): + pass + ''') + + def test_assignInListComprehension(self): + """ + Don't warn when a variable in a list comprehension is + assigned to but not used. + """ + self.flakes(''' + def f(): + [None for i in range(10)] + ''') + + def test_generatorExpression(self): + """ + Don't warn when a variable in a generator expression is + assigned to but not used. + """ + self.flakes(''' + def f(): + (None for i in range(10)) + ''') + + def test_assignmentInsideLoop(self): + """ + Don't warn when a variable assignment occurs lexically after its use. + """ + self.flakes(''' + def f(): + x = None + for i in range(10): + if i > 2: + return x + x = i * 2 + ''') + + def test_tupleUnpacking(self): + """ + Don't warn when a variable included in tuple unpacking is unused. It's + very common for variables in a tuple unpacking assignment to be unused + in good Python code, so warning will only create false positives. + """ + self.flakes(''' + def f(tup): + (x, y) = tup + ''') + self.flakes(''' + def f(): + (x, y) = 1, 2 + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + if x > 1: + print(coords) + ''') + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + ''', m.UnusedVariable) + self.flakes(''' + def f(): + coords = (x, y) = 1, 2 + ''', m.UnusedVariable) + + def test_listUnpacking(self): + """ + Don't warn when a variable included in list unpacking is unused. + """ + self.flakes(''' + def f(tup): + [x, y] = tup + ''') + self.flakes(''' + def f(): + [x, y] = [1, 2] + ''', m.UnusedVariable, m.UnusedVariable) + + def test_closedOver(self): + """ + Don't warn when the assignment is used in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + return foo + return bar + ''') + + def test_doubleClosedOver(self): + """ + Don't warn when the assignment is used in an inner function, even if + that inner function itself is in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + def baz(): + return foo + return bar + ''') + + def test_tracebackhideSpecialVariable(self): + """ + Do not warn about unused local variable __tracebackhide__, which is + a special variable for py.test. + """ + self.flakes(""" + def helper(): + __tracebackhide__ = True + """) + + def test_ifexp(self): + """ + Test C{foo if bar else baz} statements. + """ + self.flakes("a = 'moo' if True else 'oink'") + self.flakes("a = foo if True else 'oink'", m.UndefinedName) + self.flakes("a = 'moo' if True else bar", m.UndefinedName) + + def test_withStatementNoNames(self): + """ + No warnings are emitted for using inside or after a nameless C{with} + statement a name defined beforehand. + """ + self.flakes(''' + from __future__ import with_statement + bar = None + with open("foo"): + bar + bar + ''') + + def test_withStatementSingleName(self): + """ + No warnings are emitted for using a name defined by a C{with} statement + within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as bar: + bar + bar + ''') + + def test_withStatementAttributeName(self): + """ + No warnings are emitted for using an attribute as the target of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo.bar: + pass + ''') + + def test_withStatementSubscript(self): + """ + No warnings are emitted for using a subscript as the target of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo[0]: + pass + ''') + + def test_withStatementSubscriptUndefined(self): + """ + An undefined name warning is emitted if the subscript used as the + target of a C{with} statement is not defined. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo[bar]: + pass + ''', m.UndefinedName) + + def test_withStatementTupleNames(self): + """ + No warnings are emitted for using any of the tuple of names defined by + a C{with} statement within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as (bar, baz): + bar, baz + bar, baz + ''') + + def test_withStatementListNames(self): + """ + No warnings are emitted for using any of the list of names defined by a + C{with} statement within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as [bar, baz]: + bar, baz + bar, baz + ''') + + def test_withStatementComplicatedTarget(self): + """ + If the target of a C{with} statement uses any or all of the valid forms + for that part of the grammar (See + U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}), + the names involved are checked both for definedness and any bindings + created are respected in the suite of the statement and afterwards. + """ + self.flakes(''' + from __future__ import with_statement + c = d = e = g = h = i = None + with open('foo') as [(a, b), c[d], e.f, g[h:i]]: + a, b, c, d, e, g, h, i + a, b, c, d, e, g, h, i + ''') + + def test_withStatementSingleNameUndefined(self): + """ + An undefined name warning is emitted if the name first defined by a + C{with} statement is used before the C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + bar + with open('foo') as bar: + pass + ''', m.UndefinedName) + + def test_withStatementTupleNamesUndefined(self): + """ + An undefined name warning is emitted if a name first defined by a the + tuple-unpacking form of the C{with} statement is used before the + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + baz + with open('foo') as (bar, baz): + pass + ''', m.UndefinedName) + + def test_withStatementSingleNameRedefined(self): + """ + A redefined name warning is emitted if a name bound by an import is + rebound by the name defined by a C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import bar + with open('foo') as bar: + pass + ''', m.RedefinedWhileUnused) + + def test_withStatementTupleNamesRedefined(self): + """ + A redefined name warning is emitted if a name bound by an import is + rebound by one of the names defined by the tuple-unpacking form of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import bar + with open('foo') as (bar, baz): + pass + ''', m.RedefinedWhileUnused) + + def test_withStatementUndefinedInside(self): + """ + An undefined name warning is emitted if a name is used inside the + body of a C{with} statement without first being bound. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as bar: + baz + ''', m.UndefinedName) + + def test_withStatementNameDefinedInBody(self): + """ + A name defined in the body of a C{with} statement can be used after + the body ends without warning. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as bar: + baz = 10 + baz + ''') + + def test_withStatementUndefinedInExpression(self): + """ + An undefined name warning is emitted if a name in the I{test} + expression of a C{with} statement is undefined. + """ + self.flakes(''' + from __future__ import with_statement + with bar as baz: + pass + ''', m.UndefinedName) + + self.flakes(''' + from __future__ import with_statement + with bar as bar: + pass + ''', m.UndefinedName) + + @skipIf(version_info < (2, 7), "Python >= 2.7 only") + def test_dictComprehension(self): + """ + Dict comprehensions are properly handled. + """ + self.flakes(''' + a = {1: x for x in range(10)} + ''') + + @skipIf(version_info < (2, 7), "Python >= 2.7 only") + def test_setComprehensionAndLiteral(self): + """ + Set comprehensions are properly handled. + """ + self.flakes(''' + a = {1, 2, 3} + b = {x for x in range(10)} + ''') + + def test_exceptionUsedInExcept(self): + as_exc = ', ' if version_info < (2, 6) else ' as ' + self.flakes(''' + try: pass + except Exception%se: e + ''' % as_exc) + + self.flakes(''' + def download_review(): + try: pass + except Exception%se: e + ''' % as_exc) + + def test_exceptWithoutNameInFunction(self): + """ + Don't issue false warning when an unnamed exception is used. + Previously, there would be a false warning, but only when the + try..except was in a function + """ + self.flakes(''' + import tokenize + def foo(): + try: pass + except tokenize.TokenError: pass + ''') + + def test_exceptWithoutNameInFunctionTuple(self): + """ + Don't issue false warning when an unnamed exception is used. + This example catches a tuple of exception types. + """ + self.flakes(''' + import tokenize + def foo(): + try: pass + except (tokenize.TokenError, IndentationError): pass + ''') + + def test_augmentedAssignmentImportedFunctionCall(self): + """ + Consider a function that is called on the right part of an + augassign operation to be used. + """ + self.flakes(''' + from foo import bar + baz = 0 + baz += bar() + ''') + + @skipIf(version_info < (3, 3), 'new in Python 3.3') + def test_yieldFromUndefined(self): + """ + Test C{yield from} statement + """ + self.flakes(''' + def bar(): + yield from foo() + ''', m.UndefinedName) diff --git a/CadQuery/Libs/pyflakes/test/test_return_with_arguments_inside_generator.py b/CadQuery/Libs/pyflakes/test/test_return_with_arguments_inside_generator.py new file mode 100644 index 0000000..fc1272a --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/test_return_with_arguments_inside_generator.py @@ -0,0 +1,34 @@ + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +class Test(TestCase): + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_return(self): + self.flakes(''' + class a: + def b(): + for x in a.c: + if x: + yield x + return a + ''', m.ReturnWithArgsInsideGenerator) + + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_returnNone(self): + self.flakes(''' + def a(): + yield 12 + return None + ''', m.ReturnWithArgsInsideGenerator) + + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_returnYieldExpression(self): + self.flakes(''' + def a(): + b = yield a + return b + ''', m.ReturnWithArgsInsideGenerator) diff --git a/CadQuery/Libs/pyflakes/test/test_undefined_names.py b/CadQuery/Libs/pyflakes/test/test_undefined_names.py new file mode 100644 index 0000000..29627b7 --- /dev/null +++ b/CadQuery/Libs/pyflakes/test/test_undefined_names.py @@ -0,0 +1,451 @@ + +from _ast import PyCF_ONLY_AST +from sys import version_info + +from pyflakes import messages as m, checker +from pyflakes.test.harness import TestCase, skip, skipIf + + +class Test(TestCase): + def test_undefined(self): + self.flakes('bar', m.UndefinedName) + + def test_definedInListComp(self): + self.flakes('[a for a in range(10) if a]') + + def test_functionsNeedGlobalScope(self): + self.flakes(''' + class a: + def b(): + fu + fu = 1 + ''') + + def test_builtins(self): + self.flakes('range(10)') + + def test_builtinWindowsError(self): + """ + C{WindowsError} is sometimes a builtin name, so no warning is emitted + for using it. + """ + self.flakes('WindowsError') + + def test_magicGlobalsFile(self): + """ + Use of the C{__file__} magic global should not emit an undefined name + warning. + """ + self.flakes('__file__') + + def test_magicGlobalsBuiltins(self): + """ + Use of the C{__builtins__} magic global should not emit an undefined + name warning. + """ + self.flakes('__builtins__') + + def test_magicGlobalsName(self): + """ + Use of the C{__name__} magic global should not emit an undefined name + warning. + """ + self.flakes('__name__') + + def test_magicGlobalsPath(self): + """ + Use of the C{__path__} magic global should not emit an undefined name + warning, if you refer to it from a file called __init__.py. + """ + self.flakes('__path__', m.UndefinedName) + self.flakes('__path__', filename='package/__init__.py') + + def test_globalImportStar(self): + """Can't find undefined names with import *.""" + self.flakes('from fu import *; bar', m.ImportStarUsed) + + def test_localImportStar(self): + """ + A local import * still allows undefined names to be found + in upper scopes. + """ + self.flakes(''' + def a(): + from fu import * + bar + ''', m.ImportStarUsed, m.UndefinedName) + + @skipIf(version_info >= (3,), 'obsolete syntax') + def test_unpackedParameter(self): + """Unpacked function parameters create bindings.""" + self.flakes(''' + def a((bar, baz)): + bar; baz + ''') + + @skip("todo") + def test_definedByGlobal(self): + """ + "global" can make an otherwise undefined name in another function + defined. + """ + self.flakes(''' + def a(): global fu; fu = 1 + def b(): fu + ''') + + def test_globalInGlobalScope(self): + """ + A global statement in the global scope is ignored. + """ + self.flakes(''' + global x + def foo(): + print(x) + ''', m.UndefinedName) + + def test_del(self): + """Del deletes bindings.""" + self.flakes('a = 1; del a; a', m.UndefinedName) + + def test_delGlobal(self): + """Del a global binding from a function.""" + self.flakes(''' + a = 1 + def f(): + global a + del a + a + ''') + + def test_delUndefined(self): + """Del an undefined name.""" + self.flakes('del a', m.UndefinedName) + + def test_globalFromNestedScope(self): + """Global names are available from nested scopes.""" + self.flakes(''' + a = 1 + def b(): + def c(): + a + ''') + + def test_laterRedefinedGlobalFromNestedScope(self): + """ + Test that referencing a local name that shadows a global, before it is + defined, generates a warning. + """ + self.flakes(''' + a = 1 + def fun(): + a + a = 2 + return a + ''', m.UndefinedLocal) + + def test_laterRedefinedGlobalFromNestedScope2(self): + """ + Test that referencing a local name in a nested scope that shadows a + global declared in an enclosing scope, before it is defined, generates + a warning. + """ + self.flakes(''' + a = 1 + def fun(): + global a + def fun2(): + a + a = 2 + return a + ''', m.UndefinedLocal) + + def test_intermediateClassScopeIgnored(self): + """ + If a name defined in an enclosing scope is shadowed by a local variable + and the name is used locally before it is bound, an unbound local + warning is emitted, even if there is a class scope between the enclosing + scope and the local scope. + """ + self.flakes(''' + def f(): + x = 1 + class g: + def h(self): + a = x + x = None + print(x, a) + print(x) + ''', m.UndefinedLocal) + + def test_doubleNestingReportsClosestName(self): + """ + Test that referencing a local name in a nested scope that shadows a + variable declared in two different outer scopes before it is defined + in the innermost scope generates an UnboundLocal warning which + refers to the nearest shadowed name. + """ + exc = self.flakes(''' + def a(): + x = 1 + def b(): + x = 2 # line 5 + def c(): + x + x = 3 + return x + return x + return x + ''', m.UndefinedLocal).messages[0] + self.assertEqual(exc.message_args, ('x', 5)) + + def test_laterRedefinedGlobalFromNestedScope3(self): + """ + Test that referencing a local name in a nested scope that shadows a + global, before it is defined, generates a warning. + """ + self.flakes(''' + def fun(): + a = 1 + def fun2(): + a + a = 1 + return a + return a + ''', m.UndefinedLocal) + + def test_undefinedAugmentedAssignment(self): + self.flakes( + ''' + def f(seq): + a = 0 + seq[a] += 1 + seq[b] /= 2 + c[0] *= 2 + a -= 3 + d += 4 + e[any] = 5 + ''', + m.UndefinedName, # b + m.UndefinedName, # c + m.UndefinedName, m.UnusedVariable, # d + m.UndefinedName, # e + ) + + def test_nestedClass(self): + """Nested classes can access enclosing scope.""" + self.flakes(''' + def f(foo): + class C: + bar = foo + def f(self): + return foo + return C() + + f(123).f() + ''') + + def test_badNestedClass(self): + """Free variables in nested classes must bind at class creation.""" + self.flakes(''' + def f(): + class C: + bar = foo + foo = 456 + return foo + f() + ''', m.UndefinedName) + + def test_definedAsStarArgs(self): + """Star and double-star arg names are defined.""" + self.flakes(''' + def f(a, *b, **c): + print(a, b, c) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_definedAsStarUnpack(self): + """Star names in unpack are defined.""" + self.flakes(''' + a, *b = range(10) + print(a, b) + ''') + self.flakes(''' + *a, b = range(10) + print(a, b) + ''') + self.flakes(''' + a, *b, c = range(10) + print(a, b, c) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_usedAsStarUnpack(self): + """ + Star names in unpack are used if RHS is not a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = range(10) + ''') + self.flakes(''' + def f(): + (*a, b) = range(10) + ''') + self.flakes(''' + def f(): + [a, *b, c] = range(10) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_unusedAsStarUnpack(self): + """ + Star names in unpack are unused if RHS is a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = any, all, 4, 2, 'un' + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (*a, b) = [bool, int, float, complex] + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + [a, *b, c] = 9, 8, 7, 6, 5, 4 + ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_keywordOnlyArgs(self): + """Keyword-only arg names are defined.""" + self.flakes(''' + def f(*, a, b=None): + print(a, b) + ''') + + self.flakes(''' + import default_b + def f(*, a, b=default_b): + print(a, b) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_keywordOnlyArgsUndefined(self): + """Typo in kwonly name.""" + self.flakes(''' + def f(*, a, b=default_c): + print(a, b) + ''', m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotationUndefined(self): + """Undefined annotations.""" + self.flakes(''' + from abc import note1, note2, note3, note4, note5 + def func(a: note1, *args: note2, + b: note3=12, **kw: note4) -> note5: pass + ''') + + self.flakes(''' + def func(): + d = e = 42 + def func(a: {1, d}) -> (lambda c: e): pass + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_metaClassUndefined(self): + self.flakes(''' + from abc import ABCMeta + class A(metaclass=ABCMeta): pass + ''') + + def test_definedInGenExp(self): + """ + Using the loop variable of a generator expression results in no + warnings. + """ + self.flakes('(a for a in %srange(10) if a)' % + ('x' if version_info < (3,) else '')) + + def test_undefinedWithErrorHandler(self): + """ + Some compatibility code checks explicitly for NameError. + It should not trigger warnings. + """ + self.flakes(''' + try: + socket_map + except NameError: + socket_map = {} + ''') + self.flakes(''' + try: + _memoryview.contiguous + except (NameError, AttributeError): + raise RuntimeError("Python >= 3.3 is required") + ''') + # If NameError is not explicitly handled, generate a warning + self.flakes(''' + try: + socket_map + except: + socket_map = {} + ''', m.UndefinedName) + self.flakes(''' + try: + socket_map + except Exception: + socket_map = {} + ''', m.UndefinedName) + + def test_definedInClass(self): + """ + Defined name for generator expressions and dict/set comprehension. + """ + self.flakes(''' + class A: + T = range(10) + + Z = (x for x in T) + L = [x for x in T] + B = dict((i, str(i)) for i in T) + ''') + + if version_info >= (2, 7): + self.flakes(''' + class A: + T = range(10) + + X = {x for x in T} + Y = {x:x for x in T} + ''') + + def test_undefinedInLoop(self): + """ + The loop variable is defined after the expression is computed. + """ + self.flakes(''' + for i in range(i): + print(i) + ''', m.UndefinedName) + self.flakes(''' + [42 for i in range(i)] + ''', m.UndefinedName) + self.flakes(''' + (42 for i in range(i)) + ''', m.UndefinedName) + + +class NameTests(TestCase): + """ + Tests for some extra cases of name handling. + """ + def test_impossibleContext(self): + """ + A Name node with an unrecognized context results in a RuntimeError being + raised. + """ + tree = compile("x = 10", "", "exec", PyCF_ONLY_AST) + # Make it into something unrecognizable. + tree.body[0].targets[0].ctx = object() + self.assertRaises(RuntimeError, checker.Checker, tree) diff --git a/CadQuery/Libs/pygments/__init__.py b/CadQuery/Libs/pygments/__init__.py index 872ad97..9563818 100644 --- a/CadQuery/Libs/pygments/__init__.py +++ b/CadQuery/Libs/pygments/__init__.py @@ -26,7 +26,7 @@ :license: BSD, see LICENSE for details. """ -__version__ = '2.0.1' +__version__ = '2.0.2' __docformat__ = 'restructuredtext' __all__ = ['lex', 'format', 'highlight'] @@ -45,7 +45,8 @@ def lex(code, lexer): return lexer.get_tokens(code) except TypeError as err: if isinstance(err.args[0], str) and \ - 'unbound method get_tokens' in err.args[0]: + ('unbound method get_tokens' in err.args[0] or + 'missing 1 required positional argument' in err.args[0]): raise TypeError('lex() argument must be a lexer instance, ' 'not a class') raise @@ -61,15 +62,15 @@ def format(tokens, formatter, outfile=None): """ try: if not outfile: - #print formatter, 'using', formatter.encoding - realoutfile = formatter.encoding and BytesIO() or StringIO() + realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO() formatter.format(tokens, realoutfile) return realoutfile.getvalue() else: formatter.format(tokens, outfile) except TypeError as err: if isinstance(err.args[0], str) and \ - 'unbound method format' in err.args[0]: + ('unbound method format' in err.args[0] or + 'missing 1 required positional argument' in err.args[0]): raise TypeError('format() argument must be a formatter instance, ' 'not a class') raise @@ -86,6 +87,6 @@ def highlight(code, lexer, formatter, outfile=None): return format(lex(code, lexer), formatter, outfile) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover from pygments.cmdline import main sys.exit(main(sys.argv)) diff --git a/CadQuery/Libs/pygments/cmdline.py b/CadQuery/Libs/pygments/cmdline.py index b6c319c..0f629ec 100644 --- a/CadQuery/Libs/pygments/cmdline.py +++ b/CadQuery/Libs/pygments/cmdline.py @@ -198,19 +198,7 @@ def _print_list(what): print(" %s" % docstring_headline(cls)) -def main(args=sys.argv): - """ - Main command line entry point. - """ - # pylint: disable-msg=R0911,R0912,R0915 - - usage = USAGE % ((args[0],) * 6) - - try: - popts, args = getopt.getopt(args[1:], "l:f:F:o:O:P:LS:a:N:hVHgs") - except getopt.GetoptError: - print(usage, file=sys.stderr) - return 2 +def main_inner(popts, args, usage): opts = {} O_opts = [] P_opts = [] @@ -331,10 +319,13 @@ def main(args=sys.argv): opts.pop('-F', None) # select lexer - lexer = opts.pop('-l', None) - if lexer: + lexer = None + + # given by name? + lexername = opts.pop('-l', None) + if lexername: try: - lexer = get_lexer_by_name(lexer, **parsed_opts) + lexer = get_lexer_by_name(lexername, **parsed_opts) except (OptionError, ClassNotFound) as err: print('Error:', err, file=sys.stderr) return 1 @@ -441,8 +432,8 @@ def main(args=sys.argv): fmter.name in ('Terminal', 'Terminal256'): # unfortunately colorama doesn't support binary streams on Py3 if sys.version_info > (3,): - import io - outfile = io.TextIOWrapper(outfile, encoding=fmter.encoding) + from pygments.util import UnclosingTextIOWrapper + outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding) fmter.encoding = None try: import colorama.initialise @@ -461,40 +452,58 @@ def main(args=sys.argv): right = escapeinside[1] lexer = LatexEmbeddedLexer(left, right, lexer) - # ... and do it! - try: - # process filters - for fname, fopts in F_opts: + # process filters + for fname, fopts in F_opts: + try: lexer.add_filter(fname, **fopts) + except ClassNotFound as err: + print('Error:', err, file=sys.stderr) + return 1 - if '-s' not in opts: - # process whole input as per normal... - highlight(code, lexer, fmter, outfile) - else: - if not lexer: - print('Error: when using -s a lexer has to be selected with -l', - file=sys.stderr) - return 1 - # line by line processing of stdin (eg: for 'tail -f')... - try: - while 1: - if sys.version_info > (3,): - # Python 3: we have to use .buffer to get a binary stream - line = sys.stdin.buffer.readline() - else: - line = sys.stdin.readline() - if not line: - break - if not inencoding: - line = guess_decode_from_terminal(line, sys.stdin)[0] - highlight(line, lexer, fmter, outfile) - if hasattr(outfile, 'flush'): - outfile.flush() - except KeyboardInterrupt: - return 0 + # ... and do it! + if '-s' not in opts: + # process whole input as per normal... + highlight(code, lexer, fmter, outfile) + return 0 + else: + if not lexer: + print('Error: when using -s a lexer has to be selected with -l', + file=sys.stderr) + return 1 + # line by line processing of stdin (eg: for 'tail -f')... + try: + while 1: + if sys.version_info > (3,): + # Python 3: we have to use .buffer to get a binary stream + line = sys.stdin.buffer.readline() + else: + line = sys.stdin.readline() + if not line: + break + if not inencoding: + line = guess_decode_from_terminal(line, sys.stdin)[0] + highlight(line, lexer, fmter, outfile) + if hasattr(outfile, 'flush'): + outfile.flush() + except KeyboardInterrupt: + return 0 + +def main(args=sys.argv): + """ + Main command line entry point. + """ + usage = USAGE % ((args[0],) * 6) + + try: + popts, args = getopt.getopt(args[1:], "l:f:F:o:O:P:LS:a:N:hVHgs") + except getopt.GetoptError: + print(usage, file=sys.stderr) + return 2 + + try: + return main_inner(popts, args, usage) except Exception: - raise import traceback info = traceback.format_exception(*sys.exc_info()) msg = info[-1].strip() @@ -505,5 +514,3 @@ def main(args=sys.argv): print('*** Error while highlighting:', file=sys.stderr) print(msg, file=sys.stderr) return 1 - - return 0 diff --git a/CadQuery/Libs/pygments/formatters/__init__.py b/CadQuery/Libs/pygments/formatters/__init__.py index 45ceaff..22eb64c 100644 --- a/CadQuery/Libs/pygments/formatters/__init__.py +++ b/CadQuery/Libs/pygments/formatters/__init__.py @@ -75,7 +75,7 @@ def get_formatter_by_name(_alias, **options): """ cls = find_formatter_class(_alias) if cls is None: - raise ClassNotFound("No formatter found for name %r" % _alias) + raise ClassNotFound("no formatter found for name %r" % _alias) return cls(**options) @@ -95,7 +95,7 @@ def get_formatter_for_filename(fn, **options): for filename in cls.filenames: if _fn_matches(fn, filename): return cls(**options) - raise ClassNotFound("No formatter found for file name %r" % fn) + raise ClassNotFound("no formatter found for file name %r" % fn) class _automodule(types.ModuleType): diff --git a/CadQuery/Libs/pygments/formatters/_mapping.py b/CadQuery/Libs/pygments/formatters/_mapping.py index 36ae9dc..678c069 100644 --- a/CadQuery/Libs/pygments/formatters/_mapping.py +++ b/CadQuery/Libs/pygments/formatters/_mapping.py @@ -32,7 +32,8 @@ FORMATTERS = { 'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.') } -if __name__ == '__main__': + +if __name__ == '__main__': # pragma: no cover import sys import os diff --git a/CadQuery/Libs/pygments/lexers/_cocoa_builtins.py b/CadQuery/Libs/pygments/lexers/_cocoa_builtins.py index 024bfc0..84f3eee 100644 --- a/CadQuery/Libs/pygments/lexers/_cocoa_builtins.py +++ b/CadQuery/Libs/pygments/lexers/_cocoa_builtins.py @@ -19,7 +19,7 @@ COCOA_PROTOCOLS = set(['SKStoreProductViewControllerDelegate', 'AVVideoCompositi COCOA_PRIMITIVES = set(['ROTAHeader', '__CFBundle', 'MortSubtable', 'AudioFilePacketTableInfo', 'CGPDFOperatorTable', 'KerxStateEntry', 'ExtendedTempoEvent', 'CTParagraphStyleSetting', 'OpaqueMIDIPort', '_GLKMatrix3', '_GLKMatrix2', '_GLKMatrix4', 'ExtendedControlEvent', 'CAFAudioDescription', 'OpaqueCMBlockBuffer', 'CGTextDrawingMode', 'EKErrorCode', 'GCAcceleration', 'AudioUnitParameterInfo', '__SCPreferences', '__CTFrame', '__CTLine', 'AudioFile_SMPTE_Time', 'gss_krb5_lucid_context_v1', 'OpaqueJSValue', 'TrakTableEntry', 'AudioFramePacketTranslation', 'CGImageSource', 'OpaqueJSPropertyNameAccumulator', 'JustPCGlyphRepeatAddAction', '__CFBinaryHeap', 'OpaqueMIDIThruConnection', 'opaqueCMBufferQueue', 'OpaqueMusicSequence', 'MortRearrangementSubtable', 'MixerDistanceParams', 'MorxSubtable', 'MIDIObjectPropertyChangeNotification', 'SFNTLookupSegment', 'CGImageMetadataErrors', 'CGPath', 'OpaqueMIDIEndpoint', 'AudioComponentPlugInInterface', 'gss_ctx_id_t_desc_struct', 'sfntFontFeatureSetting', 'OpaqueJSContextGroup', '__SCNetworkConnection', 'AudioUnitParameterValueTranslation', 'CGImageMetadataType', 'CGPattern', 'AudioFileTypeAndFormatID', 'CGContext', 'AUNodeInteraction', 'SFNTLookupTable', 'JustPCDecompositionAction', 'KerxControlPointHeader', 'AudioStreamPacketDescription', 'KernSubtableHeader', '__SecCertificate', 'AUMIDIOutputCallbackStruct', 'MIDIMetaEvent', 'AudioQueueChannelAssignment', 'AnchorPoint', 'JustTable', '__CFNetService', 'CF_BRIDGED_TYPE', 'gss_krb5_lucid_key', 'CGPDFDictionary', 'KerxSubtableHeader', 'CAF_UUID_ChunkHeader', 'gss_krb5_cfx_keydata', 'OpaqueJSClass', 'CGGradient', 'OpaqueMIDISetup', 'JustPostcompTable', '__CTParagraphStyle', 'AudioUnitParameterHistoryInfo', 'OpaqueJSContext', 'CGShading', 'MIDIThruConnectionParams', 'BslnFormat0Part', 'SFNTLookupSingle', '__CFHost', '__SecRandom', '__CTFontDescriptor', '_NSRange', 'sfntDirectory', 'AudioQueueLevelMeterState', 'CAFPositionPeak', 'PropLookupSegment', '__CVOpenGLESTextureCache', 'sfntInstance', '_GLKQuaternion', 'AnkrTable', '__SCNetworkProtocol', 'gss_buffer_desc_struct', 'CAFFileHeader', 'KerxOrderedListHeader', 'CGBlendMode', 'STXEntryOne', 'CAFRegion', 'SFNTLookupTrimmedArrayHeader', 'SCNMatrix4', 'KerxControlPointEntry', 'OpaqueMusicTrack', '_GLKVector4', 'gss_OID_set_desc_struct', 'OpaqueMusicPlayer', '_CFHTTPAuthentication', 'CGAffineTransform', 'CAFMarkerChunk', 'AUHostIdentifier', 'ROTAGlyphEntry', 'BslnTable', 'gss_krb5_lucid_context_version', '_GLKMatrixStack', 'CGImage', 'KernStateEntry', 'SFNTLookupSingleHeader', 'MortLigatureSubtable', 'CAFUMIDChunk', 'SMPTETime', 'CAFDataChunk', 'CGPDFStream', 'AudioFileRegionList', 'STEntryTwo', 'SFNTLookupBinarySearchHeader', 'OpbdTable', '__CTGlyphInfo', 'BslnFormat2Part', 'KerxIndexArrayHeader', 'TrakTable', 'KerxKerningPair', '__CFBitVector', 'KernVersion0SubtableHeader', 'OpaqueAudioComponentInstance', 'AudioChannelLayout', '__CFUUID', 'MIDISysexSendRequest', '__CFNumberFormatter', 'CGImageSourceStatus', 'AudioFileMarkerList', 'AUSamplerBankPresetData', 'CGDataProvider', 'AudioFormatInfo', '__SecIdentity', 'sfntCMapExtendedSubHeader', 'MIDIChannelMessage', 'KernOffsetTable', 'CGColorSpaceModel', 'MFMailComposeErrorCode', 'CGFunction', '__SecTrust', 'AVAudio3DAngularOrientation', 'CGFontPostScriptFormat', 'KernStateHeader', 'AudioUnitCocoaViewInfo', 'CGDataConsumer', 'OpaqueMIDIDevice', 'KernVersion0Header', 'AnchorPointTable', 'CGImageDestination', 'CAFInstrumentChunk', 'AudioUnitMeterClipping', 'MorxChain', '__CTFontCollection', 'STEntryOne', 'STXEntryTwo', 'ExtendedNoteOnEvent', 'CGColorRenderingIntent', 'KerxSimpleArrayHeader', 'MorxTable', '_GLKVector3', '_GLKVector2', 'MortTable', 'CGPDFBox', 'AudioUnitParameterValueFromString', '__CFSocket', 'ALCdevice_struct', 'MIDINoteMessage', 'sfntFeatureHeader', 'CGRect', '__SCNetworkInterface', '__CFTree', 'MusicEventUserData', 'TrakTableData', 'GCQuaternion', 'MortContextualSubtable', '__CTRun', 'AudioUnitFrequencyResponseBin', 'MortChain', 'MorxInsertionSubtable', 'CGImageMetadata', 'gss_auth_identity', 'AudioUnitMIDIControlMapping', 'CAFChunkHeader', 'CGImagePropertyOrientation', 'CGPDFScanner', 'OpaqueMusicEventIterator', 'sfntDescriptorHeader', 'AudioUnitNodeConnection', 'OpaqueMIDIDeviceList', 'ExtendedAudioFormatInfo', 'BslnFormat1Part', 'sfntFontDescriptor', 'KernSimpleArrayHeader', '__CFRunLoopObserver', 'CGPatternTiling', 'MIDINotification', 'MorxLigatureSubtable', 'MessageComposeResult', 'MIDIThruConnectionEndpoint', 'MusicDeviceStdNoteParams', 'opaqueCMSimpleQueue', 'ALCcontext_struct', 'OpaqueAudioQueue', 'PropLookupSingle', 'CGInterpolationQuality', 'CGColor', 'AudioOutputUnitStartAtTimeParams', 'gss_name_t_desc_struct', 'CGFunctionCallbacks', 'CAFPacketTableHeader', 'AudioChannelDescription', 'sfntFeatureName', 'MorxContextualSubtable', 'CVSMPTETime', 'AudioValueRange', 'CGTextEncoding', 'AudioStreamBasicDescription', 'AUNodeRenderCallback', 'AudioPanningInfo', 'KerxOrderedListEntry', '__CFAllocator', 'OpaqueJSPropertyNameArray', '__SCDynamicStore', 'OpaqueMIDIEntity', '__CTRubyAnnotation', 'SCNVector4', 'CFHostClientContext', 'CFNetServiceClientContext', 'AudioUnitPresetMAS_SettingData', 'opaqueCMBufferQueueTriggerToken', 'AudioUnitProperty', 'CAFRegionChunk', 'CGPDFString', '__GLsync', '__CFStringTokenizer', 'JustWidthDeltaEntry', 'sfntVariationAxis', '__CFNetDiagnostic', 'CAFOverviewSample', 'sfntCMapEncoding', 'CGVector', '__SCNetworkService', 'opaqueCMSampleBuffer', 'AUHostVersionIdentifier', 'AudioBalanceFade', 'sfntFontRunFeature', 'KerxCoordinateAction', 'sfntCMapSubHeader', 'CVPlanarPixelBufferInfo', 'AUNumVersion', 'AUSamplerInstrumentData', 'AUPreset', '__CTRunDelegate', 'OpaqueAudioQueueProcessingTap', 'KerxTableHeader', '_NSZone', 'OpaqueExtAudioFile', '__CFRunLoopSource', '__CVMetalTextureCache', 'KerxAnchorPointAction', 'OpaqueJSString', 'AudioQueueParameterEvent', '__CFHTTPMessage', 'OpaqueCMClock', 'ScheduledAudioFileRegion', 'STEntryZero', 'AVAudio3DPoint', 'gss_channel_bindings_struct', 'sfntVariationHeader', 'AUChannelInfo', 'UIOffset', 'GLKEffectPropertyPrv', 'KerxStateHeader', 'CGLineJoin', 'CGPDFDocument', '__CFBag', 'KernOrderedListHeader', '__SCNetworkSet', '__SecKey', 'MIDIObjectAddRemoveNotification', 'AudioUnitParameter', 'JustPCActionSubrecord', 'AudioComponentDescription', 'AudioUnitParameterValueName', 'AudioUnitParameterEvent', 'KerxControlPointAction', 'AudioTimeStamp', 'KernKerningPair', 'gss_buffer_set_desc_struct', 'MortFeatureEntry', 'FontVariation', 'CAFStringID', 'LcarCaretClassEntry', 'AudioUnitParameterStringFromValue', 'ACErrorCode', 'ALMXGlyphEntry', 'LtagTable', '__CTTypesetter', 'AuthorizationOpaqueRef', 'UIEdgeInsets', 'CGPathElement', 'CAFMarker', 'KernTableHeader', 'NoteParamsControlValue', 'SSLContext', 'gss_cred_id_t_desc_struct', 'AudioUnitParameterNameInfo', 'CGDataConsumerCallbacks', 'ALMXHeader', 'CGLineCap', 'MIDIControlTransform', 'CGPDFArray', '__SecPolicy', 'AudioConverterPrimeInfo', '__CTTextTab', '__CFNetServiceMonitor', 'AUInputSamplesInOutputCallbackStruct', '__CTFramesetter', 'CGPDFDataFormat', 'STHeader', 'CVPlanarPixelBufferInfo_YCbCrPlanar', 'MIDIValueMap', 'JustDirectionTable', '__SCBondStatus', 'SFNTLookupSegmentHeader', 'OpaqueCMMemoryPool', 'CGPathDrawingMode', 'CGFont', '__SCNetworkReachability', 'AudioClassDescription', 'CGPoint', 'AVAudio3DVectorOrientation', 'CAFStrings', '__CFNetServiceBrowser', 'opaqueMTAudioProcessingTap', 'sfntNameRecord', 'CGPDFPage', 'CGLayer', 'ComponentInstanceRecord', 'CAFInfoStrings', 'HostCallbackInfo', 'MusicDeviceNoteParams', 'OpaqueVTCompressionSession', 'KernIndexArrayHeader', 'CVPlanarPixelBufferInfo_YCbCrBiPlanar', 'MusicTrackLoopInfo', 'opaqueCMFormatDescription', 'STClassTable', 'sfntDirectoryEntry', 'OpaqueCMTimebase', 'CGDataProviderDirectCallbacks', 'MIDIPacketList', 'CAFOverviewChunk', 'MIDIPacket', 'ScheduledAudioSlice', 'CGDataProviderSequentialCallbacks', 'AudioBuffer', 'MorxRearrangementSubtable', 'CGPatternCallbacks', 'AUDistanceAttenuationData', 'MIDIIOErrorNotification', 'CGPDFContentStream', 'IUnknownVTbl', 'MIDITransform', 'MortInsertionSubtable', 'CABarBeatTime', 'AudioBufferList', '__CVBuffer', 'AURenderCallbackStruct', 'STXEntryZero', 'JustPCDuctilityAction', 'OpaqueAudioQueueTimeline', 'VTDecompressionOutputCallbackRecord', 'OpaqueMIDIClient', '__CFPlugInInstance', 'AudioQueueBuffer', '__CFFileDescriptor', 'AudioUnitConnection', '_GKTurnBasedExchangeStatus', 'LcarCaretTable', 'CVPlanarComponentInfo', 'JustWidthDeltaGroup', 'OpaqueAudioComponent', 'ParameterEvent', '__CVPixelBufferPool', '__CTFont', 'CGColorSpace', 'CGSize', 'AUDependentParameter', 'MIDIDriverInterface', 'gss_krb5_rfc1964_keydata', '__CFDateFormatter', 'LtagStringRange', 'OpaqueVTDecompressionSession', 'gss_iov_buffer_desc_struct', 'AUPresetEvent', 'PropTable', 'KernOrderedListEntry', 'CF_BRIDGED_MUTABLE_TYPE', 'gss_OID_desc_struct', 'AudioUnitPresetMAS_Settings', 'AudioFileMarker', 'JustPCConditionalAddAction', 'BslnFormat3Part', '__CFNotificationCenter', 'MortSwashSubtable', 'AUParameterMIDIMapping', 'SCNVector3', 'OpaqueAudioConverter', 'MIDIRawData', 'sfntNameHeader', '__CFRunLoop', 'MFMailComposeResult', 'CATransform3D', 'OpbdSideValues', 'CAF_SMPTE_Time', '__SecAccessControl', 'JustPCAction', 'OpaqueVTFrameSilo', 'OpaqueVTMultiPassStorage', 'CGPathElementType', 'AudioFormatListItem', 'AudioUnitExternalBuffer', 'AudioFileRegion', 'AudioValueTranslation', 'CGImageMetadataTag', 'CAFPeakChunk', 'AudioBytePacketTranslation', 'sfntCMapHeader', '__CFURLEnumerator', 'STXHeader', 'CGPDFObjectType', 'SFNTLookupArrayHeader']) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover import os import re diff --git a/CadQuery/Libs/pygments/lexers/_lua_builtins.py b/CadQuery/Libs/pygments/lexers/_lua_builtins.py index 79a8da6..10808ef 100644 --- a/CadQuery/Libs/pygments/lexers/_lua_builtins.py +++ b/CadQuery/Libs/pygments/lexers/_lua_builtins.py @@ -143,7 +143,8 @@ MODULES = {'basic': ('_G', 'table.remove', 'table.sort')} -if __name__ == '__main__': + +if __name__ == '__main__': # pragma: no cover import re try: from urllib import urlopen diff --git a/CadQuery/Libs/pygments/lexers/_mapping.py b/CadQuery/Libs/pygments/lexers/_mapping.py index d14c014..05ada4f 100644 --- a/CadQuery/Libs/pygments/lexers/_mapping.py +++ b/CadQuery/Libs/pygments/lexers/_mapping.py @@ -67,7 +67,7 @@ LEXERS = { 'CheetahJavascriptLexer': ('pygments.lexers.templates', 'JavaScript+Cheetah', ('js+cheetah', 'javascript+cheetah', 'js+spitfire', 'javascript+spitfire'), (), ('application/x-javascript+cheetah', 'text/x-javascript+cheetah', 'text/javascript+cheetah', 'application/x-javascript+spitfire', 'text/x-javascript+spitfire', 'text/javascript+spitfire')), 'CheetahLexer': ('pygments.lexers.templates', 'Cheetah', ('cheetah', 'spitfire'), ('*.tmpl', '*.spt'), ('application/x-cheetah', 'application/x-spitfire')), 'CheetahXmlLexer': ('pygments.lexers.templates', 'XML+Cheetah', ('xml+cheetah', 'xml+spitfire'), (), ('application/xml+cheetah', 'application/xml+spitfire')), - 'CirruLexer': ('pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru', '*.cr'), ('text/x-cirru',)), + 'CirruLexer': ('pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru',), ('text/x-cirru',)), 'ClayLexer': ('pygments.lexers.c_like', 'Clay', ('clay',), ('*.clay',), ('text/x-clay',)), 'ClojureLexer': ('pygments.lexers.jvm', 'Clojure', ('clojure', 'clj'), ('*.clj',), ('text/x-clojure', 'application/x-clojure')), 'ClojureScriptLexer': ('pygments.lexers.jvm', 'ClojureScript', ('clojurescript', 'cljs'), ('*.cljs',), ('text/x-clojurescript', 'application/x-clojurescript')), @@ -372,7 +372,7 @@ LEXERS = { 'ZephirLexer': ('pygments.lexers.php', 'Zephir', ('zephir',), ('*.zep',), ()), } -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover import sys import os diff --git a/CadQuery/Libs/pygments/lexers/_php_builtins.py b/CadQuery/Libs/pygments/lexers/_php_builtins.py index 343b441..51b55de 100644 --- a/CadQuery/Libs/pygments/lexers/_php_builtins.py +++ b/CadQuery/Libs/pygments/lexers/_php_builtins.py @@ -4672,7 +4672,8 @@ MODULES = {'.NET': ('dotnet_load',), 'xdiff_string_patch', 'xdiff_string_rabdiff')} -if __name__ == '__main__': + +if __name__ == '__main__': # pragma: no cover import glob import os import pprint diff --git a/CadQuery/Libs/pygments/lexers/_postgres_builtins.py b/CadQuery/Libs/pygments/lexers/_postgres_builtins.py index 3846845..0324e35 100644 --- a/CadQuery/Libs/pygments/lexers/_postgres_builtins.py +++ b/CadQuery/Libs/pygments/lexers/_postgres_builtins.py @@ -507,7 +507,8 @@ PLPGSQL_KEYWORDS = ( 'RETURN', 'REVERSE', 'SQLSTATE', 'WHILE', ) -if __name__ == '__main__': + +if __name__ == '__main__': # pragma: no cover import re try: from urllib import urlopen diff --git a/CadQuery/Libs/pygments/lexers/_scilab_builtins.py b/CadQuery/Libs/pygments/lexers/_scilab_builtins.py index 6ffb255..84c2b58 100644 --- a/CadQuery/Libs/pygments/lexers/_scilab_builtins.py +++ b/CadQuery/Libs/pygments/lexers/_scilab_builtins.py @@ -3051,7 +3051,8 @@ variables_kw = ( 'xcoslib', ) -if __name__ == '__main__': + +if __name__ == '__main__': # pragma: no cover import subprocess from pygments.util import format_lines, duplicates_removed diff --git a/CadQuery/Libs/pygments/lexers/_sourcemod_builtins.py b/CadQuery/Libs/pygments/lexers/_sourcemod_builtins.py index 40ef529..021fc19 100644 --- a/CadQuery/Libs/pygments/lexers/_sourcemod_builtins.py +++ b/CadQuery/Libs/pygments/lexers/_sourcemod_builtins.py @@ -1091,7 +1091,9 @@ FUNCTIONS = ( 'SDKCall', 'GetPlayerResourceEntity', ) -if __name__ == '__main__': + + +if __name__ == '__main__': # pragma: no cover import re import sys try: diff --git a/CadQuery/Libs/pygments/lexers/console.py b/CadQuery/Libs/pygments/lexers/console.py index 5612058..c76ed64 100644 --- a/CadQuery/Libs/pygments/lexers/console.py +++ b/CadQuery/Libs/pygments/lexers/console.py @@ -18,7 +18,7 @@ __all__ = ['VCTreeStatusLexer', 'PyPyLogLexer'] class VCTreeStatusLexer(RegexLexer): """ - For colorizing output of version control status commans, like "hg + For colorizing output of version control status commands, like "hg status" or "svn status". .. versionadded:: 2.0 diff --git a/CadQuery/Libs/pygments/lexers/dotnet.py b/CadQuery/Libs/pygments/lexers/dotnet.py index 39c03d4..afdb778 100644 --- a/CadQuery/Libs/pygments/lexers/dotnet.py +++ b/CadQuery/Libs/pygments/lexers/dotnet.py @@ -497,7 +497,7 @@ class GenericAspxLexer(RegexLexer): # TODO support multiple languages within the same source file class CSharpAspxLexer(DelegatingLexer): """ - Lexer for highligting C# within ASP.NET pages. + Lexer for highlighting C# within ASP.NET pages. """ name = 'aspx-cs' @@ -518,7 +518,7 @@ class CSharpAspxLexer(DelegatingLexer): class VbNetAspxLexer(DelegatingLexer): """ - Lexer for highligting Visual Basic.net within ASP.NET pages. + Lexer for highlighting Visual Basic.net within ASP.NET pages. """ name = 'aspx-vb' @@ -623,6 +623,7 @@ class FSharpLexer(RegexLexer): (r'\b(member|override)(\s+)(\w+)(\.)(\w+)', bygroups(Keyword, Text, Name, Punctuation, Name.Function)), (r'\b(%s)\b' % '|'.join(keywords), Keyword), + (r'``([^`\n\r\t]|`[^`\n\r\t])+``', Name), (r'(%s)' % '|'.join(keyopts), Operator), (r'(%s|%s)?%s' % (infix_syms, prefix_syms, operators), Operator), (r'\b(%s)\b' % '|'.join(word_operators), Operator.Word), diff --git a/CadQuery/Libs/pygments/lexers/javascript.py b/CadQuery/Libs/pygments/lexers/javascript.py index 404bcf9..8e258f9 100644 --- a/CadQuery/Libs/pygments/lexers/javascript.py +++ b/CadQuery/Libs/pygments/lexers/javascript.py @@ -213,7 +213,7 @@ class LiveScriptLexer(RegexLexer): .. _LiveScript: http://gkz.github.com/LiveScript/ - New in Pygments 1.6. + .. versionadded:: 1.6 """ name = 'LiveScript' diff --git a/CadQuery/Libs/pygments/lexers/objective.py b/CadQuery/Libs/pygments/lexers/objective.py index 4e0ecf0..5b6fe27 100644 --- a/CadQuery/Libs/pygments/lexers/objective.py +++ b/CadQuery/Libs/pygments/lexers/objective.py @@ -279,6 +279,7 @@ class LogosLexer(ObjectiveCppLexer): return 1.0 return 0 + class SwiftLexer(RegexLexer): """ For `Swift `_ source. @@ -303,84 +304,89 @@ class SwiftLexer(RegexLexer): include('keywords'), # Global Types - (r'(Array|AutoreleasingUnsafeMutablePointer|BidirectionalReverseView' - r'|Bit|Bool|CFunctionPointer|COpaquePointer|CVaListPointer' - r'|Character|ClosedInterval|CollectionOfOne|ContiguousArray' - r'|Dictionary|DictionaryGenerator|DictionaryIndex|Double' - r'|EmptyCollection|EmptyGenerator|EnumerateGenerator' - r'|EnumerateSequence|FilterCollectionView' - r'|FilterCollectionViewIndex|FilterGenerator|FilterSequenceView' - r'|Float|Float80|FloatingPointClassification|GeneratorOf' - r'|GeneratorOfOne|GeneratorSequence|HalfOpenInterval|HeapBuffer' - r'|HeapBufferStorage|ImplicitlyUnwrappedOptional|IndexingGenerator' - r'|Int|Int16|Int32|Int64|Int8|LazyBidirectionalCollection' - r'|LazyForwardCollection|LazyRandomAccessCollection' - r'|LazySequence|MapCollectionView|MapSequenceGenerator' - r'|MapSequenceView|MirrorDisposition|ObjectIdentifier|OnHeap' - r'|Optional|PermutationGenerator|QuickLookObject' - r'|RandomAccessReverseView|Range|RangeGenerator|RawByte|Repeat' - r'|ReverseBidirectionalIndex|ReverseRandomAccessIndex|SequenceOf' - r'|SinkOf|Slice|StaticString|StrideThrough|StrideThroughGenerator' - r'|StrideTo|StrideToGenerator|String|UInt|UInt16|UInt32|UInt64' - r'|UInt8|UTF16|UTF32|UTF8|UnicodeDecodingResult|UnicodeScalar' - r'|Unmanaged|UnsafeBufferPointer|UnsafeBufferPointerGenerator' - r'|UnsafeMutableBufferPointer|UnsafeMutablePointer|UnsafePointer' - r'|Zip2|ZipGenerator2' - # Protocols - r'|AbsoluteValuable|AnyObject|ArrayLiteralConvertible' - r'|BidirectionalIndexType|BitwiseOperationsType' - r'|BooleanLiteralConvertible|BooleanType|CVarArgType' - r'|CollectionType|Comparable|DebugPrintable' - r'|DictionaryLiteralConvertible|Equatable' - r'|ExtendedGraphemeClusterLiteralConvertible' - r'|ExtensibleCollectionType|FloatLiteralConvertible' - r'|FloatingPointType|ForwardIndexType|GeneratorType|Hashable' - r'|IntegerArithmeticType|IntegerLiteralConvertible|IntegerType' - r'|IntervalType|MirrorType|MutableCollectionType|MutableSliceable' - r'|NilLiteralConvertible|OutputStreamType|Printable' - r'|RandomAccessIndexType|RangeReplaceableCollectionType' - r'|RawOptionSetType|RawRepresentable|Reflectable|SequenceType' - r'|SignedIntegerType|SignedNumberType|SinkType|Sliceable' - r'|Streamable|Strideable|StringInterpolationConvertible' - r'|StringLiteralConvertible|UnicodeCodecType' - r'|UnicodeScalarLiteralConvertible|UnsignedIntegerType' - r'|_ArrayBufferType|_BidirectionalIndexType|_CocoaStringType' - r'|_CollectionType|_Comparable|_ExtensibleCollectionType' - r'|_ForwardIndexType|_Incrementable|_IntegerArithmeticType' - r'|_IntegerType|_ObjectiveCBridgeable|_RandomAccessIndexType' - r'|_RawOptionSetType|_SequenceType|_Sequence_Type' - r'|_SignedIntegerType|_SignedNumberType|_Sliceable|_Strideable' - r'|_SwiftNSArrayRequiredOverridesType|_SwiftNSArrayType' - r'|_SwiftNSCopyingType|_SwiftNSDictionaryRequiredOverridesType' - r'|_SwiftNSDictionaryType|_SwiftNSEnumeratorType' - r'|_SwiftNSFastEnumerationType|_SwiftNSStringRequiredOverridesType' - r'|_SwiftNSStringType|_UnsignedIntegerType' - # Variables - r'|C_ARGC|C_ARGV|Process' - # Typealiases - r'|Any|AnyClass|BooleanLiteralType|CBool|CChar|CChar16|CChar32' - r'|CDouble|CFloat|CInt|CLong|CLongLong|CShort|CSignedChar' - r'|CUnsignedInt|CUnsignedLong|CUnsignedShort|CWideChar' - r'|ExtendedGraphemeClusterType|Float32|Float64|FloatLiteralType' - r'|IntMax|IntegerLiteralType|StringLiteralType|UIntMax|UWord' - r'|UnicodeScalarType|Void|Word' - # Foundation/Cocoa - r'|NSErrorPointer|NSObjectProtocol|Selector)\b', Name.Builtin), + (words(( + 'Array', 'AutoreleasingUnsafeMutablePointer', 'BidirectionalReverseView', + 'Bit', 'Bool', 'CFunctionPointer', 'COpaquePointer', 'CVaListPointer', + 'Character', 'ClosedInterval', 'CollectionOfOne', 'ContiguousArray', + 'Dictionary', 'DictionaryGenerator', 'DictionaryIndex', 'Double', + 'EmptyCollection', 'EmptyGenerator', 'EnumerateGenerator', + 'EnumerateSequence', 'FilterCollectionView', + 'FilterCollectionViewIndex', 'FilterGenerator', 'FilterSequenceView', + 'Float', 'Float80', 'FloatingPointClassification', 'GeneratorOf', + 'GeneratorOfOne', 'GeneratorSequence', 'HalfOpenInterval', 'HeapBuffer', + 'HeapBufferStorage', 'ImplicitlyUnwrappedOptional', 'IndexingGenerator', + 'Int', 'Int16', 'Int32', 'Int64', 'Int8', 'LazyBidirectionalCollection', + 'LazyForwardCollection', 'LazyRandomAccessCollection', + 'LazySequence', 'MapCollectionView', 'MapSequenceGenerator', + 'MapSequenceView', 'MirrorDisposition', 'ObjectIdentifier', 'OnHeap', + 'Optional', 'PermutationGenerator', 'QuickLookObject', + 'RandomAccessReverseView', 'Range', 'RangeGenerator', 'RawByte', 'Repeat', + 'ReverseBidirectionalIndex', 'ReverseRandomAccessIndex', 'SequenceOf', + 'SinkOf', 'Slice', 'StaticString', 'StrideThrough', 'StrideThroughGenerator', + 'StrideTo', 'StrideToGenerator', 'String', 'UInt', 'UInt16', 'UInt32', + 'UInt64', 'UInt8', 'UTF16', 'UTF32', 'UTF8', 'UnicodeDecodingResult', + 'UnicodeScalar', 'Unmanaged', 'UnsafeBufferPointer', + 'UnsafeBufferPointerGenerator', 'UnsafeMutableBufferPointer', + 'UnsafeMutablePointer', 'UnsafePointer', 'Zip2', 'ZipGenerator2', + # Protocols + 'AbsoluteValuable', 'AnyObject', 'ArrayLiteralConvertible', + 'BidirectionalIndexType', 'BitwiseOperationsType', + 'BooleanLiteralConvertible', 'BooleanType', 'CVarArgType', + 'CollectionType', 'Comparable', 'DebugPrintable', + 'DictionaryLiteralConvertible', 'Equatable', + 'ExtendedGraphemeClusterLiteralConvertible', + 'ExtensibleCollectionType', 'FloatLiteralConvertible', + 'FloatingPointType', 'ForwardIndexType', 'GeneratorType', 'Hashable', + 'IntegerArithmeticType', 'IntegerLiteralConvertible', 'IntegerType', + 'IntervalType', 'MirrorType', 'MutableCollectionType', 'MutableSliceable', + 'NilLiteralConvertible', 'OutputStreamType', 'Printable', + 'RandomAccessIndexType', 'RangeReplaceableCollectionType', + 'RawOptionSetType', 'RawRepresentable', 'Reflectable', 'SequenceType', + 'SignedIntegerType', 'SignedNumberType', 'SinkType', 'Sliceable', + 'Streamable', 'Strideable', 'StringInterpolationConvertible', + 'StringLiteralConvertible', 'UnicodeCodecType', + 'UnicodeScalarLiteralConvertible', 'UnsignedIntegerType', + '_ArrayBufferType', '_BidirectionalIndexType', '_CocoaStringType', + '_CollectionType', '_Comparable', '_ExtensibleCollectionType', + '_ForwardIndexType', '_Incrementable', '_IntegerArithmeticType', + '_IntegerType', '_ObjectiveCBridgeable', '_RandomAccessIndexType', + '_RawOptionSetType', '_SequenceType', '_Sequence_Type', + '_SignedIntegerType', '_SignedNumberType', '_Sliceable', '_Strideable', + '_SwiftNSArrayRequiredOverridesType', '_SwiftNSArrayType', + '_SwiftNSCopyingType', '_SwiftNSDictionaryRequiredOverridesType', + '_SwiftNSDictionaryType', '_SwiftNSEnumeratorType', + '_SwiftNSFastEnumerationType', '_SwiftNSStringRequiredOverridesType', + '_SwiftNSStringType', '_UnsignedIntegerType', + # Variables + 'C_ARGC', 'C_ARGV', 'Process', + # Typealiases + 'Any', 'AnyClass', 'BooleanLiteralType', 'CBool', 'CChar', 'CChar16', + 'CChar32', 'CDouble', 'CFloat', 'CInt', 'CLong', 'CLongLong', 'CShort', + 'CSignedChar', 'CUnsignedInt', 'CUnsignedLong', 'CUnsignedShort', + 'CWideChar', 'ExtendedGraphemeClusterType', 'Float32', 'Float64', + 'FloatLiteralType', 'IntMax', 'IntegerLiteralType', 'StringLiteralType', + 'UIntMax', 'UWord', 'UnicodeScalarType', 'Void', 'Word', + # Foundation/Cocoa + 'NSErrorPointer', 'NSObjectProtocol', 'Selector'), suffix=r'\b'), + Name.Builtin), # Functions - (r'(abs|advance|alignof|alignofValue|assert|assertionFailure' - r'|contains|count|countElements|debugPrint|debugPrintln|distance' - r'|dropFirst|dropLast|dump|enumerate|equal|extend|fatalError' - r'|filter|find|first|getVaList|indices|insert|isEmpty|join|last' - r'|lazy|lexicographicalCompare|map|max|maxElement|min|minElement' - r'|numericCast|overlaps|partition|precondition|preconditionFailure' - r'|prefix|print|println|reduce|reflect|removeAll|removeAtIndex' - r'|removeLast|removeRange|reverse|sizeof|sizeofValue|sort|sorted' - r'|splice|split|startsWith|stride|strideof|strideofValue|suffix' - r'|swap|toDebugString|toString|transcode|underestimateCount' - r'|unsafeAddressOf|unsafeBitCast|unsafeDowncast' - r'|withExtendedLifetime|withUnsafeMutablePointer' - r'|withUnsafeMutablePointers|withUnsafePointer|withUnsafePointers' - r'|withVaList)\b', Name.Builtin.Pseudo), + (words(( + 'abs', 'advance', 'alignof', 'alignofValue', 'assert', 'assertionFailure', + 'contains', 'count', 'countElements', 'debugPrint', 'debugPrintln', + 'distance', 'dropFirst', 'dropLast', 'dump', 'enumerate', 'equal', + 'extend', 'fatalError', 'filter', 'find', 'first', 'getVaList', 'indices', + 'insert', 'isEmpty', 'join', 'last', 'lazy', 'lexicographicalCompare', + 'map', 'max', 'maxElement', 'min', 'minElement', 'numericCast', 'overlaps', + 'partition', 'precondition', 'preconditionFailure', 'prefix', 'print', + 'println', 'reduce', 'reflect', 'removeAll', 'removeAtIndex', 'removeLast', + 'removeRange', 'reverse', 'sizeof', 'sizeofValue', 'sort', 'sorted', + 'splice', 'split', 'startsWith', 'stride', 'strideof', 'strideofValue', + 'suffix', 'swap', 'toDebugString', 'toString', 'transcode', + 'underestimateCount', 'unsafeAddressOf', 'unsafeBitCast', 'unsafeDowncast', + 'withExtendedLifetime', 'withUnsafeMutablePointer', + 'withUnsafeMutablePointers', 'withUnsafePointer', 'withUnsafePointers', + 'withVaList'), suffix=r'\b'), + Name.Builtin.Pseudo), # Implicit Block Variables (r'\$\d+', Name.Variable), @@ -406,16 +412,22 @@ class SwiftLexer(RegexLexer): (r'[a-zA-Z_]\w*', Name) ], 'keywords': [ - (r'(break|case|continue|default|do|else|fallthrough|for|if|in' - r'|return|switch|where|while)\b', Keyword), + (words(( + 'break', 'case', 'continue', 'default', 'do', 'else', + 'fallthrough', 'for', 'if', 'in', 'return', 'switch', 'where', + 'while'), suffix=r'\b'), + Keyword), (r'@availability\([^)]+\)', Keyword.Reserved), - (r'(associativity|convenience|dynamic|didSet|final|get|infix|inout' - r'|lazy|left|mutating|none|nonmutating|optional|override|postfix' - r'|precedence|prefix|Protocol|required|right|set|Type|unowned|weak' - r'|willSet|@(availability|autoclosure|noreturn' - r'|NSApplicationMain|NSCopying|NSManaged|objc' - r'|UIApplicationMain|IBAction|IBDesignable|IBInspectable' - r'|IBOutlet))\b',Keyword.Reserved), + (words(( + 'associativity', 'convenience', 'dynamic', 'didSet', 'final', + 'get', 'infix', 'inout', 'lazy', 'left', 'mutating', 'none', + 'nonmutating', 'optional', 'override', 'postfix', 'precedence', + 'prefix', 'Protocol', 'required', 'right', 'set', 'Type', + 'unowned', 'weak', 'willSet', '@availability', '@autoclosure', + '@noreturn', '@NSApplicationMain', '@NSCopying', '@NSManaged', + '@objc', '@UIApplicationMain', '@IBAction', '@IBDesignable', + '@IBInspectable', '@IBOutlet'), suffix=r'\b'), + Keyword.Reserved), (r'(as|dynamicType|false|is|nil|self|Self|super|true|__COLUMN__' r'|__FILE__|__FUNCTION__|__LINE__|_)\b', Keyword.Constant), (r'import\b', Keyword.Declaration, 'module'), @@ -425,9 +437,11 @@ class SwiftLexer(RegexLexer): bygroups(Keyword.Declaration, Text, Name.Function)), (r'(var|let)(\s+)([a-zA-Z_]\w*)', bygroups(Keyword.Declaration, Text, Name.Variable)), - (r'(class|deinit|enum|extension|func|import|init|internal|let' - r'|operator|private|protocol|public|static|struct|subscript' - r'|typealias|var)\b', Keyword.Declaration) + (words(( + 'class', 'deinit', 'enum', 'extension', 'func', 'import', 'init', + 'internal', 'let', 'operator', 'private', 'protocol', 'public', + 'static', 'struct', 'subscript', 'typealias', 'var'), suffix=r'\b'), + Keyword.Declaration) ], 'comment': [ (r':param: [a-zA-Z_]\w*|:returns?:|(FIXME|MARK|TODO):', diff --git a/CadQuery/Libs/pygments/lexers/python.py b/CadQuery/Libs/pygments/lexers/python.py index 259d1a9..01ab1e7 100644 --- a/CadQuery/Libs/pygments/lexers/python.py +++ b/CadQuery/Libs/pygments/lexers/python.py @@ -378,6 +378,7 @@ class PythonConsoleLexer(Lexer): tb = 0 for i, t, v in tblexer.get_tokens_unprocessed(curtb): yield tbindex+i, t, v + curtb = '' else: yield match.start(), Generic.Output, line if curcode: diff --git a/CadQuery/Libs/pygments/lexers/templates.py b/CadQuery/Libs/pygments/lexers/templates.py index c153f51..b106523 100644 --- a/CadQuery/Libs/pygments/lexers/templates.py +++ b/CadQuery/Libs/pygments/lexers/templates.py @@ -1122,7 +1122,7 @@ class HtmlPhpLexer(DelegatingLexer): class XmlPhpLexer(DelegatingLexer): """ - Subclass of `PhpLexer` that higlights unhandled data with the `XmlLexer`. + Subclass of `PhpLexer` that highlights unhandled data with the `XmlLexer`. """ name = 'XML+PHP' @@ -1180,7 +1180,7 @@ class JavascriptPhpLexer(DelegatingLexer): class HtmlSmartyLexer(DelegatingLexer): """ - Subclass of the `SmartyLexer` that highighlights unlexed data with the + Subclass of the `SmartyLexer` that highlights unlexed data with the `HtmlLexer`. Nested Javascript and CSS is highlighted too. @@ -1263,7 +1263,7 @@ class JavascriptSmartyLexer(DelegatingLexer): class HtmlDjangoLexer(DelegatingLexer): """ - Subclass of the `DjangoLexer` that highighlights unlexed data with the + Subclass of the `DjangoLexer` that highlights unlexed data with the `HtmlLexer`. Nested Javascript and CSS is highlighted too. @@ -1851,7 +1851,7 @@ class HandlebarsHtmlLexer(DelegatingLexer): class YamlJinjaLexer(DelegatingLexer): """ - Subclass of the `DjangoLexer` that highighlights unlexed data with the + Subclass of the `DjangoLexer` that highlights unlexed data with the `YamlLexer`. Commonly used in Saltstack salt states. diff --git a/CadQuery/Libs/pygments/unistring.py b/CadQuery/Libs/pygments/unistring.py index d7c4c32..ceff1ac 100644 --- a/CadQuery/Libs/pygments/unistring.py +++ b/CadQuery/Libs/pygments/unistring.py @@ -132,7 +132,7 @@ def allexcept(*args): return u''.join(globals()[cat] for cat in newcats) -def _handle_runs(char_list): +def _handle_runs(char_list): # pragma: no cover buf = [] for c in char_list: if len(c) == 1: @@ -149,7 +149,7 @@ def _handle_runs(char_list): yield u'%s-%s' % (a, b) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover import unicodedata # we need Py3 for the determination of the XID_* properties diff --git a/CadQuery/Libs/pygments/util.py b/CadQuery/Libs/pygments/util.py index 9f683c0..22fab2f 100644 --- a/CadQuery/Libs/pygments/util.py +++ b/CadQuery/Libs/pygments/util.py @@ -55,7 +55,7 @@ def get_bool_opt(options, optname, default=None): elif not isinstance(string, string_types): raise OptionError('Invalid type %r for option %s; use ' '1/0, yes/no, true/false, on/off' % ( - string, optname)) + string, optname)) elif string.lower() in ('1', 'yes', 'true', 'on'): return True elif string.lower() in ('0', 'no', 'false', 'off'): @@ -63,7 +63,7 @@ def get_bool_opt(options, optname, default=None): else: raise OptionError('Invalid value %r for option %s; use ' '1/0, yes/no, true/false, on/off' % ( - string, optname)) + string, optname)) def get_int_opt(options, optname, default=None): @@ -73,11 +73,11 @@ def get_int_opt(options, optname, default=None): except TypeError: raise OptionError('Invalid type %r for option %s; you ' 'must give an integer value' % ( - string, optname)) + string, optname)) except ValueError: raise OptionError('Invalid value %r for option %s; you ' 'must give an integer value' % ( - string, optname)) + string, optname)) def get_list_opt(options, optname, default=None): @@ -89,7 +89,7 @@ def get_list_opt(options, optname, default=None): else: raise OptionError('Invalid type %r for option %s; you ' 'must give a list value' % ( - val, optname)) + val, optname)) def docstring_headline(obj): @@ -185,6 +185,8 @@ def html_doctype_matches(text): _looks_like_xml_cache = {} + + def looks_like_xml(text): """Check if a doctype exists or if we have some tags.""" if xml_decl_re.match(text): @@ -200,6 +202,7 @@ def looks_like_xml(text): _looks_like_xml_cache[key] = rv return rv + # Python narrow build compatibility def _surrogatepair(c): @@ -210,6 +213,7 @@ def _surrogatepair(c): # http://www.unicode.org/book/ch03.pdf return (0xd7c0 + (c >> 10), (0xdc00 + (c & 0x3ff))) + def unirange(a, b): """Returns a regular expression string to match the given non-BMP range.""" if b < a: @@ -350,7 +354,8 @@ if sys.version_info < (3, 0): u_prefix = 'u' iteritems = dict.iteritems itervalues = dict.itervalues - import StringIO, cStringIO + import StringIO + import cStringIO # unfortunately, io.StringIO in Python 2 doesn't accept str at all StringIO = StringIO.StringIO BytesIO = cStringIO.StringIO @@ -362,7 +367,12 @@ else: u_prefix = '' iteritems = dict.items itervalues = dict.values - from io import StringIO, BytesIO + from io import StringIO, BytesIO, TextIOWrapper + + class UnclosingTextIOWrapper(TextIOWrapper): + # Don't close underlying buffer on destruction. + def close(self): + pass def add_metaclass(metaclass): diff --git a/CadQuery/Libs/pyqode/core/__init__.py b/CadQuery/Libs/pyqode/core/__init__.py index 8ac5097..bf97756 100644 --- a/CadQuery/Libs/pyqode/core/__init__.py +++ b/CadQuery/Libs/pyqode/core/__init__.py @@ -8,4 +8,4 @@ a series of modes and panels that might be useful for any kind of code editor widget, i.e. pyqode.core is a generic code editor widget. """ -__version__ = '2.5.dev1' +__version__ = '2.5.0' diff --git a/CadQuery/Libs/pyqode/core/api/code_edit.py b/CadQuery/Libs/pyqode/core/api/code_edit.py index cf02c38..1d72a7e 100644 --- a/CadQuery/Libs/pyqode/core/api/code_edit.py +++ b/CadQuery/Libs/pyqode/core/api/code_edit.py @@ -740,16 +740,24 @@ class CodeEdit(QtWidgets.QPlainTextEdit): """ cursor = self.textCursor() assert isinstance(cursor, QtGui.QTextCursor) - cursor.beginEditBlock() - col = cursor.positionInBlock() - cursor.select(cursor.LineUnderCursor) + has_selection = True + if not cursor.hasSelection(): + cursor.select(cursor.LineUnderCursor) + has_selection = False line = cursor.selectedText() - cursor.movePosition(cursor.EndOfBlock) - cursor.insertText('\n' + line) - cursor.movePosition(cursor.StartOfBlock) - cursor.movePosition(cursor.Right, cursor.MoveAnchor, col) - self.setTextCursor(cursor) + line = '\n'.join(line.split('\u2029')) + end = cursor.selectionEnd() + cursor.setPosition(end) + cursor.beginEditBlock() + cursor.insertText('\n') + cursor.insertText(line) cursor.endEditBlock() + if has_selection: + pos = cursor.position() + cursor.setPosition(end+1) + cursor.setPosition(pos, cursor.KeepAnchor) + self.setTextCursor(cursor) + @QtCore.Slot() def indent(self): @@ -1170,7 +1178,7 @@ class CodeEdit(QtWidgets.QPlainTextEdit): move = QtGui.QTextCursor.MoveAnchor if select: move = QtGui.QTextCursor.KeepAnchor - if delta: + if delta > 0: cursor.movePosition(QtGui.QTextCursor.Left, move, delta) else: cursor.movePosition(QtGui.QTextCursor.StartOfBlock, move) diff --git a/CadQuery/Libs/pyqode/core/managers/backend.py b/CadQuery/Libs/pyqode/core/managers/backend.py index ab46e33..98a68a9 100644 --- a/CadQuery/Libs/pyqode/core/managers/backend.py +++ b/CadQuery/Libs/pyqode/core/managers/backend.py @@ -27,6 +27,9 @@ class BackendManager(Manager): - send_request """ + LAST_PORT = None + LAST_PROCESS = None + SHARE_COUNT = 0 def __init__(self, editor): super(BackendManager, self).__init__(editor) @@ -35,6 +38,7 @@ class BackendManager(Manager): self.server_script = None self.interpreter = None self.args = None + self._shared = False @staticmethod def pick_free_port(): @@ -45,7 +49,8 @@ class BackendManager(Manager): test_socket.close() return free_port - def start(self, script, interpreter=sys.executable, args=None, error_callback=None): + def start(self, script, interpreter=sys.executable, args=None, + error_callback=None, reuse=False): """ Starts the backend process. @@ -65,29 +70,46 @@ class BackendManager(Manager): application (frozen backends do not require an interpreter). :param args: list of additional command line args to use to start the backend process. + :param reuse: True to reuse an existing backend process. WARNING: to + use this, your application must have one single server script. If + you're creating an app which supports multiple programming languages + you will need to merge all backend scripts into one single script, + otherwise the wrong script might be picked up). """ - if self.running: - self.stop() - self.server_script = script - self.interpreter = interpreter - self.args = args - backend_script = script.replace('.pyc', '.py') - self._port = self.pick_free_port() - if hasattr(sys, "frozen") and not backend_script.endswith('.py'): - # frozen backend script on windows/mac does not need an interpreter - program = backend_script - pgm_args = [str(self._port)] + self._shared = reuse + if reuse and BackendManager.SHARE_COUNT: + self._port = BackendManager.LAST_PORT + self._process = BackendManager.LAST_PROCESS + BackendManager.SHARE_COUNT += 1 else: - program = interpreter - pgm_args = [backend_script, str(self._port)] - if args: - pgm_args += args - self._process = BackendProcess(self.editor) - if error_callback: - self._process.error.connect(error_callback) - self._process.start(program, pgm_args) - _logger().info('starting backend process: %s %s', program, - ' '.join(pgm_args)) + if self.running: + self.stop() + self.server_script = script + self.interpreter = interpreter + self.args = args + backend_script = script.replace('.pyc', '.py') + self._port = self.pick_free_port() + if hasattr(sys, "frozen") and not backend_script.endswith('.py'): + # frozen backend script on windows/mac does not need an + # interpreter + program = backend_script + pgm_args = [str(self._port)] + else: + program = interpreter + pgm_args = [backend_script, str(self._port)] + if args: + pgm_args += args + self._process = BackendProcess(self.editor) + if error_callback: + self._process.error.connect(error_callback) + self._process.start(program, pgm_args) + + if reuse: + BackendManager.LAST_PROCESS = self._process + BackendManager.LAST_PORT = self._port + BackendManager.SHARE_COUNT += 1 + _logger().info('starting backend process: %s %s', program, + ' '.join(pgm_args)) def stop(self): """ @@ -95,6 +117,10 @@ class BackendManager(Manager): """ if self._process is None: return + if self._shared: + BackendManager.SHARE_COUNT -= 1 + if BackendManager.SHARE_COUNT: + return _logger().debug('stopping backend process') # close all sockets for socket in self._sockets: diff --git a/CadQuery/Libs/pyqode/core/modes/__init__.py b/CadQuery/Libs/pyqode/core/modes/__init__.py index 0e26bfb..40a43ab 100644 --- a/CadQuery/Libs/pyqode/core/modes/__init__.py +++ b/CadQuery/Libs/pyqode/core/modes/__init__.py @@ -17,6 +17,7 @@ from .filewatcher import FileWatcherMode from .indenter import IndenterMode from .matcher import SymbolMatcherMode from .occurences import OccurrencesHighlighterMode +from .outline import OutlineMode from .right_margin import RightMarginMode from .pygments_sh import PygmentsSH from .wordclick import WordClickMode @@ -39,6 +40,7 @@ __all__ = [ 'FileWatcherMode', 'IndenterMode', 'OccurrencesHighlighterMode', + 'OutlineMode', 'PygmentsSH', 'PygmentsSyntaxHighlighter', 'PYGMENTS_STYLES', diff --git a/CadQuery/Libs/pyqode/core/modes/autocomplete.py b/CadQuery/Libs/pyqode/core/modes/autocomplete.py index 1566eea..b5b26bf 100644 --- a/CadQuery/Libs/pyqode/core/modes/autocomplete.py +++ b/CadQuery/Libs/pyqode/core/modes/autocomplete.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ Contains the AutoCompleteMode """ import logging +from pyqode.qt import QtCore from pyqode.core.api import TextHelper from pyqode.core.api.mode import Mode @@ -71,10 +72,29 @@ class AutoCompleteMode(Mode): next_char = TextHelper(self.editor).get_right_character() self.logger.debug('next char: %s', next_char) ignore = False - if txt and next_char == txt and next_char in self.MAPPING: + if event.key() == QtCore.Qt.Key_Backspace: + # get the character that will get deleted + tc = self.editor.textCursor() + pos = tc.position() + tc.movePosition(tc.Left) + tc.movePosition(tc.Right, tc.KeepAnchor) + del_char = tc.selectedText() + if del_char in self.MAPPING and self.MAPPING[del_char] == next_char: + tc.beginEditBlock() + tc.movePosition(tc.Right, tc.KeepAnchor) + tc.insertText('') + tc.setPosition(pos - 2) + tc.endEditBlock() + self.editor.setTextCursor(tc) + ignore = True + elif txt and next_char == txt and next_char in self.MAPPING: ignore = True elif event.text() == ')' or event.text() == ']' or event.text() == '}': - if next_char == ')' or next_char == ']' or next_char == '}': + # if typing the same symbol twice, the symbol should not be written + # and the cursor moved just after the char + # e.g. if you type ) just before ), the cursor will just move after + # the existing ) + if next_char == event.text(): ignore = True if ignore: event.accept() diff --git a/CadQuery/Libs/pyqode/core/modes/filewatcher.py b/CadQuery/Libs/pyqode/core/modes/filewatcher.py index 4f6f44b..8f940e1 100644 --- a/CadQuery/Libs/pyqode/core/modes/filewatcher.py +++ b/CadQuery/Libs/pyqode/core/modes/filewatcher.py @@ -3,8 +3,10 @@ Contains the mode that control the external changes of file. """ import os +from pyqode.core.api import TextHelper from pyqode.core.api.mode import Mode from pyqode.qt import QtCore, QtWidgets +from pyqode.core.cache import Cache class FileWatcherMode(Mode, QtCore.QObject): @@ -57,9 +59,6 @@ class FileWatcherMode(Mode, QtCore.QObject): if state: self.editor.new_text_set.connect(self._update_mtime) self.editor.new_text_set.connect(self._timer.start) - # self.editor.text_saved.connect(self._update_mtime) - # self.editor.text_saved.connect(self._timer.start) - # self.editor.text_saving.connect(self._timer.stop) self.editor.text_saving.connect(self._cancel_next_change) self.editor.text_saved.connect(self._restart_monitoring) self.editor.focused_in.connect(self._check_for_pending) @@ -67,9 +66,6 @@ class FileWatcherMode(Mode, QtCore.QObject): self._timer.stop() self.editor.new_text_set.connect(self._update_mtime) self.editor.new_text_set.connect(self._timer.start) - # self.editor.text_saved.disconnect(self._update_mtime) - # self.editor.text_saved.disconnect(self._timer.start) - # self.editor.text_saving.disconnect(self._timer.stop) self.editor.text_saving.disconnect(self._cancel_next_change) self.editor.text_saved.disconnect(self._restart_monitoring) self.editor.focused_in.disconnect(self._check_for_pending) @@ -147,6 +143,12 @@ class FileWatcherMode(Mode, QtCore.QObject): """ def inner_action(*args): """ Inner action: open file """ + # cache cursor position before reloading so that the cursor position + # is restored automatically after reload has finished. + # See OpenCobolIDE/OpenCobolIDE#97 + Cache().set_cursor_position( + self.editor.file.path, + TextHelper(self.editor).cursor_position()) self.editor.file.open(self.editor.file.path) self.file_reloaded.emit() diff --git a/CadQuery/Libs/pyqode/core/modes/occurences.py b/CadQuery/Libs/pyqode/core/modes/occurences.py index a5c02ac..9f6ba2f 100644 --- a/CadQuery/Libs/pyqode/core/modes/occurences.py +++ b/CadQuery/Libs/pyqode/core/modes/occurences.py @@ -143,6 +143,12 @@ class OccurrencesHighlighterMode(Mode): self._request_highlight() def _on_results_available(self, results): + if len(results) > 500: + # limit number of results (on very big file where a lots of + # occurrences can be found, this would totally freeze the editor + # during a few seconds, with a limit of 500 we can make sure + # the editor will always remain responsive). + results = results[:500] if len(results) > 1: for start, end in results: deco = TextDecoration(self.editor.textCursor(), diff --git a/CadQuery/Libs/pyqode/core/modes/outline.py b/CadQuery/Libs/pyqode/core/modes/outline.py new file mode 100644 index 0000000..c628681 --- /dev/null +++ b/CadQuery/Libs/pyqode/core/modes/outline.py @@ -0,0 +1,79 @@ +import logging +from pyqode.core.api import Mode +from pyqode.core.api import DelayJobRunner +from pyqode.core.backend import NotRunning +from pyqode.core.share import Definition +from pyqode.qt import QtCore + + +def _logger(): + return logging.getLogger(__name__) + + +class OutlineMode(Mode, QtCore.QObject): + """ + Generic mode that provides outline information through the + document_changed signal and a specialised worker function. + + To use this mode, you need to write a worker function that returns a list + of pyqode.core.share.Definition (see + pyqode.python.backend.workers.defined_names() for an example of how to + implement the worker function). + """ + + #: Signal emitted when the document structure changed. + document_changed = QtCore.Signal() + + @property + def definitions(self): + """ + Gets the list of top level definitions. + """ + return self._results + + def __init__(self, worker, delay=1000): + Mode.__init__(self) + QtCore.QObject.__init__(self) + self._worker = worker + self._jobRunner = DelayJobRunner(delay=delay) + #: The list of definitions found in the file, each item is a + #: pyqode.core.share.Definition. + self._results = [] + + def on_state_changed(self, state): + if state: + self.editor.new_text_set.connect(self._run_analysis) + self.editor.textChanged.connect(self._request_analysis) + else: + self.editor.textChanged.disconnect(self._request_analysis) + self.editor.new_text_set.disconnect(self._run_analysis) + self._jobRunner.cancel_requests() + + def _request_analysis(self): + self._jobRunner.request_job(self._run_analysis) + + def _run_analysis(self): + if self.enabled and self.editor and self.editor.toPlainText() and \ + self.editor.file: + request_data = { + 'code': self.editor.toPlainText(), + 'path': self.editor.file.path, + 'encoding': self.editor.file.encoding + } + try: + self.editor.backend.send_request( + self._worker, request_data, + on_receive=self._on_results_available) + except NotRunning: + QtCore.QTimer.singleShot(100, self._run_analysis) + else: + self._results = [] + self.document_changed.emit() + + def _on_results_available(self, results): + if results: + results = [Definition.from_dict(ddict) for ddict in results] + self._results = results + if self._results is not None: + _logger().debug("Document structure changed") + self.document_changed.emit() diff --git a/CadQuery/Libs/pyqode/core/panels/folding.py b/CadQuery/Libs/pyqode/core/panels/folding.py index 6a32ac1..15808e5 100644 --- a/CadQuery/Libs/pyqode/core/panels/folding.py +++ b/CadQuery/Libs/pyqode/core/panels/folding.py @@ -9,7 +9,7 @@ from pyqode.core.api import TextBlockHelper, folding, TextDecoration, \ DelayJobRunner from pyqode.core.api.folding import FoldScope from pyqode.core.api.panel import Panel -from pyqode.qt import QtCore, QtWidgets, QtGui +from pyqode.qt import QtCore, QtWidgets, QtGui, PYQT5_API from pyqode.core.api.utils import TextHelper, drift_color, keep_tc_pos @@ -19,6 +19,7 @@ def _logger(): class FoldingPanel(Panel): + """ Displays the document outline and lets the user collapse/expand blocks. The data represented by the panel come from the text block user state and @@ -356,7 +357,7 @@ class FoldingPanel(Panel): rect = QtCore.QRect(0, top, self.sizeHint().width(), self.sizeHint().height()) if self._native: - if os.environ['QT_API'].lower() != 'pyqt5': + if os.environ['QT_API'].lower() not in PYQT5_API: opt = QtGui.QStyleOptionViewItemV2() else: opt = QtWidgets.QStyleOptionViewItem() diff --git a/CadQuery/Libs/pyqode/core/share.py b/CadQuery/Libs/pyqode/core/share.py new file mode 100644 index 0000000..ca71f2b --- /dev/null +++ b/CadQuery/Libs/pyqode/core/share.py @@ -0,0 +1,60 @@ +""" +This module contains some definition that can be shared +between the backend and the frontend (e.g. this module can be imported +without requiring any additional libraries). +""" + + +class Definition(object): + """ + Represents a definition in a source file. + + This class is used by the OutlineExplorerWidget to show the defined names + in a source file. + + Definition usually form a tree (a definition may have some child + definition, e.g. methods of a class). + """ + def __init__(self, name, line, column=0, icon=''): + #: Icon resource name associated with the definition, can be None + self.icon = icon + #: Definition name (name of the class, method, variable) + self.name = name + #: The line of the definition in the current editor text + self.line = line + #: The column of the definition in the current editor text + self.column = column + #: Possible list of children (only classes have children) + self.children = [] + + def add_child(self, definition): + """ + Adds a child definition + """ + self.children.append(definition) + + def to_dict(self): + """ + Serializes a definition to a dictionary, ready for json. + + Children are serialised recursively. + """ + ddict = {'name': self.name, 'icon': self.icon, + 'line': self.line, 'column': self.column, + 'children': []} + for child in self.children: + ddict['children'].append(child.to_dict()) + return ddict + + @staticmethod + def from_dict(ddict): + """ + Deserializes a definition from a simple dict. + """ + d = Definition(ddict['name'], ddict['line'], ddict['column'], ddict['icon']) + for child_dict in ddict['children']: + d.children.append(Definition.from_dict(child_dict)) + return d + + def __repr__(self): + return 'Definition(%r, %r, %r, %r)' % (self.name, self.line, self.column, self.icon) diff --git a/CadQuery/Libs/pyqode/core/widgets/__init__.py b/CadQuery/Libs/pyqode/core/widgets/__init__.py index ff7ff91..35681bb 100644 --- a/CadQuery/Libs/pyqode/core/widgets/__init__.py +++ b/CadQuery/Libs/pyqode/core/widgets/__init__.py @@ -12,6 +12,7 @@ pyqode applications: - CodeEditTabWidget: tab widget made to handle CodeEdit instances (or any other object that have the same interface). - ErrorsTable: a QTableWidget specialised to show CheckerMessage. + - OutlineTreeWidget: a widget that show the outline of an editor. """ @@ -25,6 +26,7 @@ from pyqode.core.widgets.menu_recents import RecentFilesManager from pyqode.core.widgets.tabs import TabWidget from pyqode.core.widgets.tab_bar import TabBar from pyqode.core.widgets.prompt_line_edit import PromptLineEdit +from pyqode.core.widgets.outline import OutlineTreeWidget from pyqode.core.widgets.splittable_tab_widget import ( SplittableTabWidget, SplittableCodeEditTabWidget) from pyqode.core.widgets.filesystem_treeview import FileSystemTreeView @@ -46,6 +48,7 @@ __all__ = [ 'TextCodeEdit', 'GenericCodeEdit', 'PromptLineEdit', + 'OutlineTreeWidget', 'SplittableTabWidget', 'SplittableCodeEditTabWidget', 'TabBar' diff --git a/CadQuery/Libs/pyqode/core/widgets/code_edits.py b/CadQuery/Libs/pyqode/core/widgets/code_edits.py index 28c7d60..28f57ab 100644 --- a/CadQuery/Libs/pyqode/core/widgets/code_edits.py +++ b/CadQuery/Libs/pyqode/core/widgets/code_edits.py @@ -35,11 +35,13 @@ class TextCodeEdit(CodeEdit): def __init__(self, parent=None, server_script=server.__file__, interpreter=sys.executable, args=None, - create_default_actions=True, color_scheme='qt'): + create_default_actions=True, color_scheme='qt', + reuse_backend=False): from pyqode.core import panels from pyqode.core import modes super(TextCodeEdit, self).__init__(parent, create_default_actions) - self.backend.start(server_script, interpreter, args) + self.backend.start(server_script, interpreter, args, + reuse=reuse_backend) # append panels self.panels.append(panels.FoldingPanel()) @@ -86,11 +88,13 @@ class GenericCodeEdit(CodeEdit): def __init__(self, parent=None, server_script=server.__file__, interpreter=sys.executable, args=None, - create_default_actions=True, color_scheme='qt'): + create_default_actions=True, color_scheme='qt', + reuse_backend=False): super(GenericCodeEdit, self).__init__(parent, create_default_actions) from pyqode.core import panels from pyqode.core import modes - self.backend.start(server_script, interpreter, args) + self.backend.start(server_script, interpreter, args, + reuse=reuse_backend) # append panels self.panels.append(panels.FoldingPanel()) self.panels.append(panels.LineNumberPanel()) diff --git a/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py b/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py index f2eaed8..c38eadf 100644 --- a/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py +++ b/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py @@ -34,14 +34,13 @@ class FileSystemTreeView(QtWidgets.QTreeView): Excludes :attr:`ignored_directories` and :attr:`ignored_extensions` from the file system model. """ - #: The list of directories to exclude - ignored_directories = ['__pycache__'] - #: The list of file extension to exclude - ignored_extensions = ['.pyc', '.pyd', '.so', '.dll', '.exe', - '.egg-info', '.coverage', '.DS_Store'] - def __init__(self): super(FileSystemTreeView.FilterProxyModel, self).__init__() + #: The list of directories to ignore + self.ignored_directories = ['__pycache__'] + #: The list of file extension to exclude + self.ignored_extensions = ['.pyc', '.pyd', '.so', '.dll', '.exe', + '.egg-info', '.coverage', '.DS_Store'] self._ignored_unused = [] def set_root_path(self, path): @@ -98,9 +97,10 @@ class FileSystemTreeView(QtWidgets.QTreeView): self.customContextMenuRequested.connect(self._show_context_menu) self.helper = FileSystemHelper(self) self.setSelectionMode(self.ExtendedSelection) + self._ignored_extensions = [] + self._ignored_directories = [] - @classmethod - def ignore_directories(cls, *directories): + def ignore_directories(self, *directories): """ Adds the specified directories to the list of ignored directories. @@ -109,10 +109,9 @@ class FileSystemTreeView(QtWidgets.QTreeView): :param directories: the directories to ignore """ for d in directories: - cls.FilterProxyModel.ignored_directories.append(d) + self._ignored_directories.append(d) - @classmethod - def ignore_extensions(cls, *extensions): + def ignore_extensions(self, *extensions): """ Adds the specified extensions to the list of ignored directories. @@ -123,7 +122,7 @@ class FileSystemTreeView(QtWidgets.QTreeView): .. note:: extension must have the dot: '.py' and not 'py' """ for d in extensions: - cls.FilterProxyModel.ignored_extensions.append(d) + self._ignored_extensions.append(d) def set_context_menu(self, context_menu): """ @@ -146,6 +145,10 @@ class FileSystemTreeView(QtWidgets.QTreeView): path = os.path.abspath(os.path.join(path, os.pardir)) self._fs_model_source = QtWidgets.QFileSystemModel() self._fs_model_proxy = self.FilterProxyModel() + for item in self._ignored_directories: + self._fs_model_proxy.ignored_directories.append(item) + for item in self._ignored_extensions: + self._fs_model_proxy.ignored_extensions.append(item) self._fs_model_proxy.setSourceModel(self._fs_model_source) self.setModel(self._fs_model_proxy) self._fs_model_proxy.set_root_path(path) @@ -519,7 +522,15 @@ class FileSystemContextMenu(QtWidgets.QMenu): self.action_delete.triggered.connect(self._on_delete_triggered) self.addAction(self.action_delete) self.addSeparator() - action = self.addAction('Show in explorer') + + system = platform.system() + if system == 'Windows': + text = 'Open in explorer' + elif system == 'Darwin': + text = 'Open in finder' + else: + text = 'Show in %s' % self.get_linux_file_explorer().capitalize() + action = self.addAction(text) action.triggered.connect(self._on_show_in_explorer_triggered) def get_new_user_actions(self): @@ -564,13 +575,20 @@ class FileSystemContextMenu(QtWidgets.QMenu): def _on_create_file_triggered(self): self.tree_view.helper.create_file() + def get_linux_file_explorer(self): + output = subprocess.check_output( + ['xdg-mime', 'query', 'default', 'inode/directory']).decode() + if output: + explorer = output.splitlines()[0].replace( + '.desktop', '').split('.')[-1].lower() + return explorer + return 'nautilus' + def _on_show_in_explorer_triggered(self): path = self.tree_view.helper.get_current_path() system = platform.system() if system == 'Linux': - output = subprocess.check_output( - ['xdg-mime', 'query', 'default', 'inode/directory']).decode() - explorer = output.splitlines()[0].replace('.desktop', '') + explorer = self.get_linux_file_explorer() if explorer in ['nautilus', 'dolphin']: subprocess.Popen([explorer, '--select', path]) else: diff --git a/CadQuery/Libs/pyqode/core/widgets/interactive.py b/CadQuery/Libs/pyqode/core/widgets/interactive.py index 8b8cfff..fdad36c 100644 --- a/CadQuery/Libs/pyqode/core/widgets/interactive.py +++ b/CadQuery/Libs/pyqode/core/widgets/interactive.py @@ -45,16 +45,13 @@ class InteractiveConsole(QTextEdit): self._app_msg_col = QColor("#4040FF") self._stdin_col = QColor("#22AA22") self._stderr_col = QColor("#FF0000") - self._process = None + self._write_app_messages = True + self._process_name = '' + self.process = None self._args = None self._usr_buffer = "" self._clear_on_start = True - self.process = QProcess() self._merge_outputs = False - self.process.finished.connect(self._on_process_finished) - self.process.error.connect(self._write_error) - self.process.readyReadStandardError.connect(self._on_stderr) - self.process.readyReadStandardOutput.connect(self._on_stdout) self._running = False self._writer = self.write self._user_stop = False @@ -95,6 +92,14 @@ class InteractiveConsole(QTextEdit): _logger().debug('%s', txt) self._writer(self, txt, self.stderr_color) + @property + def write_app_messages(self): + return self._write_app_messages + + @write_app_messages.setter + def write_app_messages(self, value): + self._write_app_messages = value + @property def background_color(self): """ The console background color. Default is white. """ @@ -220,6 +225,11 @@ class InteractiveConsole(QTextEdit): if args is None: args = [] if not self._running: + self.process = QProcess() + self.process.finished.connect(self._on_process_finished) + self.process.error.connect(self._write_error) + self.process.readyReadStandardError.connect(self._on_stderr) + self.process.readyReadStandardOutput.connect(self._on_stdout) if cwd: self.process.setWorkingDirectory(cwd) e = self.process.systemEnvironment() @@ -231,13 +241,13 @@ class InteractiveConsole(QTextEdit): ev.insert(k, v) self.process.setProcessEnvironment(ev) self._running = True - self._process = process + self._process_name = process self._args = args if self._clear_on_start: self.clear() self._user_stop = False - self.process.start(process, args) self._write_started() + self.process.start(process, args) else: _logger().warning('a process is already running') @@ -246,9 +256,11 @@ class InteractiveConsole(QTextEdit): Stop the process (by killing it). """ _logger().debug('killing process') - self._user_stop = True - self.process.kill() - self.setReadOnly(True) + if self.process is not None: + self._user_stop = True + self.process.kill() + self.setReadOnly(True) + self._running = False def keyPressEvent(self, event): if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: @@ -274,9 +286,10 @@ class InteractiveConsole(QTextEdit): def _on_process_finished(self, exit_code, exit_status): if not self._user_stop: - self._writer( - self, "\nProcess finished with exit code %d" % - exit_code, self._app_msg_col) + if self._write_app_messages: + self._writer( + self, "\nProcess finished with exit code %d" % + exit_code, self._app_msg_col) self._running = False _logger().debug('process finished (exit_code=%r, exit_status=%r)', exit_code, exit_status) @@ -284,8 +297,10 @@ class InteractiveConsole(QTextEdit): self.setReadOnly(True) def _write_started(self): + if not self._write_app_messages: + return self._writer(self, "{0} {1}\n".format( - self._process, " ".join(self._args)), self._app_msg_col) + self._process_name, " ".join(self._args)), self._app_msg_col) self._running = True _logger().debug('process started') @@ -295,7 +310,7 @@ class InteractiveConsole(QTextEdit): self.app_msg_color) else: self._writer(self, "Failed to start {0} {1}\n".format( - self._process, " ".join(self._args)), self.app_msg_color) + self._process_name, " ".join(self._args)), self.app_msg_color) err = PROCESS_ERROR_STRING[error] self._writer(self, "Error: %s" % err, self.stderr_color) _logger().debug('process error: %s', err) diff --git a/CadQuery/Libs/pyqode/core/widgets/menu_recents.py b/CadQuery/Libs/pyqode/core/widgets/menu_recents.py index 6968689..f0e16a2 100644 --- a/CadQuery/Libs/pyqode/core/widgets/menu_recents.py +++ b/CadQuery/Libs/pyqode/core/widgets/menu_recents.py @@ -43,8 +43,23 @@ class RecentFilesManager(QtCore.QObject): :param default: default value. :return: value """ - return [os.path.normpath(pth) for pth in - self._settings.value('recent_files/%s' % key, default)] + def unique(seq, idfun=None): + if idfun is None: + def idfun(x): + return x + # order preserving + seen = {} + result = [] + for item in seq: + marker = idfun(item) + if marker in seen: + continue + seen[marker] = 1 + result.append(item) + return result + + return unique([os.path.normpath(pth) for pth in + self._settings.value('recent_files/%s' % key, default)]) def set_value(self, key, value): """ @@ -105,7 +120,7 @@ class MenuRecentFiles(QtWidgets.QMenu): """ Menu that manage the list of recent files. - To use the menu, simply pass connect to the open_requested signal. + To use the menu, simply connect to the open_requested signal. """ #: Signal emitted when the user clicked on a recent file action. diff --git a/CadQuery/Libs/pyqode/core/widgets/outline.py b/CadQuery/Libs/pyqode/core/widgets/outline.py new file mode 100644 index 0000000..8fa132c --- /dev/null +++ b/CadQuery/Libs/pyqode/core/widgets/outline.py @@ -0,0 +1,172 @@ +""" +This document contains the tree widget used to display the editor document +outline. + +""" +from pyqode.core.panels import FoldingPanel +from pyqode.core.modes.outline import OutlineMode +from pyqode.qt import QtCore, QtGui, QtWidgets +from pyqode.core.api import TextBlockHelper, TextBlockUserData, TextHelper + + +class OutlineTreeWidget(QtWidgets.QTreeWidget): + """ + Displays the outline of a CodeEdit. + + To use this widget: + + 1. add an OutlineMode to CodeEdit + 2. call set_editor with a CodeEdit instance to show it's outline. + + """ + def __init__(self, parent=None): + super(OutlineTreeWidget, self).__init__(parent) + self._editor = None + self._outline_mode = None + self._folding_panel = None + self._expanded_items = [] + self.setHeaderHidden(True) + self.itemClicked.connect(self._on_item_clicked) + self.itemCollapsed.connect(self._on_item_state_changed) + self.itemExpanded.connect(self._on_item_state_changed) + self._updating = True + + def set_editor(self, editor): + """ + Sets the current editor. The widget display the structure of that + editor. + + :param editor: CodeEdit + """ + if self._outline_mode: + try: + self._outline_mode.document_changed.disconnect(self._on_changed) + except (TypeError, RuntimeError): + pass + if self._folding_panel: + try: + self._folding_panel.trigger_state_changed.disconnect( + self._on_block_state_changed) + except (TypeError, RuntimeError): + pass + self._editor = editor + if self._editor is not None: + try: + self._folding_panel = editor.panels.get(FoldingPanel) + except KeyError: + pass + else: + self._folding_panel.trigger_state_changed.connect( + self._on_block_state_changed) + try: + analyser = editor.modes.get(OutlineMode) + except KeyError: + self._outline_mode = None + else: + self._outline_mode = analyser + analyser.document_changed.connect(self._on_changed) + self._on_changed() + + def _on_item_state_changed(self, item): + if self._updating: + return + block = item.data(0, QtCore.Qt.UserRole).block + assert isinstance(item, QtWidgets.QTreeWidgetItem) + item_state = not item.isExpanded() + block_state = TextBlockHelper.get_fold_trigger_state(block) + if item_state != block_state: + self._updating = True + self._folding_panel.toggle_fold_trigger(block) + self._updating = False + + def _on_block_state_changed(self, block, state): + if self._updating: + return + data = block.userData() + if data is not None: + try: + item_state = not data.tree_item.isExpanded() + if item_state != state: + if state: + self.collapseItem(data.tree_item) + else: + self.expandItem(data.tree_item) + except AttributeError: + # a block that is not represented in the tree view has + # folded/unfolded, just ignore it + pass + + def _on_changed(self): + """ + Update the tree items + """ + self._updating = True + to_collapse = [] + self.clear() + if self._editor and self._outline_mode and self._folding_panel: + items, to_collapse = self.to_tree_widget_items( + self._outline_mode.definitions, to_collapse=to_collapse) + if len(items): + self.addTopLevelItems(items) + self.expandAll() + for item in reversed(to_collapse): + self.collapseItem(item) + self._updating = False + return + + # no data + root = QtWidgets.QTreeWidgetItem() + root.setText(0, 'No data') + root.setIcon(0, QtGui.QIcon.fromTheme( + 'dialog-information', + QtGui.QIcon(':/pyqode-icons/rc/dialog-info.png'))) + self.addTopLevelItem(root) + self._updating = False + + def _on_item_clicked(self, item): + """ + Go to the item position in the editor. + """ + if item: + name = item.data(0, QtCore.Qt.UserRole) + if name: + go = name.block.blockNumber() + helper = TextHelper(self._editor) + if helper.current_line_nbr() != go: + helper.goto_line(go, column=name.column) + self._editor.setFocus() + + def to_tree_widget_items(self, definitions, to_collapse=None): + """ + Converts the list of top level definitions to a list of top level + tree items. + """ + def convert(name, editor, to_collapse): + ti = QtWidgets.QTreeWidgetItem() + ti.setText(0, name.name) + ti.setIcon(0, QtGui.QIcon(name.icon)) + name.block = editor.document().findBlockByNumber(name.line) + ti.setData(0, QtCore.Qt.UserRole, name) + block_data = name.block.userData() + if block_data is None: + block_data = TextBlockUserData() + name.block.setUserData(block_data) + block_data.tree_item = ti + + if to_collapse is not None and \ + TextBlockHelper.get_fold_trigger_state(name.block): + to_collapse.append(ti) + + for ch in name.children: + ti_ch, to_collapse = convert(ch, editor, to_collapse) + if ti_ch: + ti.addChild(ti_ch) + return ti, to_collapse + + items = [] + for d in definitions: + value, to_collapse = convert(d, self._editor, to_collapse) + items.append(value) + if to_collapse is not None: + return items, to_collapse + return items diff --git a/CadQuery/Libs/pyqode/core/widgets/prompt_line_edit.py b/CadQuery/Libs/pyqode/core/widgets/prompt_line_edit.py index 7ee8478..80459b5 100644 --- a/CadQuery/Libs/pyqode/core/widgets/prompt_line_edit.py +++ b/CadQuery/Libs/pyqode/core/widgets/prompt_line_edit.py @@ -4,9 +4,14 @@ This module contains the PromptLineEdit widget implementation. """ import os from pyqode.qt import QtWidgets, QtCore, QtGui +from pyqode.qt import QT_API +from pyqode.qt import PYQT5_API +from pyqode.qt import PYQT4_API +from pyqode.qt import PYSIDE_API class PromptLineEdit(QtWidgets.QLineEdit): + """ Extends QLineEdit to show a prompt text and a clear icon """ @@ -52,16 +57,20 @@ class PromptLineEdit(QtWidgets.QLineEdit): def paintEvent(self, event): super(PromptLineEdit, self).paintEvent(event) + qt_api = os.environ['QT_API'].lower() if self._prompt_text and not self.text() and self.isEnabled(): - if os.environ['QT_API'].lower() == 'pyqt4': + if qt_api in PYQT4_API: from PyQt4.QtGui import QStyleOptionFrameV3 option = QStyleOptionFrameV3() - elif os.environ['QT_API'].lower() == 'pyside': + elif qt_api in PYSIDE_API: from PySide.QtGui import QStyleOptionFrameV3 option = QStyleOptionFrameV3() - else: + elif qt_api in PYQT5_API: from PyQt5.QtWidgets import QStyleOptionFrame option = QStyleOptionFrame() + else: + msg = 'Qt bindings "%s" is not supported' % qt_api + raise PythonQtError(msg) self.initStyleOption(option) diff --git a/CadQuery/Libs/pyqode/core/widgets/splittable_tab_widget.py b/CadQuery/Libs/pyqode/core/widgets/splittable_tab_widget.py index 09a8fbf..a6155e4 100644 --- a/CadQuery/Libs/pyqode/core/widgets/splittable_tab_widget.py +++ b/CadQuery/Libs/pyqode/core/widgets/splittable_tab_widget.py @@ -1,6 +1,7 @@ """ This module contains the splittable tab widget API """ +import inspect import logging import mimetypes import os @@ -770,8 +771,13 @@ class SplittableCodeEditTabWidget(SplittableTabWidget): def register_code_edit(cls, code_edit_class): """ Register an additional code edit **class** - :param code_edit_class: code edit class to regiter. + + .. warning: This method expect a class, not an instance! + + :param code_edit_class: code edit class to register. """ + if not inspect.isclass(code_edit_class): + raise TypeError('must be a class, not an instance.') for mimetype in code_edit_class.mimetypes: if mimetype in cls.editors: _logger().warn('editor for mimetype already registered, ' @@ -814,27 +820,37 @@ class SplittableCodeEditTabWidget(SplittableTabWidget): open/create. :type mimetype: mime type - :return: CodeEdit instance + :param args: Positional arguments that must be forwarded to the editor + widget constructor. + :param kwargs: Keyworded arguments that must be forwarded to the editor + widget constructor. + :return: Code editor widget instance. """ if mimetype in self.editors.keys(): return self.editors[mimetype]( *args, parent=self.main_tab_widget, **kwargs) - return self.fallback_editor(parent=self.main_tab_widget) + return self.fallback_editor(*args, parent=self.main_tab_widget, + **kwargs) - def create_new_document(self, base_name='New Document', extension='.txt'): + def create_new_document(self, base_name='New Document', extension='.txt', + *args, **kwargs): """ Creates a new document. The document name will be ``base_name + count + extension`` - :type base_name: Base name of the document. An int will be appended. - :type extension: Document extension (must include the DOT) + :param base_name: Base name of the document. An int will be appended. + :param extension: Document extension (dotted) + :param args: Positional arguments that must be forwarded to the editor + widget constructor. + :param kwargs: Keyworded arguments that must be forwarded to the editor + widget constructor. - :return: The created code editor + :return: Code editor widget instance. """ SplittableCodeEditTabWidget._new_count += 1 name = '%s%d%s' % (base_name, self._new_count, extension) - tab = self._create_code_edit(self.guess_mimetype(name)) + tab = self._create_code_edit(self.guess_mimetype(name), *args, **kwargs) tab.setDocumentTitle(name) self.add_tab(tab, title=name, icon=self._icon(name)) return tab diff --git a/CadQuery/Libs/pyqode/python/__init__.py b/CadQuery/Libs/pyqode/python/__init__.py index 70500fc..6157c9d 100644 --- a/CadQuery/Libs/pyqode/python/__init__.py +++ b/CadQuery/Libs/pyqode/python/__init__.py @@ -6,5 +6,5 @@ panels for the frontend and a series of worker for the backend (code completion, documentation lookups, code linters, and so on...). """ -__version__ = '2.4.dev' +__version__ = '2.5.0' diff --git a/CadQuery/Libs/pyqode/python/backend/server.py b/CadQuery/Libs/pyqode/python/backend/server.py index 5e35efa..86827f3 100644 --- a/CadQuery/Libs/pyqode/python/backend/server.py +++ b/CadQuery/Libs/pyqode/python/backend/server.py @@ -35,7 +35,7 @@ if __name__ == '__main__': # add user paths to sys.path if args.syspath: for path in args.syspath: - print('append path %s to sys.path\n' % path) + print('append path %s to sys.path' % path) sys.path.append(path) from pyqode.core import backend diff --git a/CadQuery/Libs/pyqode/python/backend/workers.py b/CadQuery/Libs/pyqode/python/backend/workers.py index 01ece22..1cbe314 100644 --- a/CadQuery/Libs/pyqode/python/backend/workers.py +++ b/CadQuery/Libs/pyqode/python/backend/workers.py @@ -7,6 +7,7 @@ import logging import os import tempfile import jedi +from pyqode.core.share import Definition def _logger(): @@ -73,66 +74,6 @@ def goto_assignments(request_data): _old_definitions = {} -class Definition(object): - """ - Represents a defined name in a python source code (import, function, class, - method). Definition usually form a tree limited to 2 levels (we stop at the - method level). - """ - def __init__(self, name='', icon='', line=1, column=0, full_name=''): - #: Icon resource name associated with the definition, can be None - self.icon = icon - #: Definition name (name of the class, method, variable) - self.name = name - #: The line of the definition in the current editor text - self.line = line - #: The column of the definition in the current editor text - self.column = column - #: Symbol name + parent name (for methods and class variables) - self.full_name = full_name - #: Possible list of children (only classes have children) - self.children = [] - if self.full_name == "": - self.full_name = self.name - - def add_child(self, definition): - """ - Adds a child definition - """ - self.children.append(definition) - - def to_dict(self): - """ - Serialises a definition to a dictionary, ready for json. - - Children are serialised recursively. - """ - ddict = {'name': self.name, 'icon': self.icon, - 'line': self.line, 'column': self.column, - 'full_name': self.full_name, 'children': []} - for child in self.children: - ddict['children'].append(child.to_dict()) - return ddict - - def from_dict(self, ddict): - """ - Deserialise the definition from a simple dict. - """ - self.name = ddict['name'] - self.icon = ddict['icon'] - self.line = ddict['line'] - self.column = ddict['column'] - self.full_name = ddict['full_name'] - self.children[:] = [] - for child_dict in ddict['children']: - self.children.append(Definition().from_dict(child_dict)) - return self - - def __repr__(self): - return 'Definition(%r, %r, %r, %r)' % (self.name, self.icon, - self.line, self.column) - - def _extract_def(d): d_line, d_column = d.start_pos # use full name for import type @@ -144,8 +85,8 @@ def _extract_def(d): name = d.name else: name = d.name - definition = Definition(name, icon_from_typename(d.name, d.type), - d_line - 1, d_column, d.full_name) + definition = Definition(name, d_line - 1, d_column, + icon_from_typename(d.name, d.type)) # check for methods in class or nested methods/classes if d.type == "class" or d.type == 'function': try: @@ -210,7 +151,7 @@ def run_pep8(request_data): path = request_data['path'] # setup our custom style guide with our custom checker which returns a list # of strings instread of spitting the results at stdout - pep8style = pep8.StyleGuide(parse_argv=False, config_file=True, + pep8style = pep8.StyleGuide(parse_argv=False, config_file='', checker_class=CustomChecker) try: results = pep8style.input_file(path, lines=code.splitlines(True)) diff --git a/CadQuery/Libs/pyqode/python/modes/__init__.py b/CadQuery/Libs/pyqode/python/modes/__init__.py index e1d4bad..a67ef99 100644 --- a/CadQuery/Libs/pyqode/python/modes/__init__.py +++ b/CadQuery/Libs/pyqode/python/modes/__init__.py @@ -8,7 +8,6 @@ from .autocomplete import PyAutoCompleteMode from .autoindent import PyAutoIndentMode from .calltips import CalltipsMode from .comments import CommentsMode -from .document_analyser import DocumentAnalyserMode from .frosted_checker import FrostedCheckerMode from .goto_assignements import Assignment from .goto_assignements import GoToAssignmentsMode @@ -30,7 +29,6 @@ __all__ = [ 'Assignment', 'CalltipsMode', 'CommentsMode', - 'DocumentAnalyserMode', 'FrostedCheckerMode', 'GoToAssignmentsMode', 'PEP8CheckerMode', diff --git a/CadQuery/Libs/pyqode/python/modes/autoindent.py b/CadQuery/Libs/pyqode/python/modes/autoindent.py index cad3a42..dfc561c 100644 --- a/CadQuery/Libs/pyqode/python/modes/autoindent.py +++ b/CadQuery/Libs/pyqode/python/modes/autoindent.py @@ -237,7 +237,10 @@ class PyAutoIndentMode(AutoIndentMode): tc2 = QTextCursor(tc) tc2.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) char = tc2.selectedText() - return char + while char == ' ': + tc2.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) + char = tc2.selectedText() + return char.strip() def _handle_indent_between_paren(self, column, line, parent_impl, tc): """ @@ -254,7 +257,7 @@ class PyAutoIndentMode(AutoIndentMode): open_line_indent = len(open_line_txt) - len(open_line_txt.lstrip()) if prev_open: post = (open_line_indent + self.editor.tab_length) * ' ' - elif next_close: + elif next_close and prev_char != ',': post = open_line_indent * ' ' elif tc.block().blockNumber() == open_line: post = open_symbol_col * ' ' diff --git a/CadQuery/Libs/pyqode/python/modes/document_analyser.py b/CadQuery/Libs/pyqode/python/modes/document_analyser.py deleted file mode 100644 index 78afc04..0000000 --- a/CadQuery/Libs/pyqode/python/modes/document_analyser.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -import logging -from pyqode.core import api -from pyqode.core.api import Mode, TextBlockUserData, TextBlockHelper -from pyqode.core.api import DelayJobRunner -from pyqode.core.backend import NotRunning -from pyqode.python.backend.workers import Definition, defined_names -from pyqode.qt import QtCore, QtGui, QtWidgets - - -def _logger(): - return logging.getLogger(__name__) - - -class DocumentAnalyserMode(Mode, QtCore.QObject): - """ Analyses the document outline as a tree of statements. - - This mode analyses the structure of a document (a tree of - :class:`pyqode.python.backend.workers.Definition`. - - :attr:`pyqode.python.modes.DocumentAnalyserMode.document_changed` - is emitted whenever the document structure changed. - - To keep good performances, the analysis task is run when the application is - idle for more than 1 second (by default). - """ - #: Signal emitted when the document structure changed. - document_changed = QtCore.Signal() - - def __init__(self, delay=1000): - Mode.__init__(self) - QtCore.QObject.__init__(self) - self._jobRunner = DelayJobRunner(delay=delay) - #: The list of results (elements might have children; this is actually - #: a tree). - self.results = [] - - def on_state_changed(self, state): - if state: - self.editor.new_text_set.connect(self._run_analysis) - self.editor.textChanged.connect(self._request_analysis) - else: - self.editor.textChanged.disconnect(self._request_analysis) - self.editor.new_text_set.disconnect(self._run_analysis) - self._jobRunner.cancel_requests() - - def _request_analysis(self): - self._jobRunner.request_job(self._run_analysis) - - def _run_analysis(self): - if self.enabled and self.editor and self.editor.toPlainText() and \ - self.editor.file: - request_data = { - 'code': self.editor.toPlainText(), - 'path': self.editor.file.path, - 'encoding': self.editor.file.encoding - } - try: - self.editor.backend.send_request( - defined_names, request_data, - on_receive=self._on_results_available) - except NotRunning: - QtCore.QTimer.singleShot(100, self._run_analysis) - else: - self.results = [] - self.document_changed.emit() - - def _on_results_available(self, results): - if results: - results = [Definition().from_dict(ddict) for ddict in results] - self.results = results - if self.results is not None: - _logger().debug("Document structure changed") - self.document_changed.emit() - - @property - def flattened_results(self): - """ - Flattens the document structure tree as a simple sequential list. - """ - ret_val = [] - for d in self.results: - ret_val.append(d) - for sub_d in d.children: - nd = Definition(sub_d.name, sub_d.icon, sub_d.line, - sub_d.column, sub_d.full_name) - nd.name = " " + nd.name - nd.full_name = " " + nd.full_name - ret_val.append(nd) - return ret_val - - def to_tree_widget_items(self, to_collapse=None): - """ - Returns the results as a list of top level QTreeWidgetItem. - - This is a convenience function that you can use to update a document - tree widget wheneve the document changed. - """ - def convert(name, editor, to_collapse): - ti = QtWidgets.QTreeWidgetItem() - ti.setText(0, name.name) - ti.setIcon(0, QtGui.QIcon(name.icon)) - name.block = editor.document().findBlockByNumber(name.line) - ti.setData(0, QtCore.Qt.UserRole, name) - block_data = name.block.userData() - if block_data is None: - block_data = TextBlockUserData() - name.block.setUserData(block_data) - block_data.tree_item = ti - - if to_collapse is not None and \ - TextBlockHelper.get_fold_trigger_state(name.block): - to_collapse.append(ti) - - for ch in name.children: - ti_ch, to_collapse = convert(ch, editor, to_collapse) - if ti_ch: - ti.addChild(ti_ch) - return ti, to_collapse - - items = [] - for d in self.results: - value, to_collapse = convert(d, self.editor, to_collapse) - items.append(value) - if to_collapse is not None: - return items, to_collapse - return items diff --git a/CadQuery/Libs/pyqode/python/panels/symbol_browser.py b/CadQuery/Libs/pyqode/python/panels/symbol_browser.py index ece310d..4dc9874 100644 --- a/CadQuery/Libs/pyqode/python/panels/symbol_browser.py +++ b/CadQuery/Libs/pyqode/python/panels/symbol_browser.py @@ -5,6 +5,7 @@ SymbolBrowserPanel import logging from pyqode.core.api import Panel, TextHelper from pyqode.qt import QtGui, QtCore, QtWidgets +from pyqode.core.share import Definition def _logger(): @@ -39,26 +40,40 @@ class SymbolBrowserPanel(Panel): self._on_cursor_pos_changed) try: self.editor.modes.get( - 'DocumentAnalyserMode').document_changed.connect( + 'OutlineMode').document_changed.connect( self._on_document_changed) except KeyError: - _logger().warning("No DocumentAnalyserMode found, install it " + _logger().warning("No OutlineMode found, install it " "before SymbolBrowserPanel!") else: self.editor.cursorPositionChanged.disconnect( self._on_cursor_pos_changed) try: self.editor.modes.get( - 'DocumentAnalyserMode').document_changed.disconnect( + 'OutlineMode').document_changed.disconnect( self._on_document_changed) except KeyError: pass def _on_document_changed(self): + def flatten(results): + """ + Flattens the document structure tree as a simple sequential list. + """ + ret_val = [] + for de in results: + ret_val.append(de) + for sub_d in de.children: + nd = Definition( + sub_d.name, sub_d.line, sub_d.column, sub_d.icon) + nd.name = " " + nd.name + ret_val.append(nd) + return ret_val + if not self or not self.editor: return - mode = self.editor.modes.get('DocumentAnalyserMode') - definitions = mode.flattened_results + mode = self.editor.modes.get('OutlineMode') + definitions = flatten(mode.definitions) self.combo_box.clear() if definitions: self.combo_box.addItem(" < Select a symbol >") diff --git a/CadQuery/Libs/pyqode/python/widgets/__init__.py b/CadQuery/Libs/pyqode/python/widgets/__init__.py index c318f72..5944ed2 100644 --- a/CadQuery/Libs/pyqode/python/widgets/__init__.py +++ b/CadQuery/Libs/pyqode/python/widgets/__init__.py @@ -7,6 +7,7 @@ This packages contains the available python widgets: """ from .code_edit import PyCodeEditBase, PyCodeEdit from .interactive import PyInteractiveConsole +# todo PyOutlineTreeWidget is deprecated and should be removed soon. from .outline import PyOutlineTreeWidget diff --git a/CadQuery/Libs/pyqode/python/widgets/code_edit.py b/CadQuery/Libs/pyqode/python/widgets/code_edit.py index e55df34..cf00f9b 100644 --- a/CadQuery/Libs/pyqode/python/widgets/code_edit.py +++ b/CadQuery/Libs/pyqode/python/widgets/code_edit.py @@ -12,6 +12,7 @@ from pyqode.core import panels from pyqode.python import managers as pymanagers from pyqode.python import modes as pymodes from pyqode.python import panels as pypanels +from pyqode.python.backend.workers import defined_names from pyqode.python.folding import PythonFoldDetector @@ -26,8 +27,7 @@ class PyCodeEditBase(api.CodeEdit): encoding. """ - def __init__(self, parent=None, create_default_actions=True, - color_scheme='qt'): + def __init__(self, parent=None, create_default_actions=True): super(PyCodeEditBase, self).__init__(parent, create_default_actions) self.file = pymanagers.PyFileManager(self) @@ -53,16 +53,17 @@ class PyCodeEdit(PyCodeEditBase): def __init__(self, parent=None, server_script=server.__file__, interpreter=sys.executable, args=None, - create_default_actions=True, color_scheme='qt'): + create_default_actions=True, color_scheme='qt', + reuse_backend=False): super(PyCodeEdit, self).__init__( - parent=parent, create_default_actions=create_default_actions, - color_scheme=color_scheme) - self.backend.start(server_script, interpreter, args) + parent=parent, create_default_actions=create_default_actions) + self.backend.start(server_script, interpreter, args, + reuse=reuse_backend) self.setLineWrapMode(self.NoWrap) self.setWindowTitle("pyQode - Python Editor") # install those modes first as they are required by other modes/panels - self.modes.append(pymodes.DocumentAnalyserMode()) + self.modes.append(modes.OutlineMode(defined_names)) # panels self.panels.append(panels.FoldingPanel()) @@ -87,6 +88,7 @@ class PyCodeEdit(PyCodeEditBase): self.modes.append(modes.OccurrencesHighlighterMode()) self.modes.append(modes.SmartBackSpaceMode()) self.modes.append(modes.ExtendedSelectionMode()) + self.modes.append(modes.CaseConverterMode()) # python specifics self.modes.append(pymodes.PyAutoIndentMode()) self.modes.append(pymodes.PyAutoCompleteMode()) diff --git a/CadQuery/Libs/pyqode/python/widgets/interactive.py b/CadQuery/Libs/pyqode/python/widgets/interactive.py index 994a430..4eaee73 100644 --- a/CadQuery/Libs/pyqode/python/widgets/interactive.py +++ b/CadQuery/Libs/pyqode/python/widgets/interactive.py @@ -36,6 +36,13 @@ class PyInteractiveConsole(InteractiveConsole): self.setLineWrapMode(self.NoWrap) self._module_color = QtGui.QColor('blue') + def start_process(self, process, args=None, cwd=None, env=None): + if env is None: + env = {} + if 'PYTHONUNBUFFERED' not in env: + env['PYTHONUNBUFFERED'] = '1' + super(PyInteractiveConsole, self).start_process(process, args, cwd, env) + def _write(self, text_edit, text, color): def write(text_edit, text, color): text_edit.moveCursor(QtGui.QTextCursor.End) diff --git a/CadQuery/Libs/pyqode/python/widgets/outline.py b/CadQuery/Libs/pyqode/python/widgets/outline.py index b6ef909..2b27f4c 100644 --- a/CadQuery/Libs/pyqode/python/widgets/outline.py +++ b/CadQuery/Libs/pyqode/python/widgets/outline.py @@ -3,134 +3,11 @@ This document contains the tree widget used to display the editor document outline. """ -from pyqode.core.panels import FoldingPanel -from pyqode.python.modes import DocumentAnalyserMode -from pyqode.qt import QtCore, QtGui, QtWidgets -from pyqode.core.api import TextBlockHelper, TextHelper, TextBlockUserData +from pyqode.core.widgets import OutlineTreeWidget -class PyOutlineTreeWidget(QtWidgets.QTreeWidget): +class PyOutlineTreeWidget(OutlineTreeWidget): """ - Displays the outline of a PyCodeEdit. The treeview is fully synced with - the fold panel. - - To use the widget, you just have to set the active editor using - :func:`PyOutlineTreeWidget.set_editor`. - + Deprecated, use pyqode.core.widgets.OutlineTreeWidget instead. """ - def __init__(self, parent=None): - super(PyOutlineTreeWidget, self).__init__(parent) - self._editor = None - self._analyser = None - self._folding_panel = None - self._expanded_items = [] - self.setHeaderHidden(True) - self.itemClicked.connect(self._on_item_clicked) - self.itemCollapsed.connect(self._on_item_state_changed) - self.itemExpanded.connect(self._on_item_state_changed) - self._updating = True - - def set_editor(self, editor): - """ - Sets the current editor. The widget display the structure of that - editor. - - :param editor: PyCodeEdit - """ - if self._analyser: - try: - self._analyser.document_changed.disconnect(self._on_changed) - except (TypeError, RuntimeError): - pass - if self._folding_panel: - try: - self._folding_panel.trigger_state_changed.disconnect( - self._on_block_state_changed) - except (TypeError, RuntimeError): - pass - self._editor = editor - if self._editor is not None: - try: - self._folding_panel = editor.panels.get(FoldingPanel) - except KeyError: - pass - else: - self._folding_panel.trigger_state_changed.connect( - self._on_block_state_changed) - try: - analyser = editor.modes.get(DocumentAnalyserMode) - except KeyError: - self._analyser = None - else: - assert isinstance(analyser, DocumentAnalyserMode) - self._analyser = analyser - analyser.document_changed.connect(self._on_changed) - self._on_changed() - - def _on_item_state_changed(self, item): - if self._updating: - return - block = item.data(0, QtCore.Qt.UserRole).block - assert isinstance(item, QtWidgets.QTreeWidgetItem) - item_state = not item.isExpanded() - block_state = TextBlockHelper.get_fold_trigger_state(block) - if item_state != block_state: - self._updating = True - self._folding_panel.toggle_fold_trigger(block) - self._updating = False - - def _on_block_state_changed(self, block, state): - if self._updating: - return - data = block.userData() - if data is not None: - try: - item_state = not data.tree_item.isExpanded() - if item_state != state: - if state: - self.collapseItem(data.tree_item) - else: - self.expandItem(data.tree_item) - except AttributeError: - # a block that is not represented in the tree view has - # folded/unfolded, just ignore it - pass - - def _on_changed(self): - """ - Update the tree items - """ - self._updating = True - to_collapse = [] - self.clear() - if self._editor and self._analyser and self._folding_panel: - items, to_collapse = self._analyser.to_tree_widget_items( - to_collapse=to_collapse) - if len(items): - self.addTopLevelItems(items) - self.expandAll() - for item in reversed(to_collapse): - self.collapseItem(item) - self._updating = False - return - # no data - root = QtWidgets.QTreeWidgetItem() - root.setText(0, 'No data') - root.setIcon(0, QtGui.QIcon.fromTheme( - 'dialog-information', - QtGui.QIcon(':/pyqode-icons/rc/dialog-info.png'))) - self.addTopLevelItem(root) - self._updating = False - - def _on_item_clicked(self, item): - """ - Go to the item position in the editor. - """ - if item: - name = item.data(0, QtCore.Qt.UserRole) - if name: - go = name.block.blockNumber() - helper = TextHelper(self._editor) - if helper.current_line_nbr() != go: - helper.goto_line(go, column=name.column) - self._editor.setFocus() + pass \ No newline at end of file diff --git a/CadQuery/Libs/pyqode/qt/QtCore.py b/CadQuery/Libs/pyqode/qt/QtCore.py index d92bffc..342df9e 100644 --- a/CadQuery/Libs/pyqode/qt/QtCore.py +++ b/CadQuery/Libs/pyqode/qt/QtCore.py @@ -8,7 +8,7 @@ from pyqode.qt import PYQT4_API from pyqode.qt import PYSIDE_API -if os.environ[QT_API] == PYQT5_API: +if os.environ[QT_API] in PYQT5_API: from PyQt5.QtCore import * # compatibility with pyside from PyQt5.QtCore import pyqtSignal as Signal @@ -16,7 +16,7 @@ if os.environ[QT_API] == PYQT5_API: from PyQt5.QtCore import pyqtProperty as Property # use a common __version__ from PyQt5.QtCore import QT_VERSION_STR as __version__ -elif os.environ[QT_API] == PYQT4_API: +elif os.environ[QT_API] in PYQT4_API: from PyQt4.QtCore import * # compatibility with pyside from PyQt4.QtCore import pyqtSignal as Signal @@ -25,7 +25,7 @@ elif os.environ[QT_API] == PYQT4_API: from PyQt4.QtGui import QSortFilterProxyModel # use a common __version__ from PyQt4.QtCore import QT_VERSION_STR as __version__ -elif os.environ[QT_API] == PYSIDE_API: +elif os.environ[QT_API] in PYSIDE_API: from PySide.QtCore import * from PySide.QtGui import QSortFilterProxyModel # use a common __version__ diff --git a/CadQuery/Libs/pyqode/qt/QtDesigner.py b/CadQuery/Libs/pyqode/qt/QtDesigner.py index 6164ff1..d0be520 100644 --- a/CadQuery/Libs/pyqode/qt/QtDesigner.py +++ b/CadQuery/Libs/pyqode/qt/QtDesigner.py @@ -7,7 +7,7 @@ from pyqode.qt import PYQT5_API from pyqode.qt import PYQT4_API -if os.environ[QT_API] == PYQT5_API: +if os.environ[QT_API] in PYQT5_API: from PyQt5.QtDesigner import * -elif os.environ[QT_API] == PYQT4_API: +elif os.environ[QT_API] in PYQT4_API: from PyQt4.QtDesigner import * diff --git a/CadQuery/Libs/pyqode/qt/QtGui.py b/CadQuery/Libs/pyqode/qt/QtGui.py index 1e3f9aa..ce49cf7 100644 --- a/CadQuery/Libs/pyqode/qt/QtGui.py +++ b/CadQuery/Libs/pyqode/qt/QtGui.py @@ -12,9 +12,9 @@ from pyqode.qt import PYQT4_API from pyqode.qt import PYSIDE_API -if os.environ[QT_API] == PYQT5_API: +if os.environ[QT_API] in PYQT5_API: from PyQt5.QtGui import * -elif os.environ[QT_API] == PYQT4_API: +elif os.environ[QT_API] in PYQT4_API: from PyQt4.QtGui import * -elif os.environ[QT_API] == PYSIDE_API: +elif os.environ[QT_API] in PYSIDE_API: from PySide.QtGui import * diff --git a/CadQuery/Libs/pyqode/qt/QtNetwork.py b/CadQuery/Libs/pyqode/qt/QtNetwork.py index c01be0c..caee947 100644 --- a/CadQuery/Libs/pyqode/qt/QtNetwork.py +++ b/CadQuery/Libs/pyqode/qt/QtNetwork.py @@ -7,9 +7,9 @@ from pyqode.qt import PYQT5_API from pyqode.qt import PYQT4_API from pyqode.qt import PYSIDE_API -if os.environ[QT_API] == PYQT5_API: +if os.environ[QT_API] in PYQT5_API: from PyQt5.QtNetwork import * -elif os.environ[QT_API] == PYQT4_API: +elif os.environ[QT_API] in PYQT4_API: from PyQt4.QtNetwork import * -elif os.environ[QT_API] == PYSIDE_API: +elif os.environ[QT_API] in PYSIDE_API: from PySide.QtNetwork import * diff --git a/CadQuery/Libs/pyqode/qt/QtTest.py b/CadQuery/Libs/pyqode/qt/QtTest.py index 287bd5f..43e3b93 100644 --- a/CadQuery/Libs/pyqode/qt/QtTest.py +++ b/CadQuery/Libs/pyqode/qt/QtTest.py @@ -11,14 +11,14 @@ from pyqode.qt import PYQT5_API from pyqode.qt import PYQT4_API from pyqode.qt import PYSIDE_API -if os.environ[QT_API] == PYQT5_API: +if os.environ[QT_API] in PYQT5_API: from PyQt5.QtTest import QTest -elif os.environ[QT_API] == PYQT4_API: +elif os.environ[QT_API] in PYQT4_API: from PyQt4.QtTest import QTest as OldQTest class QTest(OldQTest): @staticmethod def qWaitForWindowActive(QWidget): OldQTest.qWaitForWindowShown(QWidget) -elif os.environ[QT_API] == PYSIDE_API: +elif os.environ[QT_API] in PYSIDE_API: raise ImportError('QtTest support is incomplete for PySide') diff --git a/CadQuery/Libs/pyqode/qt/QtWidgets.py b/CadQuery/Libs/pyqode/qt/QtWidgets.py index c4949a9..f685a79 100644 --- a/CadQuery/Libs/pyqode/qt/QtWidgets.py +++ b/CadQuery/Libs/pyqode/qt/QtWidgets.py @@ -11,14 +11,14 @@ from pyqode.qt import PYQT5_API from pyqode.qt import PYQT4_API from pyqode.qt import PYSIDE_API - -if os.environ[QT_API] == PYQT5_API: +if os.environ[QT_API] in PYQT5_API: from PyQt5.QtWidgets import * -elif os.environ[QT_API] == PYQT4_API: +elif os.environ[QT_API] in PYQT4_API: from PyQt4.QtGui import * from PyQt4.QtGui import QFileDialog as OldFileDialog class QFileDialog(OldFileDialog): + @staticmethod def getOpenFileName(parent=None, caption='', directory='', filter='', selectedFilter='', @@ -42,5 +42,5 @@ elif os.environ[QT_API] == PYQT4_API: return OldFileDialog.getSaveFileNameAndFilter( parent, caption, directory, filter, selectedFilter, options) -elif os.environ[QT_API] == PYSIDE_API: - from PySide.QtGui import * \ No newline at end of file +elif os.environ[QT_API] in PYSIDE_API: + from PySide.QtGui import * diff --git a/CadQuery/Libs/pyqode/qt/__init__.py b/CadQuery/Libs/pyqode/qt/__init__.py index 94dafac..176683e 100644 --- a/CadQuery/Libs/pyqode/qt/__init__.py +++ b/CadQuery/Libs/pyqode/qt/__init__.py @@ -52,20 +52,24 @@ import os import sys import logging -__version__ = '2.5.dev' +__version__ = '2.5.0' #: Qt API environment variable name QT_API = 'QT_API' -#: name of the expected PyQt5 api -PYQT5_API = 'pyqt5' -#: name of the expected PyQt4 api -PYQT4_API = 'pyqt4' -#: name of the expected PySide api -PYSIDE_API = 'pyside' +#: names of the expected PyQt5 api +PYQT5_API = ['pyqt5'] +#: names of the expected PyQt4 api +PYQT4_API = [ + 'pyqt', # name used in IPython.qt + 'pyqt4' # pyqode.qt original name +] +#: names of the expected PySide api +PYSIDE_API = ['pyside'] class PythonQtError(Exception): + """ Error raise if no bindings could be selected """ @@ -103,20 +107,20 @@ def autodetect(): try: logging.getLogger(__name__).debug('trying PyQt5') import PyQt5 - os.environ[QT_API] = PYQT5_API + os.environ[QT_API] = PYQT5_API[0] logging.getLogger(__name__).debug('imported PyQt5') except ImportError: try: logging.getLogger(__name__).debug('trying PyQt4') setup_apiv2() import PyQt4 - os.environ[QT_API] = PYQT4_API + os.environ[QT_API] = PYQT4_API[0] logging.getLogger(__name__).debug('imported PyQt4') except ImportError: try: logging.getLogger(__name__).debug('trying PySide') import PySide - os.environ[QT_API] = PYSIDE_API + os.environ[QT_API] = PYSIDE_API[0] logging.getLogger(__name__).debug('imported PySide') except ImportError: raise PythonQtError('No Qt bindings could be found') @@ -125,21 +129,21 @@ def autodetect(): if QT_API in os.environ: # check if the selected QT_API is available try: - if os.environ[QT_API].lower() == PYQT5_API.lower(): + if os.environ[QT_API].lower() in PYQT5_API: logging.getLogger(__name__).debug('importing PyQt5') import PyQt5 - os.environ[QT_API] = PYQT5_API + os.environ[QT_API] = PYQT5_API[0] logging.getLogger(__name__).debug('imported PyQt5') - elif os.environ[QT_API].lower() == PYQT4_API.lower(): + elif os.environ[QT_API].lower() in PYQT4_API: logging.getLogger(__name__).debug('importing PyQt4') setup_apiv2() import PyQt4 - os.environ[QT_API] = PYQT4_API + os.environ[QT_API] = PYQT4_API[0] logging.getLogger(__name__).debug('imported PyQt4') - elif os.environ[QT_API].lower() == PYSIDE_API.lower(): + elif os.environ[QT_API].lower() in PYSIDE_API: logging.getLogger(__name__).debug('importing PySide') import PySide - os.environ[QT_API] = PYSIDE_API + os.environ[QT_API] = PYSIDE_API[0] logging.getLogger(__name__).debug('imported PySide') except ImportError: logging.getLogger(__name__).warning(