# pyflyby/_flags.py. # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen. # License: MIT http://opensource.org/licenses/MIT from __future__ import (absolute_import, division, print_function, with_statement) import __future__ import ast import operator import six from six.moves import reduce import warnings from pyflyby._util import cached_attribute # Initialize mappings from compiler_flag to feature name and vice versa. _FLAG2NAME = {} _NAME2FLAG = {} for name in __future__.all_feature_names: flag = getattr(__future__, name).compiler_flag _FLAG2NAME[flag] = name _NAME2FLAG[name] = flag for name in dir(ast): if name.startswith('PyCF'): flag_name = name[len('PyCF_'):].lower() flag = getattr(ast, name) _FLAG2NAME[flag] = flag_name _NAME2FLAG[flag_name] = flag _FLAGNAME_ITEMS = sorted(_FLAG2NAME.items()) _ALL_FLAGS = reduce(operator.or_, _FLAG2NAME.keys()) class CompilerFlags(int): """ Representation of Python "compiler flags", i.e. features from __future__. >>> print(CompilerFlags(0x18000).__interactive_display__()) # doctest: +SKIP CompilerFlags(0x18000) # from __future__ import with_statement, print_function >>> print(CompilerFlags(0x10000, 0x8000).__interactive_display__()) # doctest: +SKIP CompilerFlags(0x18000) # from __future__ import with_statement, print_function >>> print(CompilerFlags('with_statement', 'print_function').__interactive_display__()) # doctest: +SKIP CompilerFlags(0x18000) # from __future__ import with_statement, print_function This can be used as an argument to the built-in compile() function. For instance, in Python 2:: >>> compile("print('x', file=None)", "?", "exec", flags=0, dont_inherit=1) #doctest:+SKIP Traceback (most recent call last): ... SyntaxError: invalid syntax >>> compile("print('x', file=None)", "?", "exec", flags=CompilerFlags("print_function"), dont_inherit=1) #doctest:+ELLIPSIS """ def __new__(cls, *args): """ Construct a new ``CompilerFlags`` instance. :param args: Any number (zero or more) ``CompilerFlags`` s, ``int`` s, or ``str`` s, which are bitwise-ORed together. :rtype: `CompilerFlags` """ if len(args) == 0: return cls._ZERO elif len(args) == 1: arg, = args if isinstance(arg, cls): return arg elif arg is None: return cls._ZERO elif isinstance(arg, int): warnings.warn('creating CompilerFlags from integers is deprecated, ' ' flags values change between Python versions. If you are sure use .from_int', DeprecationWarning, stacklevel=2) return cls.from_int(arg) elif isinstance(arg, six.string_types): return cls.from_str(arg) elif isinstance(arg, ast.AST): return cls.from_ast(arg) elif isinstance(arg, (tuple, list)): return cls(*arg) else: raise TypeError("CompilerFlags: unknown type %s" % (type(arg).__name__,)) else: flags = [] for x in args: if isinstance(x, cls): flags.append(int(x)) elif isinstance(x, int): warnings.warn( "creating CompilerFlags from integers is deprecated, " " flags values change between Python versions. If you are sure use .from_int", DeprecationWarning, stacklevel=2, ) flags.append(x) elif isinstance(x, str): flags.append(int(cls(x))) else: raise ValueError #assert flags == [0x10000, 0x8000], flags return cls.from_int(reduce(operator.or_, flags)) @classmethod def from_int(cls, arg): if arg == -1: return cls._UNKNOWN # Instance optimization if arg == 0: return cls._ZERO # Instance optimization self = int.__new__(cls, arg) bad_flags = int(self) & ~_ALL_FLAGS if bad_flags: raise ValueError( "CompilerFlags: unknown flag value(s) %s %s" % (bin(bad_flags), hex(bad_flags))) return self @classmethod def from_str(cls, arg): try: flag = _NAME2FLAG[arg] except KeyError: raise ValueError( "CompilerFlags: unknown flag %r" % (arg,)) return cls.from_int(flag) @classmethod def from_ast(cls, nodes): """ Parse the compiler flags from AST node(s). :type nodes: ``ast.AST`` or sequence thereof :rtype: ``CompilerFlags`` """ if isinstance(nodes, ast.Module): nodes = nodes.body elif isinstance(nodes, ast.AST): nodes = [nodes] flags = [] for node in nodes: if not isinstance(node, ast.ImportFrom): # Got a non-import; stop looking further. break if not node.module == "__future__": # Got a non-__future__-import; stop looking further. break # Get the feature names. names = [n.name for n in node.names] flags.extend(names) return cls(flags) @cached_attribute def names(self): return tuple( n for f, n in _FLAGNAME_ITEMS if f & self) def __or__(self, o): if o == 0: return self if not isinstance(o, CompilerFlags): o = CompilerFlags(o) if self == 0: return o return CompilerFlags.from_int(int(self) | int(o)) def __ror__(self, o): return self | o def __and__(self, o): if not isinstance(o, int): o = CompilerFlags(o) return CompilerFlags.from_int(int(self) & int(o)) def __rand__(self, o): return self & o def __xor__(self, o): if not isinstance(o, CompilerFlags): o = CompilerFlags.from_int(o) return CompilerFlags.from_int(int(self) ^ int(o)) def __rxor__(self, o): return self ^ o def __repr__(self): return "CompilerFlags(%s)" % (hex(self),) def __str__(self): return hex(self) def __interactive_display__(self): s = repr(self) if self != 0: s += " # from __future__ import " + ", ".join(self.names) return s CompilerFlags._ZERO = int.__new__(CompilerFlags, 0) CompilerFlags._UNKNOWN = int.__new__(CompilerFlags, -1) # flags that _may_ exists on future versions. _future_flags = { "nested_scopes", "generators", "division", "absolute_import", "with_statement", "print_function", "unicode_literals", "barry_as_FLUFL", "generator_stop", "annotations", "allow_top_level_await", "only_ast", "type_comments", } for k in _future_flags: setattr(CompilerFlags, k, CompilerFlags._UNKNOWN) for k, v in _NAME2FLAG.items(): setattr(CompilerFlags, k, CompilerFlags.from_int(v))