extractMethod.py :  » Development » Bicycle-Repair-Man » bicyclerepair-0.9 » bike » refactor » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Bicycle Repair Man 
Bicycle Repair Man » bicyclerepair 0.9 » bike » refactor » extractMethod.py
import re
import compiler
from bike.parsing import visitor
from bike.query.common import getScopeForLine
from bike.parsing.parserutils import generateLogicalLines,\
                makeLineParseable, maskStringsAndRemoveComments
from parser import ParserError
from bike.parsing.fastparserast import Class
from bike.transformer.undo import getUndoStack
from bike.refactor.utils import getTabWidthOfLine,getLineSeperator,\
                reverseCoordsIfWrongWayRound
from bike.transformer.save import queueFileToSave
from bike.parsing.load import getSourceNode
TABSIZE = 4

class coords:
    def __init__(self, line, column):
        self.column = column
        self.line = line
    def __str__(self):
        return "("+str(self.column)+","+str(self.line)+")"

commentRE = re.compile(r"#.*?$")

class ParserException(Exception): pass

def extractMethod(filename, startcoords, endcoords, newname):
    ExtractMethod(getSourceNode(filename),
                  startcoords, endcoords, newname).execute()

class ExtractMethod(object):
    def __init__(self,sourcenode, startcoords, endcoords, newname):
        self.sourcenode = sourcenode

        startcoords, endcoords = \
               reverseCoordsIfWrongWayRound(startcoords,endcoords)

        self.startline = startcoords.line
        self.endline = endcoords.line
        self.startcol = startcoords.column
        self.endcol= endcoords.column

        self.newfn = NewFunction(newname)

        self.getLineSeperator()
        self.adjustStartColumnIfLessThanTabwidth()
        self.adjustEndColumnIfStartsANewLine()
        self.fn = self.getFunctionObject()
        self.getRegionToBuffer()
        #print "-"*80
        #print self.extractedLines
        #print "-"*80
        self.deduceIfIsMethodOrFunction()

    def execute(self):
        self.deduceArguments()
        getUndoStack().addSource(self.sourcenode.filename,
                                 self.sourcenode.getSource())
        srclines = self.sourcenode.getLines()
        newFnInsertPosition = self.fn.getEndLine()-1
        self.insertNewFunctionIntoSrcLines(srclines, self.newfn,
                                           newFnInsertPosition)
        self.writeCallToNewFunction(srclines)

        src = "".join(srclines)
        queueFileToSave(self.sourcenode.filename,src)        

    def getLineSeperator(self):
        line = self.sourcenode.getLines()[self.startline-1]
        linesep = getLineSeperator(line)
        self.linesep = linesep        

    def adjustStartColumnIfLessThanTabwidth(self):
        tabwidth = getTabWidthOfLine(self.sourcenode.getLines()[self.startline-1])
        if self.startcol < tabwidth: self.startcol = tabwidth

    def adjustEndColumnIfStartsANewLine(self):
        if self.endcol == 0:
            self.endline -=1
            nlSize = len(self.linesep)
            self.endcol = len(self.sourcenode.getLines()[self.endline-1])-nlSize


    def getFunctionObject(self):
        return getScopeForLine(self.sourcenode,self.startline)


    def getTabwidthOfParentFunction(self):
        line = self.sourcenode.getLines()[self.fn.getStartLine()-1]
        match = re.match("\s+",line)
        if match is None:
            return 0
        else:
            return match.end(0)

    # should be in the transformer module
    def insertNewFunctionIntoSrcLines(self,srclines,newfn,insertpos):
        tabwidth = self.getTabwidthOfParentFunction()

        while re.match("\s*"+self.linesep,srclines[insertpos-1]):
            insertpos -= 1

        srclines.insert(insertpos, self.linesep)
        insertpos +=1

        fndefn = "def "+newfn.name+"("

        if self.isAMethod:
            fndefn += "self"
            if newfn.args != []:
                fndefn += ", "+", ".join(newfn.args)
        else:
            fndefn += ", ".join(newfn.args)

        fndefn += "):"+self.linesep


        srclines.insert(insertpos,tabwidth*" "+fndefn)
        insertpos +=1

        tabwidth += TABSIZE


        if self.extractedCodeIsAnExpression(srclines):
            assert len(self.extractedLines) == 1

            fnbody = [tabwidth*" "+ "return "+self.extractedLines[0]]


        else:
            fnbody = [tabwidth*" "+line for line in self.extractedLines]
            if newfn.retvals != []:
                fnbody.append(tabwidth*" "+"return "+
                             ", ".join(newfn.retvals) + self.linesep)

        for line in fnbody:
            srclines.insert(insertpos,line)
            insertpos +=1


    def writeCallToNewFunction(self, srclines):
        startline = self.startline
        endline = self.endline
        startcol = self.startcol
        endcol= self.endcol

        fncall = self.constructFunctionCallString(self.newfn.name, self.newfn.args,
                                                  self.newfn.retvals)

        self.replaceCodeWithFunctionCall(srclines, fncall,
                                         startline, endline, startcol, endcol)


    def replaceCodeWithFunctionCall(self, srclines, fncall,
                                    startline, endline, startcol, endcol):
        if startline == endline:  # i.e. extracted code part of existing line
            line = srclines[startline-1]
            srclines[startline-1] = self.replaceSectionOfLineWithFunctionCall(line,
                                                         startcol, endcol, fncall)
        else:
            self.replaceLinesWithFunctionCall(srclines, startline, endline, fncall)


    def replaceLinesWithFunctionCall(self, srclines, startline, endline, fncall):
        tabwidth = getTabWidthOfLine(srclines[startline-1])
        line = tabwidth*" " + fncall + self.linesep
        srclines[startline-1:endline] = [line]



    def replaceSectionOfLineWithFunctionCall(self, line, startcol, endcol, fncall):
        line = line[:startcol] + fncall + line[endcol:]
        if not line.endswith(self.linesep):
            line+=self.linesep
        return line



    def constructFunctionCallString(self, fnname, fnargs, retvals):
        fncall = fnname + "("+", ".join(fnargs)+")"
        if self.isAMethod:
            fncall = "self." + fncall

        if retvals != []:
            fncall = ", ".join(retvals) + " = "+fncall
        return fncall


    def deduceArguments(self):
        lines = self.fn.getLinesNotIncludingThoseBelongingToChildScopes()

        # strip off comments
        lines = [commentRE.sub(self.linesep,line) for line in lines]
        extractedLines = maskStringsAndRemoveComments("".join(self.extractedLines)).splitlines(1)

        linesbefore = lines[:(self.startline - self.fn.getStartLine())]
        linesafter = lines[(self.endline - self.fn.getStartLine()) + 1:]

        # split into logical lines
        linesbefore = [line for line in generateLogicalLines(linesbefore)]        
        extractedLines = [line for line in generateLogicalLines(extractedLines)]
        linesafter = [line for line in generateLogicalLines(linesafter)]

        if self.startline == self.endline:
            # need to include the line code is extracted from
            line = generateLogicalLines(lines[self.startline - self.fn.getStartLine():]).next()
            linesbefore.append(line[:self.startcol] + "dummyFn()" + line[self.endcol:])
        assigns = getAssignments(linesbefore)
        fnargs = getFunctionArgs(linesbefore)
        candidateArgs = assigns + fnargs            
        refs = getVariableReferencesInLines(extractedLines)
        self.newfn.args = [ref for ref in refs if ref in candidateArgs]

        assignsInExtractedBlock = getAssignments(extractedLines)
        usesAfterNewFunctionCall = getVariableReferencesInLines(linesafter)
        usesInPreceedingLoop = getVariableReferencesInLines(
            self.getPreceedingLinesInLoop(linesbefore,line))
        self.newfn.retvals = [ref for ref in usesInPreceedingLoop+usesAfterNewFunctionCall
                                   if ref in assignsInExtractedBlock]

    def getPreceedingLinesInLoop(self,linesbefore,firstLineToExtract):
        if linesbefore == []: return []
        tabwidth = getTabWidthOfLine(firstLineToExtract)
        rootTabwidth = getTabWidthOfLine(linesbefore[0])
        llines = [line for line in generateLogicalLines(linesbefore)]
        startpos = len(llines)-1
        loopTabwidth = tabwidth
        for idx in range(startpos,0,-1):
            line = llines[idx]
            if re.match("(\s+)for",line) is not None or \
               re.match("(\s+)while",line) is not None:
                candidateLoopTabwidth = getTabWidthOfLine(line)
                if candidateLoopTabwidth < loopTabwidth:
                    startpos = idx
        return llines[startpos:]

    




    def getRegionToBuffer(self):
        startline = self.startline
        endline = self.endline
        startcol = self.startcol
        endcol= self.endcol


        self.extractedLines = self.sourcenode.getLines()[startline-1:endline]

        match = re.match("\s*",self.extractedLines[0])
        tabwidth = match.end(0)

        self.extractedLines = [line[startcol:] for line in self.extractedLines]

        # above cropping can take a blank line's newline off.
        # this puts it back
        for idx in range(len(self.extractedLines)):
            if self.extractedLines[idx] == '':
                self.extractedLines[idx] = self.linesep

        if startline == endline:
            # need to crop the end
            # (n.b. if region is multiple lines, then whole lines are taken)
            self.extractedLines[-1] = self.extractedLines[-1][:endcol-startcol]

        if self.extractedLines[-1][-1] != '\n':
            self.extractedLines[-1] += self.linesep

    def extractedCodeIsAnExpression(self,lines):
        if len(self.extractedLines) == 1:
            charsBeforeSelection = lines[self.startline-1][:self.startcol]
            if re.match("^\s*$",charsBeforeSelection) is not None:
                return 0
            if re.search(":\s*$",charsBeforeSelection) is not None:
                return 0
            return 1
        return 0

    def deduceIfIsMethodOrFunction(self):
        if isinstance(self.fn.getParent(),Class):
            self.isAMethod = 1
        else:
            self.isAMethod = 0


