home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / python2.6 / lib2to3 / refactor.pyc (.txt) < prev   
Encoding:
Python Compiled Bytecode  |  2009-04-20  |  17.4 KB  |  577 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Refactoring framework.
  5.  
  6. Used as a main program, this can refactor any number of files and/or
  7. recursively descend down directories.  Imported as a module, this
  8. provides infrastructure to write your own refactoring tool.
  9. '''
  10. __author__ = 'Guido van Rossum <guido@python.org>'
  11. import os
  12. import sys
  13. import difflib
  14. import logging
  15. import operator
  16. from collections import defaultdict
  17. from itertools import chain
  18. from pgen2 import driver
  19. from pgen2 import tokenize
  20. from  import pytree
  21. from  import patcomp
  22. from  import fixes
  23. from  import pygram
  24.  
  25. def get_all_fix_names(fixer_pkg, remove_prefix = True):
  26.     '''Return a sorted list of all available fix names in the given package.'''
  27.     pkg = __import__(fixer_pkg, [], [], [
  28.         '*'])
  29.     fixer_dir = os.path.dirname(pkg.__file__)
  30.     fix_names = []
  31.     for name in sorted(os.listdir(fixer_dir)):
  32.         if name.startswith('fix_') and name.endswith('.py'):
  33.             if remove_prefix:
  34.                 name = name[4:]
  35.             
  36.             fix_names.append(name[:-3])
  37.             continue
  38.     
  39.     return fix_names
  40.  
  41.  
  42. def get_head_types(pat):
  43.     ''' Accepts a pytree Pattern Node and returns a set
  44.         of the pattern types which will match first. '''
  45.     if isinstance(pat, (pytree.NodePattern, pytree.LeafPattern)):
  46.         return set([
  47.             pat.type])
  48.     if isinstance(pat, pytree.NegatedPattern):
  49.         if pat.content:
  50.             return get_head_types(pat.content)
  51.         return set([
  52.             None])
  53.     if isinstance(pat, pytree.WildcardPattern):
  54.         r = set()
  55.         for p in pat.content:
  56.             for x in p:
  57.                 r.update(get_head_types(x))
  58.             
  59.         
  60.         return r
  61.     raise Exception("Oh no! I don't understand pattern %s" % pat)
  62.  
  63.  
  64. def get_headnode_dict(fixer_list):
  65.     ''' Accepts a list of fixers and returns a dictionary
  66.         of head node type --> fixer list.  '''
  67.     head_nodes = defaultdict(list)
  68.     for fixer in fixer_list:
  69.         if not fixer.pattern:
  70.             head_nodes[None].append(fixer)
  71.             continue
  72.         
  73.         for t in get_head_types(fixer.pattern):
  74.             head_nodes[t].append(fixer)
  75.         
  76.     
  77.     return head_nodes
  78.  
  79.  
  80. def get_fixers_from_package(pkg_name):
  81.     '''
  82.     Return the fully qualified names for fixers in the package pkg_name.
  83.     '''
  84.     return [ pkg_name + '.' + fix_name for fix_name in get_all_fix_names(pkg_name, False) ]
  85.  
  86.  
  87. class FixerError(Exception):
  88.     '''A fixer could not be loaded.'''
  89.     pass
  90.  
  91.  
  92. class RefactoringTool(object):
  93.     _default_options = {
  94.         'print_function': False }
  95.     CLASS_PREFIX = 'Fix'
  96.     FILE_PREFIX = 'fix_'
  97.     
  98.     def __init__(self, fixer_names, options = None, explicit = None):
  99.         '''Initializer.
  100.  
  101.         Args:
  102.             fixer_names: a list of fixers to import
  103.             options: an dict with configuration.
  104.             explicit: a list of fixers to run even if they are explicit.
  105.         '''
  106.         self.fixers = fixer_names
  107.         if not explicit:
  108.             pass
  109.         self.explicit = []
  110.         self.options = self._default_options.copy()
  111.         if options is not None:
  112.             self.options.update(options)
  113.         
  114.         self.errors = []
  115.         self.logger = logging.getLogger('RefactoringTool')
  116.         self.fixer_log = []
  117.         self.wrote = False
  118.         if self.options['print_function']:
  119.             del pygram.python_grammar.keywords['print']
  120.         
  121.         self.driver = driver.Driver(pygram.python_grammar, convert = pytree.convert, logger = self.logger)
  122.         (self.pre_order, self.post_order) = self.get_fixers()
  123.         self.pre_order_heads = get_headnode_dict(self.pre_order)
  124.         self.post_order_heads = get_headnode_dict(self.post_order)
  125.         self.files = []
  126.  
  127.     
  128.     def get_fixers(self):
  129.         '''Inspects the options to load the requested patterns and handlers.
  130.  
  131.         Returns:
  132.           (pre_order, post_order), where pre_order is the list of fixers that
  133.           want a pre-order AST traversal, and post_order is the list that want
  134.           post-order traversal.
  135.         '''
  136.         pre_order_fixers = []
  137.         post_order_fixers = []
  138.         for fix_mod_path in self.fixers:
  139.             mod = __import__(fix_mod_path, { }, { }, [
  140.                 '*'])
  141.             fix_name = fix_mod_path.rsplit('.', 1)[-1]
  142.             if fix_name.startswith(self.FILE_PREFIX):
  143.                 fix_name = fix_name[len(self.FILE_PREFIX):]
  144.             
  145.             parts = fix_name.split('_')
  146.             class_name = [] + []([ p.title() for p in parts ])
  147.             
  148.             try:
  149.                 fix_class = getattr(mod, class_name)
  150.             except AttributeError:
  151.                 ''.join
  152.                 ''.join
  153.                 self.CLASS_PREFIX
  154.                 raise FixerError("Can't find %s.%s" % (fix_name, class_name))
  155.             except:
  156.                 ''.join
  157.  
  158.             fixer = fix_class(self.options, self.fixer_log)
  159.             self.log_debug('Adding transformation: %s', fix_name)
  160.             if fixer.order == 'pre':
  161.                 pre_order_fixers.append(fixer)
  162.                 continue
  163.             None if fixer.explicit and self.explicit is not True and fix_mod_path not in self.explicit else self.CLASS_PREFIX
  164.             if fixer.order == 'post':
  165.                 post_order_fixers.append(fixer)
  166.                 continue
  167.             raise FixerError('Illegal fixer order: %r' % fixer.order)
  168.         
  169.         key_func = operator.attrgetter('run_order')
  170.         pre_order_fixers.sort(key = key_func)
  171.         post_order_fixers.sort(key = key_func)
  172.         return (pre_order_fixers, post_order_fixers)
  173.  
  174.     
  175.     def log_error(self, msg, *args, **kwds):
  176.         '''Called when an error occurs.'''
  177.         raise 
  178.  
  179.     
  180.     def log_message(self, msg, *args):
  181.         '''Hook to log a message.'''
  182.         if args:
  183.             msg = msg % args
  184.         
  185.         self.logger.info(msg)
  186.  
  187.     
  188.     def log_debug(self, msg, *args):
  189.         if args:
  190.             msg = msg % args
  191.         
  192.         self.logger.debug(msg)
  193.  
  194.     
  195.     def print_output(self, lines):
  196.         '''Called with lines of output to give to the user.'''
  197.         pass
  198.  
  199.     
  200.     def refactor(self, items, write = False, doctests_only = False):
  201.         '''Refactor a list of files and directories.'''
  202.         for dir_or_file in items:
  203.             if os.path.isdir(dir_or_file):
  204.                 self.refactor_dir(dir_or_file, write, doctests_only)
  205.                 continue
  206.             self.refactor_file(dir_or_file, write, doctests_only)
  207.         
  208.  
  209.     
  210.     def refactor_dir(self, dir_name, write = False, doctests_only = False):
  211.         """Descends down a directory and refactor every Python file found.
  212.  
  213.         Python files are assumed to have a .py extension.
  214.  
  215.         Files and subdirectories starting with '.' are skipped.
  216.         """
  217.         for dirpath, dirnames, filenames in os.walk(dir_name):
  218.             self.log_debug('Descending into %s', dirpath)
  219.             dirnames.sort()
  220.             filenames.sort()
  221.             for name in filenames:
  222.                 if not name.startswith('.') and name.endswith('py'):
  223.                     fullname = os.path.join(dirpath, name)
  224.                     self.refactor_file(fullname, write, doctests_only)
  225.                     continue
  226.             
  227.             dirnames[:] = _[1]
  228.         
  229.  
  230.     
  231.     def refactor_file(self, filename, write = False, doctests_only = False):
  232.         '''Refactors a file.'''
  233.         
  234.         try:
  235.             f = open(filename)
  236.         except IOError:
  237.             err = None
  238.             self.log_error("Can't open %s: %s", filename, err)
  239.             return None
  240.  
  241.         
  242.         try:
  243.             input = f.read() + '\n'
  244.         finally:
  245.             f.close()
  246.  
  247.         if doctests_only:
  248.             self.log_debug('Refactoring doctests in %s', filename)
  249.             output = self.refactor_docstring(input, filename)
  250.             if output != input:
  251.                 self.processed_file(output, filename, input, write = write)
  252.             else:
  253.                 self.log_debug('No doctest changes in %s', filename)
  254.         else:
  255.             tree = self.refactor_string(input, filename)
  256.             if tree and tree.was_changed:
  257.                 self.processed_file(str(tree)[:-1], filename, write = write)
  258.             else:
  259.                 self.log_debug('No changes in %s', filename)
  260.  
  261.     
  262.     def refactor_string(self, data, name):
  263.         '''Refactor a given input string.
  264.  
  265.         Args:
  266.             data: a string holding the code to be refactored.
  267.             name: a human-readable name for use in error/log messages.
  268.  
  269.         Returns:
  270.             An AST corresponding to the refactored input stream; None if
  271.             there were errors during the parse.
  272.         '''
  273.         
  274.         try:
  275.             tree = self.driver.parse_string(data)
  276.         except Exception:
  277.             err = None
  278.             self.log_error("Can't parse %s: %s: %s", name, err.__class__.__name__, err)
  279.             return None
  280.  
  281.         self.log_debug('Refactoring %s', name)
  282.         self.refactor_tree(tree, name)
  283.         return tree
  284.  
  285.     
  286.     def refactor_stdin(self, doctests_only = False):
  287.         input = sys.stdin.read()
  288.         if doctests_only:
  289.             self.log_debug('Refactoring doctests in stdin')
  290.             output = self.refactor_docstring(input, '<stdin>')
  291.             if output != input:
  292.                 self.processed_file(output, '<stdin>', input)
  293.             else:
  294.                 self.log_debug('No doctest changes in stdin')
  295.         else:
  296.             tree = self.refactor_string(input, '<stdin>')
  297.             if tree and tree.was_changed:
  298.                 self.processed_file(str(tree), '<stdin>', input)
  299.             else:
  300.                 self.log_debug('No changes in stdin')
  301.  
  302.     
  303.     def refactor_tree(self, tree, name):
  304.         '''Refactors a parse tree (modifying the tree in place).
  305.  
  306.         Args:
  307.             tree: a pytree.Node instance representing the root of the tree
  308.                   to be refactored.
  309.             name: a human-readable name for this tree.
  310.  
  311.         Returns:
  312.             True if the tree was modified, False otherwise.
  313.         '''
  314.         for fixer in chain(self.pre_order, self.post_order):
  315.             fixer.start_tree(tree, name)
  316.         
  317.         self.traverse_by(self.pre_order_heads, tree.pre_order())
  318.         self.traverse_by(self.post_order_heads, tree.post_order())
  319.         for fixer in chain(self.pre_order, self.post_order):
  320.             fixer.finish_tree(tree, name)
  321.         
  322.         return tree.was_changed
  323.  
  324.     
  325.     def traverse_by(self, fixers, traversal):
  326.         '''Traverse an AST, applying a set of fixers to each node.
  327.  
  328.         This is a helper method for refactor_tree().
  329.  
  330.         Args:
  331.             fixers: a list of fixer instances.
  332.             traversal: a generator that yields AST nodes.
  333.  
  334.         Returns:
  335.             None
  336.         '''
  337.         if not fixers:
  338.             return None
  339.         for node in traversal:
  340.             for fixer in fixers[node.type] + fixers[None]:
  341.                 results = fixer.match(node)
  342.                 if results:
  343.                     new = fixer.transform(node, results)
  344.                     if new is not None:
  345.                         pass
  346.                     fixers if new != node or str(new) != str(node) else str(new) != str(node)
  347.                     continue
  348.             
  349.         
  350.  
  351.     
  352.     def processed_file(self, new_text, filename, old_text = None, write = False):
  353.         '''
  354.         Called when a file has been refactored, and there are changes.
  355.         '''
  356.         self.files.append(filename)
  357.         if old_text is None:
  358.             
  359.             try:
  360.                 f = open(filename, 'r')
  361.             except IOError:
  362.                 err = None
  363.                 self.log_error("Can't read %s: %s", filename, err)
  364.                 return None
  365.  
  366.             
  367.             try:
  368.                 old_text = f.read()
  369.             finally:
  370.                 f.close()
  371.  
  372.         
  373.         if old_text == new_text:
  374.             self.log_debug('No changes to %s', filename)
  375.             return None
  376.         self.print_output(diff_texts(old_text, new_text, filename))
  377.         if write:
  378.             self.write_file(new_text, filename, old_text)
  379.         else:
  380.             self.log_debug('Not writing changes to %s', filename)
  381.  
  382.     
  383.     def write_file(self, new_text, filename, old_text):
  384.         '''Writes a string to a file.
  385.  
  386.         It first shows a unified diff between the old text and the new text, and
  387.         then rewrites the file; the latter is only done if the write option is
  388.         set.
  389.         '''
  390.         
  391.         try:
  392.             f = open(filename, 'w')
  393.         except os.error:
  394.             err = None
  395.             self.log_error("Can't create %s: %s", filename, err)
  396.             return None
  397.  
  398.         
  399.         try:
  400.             f.write(new_text)
  401.         except os.error:
  402.             err = None
  403.             self.log_error("Can't write %s: %s", filename, err)
  404.         finally:
  405.             f.close()
  406.  
  407.         self.log_debug('Wrote changes to %s', filename)
  408.         self.wrote = True
  409.  
  410.     PS1 = '>>> '
  411.     PS2 = '... '
  412.     
  413.     def refactor_docstring(self, input, filename):
  414.         '''Refactors a docstring, looking for doctests.
  415.  
  416.         This returns a modified version of the input string.  It looks
  417.         for doctests, which start with a ">>>" prompt, and may be
  418.         continued with "..." prompts, as long as the "..." is indented
  419.         the same as the ">>>".
  420.  
  421.         (Unfortunately we can\'t use the doctest module\'s parser,
  422.         since, like most parsers, it is not geared towards preserving
  423.         the original source.)
  424.         '''
  425.         result = []
  426.         block = None
  427.         block_lineno = None
  428.         indent = None
  429.         lineno = 0
  430.         for line in input.splitlines(True):
  431.             lineno += 1
  432.             if line.lstrip().startswith(self.PS1):
  433.                 if block is not None:
  434.                     result.extend(self.refactor_doctest(block, block_lineno, indent, filename))
  435.                 
  436.                 block_lineno = lineno
  437.                 block = [
  438.                     line]
  439.                 i = line.find(self.PS1)
  440.                 indent = line[:i]
  441.                 continue
  442.             if indent is not None:
  443.                 if line.startswith(indent + self.PS2) or line == indent + self.PS2.rstrip() + '\n':
  444.                     block.append(line)
  445.                     continue
  446.             if block is not None:
  447.                 result.extend(self.refactor_doctest(block, block_lineno, indent, filename))
  448.             
  449.             block = None
  450.             indent = None
  451.             result.append(line)
  452.         
  453.         if block is not None:
  454.             result.extend(self.refactor_doctest(block, block_lineno, indent, filename))
  455.         
  456.         return ''.join(result)
  457.  
  458.     
  459.     def refactor_doctest(self, block, lineno, indent, filename):
  460.         '''Refactors one doctest.
  461.  
  462.         A doctest is given as a block of lines, the first of which starts
  463.         with ">>>" (possibly indented), while the remaining lines start
  464.         with "..." (identically indented).
  465.  
  466.         '''
  467.         
  468.         try:
  469.             tree = self.parse_block(block, lineno, indent)
  470.         except Exception:
  471.             err = None
  472.             if self.log.isEnabledFor(logging.DEBUG):
  473.                 for line in block:
  474.                     self.log_debug('Source: %s', line.rstrip('\n'))
  475.                 
  476.             
  477.             self.log_error("Can't parse docstring in %s line %s: %s: %s", filename, lineno, err.__class__.__name__, err)
  478.             return block
  479.  
  480.         if self.refactor_tree(tree, filename):
  481.             new = str(tree).splitlines(True)
  482.             clipped = new[:lineno - 1]
  483.             new = new[lineno - 1:]
  484.             if not clipped == [
  485.                 '\n'] * (lineno - 1):
  486.                 raise AssertionError, clipped
  487.             if not new[-1].endswith('\n'):
  488.                 new[-1] += '\n'
  489.             
  490.             block = [
  491.                 indent + self.PS1 + new.pop(0)]
  492.             if new:
  493.                 [] += [ indent + self.PS2 + line for line in new ]
  494.             
  495.         
  496.         return block
  497.  
  498.     
  499.     def summarize(self):
  500.         if self.wrote:
  501.             were = 'were'
  502.         else:
  503.             were = 'need to be'
  504.         if not self.files:
  505.             self.log_message('No files %s modified.', were)
  506.         else:
  507.             self.log_message('Files that %s modified:', were)
  508.             for file in self.files:
  509.                 self.log_message(file)
  510.             
  511.         if self.fixer_log:
  512.             self.log_message('Warnings/messages while refactoring:')
  513.             for message in self.fixer_log:
  514.                 self.log_message(message)
  515.             
  516.         
  517.         if self.errors:
  518.             if len(self.errors) == 1:
  519.                 self.log_message('There was 1 error:')
  520.             else:
  521.                 self.log_message('There were %d errors:', len(self.errors))
  522.             for msg, args, kwds in self.errors:
  523.                 self.log_message(msg, *args, **kwds)
  524.             
  525.         
  526.  
  527.     
  528.     def parse_block(self, block, lineno, indent):
  529.         '''Parses a block into a tree.
  530.  
  531.         This is necessary to get correct line number / offset information
  532.         in the parser diagnostics and embedded into the parse tree.
  533.         '''
  534.         return self.driver.parse_tokens(self.wrap_toks(block, lineno, indent))
  535.  
  536.     
  537.     def wrap_toks(self, block, lineno, indent):
  538.         '''Wraps a tokenize stream to systematically modify start/end.'''
  539.         tokens = tokenize.generate_tokens(self.gen_lines(block, indent).next)
  540.         for line0, col0 in tokens:
  541.             (line1, col1) = None
  542.             line_text = None
  543.             line0 += lineno - 1
  544.             line1 += lineno - 1
  545.             yield (type, value, (line0, col0), (line1, col1), line_text)
  546.         
  547.  
  548.     
  549.     def gen_lines(self, block, indent):
  550.         '''Generates lines as expected by tokenize from a list of lines.
  551.  
  552.         This strips the first len(indent + self.PS1) characters off each line.
  553.         '''
  554.         prefix1 = indent + self.PS1
  555.         prefix2 = indent + self.PS2
  556.         prefix = prefix1
  557.         for line in block:
  558.             if line.startswith(prefix):
  559.                 yield line[len(prefix):]
  560.             elif line == prefix.rstrip() + '\n':
  561.                 yield '\n'
  562.             else:
  563.                 raise AssertionError('line=%r, prefix=%r' % (line, prefix))
  564.             prefix = line.startswith(prefix)
  565.         
  566.         while True:
  567.             yield ''
  568.  
  569.  
  570.  
  571. def diff_texts(a, b, filename):
  572.     '''Return a unified diff of two strings.'''
  573.     a = a.splitlines()
  574.     b = b.splitlines()
  575.     return difflib.unified_diff(a, b, filename, filename, '(original)', '(refactored)', lineterm = '')
  576.  
  577.