# holds information about the new function
class NewFunction:
    def __init__(self,name):
        self.name = name


# lines = list of lines.
# Have to have strings masked and comments removed
def getAssignments(lines):
    class AssignVisitor:
        def __init__(self):
            self.assigns = []

        def visitAssTuple(self, node):
            for a in node.nodes:
                if a.name not in self.assigns:
                    self.assigns.append(a.name)

        def visitAssName(self, node):
            if node.name not in self.assigns:
                self.assigns.append(node.name)

        def visitAugAssign(self, node):
            if isinstance(node.node, compiler.ast.Name):
                if node.node.name not in self.assigns:
                    self.assigns.append(node.node.name)

    assignfinder = AssignVisitor()
    for line in lines:
        doctoredline = makeLineParseable(line)
        try:
            ast = compiler.parse(doctoredline)
        except ParserError:
            raise ParserException("couldnt parse:"+doctoredline)
        visitor.walk(ast, assignfinder)
    return assignfinder.assigns


# lines = list of lines.
# Have to have strings masked and comments removed
def getFunctionArgs(lines):
    if lines == []: return []

    class FunctionVisitor:
        def __init__(self):
            self.result = []
        def visitFunction(self, node):
            for n in node.argnames:
                if n != "self":
                    self.result.append(n)
    fndef = generateLogicalLines(lines).next()
    doctoredline = makeLineParseable(fndef)
    try:
        ast = compiler.parse(doctoredline)
    except ParserError:
        raise ParserException("couldnt parse:"+doctoredline)
    return visitor.walk(ast, FunctionVisitor()).result



# lines = list of lines. Have to have strings masked and comments removed
def getVariableReferencesInLines(lines):
    class NameVisitor:
        def __init__(self):
            self.result = []
        def visitName(self, node):
            if node.name not in self.result:
                self.result.append(node.name)
    reffinder = NameVisitor()
    for line in lines:
        doctoredline = makeLineParseable(line)
        try:
            ast = compiler.parse(doctoredline)
        except ParserError:
            raise ParserException("couldnt parse:"+doctoredline)
        visitor.walk(ast, reffinder)
    return reffinder.result
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.