bicyclerepair-0.9/0040775000076400007640000000000010036707253012340 5ustar pldpldbicyclerepair-0.9/bike/0040775000076400007640000000000010036707253013252 5ustar pldpldbicyclerepair-0.9/bike/parsing/0040775000076400007640000000000010036707253014715 5ustar pldpldbicyclerepair-0.9/bike/parsing/__init__.py0100644000076400007640000000013607417674162017034 0ustar pldpld#from addtypeinfo import addtypeinfo #from load import load #from brmtransformer import parse bicyclerepair-0.9/bike/parsing/constants.py0100644000076400007640000000013407424624764017310 0ustar pldpldmessages={"PARSING":"Parsing","ADDTYPEINFO":"Deducing Type Information"} MAXCALLDEPTH = 10 bicyclerepair-0.9/bike/parsing/fastparser.py0100644000076400007640000000514210012211404017414 0ustar pldpld#!/usr/bin/env python from bike.parsing.fastparserast import * from bike.parsing.parserutils import * from parser import ParserError #import exceptions indentRE = re.compile("^\s*(\w+)") # returns a tree of objects representing nested classes and functions # in the source def fastparser(src,modulename="",filename=""): try: return fastparser_impl(src,modulename,filename) except RuntimeError, ex: # if recursive call exceeds maximum depth if str(ex) == "maximum recursion limit exceeded": raise ParserError,"maximum recursion depth exceeded when fast-parsing src "+filename else: raise def fastparser_impl(src,modulename,filename): lines = src.splitlines(1) maskedSrc = maskPythonKeywordsInStringsAndComments(src) maskedLines = maskedSrc.splitlines(1) root = Module(filename,modulename,lines,maskedSrc) parentnode = root lineno = 0 for line in maskedLines: lineno+=1 #print "line",lineno,":",line m = indentRE.match(line) if m: indent = m.start(1) tokenstr = m.group(1) if tokenstr == "import" or tokenstr == "from": while indent <= parentnode.indent: # root indent is -TABWIDTH parentnode = parentnode.getParent() try: parentnode.importlines.append(lineno) except AttributeError: parentnode.importlines = [lineno] elif tokenstr == "class": m2 = classNameRE.match(line) if m2: n = Class(m2.group(1), filename, root, lineno, indent, lines, maskedSrc) root.flattenedNodes.append(n) while indent <= parentnode.indent: parentnode = parentnode.getParent() parentnode.addChild(n) parentnode = n elif tokenstr == "def": m2 = fnNameRE.match(line) if m2: n = Function(m2.group(1), filename, root, lineno, indent, lines, maskedSrc) root.flattenedNodes.append(n) while indent <= parentnode.indent: parentnode = parentnode.getParent() parentnode.addChild(n) parentnode = n elif indent <= parentnode.indent and \ tokenstr in ['if','for','while','try']: parentnode = parentnode.getParent() while indent <= parentnode.indent: parentnode = parentnode.getParent() return root bicyclerepair-0.9/bike/parsing/fastparserast.py0100644000076400007640000002274110014642107020142 0ustar pldpldfrom __future__ import generators from parserutils import generateLogicalLines, maskStringsAndComments, maskStringsAndRemoveComments import re import os import compiler from bike.transformer.save import resetOutputQueue TABWIDTH = 4 classNameRE = re.compile("^\s*class\s+(\w+)") fnNameRE = re.compile("^\s*def\s+(\w+)") _root = None def getRoot(): global _root if _root is None: resetRoot() return _root def resetRoot(root = None): global _root _root = root or Root() _root.unittestmode = False resetOutputQueue() def getModule(filename_path): from bike.parsing.load import CantLocateSourceNodeException, getSourceNode try: sourcenode = getSourceNode(filename_path) return sourcenode.fastparseroot except CantLocateSourceNodeException: return None def getPackage(directory_path): from bike.parsing.pathutils import getRootDirectory rootdir = getRootDirectory(directory_path) if rootdir == directory_path: return getRoot() else: return Package(directory_path, os.path.basename(directory_path)) class Root: def __init__(self, pythonpath = None): # singleton hack to allow functions in query package to appear # 'stateless' resetRoot(self) # this is to get round a python optimisation which reuses an # empty list as a default arg. unfortunately the client of # this method may fill that list, so it's not empty if not pythonpath: pythonpath = [] self.pythonpath = pythonpath def __repr__(self): return "Root()" #return "Root(%s)"%(self.getChildNodes()) # dummy method def getChild(self,name): return None class Package: def __init__(self, path, name): self.path = path self.name = name def getChild(self,name): from bike.parsing.newstuff import getModule return getModule(os.path.join(self.path,name+".py")) def __repr__(self): return "Package(%s,%s)"%(self.path, self.name) # used so that linenum can be an attribute class Line(str): pass class StructuralNode: def __init__(self, filename, srclines, modulesrc): self.childNodes = [] self.filename = filename self._parent = None self._modulesrc = modulesrc self._srclines = srclines self._maskedLines = None def addChild(self, node): self.childNodes.append(node) node.setParent(self) def setParent(self, parent): self._parent = parent def getParent(self): return self._parent def getChildNodes(self): return self.childNodes def getChild(self,name): matches = [c for c in self.getChildNodes() if c.name == name] if matches != []: return matches[0] def getLogicalLine(self,physicalLineno): return generateLogicalLines(self._srclines[physicalLineno-1:]).next() # badly named: actually returns line numbers of import statements def getImportLineNumbers(self): try: return self.importlines except AttributeError: return[] def getLinesNotIncludingThoseBelongingToChildScopes(self): srclines = self.getMaskedModuleLines() lines = [] lineno = self.getStartLine() for child in self.getChildNodes(): lines+=srclines[lineno-1: child.getStartLine()-1] lineno = child.getEndLine() lines+=srclines[lineno-1: self.getEndLine()-1] return lines def generateLinesNotIncludingThoseBelongingToChildScopes(self): srclines = self.getMaskedModuleLines() lines = [] lineno = self.getStartLine() for child in self.getChildNodes(): for line in srclines[lineno-1: child.getStartLine()-1]: yield self.attachLinenum(line,lineno) lineno +=1 lineno = child.getEndLine() for line in srclines[lineno-1: self.getEndLine()-1]: yield self.attachLinenum(line,lineno) lineno +=1 def generateLinesWithLineNumbers(self,startline=1): srclines = self.getMaskedModuleLines() for lineno in range(startline,len(srclines)+1): yield self.attachLinenum(srclines[lineno-1],lineno) def attachLinenum(self,line,lineno): line = Line(line) line.linenum = lineno return line def getMaskedModuleLines(self): from bike.parsing.load import Cache try: maskedlines = Cache.instance.maskedlinescache[self.filename] except: # make sure src is actually masked # (could just have keywords masked) maskedsrc = maskStringsAndComments(self._modulesrc) maskedlines = maskedsrc.splitlines(1) Cache.instance.maskedlinescache[self.filename] = maskedlines return maskedlines class Module(StructuralNode): def __init__(self, filename, name, srclines, maskedsrc): StructuralNode.__init__(self, filename, srclines, maskedsrc) self.name = name self.indent = -TABWIDTH self.flattenedNodes = [] self.module = self def getMaskedLines(self): return self.getMaskedModuleLines() def getFlattenedListOfChildNodes(self): return self.flattenedNodes def getStartLine(self): return 1 def getEndLine(self): return len(self.getMaskedModuleLines())+1 def getSourceNode(self): return self.sourcenode def setSourceNode(self, sourcenode): self.sourcenode = sourcenode def matchesCompilerNode(self,node): return isinstance(node,compiler.ast.Module) and \ node.name == self.name def getParent(self): if self._parent is not None: return self._parent else: from newstuff import getPackage return getPackage(os.path.dirname(self.filename)) def __str__(self): return "bike:Module:"+self.filename indentRE = re.compile("^(\s*)\S") class Node: # module = the module node # linenum = starting line number def __init__(self, name, module, linenum, indent): self.name = name self.module = module self.linenum = linenum self.endline = None self.indent = indent def getMaskedLines(self): return self.getMaskedModuleLines()[self.getStartLine()-1:self.getEndLine()-1] def getStartLine(self): return self.linenum def getEndLine(self): if self.endline is None: physicallines = self.getMaskedModuleLines() lineno = self.linenum logicallines = generateLogicalLines(physicallines[lineno-1:]) # skip the first line, because it's the declaration line = logicallines.next() lineno+=line.count("\n") # scan to the end of the fn for line in logicallines: #print lineno,":",line, match = indentRE.match(line) if match and match.end()-1 <= self.indent: break lineno+=line.count("\n") self.endline = lineno return self.endline # linenum starts at 0 def getLine(self, linenum): return self._srclines[(self.getStartLine()-1) + linenum] baseClassesRE = re.compile("class\s+[^(]+\(([^)]+)\):") class Class(StructuralNode, Node): def __init__(self, name, filename, module, linenum, indent, srclines, maskedmodulesrc): StructuralNode.__init__(self, filename, srclines, maskedmodulesrc) Node.__init__(self, name, module, linenum, indent) self.type = "Class" def getBaseClassNames(self): #line = self.getLine(0) line = self.getLogicalLine(self.getStartLine()) match = baseClassesRE.search(line) if match: return [s.strip()for s in match.group(1).split(",")] else: return [] def getColumnOfName(self): match = classNameRE.match(self.getLine(0)) return match.start(1) def __repr__(self): return "" % self.name def __str__(self): return "bike:Class:"+self.filename+":"+\ str(self.getStartLine())+":"+self.name def matchesCompilerNode(self,node): return isinstance(node,compiler.ast.Class) and \ node.name == self.name def __eq__(self,other): return isinstance(other,Class) and \ self.filename == other.filename and \ self.getStartLine() == other.getStartLine() # describes an instance of a class class Instance: def __init__(self, type): assert type is not None self._type = type def getType(self): return self._type def __str__(self): return "Instance(%s)"%(self.getType()) class Function(StructuralNode, Node): def __init__(self, name, filename, module, linenum, indent, srclines, maskedsrc): StructuralNode.__init__(self, filename, srclines, maskedsrc) Node.__init__(self, name, module, linenum, indent) self.type = "Function" def getColumnOfName(self): match = fnNameRE.match(self.getLine(0)) return match.start(1) def __repr__(self): return "" % self.name def __str__(self): return "bike:Function:"+self.filename+":"+\ str(self.getStartLine())+":"+self.name def matchesCompilerNode(self,node): return isinstance(node,compiler.ast.Function) and \ node.name == self.name bicyclerepair-0.9/bike/parsing/load.py0100644000076400007640000000474410011650304016175 0ustar pldpldfrom bike.globals import * import os from bike.parsing.fastparser import fastparser class Cache: def __init__(self): self.reset() def reset(self): self.srcnodecache = {} self.typecache = {} self.maskedlinescache = {} instance = None Cache.instance = Cache() class CantLocateSourceNodeException(Exception): pass def getSourceNode(filename_path): #print "getSourceNode:",filename_path sourcenode = None try: sourcenode = Cache.instance.srcnodecache[filename_path] except KeyError: pass if sourcenode is None: from bike.parsing.newstuff import translateFnameToModuleName sourcenode = SourceFile.createFromFile(filename_path, translateFnameToModuleName(filename_path)) if sourcenode is None: raise CantLocateSourceNodeException(filename_path) Cache.instance.srcnodecache[filename_path]=sourcenode return sourcenode class SourceFile: def createFromString(filename, modulename, src): return SourceFile(filename,modulename,src) createFromString = staticmethod(createFromString) def createFromFile(filename,modulename): try: f = file(filename) src = f.read() f.close() except IOError: return None else: return SourceFile(filename,modulename,src) createFromFile = staticmethod(createFromFile) def __init__(self, filename, modulename, src): if os.path.isabs(filename): self.filename = filename else: self.filename = os.path.abspath(filename) self.modulename = modulename self.resetWithSource(src) def resetWithSource(self, source): # fastparser ast self.fastparseroot = fastparser(source,self.modulename,self.filename) self.fastparseroot.setSourceNode(self) self._lines = source.splitlines(1) self.sourcenode = self def __repr__(self): return "Source(%s,%s)"%('source', self.filename) def getChildNodes(self): return self.fastparseroot.getChildNodes() def getSource(self): return "".join(self.getLines()) def getLine(self,linenum): return self.getLines()[linenum-1] # TODO: rename me! def getFlattenedListOfFastParserASTNodes(self): return self.fastparseroot.getFlattenedListOfChildNodes() def getLines(self): return self._lines bicyclerepair-0.9/bike/parsing/newstuff.py0100644000076400007640000000766110012255630017124 0ustar pldpldfrom __future__ import generators # Holding module for scaffolding needed to transition parsing package # into stateless design import os import re from bike.parsing.pathutils import getRootDirectory, getPackageBaseDirectory, \ filenameToModulePath, getPathOfModuleOrPackage, getFilesForName from bike.parsing.fastparserast import Module, Package, getRoot, getPackage, getModule import sys from bike.parsing.load import getSourceNode, CantLocateSourceNodeException def translateFnameToModuleName(filename_path): return filenameToModulePath(filename_path) # scope is the scope to search from def getModuleOrPackageUsingFQN(fqn, dirpath=None): pythonpath = getPythonPath() #print "getModuleOrPackageUsingFQN",pythonpath,fqn if dirpath is not None: assert os.path.isdir(dirpath) pythonpath = [dirpath] + pythonpath filename = getPathOfModuleOrPackage(fqn,pythonpath) #print "getModuleOrPackageUsingFQN - filename",filename if filename is not None: if os.path.isdir(filename): return getPackage(filename) else: return getModule(filename) else: return None def getPythonPath(): return getRoot().pythonpath def generateModuleFilenamesInPythonPath(contextFilename): files = [] rootdir = getRootDirectory(contextFilename) if rootdir in getPythonPath(): # just search the pythonpath for path in getPythonPath(): for file in getFilesForName(path): if file not in files: # check for duplicates files.append(file) yield file else: # search the package hierarchy containing contextFilename # in addition to pythonpath basedir = getPackageBaseDirectory(contextFilename) for path in [basedir] + getPythonPath(): for file in getFilesForName(path): if file not in files: # check for duplicates files.append(file) yield file # and search the files immediately above the package hierarchy for file in getFilesForName(os.path.join(rootdir,"*.py")): if file not in files: # check for duplicates files.append(file) yield file def generateModuleFilenamesInPackage(filenameInPackage): basedir = getPackageBaseDirectory(filenameInPackage) for file in getFilesForName(basedir): yield file # search all sourcenodes globally from the perspective of file 'contextFilename' def getSourceNodesContainingRegex(regexstr,contextFilename): regex = re.compile(regexstr) for fname in generateModuleFilenamesInPythonPath(contextFilename): try: f = file(fname) src = f.read() finally: f.close() if regex.search(src) is not None: yield getSourceNode(fname) fromRegex = re.compile("^\s*from\s+(\w+)\s+import") importregex = re.compile("^\s*import\s+(\w+)") # fileInPackage is the filename of a file in the package hierarchy # generates file and directory paths def generatePackageDependencies(fileInPackage): rejectPackagePaths = [getPackageBaseDirectory(fileInPackage)] for fname in generateModuleFilenamesInPackage(fileInPackage): try: f = file(fname) src = f.read() finally: f.close() packagepath = None for line in src.splitlines(): match = fromRegex.search(line) or importregex.search(line) if match is not None: modulepath = match.group(1) packagename = modulepath.split('.')[0] packagepath = getPathOfModuleOrPackage(packagename, getPythonPath()) if packagepath is not None and \ packagepath not in rejectPackagePaths: rejectPackagePaths.append(packagepath) # avoid duplicates yield packagepath bicyclerepair-0.9/bike/parsing/parserutils.py0100644000076400007640000001606210005002254017625 0ustar pldpldfrom __future__ import generators import re escapedQuotesRE = re.compile(r"(\\\\|\\\"|\\\')") # changess \" \' and \\ into ** so that text searches # for " and ' won't hit escaped ones def maskEscapedQuotes(src): return escapedQuotesRE.sub("**", src) stringsAndCommentsRE = \ re.compile("(\"\"\".*?\"\"\"|'''.*?'''|\"[^\"]*\"|\'[^\']*\'|#.*?\n)", re.DOTALL) import string #transtable = string.maketrans('classdefifforwhiletry', "*********************") # performs a transformation on all of the comments and strings so that # text searches for python keywords won't accidently find a keyword in # a string or comment def maskPythonKeywordsInStringsAndComments(src): src = escapedQuotesRE.sub("**", src) allstrings = stringsAndCommentsRE.split(src) # every odd element is a string or comment for i in xrange(1, len(allstrings), 2): allstrings[i] = allstrings[i].upper() #allstrings[i] = allstrings[i].translate(transtable) return "".join(allstrings) allchars = string.maketrans("", "") allcharsExceptNewline = allchars[: allchars.index('\n')]+allchars[allchars.index('\n')+1:] allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline)) # replaces all chars in a string or a comment with * (except newlines). # this ensures that text searches don't mistake comments for keywords, and that all # matches are in the same line/comment as the original def maskStringsAndComments(src): src = escapedQuotesRE.sub("**", src) allstrings = stringsAndCommentsRE.split(src) # every odd element is a string or comment for i in xrange(1, len(allstrings), 2): if allstrings[i].startswith("'''")or allstrings[i].startswith('"""'): allstrings[i] = allstrings[i][:3]+ \ allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-3:] else: allstrings[i] = allstrings[i][0]+ \ allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-1] return "".join(allstrings) # replaces all chars in a string or a comment with * (except newlines). # this ensures that text searches don't mistake comments for keywords, and that all # matches are in the same line/comment as the original def maskStringsAndRemoveComments(src): src = escapedQuotesRE.sub("**", src) allstrings = stringsAndCommentsRE.split(src) # every odd element is a string or comment for i in xrange(1, len(allstrings), 2): if allstrings[i].startswith("'''")or allstrings[i].startswith('"""'): allstrings[i] = allstrings[i][:3]+ \ allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-3:] elif allstrings[i].startswith("#"): allstrings[i] = '\n' else: allstrings[i] = allstrings[i][0]+ \ allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-1] return "".join(allstrings) implicitContinuationChars = (('(', ')'), ('[', ']'), ('{', '}')) emptyHangingBraces = [0,0,0,0,0] linecontinueRE = re.compile(r"\\\s*(#.*)?$") multiLineStringsRE = \ re.compile("(^.*?\"\"\".*?\"\"\".*?$|^.*?'''.*?'''.*?$)", re.DOTALL) #def splitLogicalLines(src): # src = multiLineStringsRE.split(src) # splits the string into logical lines. This requires the comments to # be removed, and strings masked (see other fns in this module) def splitLogicalLines(src): physicallines = src.splitlines(1) return [x for x in generateLogicalLines(physicallines)] class UnbalancedBracesException: pass # splits the string into logical lines. This requires the strings # masked (see other fns in this module) # Physical Lines *Must* start on a non-continued non-in-a-comment line # (although detects unbalanced braces) def generateLogicalLines(physicallines): tmp = [] hangingBraces = list(emptyHangingBraces) hangingComments = 0 for line in physicallines: # update hanging braces for i in range(len(implicitContinuationChars)): contchar = implicitContinuationChars[i] numHanging = hangingBraces[i] hangingBraces[i] = numHanging+line.count(contchar[0]) - \ line.count(contchar[1]) hangingComments ^= line.count('"""') % 2 hangingComments ^= line.count("'''") % 2 if hangingBraces[0] < 0 or \ hangingBraces[1] < 0 or \ hangingBraces[2] < 0: raise UnbalancedBracesException() if linecontinueRE.search(line): tmp.append(line) elif hangingBraces != emptyHangingBraces: tmp.append(line) elif hangingComments: tmp.append(line) else: tmp.append(line) yield "".join(tmp) tmp = [] # see above but yields (line,linenum) # needs physicallines to have linenum attribute # TODO: refactor with previous function def generateLogicalLinesAndLineNumbers(physicallines): tmp = [] hangingBraces = list(emptyHangingBraces) hangingComments = 0 linenum = None for line in physicallines: if tmp == []: linenum = line.linenum # update hanging braces for i in range(len(implicitContinuationChars)): contchar = implicitContinuationChars[i] numHanging = hangingBraces[i] hangingBraces[i] = numHanging+line.count(contchar[0]) - \ line.count(contchar[1]) hangingComments ^= line.count('"""') % 2 hangingComments ^= line.count("'''") % 2 if linecontinueRE.search(line): tmp.append(line) elif hangingBraces != emptyHangingBraces: tmp.append(line) elif hangingComments: tmp.append(line) else: tmp.append(line) yield "".join(tmp),linenum tmp = [] # takes a line of code, and decorates it with noops so that it can be # parsed by the python compiler. # e.g. "if foo:" -> "if foo: pass" # returns the line, and the adjustment made to the column pos of the first char # line must have strings and comments masked # # N.B. it only inserts keywords whitespace and 0's notSpaceRE = re.compile("\s*(\S)") commentRE = re.compile("#.*$") def makeLineParseable(line): return makeLineParseableWhenCommentsRemoved(commentRE.sub("",line)) def makeLineParseableWhenCommentsRemoved(line): line = line.strip() if ":" in line: if line.endswith(":"): line += " pass" if line.startswith("try"): line += "\nexcept: pass" elif line.startswith("except") or line.startswith("finally"): line = "try: pass\n" + line return line elif line.startswith("else") or line.startswith("elif"): line = "if 0: pass\n" + line return line elif line.startswith("yield"): return ("return"+line[5:]) return line bicyclerepair-0.9/bike/parsing/pathutils.py0100664000076400007640000001121410012255746017277 0ustar pldpld # A some of this code is take from Pythius - # Copyright (GPL) 2001 Jurgen Hermann from bike.globals import * import os def containsAny(str, set): """ Check whether 'str' contains ANY of the chars in 'set' """ return 1 in [c in str for c in set] def getPathOfModuleOrPackage(dotted_name, pathlist = None): """ Get the filesystem path for a module or a package. Return the file system path to a file for a module, and to a directory for a package. Return None if the name is not found, or is a builtin or extension module. """ from bike.parsing.newstuff import getPythonPath if pathlist is None: pathlist = getPythonPath() import imp # split off top-most name parts = dotted_name.split('.', 1) if len(parts) > 1: # we have a dotted path, import top-level package try: file, pathname, description = imp.find_module(parts[0], pathlist) if file: file.close() except ImportError: return None # check if it's indeed a package if description[2] == imp.PKG_DIRECTORY: # recursively handle the remaining name parts pathname = getPathOfModuleOrPackage(parts[1], [pathname]) else: pathname = None else: # plain name try: file, pathname, description = imp.find_module(dotted_name, pathlist) if file: file.close() if description[2]not in[imp.PY_SOURCE, imp.PKG_DIRECTORY]: pathname = None except ImportError: pathname = None return pathname def getFilesForName(name): """ Get a list of module files for a filename, a module or package name, or a directory. """ import imp if not os.path.exists(name): # check for glob chars if containsAny(name, "*?[]"): import glob files = glob.glob(name) list = [] for file in files: list.extend(getFilesForName(file)) return list # try to find module or package name = getPathOfModuleOrPackage(name) if not name: return[] if os.path.isdir(name): # find all python files in directory list = [] os.path.walk(name, _visit_pyfiles, list) return list elif os.path.exists(name) and not name.startswith("."): # a single file return [name] return [] def _visit_pyfiles(list, dirname, names): """ Helper for getFilesForName(). """ # get extension for python source files if not globals().has_key('_py_ext'): import imp global _py_ext _py_ext = [triple[0]for triple in imp.get_suffixes()if triple[2] == imp.PY_SOURCE][0] # don't recurse into CVS or Subversion directories if 'CVS'in names: names.remove('CVS') if '.svn'in names: names.remove('.svn') names_copy = [] + names for n in names_copy: if os.path.isdir(os.path.join(dirname, n))and \ not os.path.exists(os.path.join(dirname, n, "__init__.py")): names.remove(n) # add all *.py files to list list.extend( [os.path.join(dirname, file) for file in names if os.path.splitext(file)[1] == _py_ext and not file.startswith(".")]) # returns the directory which holds the first package of the package # hierarchy under which 'filename' belongs def getRootDirectory(filename): if os.path.isdir(filename): dir = filename else: dir = os.path.dirname(filename) while dir != "" and \ os.path.exists(os.path.join(dir, "__init__.py")): dir = os.path.dirname(dir) return dir # Returns the root package directoryname of the package hierarchy # under which 'filename' belongs def getPackageBaseDirectory(filename): if os.path.isdir(filename): dir = filename else: dir = os.path.dirname(filename) if not os.path.exists(os.path.join(dir, "__init__.py")): # parent dir is not a package return dir while dir != "" and \ os.path.exists(os.path.join(os.path.dirname(dir), "__init__.py")): dir = os.path.dirname(dir) return dir def filenameToModulePath(fname): directoriesPreceedingRoot = getRootDirectory(fname) import os # strip off directories preceeding root package directory if directoriesPreceedingRoot != "": mpath = fname.replace(directoriesPreceedingRoot, "") else: mpath = fname if(mpath[0] == os.path.normpath("/")): mpath = mpath[1:] mpath, ext = os.path.splitext(mpath) mpath = mpath.replace(os.path.normpath("/"), ".") return mpath bicyclerepair-0.9/bike/parsing/setpath.py0100644000076400007640000000034207722363224016735 0ustar pldpldimport sys,os if not os.path.abspath("../..") in sys.path: from bike import log print >> log.warning, "Appending to the system path. This should only happen in unit tests" sys.path.append(os.path.abspath("../..")) bicyclerepair-0.9/bike/parsing/test_fastparser.py0100755000076400007640000001031407720604140020472 0ustar pldpld#!/usr/bin/env python import unittest from fastparser import* from bike.parsing.load import* from bike.parsing.fastparserast import* from bike.testutils import * class TestFastParser(BRMTestCase): def test_doesntGetClassDeclsInMLStrings(self): src = trimLines(''' """ class foo bah """ ''') root = fastparser(src) assert root.getChildNodes() == [] def test_evaluatesMLStringWithQuoteInIt(self): src = trimLines(''' """some ml comment inclosing a " """ def foo: pass " hello " ''') root = fastparser(src) assert root.getChildNodes() != [] def test_handlesClassDefsWithTwoSpacesInDecl(self): src = trimLines(''' class foo: pass ''') root = fastparser(src) assert root.getChildNodes() != [] def test_handlesFnDefsWithTwoSpacesInDecl(self): src = trimLines(''' def foo: pass ''') root = fastparser(src) assert root.getChildNodes() != [] MLStringWithQuoteInIt = """ \"\"\"some ml comment inclosing a \" \"\"\" def foo: pass \" hello \" """ def load(path): files = getFilesForName(path) for fname in files: src = file(fname).read() fastparser(src) #print fname #myroot = parseFile(fname) def fastparsetreeToString(root): class stringholder: pass s = stringholder() s.mystr = "" s.tabstr = "" def t2s(node): if isinstance(node, Class): s.mystr+=s.tabstr+"class "+node.name+"\n" s.tabstr+="\t" for n in node.getChildNodes(): t2s(n) s.tabstr = s.tabstr[:-1] elif isinstance(node, Function): s.mystr+=s.tabstr+"function "+node.name+"\n" s.tabstr+="\t" for n in node.getChildNodes(): t2s(n) s.tabstr = s.tabstr[:-1] for n in root.getChildNodes(): t2s(n) return s.mystr def compilerParseTreeToString(root): try: class TreeVisitor: def __init__(self): self.mystr = "" self.tabstr = "" def visitClass(self, node): self.mystr+=self.tabstr+"class "+node.name+"\n" self.tabstr+="\t" for child in node.getChildNodes(): self.visit(child) self.tabstr = self.tabstr[:-1] def visitFunction(self, node): self.mystr+=self.tabstr+"function "+node.name+"\n" self.tabstr+="\t" for child in node.getChildNodes(): self.visit(child) self.tabstr = self.tabstr[:-1] return compiler.walk(root, TreeVisitor()).mystr except: log.exception("ex") import sys sys.exit(0) def compareCompilerWithFastparserOverPath(path): from bike.parsing.load import getFilesForName files = getFilesForName(path) for fname in files: if fname.endswith("bdist_wininst.py"): continue log.info(fname) src = file(fname).read() try: compiler_root = compiler.parse(src) except SyntaxError: continue fastparse_root = fastparser(src) str1 = fastparsetreeToString(fastparse_root) str2 = compilerParseTreeToString(compiler_root) assert str1 == str2, "\n"+"-"*70+"\n"+str1+"-"*70+"\n"+str2 def timeParseOfPythonLibrary(path): import time t1 = time.time() files = getFilesForName(path) import sys for fname in files: if fname.endswith("bdist_wininst.py"): continue src = file(fname).read() fastparser(src) print "\n", time.time()-t1 if __name__ == "__main__": from bike import logging logging.init() log = logging.getLogger("bike") log.setLevel(logging.INFO) # add soak tests to end of test class Z_SoakTestFastparser(BRMTestCase): def test_A_timeParseOfPythonLibrary(self): timeParseOfPythonLibrary("/usr/local/lib/python2.2") def test_parsesPythonLibraryCorrectly(self): print "" compareCompilerWithFastparserOverPath("/usr/local/lib/python2.2") unittest.main() bicyclerepair-0.9/bike/parsing/test_fastparserast.py0100755000076400007640000001010110010512404021157 0ustar pldpld#!/usr/bin/env python import unittest from fastparserast import * from fastparser import fastparser from bike.query.getTypeOf import getTypeOf from bike.testutils import * class TestGetModule(BRMTestCase): def test_getRootWorksAfterDefinedByCreateSourceNodeAt(self): src=trimLines(""" class TheClass: pass a = TheClass() """) root = createSourceNodeAt(src,"mymodule") assert root == getRoot() def test_returnsNoneIfModuleDoesntExist(self): assert getModule(tmpfile) == None class TestGetEndLine(BRMTestCase): def test_returnsEndLineWithSimpleFunction(self): src = trimLines(""" class TheClass: def theMethod(): pass def foo(): b = TheClass() return b a = foo() a.theMethod() """) root = fastparser(src) fn = getTypeOf(root,"foo") self.assertEqual(fn.getEndLine(),7) def test_worksWithFunctionsThatHaveEmptyLinesInThem(self): src = fnWithEmptyLineInIt root = fastparser(src) fn = getTypeOf(root,"TheClass.theFunction") self.assertEqual(fn.getEndLine(),8) class TestGetBaseClassNames(BRMTestCase): def test_worksForClassHierarchy(self): src = trimLines(""" class root: def theMethod(): pass class a(root): def theMethod(): pass class b(root): pass class TheClass(a,b): def theMethod(): pass rootinstance = root() rootinstance.theMethod() """) #classes = getASTNodeFromSrc(src,"Source").fastparseroot.getChildNodes() classes = createAST(src).fastparseroot.getChildNodes() self.assertEqual(classes[3].getBaseClassNames(),['a','b']) def test_returnsEmptyListForClassWithNoBases(self): src = trimLines(""" class root: pass """) #classes = getASTNodeFromSrc(src,"Source").fastparseroot.getChildNodes() classes = createAST(src).fastparseroot.getChildNodes() self.assertEqual(classes[0].getBaseClassNames(),[]) class TestGetMaskedLines(BRMTestCase): def test_doit(self): src =trimLines(""" class foo: #bah pass """) mod = createAST(src).fastparseroot lines = mod.getMaskedModuleLines() assert lines[0] == "class foo: #***\n" class TestGetLinesNotIncludingThoseBelongingToChildScopes(BRMTestCase): def test_worksForModule(self): src =trimLines(""" class TheClass: def theMethod(): pass def foo(): b = TheClass() return b a = foo() a.theMethod() """) mod = createAST(src).fastparseroot self.assertEqual(''.join(mod.getLinesNotIncludingThoseBelongingToChildScopes()), trimLines(""" a = foo() a.theMethod() """)) def test_worksForModuleWithSingleLineFunctions(self): src=trimLines(""" a = blah() def foo(): pass b = 1 """) mod = createAST(src).fastparseroot lines = mod.getLinesNotIncludingThoseBelongingToChildScopes() self.assertEqual(''.join(lines), trimLines(""" a = blah() b = 1 """)) def test_worksForSingleLineFunction(self): src=trimLines(""" a = blah() def foo(): pass b = 1 """) fn = createAST(src).fastparseroot.getChildNodes()[0] lines = fn.getLinesNotIncludingThoseBelongingToChildScopes() self.assertEqual(''.join(lines), trimLines(""" def foo(): pass """)) fnWithEmptyLineInIt = """ class TheClass: def theFunction(): a = foo() print 'a' # end of function """ if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/parsing/test_load.py0100755000076400007640000000046110010536120017225 0ustar pldpld#!/usr/bin/env python import setpath import unittest import compiler import os from bike import testdata from bike.testutils import * from bike.mock import Mock from pathutils import getPathOfModuleOrPackage from load import * import load as loadmodule if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/parsing/test_newstuff.py0100755000076400007640000002454610012406301020160 0ustar pldpld#!/usr/bin/env python import setpath import os import unittest from bike.testutils import * from bike.parsing.fastparserast import getRoot from bike.parsing.newstuff import getModuleOrPackageUsingFQN,\ generateModuleFilenamesInPythonPath, getSourceNodesContainingRegex,\ generatePackageDependencies class TestGetModuleOrPackageUsingFQN(BRMTestCase): def test_worksForFullPath(self): try: createPackageStructure("pass","pass") self.assertEqual(getModuleOrPackageUsingFQN("a.b.bah").filename, pkgstructureFile2) finally: removePackageStructure() def test_worksForPackage(self): try: createPackageStructure("pass","pass") self.assertEqual(getModuleOrPackageUsingFQN("a.b").path, pkgstructureChilddir) finally: removePackageStructure() class TestGenerateModuleFilenamesInPythonPath(BRMTestCase): def test_works(self): try: createPackageStructure("pass","pass") fnames = [f for f in \ generateModuleFilenamesInPythonPath(pkgstructureFile2)] assert os.path.join(pkgstructureBasedir,"__init__.py") in fnames assert pkgstructureFile1 in fnames assert os.path.join(pkgstructureChilddir,"__init__.py") in fnames assert pkgstructureFile2 in fnames assert len(fnames) == 5 finally: removePackageStructure() def test_doesntTraverseIntoNonPackages(self): try: createPackageStructure("pass","pass") nonPkgDir = os.path.join(pkgstructureChilddir,"c") newfile = os.path.join(nonPkgDir,"baz.py") # N.B. don't put an __init__.py in it, so isnt a package os.makedirs(nonPkgDir) writeFile(newfile,"pass") fnames = [f for f in \ generateModuleFilenamesInPythonPath(pkgstructureFile2)] assert newfile not in fnames finally: #os.remove(initfile) os.remove(newfile) os.removedirs(nonPkgDir) removePackageStructure() def test_doesScanFilesInTheRootDirectory(self): try: createPackageStructure("pass","pass","pass") fnames = [f for f in \ generateModuleFilenamesInPythonPath(pkgstructureFile2)] assert pkgstructureFile0 in fnames finally: #os.remove(initfile) removePackageStructure() def test_returnsOtherFilesInSameNonPackageDirectory(self): try: oldpath = getRoot().pythonpath getRoot().pythonpath = [] # clear the python path writeTmpTestFile("") newtmpfile = os.path.join(tmproot,"baz.py") writeFile(newtmpfile, "") fnames = [f for f in \ generateModuleFilenamesInPythonPath(tmpfile)] assert newtmpfile in fnames finally: os.remove(newtmpfile) deleteTmpTestFile() getRoot().pythonpath = oldpath def test_doesntTraverseIntoNonPackagesUnderRoot(self): try: os.makedirs(pkgstructureBasedir) writeFile(pkgstructureFile1,"pass") fnames = [f for f in \ generateModuleFilenamesInPythonPath(pkgstructureFile2)] assert pkgstructureFile1 not in fnames finally: os.remove(pkgstructureFile1) os.removedirs(pkgstructureBasedir) def test_doesntGenerateFilenamesMoreThanOnce(self): try: createPackageStructure("pass","pass") newfile = os.path.join(pkgstructureChilddir,"baz.py") writeFile(newfile,"pass") fnames = [f for f in \ generateModuleFilenamesInPythonPath(pkgstructureFile2)] matched = [f for f in fnames if f == newfile] self.assertEqual(1, len(matched)) finally: os.remove(newfile) removePackageStructure() class TestGetSourceNodesContainingRegex(BRMTestCase): def test_works(self): try: createPackageStructure("# testregexfoobah","pass") srcfiles = [s for s in getSourceNodesContainingRegex("testregexfoobah", pkgstructureFile2)] self.assertEqual(pkgstructureFile1,srcfiles[0].filename) finally: removePackageStructure() class TestGenerateModuleFilenamesInPythonPath2(BRMTestCase): def test_getsAllFilenamesInSameHierarchyAsContextFile(self): try: oldpath = getRoot().pythonpath getRoot().pythonpath = [] # clear the python path createPackageStructure("","") fnames = [f for f in generateModuleFilenamesInPythonPath(pkgstructureFile1)] self.assert_(pkgstructureFile0 in fnames) self.assert_(pkgstructureFile1 in fnames) self.assert_(pkgstructureFile2 in fnames) finally: getRoot().pythonpath = oldpath removePackageStructure() def test_getsFilenamesInSubPackagesIfCtxFilenameIsInTheRoot(self): try: oldpath = getRoot().pythonpath getRoot().pythonpath = [] # clear the python path createPackageStructure("","") fnames = [f for f in generateModuleFilenamesInPythonPath(pkgstructureFile0)] self.assert_(pkgstructureFile1 in fnames) self.assert_(pkgstructureFile2 in fnames) finally: getRoot().pythonpath = oldpath removePackageStructure() def test_doesntTraverseOtherPackagesOffOfTheRoot(self): try: oldpath = getRoot().pythonpath getRoot().pythonpath = [] # clear the python path createPackageStructure("","") os.makedirs(os.path.join(pkgstructureRootDir, "c")) writeFile(os.path.join(pkgstructureRootDir, "c", "__init__.py"), "# ") bazfile = os.path.join(pkgstructureRootDir, "c", "baz.py") writeFile(bazfile, "pass") fnames = [f for f in generateModuleFilenamesInPythonPath(pkgstructureFile1)] self.assert_(pkgstructureFile0 in fnames) self.assert_(pkgstructureFile1 in fnames) self.assert_(pkgstructureFile2 in fnames) self.assert_(bazfile not in fnames) finally: getRoot().pythonpath = oldpath os.remove(os.path.join(pkgstructureRootDir, "c", "baz.py")) os.remove(os.path.join(pkgstructureRootDir, "c", "__init__.py")) os.removedirs(os.path.join(pkgstructureRootDir, "c")) removePackageStructure() class TestGetPackageDependencies(BRMTestCase): def test_followsImportModule(self): try: createPackageStructure("","import c.bing") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([pkgstructureBasedir2],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_followsFromImportPackage(self): try: createPackageStructure("","import c") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([pkgstructureBasedir2],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_followsFromImportStar(self): try: createPackageStructure("","from c import *") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([pkgstructureBasedir2],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_followsFromImportModule(self): try: createPackageStructure("","from c import bing") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([pkgstructureBasedir2],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_doesntBreakIfImportIsInAMultilineString(self): try: createPackageStructure("",trimLines(""" ''' from aoeuaoeu import aocxaoieicxoe ''' """)) createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_doesntBreakIfImportIsCommented(self): try: createPackageStructure("","#from aoeuaoeu import aocxaoieicxoe") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_doesntBreakIfCantFindImport(self): try: createPackageStructure("","from aoeuaoeu import aocxaoieicxoe") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([],dependencies) finally: removeSecondPackageStructure() removePackageStructure() def test_doesntIncludeCurrentPackage(self): try: createPackageStructure("","import a.foo") createSecondPackageStructure("") dependencies = [d for d in generatePackageDependencies(pkgstructureFile2)] self.assertEqual([],dependencies) finally: removeSecondPackageStructure() removePackageStructure() if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/parsing/test_parserutils.py0100755000076400007640000001042207720604141020676 0ustar pldpld#!/usr/bin/env python import unittest from parserutils import * from bike.testutils import * class TestRemoveEscapedQuotes(BRMTestCase): def testMaskEscapedQuotes_MasksEscapedQuotes(self): src = '\" \\\\\\\" \' \\\\\\\\\" \' \'' self.assertEqual(maskEscapedQuotes(src),'" **** \' ****" \' \'') class TestMungePythonKeywordsInStrings(BRMTestCase): def test_mungesKeywords(self): src = '\"\"\"class try while\"\"\" class2 try2 while2 \'\'\' def if for \'\'\' def2 if2 for2' self.assertEqual(maskPythonKeywordsInStringsAndComments(src), '"""CLASS TRY WHILE""" class2 try2 while2 \'\'\' DEF IF FOR \'\'\' def2 if2 for2') class TestSplitLines(BRMTestCase): def test_handlesExplicitlyContinuedLineWithComment(self): self.assertEqual(splitLogicalLines(explicitlyContinuedLineWithComment), ['\n', 'z = a + b + \\ # comment\n c + d\n', 'pass\n']) def test_handlesImplicitlyContinuedLine(self): self.assertEqual(splitLogicalLines(implicitlyContinuedLine), ['\n', 'z = a + b + (\n c + d)\n', 'pass\n']) def test_handlesNestedImplicitlyContinuedLine(self): self.assertEqual(splitLogicalLines(implicitlyContinuedLine2), ['\n', 'z = a + b + ( c + [d\n + e]\n + f) # comment\n', 'pass\n']) def test_handlesMultiLineStrings(self): self.assertEqual(splitLogicalLines(multilineComment), ['\n', "''' this is an mlc\nso is this\n'''\n", 'pass\n']) class TestMakeLineParseable(BRMTestCase): def test_worksWithIfStatement(self): src = "if foo:" self.assertEqual(makeLineParseable(src),("if foo: pass")) def test_worksWithTryStatement(self): src = "try :" self.assertEqual(makeLineParseable(src),("try : pass\nexcept: pass")) def test_worksOnTryStatementWithCodeInlined(self): src = "try : a = 1" self.assertEqual(makeLineParseable(src),("try : a = 1\nexcept: pass")) def test_worksWithExceptStatement(self): src = "except :" self.assertEqual(makeLineParseable(src),("try: pass\nexcept : pass")) def test_worksWithFinallyStatement(self): src = "finally:" self.assertEqual(makeLineParseable(src),("try: pass\nfinally: pass")) def test_worksWithIfStatement(self): src = "if foo:" self.assertEqual(makeLineParseable(src),("if foo: pass")) def test_worksWithElseStatement(self): src = "else :" self.assertEqual(makeLineParseable(src),("if 0: pass\nelse : pass")) def test_worksWithElifStatement(self): src = "elif foo:" self.assertEqual(makeLineParseable(src),("if 0: pass\nelif foo: pass")) def runOverPath(path): import compiler from parser import ParserError from bike.parsing.load import getFilesForName files = getFilesForName(path) for fname in files: print fname src = file(fname).read() #print src src = maskStringsAndRemoveComments(src) for logicalline in splitLogicalLines(src): #print "logicalline=",logicalline logicalline = logicalline.strip() logicalline = makeLineParseable(logicalline) try: compiler.parse(logicalline) except ParserError: print "ParserError on logicalline:",logicalline except: log.exception("caught exception") explicitlyContinuedLineWithComment = """ z = a + b + \ # comment c + d pass """ implicitlyContinuedLine = """ z = a + b + ( c + d) pass """ implicitlyContinuedLine2 = """ z = a + b + ( c + [d + e] + f) # comment pass """ multilineComment = """ ''' this is an mlc so is this ''' pass """ if __name__ == "__main__": from bike import logging logging.init() log = logging.getLogger("bike") log.setLevel(logging.INFO) # add soak tests to end of test class Z_SoakTest(BRMTestCase): def test_linesRunThroughPythonParser(self): print "" #print splitLogicalLines(file('/usr/local/lib/python2.2/aifc.py').read()) #runOverPath('/usr/local/lib/python2.2/test/badsyntax_nocaret.py') runOverPath('/usr/local/lib/python2.2/') unittest.main() bicyclerepair-0.9/bike/parsing/test_pathutils.py0100664000076400007640000001171010010525417020330 0ustar pldpld#!/usr/bin/env python import setpath import unittest import compiler import os from bike import testdata from bike.testutils import * from bike.mock import Mock from pathutils import getPathOfModuleOrPackage from pathutils import * import pathutils as loadmodule class TestGetFilesForName(BRMTestCase): def testGetFilesForName_recursivelyReturnsFilesInBreadthFirstOrder(self): createPackageStructure("pass", "pass") files = getFilesForName(pkgstructureBasedir) for f in files: assert f in \ [os.path.join(pkgstructureBasedir, '__init__.py'), os.path.join(pkgstructureBasedir, 'foo.py'), os.path.join(pkgstructureChilddir, '__init__.py'), os.path.join(pkgstructureChilddir, 'bah.py')] def testGetFilesForName_globsStars(self): createPackageStructure("pass", "pass") assert getFilesForName(os.path.join(pkgstructureBasedir, "fo*")) == [os.path.join(pkgstructureBasedir, 'foo.py')] removePackageStructure() def testGetFilesForName_doesntListFilesWithDotAtFront(self): writeFile(os.path.join(".foobah.py"),"") files = getFilesForName("a") self.assertEqual([],files) class TestGetRootDirectory(BRMTestCase): def test_returnsParentDirectoryIfFileNotInPackage(self): try: # this doesnt have __init__.py file, so # isnt package os.makedirs("a") writeFile(os.path.join("a", "foo.py"), "pass") dir = loadmodule.getRootDirectory(os.path.join("a", "foo.py")) assert dir == "a" finally: os.remove(os.path.join("a", "foo.py")) os.removedirs(os.path.join("a")) def test_returnsFirstNonPackageParentDirectoryIfFileInPackage(self): try: os.makedirs(os.path.join("root", "a", "b")) writeFile(os.path.join("root", "a", "__init__.py"), "# ") writeFile(os.path.join("root", "a", "b", "__init__.py"), "# ") writeFile(os.path.join("root", "a", "b", "foo.py"), "pass") dir = loadmodule.getRootDirectory(os.path.join("root", "a", "b", "foo.py")) assert dir == "root" finally: os.remove(os.path.join("root", "a", "__init__.py")) os.remove(os.path.join("root", "a", "b", "__init__.py")) os.remove(os.path.join("root", "a", "b", "foo.py")) os.removedirs(os.path.join("root", "a", "b")) def test_returnsFirstNonPackageParentDirectoryIfPathIsAPackage(self): try: os.makedirs(os.path.join("root", "a", "b")) writeFile(os.path.join("root", "a", "__init__.py"), "# ") writeFile(os.path.join("root", "a", "b", "__init__.py"), "# ") writeFile(os.path.join("root", "a", "b", "foo.py"), "pass") dir = loadmodule.getRootDirectory(os.path.join("root", "a", "b")) assert dir == "root" finally: os.remove(os.path.join("root", "a", "__init__.py")) os.remove(os.path.join("root", "a", "b", "__init__.py")) os.remove(os.path.join("root", "a", "b", "foo.py")) os.removedirs(os.path.join("root", "a", "b")) def test_returnsDirIfDirIsTheRootDirectory(self): try: os.makedirs(os.path.join("root", "a", "b")) writeFile(os.path.join("root", "a", "__init__.py"), "# ") writeFile(os.path.join("root", "a", "b", "__init__.py"), "# ") writeFile(os.path.join("root", "a", "b", "foo.py"), "pass") dir = loadmodule.getRootDirectory("root") assert dir == "root" finally: os.remove(os.path.join("root", "a", "__init__.py")) os.remove(os.path.join("root", "a", "b", "__init__.py")) os.remove(os.path.join("root", "a", "b", "foo.py")) os.removedirs(os.path.join("root", "a", "b")) class getPackageBaseDirectory(BRMTestCase): def test_returnsBasePackageIfFileInPackageHierarchy(self): try: createPackageStructure("","") dir = loadmodule.getPackageBaseDirectory(pkgstructureFile2) self.assertEqual(pkgstructureBasedir, dir) finally: removePackageStructure() def test_returnsFileDirectoryIfFileNotInPackage(self): try: createPackageStructure("","") dir = loadmodule.getPackageBaseDirectory(pkgstructureFile0) self.assertEqual(pkgstructureRootDir, dir) finally: removePackageStructure() class TestGetPathOfModuleOrPackage(BRMTestCase): def test_worksForFullPath(self): try: createPackageStructure("pass","pass") import sys self.assertEqual(getPathOfModuleOrPackage("a.b.bah", [pkgstructureRootDir]), pkgstructureFile2) finally: removePackageStructure() if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/parsing/test_utils.py0100755000076400007640000000224107437253651017474 0ustar pldpld#!/usr/bin/env python import setpath import unittest from utils import * class Test_CarCdrEtc(unittest.TestCase): def test_carReturnsTheFirstElementOfTheFqn(self): fqn = "apple.pear.foo" assert fqn_car(fqn) == "apple" def test_carReturnsElementInOneElementFqn(self): fqn = "apple" assert fqn_car(fqn) == "apple" def test_cdrReturnsTheAllElementsOfTheFqnExceptFirst(self): fqn = "apple.pear.foo" assert fqn_cdr(fqn) == "pear.foo" def test_cdrReturnsEmptyStringForOneElementFqn(self): fqn = "apple" assert fqn_cdr(fqn) == "" def test_rcarReturnsTheLastElementOfTheFqn(self): fqn = "apple.pear.foo" assert fqn_rcar(fqn) == "foo" def test_rcarReturnsElementInOneElementFqn(self): fqn = "apple" assert fqn_rcar(fqn) == "apple" def test_rdrReturnsTheAllElementsOfTheFqnExceptLast(self): fqn = "apple.pear.foo" assert fqn_rcdr(fqn) == "apple.pear" def test_rdrReturnsEmptyStringForOneElementFqn(self): fqn = "apple" assert fqn_rcdr(fqn) == "" if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/parsing/test_visitor.py0100755000076400007640000000567207720604141020033 0ustar pldpld#!/usr/bin/env python from __future__ import generators import setpath import unittest import visitor from bike.testutils import * class TestVisitor(BRMTestCase): def test_callsVistorFunctions(self): tree = createTree() class TreeVisitor: def __init__(self): self.txt = [] def visitAClass(self,node): self.txt.append("visitAClass") self.txt.append(node.txt) return self.visitChildren(node) def visitCClass(self,node): self.txt.append("visitCClass") self.txt.append(node.txt) def getTxt(self): return ",".join(self.txt) self.assertEqual(visitor.walk(tree,TreeVisitor()).getTxt(), "visitAClass,aclass,visitCClass,cclass0,visitCClass,cclass1,visitCClass,cclass2") def test_callsVisitorFunctionsWithYield(self): tree = createTree() class TreeVisitor: def __init__(self): self.txt = [] def visitAClass(self,node): self.txt.append("visitAClass") self.txt.append(node.txt) yield node for i in self.visitChildren(node): yield i def visitCClass(self,node): self.txt.append("visitCClass") self.txt.append(node.txt) if 0: yield 1 def getTxt(self): return ",".join(self.txt) for node in visitor.walkAndGenerate(tree,TreeVisitor()): assert node.txt == "aclass" def createTree(): n = AClass("aclass") for i in xrange(3): b = n.addChildNode(BClass("bclass%d" % i)) for j in xrange(20): b = b.addChildNode(BClass("bclass%d" % i)) b.addChildNode(CClass("cclass%d" % i)) return n class node(object): def __init__(self,txt): self._childNodes=[] self.txt = txt def addChildNode(self,node): self._childNodes.append(node) return node def getChildNodes(self): return [x for x in self._childNodes] class AClass(node): pass class BClass(node): pass class CClass(node): pass if __name__ == "__main__": # add perf test at end of tests class Z_SoakTestFastparser(BRMTestCase): def test_parsesPythonLibraryCorrectly(self): class TreeVisitor: pass import time tree = createTree() t1 = time.time() for i in xrange(1000): visitor.walk(tree,TreeVisitor()) print "tree without yield",time.time()-t1 t1 = time.time() for i in xrange(1000): for node in visitor.walkAndGenerate(tree,TreeVisitor()): pass print "tree with yield",time.time()-t1 unittest.main() bicyclerepair-0.9/bike/parsing/testall.py0100755000076400007640000000060210010510325016713 0ustar pldpld#!/usr/bin/env python import setpath import unittest #import all the tests from test_load import * from test_newstuff import * from test_parserutils import * from test_fastparser import * from test_fastparserast import * if __name__ == "__main__": from bike import logging logging.init() log = logging.getLogger("bike") log.setLevel(logging.WARN) unittest.main() bicyclerepair-0.9/bike/parsing/utils.py0100644000076400007640000000143107417674162016434 0ustar pldpld# get the first element of a fully qualified python path #(e.g. _car('a.b.c.d') = 'a') def fqn_car(fqn): try: return fqn[:fqn.index(".")] except ValueError: # i.e. no dots in fqn return fqn # get the other elements of a fully qualified python path #(e.g. _cdr('a.b.c.d') = 'b.c.d') def fqn_cdr(fqn): try: return fqn[fqn.index(".")+1:] except ValueError: # i.e. no dots in fqn return "" # reverse of above _rcar("a.b.c.d") = "d" def fqn_rcar(fqn): try: return fqn[fqn.rindex(".")+1:] except ValueError: # i.e. no dots in fqn return fqn # reverse of above _rcdr("a.b.c.d") = "a.b.c" def fqn_rcdr(fqn): try: return fqn[:fqn.rindex(".")] except ValueError: # i.e. no dots in fqn return "" bicyclerepair-0.9/bike/parsing/visitor.py0100644000076400007640000000243310010510227016744 0ustar pldpldfrom __future__ import generators class TreeWalker(object): VERBOSE = 0 def __init__(self): self.node = None self._cache = {} def default(self, node, *args): for child in node.getChildNodes(): self.dispatch(child, *args) def dispatch(self, node, *args): self.node = node klass = node.__class__ meth = self._cache.get(klass, None) if meth is None: className = klass.__name__ meth = getattr(self.visitor, 'visit' + className, self.default) self._cache[klass] = meth return meth(node, *args) def preorder(self, tree, visitor, *args): """Do preorder walk of tree using visitor""" self.visitor = visitor visitor.visit = self.dispatch visitor.visitChildren = self.default return self.dispatch(tree, *args) class GeneratingTreeWalker(TreeWalker): def default(self, node, *args): for child in node.getChildNodes(): for i in self.dispatch(child, *args): yield i def walk(tree, visitor): walker = TreeWalker() walker.preorder(tree, visitor) return walker.visitor def walkAndGenerate(tree,visitor): walker = GeneratingTreeWalker() return walker.preorder(tree, visitor) bicyclerepair-0.9/bike/query/0040775000076400007640000000000010036707253014417 5ustar pldpldbicyclerepair-0.9/bike/query/__init__.py0100664000076400007640000000000107720604141016512 0ustar pldpld#bicyclerepair-0.9/bike/query/common.py0100644000076400007640000002564710012403633016257 0ustar pldpldfrom __future__ import generators from bike.globals import * from bike.parsing.fastparserast import getRoot, Function, Class, Module, getModule from bike.parsing.parserutils import generateLogicalLines, makeLineParseable, UnbalancedBracesException, generateLogicalLinesAndLineNumbers from bike.parsing.newstuff import getSourceNodesContainingRegex from bike.parsing import visitor from bike import log import compiler from compiler.ast import Getattr, Name import re class Match: def __repr__(self): return ",".join([self.filename, str(self.lineno), str(self.colno), str(self.confidence)]) def __eq__(self,other): if self is None or other is None: return False return self.filename == other.filename and \ self.lineno == other.lineno and \ self.colno == other.colno def getScopeForLine(sourceNode, lineno): scope = None childnodes = sourceNode.getFlattenedListOfFastParserASTNodes() if childnodes == []: return sourceNode.fastparseroot #module node scope = sourceNode.fastparseroot for node in childnodes: if node.linenum > lineno: break scope = node if scope.getStartLine() != scope.getEndLine(): # is inline while scope.getEndLine() <= lineno: scope = scope.getParent() return scope # global from the perspective of 'contextFilename' def globalScanForMatches(contextFilename, matchFinder, targetname): for sourcenode in getSourceNodesContainingRegex(targetname, contextFilename): print >> log.progress, "Scanning", sourcenode.filename searchscope = sourcenode.fastparseroot for match in scanScopeForMatches(sourcenode,searchscope, matchFinder,targetname): yield match def scanScopeForMatches(sourcenode,scope,matchFinder,targetname): lineno = scope.getStartLine() for line in generateLogicalLines(scope.getMaskedLines()): if line.find(targetname) != -1: doctoredline = makeLineParseable(line) ast = compiler.parse(doctoredline) scope = getScopeForLine(sourcenode, lineno) matchFinder.reset(line) matchFinder.setScope(scope) matches = visitor.walk(ast, matchFinder).getMatches() for index, confidence in matches: match = Match() match.filename = sourcenode.filename match.sourcenode = sourcenode x, y = indexToCoordinates(line, index) match.lineno = lineno+y match.colno = x match.colend = match.colno+len(targetname) match.confidence = confidence yield match lineno+=line.count("\n") def walkLinesContainingStrings(scope,astWalker,targetnames): lineno = scope.getStartLine() for line in generateLogicalLines(scope.getMaskedLines()): if lineContainsOneOf(line,targetnames): doctoredline = makeLineParseable(line) ast = compiler.parse(doctoredline) astWalker.lineno = lineno matches = visitor.walk(ast, astWalker) lineno+=line.count("\n") def lineContainsOneOf(line,targetnames): for name in targetnames: if line.find(name) != -1: return True return False # translates an idx in a logical line into physical line coordinates # returns x and y coords def indexToCoordinates(src, index): y = src[: index].count("\n") startOfLineIdx = src.rfind("\n", 0, index)+1 x = index-startOfLineIdx return x, y # interface for MatchFinder classes # implement the visit methods class MatchFinder: def setScope(self, scope): self.scope = scope def reset(self, line): self.matches = [] self.words = re.split("(\w+)", line) # every other one is a non word self.positions = [] i = 0 for word in self.words: self.positions.append(i) #if '\n' in word: # handle newlines # i = len(word[word.index('\n')+1:]) #else: i+=len(word) self.index = 0 def getMatches(self): return self.matches # need to visit childnodes in same order as they appear def visitPrintnl(self,node): if node.dest: self.visit(node.dest) for n in node.nodes: self.visit(n) def visitName(self, node): self.popWordsUpTo(node.name) def visitClass(self, node): self.popWordsUpTo(node.name) for base in node.bases: self.visit(base) def zipArgs(self, argnames, defaults): """Takes a list of argument names and (possibly a shorter) list of default values and zips them into a list of pairs (argname, default). Defaults are aligned so that the last len(defaults) arguments have them, and the first len(argnames) - len(defaults) pairs have None as a default. """ fixed_args = len(argnames) - len(defaults) defaults = [None] * fixed_args + list(defaults) return zip(argnames, defaults) def visitFunction(self, node): self.popWordsUpTo(node.name) for arg, default in self.zipArgs(node.argnames, node.defaults): self.popWordsUpTo(arg) if default is not None: self.visit(default) self.visit(node.code) def visitGetattr(self,node): self.visit(node.expr) self.popWordsUpTo(node.attrname) def visitAssName(self, node): self.popWordsUpTo(node.name) def visitAssAttr(self, node): self.visit(node.expr) self.popWordsUpTo(node.attrname) def visitImport(self, node): for name, alias in node.names: for nameelem in name.split("."): self.popWordsUpTo(nameelem) if alias is not None: self.popWordsUpTo(alias) def visitFrom(self, node): for elem in node.modname.split("."): self.popWordsUpTo(elem) for name, alias in node.names: self.popWordsUpTo(name) if alias is not None: self.popWordsUpTo(alias) def visitLambda(self, node): for arg, default in self.zipArgs(node.argnames, node.defaults): self.popWordsUpTo(arg) if default is not None: self.visit(default) self.visit(node.code) def visitGlobal(self, node): for name in node.names: self.popWordsUpTo(name) def popWordsUpTo(self, word): if word == "*": return # won't be able to find this posInWords = self.words.index(word) idx = self.positions[posInWords] self.words = self.words[posInWords+1:] self.positions = self.positions[posInWords+1:] def appendMatch(self,name,confidence=100): idx = self.getNextIndexOfWord(name) self.matches.append((idx, confidence)) def getNextIndexOfWord(self,name): return self.positions[self.words.index(name)] class CouldNotLocateNodeException(Exception): pass def translateSourceCoordsIntoASTNode(filename,lineno,col): module = getModule(filename) maskedlines = module.getMaskedModuleLines() lline,backtrackchars = getLogicalLine(module, lineno) doctoredline = makeLineParseable(lline) ast = compiler.parse(doctoredline) idx = backtrackchars+col nodefinder = ASTNodeFinder(lline,idx) node = compiler.walk(ast, nodefinder).node if node is None: raise CouldNotLocateNodeException("Could not translate editor coordinates into source node") return node def getLogicalLine(module, lineno): # we know that the scope is the start of a logical line, so # we search from there scope = getScopeForLine(module.getSourceNode(), lineno) linegenerator = \ module.generateLinesWithLineNumbers(scope.getStartLine()) for lline,llinenum in \ generateLogicalLinesAndLineNumbers(linegenerator): if llinenum > lineno: break prevline = lline prevlinenum = llinenum backtrackchars = 0 for i in range(prevlinenum,lineno): backtrackchars += len(module.getSourceNode().getLines()[i-1]) return prevline, backtrackchars class ASTNodeFinder(MatchFinder): # line is a masked line of text # lineno and col are coords def __init__(self,line,col): self.line = line self.col = col self.reset(line) self.node = None def visitName(self,node): if self.checkIfNameMatchesColumn(node.name): self.node = node self.popWordsUpTo(node.name) def visitGetattr(self,node): self.visit(node.expr) if self.checkIfNameMatchesColumn(node.attrname): self.node = node self.popWordsUpTo(node.attrname) def visitFunction(self, node): if self.checkIfNameMatchesColumn(node.name): self.node = node self.popWordsUpTo(node.name) for arg, default in self.zipArgs(node.argnames, node.defaults): if self.checkIfNameMatchesColumn(arg): self.node = Name(arg) self.popWordsUpTo(arg) if default is not None: self.visit(default) self.visit(node.code) visitAssName = visitName visitAssAttr = visitGetattr def visitClass(self, node): if self.checkIfNameMatchesColumn(node.name): self.node = node self.popWordsUpTo(node.name) for base in node.bases: self.visit(base) def checkIfNameMatchesColumn(self,name): idx = self.getNextIndexOfWord(name) #print "name",name,"idx",idx,"self.col",self.col if idx <= self.col and idx+len(name) > self.col: return 1 return 0 def visitFrom(self, node): for elem in node.modname.split("."): self.popWordsUpTo(elem) for name, alias in node.names: if self.checkIfNameMatchesColumn(name): self.node = self._manufactureASTNodeFromFQN(name) return self.popWordsUpTo(name) if alias is not None: self.popWordsUpTo(alias) # gets round the fact that imports etc dont contain nested getattr # nodes for fqns (e.g. import a.b.bah) by converting the fqn # string into a getattr instance def _manufactureASTNodeFromFQN(self,fqn): if "." in fqn: assert 0, "getattr not supported yet" else: return Name(fqn) def isAMethod(scope,node): return isinstance(node,compiler.ast.Function) and \ isinstance(scope,Class) def convertNodeToMatchObject(node,confidence=100): m = Match() m.sourcenode = node.module.getSourceNode() m.filename = node.filename if isinstance(node,Module): m.lineno = 1 m.colno = 0 elif isinstance(node,Class) or isinstance(node,Function): m.lineno = node.getStartLine() m.colno = node.getColumnOfName() m.confidence = confidence return m bicyclerepair-0.9/bike/query/findDefinition.py0100644000076400007640000002305610014644200017707 0ustar pldpldfrom __future__ import generators from bike.query.common import Match, MatchFinder, \ getScopeForLine, indexToCoordinates, \ translateSourceCoordsIntoASTNode, scanScopeForMatches, \ isAMethod, convertNodeToMatchObject, walkLinesContainingStrings from bike.parsing.parserutils import generateLogicalLines,\ generateLogicalLinesAndLineNumbers, \ splitLogicalLines, makeLineParseable import compiler from compiler.ast import Getattr, Name, AssName, AssAttr from bike.parsing.fastparserast import getRoot, Package, Class, \ Module, Function, Instance import re from bike.query.getTypeOf import getTypeOfExpr, UnfoundType, \ isWordInLine, resolveImportedModuleOrPackage from bike.parsing import visitor from bike.parsing.visitor import walkAndGenerate from bike.parsing.parserutils import makeLineParseable,splitLogicalLines from bike.parsing.newstuff import getSourceNodesContainingRegex from bike.parsing.load import getSourceNode from bike import log class CantFindDefinitionException: pass def findAllPossibleDefinitionsByCoords(filepath,lineno,col): #try: node = translateSourceCoordsIntoASTNode(filepath,lineno,col) #except: # import traceback # traceback.print_exc() if node is None: raise "selected node type not supported" scope = getScopeForLine(getSourceNode(filepath),lineno) match = findDefinitionFromASTNode(scope,node) if match is not None: yield match if isinstance(node,Getattr) and (match is None or match.confidence != 100): root = getRoot() name = node.attrname for match in scanPythonPathForMatchingMethodNames(name,filepath): yield match print >>log.progress,"done" def findDefinitionFromASTNode(scope,node): assert node is not None if isinstance(node,Name) or isinstance(node,AssName): while 1: # try scope children childscope = scope.getChild(node.name) if childscope is not None: return convertNodeToMatchObject(childscope,100) if isinstance(scope,Package): scope = scope.getChild("__init__") # try arguments and assignments match = scanScopeAST(scope,node.name, AssignmentAndFnArgsSearcher(node.name)) if match is not None: return match # try imports match = searchImportedModulesForDefinition(scope,node) if match is not None: return match if not isinstance(scope,Module): # try parent scope scope = scope.getParent() else: break assert isinstance(scope,Module) elif isinstance(node,Getattr) or isinstance(node,AssAttr): exprtype = getTypeOfExpr(scope,node.expr) if not (exprtype is None or isinstance(exprtype,UnfoundType)): if isinstance(exprtype,Instance): exprtype = exprtype.getType() match = findDefinitionOfAttributeFromASTNode(exprtype, node.attrname) else: match = findDefinitionFromASTNode(exprtype, Name(node.attrname)) if match is not None: return match elif isinstance(node,compiler.ast.Function) or \ isinstance(node,compiler.ast.Class): if isAMethod(scope,node): match = findDefinitionOfAttributeFromASTNode(scope, node.name) else: match = findDefinitionFromASTNode(scope,Name(node.name)) if match is not None: return match type = getTypeOfExpr(scope,node) if type is not None and (not isinstance(type,UnfoundType)) and \ (not isinstance(type,Instance)): return convertNodeToMatchObject(type,100) else: return None def findDefinitionOfAttributeFromASTNode(type,name): assert isinstance(type,Class) attrfinder = AttrbuteDefnFinder([type],name) # first scan the method names: for child in type.getChildNodes(): if child.name == name: return convertNodeToMatchObject(child,100) # then scan the method source for attribues for child in type.getChildNodes(): if isinstance(child,Function): try: return scanScopeForMatches(child.module.getSourceNode(), child, attrfinder, name).next() except StopIteration: continue class AttrbuteDefnFinder(MatchFinder): def __init__(self,targetClasses,targetAttribute): self.targetClasses = targetClasses self.targetAttributeName = targetAttribute def visitAssAttr(self, node): for c in node.getChildNodes(): self.visit(c) if node.attrname == self.targetAttributeName: exprtype = getTypeOfExpr(self.scope,node.expr) if isinstance(exprtype,Instance) and \ exprtype.getType() in self.targetClasses: self.appendMatch(self.targetAttributeName) #else: # self.appendMatch(self.targetAttributeName,50) self.popWordsUpTo(node.attrname) def searchImportedModulesForDefinition(scope,node): lines = scope.module.getSourceNode().getLines() for lineno in scope.getImportLineNumbers(): logicalline = getLogicalLine(lines,lineno) logicalline = makeLineParseable(logicalline) ast = compiler.parse(logicalline) class ImportVisitor: def __init__(self,node): self.target = node self.match = None assert isinstance(self.target,Name), \ "Getattr not supported" def visitFrom(self, node): module = resolveImportedModuleOrPackage(scope,node.modname) if module is None: # couldn't find module return if node.names[0][0] == '*': # e.g. from foo import * match = findDefinitionFromASTNode(module,self.target) if match is not None: self.match = match return for name, alias in node.names: if alias is None and name == self.target.name: match = findDefinitionFromASTNode(module,self.target) if match is not None: self.match = match return match = visitor.walk(ast, ImportVisitor(node)).match if match: return match # loop def getLogicalLine(lines,lineno): return generateLogicalLines(lines[lineno-1:]).next() class AssignmentAndFnArgsSearcher(MatchFinder): def __init__(self,name): self.targetname = name self.match = None def visitAssName(self, node): if node.name == self.targetname: idx = self.getNextIndexOfWord(self.targetname) self.match = idx return def visitFunction(self, node): self.popWordsUpTo(node.name) for arg, default in self.zipArgs(node.argnames, node.defaults): if arg == self.targetname: idx = self.getNextIndexOfWord(self.targetname) self.match = idx return self.popWordsUpTo(arg) if default is not None: self.visit(default) self.visit(node.code) def getMatch(self): return self.match # scans for lines containing keyword, and then runs the visitor over # the parsed AST for that line def scanScopeAST(scope,keyword,matchfinder): lines = scope.generateLinesNotIncludingThoseBelongingToChildScopes() match = None for line,linenum in generateLogicalLinesAndLineNumbers(lines): if isWordInLine(keyword, line): doctoredline = makeLineParseable(line) ast = compiler.parse(doctoredline) matchfinder.reset(line) match = visitor.walk(ast,matchfinder).getMatch() if match is not None: column,yoffset = indexToCoordinates(line,match) m = createMatch(scope,linenum + yoffset,column) return m return None def createMatch(scope,lineno,x): m = Match() m.sourcenode = scope.module.getSourceNode() m.filename = m.sourcenode.filename m.lineno = lineno m.colno = x m.confidence = 100 return m # scan for methods globally (from perspective of 'perspectiveFilename') def scanPythonPathForMatchingMethodNames(name, contextFilename): class MethodFinder: def __init__(self,srcnode): self.matches = [] self.srcnode = srcnode def visitFunction(self,node): node = getScopeForLine(self.srcnode, self.lineno) if isinstance(node.getParent(),Class): if node.name == name: self.matches.append(convertNodeToMatchObject(node,50)) for srcnode in getSourceNodesContainingRegex(name,contextFilename): m = MethodFinder(srcnode) walkLinesContainingStrings(srcnode.fastparseroot,m,[name]) for match in m.matches: yield match def getIndexOfWord(line,targetword): words = re.split("(\w+)", line) idx = 0 for word in words: if word == targetword: break idx += len(word) return idx bicyclerepair-0.9/bike/query/findReferences.py0100644000076400007640000002111310014644133017675 0ustar pldpldfrom __future__ import generators from bike.globals import * from bike.parsing.fastparserast import Module, Class, Function, getRoot, Instance from bike.query.common import Match, MatchFinder,\ getScopeForLine, indexToCoordinates, \ translateSourceCoordsIntoASTNode, scanScopeForMatches,\ globalScanForMatches, isAMethod, convertNodeToMatchObject from compiler.ast import AssName,Name,Getattr,AssAttr import compiler from findDefinition import findDefinitionFromASTNode from bike.query.getTypeOf import getTypeOfExpr, UnfoundType from bike.query.relationships import getRootClassesOfHierarchy from bike import log from bike.parsing.load import getSourceNode class CouldntFindDefinitionException(Exception): pass def findReferencesIncludingDefn(filename,lineno,col): return findReferences(filename,lineno,col,1) def findReferences(filename,lineno,col,includeDefn=0): sourcenode = getSourceNode(filename) node = translateSourceCoordsIntoASTNode(filename,lineno,col) assert node is not None scope,defnmatch = getDefinitionAndScope(sourcenode,lineno,node) try: for match in findReferencesIncludingDefn_impl(sourcenode,node, scope,defnmatch): if not includeDefn and match == defnmatch: continue # don't return definition else: yield match except CouldntFindDefinitionException: raise CouldntFindDefinitionException("Could not find definition. Please locate manually (maybe using find definition) and find references from that") def findReferencesIncludingDefn_impl(sourcenode,node,scope,defnmatch): if isinstance(node,Name) or isinstance(node,AssName): return generateRefsToName(node.name,scope,sourcenode,defnmatch) elif isinstance(node,Getattr) or isinstance(node,AssAttr): exprtype = getTypeOfExpr(scope,node.expr) if exprtype is None or isinstance(exprtype,UnfoundType): raise CouldntFindDefinitionException() if isinstance(exprtype,Instance): exprtype = exprtype.getType() return generateRefsToAttribute(exprtype,node.attrname) else: targetname = node.attrname return globalScanForMatches(sourcenode.filename, NameRefFinder(targetname, defnmatch), targetname, ) if match is not None: return match elif isinstance(node,compiler.ast.Function) or \ isinstance(node,compiler.ast.Class): return handleClassOrFunctionRefs(scope, node, defnmatch) else: assert 0,"Seed to references must be Name,Getattr,Function or Class" def handleClassOrFunctionRefs(scope, node, defnmatch): if isAMethod(scope,node): for ref in generateRefsToAttribute(scope,node.name): yield ref else: #yield convertNodeToMatchObject(node,100) yield defnmatch for ref in generateRefsToName(node.name,scope, scope.module.getSourceNode(), defnmatch): yield ref def getDefinitionAndScope(sourcenode,lineno,node): scope = getScopeForLine(sourcenode,lineno) if scope.getStartLine() == lineno and \ scope.matchesCompilerNode(node): # scope is the node return scope.getParent(), convertNodeToMatchObject(scope,100) defnmatch = findDefinitionFromASTNode(scope,node) if defnmatch is None: raise CouldntFindDefinitionException() scope = getScopeForLine(sourcenode,defnmatch.lineno) return scope,defnmatch def generateRefsToName(name,scope,sourcenode,defnmatch): assert scope is not None if isinstance(scope,Function): # search can be limited to scope return scanScopeForMatches(sourcenode,scope, NameRefFinder(name,defnmatch), name) else: return globalScanForMatches(sourcenode.filename, NameRefFinder(name,defnmatch), name) class NameRefFinder(MatchFinder): def __init__(self, targetstr,targetMatch): self.targetstr = targetstr self.targetMatch = targetMatch def visitName(self, node): if node.name == self.targetstr: potentualMatch = findDefinitionFromASTNode(self.scope, node) if potentualMatch is not None and \ potentualMatch == self.targetMatch: self.appendMatch(node.name) self.popWordsUpTo(node.name) visitAssName = visitName def visitFunction(self, node): self.popWordsUpTo(node.name) for arg, default in self.zipArgs(node.argnames, node.defaults): if arg == self.targetstr: self.appendMatch(arg) self.popWordsUpTo(arg) if default is not None: self.visit(default) self.visit(node.code) def visitFrom(self, node): for elem in node.modname.split("."): self.popWordsUpTo(elem) for name, alias in node.names: if name == self.targetstr: if alias is not None: pretendNode = Name(alias) else: pretendNode = Name(name) if findDefinitionFromASTNode(self.scope, pretendNode) \ == self.targetMatch: self.appendMatch(name) self.popWordsUpTo(name) if alias is not None: self.popWordsUpTo(alias) def visitGetattr(self, node): for c in node.getChildNodes(): self.visit(c) if node.attrname == self.targetstr: defn = findDefinitionFromASTNode(self.scope, node) if defn is not None and defn == self.targetMatch: self.appendMatch(node.attrname) self.popWordsUpTo(node.attrname) def visitImport(self, node): for name, alias in node.names: if name.split(".")[-1] == self.targetstr: getattr = self.createGetattr(name) if findDefinitionFromASTNode(self.scope, getattr) == self.targetMatch: self.appendMatch(self.targetstr) for nameelem in name.split("."): self.popWordsUpTo(nameelem) if alias is not None: self.popWordsUpTo(alias) def createGetattr(self,fqn): node = Name(fqn[0]) for name in fqn.split(".")[1:]: node = Getattr(node,name) return node def generateRefsToAttribute(classobj,attrname): rootClasses = getRootClassesOfHierarchy(classobj) attrRefFinder = AttrbuteRefFinder(rootClasses,attrname) for ref in globalScanForMatches(classobj.filename, attrRefFinder, attrname): yield ref print >>log.progress,"Done" class AttrbuteRefFinder(MatchFinder): def __init__(self,rootClasses,targetAttribute): self.rootClasses = rootClasses self.targetAttributeName = targetAttribute def visitGetattr(self, node): for c in node.getChildNodes(): self.visit(c) if node.attrname == self.targetAttributeName: exprtype = getTypeOfExpr(self.scope,node.expr) if isinstance(exprtype,Instance) and \ self._isAClassInTheSameHierarchy(exprtype.getType()): self.appendMatch(self.targetAttributeName) elif isinstance(exprtype,UnfoundType) or \ exprtype is None: # couldn't find type, so not sure self.appendMatch(self.targetAttributeName,50) else: pass # definately not a match self.popWordsUpTo(node.attrname) visitAssAttr = visitGetattr def visitFunction(self,node): # visit methods if node.name == self.targetAttributeName: parentScope = self.scope.getParent() #print parentScope #print self.targetClasses if isinstance(parentScope,Class) and \ self._isAClassInTheSameHierarchy(parentScope): self.appendMatch(node.name) for c in node.getChildNodes(): self.visit(c) def _isAClassInTheSameHierarchy(self,classobj): #return classobj in self.targetClasses targetRootClasses = getRootClassesOfHierarchy(classobj) for rootclass in self.rootClasses: if rootclass in targetRootClasses: return True return False bicyclerepair-0.9/bike/query/getAllRelatedClasses.py0100644000076400007640000000273207562157366021035 0ustar pldpld """ def getAllRelatedClasses(root,classfqn): classobj = getTypeOf(root,classfqn) rootClasses = _getRootClasses(classobj) #print rootClasses relatedClasses = [] + rootClasses for rootClass in rootClasses: relatedClasses += _getAllSubClasses(rootClass,root) return relatedClasses def _getRootClasses(klass): if klass is None: # i.e. dont have base class in our ast return None if klass.getBaseClassNames() == []: # i.e. is a root class return[klass] else: rootclasses = [] for base in klass.getBaseClassNames(): baseclass = getTypeOf(klass,base) rootclass = _getRootClasses(baseclass) if rootclass is None: # base class not in our ast rootclass = [klass] rootclasses+=rootclass return rootclasses def _getAllSubClasses(baseclass, root, subclasses = []): class ClassVisitor: def visitSource(self,node): self.visit(node.fastparseroot) def visitClass(self, node): for basename in node.getBaseClassNames(): if basename.find(baseclass.name) != -1 and \ getTypeOf(node,basename) == baseclass: subclasses.append(node) _getAllSubClasses(node,root,subclasses) for child in node.getChildNodes(): self.visit(child) walk(root, ClassVisitor()) return subclasses """ bicyclerepair-0.9/bike/query/getPackageDependencies.py0100664000076400007640000000021310007754253021324 0ustar pldpld#!/usr/bin/env python # fileInPackage is the filename of a file in the package hierarchy def getPackageDependencies(fileInPackage): bicyclerepair-0.9/bike/query/getReferencesToModule.py0100644000076400007640000000450310010141505021200 0ustar pldpldfrom __future__ import generators from bike.query.common import Match, globalScanForMatches, getScopeForLine, MatchFinder from getTypeOf import getTypeOf, getTypeOfExpr import compiler import re def getReferencesToModule(root, fqn): modulename = fqn.split(".")[-1] moduleobj = getTypeOf(root, fqn) moduleRefFinder = ModuleRefFinder(moduleobj) for ref in globalScanForMatches(moduleRefFinder, modulename): yield ref class ModuleRefFinder(MatchFinder): def __init__(self, targetmodule): self.targetmodule = targetmodule def visitName(self, node): if node.name == self.targetmodule.name: if getTypeOfExpr(self.scope, node) == self.targetmodule: self.appendMatch(node.name) self.popWordsUpTo(node.name) def visitImport(self, node): for name, alias in node.names: if name.split(".")[-1] == self.targetmodule.name: if getTypeOf(self.scope, name) == self.targetmodule: self.appendMatch(self.targetmodule.name) for nameelem in name.split("."): self.popWordsUpTo(nameelem) if alias is not None: self.popWordsUpTo(alias) def visitGetattr(self, node): for c in node.getChildNodes(): self.visit(c) if node.attrname == self.targetmodule.name: if getTypeOfExpr(self.scope, node) == self.targetmodule: self.appendMatch(self.targetmodule.name) self.popWordsUpTo(node.attrname) def visitFrom(self, node): for elem in node.modname.split("."): if elem == self.targetmodule.name: getTypeOf(self.scope, elem) == self.targetmodule self.appendMatch(self.targetmodule.name) self.popWordsUpTo(elem) for name, alias in node.names: if name == self.targetmodule.name: if alias and \ getTypeOf(self.scope, alias) == self.targetmodule: self.appendMatch(self.targetmodule.name) elif getTypeOf(self.scope, name) == self.targetmodule: self.appendMatch(self.targetmodule.name) if name != "*": self.popWordsUpTo(name) if alias is not None: self.popWordsUpTo(alias) bicyclerepair-0.9/bike/query/getTypeOf.py0100644000076400007640000002743310014636361016700 0ustar pldpld# getTypeOf(scope,fqn) and getTypeOfExpr(scope,ast) from bike.parsing.fastparserast import Class, Function, Module, Root, getRoot, Package, Instance, getModule from bike.parsing.parserutils import generateLogicalLines, makeLineParseable,splitLogicalLines, makeLineParseable from bike.parsing import visitor from bike import log from bike.parsing.newstuff import getModuleOrPackageUsingFQN from bike.parsing.pathutils import getPackageBaseDirectory from bike.parsing.load import Cache import os import re import compiler # used if an assignment exists, but cant find type # e.g. a = SomeFunctionNotLoaded() # (as opposed to 'None' if cant find an assignment) class UnfoundType: pass getTypeOfStack = [] # name is the fqn of the reference, scope is the scope ast object from # which the question is being asked. # returns an fastparser-ast object representing the type # or None if type not found def getTypeOf(scope, fqn): if isinstance(scope, Root): assert False, "Can't use getTypeOf to resolve from Root. Use getModuleOrPackageUsingFQN instead" #print "getTypeOf:"+fqn+" -- "+str(scope) #print #print str(getTypeOfStack) #print if (fqn,scope) in getTypeOfStack: # loop protection return None # this is crap! hashcode = str(scope)+fqn try: getTypeOfStack.append((fqn,scope)) try: type = Cache.instance.typecache[hashcode] except KeyError: type = getTypeOf_impl(scope, fqn) Cache.instance.typecache[hashcode] = type return type finally: del getTypeOfStack[-1] def getTypeOf_impl(scope, fqn): #print "getTypeOf_impl",scope,fqn if fqn == "None": return None if "."in fqn: rcdr = ".".join(fqn.split(".")[:-1]) rcar = fqn.split(".")[-1] newscope = getTypeOf(scope,rcdr) if newscope is not None: return getTypeOf(newscope, rcar) else: #print "couldnt find "+rcdr+" in "+str(scope) pass assert scope is not None #assert not ("." in fqn) if isinstance(scope,UnfoundType): return UnfoundType() if isinstance(scope, Package): #assert 0,scope return handlePackageScope(scope, fqn) elif isinstance(scope,Instance): return handleClassInstanceAttribute(scope, fqn) else: return handleModuleClassOrFunctionScope(scope,fqn) def handleModuleClassOrFunctionScope(scope,name): if name == "self" and isinstance(scope,Function) and \ isinstance(scope.getParent(),Class): return Instance(scope.getParent()) matches = [c for c in scope.getChildNodes()if c.name == name] if matches != []: return matches[0] type = scanScopeSourceForType(scope, name) if type != None: return type #print "name = ",name,"scope = ",scope type = getImportedType(scope, name) # try imported types #print "type=",type if type != None: return type parentScope = scope.getParent() while isinstance(parentScope,Class): # don't search class scope, since this is not accessible except # through self (is this true?) parentScope = parentScope.getParent() if not (isinstance(parentScope,Package) or isinstance(parentScope,Root)): return getTypeOf(parentScope, name) def handleClassInstanceAttribute(instance, attrname): theClass = instance.getType() # search methods and inner classes match = theClass.getChild(attrname) if match: return match #search methods for assignments with self.foo getattrs for child in theClass.getChildNodes(): if not isinstance(child,Function): continue res = scanScopeAST(child,attrname, SelfAttributeAssignmentVisitor(child,attrname)) if res is not None: return res def handlePackageScope(package, fqn): #print "handlePackageScope",package,fqn child = package.getChild(fqn) if child: return child if isinstance(package,Root): return getModuleOrPackageUsingFQN(fqn) # try searching the fs node = getModuleOrPackageUsingFQN(fqn,package.path) if node: return node # try the package init module initmod = package.getChild("__init__") if initmod is not None: type = getImportedType(initmod, fqn) if type: return type # maybe fqn is absolute return getTypeOf(getRoot(), fqn) wordRE = re.compile("\w+") def isWordInLine(word, line): if line.find(word) != -1: words = wordRE.findall(line) if word in words: return 1 return 0 def getImportedType(scope, fqn): lines = scope.module.getSourceNode().getLines() for lineno in scope.getImportLineNumbers(): logicalline = generateLogicalLines(lines[lineno-1:]).next() logicalline = makeLineParseable(logicalline) ast = compiler.parse(logicalline) match = visitor.walk(ast, ImportVisitor(scope,fqn)).match if match: return match #else loop class ImportVisitor: def __init__(self,scope,fqn): self.match = None self.targetfqn = fqn self.scope = scope def visitImport(self, node): # if target fqn is an import, then it must be a module or package for name, alias in node.names: if name == self.targetfqn: self.match = resolveImportedModuleOrPackage(self.scope,name) elif alias is not None and alias == self.targetfqn: self.match = resolveImportedModuleOrPackage(self.scope,name) def visitFrom(self, node): if node.names[0][0] == '*': # e.g. from foo import * if not "."in self.targetfqn: module = resolveImportedModuleOrPackage(self.scope, node.modname) if module: self.match = getTypeOf(module, self.targetfqn) else: for name, alias in node.names: if alias == self.targetfqn or \ (alias is None and name == self.targetfqn): scope = resolveImportedModuleOrPackage(self.scope, node.modname) if scope is not None: if isinstance(scope,Package): self.match = getModuleOrPackageUsingFQN(name,scope.path) else: assert isinstance(scope,Module) self.match = getTypeOf(scope, name) class TypeNotSupportedException: def __init__(self,msg): self.msg = msg def __str__(self): return self.msg # attempts to evaluate the type of the expression def getTypeOfExpr(scope, ast): if isinstance(ast, compiler.ast.Name): return getTypeOf(scope, ast.name) elif isinstance(ast, compiler.ast.Getattr) or \ isinstance(ast, compiler.ast.AssAttr): # need to do this in order to match foo.bah.baz as # a string in import statements fqn = attemptToConvertGetattrToFqn(ast) if fqn is not None: return getTypeOf(scope,fqn) expr = getTypeOfExpr(scope, ast.expr) if expr is not None: attrnametype = getTypeOf(expr, ast.attrname) return attrnametype return None elif isinstance(ast, compiler.ast.CallFunc): node = getTypeOfExpr(scope,ast.node) if isinstance(node,Class): return Instance(node) elif isinstance(node,Function): return getReturnTypeOfFunction(node) else: #raise TypeNotSupportedException, \ # "Evaluation of "+str(ast)+" not supported. scope="+str(scope) print >> log.warning, "Evaluation of "+str(ast)+" not supported. scope="+str(scope) return None def attemptToConvertGetattrToFqn(ast): fqn = ast.attrname ast = ast.expr while isinstance(ast,compiler.ast.Getattr): fqn = ast.attrname + "." + fqn ast = ast.expr if isinstance(ast,compiler.ast.Name): return ast.name + "." + fqn else: return None getReturnTypeOfFunction_stack = [] def getReturnTypeOfFunction(function): if function in getReturnTypeOfFunction_stack: # loop protection return None try: getReturnTypeOfFunction_stack.append(function) return getReturnTypeOfFunction_impl(function) finally: del getReturnTypeOfFunction_stack[-1] def getReturnTypeOfFunction_impl(function): return scanScopeAST(function,"return",ReturnTypeVisitor(function)) # does parse of scope sourcecode to deduce type def scanScopeSourceForType(scope, name): return scanScopeAST(scope,name,AssignmentVisitor(scope,name)) # scans for lines containing keyword, and then runs the visitor over # the parsed AST for that line def scanScopeAST(scope,keyword,astvisitor): lines = scope.getLinesNotIncludingThoseBelongingToChildScopes() src = ''.join(lines) match = None #print "scanScopeAST:"+str(scope) for line in splitLogicalLines(src): if isWordInLine(keyword, line): #print "scanning for "+keyword+" in line:"+line[:-1] doctoredline = makeLineParseable(line) ast = compiler.parse(doctoredline) match = visitor.walk(ast,astvisitor).getMatch() if match: return match return match class AssignmentVisitor: def __init__(self,scope,targetName): self.match=None self.scope = scope self.targetName = targetName def getMatch(self): return self.match def visitAssign(self,node): if isinstance(node.expr,compiler.ast.CallFunc): for assnode in node.nodes: if isinstance(assnode,compiler.ast.AssName) and \ assnode.name == self.targetName: self.match = getTypeOfExpr(self.scope,node.expr) if self.match is None: self.match = UnfoundType() class SelfAttributeAssignmentVisitor: def __init__(self,scope,targetName): self.match=None self.scope = scope self.targetName = targetName def getMatch(self): return self.match def visitAssign(self,node): if isinstance(node.expr,compiler.ast.CallFunc): for assnode in node.nodes: if isinstance(assnode,compiler.ast.AssAttr) and \ isinstance(assnode.expr,compiler.ast.Name) and \ assnode.expr.name == "self" and \ assnode.attrname == self.targetName: self.match = getTypeOfExpr(self.scope,node.expr) #print "here!",self.match.getType().fqn class ReturnTypeVisitor: def __init__(self,fn): self.match=None self.fn = fn def getMatch(self): return self.match def visitReturn(self,node): try: self.match = getTypeOfExpr(self.fn,node.value) except TypeNotSupportedException, ex: pass def resolveImportedModuleOrPackage(scope,fqn): # try searching from directory containing scope module path = os.path.dirname(scope.module.filename) node = getModuleOrPackageUsingFQN(fqn,path) if node is not None: return node # try searching in same package hierarchy basedir = getPackageBaseDirectory(scope.module.filename) if fqn.split('.')[0] == os.path.split(basedir)[-1]: # base package in fqn matches base directory restOfFqn = ".".join(fqn.split('.')[1:]) node = getModuleOrPackageUsingFQN(restOfFqn,basedir) if node is not None: return node # try searching the python path node = getModuleOrPackageUsingFQN(fqn) if node is not None: return node bicyclerepair-0.9/bike/query/relationships.py0100644000076400007640000000205210010513151017627 0ustar pldpld# queries to do with module/class/function relationships from __future__ import generators from bike.globals import * from getTypeOf import getTypeOf, getTypeOfExpr from bike.parsing.newstuff import generateModuleFilenamesInPythonPath, generateModuleFilenamesInPackage, getPythonPath from bike.parsing.pathutils import getPackageBaseDirectory from bike.query.common import MatchFinder, walkLinesContainingStrings, getScopeForLine from bike import log from bike.parsing.fastparserast import Module import re def getRootClassesOfHierarchy(klass): if klass is None: # i.e. dont have base class in our ast return None if klass.getBaseClassNames() == []: # i.e. is a root class return [klass] else: rootclasses = [] for base in klass.getBaseClassNames(): baseclass = getTypeOf(klass,base) rootclass = getRootClassesOfHierarchy(baseclass) if rootclass is None: # base class not in our ast rootclass = [klass] rootclasses+=rootclass return rootclasses bicyclerepair-0.9/bike/query/setpath.py0100644000076400007640000000033407722322025016432 0ustar pldpldimport sys,os if not os.path.abspath("..") in sys.path: from bike import log print >> log.warning, "Appending to the system path. This should only happen in unit tests" sys.path.append(os.path.abspath("..")) bicyclerepair-0.9/bike/query/test_common.py0100755000076400007640000000657510012403325017316 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os import compiler from bike.testutils import * from bike.parsing.load import getSourceNode from bike.parsing.fastparser import fastparser from bike.parsing.fastparserast import Module, Class from common import indexToCoordinates, getScopeForLine, walkLinesContainingStrings, translateSourceCoordsIntoASTNode class TestGetScopeForLine(BRMTestCase): def test_worksWithFunctionScope(self): src = trimLines(""" class a: def foo(): pass """) node = createAST(src) self.assertEqual(getScopeForLine(node,3).name,"foo") def test_worksWithModuleScope(self): src = trimLines(""" class TheClass: pass a = TheClass() """) node = createAST(src) assert isinstance(getScopeForLine(node,3),Module) def test_worksWithInlineClass(self): src = trimLines(""" class TheClass: pass""") node = createAST(src) assert isinstance(getScopeForLine(node,1),Class) class TestIndexToCoordinates(BRMTestCase): def test_worksOnSingleLineString(self): src = trimLines(''' foo bah ''') x,y = indexToCoordinates(src,src.index("bah")) self.assertEqual(x,4) self.assertEqual(y,0) x,y = indexToCoordinates(src,src.index("foo")) self.assertEqual(x,0) self.assertEqual(y,0) def test_worksOnMultilLineString(self): src = trimLines(''' foo bah baz boh ''') x,y = indexToCoordinates(src,src.index("boh")) self.assertEqual(x,4) self.assertEqual(y,1) class TestTranslateSourceCoordsIntoASTNode(BRMTestCase): def test_worksOnImport(self): src = trimLines(''' from foo import bar, baz ''') createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") node = translateSourceCoordsIntoASTNode(filename, 1, 17) assert node.name == 'bar' def test_worksOnMultiline(self): src = trimLines(""" def foo(x, y, z): return x*y*z """) createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") node = translateSourceCoordsIntoASTNode(filename, 3, 8) assert node.name == 'z' class TestMatchFinder(BRMTestCase): def test_visitLambda(self): from common import MatchFinder finder = MatchFinder() src = '''x = lambda a, b, c=None, d=None: (a + b) and c or d''' ast = compiler.parse(src) finder.reset(src) compiler.walk(ast, finder) class TestWalkLinesContainingStrings(BRMTestCase): def test_walksClasses(self): src=trimLines(""" class TestClass(a, baseclass): pass """) class MyWalker: def visitClass(self, node): self.basenames = [] for name in node.bases: self.basenames.append(name.name) writeTmpTestFile(src) srcnode = getSourceNode(tmpfile) walker = MyWalker() walkLinesContainingStrings(srcnode.fastparseroot,walker, "baseclass") self.assertEqual(["a","baseclass"],walker.basenames) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_findDefinition.py0100755000076400007640000005164510014635646020774 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.query.findDefinition import findAllPossibleDefinitionsByCoords from bike.query.getTypeOf import getTypeOf,resolveImportedModuleOrPackage from bike.parsing.newstuff import getModuleOrPackageUsingFQN from bike.parsing.fastparserast import getRoot from bike.testutils import * class TestFindDefinitionByCoords(BRMTestCase): def test_findsClassRef(self): src=trimLines(""" class TheClass: pass a = TheClass() """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),3,6)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def tests_findsMethodRef(self): src=trimLines(""" class TheClass: def theMethod(self): pass a = TheClass() a.theMethod() """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),5,3)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 2 assert defn[0].colno == 8 assert defn[0].confidence == 100 def test_returnsOtherMethodsWithSameName(self): src=trimLines(""" class TheClass: def theMethod(self): pass a = SomeOtherClass() a.theMethod() """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),5,3)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 2 assert defn[0].colno == 8 assert defn[0].confidence == 50 def test_findsTemporaryDefinition(self): src=trimLines(""" a = 3 b = a + 1 """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),2,4)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsArgumentDefinition(self): src=trimLines(""" def someFunction(a): b = a + 1 """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),2,8)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 1 assert defn[0].colno == 17 assert defn[0].confidence == 100 def test_findsClassInstanceDefinition(self): src=trimLines(""" class TheClass(): pass a = TheClass() print a """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),4,6)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 3 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsDefinitionInParentScope(self): src=trimLines(""" a = 3 def foo(self): b = a + 1 """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),3,8)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsDefinitionWithinFunction(self): src=trimLines(""" def foo(yadda): a = someFunction() print a """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),3,10)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 2 assert defn[0].colno == 4 assert defn[0].confidence == 100 def test_findsDefinitionFromSubsequentAssignment(self): src=trimLines(""" def foo(yadda): a = 3 print a a = 5 """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),4,4)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 2 assert defn[0].colno == 4 assert defn[0].confidence == 100 def test_findsDefinitionFromDefinition(self): src=trimLines(""" def foo(yadda): a = 3 print a a = 5 """) createSourceNodeAt(src,"mymodule") defn = [x for x in findAllPossibleDefinitionsByCoords(os.path.abspath("mymodule.py"),4,4)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 2 assert defn[0].colno == 4 assert defn[0].confidence == 100 def test_findsClassRefUsingFromImportStatement(self): src=trimLines(""" from a.b.bah import TheClass """) classsrc=trimLines(""" class TheClass: pass """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(classsrc, "a.b.bah") module = getModuleOrPackageUsingFQN("a.foo") filename = os.path.abspath(os.path.join("a","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,1,21)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def test_findsVariableRefUsingFromImportStatement(self): importsrc=trimLines(""" from a.b.bah import mytext print mytext """) src=trimLines(""" mytext = 'hello' """) root = createSourceNodeAt(importsrc,"a.foo") root = createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,6)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsVariableRefUsingImportStatement(self): importsrc=trimLines(""" import a.b.bah print a.b.bah.mytext """) src=trimLines(""" mytext = 'hello' """) root = createSourceNodeAt(importsrc,"a.foo") root = createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,14)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsVariableRefUsingFromImportStarStatement(self): importsrc=trimLines(""" from a.b.bah import * print mytext """) src=trimLines(""" mytext = 'hello' """) createSourceNodeAt(importsrc,"a.foo") createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,6)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsVariableRefUsingFromPackageImportModuleStatement(self): importsrc=trimLines(""" from a.b import bah print bah.mytext """) src=trimLines(""" mytext = 'hello' """) root = createSourceNodeAt(importsrc,"a.b.foo") root = createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","b","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,10)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsImportedVariableRefInAFunctionArg(self): importsrc=trimLines(""" from a.b import bah someFunction(bah.mytext) """) src=trimLines(""" mytext = 'hello' """) root = createSourceNodeAt(importsrc,"a.b.foo") root = createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","b","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,17)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsVariableRefUsingFromImportStatementInFunction(self): importsrc=trimLines(""" def foo: from a.b.bah import mytext print mytext """) src=trimLines(""" mytext = 'hello' """) root = createSourceNodeAt(importsrc,"a.foo") root = createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,3,10)] assert defn[0].filename == os.path.abspath(os.path.join("a","b","bah.py")) assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsVariableRefByImportingModule(self): importsrc=trimLines(""" import a.b.bah print a.b.bah.mytext """) src=trimLines(""" mytext = 'hello' """) defn = self.helper(importsrc, src, 2, 14) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def test_findsVariableRefByImportingModuleWithFrom(self): importsrc=trimLines(""" from a.b import bah someFunction(bah.mytext) """) src=trimLines(""" mytext = 'hello' """) defn = self.helper(importsrc, src, 2, 17) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 1 assert defn[0].colno == 0 assert defn[0].confidence == 100 def helper(self, src, classsrc, line, col): try: createPackageStructure(src,classsrc) filename = pkgstructureFile1 #Root(None,None,[pkgstructureRootDir]) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,line,col)] finally: removePackageStructure() return defn def test_doesntfindVariableRefOfUnimportedModule(self): importsrc=trimLines(""" # a.b.bah not imported print a.b.bah.mytext """) src=trimLines(""" mytext = 'hello' """) root = createSourceNodeAt(importsrc,"a.b.foo") root = createSourceNodeAt(src, "a.b.bah") filename = os.path.abspath(os.path.join("a","b","foo.py")) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,14)] self.assertEqual(defn,[]) def test_findsSelfAttributeDefinition(self): src=trimLines(""" class MyClass: def __init__(self): self.a = 'hello' def myMethod(self): print self.a """) root = createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") defn = [x for x in findAllPossibleDefinitionsByCoords(filename,5,18)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 3 assert defn[0].colno == 12 assert defn[0].confidence == 100 def test_findsSelfAttributeDefinitionFromSamePlace(self): src=trimLines(""" class MyClass: def __init__(self): self.a = 'hello' def myMethod(self): print self.a """) root = createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") defn = [x for x in findAllPossibleDefinitionsByCoords(filename,3,12)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 3 assert defn[0].colno == 12 assert defn[0].confidence == 100 def test_findsSelfAttributeDefinition(self): src=trimLines(""" class MyClass: def someOtherFn(self): pass def load(self, source): # fastparser ast self.fastparseroot = fastparser(source,self.modulename) """) root = createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") defn = [x for x in findAllPossibleDefinitionsByCoords(filename,6,14)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 6 assert defn[0].colno == 13 assert defn[0].confidence == 100 def test_findsDefnOfInnerClass(self): src = trimLines(""" class TheClass: class TheClass: pass a = TheClass.TheClass() """) root = createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") defn = [x for x in findAllPossibleDefinitionsByCoords(filename,4,14)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 2 assert defn[0].colno == 10 assert defn[0].confidence == 100 def test_findsDefnOfOuterClass(self): src = trimLines(""" class TheClass: class TheClass: pass a = TheClass.TheClass() """) root = createSourceNodeAt(src,"mymodule") filename = os.path.abspath("mymodule.py") defn = [x for x in findAllPossibleDefinitionsByCoords(filename,4,4)] assert defn[0].filename == os.path.abspath("mymodule.py") assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def test_findsClassDeclaredIn__init__Module(self): importsrc=trimLines(""" class TheClass: pass """) src=trimLines(""" from a import TheClass c = TheClass() """) root = createSourceNodeAt(importsrc,"a.__init__") root = createSourceNodeAt(src, "mymodule") filename = os.path.abspath("mymodule.py") defn = [x for x in findAllPossibleDefinitionsByCoords(filename,2,6)] assert defn[0].filename == os.path.abspath(os.path.join("a", "__init__.py")) assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 class TestFindDefinitionUsingFiles(BRMTestCase): def test_findsASimpleDefinitionUsingFiles(self): src=trimLines(""" class TheClass: pass a = TheClass() """) writeTmpTestFile(src) defn = [x for x in findAllPossibleDefinitionsByCoords(tmpfile,3,6)] assert defn[0].filename == tmpfile assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def test_findsDefinitionInAnotherModuleUsingFiles(self): src=trimLines(""" from a.b.bah import TheClass """) classsrc=trimLines(""" class TheClass: pass """) defn = self.helper(src, classsrc, 1, 21) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def test_findsDefinitionInAnotherRelativeModuleUsingFiles(self): src=trimLines(""" from b.bah import TheClass """) classsrc=trimLines(""" class TheClass: pass """) defn = self.helper(src, classsrc,1,21) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def test_findsMethodDefinitionInAnotherModuleUsingFiles(self): src=trimLines(""" from b.bah import TheClass a = TheClass() a.theMethod() """) classsrc=trimLines(""" class TheClass: def theMethod(self): pass """) defn = self.helper(src, classsrc, 3, 2) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 2 assert defn[0].colno == 8 assert defn[0].confidence == 100 def test_findsDefinitonOfMethodWhenUseIsOnAMultiLine(self): classsrc=trimLines(""" class TheClass: def theMethod(self): pass """) src=trimLines(""" from b.bah import TheClass a = TheClass() i,j = (32, a.theMethod()) # <--- find me! something=somethingelse """) defn = self.helper(src, classsrc, 4, 9) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 2 assert defn[0].colno == 8 assert defn[0].confidence == 100 def test_findsDefinitionWhenUseIsOnAMultilineAndNextLineBalancesBrace(self): classsrc=trimLines(""" class TheClass: def theMethod(self): pass """) src=trimLines(""" from b.bah import TheClass c = TheClass() f1, f2 = (c.func1, c.theMethod) f1, f2 = (c.func1, c.theMethod) """) defn = self.helper(src, classsrc, 4, 10) self.assertEqual(pkgstructureFile2,defn[0].filename) self.assertEqual(2,defn[0].lineno) self.assertEqual(8,defn[0].colno) self.assertEqual(100,defn[0].confidence) def test_worksIfFindingDefnOfRefInSlashMultiline(self): classsrc=trimLines(""" class TheClass: def theMethod(self): pass """) src=trimLines(""" from b.bah import TheClass c = TheClass() f1, f2 = c.func1 \\ ,c.theMethod """) defn = self.helper(src, classsrc, 4, 10) self.assertEqual(pkgstructureFile2,defn[0].filename) self.assertEqual(2,defn[0].lineno) self.assertEqual(8,defn[0].colno) self.assertEqual(100,defn[0].confidence) def test_findsDefnInSameNonPackageDirectory(self): try: getRoot().pythonpath = [] # clear the python path classsrc = trimLines(""" def testFunction(): print 'hello' """) src = trimLines(""" from baz import testFunction """) writeTmpTestFile(src) newtmpfile = os.path.join(tmproot,"baz.py") writeFile(newtmpfile, classsrc) refs = [x for x in findAllPossibleDefinitionsByCoords(tmpfile,1,16)] assert refs[0].filename == newtmpfile assert refs[0].lineno == 1 finally: os.remove(newtmpfile) deleteTmpTestFile() def test_findsDefnInPackageSubDirectoryAndRootNotInPath(self): src=trimLines(""" from b.bah import TheClass """) classsrc=trimLines(""" class TheClass: def theMethod(self): pass """) getRoot().pythonpath = [] # clear the python path defn = self.helper(src, classsrc, 1, 18) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def test_findsDefnInSamePackageHierarchyAndRootNotInPath(self): src=trimLines(""" from a.b.bah import TheClass """) classsrc=trimLines(""" class TheClass: def theMethod(self): pass """) getRoot().pythonpath = [] # clear the python path defn = self.helper(src, classsrc, 1, 20) assert defn[0].filename == pkgstructureFile2 assert defn[0].lineno == 1 assert defn[0].colno == 6 assert defn[0].confidence == 100 def helper(self, src, classsrc, line, col): try: createPackageStructure(src,classsrc) filename = pkgstructureFile1 #Root(None,None,[pkgstructureRootDir]) defn = [x for x in findAllPossibleDefinitionsByCoords(filename,line,col)] finally: removePackageStructure() return defn if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_findReferences.py0100755000076400007640000003572610012405432020751 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * #from bike.testutils import trimLines, createSourceNodeAt, \ # createSourceNodeAt_old, BRMTestCase from bike import testdata from findReferences import findReferences, findReferencesIncludingDefn from bike.query.getTypeOf import getTypeOf class helpers: def helper(self,src,lineno,colno): writeTmpTestFile(src) refs = [x for x in findReferences(tmpfile,lineno,colno)] return refs def helper2(self,src,lineno,colno): writeTmpTestFile(src) refs = [x for x in findReferencesIncludingDefn(tmpfile,lineno, colno)] return refs def helper3(self, src, importedsrc, line, col): createPackageStructure(src,importedsrc) filename = pkgstructureFile2 refs = [x for x in findReferences(filename,line,col) if x.confidence == 100] return refs def helper4(self, src, importedsrc, line, col): createPackageStructure(src,importedsrc) filename = pkgstructureFile1 refs = [x for x in findReferences(filename,line,col) if x.confidence == 100] return refs class TestFindReferences(BRMTestCase,helpers): def test_findsSimpleReferencesGivenAssignment(self): src=trimLines(""" def foo(): a = 3 print a """) refs = self.helper(src,3,10) assert refs[0].filename == tmpfile assert refs[0].lineno == 3 assert refs[0].colno == 10 assert refs[0].confidence == 100 def test_findsSimpleReferencesGivenReference(self): src=trimLines(""" def foo(): a = 3 print a """) refs = self.helper2(src,3,10) assert refs[0].filename == tmpfile assert refs[0].lineno == 2 assert refs[0].colno == 4 assert refs[0].confidence == 100 def test_findsReferencesToOtherAssignments(self): src=trimLines(""" def foo(): a = 3 a = 4 """) refs = self.helper(src,2,4) assert refs[0].filename == tmpfile assert refs[0].lineno == 3 assert refs[0].colno == 4 assert refs[0].confidence == 100 def test_findsFunctionArg(self): src=trimLines(""" def foo(a): print a """) refs = self.helper2(src,2,10) assert refs[0].filename == tmpfile assert refs[0].lineno == 1 assert refs[0].colno == 8 assert refs[0].confidence == 100 def test_findsFunctionArgWithDefault(self): src=trimLines(""" def foo(a=None, b=None): print a, b """) refs = self.helper(src,1,4) self.assertEquals(refs, []) def test_findsFunctionArgWithDefault2(self): src=trimLines(""" def foo(a=None, b=None): print a, b """) refs = self.helper2(src,2,13) assert refs[0].filename == tmpfile assert refs[0].lineno == 1 assert refs[0].colno == 16 assert refs[0].confidence == 100 def test_findsReferencesGivenFunctionArg(self): src=trimLines(""" def foo(a): print a """) refs = self.helper(src,1,8) assert refs[0].filename == tmpfile assert refs[0].lineno == 2 assert refs[0].colno == 10 assert refs[0].confidence == 100 def test_findsVariableRefInImportStatementUsingFromImportStatement(self): importsrc=trimLines(""" from a.b.bah import mytext """) src=trimLines(""" mytext = 'hello' """) refs = self.helper3(importsrc,src,1,1) assert refs[0].filename == pkgstructureFile1 assert refs[0].lineno == 1 assert refs[0].colno == 20 assert refs[0].confidence == 100 def test_findsVariableRefUsingFromImportStatement(self): importsrc=trimLines(""" from a.b.bah import mytext print mytext """) src=trimLines(""" mytext = 'hello' """) refs = self.helper3(importsrc,src,1,1) assert refs[0].filename == pkgstructureFile1 assert refs[1].lineno == 2 assert refs[1].colno == 6 assert refs[1].confidence == 100 def test_findsImportedVariableRefInAFunctionArg(self): importsrc=trimLines(""" from a.b import bah someFunction(bah.mytext) """) src=trimLines(""" mytext = 'hello' """) refs = self.helper3(importsrc,src,1,1) assert refs[0].filename == pkgstructureFile1 assert refs[0].lineno == 2 assert refs[0].colno == 17 assert refs[0].confidence == 100 def test_getsReferenceOfSimpleMethodCall(self): src = trimLines(""" from b.bah import TheClass a = TheClass() a.theMethod() """) refs = self.helper4(src,testdata.TheClass,3,2) assert refs[0].filename == pkgstructureFile1 self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,2) def test_findsRefToSelfAttribute(self): src=trimLines(""" class MyClass: def __init__(self): self.a = 'hello' def myMethod(self): print self.a """) refs = self.helper(src,3,12) assert refs[0].filename == tmpfile assert refs[0].lineno == 5 assert refs[0].colno == 18 assert refs[0].confidence == 100 class FindReferencesToMethod(BRMTestCase,helpers): def test_findsReferenceOfSimpleMethodCall(self): src = trimLines(""" from b.bah import TheClass a = TheClass() a.theMethod() """) refs = self.helper3(src,testClass,2,8) assert refs[0].filename == pkgstructureFile1 self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,2) self.assertEqual(refs[0].colno,2) def test_getsReferenceOfMethodCallFromClassImportedWithAlias(self): src = trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.theMethod() """) refs = self.helper3(src,testClass,2,8) assert refs[0].filename == pkgstructureFile1 self.assertEqual(refs[0].lineno,5) self.assertEqual(refs[0].colno,6) def test_getsReferenceOfMethodCallWhenInstanceReturnedByFunction(self): src = trimLines(""" from b.bah import TheClass def foo(): return TheClass() a = foo() a.theMethod() """) refs = self.helper3(src,testClass,2,8) assert refs[0].filename == pkgstructureFile1 self.assertEqual(refs[0].lineno,6) self.assertEqual(refs[0].colno,2) def test_getsReferenceOfMethodCallInSameClass(self): src = trimLines(""" class TheClass: def theMethod(self): pass def anotherMethod(self): self.theMethod() """) refs = self.helper4(src,testClass,2,8) assert refs[0].filename == pkgstructureFile1 self.assertEqual(refs[0].lineno,5) self.assertEqual(refs[0].colno,13) def test_getsReferenceOfMethodOnBaseClassInstance(self): src = trimLines(""" class root: def theMethod(self): pass class a(root): def theMethod(self): pass class b(root): pass class TheClass(b): def theMethod(self): pass rootinstance = root() rootinstance.theMethod() """) refs = self.helper4(src,"pass",2,8) self.assertEqual(refs[2].filename,pkgstructureFile1) self.assertEqual(refs[2].lineno,17) self.assertEqual(refs[2].colno,13) def test_doesntGetReferenceToMethodWhenObjectCreatedInChildScopeToMethodReference(self): src = trimLines(""" from b.bah import TheClass a = AnotherClass() def foo(): a = TheClass() a.theMethod() """) refs = self.helper3(src,testClass,2,8) assert refs == [] def test_renamesMethodReferenceOfInstanceCreatedInSubsequentFunction(self): src = trimLines(""" class TheClass: def theMethod(): pass class NotTheClass: def theMethod(): pass def foo(): a = bah() a.theMethod() def bah(): return TheClass() """) refs = self.helper4(src,"pass",2,8) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,10) self.assertEqual(refs[0].colno,6) def test_getsReferenceInMiddleOfBiggerCompoundCall(self): src = trimLines(""" class TheClass: def theMethod(self): return AnotherClass() TheClass().theMethod().anotherMethod() """) refs = self.helper4(src,"pass",2,8) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,11) self.assertEqual(refs[0].colend,20) def test_doesntBarfWhenObjectIsArrayMember(self): src = trimLines(""" class TheClass: def theMethod(self): pass a[0] = TheClass() a[0].theMethod() """) refs = self.helper4(src,"pass",2,8) # should get to here without exception class FindReferencesToClass(BRMTestCase, helpers): def test_returnsEmptyListIfNoReferences(self): src = trimLines(""" class MyClass: pass a = TheClass() """) refs = self.helper4(src,"pass",1,6) assert refs == [] def test_findsSimpleReferenceInSameModule(self): src = trimLines(""" class TheClass: pass a = TheClass() """) refs = self.helper4(src,"pass",1,6) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,4) self.assertEqual(refs[0].confidence,100) def test_doesntBarfOnSingleLineSourceWithInlineClass(self): src=trimLines(""" from b.bah import TheClass a = TheClass() """) refs = self.helper3(src,"class TheClass: pass",1,6) assert refs != [] def test_findsReferenceToClassImportedInSameClassScope(self): src=trimLines(""" class AnotherClass: from b.bah import TheClass TheClass.baz = 0 """) refs = self.helper3(src,"class TheClass: pass",1,6) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,22) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[1].lineno,3) self.assertEqual(refs[1].colno,4) def testFindsClassReferenceWhenScopeIsSameNameAsClass(self): src = trimLines(""" class TheClass: class TheClass: pass a = TheClass.TheClass() """) refs = self.helper4(src,"pass",2,10) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,4) self.assertEqual(refs[0].colno,13) self.assertEqual(refs[0].confidence,100) def testFindsClassReferenceWhenChildIsSameNameAsClass(self): src = trimLines(""" class TheClass: class TheClass: pass a = TheClass.TheClass() """) refs = self.helper4(src,"pass",1,6) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,4) self.assertEqual(refs[0].colno,4) self.assertEqual(refs[0].confidence,100) class TestFindReferencesIncludingDefn(BRMTestCase,helpers): def test_findsMethodDecl(self): src=trimLines(""" class TheClass: def theMethod(self): pass """) refs = self.helper2(src,2,8) self.assertEqual(refs[0].filename,tmpfile) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,8) self.assertEqual(refs[0].confidence,100) class TestFindReferencesUsingFiles(BRMTestCase): def test_findsSimpleReferencesUsingFiles(self): src=trimLines(""" def foo(): a = 3 print a """) refs = self.helper("pass",src,2,4) assert refs[0].filename == pkgstructureFile2 assert refs[0].lineno == 3 assert refs[0].colno == 10 assert refs[0].confidence == 100 def test_findsReferenceInModuleWhichImportsClassWithFromAndAlias(self): src = trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() """) refs = self.helper(src,testClass,1,6) self.assertEqual(refs[0].filename,pkgstructureFile1) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,18) self.assertEqual(refs[0].confidence,100) def test_doesntBarfWhenCantLocatePackageWhenTryingToFindBaseClass(self): src = trimLines(""" from doesntexist import baseclass class foo(baseclass): def myMethod(self): pass """) refs = self.helper("",src,3,8) def test_doesntBarfWhenComesAcrossAPrintNl(self): src = trimLines(""" class TheClass: pass print >>foo, TheClass """) refs = self.helper("",src,1,6) def test_returnsOtherFilesInSameNonPackageDirectory(self): try: getRoot().pythonpath = [] # clear the python path classsrc = trimLines(""" def testFunction(): print 'hello' """) src = trimLines(""" from baz import testFunction """) writeTmpTestFile(src) newtmpfile = os.path.join(tmproot,"baz.py") writeFile(newtmpfile, classsrc) refs = [x for x in findReferences(newtmpfile,1,4)] assert refs[0].filename == tmpfile assert refs[0].lineno == 1 finally: os.remove(newtmpfile) deleteTmpTestFile() def helper(self, src, classsrc, line, col): try: createPackageStructure(src,classsrc) filename = pkgstructureFile2 refs = [x for x in findReferences(filename,line,col)] finally: removePackageStructure() return refs testClass = trimLines(""" class TheClass: def theMethod(self): pass def differentMethod(self): pass class DifferentClass: def theMethod(self): pass """) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_getPackageDependencies.py0100664000076400007640000000031710007753761022373 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * class TestGetPackageDependencies(BRMTestCase): def test_foo(self): assert 0 bicyclerepair-0.9/bike/query/test_getReferencesToClass.py0100755000076400007640000002327010001336654022077 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * from bike.query.findReferences import findReferences from bike.parsing.fastparserast import Module class TestGetReferencesToClass(BRMTestCase): def test_returnsEmptyListIfNoReferences(self): src = trimLines(""" class MyClass: pass a = TheClass() """) root = createSourceNodeAt(src,"mymodule") refs = [x for x in findReferences(os.path.abspath("mymodule.py"),1,6)] self.assertEqual(refs,[]) def test_findsSimpleReferenceInSameModule(self): src = trimLines(""" class TheClass: pass a = TheClass() """) root = createSourceNodeAt(src,"mymodule") refs = [x for x in findReferences(os.path.abspath("mymodule.py"),1,6)] self.assertEqual(refs[0].filename,os.path.abspath("mymodule.py")) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,4) self.assertEqual(refs[0].confidence,100) def test_findsReferencesInModuleWhichImportsClass(self): src = trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.theMethod() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") refs = [x for x in findReferences(os.path.abspath("a/b/bah.py"),1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,14) self.assertEqual(refs[0].confidence,100) def test_findsReferenceInModuleWhichImportsClassWithFrom(self): src = trimLines(""" from b.bah import TheClass def foo(): a = TheClass() a.theMethod() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,18) self.assertEqual(refs[0].confidence,100) self.assertEqual(refs[1].filename,os.path.abspath(os.path.join("a/foo.py"))) self.assertEqual(refs[1].lineno,3) self.assertEqual(refs[1].colno,8) self.assertEqual(refs[1].confidence,100) def test_findsReferenceToClassImportedInSameClassScope(self): src=trimLines(""" class AnotherClass: from b.bah import TheClass TheClass.baz = 0 """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] assert refs != [] def test_findsReferenceInModuleWhichImportsClassWithFromAndAlias(self): src = trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.theMethod() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,18) self.assertEqual(refs[0].confidence,100) def test_findsReferenceInModuleWhichImportsClassWithImportAs(self): src = trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.theMethod() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,18) self.assertEqual(refs[0].confidence,100) def test_findsReferenceInModuleWhichImportsClassWithFromImportStar(self): src = trimLines(""" from b.bah import * a = TheClass() a.theMethod() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,4) self.assertEqual(refs[0].confidence,100) def test_findsReferenceInModuleWhichImportsClassWithFromImportStar2(self): src = trimLines(""" from a.b.bah import * a = TheClass() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,4) self.assertEqual(refs[0].confidence,100) def test_findsClassReferenceInInstanceCreation(self): src = trimLines(""" class TheClass: def theMethod(self): pass TheClass().theMethod() """) root = createSourceNodeAt(src, "a.foo") filename = os.path.abspath("a/foo.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,0) self.assertEqual(refs[0].confidence,100) def test_findsClassReferenceInInstanceCreationWithFQN(self): src = trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.theMethod() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,14) self.assertEqual(refs[0].confidence,100) def test_doesntfindReferenceInModuleWhichDoesntImportClass(self): src = trimLines(""" a = TheClass() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] assert refs == [] def test_findsReferenceInClassBases(self): src =trimLines(""" from b.bah import TheClass class DerivedClass(TheClass): pass """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[1].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[1].lineno,2) self.assertEqual(refs[1].colno,19) self.assertEqual(refs[1].confidence,100) def test_findsReferenceInMultiLineImportStatement(self): src =trimLines(""" from b.bah import foo, \\ TheFooBah, TheClass, Foobah, SomethingElse """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,21) self.assertEqual(refs[0].confidence,100) def test_findsReferenceWhenModulenameSameAsClassMethodName(self): # asserts that brm doesnt search class scope after not finding name # in method scope (since class scope is invisible unless called on 'self' src =trimLines(""" from a.b import bah class baz: def bah(self): print bah.TheClass """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,4) self.assertEqual(refs[0].colno,18) self.assertEqual(refs[0].confidence,100) def test_doesntBarfOnFromImportStarWhenNameIsInFromClause(self): src = trimLines(""" from a.b.bah import TheClass a = TheClass() """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(ClassTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,1,6)] ClassTestdata = trimLines(""" class TheClass: def theMethod(self): pass def differentMethod(self): pass class DifferentClass: def theMethod(self): pass """) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_getReferencesToMethod.py0100755000076400007640000001421510012405504022242 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * from bike.query.findReferences import findReferences from bike.parsing.fastparserast import Module class TestGetReferencesToMethod(BRMTestCase): def test_getsReferenceOfSimpleMethodCall(self): src = trimLines(""" from b.bah import TheClass a = TheClass() a.theMethod() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(MethodTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] self.assertEqual(refs[0].filename, os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,2) self.assertEqual(refs[0].colno,2) def test_getsReferenceOfMethodCallFromClassImportedWithAlias(self): src = trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.theMethod() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(MethodTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] self.assertEqual(refs[0].filename, os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,5) self.assertEqual(refs[0].colno,6) def test_getsReferenceOfMethodCallWhenInstanceReturnedByFunction(self): src = trimLines(""" from b.bah import TheClass def foo(): return TheClass() a = foo() a.theMethod() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(MethodTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] self.assertEqual(refs[0].filename, os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,6) self.assertEqual(refs[0].colno,2) def test_getsReferenceOfMethodCallInSameClass(self): src = trimLines(""" class TheClass: def theMethod(self): pass def anotherMethod(self): self.theMethod() """) root = createSourceNodeAt(src,"a.foo") filename = os.path.abspath("a/foo.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] self.assertEqual(refs[0].filename, os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,5) self.assertEqual(refs[0].colno,13) def test_getsReferenceOfMethodOnBaseClassInstance(self): src = trimLines(""" class root: def theMethod(): pass class a(root): def theMethod(): pass class b(root): pass class TheClass(b): def theMethod(self): pass rootinstance = root() rootinstance.theMethod() """) refs =self.helper4(src,"pass",2,8) self.assertEqual(refs[2].filename,pkgstructureFile1) self.assertEqual(refs[2].lineno,17) self.assertEqual(refs[2].colno,13) def helper4(self, src, importedsrc, line, col): try: createPackageStructure(src,importedsrc) filename = pkgstructureFile1 refs = [x for x in findReferences(filename,line,col) if x.confidence == 100] finally: removePackageStructure() return refs def test_doesntGetReferenceToMethodWhenObjectCreatedInChildScopeToMethodReference(self): src = trimLines(""" from b.bah import TheClass a = AnotherClass() def foo(): a = TheClass() a.theMethod() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(MethodTestdata, "a.b.bah") filename = os.path.abspath("a/b/bah.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] assert len(refs) == 0 def test_renamesMethodReferenceOfInstanceCreatedInSubsequentFunction(self): src = trimLines(""" class TheClass: def theMethod(): pass class NotTheClass: def theMethod(): pass def foo(): a = bah() a.theMethod() def bah(): return TheClass() """) root = createSourceNodeAt(src,"a.foo") filename = os.path.abspath("a/foo.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] self.assertEqual(refs[0].filename, os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,10) self.assertEqual(refs[0].colno,6) def test_getsReferenceInMiddleOfBiggerCompoundCall(self): src = trimLines(""" class TheClass: def theMethod(self): return AnotherClass() TheClass().theMethod().anotherMethod() """) root = createSourceNodeAt(src,"a.foo") filename = os.path.abspath("a/foo.py") refs = [x for x in findReferences(filename,2,8) if x.confidence == 100] self.assertEqual(refs[0].filename, os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,3) self.assertEqual(refs[0].colno,11) self.assertEqual(refs[0].colend,20) MethodTestdata = trimLines(""" class TheClass: def theMethod(self): pass def differentMethod(self): pass class DifferentClass: def theMethod(self): pass """) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_getReferencesToModule.py0100755000076400007640000001461007720604143022262 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * from bike.query.getReferencesToModule import * from bike.parsing.fastparserast import Module class TestGetReferencesToModule(BRMTestCase): def test_returnsEmptyListIfNoReferences(self): src = trimLines(""" class MyClass: pass a = TheClass() """) root = createSourceNodeAt(src,"mymodule") self.assertEqual([x for x in getReferencesToModule(root,"myothermodule")],[]) def test_findsReferencesInModuleWhichImportsModule(self): src = trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.theMethod() """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,9) self.assertEqual(refs[0].confidence,100) self.assertEqual(refs[1].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[1].lineno,3) self.assertEqual(refs[1].colno,10) self.assertEqual(refs[0].confidence,100) def test_findsReferenceInModuleWhichImportsModuleWithFrom(self): src = trimLines(""" from b import bah def foo(): a = bah.TheClass() a.theMethod() """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,14) self.assertEqual(refs[0].confidence,100) self.assertEqual(refs[1].filename,os.path.abspath(os.path.join("a/foo.py"))) self.assertEqual(refs[1].lineno,3) self.assertEqual(refs[1].colno,8) self.assertEqual(refs[0].confidence,100) def test_findsReferenceInModuleWhichImportsModuleWithFromAndAlias(self): src = trimLines(""" from b import bah as mymodule def foo(): a = mymodule.MyTheClass() a.theMethod() """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,14) self.assertEqual(refs[0].confidence,100) """ # mymodule.MyTheClass self.assertEqual(refs[1].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[1].lineno,3) self.assertEqual(refs[1].colno,10) self.assertEqual(refs[1].confidence,100) """ def test_findsReferenceInModuleWhichImportsModuleWithFromImportStar(self): src = trimLines(""" from b.bah import * a = TheClass() a.theMethod() """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,7) self.assertEqual(refs[0].confidence,100) ''' Dont think this is a valid test, since cant import a module with from package import * def test_findsReferenceInModuleWhichImportsClassWithFromImportStar2(self): src = trimLines(""" from a.b import * a = bah.TheClass() """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,4) self.assertEqual(refs[0].confidence,100) ''' def test_findsReferenceInClassBases(self): src =trimLines(""" from b import bah class DerivedClass(bah.TheClass): pass """) root = createSourceNodeAt(src, "a.foo") root = createSourceNodeAt(testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[1].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[1].lineno,2) self.assertEqual(refs[1].colno,19) self.assertEqual(refs[1].confidence,100) def test_findsReferenceInMultiLineImportStatement(self): src =trimLines(""" from b import foo, \\ TheFooBah, TheClass, TheBastard, SomethingElse, bah """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,2) self.assertEqual(refs[0].colno,58) self.assertEqual(refs[0].confidence,100) def test_findsReferenceWhenModulenameSameAsClassMethodName(self): # asserts that brm doesnt search class scope after not finding name # in method scope (since class scope is invisible unless called on 'self' src =trimLines(""" from a.b import bah class baz: def bah(self): print bah.TheClass """) root = createSourceNodeAt( src, "a.foo") root = createSourceNodeAt( testdata.TheClass, "a.b.bah") refs = [x for x in getReferencesToModule(root,"a.b.bah")] self.assertEqual(refs[0].filename,os.path.abspath(os.path.join("a","foo.py"))) self.assertEqual(refs[0].lineno,1) self.assertEqual(refs[0].colno,16) self.assertEqual(refs[0].confidence,100) assert (len(refs))==2 if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_getTypeOf.py0100755000076400007640000001455210012412001017715 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * from bike.query.getTypeOf import getTypeOf, UnfoundType,\ attemptToConvertGetattrToFqn from bike.parsing.fastparserast import Class, Function, Instance from bike.parsing.newstuff import getModuleOrPackageUsingFQN from compiler.ast import Getattr,CallFunc,Name class TestGetTypeOf(BRMTestCase): def test_getsTypeOfSimpleClassInstanceReference(self): src = trimLines(""" from b.bah import TheClass a = TheClass() a.theMethod() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(testdata.TheClass, "a.b.bah") module = getModuleOrPackageUsingFQN("a.foo") res = getTypeOf(module,"a") assert isinstance(res,Instance) assert isinstance(res.getType(),Class) assert res.getType().name == "TheClass" def test_getsTypeOfImportedClassReference(self): src = trimLines(""" import b.bah a = b.bah.TheClass() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(testdata.TheClass, "a.b.bah") module = getModuleOrPackageUsingFQN("a.foo") res = getTypeOf(module,"a") assert isinstance(res,Instance) assert isinstance(res.getType(),Class) assert res.getType().name == "TheClass" def test_getsTypeOfClassReferenceFromImportedPackage(self): src = trimLines(""" import b a = b.bah.TheClass() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(testdata.TheClass, "a.b.bah") module = getModuleOrPackageUsingFQN("a.foo") res = getTypeOf(module,"a") assert isinstance(res,Instance) assert isinstance(res.getType(),Class) assert res.getType().name == "TheClass" def test_getsTypeOfInstanceThatIsAnAttributeOfSelf(self): src = trimLines(""" class TheClass: def theMethod(self): pass class AnotherClass: def __init__(self): self.a = TheClass() def anotherFn(self): self.a.theMethod() """) root = createSourceNodeAt(src,"a.foo") module = getModuleOrPackageUsingFQN('a.foo') theclass = getTypeOf(module,"TheClass") fn = getTypeOf(module,"AnotherClass.anotherFn") self.assertEqual(getTypeOf(fn,"self.a").getType().name, "TheClass") #self.assertEqual(getTypeOf(fn,"self.a").getType(), theclass) def test_doesntGetTypeDefinedInChildFunction(self): src = trimLines(""" from b.bah import TheClass a = AnotherClass() def foo(): a = TheClass() a.theMethod() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(testdata.TheClass, "a.b.bah") themodule = getModuleOrPackageUsingFQN("a.foo") assert isinstance(getTypeOf(themodule,"a"),UnfoundType) def test_getsTypeOfClassReferencedViaAlias(self): src = trimLines(""" from b.bah import TheClass as FooBah FooBah() """) root = createSourceNodeAt(src,"a.foo") root = createSourceNodeAt(testdata.TheClass, "a.b.bah") themodule = getModuleOrPackageUsingFQN("a.foo") self.assertEqual(getTypeOf(themodule,"FooBah").name,"TheClass") self.assertEqual(getTypeOf(themodule,"FooBah").filename, os.path.abspath(os.path.join("a","b","bah.py"))) def test_getsTypeOfClassImportedFromPackageScope(self): initfile = trimLines(""" from bah import TheClass """) src = trimLines(""" from a import b b.TheClass() """) createSourceNodeAt(src,"a.foo") createSourceNodeAt(testdata.TheClass, "a.b.bah") createSourceNodeAt(initfile,"a.b.__init__") themodule = getModuleOrPackageUsingFQN("a.foo") self.assertEqual(getTypeOf(themodule,"b.TheClass").name,"TheClass") self.assertEqual(getTypeOf(themodule,"b.TheClass").filename, os.path.abspath(os.path.join("a","b","bah.py"))) def test_attemptToConvertGetattrToFqn_returnsNoneIfFails(self): ast = Getattr(CallFunc(Name("foo"),[],[],[]),"hello") assert attemptToConvertGetattrToFqn(ast) is None def test_attemptToConvertGetattrToFqn_works(self): ast = Getattr(Getattr(Name("foo"),"bah"),"hello") assert attemptToConvertGetattrToFqn(ast) == "foo.bah.hello" def test_handlesRecursionProblem(self): src = trimLines(""" def fn(root): node = root node = node.getPackage('something') """) root = createSourceNodeAt(src,"a.foo") m = getModuleOrPackageUsingFQN("a.foo") fn = getTypeOf(m,"fn") getTypeOf(fn,"node") # stack overflow! def test_doesntGotIntoRecursiveLoopWhenEvaluatingARecursiveFunction(self): src = trimLines(""" def fn(v): if v < 45: return fn(root+1) val = fn(3) """) root = createSourceNodeAt(src,"a.foo") mod = getModuleOrPackageUsingFQN("a.foo") getTypeOf(mod,"val") # stack overflow! def test_getsModuleImportedWithFrom(self): importsrc=trimLines(""" from a.b import bah """) src=trimLines(""" mytext = 'hello' """) type = self.helper(importsrc,src,'bah') self.assertEqual(pkgstructureFile2,type.filename) def helper(self,importsrc,src,name): try: createPackageStructure(importsrc,src) from bike.parsing.newstuff import getModule scope = getModule(pkgstructureFile1) return getTypeOf(scope,name) finally: removePackageStructure() def test_getsTypeOfClassImportedAsAlias(self): importsrc = trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.theMethod() """) src=trimLines(""" class TheClass: def theMethod(self): pass """) type = self.helper(importsrc,src,'MyTheClass') self.assertEqual("TheClass",type.name) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/test_relationships.py0100755000076400007640000000576610010123317020711 0ustar pldpld#!/usr/bin/env python import setpath import unittest import os from bike import testdata from bike.testutils import * from bike.query.getTypeOf import getTypeOf from bike.parsing.fastparserast import Module from bike.query.relationships import getRootClassesOfHierarchy from bike.parsing.newstuff import getModule class TestGetRootClassesOfHierarchy(BRMTestCase): def test_getsRootClassFromDerivedClass(self): src = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass(BaseClass): pass """) rootclasses = self.helper(src,"DerivedClass") self.assertEqual("TheClass",rootclasses[0].name) self.assertEqual(len(rootclasses),1) def test_getsRootClassFromDerivedDerivedClass(self): src = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass(BaseClass): pass class DerivedDerivedClass(DerivedClass): pass """) rootclasses = self.helper(src,"DerivedDerivedClass") self.assertEqual("TheClass",rootclasses[0].name) self.assertEqual(len(rootclasses),1) def test_getsRootClassFromDiamondOfClasses(self): src = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass(BaseClass): pass class DerivedDerivedClass(DerivedClass,BaseClass): pass """) rootclasses = self.helper(src,"DerivedDerivedClass") self.assertEqual("TheClass",rootclasses[0].name) self.assertEqual("TheClass",rootclasses[1].name) self.assertEqual(len(rootclasses),2) def test_getsRootClassesFromMultipleInheritance(self): src = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass: pass class DerivedDerivedClass(DerivedClass,BaseClass): pass """) rootclasses = self.helper(src,"DerivedDerivedClass") self.assertEqual("DerivedClass",rootclasses[0].name) self.assertEqual("TheClass",rootclasses[1].name) self.assertEqual(len(rootclasses),2) def test_getsRootClassesFromMultipleInheritanceWithNewStyleClass(self): src = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass(Object): pass class DerivedDerivedClass(DerivedClass,BaseClass): pass """) rootclasses = self.helper(src,"DerivedDerivedClass") self.assertEqual("DerivedClass",rootclasses[0].name) self.assertEqual("TheClass",rootclasses[1].name) self.assertEqual(len(rootclasses),2) def helper(self,src,classname): try: createPackageStructure(src,testdata.TheClass) classobj = getTypeOf(getModule(pkgstructureFile1),classname) return getRootClassesOfHierarchy(classobj) finally: removePackageStructure() if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/query/testall.py0100755000076400007640000000055610007754250016442 0ustar pldpld#!/usr/bin/env python import setpath from test_common import * from test_getReferencesToClass import * from test_getReferencesToMethod import * #from test_getReferencesToModule import * from test_findDefinition import * from test_findReferences import * from test_relationships import * from test_getTypeOf import * if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/0040775000076400007640000000000010036707253015057 5ustar pldpldbicyclerepair-0.9/bike/refactor/__init__.py0100644000076400007640000000000207372751225017162 0ustar pldpld bicyclerepair-0.9/bike/refactor/extractMethod.py0100644000076400007640000003064610011752577020252 0ustar pldpldimport 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 bicyclerepair-0.9/bike/refactor/extractVariable.py0100644000076400007640000000240710010510556020535 0ustar pldpldfrom bike.parsing.parserutils import maskStringsAndRemoveComments from bike.transformer.undo import getUndoStack from parser import ParserError import compiler from bike.refactor.extractMethod import coords from bike.refactor.utils import getTabWidthOfLine, getLineSeperator,\ reverseCoordsIfWrongWayRound from bike.transformer.save import queueFileToSave from bike.parsing.load import getSourceNode def extractLocalVariable(filename, startcoords, endcoords, varname): sourceobj = getSourceNode(filename) if startcoords.line != endcoords.line: raise "Can't do multi-line extracts yet" startcoords, endcoords = \ reverseCoordsIfWrongWayRound(startcoords,endcoords) line = sourceobj.getLine(startcoords.line) tabwidth = getTabWidthOfLine(line) linesep = getLineSeperator(line) region = line[startcoords.column:endcoords.column] getUndoStack().addSource(sourceobj.filename,sourceobj.getSource()) sourceobj.getLines()[startcoords.line-1] = \ line[:startcoords.column] + varname + line[endcoords.column:] defnline = tabwidth*" " + varname + " = " + region + linesep sourceobj.getLines().insert(startcoords.line-1,defnline) queueFileToSave(sourceobj.filename,"".join(sourceobj.getLines())) bicyclerepair-0.9/bike/refactor/inlineVariable.py0100644000076400007640000000671510010510562020344 0ustar pldpldfrom bike.query.findDefinition import findAllPossibleDefinitionsByCoords from bike.query.findReferences import findReferences from bike.parsing.parserutils import maskStringsAndRemoveComments, linecontinueRE from bike.transformer.undo import getUndoStack from bike.transformer.save import queueFileToSave from parser import ParserError from bike.parsing.load import getSourceNode import compiler import re def inlineLocalVariable(filename, lineno,col): sourceobj = getSourceNode(filename) return inlineLocalVariable_old(sourceobj, lineno,col) def inlineLocalVariable_old(sourcenode,lineno,col): definition, region, regionlinecount = getLocalVariableInfo(sourcenode, lineno, col) addUndo(sourcenode) replaceReferences(sourcenode, findReferences(sourcenode.filename, definition.lineno, definition.colno), region) delLines(sourcenode, definition.lineno-1, regionlinecount) updateSource(sourcenode) def getLocalVariableInfo(sourcenode, lineno, col): definition = findDefinition(sourcenode, lineno, col) region, linecount = getRegionToInline(sourcenode, definition) return definition, region, linecount def findDefinition(sourcenode, lineno, col): definition = findAllPossibleDefinitionsByCoords(sourcenode.filename, lineno,col).next() assert definition.confidence == 100 return definition def getRegionToInline(sourcenode, defn): line, linecount = getLineAndContinues(sourcenode, defn.lineno) start, end = findRegionToInline(maskStringsAndRemoveComments(line)) return line[start:end], linecount def findRegionToInline(maskedline): match = re.compile("[^=]+=\s*(.+)$\n", re.DOTALL).match(maskedline) assert match return match.start(1), match.end(1) # Possible refactoring: move to class of sourcenode def getLineAndContinues(sourcenode, lineno): line = sourcenode.getLine(lineno) linecount = 1 while linecontinueRE.search(line): line += sourcenode.getLine(lineno + linecount) linecount += 1 return line, linecount def addUndo(sourcenode): getUndoStack().addSource(sourcenode.filename,sourcenode.getSource()) def replaceReferences(sourcenode, references, replacement): for reference in safeReplaceOrder( references ): replaceReference(sourcenode, reference, replacement) def safeReplaceOrder( references ): """ When inlining a variable, if multiple instances occur on the line, then the last reference must be replaced first. Otherwise the remaining intra-line references will be incorrect. """ def safeReplaceOrderCmp(self, other): return -cmp(self.colno, other.colno) result = list(references) result.sort(safeReplaceOrderCmp) return result def replaceReference(sourcenode, ref, replacement): """ sourcenode.getLines()[ref.lineno-1][ref.colno:ref.colend] = replacement But strings don't support slice assignment as they are immutable. :( """ sourcenode.getLines()[ref.lineno-1] = \ replaceSubStr(sourcenode.getLines()[ref.lineno-1], ref.colno, ref.colend, replacement) def replaceSubStr(str, start, end, replacement): return str[:start] + replacement + str[end:] # Possible refactoring: move to class of sourcenode def delLines(sourcenode, lineno, linecount=1): del sourcenode.getLines()[lineno:lineno+linecount] def updateSource(sourcenode): queueFileToSave(sourcenode.filename,"".join(sourcenode.getLines())) bicyclerepair-0.9/bike/refactor/moveToModule.py0100664000076400007640000001323510014652500020037 0ustar pldpldimport bike.globals from bike.parsing.load import getSourceNode from bike.parsing.fastparserast import Module from bike.query.common import getScopeForLine, convertNodeToMatchObject from bike.transformer.save import queueFileToSave, save from bike.transformer.undo import getUndoStack from bike.refactor.extractMethod import getVariableReferencesInLines from bike.refactor.utils import getLineSeperator from bike.query.findDefinition import findDefinitionFromASTNode from bike.query.findReferences import findReferences from bike.parsing.pathutils import filenameToModulePath from compiler.ast import Name import re def moveClassToNewModule(origfile,line,newfile): srcnode = getSourceNode(origfile) targetsrcnode = getSourceNode(newfile) classnode = getScopeForLine(srcnode,line) classlines = srcnode.getLines()[classnode.getStartLine()-1: classnode.getEndLine()-1] getUndoStack().addSource(srcnode.filename, srcnode.getSource()) getUndoStack().addSource(targetsrcnode.filename, targetsrcnode.getSource()) srcnode.getLines()[classnode.getStartLine()-1: classnode.getEndLine()-1] = [] targetsrcnode.getLines().extend(classlines) queueFileToSave(srcnode.filename,srcnode.getSource()) queueFileToSave(targetsrcnode.filename,targetsrcnode.getSource()) exactFromRE = "(from\s+\S+\s+import\s+%s)(.*)" fromRE = "from\s+\S+\s+import\s+(.*)" def moveFunctionToNewModule(origfile,line,newfile): srcnode = getSourceNode(origfile) targetsrcnode = getSourceNode(newfile) scope = getScopeForLine(srcnode,line) linesep = getLineSeperator(srcnode.getLines()[0]) matches =[m for m in findReferences(origfile, line, scope.getColumnOfName())] origFileImport = [] fromline = 'from %s import %s'%(filenameToModulePath(newfile),scope.name) for match in matches: if match.filename == origfile: origFileImport = fromline + linesep else: s = getSourceNode(match.filename) m = s.fastparseroot if match.lineno in m.getImportLineNumbers(): getUndoStack().addSource(s.filename, s.getSource()) maskedline = m.getLogicalLine(match.lineno) origline = s.getLines()[match.lineno-1] reMatch = re.match(exactFromRE%(scope.name),maskedline) if reMatch and not (',' in reMatch.group(2) or \ '\\' in reMatch.group(2)): # i.e. line is 'from module import foo' if match.filename == newfile: #remove the import s.getLines()[match.lineno-1:match.lineno] = [] pass else: restOfOrigLine = origline[len(reMatch.group(1)):] s.getLines()[match.lineno-1] = fromline + restOfOrigLine elif re.match(fromRE,maskedline): # i.e. line is 'from module import foo,bah,baz' #remove the element from the import stmt line = removeNameFromMultipleImportLine(scope.name, origline) s.getLines()[match.lineno-1] = line #and add a new line nextline = match.lineno + maskedline.count('\\') + 1 s.getLines()[nextline-1:nextline-1] = [fromline+linesep] queueFileToSave(s.filename,s.getSource()) refs = getVariableReferencesInLines(scope.getMaskedLines()) scopeLines = srcnode.getLines()[scope.getStartLine()-1: scope.getEndLine()-1] importModules = deduceImportsForNewFile(refs, scope) importlines = composeNewFileImportLines(importModules, linesep) getUndoStack().addSource(srcnode.filename, srcnode.getSource()) getUndoStack().addSource(targetsrcnode.filename, targetsrcnode.getSource()) srcnode.getLines()[scope.getStartLine()-1: scope.getEndLine()-1] = origFileImport targetsrcnode.getLines().extend(importlines+scopeLines) queueFileToSave(srcnode.filename,srcnode.getSource()) queueFileToSave(targetsrcnode.filename,targetsrcnode.getSource()) def removeNameFromMultipleImportLine(name, origline): def replacefn(match): return match.group(1) line = re.sub('(\W)%s\s*?,'%(name),replacefn,origline) return line def composeNewFileImportLines(importModules, linesep): importlines = [] for mpath in importModules: importlines += "from %s import %s"%(mpath, ', '.join(importModules[mpath])) importlines += linesep return importlines def deduceImportsForNewFile(refs, scope): importModules = {} for ref in refs: match = findDefinitionFromASTNode(scope,Name(ref)) if match.filename == scope.module.filename: tgtscope = getScopeForLine(getSourceNode(match.filename), match.lineno) while tgtscope != scope and not isinstance(tgtscope,Module): tgtscope = tgtscope.getParent() if not isinstance(tgtscope,Module): continue # was defined in this function mpath = filenameToModulePath(match.filename) if mpath in importModules: importModules[mpath].append(ref) else: importModules[mpath] = [ref] return importModules bicyclerepair-0.9/bike/refactor/rename.py0100644000076400007640000000140610010507074016663 0ustar pldpldfrom bike.transformer.WordRewriter import WordRewriter from bike.query.findReferences import findReferencesIncludingDefn from bike.transformer.save import save def rename(filename,lineno,col,newname,promptcallback=None): strrewrite = WordRewriter() for match in findReferencesIncludingDefn(filename,lineno,col): #print "rename match ",match if match.confidence == 100 or promptUser(promptcallback,match): strrewrite.rewriteString(match.sourcenode, match.lineno,match.colno,newname) strrewrite.commit() def promptUser(promptCallback,match): if promptCallback is not None and \ promptCallback(match.filename, match.lineno, match.colno, match.colend): return 1 return 0 bicyclerepair-0.9/bike/refactor/setpath.py0100644000076400007640000000034207722363037017101 0ustar pldpldimport sys,os if not os.path.abspath("../..") in sys.path: from bike import log print >> log.warning, "Appending to the system path. This should only happen in unit tests" sys.path.append(os.path.abspath("../..")) bicyclerepair-0.9/bike/refactor/test_extractMethod.py0100755000076400007640000005243410010511015021266 0ustar pldpld#!/usr/bin/env python import setpath import unittest from bike.refactor.extractMethod import ExtractMethod, \ extractMethod, coords from bike import testdata from bike.testutils import * from bike.parsing.load import Cache def assertTokensAreSame(t1begin, t1end, tokens): it = t1begin.clone() pos = 0 while it != t1end: assert it.deref() == tokens[pos] it.incr() pos+=1 assert pos == len(tokens) def helper(src,startcoords, endcoords, newname): sourcenode = createAST(src) extractMethod(tmpfile, startcoords, endcoords, newname) return sourcenode.getSource() class TestExtractMethod(BRMTestCase): def test_extractsPass(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): pass """) srcAfter=trimLines(""" class MyClass: def myMethod(self): self.newMethod() def newMethod(self): pass """) src = helper(srcBefore, coords(3, 8), coords(3, 12), "newMethod") self.assertEqual(src,srcAfter) def test_extractsPassWhenFunctionAllOnOneLine(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): pass # comment """) srcAfter=trimLines(""" class MyClass: def myMethod(self): self.newMethod() # comment def newMethod(self): pass """) src = helper(srcBefore, coords(2, 24), coords(2, 28),"newMethod") self.assertEqual(src,srcAfter) def test_extractsPassFromForLoop(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): # comment for i in foo: pass """) srcAfter=trimLines(""" class MyClass: def myMethod(self): # comment for i in foo: self.newMethod() def newMethod(self): pass """) src = helper(srcBefore, coords(4, 12), coords(4, 16), "newMethod") self.assertEqual(srcAfter, src) def test_newMethodHasArgumentsForUsedTemporarys(self): srcBefore=trimLines(""" class MyClass: def myMethod(self, c): a = something() b = somethingelse() print a + b + c + d print \"hello\" dosomethingelse(a, b) """) srcAfter=trimLines(""" class MyClass: def myMethod(self, c): a = something() b = somethingelse() self.newMethod(a, b, c) dosomethingelse(a, b) def newMethod(self, a, b, c): print a + b + c + d print \"hello\" """) src = helper(srcBefore, coords(5, 8), coords(6, 21), "newMethod") self.assertEqual(srcAfter, src) def test_newMethodHasSingleArgument(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): a = something() print a print \"hello\" dosomethingelse(a, b) """) srcAfter=trimLines(""" class MyClass: def myMethod(self): a = something() self.newMethod(a) dosomethingelse(a, b) def newMethod(self, a): print a print \"hello\" """) src = helper(srcBefore, coords(4, 8), coords(5, 21), "newMethod") self.assertEqual(srcAfter, src) def test_doesntHaveDuplicateArguments(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): a = 3 print a print a """) srcAfter=trimLines(""" class MyClass: def myMethod(self): a = 3 self.newMethod(a) def newMethod(self, a): print a print a """) src = helper(srcBefore, coords(4, 0), coords(6, 0), "newMethod") self.assertEqual(srcAfter, src) def test_extractsQueryWhenFunctionAllOnOneLine(self): srcBefore=trimLines(""" class MyClass: def myMethod(self, a): print a # comment """) srcAfter=trimLines(""" class MyClass: def myMethod(self, a): self.newMethod(a) # comment def newMethod(self, a): print a """) src = helper(srcBefore, coords(2, 27), coords(2, 34), "newMethod") self.assertEqual(srcAfter, src) def test_worksWhenAssignmentsToTuples(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): a, b, c = 35, 36, 37 print a + b """) srcAfter=trimLines(""" class MyClass: def myMethod(self): a, b, c = 35, 36, 37 self.newMethod(a, b) def newMethod(self, a, b): print a + b """) src = helper(srcBefore, coords(4, 8), coords(4, 19), "newMethod") self.assertEqual(srcAfter, src) def test_worksWhenUserSelectsABlockButDoesntSelectTheHangingDedent(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): # comment for i in foo: pass """) srcAfter=trimLines(""" class MyClass: def myMethod(self): # comment for i in foo: self.newMethod() def newMethod(self): pass """) src = helper(srcBefore, coords(4, 8), coords(4, 16), "newMethod") self.assertEqual(srcAfter, src) def test_newMethodHasSingleReturnValue(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): a = 35 # <-- extract me print a """) srcAfter=trimLines(""" class MyClass: def myMethod(self): a = self.newMethod() print a def newMethod(self): a = 35 # <-- extract me return a """) src = helper(srcBefore, coords(3, 4), coords(3, 34), "newMethod") self.assertEqual(srcAfter, src) def test_newMethodHasMultipleReturnValues(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): a = 35 b = 352 print a + b """) srcAfter=trimLines(""" class MyClass: def myMethod(self): a, b = self.newMethod() print a + b def newMethod(self): a = 35 b = 352 return a, b """) src = helper(srcBefore, coords(3, 8), coords(4, 15), "newMethod") self.assertEqual(srcAfter, src) def test_worksWhenMovingCodeJustAfterDedent(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): # comment for i in foo: pass print \"hello\" """) srcAfter=trimLines(""" class MyClass: def myMethod(self): # comment for i in foo: pass self.newMethod() def newMethod(self): print \"hello\" """) src = helper(srcBefore, coords(5, 8), coords(5, 21), "newMethod") self.assertEqual(srcAfter, src) def test_extractsPassWhenSelectionCoordsAreReversed(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): pass """) srcAfter=trimLines(""" class MyClass: def myMethod(self): self.newMethod() def newMethod(self): pass """) src = helper(srcBefore, coords(3, 12), coords(3, 8), "newMethod") self.assertEqual(srcAfter, src) def test_extractsExpression(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): # comment a = 32 b = 2 + a * 1 + 2 """) srcAfter=trimLines(""" class MyClass: def myMethod(self): # comment a = 32 b = 2 + self.newMethod(a) + 2 def newMethod(self, a): return a * 1 """) src = helper(srcBefore, coords(4, 16), coords(4, 21), "newMethod") self.assertEqual(srcAfter, src) def test_extractsExpression2(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): # comment g = 32 assert output.thingy(g) == \"bah\" """) srcAfter=trimLines(""" class MyClass: def myMethod(self): # comment g = 32 assert self.newMethod(g) == \"bah\" def newMethod(self, g): return output.thingy(g) """) src = helper(srcBefore, coords(4, 15), coords(4, 31), "newMethod") self.assertEqual(srcAfter, src) class TestExtractFunction(BRMTestCase): def runTarget(self, src, begincoords, endcoords, newname): ast = createAST(src) extractFunction(ast, begincoords, endcoords, newname) return ast def test_extractsFunction(self): srcBefore=trimLines(""" def myFunction(): # comment a = 3 c = a + 99 b = c * 1 print b """) srcAfter=trimLines(""" def myFunction(): # comment a = 3 b = newFunction(a) print b def newFunction(a): c = a + 99 b = c * 1 return b """) src = helper(srcBefore, coords(3, 4), coords(4, 13), "newFunction") self.assertEqual(srcAfter, src) def test_extractsAssignToAttribute(self): srcBefore=trimLines(""" def simulateLoad(path): item = foo() item.decl = line """) srcAfter=trimLines(""" def simulateLoad(path): item = foo() newFunction(item) def newFunction(item): item.decl = line """) src = helper(srcBefore, coords(3, 0), coords(4, 0), "newFunction") self.assertEqual(srcAfter, src) def test_extractsFromFirstBlockOfIfElseStatement(self): srcBefore=trimLines(""" def foo(): if bah: print \"hello1\" print \"hello2\" elif foo: pass """) srcAfter=trimLines(""" def foo(): if bah: newFunction() print \"hello2\" elif foo: pass def newFunction(): print \"hello1\" """) src = helper(srcBefore, coords(3, 0), coords(4, 0), "newFunction") self.assertEqual(srcAfter, src) def test_extractsAugAssign(self): srcBefore=trimLines(""" def foo(): a = 3 a += 1 print a """) srcAfter=trimLines(""" def foo(): a = 3 a = newFunction(a) print a def newFunction(a): a += 1 return a """) src = helper(srcBefore, coords(3, 0), coords(4, 0), "newFunction") self.assertEqual(srcAfter, src) def test_extractsForLoopUsingLoopVariable(self): srcBefore=trimLines(""" def foo(): for i in range(1, 3): print i """) srcAfter=trimLines(""" def foo(): for i in range(1, 3): newFunction(i) def newFunction(i): print i """) src = helper(srcBefore, coords(3, 0), coords(4, 0), "newFunction") self.assertEqual(srcAfter, src) def test_extractWhileLoopVariableIncrement(self): srcBefore=trimLines(""" def foo(): a = 0 while a != 3: a = a+1 """) srcAfter=trimLines(""" def foo(): a = 0 while a != 3: a = newFunction(a) def newFunction(a): a = a+1 return a """) src = helper(srcBefore, coords(4, 0), coords(5, 0), "newFunction") self.assertEqual(srcAfter, src) def test_extractAssignedVariableUsedInOuterForLoop(self): srcBefore=trimLines(""" def foo(): b = 0 for a in range(1, 3): b = b+1 while b != 2: print a b += 1 """) srcAfter=trimLines(""" def foo(): b = 0 for a in range(1, 3): b = b+1 while b != 2: b = newFunction(a, b) def newFunction(a, b): print a b += 1 return b """) src = helper(srcBefore, coords(6, 0), coords(8, 0), "newFunction") self.assertEqual(srcAfter, src) def test_extractsConditionalFromExpression(self): srcBefore=trimLines(""" def foo(): if 123+3: print aoue """) srcAfter=trimLines(""" def foo(): if newFunction(): print aoue def newFunction(): return 123+3 """) src = helper(srcBefore, coords(2, 7), coords(2, 12), "newFunction") self.assertEqual(srcAfter, src) def test_extractCodeAfterCommentInMiddleOfFnDoesntRaiseParseException(self): srcBefore=trimLines(""" def theFunction(): print 1 # comment print 2 """) srcAfter=trimLines(""" def theFunction(): print 1 # comment newFunction() def newFunction(): print 2 """) src = helper(srcBefore, coords(4, 0), coords(5, 0), "newFunction") self.assertEqual(srcAfter, src) def test_canExtractQueryFromNestedIfStatement(self): srcBefore=trimLines(""" def theFunction(): if foo: # comment if bah: pass """) srcAfter=trimLines(""" def theFunction(): if foo: # comment if newFunction(): pass def newFunction(): return bah """) src = helper(srcBefore, coords(3, 11), coords(3, 14), "newFunction") self.assertEqual(srcAfter, src) def test_doesntMessUpTheNextFunctionOrClass(self): srcBefore=trimLines(""" def myFunction(): a = 3 print \"hello\"+a # extract me class MyClass: def myMethod(self): b = 12 # extract me c = 3 # and me d = 2 # and me print b, c """) srcAfter=trimLines(""" def myFunction(): a = 3 newFunction(a) def newFunction(a): print \"hello\"+a # extract me class MyClass: def myMethod(self): b = 12 # extract me c = 3 # and me d = 2 # and me print b, c """) # extract code on one line src = helper(srcBefore, coords(3, 4), coords(3, 34), "newFunction") self.assertEqual(srcAfter, src) # extract code on 2 lines (most common user method) resetRoot() Cache.instance.reset() Root() src = helper(srcBefore, coords(3, 0), coords(4, 0), "newFunction") self.assertEqual(srcAfter, src) def test_doesntBallsUpIndentWhenTheresALineWithNoSpacesInIt(self): srcBefore=trimLines(""" def theFunction(): if 1: pass pass """) srcAfter=trimLines(""" def theFunction(): newFunction() def newFunction(): if 1: pass pass """) src = helper(srcBefore, coords(2, 4), coords(5, 8), "newFunction") self.assertEqual(srcAfter, src) def test_doesntHaveToBeInsideAFunction(self): srcBefore=trimLines(r""" a = 1 print a + 2 f(b) """) srcAfter=trimLines(r""" a = 1 newFunction(a) def newFunction(a): print a + 2 f(b) """) src = helper(srcBefore, coords(2, 0), coords(3, 4), "newFunction") self.assertEqual(srcAfter, src) def test_doesntBarfWhenEncountersMethodCalledOnCreatedObj(self): srcBefore=trimLines(r""" results = QueryEngine(q).foo() """) srcAfter=trimLines(r""" newFunction() def newFunction(): results = QueryEngine(q).foo() """) src = helper(srcBefore, coords(1, 0), coords(2, 0), "newFunction") self.assertEqual(srcAfter, src) def test_worksIfNoLinesBeforeExtractedCode(self): srcBefore=trimLines(r""" print a + 2 f(b) """) srcAfter=trimLines(r""" newFunction() def newFunction(): print a + 2 f(b) """) src = helper(srcBefore, coords(1, 0), coords(2, 4), "newFunction") self.assertEqual(srcAfter, src) class TestGetRegionAsString(BRMTestCase): def test_getsHighlightedSingleLinePassStatement(self): src=trimLines(""" class MyClass: def myMethod(self): pass """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(3, 8), coords(3, 12), "foobah") em.getRegionToBuffer() self.assertEqual(len(em.extractedLines), 1) self.assertEqual(em.extractedLines[0], "pass\n") def test_getsSingleLinePassStatementWhenWholeLineIsHighlighted(self): src=trimLines(""" class MyClass: def myMethod(self): pass """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(3, 0), coords(3, 12), "foobah") em.getRegionToBuffer() self.assertEqual(len(em.extractedLines), 1) self.assertEqual(em.extractedLines[0], "pass\n") def test_getsMultiLineRegionWhenJustRegionIsHighlighted(self): src=trimLines(""" class MyClass: def myMethod(self): print 'hello' pass """) region=trimLines(""" print 'hello' pass """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(3, 8), coords(4, 12), "foobah") em.getRegionToBuffer() self.assertEqual(em.extractedLines, region.splitlines(1)) def test_getsMultiLineRegionWhenRegionLinesAreHighlighted(self): src=trimLines(""" class MyClass: def myMethod(self): print 'hello' pass """) region=trimLines(""" print 'hello' pass """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(3, 0), coords(5, 0), "foobah") em.getRegionToBuffer() self.assertEqual(em.extractedLines, region.splitlines(1)) def test_getsHighlightedSubstringOfLine(self): src=trimLines(""" class MyClass: def myMethod(self): if a == 3: pass """) region=trimLines(""" a == 3 """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(3, 11), coords(3, 17), "foobah") em.getRegionToBuffer() self.assertEqual(em.extractedLines, region.splitlines(1)) class TestGetTabwidthOfParentFunction(BRMTestCase): def test_getsTabwidthForSimpleMethod(self): src=trimLines(""" class MyClass: def myMethod(self): pass """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(3, 11), coords(3, 17), "foobah") self.assertEqual(em.getTabwidthOfParentFunction(), 4) def test_getsTabwidthForFunctionAtRootScope(self): src=trimLines(""" def myFn(self): pass """) sourcenode = createAST(src) em = ExtractMethod(sourcenode, coords(2, 0), coords(2, 9), "foobah") self.assertEqual(em.getTabwidthOfParentFunction(), 0) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_extractVariable.py0100755000076400007640000000205510010141266021574 0ustar pldpld#!/usr/bin/env python import setpath import unittest from bike.testutils import * from bike.refactor.extractVariable import coords, extractLocalVariable class TestExtractLocalVariable(BRMTestCase): def test_worksOnSimpleCase(self): srcBefore=trimLines(""" def foo(): print 3 + 2 """) srcAfter=trimLines(""" def foo(): a = 3 + 2 print a """) sourcenode = createAST(srcBefore) extractLocalVariable(tmpfile,coords(2,10),coords(2,15),'a') self.assertEqual(sourcenode.getSource(),srcAfter) def test_worksIfCoordsTheWrongWayRound(self): srcBefore=trimLines(""" def foo(): print 3 + 2 """) srcAfter=trimLines(""" def foo(): a = 3 + 2 print a """) sourcenode = createAST(srcBefore) extractLocalVariable(tmpfile,coords(2,15),coords(2,10),'a') self.assertEqual(sourcenode.getSource(),srcAfter) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_inlineVariable.py0100755000076400007640000000477207722313773021434 0ustar pldpld#!/usr/bin/env python import setpath import unittest from bike.testutils import trimLines,createAST, BRMTestCase from inlineVariable import inlineLocalVariable_old class TestInlineLocalVariable(BRMTestCase): def test_worksWhenUserDoesItAgainstReference(self): srcBefore=r""" def foo(): b = 'hello' print b """ srcAfter=r""" def foo(): print 'hello' """ self.helper( srcBefore, 3, 10, srcAfter ) def test_worksWhenInlinedCodeIsOverTwoLines(self): srcBefore=r""" def foo(): b = 3 + \ 2 print b """ srcAfter=r""" def foo(): print 3 + \ 2 """ self.helper(srcBefore, 2, 4, srcAfter) ''' Needs Adding Again def test_addsBracketsWhenInlinedCodeHasPresidenceOverSurroundingCode(self): srcBefore=trimLines(r""" def foo(): b = 3 + 2 print 3 * b """) srcAfter=trimLines(r""" def foo(): print 3 * (3 + 2) """) assert 0 ''' def test_worksWithMultipleInstancesOfVariableOnLine(self): srcBefore=r""" def foo(): x = 11 print x, x """ srcAfter=r""" def foo(): print 11, 11 """ self.helper(srcBefore, 2, 4, srcAfter) def test_worksWithMultipleMultilineCode(self): srcBefore=r""" def foo(): b = 3 + \ 2 print b print b """ srcAfter=r""" def foo(): print 3 + \ 2 print 3 + \ 2 """ self.helper(srcBefore, 2, 4, srcAfter) ''' Can't do this without some hairy logic to deduce how to inline the variables. E.g. how do you inline a,b = foo() ? def test_handlesTupleAssignment(self): srcBefore=r""" def foo(): x, y = 1, 2 print x print y """ srcAfter=r""" def foo(): y = 2 print 1 print y """ self.helper(srcBefore, 2, 4, srcAfter) ''' def helper(self, srcBefore, y, x, srcAfter): sourcenode = createAST(trimLines(srcBefore)) inlineLocalVariable_old(sourcenode,y,x) self.assertEqual(sourcenode.getSource(),trimLines(srcAfter)) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_moveToModule.py0100755000076400007640000001515310036707102021103 0ustar pldpld#!/usr/bin/env python import setpath from bike.testutils import * from bike.transformer.save import save from moveToModule import * class TestMoveClass(BRMTestCase): def test_movesTheText(self): src1=trimLines(""" def before(): pass class TheClass: pass def after(): pass """) src1after=trimLines(""" def before(): pass def after(): pass """) src2after=trimLines(""" class TheClass: pass """) try: createPackageStructure(src1, "") moveClassToNewModule(pkgstructureFile1,2, pkgstructureFile2) save() self.assertEqual(src1after,file(pkgstructureFile1).read()) self.assertEqual(src2after,file(pkgstructureFile2).read()) finally: removePackageStructure() class TestMoveFunction(BRMTestCase): def test_importsNameReference(self): src1=trimLines(""" a = 'hello' def theFunction(self): print a """) src2after=trimLines(""" from a.foo import a def theFunction(self): print a """) self.helper(src1, src2after) def test_importsExternalReference(self): src0=(""" a = 'hello' """) src1=trimLines(""" from top import a def theFunction(self): print a """) src2after=trimLines(""" from top import a def theFunction(self): print a """) try: createPackageStructure(src1, "", src0) moveFunctionToNewModule(pkgstructureFile1,2, pkgstructureFile2) save() self.assertEqual(src2after,file(pkgstructureFile2).read()) finally: removePackageStructure() def test_doesntImportRefCreatedInFunction(self): src1=trimLines(""" def theFunction(self): a = 'hello' print a """) src2after=trimLines(""" def theFunction(self): a = 'hello' print a """) self.helper(src1, src2after) def test_doesntImportRefCreatedInFunction(self): src1=trimLines(""" def theFunction(self): a = 'hello' print a """) src2after=trimLines(""" def theFunction(self): a = 'hello' print a """) self.helper(src1, src2after) def test_addsImportStatementToOriginalFileIfRequired(self): src1=trimLines(""" def theFunction(self): pass b = theFunction() """) src1after=trimLines(""" from a.b.bah import theFunction b = theFunction() """) try: createPackageStructure(src1,"") moveFunctionToNewModule(pkgstructureFile1,1, pkgstructureFile2) save() self.assertEqual(src1after,file(pkgstructureFile1).read()) finally: removePackageStructure() def test_updatesFromImportStatementsInOtherModules(self): src0=trimLines(""" from a.foo import theFunction print theFunction() """) src1=trimLines(""" def theFunction(self): pass """) src0after=trimLines(""" from a.b.bah import theFunction print theFunction() """) try: createPackageStructure(src1,"",src0) moveFunctionToNewModule(pkgstructureFile1,1, pkgstructureFile2) save() self.assertEqual(src0after,file(pkgstructureFile0).read()) finally: removePackageStructure() def test_updatesFromImportMultiplesInOtherModules(self): src0=trimLines(""" from a.foo import something,theFunction,somethingelse #comment print theFunction() """) src1=trimLines(""" def theFunction(self): pass something = '' somethingelse = 0 """) src0after=trimLines(""" from a.foo import something,somethingelse #comment from a.b.bah import theFunction print theFunction() """) try: createPackageStructure(src1,"",src0) moveFunctionToNewModule(pkgstructureFile1,1, pkgstructureFile2) save() self.assertEqual(src0after,file(pkgstructureFile0).read()) finally: removePackageStructure() def test_updatesFromImportMultiplesInTargetModule(self): src0=trimLines(""" from a.foo import something,theFunction,somethingelse #comment print theFunction() """) src1=trimLines(""" def theFunction(self): pass something = '' somethingelse = 0 """) src0after=trimLines(""" from a.foo import something,somethingelse #comment print theFunction() def theFunction(self): pass """) try: createPackageStructure(src1,"",src0) moveFunctionToNewModule(pkgstructureFile1,1, pkgstructureFile0) save() #print file(pkgstructureFile0).read() self.assertEqual(src0after,file(pkgstructureFile0).read()) finally: removePackageStructure() def test_updatesFromImportInTargetModule(self): src0=trimLines(""" from a.foo import theFunction print theFunction() """) src1=trimLines(""" def theFunction(self): pass """) src0after=trimLines(""" print theFunction() def theFunction(self): pass """) try: createPackageStructure(src1,"",src0) moveFunctionToNewModule(pkgstructureFile1,1, pkgstructureFile0) save() self.assertEqual(src0after,file(pkgstructureFile0).read()) finally: removePackageStructure() def helper(self, src1, src2after): try: createPackageStructure(src1, "") moveFunctionToNewModule(pkgstructureFile1,2, pkgstructureFile2) save() self.assertEqual(src2after,file(pkgstructureFile2).read()) finally: removePackageStructure() if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_rename.py0100755000076400007640000000175110012406272017730 0ustar pldpld#!/usr/bin/env python import setpath import unittest from bike import testdata from rename import rename from bike.testutils import * class TestRenameTemporary(BRMTestCase): def test_renamesSimpleReferencesGivenAssignment(self): src=trimLines(""" def foo(): a = 3 print a """) srcAfter=trimLines(""" def foo(): b = 3 print b """) src = self.helper(src,"",2,4,"b") self.assertEqual(srcAfter,src) def helper(self, src, classsrc, line, col, newname): try: createPackageStructure(src,classsrc) filename = pkgstructureFile1 rename(filename,line,col,newname) # modify me once save is moved #return readFile(filename) from bike.transformer.save import outputqueue return outputqueue[filename] finally: removePackageStructure() if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_renameClass.py0100755000076400007640000001554410010507074020723 0ustar pldpld#!/usr/bin/env python import setpath import unittest from rename import rename from bike.transformer.save import save from bike.testutils import * import compiler class RenameClassTests: def testRenamesClassDcl(self): srcBefore=trimLines(""" class TheClass: def theMethod(): pass """) srcAfter=trimLines(""" class NewName: def theMethod(): pass """) src = self.rename(srcBefore, 1,6,"NewName") self.assertEqual(srcAfter,src) # i.e. a = TheClass() def testRenamesClassReference(self): srcBefore=trimLines(""" class TheClass: pass a = TheClass() """) srcAfter=trimLines(""" class NewName: pass a = NewName() """) src = self.rename(srcBefore, 1,6,"NewName") self.assertEqual(srcAfter,src) # i.e. a = TheClass.TheClass() def testRenamesClassReferenceWhenScopeIsSameNameAsClass(self): srcBefore = trimLines(""" class TheClass: class TheClass: pass a = TheClass.TheClass() """) srcAfter=trimLines(""" class TheClass: class NewName: pass a = TheClass.NewName() """) src = self.rename(srcBefore, 2,10, "NewName") self.assertEqual(srcAfter,src) # i.e. a = TheClass.TheClass() def testRenamesClassReferenceWhenChildIsSameNameAsClass(self): srcBefore = trimLines(""" class TheClass: class TheClass: pass a = TheClass.TheClass() """) srcAfter=trimLines(""" class NewName: class TheClass: pass a = NewName.TheClass() """) src = self.rename(srcBefore, 1,6,"NewName") self.assertEqual(srcAfter,src) # a = TheClass() + TheClass() def testRenamesClassReferenceWhenTwoRefsInTheSameLine(self): srcBefore=trimLines(""" class TheClass: pass a = TheClass() + TheClass() """) srcAfter=trimLines(""" class NewName: pass a = NewName() + NewName() """) src = self.rename(srcBefore,1,6, "NewName") self.assertEqual(srcAfter,src) def testRenamesClassReferenceInInstanceCreation(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass TheClass().theMethod() """) srcAfter=trimLines(""" class NewName: def theMethod(self): pass NewName().theMethod() """) src = self.rename(srcBefore,1,6,"NewName") self.assertEqual(srcAfter,src) # i.e. if renaming TheClass, shouldnt rename a.b.c.TheClass def testDoesntRenameBugusClassReferenceOnEndOfGetattrNest(self): srcBefore=trimLines(""" class TheClass: pass a.b.c.TheClass # Shouldn't be renamed """) srcAfter=trimLines(""" class NewName: pass a.b.c.TheClass # Shouldn't be renamed """) src = self.rename(srcBefore,1,6,"NewName") self.assertEqual(srcAfter,src) def testRenamesClassRefUsedInExceptionRaise(self): srcBefore=trimLines(""" class TheClass: pass raise TheClass, \"hello mum\" """) srcAfter=trimLines(""" class NewName: pass raise NewName, \"hello mum\" """) src = self.rename(srcBefore, 1,6, "NewName") self.assertEqual(srcAfter,src) def testRenamesClassReferenceNameInInheritenceSpec(self): srcBefore=trimLines(""" class TheClass: pass class DerivedClass(TheClass): pass """) srcAfter=trimLines(""" class NewName: pass class DerivedClass(NewName): pass """) src = self.rename(srcBefore, 1,6, "NewName") self.assertEqual(srcAfter,src) class RenameClassTests_importsClass: def testRenamesClassReferenceInInstanceCreationWithFQN(self): srcBefore=trimLines(""" import b.bah def foo(): a = b.bah.TheClass() """) srcAfter=trimLines(""" import b.bah def foo(): a = b.bah.NewName() """) src = self.renameClass(srcBefore,"NewName") self.assertEqual(srcAfter,src) def testRenamesClassReferencesInInheritenceSpecs(self): srcBefore=trimLines(""" import b class DerivedClass(b.bah.TheClass): pass """) srcAfter=trimLines(""" import b class DerivedClass(b.bah.NewName): pass """) src = self.renameClass(srcBefore,"NewName") self.assertEqual(srcAfter,src) def testRenamesFromImportReferenceWhenInBodyOfClass(self): srcBefore=trimLines(""" class AnotherClass: from b.bah import TheClass TheClass.baz = 0 """) srcAfter=trimLines(""" class AnotherClass: from b.bah import NewName NewName.baz = 0 """) src = self.renameClass(srcBefore,"NewName") self.assertEqual(srcAfter,src) def testRenamesReferenceToClassImportedInSameClassScope(self): srcBefore=trimLines(""" class AnotherClass: from b.bah import TheClass TheClass.baz = 0 """) srcAfter=trimLines(""" class AnotherClass: from b.bah import NewName NewName.baz = 0 """) src = self.renameClass(srcBefore,"NewName") self.assertEqual(srcAfter,src) def testRenamesReferenceToClassImportedWithFromImportStar(self): srcBefore=trimLines(""" from a.b.bah import * a = TheClass() """) srcAfter=trimLines(""" from a.b.bah import * a = NewName() """) src = self.renameClass(srcBefore,"NewName") self.assertEqual(srcAfter,src) class TestRenameClass(BRMTestCase, RenameClassTests): def rename(self, src, line, col, newname): createPackageStructure(src,"pass") rename(pkgstructureFile1,line,col, newname) save() return file(pkgstructureFile1).read() class TestRenameClassReferenceWithDirectoryStructure(BRMTestCase, RenameClassTests_importsClass): def renameClass(self, src, newname): createPackageStructure(src,TheClassTestdata) rename(pkgstructureFile2,1,6, newname) save() return file(pkgstructureFile1).read() TheClassTestdata = trimLines(""" class TheClass: def theMethod(self): pass def differentMethod(self): pass class DifferentClass: def theMethod(self): pass """) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_renameFunction.py0100755000076400007640000000602310010507074021433 0ustar pldpld#!/usr/bin/env python import setpath import unittest from rename import rename from bike import testdata from bike.testutils import * from bike.transformer.save import save import compiler class RenameFunctionTests: def runTarget(self, src, klassfqn, newname): # see concrete subclasses for implementation pass def testRenamesFunctionDcl(self): srcBefore=trimLines(""" def theFunction(): pass """) srcAfter=trimLines(""" def newName(): pass """) src = self.rename(srcBefore,1,4,"newName") self.assertEqual(srcAfter,src) def testDoesntBarfWhenFunctionIncludesBrackettedExpression(self): srcBefore=trimLines(""" def theFunction(): return ('\\n').strip() """) srcAfter=trimLines(""" def newName(): return ('\\n').strip() """) src = self.rename(srcBefore,1,4, "newName") self.assertEqual(srcAfter,src) class RenameFunctionTests_importsFunction: def testRenamesImportedFunctionReference(self): srcBefore=trimLines(""" import b.bah b.bah.theFunction() """) srcAfter=trimLines(""" import b.bah b.bah.newName() """) src = self.renameFunction(srcBefore,"newName") self.assertEqual(srcAfter,src) def testRenamesFunctionReferenceImportedWithFromClause(self): srcBefore=trimLines(""" from b.bah import theFunction theFunction() """) srcAfter=trimLines(""" from b.bah import newName newName() """) src = self.renameFunction(srcBefore,"newName") self.assertEqual(srcAfter,src) def testRenamesFunctionRefInImportClause(self): srcBefore=trimLines(""" import b.bah b.bah.theFunction() """) srcAfter=trimLines(""" import b.bah b.bah.newName() """) src = self.renameFunction(srcBefore,"newName") self.assertEqual(srcAfter,src) def testRenamesFunctionRefInImportFromClause(self): srcBefore=trimLines(""" from b.bah import theFunction theFunction() """) srcAfter=trimLines(""" from b.bah import newName newName() """) src = self.renameFunction(srcBefore,"newName") self.assertEqual(srcAfter,src) class TestRenameFunction(BRMTestCase, RenameFunctionTests): def rename(self, src, line, col, newname): writeTmpTestFile(src) rename(tmpfile,line,col, newname) save() return file(tmpfile).read() class TestRenameFunctionReferenceWithDirectoryStructure(BRMTestCase, RenameFunctionTests_importsFunction): def renameFunction(self, src, newname): createPackageStructure(src,FunctionTestdata) rename(pkgstructureFile2,1,4, newname) save() return file(pkgstructureFile1).read() FunctionTestdata = trimLines(""" def theFunction(): pass """) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/test_renameMethod.py0100755000076400007640000004670610012405705021102 0ustar pldpld#!/usr/bin/env python import setpath import unittest from rename import rename import compiler from bike import testdata from bike.testutils import* from bike.transformer.save import save class RenameMethodTests: def test_renamesTheMethod(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass """) srcAfter=trimLines(""" class TheClass: def newName(self): pass """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_doesntRenameMethodOfSameNameOnOtherClasses(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass class b: def theMethod(self): pass """) srcAfter=trimLines(""" class TheClass: def newName(self): pass class b: def theMethod(self): pass """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_doesntRenameOtherMethodsOfSameClass(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): a=b def aMethod(self): pass """) srcAfter=trimLines(""" class TheClass: def newName(self): a=b def aMethod(self): pass """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_renamesMethodWhenClassNestedInFunction(self): srcBefore=trimLines(""" def theFunction(): class TheClass: def theMethod(self): pass """) srcAfter=trimLines(""" def theFunction(): class TheClass: def newName(self): pass """) src = self.rename(srcBefore,3,12,"newName") self.assertEqual(srcAfter,src) def test_doesntBarfOnInheritanceHierarchies(self): srcBefore=trimLines(""" from b.bah import DifferentClass class TheClass(foo.bah): def theMethod(self): pass """) src = self.rename(srcBefore,2,8,"newName") def test_renamesMethodWhenMethodCallFromOtherMethodInSameClass(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass def anotherMethod(self): self.theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(self): pass def anotherMethod(self): self.newName() """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_doesntBarfOnNestedClasses(self): srcBefore=trimLines(""" class TheClass: class AnotherClass: pass def theMethod(self): pass """) src = self.rename(srcBefore,4,8,"newName") def test_renamesMethodWhenBaseClassesArentInAST(self): srcBefore=trimLines(""" class TheClass(notInAst): def theMethod(self): pass """) srcAfter=trimLines(""" class TheClass(notInAst): def newName(self): pass """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_renamesMethodInRelatedClasses(self): srcBefore=trimLines(""" class root: def theMethod(self): pass class a(root): def theMethod(self): pass class b(root): pass class TheClass(b): def theMethod(self): pass """) srcAfter=trimLines(""" class root: def newName(self): pass class a(root): def newName(self): pass class b(root): pass class TheClass(b): def newName(self): pass """) src = self.rename(srcBefore,13,8,"newName") self.assertEqual(srcAfter,src) def test_renameMethodDoesntBarfOnNoneAsDefaultArgToMethod(self): srcBefore=trimLines(""" class TheClass: def theMethod(self, root, flist, stack=None): pass """) src = self.rename(srcBefore,2,8,"newName") class RenameMethodTests_ImportsClass: def test_renamesMethodOnDerivedClassInstance(self): srcBefore = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass(BaseClass): pass class DerivedDerivedClass(DerivedClass): def theMethod(self): print 'hello' """) srcAfter = trimLines(""" from b.bah import TheClass as BaseClass class DerivedClass(BaseClass): pass class DerivedDerivedClass(DerivedClass): def newName(self): print 'hello' """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) class RenameMethodReferenceTests: # Generic tests. These tests are designed to be run in the context of a ui # and in a package hierarchy structure def test_doesntBarfWhenConfrontedWithComplexReturnTypes(self): src = trimLines(""" import a class TheClass: def theMethod(self): pass def bah(): return a[35] b = bah() b.theMethod() """) self.rename(src,3,8,"newName") def test_doesntbarfWhenCallMadeOnInstanceReturnedFromFnCall(self): srcBefore=trimLines(""" from foo import e class TheClass: def theMethod(self): pass ast = e().f(src) """) self.rename(srcBefore,3,8,"newName") def test_doesntStackOverflowOnRecursiveFunctions(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass def foo(a): return foo(a) """) self.rename(srcBefore,2,8,"newName") def test_renamesMethodReferenceOfInstanceCreatedInParentScopeAfterFunction(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass a = TheClass() def foo(): a.theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(self): pass a = TheClass() def foo(): a.newName() """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_renamesMethodReferenceOfInstanceObtainedByCallingFunction(self): srcBefore=trimLines(""" class TheClass: def theMethod(): pass def foo(): b = TheClass() return b a = foo() a.theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(): pass def foo(): b = TheClass() return b a = foo() a.newName() """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_renamesMethodReferenceOfInstanceCreatedInAnotherFunction(self): srcBefore=trimLines(""" class TheClass: def theMethod(): pass def bah(): return TheClass() def foo(): a = bah() a.theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(): pass def bah(): return TheClass() def foo(): a = bah() a.newName() """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_renamesMethodReferenceOfInstanceCreatedInSubsequentFunction(self): srcBefore = trimLines(""" class TheClass: def theMethod(): pass class NotTheClass: def theMethod(): pass def foo(): a = bah() a.theMethod() def bah(): return TheClass() """) srcAfter=trimLines(""" class TheClass: def newName(): pass class NotTheClass: def theMethod(): pass def foo(): a = bah() a.newName() def bah(): return TheClass() """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_renamesMethodReferenceOnInstanceThatIsAnAttributeOfSelf(self): srcBefore = trimLines(""" class TheClass: def theMethod(self): pass class AnotherClass: def __init__(self): self.a = TheClass() def anotherFn(self): self.a.theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(self): pass class AnotherClass: def __init__(self): self.a = TheClass() def anotherFn(self): self.a.newName() """) src = self.rename(srcBefore,2,8,"newName") self.assertEqual(srcAfter,src) def test_doesntBarfOnGetattrThatItCantDeduceTypeOf(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass a = TheClass a.b.bah = 3 """) self.rename(srcBefore,2,8,"newName") class RenameMethodReferenceTests_ImportsClass: def test_renamesReferenceOfClassImportedAsAnotherName(self): srcBefore=trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.theMethod() """) srcAfter=trimLines(""" from b.bah import TheClass as MyTheClass def foo(): a = MyTheClass() a.newName() """) src = self.renameMethod(srcBefore,2,8, "newName") self.assertEqual(srcAfter,src) def test_renamesReferenceWhenObjectCreationAndReferenceInModuleScope(self): srcBefore=trimLines(""" from b.bah import TheClass a = TheClass() a.theMethod() """) srcAfter=trimLines(""" from b.bah import TheClass a = TheClass() a.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) def test_renamesReferenceWhenObjectCreatedInSameFunctionAsReference(self): srcBefore=trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.theMethod() """) srcAfter=trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) def test_doesntrenameDifferentMethodReferenceWhenObjectCreatedInSameScope(self): srcBefore=trimLines(""" import b.bah.TheClass def foo(): a = b.bah.TheClass() a.theMethod() """) src = self.renameMethod(srcBefore, 4,8, "newName") self.assertEqual(srcBefore,src) def test_doesntrenameMethodReferenceWhenDifferentObjectCreatedInSameScope(self): srcBefore=trimLines(""" import b.bah.TheClass def foo(): a = b.bah.TheClass() a.theMethod() """) src = self.renameMethod(srcBefore, 8,8,"newName") self.assertEqual(srcBefore,src) def test_renamesReferenceOfImportedClass(self): srcBefore=trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.theMethod() """) srcAfter=trimLines(""" import b.bah def foo(): a = b.bah.TheClass() a.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) def test_doesntRenameReferenceOfDifferentImportedClass(self): srcBefore=trimLines(""" from b.bah import DifferentClass def foo(): a = b.bah.TheClass() a.theMethod() """) src = self.renameMethod(srcBefore, 8,8, "newName") self.assertEqual(srcBefore,src) def test_renamesReferenceOfClassImportedWithFromClause(self): srcBefore=trimLines(""" from b.bah import TheClass def foo(): a = TheClass() a.theMethod() """) srcAfter=trimLines(""" from b.bah import TheClass def foo(): a = TheClass() a.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) def test_doesntrenameReferenceOfClassImportedWithDifferentAsClause(self): srcBefore = trimLines(""" from b.bah import TheClass as MyClass def foo(): a = TheClass() a.theMethod() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcBefore,src) def test_renamesReferenceOfClassImportedWithFromFooImportStar(self): srcBefore=trimLines(""" from b.bah import * a = TheClass() a.theMethod() """) srcAfter=trimLines(""" from b.bah import * a = TheClass() a.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) def test_renamesMethodReferenceOfInstanceCreatedInParentScope(self): srcBefore=trimLines(""" from b.bah import TheClass a = TheClass() def foo(): a.theMethod() """) srcAfter=trimLines(""" from b.bah import TheClass a = TheClass() def foo(): a.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) def test_doesntRenameMethodWhenObjectCreatedInChildScopeToMethodReference(self): srcBefore = trimLines(""" from b.bah import TheClass a = AnotherClass() def foo(): a = TheClass() a.theMethod() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcBefore,src) def test_renamesReferenceOnDerivedClassInstance(self): srcBefore=trimLines(""" import b class DerivedClass(b.bah.TheClass): pass class DerivedDerivedClass(DerivedClass): pass theInstance = DerivedDerivedClass() theInstance.theMethod() """) srcAfter=trimLines(""" import b class DerivedClass(b.bah.TheClass): pass class DerivedDerivedClass(DerivedClass): pass theInstance = DerivedDerivedClass() theInstance.newName() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) # tests that cover stuff not renamed automatically # (I.e. are renamed after user manually expresses desire to do so) class RenameMethodAfterPromptTests: def test_renamesReferenceWhenMethodCallDoneOnInstanceCreation(self): srcBefore=trimLines(""" class TheClass: def theMethod(self): pass TheClass().theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(self): pass TheClass().newName() """) src = self.renameMethod(srcBefore,2,8, "newName") self.assertEqual(srcAfter,src) def test_renamesReferenceInMiddleOfBiggerCompoundCall(self): srcBefore = trimLines(""" class TheClass: def theMethod(self): return AnotherClass() TheClass().theMethod().anotherMethod() """) srcAfter=trimLines(""" class TheClass: def newName(self): return AnotherClass() TheClass().newName().anotherMethod() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) class TestRenameMethodWithSingleModule(BRMTestCase, RenameMethodTests, RenameMethodReferenceTests): # template method def rename(self, src, line, col, newname): try: createPackageStructure(src, "pass") rename(pkgstructureFile1,line,col,newname) save() return file(pkgstructureFile1).read() finally: removePackageStructure() class TestRenameMethodWithDirectoryStructure(RenameMethodTests, RenameMethodReferenceTests, BRMTestCase): def rename(self, src, line, col, newname): try: createPackageStructure("pass",src) rename(pkgstructureFile2,line,col,newname) save() return file(pkgstructureFile2).read() finally: removePackageStructure() class TestRenameMethodReferenceWithDirectoryStructure(BRMTestCase, RenameMethodTests_ImportsClass, RenameMethodReferenceTests_ImportsClass): def renameMethod(self, src, line, col, newname): try: createPackageStructure(src,MethodTestdata) rename(pkgstructureFile2,line,col,newname) save() return file(pkgstructureFile1).read() finally: removePackageStructure() class TestRenameMethodStuffCorrectlyAfterPromptReturnsTrue(BRMTestCase, RenameMethodAfterPromptTests): def callback(self, filename, line, colbegin, colend): return 1 def renameMethod(self, src, line, col, newname): createPackageStructure(src, MethodTestdata) rename(pkgstructureFile1,line,col,newname,self.callback) save() return file(pkgstructureFile1).read() class TestDoesntRenameMethodIfPromptReturnsFalse(BRMTestCase): def callback(self, filename, line, colbegin, colend): return 0 def renameMethod(self, src, line, col, newname): createPackageStructure(src, MethodTestdata) rename(pkgstructureFile1,line,col,newname,self.callback) save() return file(pkgstructureFile1).read() def test_doesntRenameMethodIfPromptReturnsFalse(self): srcBefore = trimLines(""" class TheClass: def theMethod(self): pass b = TheClass() b.theMethod() a = someFunction() a.theMethod() """) srcAfter=trimLines(""" class TheClass: def newName(self): pass b = TheClass() b.newName() a = someFunction() a.theMethod() """) src = self.renameMethod(srcBefore, 2,8, "newName") self.assertEqual(srcAfter,src) MethodTestdata = trimLines(""" class TheClass: def theMethod(self): pass def differentMethod(self): pass class DifferentClass: def theMethod(self): pass """) if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/testall.py0100755000076400007640000000052610011741523017071 0ustar pldpld#!/usr/bin/env python import setpath from test_renameMethod import * from test_renameClass import * from test_renameFunction import * from test_rename import * from test_extractMethod import * from test_inlineVariable import * from test_extractVariable import * from test_moveToModule import * if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/refactor/utils.py0100664000076400007640000000115007635313753016574 0ustar pldpldimport re def getLineSeperator(line): if line.endswith("\r\n"): linesep = "\r\n" # windoze else: linesep = line[-1] # mac or unix return linesep def getTabWidthOfLine(line): match = re.match("\s+",line) if match is None: return 0 else: return match.end(0) def reverseCoordsIfWrongWayRound(startcoords,endcoords): if(startcoords.line > endcoords.line) or \ (startcoords.line == endcoords.line and \ startcoords.column > endcoords.column): return endcoords,startcoords else: return startcoords,endcoords bicyclerepair-0.9/bike/transformer/0040775000076400007640000000000010036707253015614 5ustar pldpldbicyclerepair-0.9/bike/transformer/WordRewriter.py0100644000076400007640000000343210010510533020603 0ustar pldpldfrom bike.parsing.load import getSourceNode from bike.transformer.undo import getUndoStack from bike.transformer.save import queueFileToSave import re # This class maintains a set of changed lines to the original source # nodes. This is important because the act of changing a line messes # up the coordinates on which renames are done. # Commit writes the changes back to the source nodes class WordRewriter: def __init__(self): self.modifiedsrc = {} def rewriteString(self, srcnode, lineno, colno, newname): filename = srcnode.filename if not self.modifiedsrc.has_key(filename): getUndoStack().addSource(filename,srcnode.getSource()) self.modifiedsrc[filename] = {} if not self.modifiedsrc[filename].has_key(lineno): line = srcnode.getLines()[lineno-1] self.modifiedsrc[filename][lineno] = self._lineToDict(line) self.modifiedsrc[filename][lineno][colno] = newname # writes all the changes back to the src nodes def commit(self): for filename in self.modifiedsrc.keys(): srcnode = getSourceNode(filename) for lineno in self.modifiedsrc[filename]: lines = srcnode.getLines() lines[lineno-1] = self._dictToLine(self.modifiedsrc[filename][lineno]) queueFileToSave(filename,"".join(srcnode.getLines())) # this function creates a dictionary with each word referenced by # its column position in the original line def _lineToDict(self, line): words = re.split("(\w+)", line) h = {};i = 0 for word in words: h[i] = word i+=len(word) return h def _dictToLine(self, d): cols = d.keys() cols.sort() return "".join([d[colno]for colno in cols]) bicyclerepair-0.9/bike/transformer/__init__.py0100644000076400007640000000014207562157654017733 0ustar pldpld""" Package containing modules which transform the internal representation of the sourcecode. """ bicyclerepair-0.9/bike/transformer/save.py0100664000076400007640000000155210010510541017104 0ustar pldpldfrom bike import log outputqueue = {} def getQueuedFile(filename): try: return outputqueue[filename] except: pass #print "HERE!" def resetOutputQueue(): global outputqueue outputqueue = {} def queueFileToSave(filename,src): outputqueue[filename] = src from bike.parsing.load import getSourceNode getSourceNode(filename).resetWithSource(src) def save(): from bike.transformer.undo import getUndoStack global outputqueue savedFiles = [] for filename,src in outputqueue.iteritems(): print >> log.progress, "Writing:",filename f = file(filename, "w+") f.write(outputqueue[filename]) f.close() savedFiles.append(filename) outputqueue = {} #print "stack is "+ str(getUndoStack().stack) getUndoStack().commitUndoFrame() return savedFiles bicyclerepair-0.9/bike/transformer/setpath.py0100644000076400007640000000033410010124470017613 0ustar pldpldimport sys,os if not os.path.abspath("..") in sys.path: from bike import log print >> log.warning, "Appending to the system path. This should only happen in unit tests" sys.path.append(os.path.abspath("..")) bicyclerepair-0.9/bike/transformer/testall.py0100755000076400007640000000014010010126320017605 0ustar pldpld#!/usr/bin/env python #from test_undo import * if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/transformer/undo.py0100644000076400007640000000270710010125414017115 0ustar pldpldfrom bike import log from bike.transformer.save import queueFileToSave _undoStack = None def getUndoStack(forceNewStack = 0): global _undoStack if _undoStack is None or forceNewStack: _undoStack = UndoStack() return _undoStack class UndoStackEmptyException: pass class UndoStack(object): def __init__(self): self.stack = [] self.stack.append({}) self.frame = self.stack[-1] self.setUndoBufferSize(10) def setUndoBufferSize(self, undoBufferSize): self.undoBufferSize = undoBufferSize def addSource(self, filename, src): if filename not in self.frame: self.frame[filename] = src def commitUndoFrame(self): #restrict size of buffer while len(self.stack) > self.undoBufferSize: #print "clipping undo stack" del self.stack[0] if len(self.frame) != 0: #print "commitUndoFrame" self.stack.append({}) self.frame = self.stack[-1] def undo(self, **opts): #print "undo called",self.stack if len(self.stack) < 2: raise UndoStackEmptyException() undoframe = self.stack[-2] #print "undoframe is",undoframe for filename,src in undoframe.iteritems(): print >>log.progress, "Undoing:",filename queueFileToSave(filename,src) self.stack = self.stack[:-2] self.stack.append({}) self.frame = self.stack[-1] bicyclerepair-0.9/bike/__init__.py0100644000076400007640000000047007722313346015363 0ustar pldpld# The root bicyclerepairman package # do: # --------------------- # import bike # ctx = bike.load() # --------------------- # to instantiate a bicyclerepairman context object from bikefacade import init, NotAPythonModuleOrPackageException, CouldntLocateASTNodeFromCoordinatesException, UndoStackEmptyException bicyclerepair-0.9/bike/bikefacade.py0100644000076400007640000002301610012114176015645 0ustar pldpldimport os import sys import compiler from parser import ParserError from bike.parsing.pathutils import getRootDirectory from bike.refactor import extractMethod from bike.refactor.rename import rename from bike.refactor.extractMethod import coords from bike.transformer.save import save as saveUpdates from bike.parsing.utils import fqn_rcar, fqn_rcdr from bike.parsing import visitor from bike.transformer.undo import getUndoStack, UndoStackEmptyException from bike.parsing.fastparserast import getRoot, Class, Function from bike.query.common import getScopeForLine from bike.query.getTypeOf import getTypeOfExpr, UnfoundType from bike.query.findReferences import findReferences from bike.query.findDefinition import findAllPossibleDefinitionsByCoords from bike.refactor import inlineVariable, extractVariable, moveToModule from bike.parsing.load import Cache from bike import log def init(): #context = BRMContext_impl() context = BRMContext_wrapper() return context # the context object public interface class BRMContext(object): def save(self): """ save the changed files out to disk """ def setRenameMethodPromptCallback(self, callback): """ sets a callback to ask the user about method refs which brm can't deduce the type of. The callback must be callable, and take the following parameters: - filename - linenumber - begin column - end column (begin and end columns enclose the problematic method call) """ def renameByCoordinates(self, filename_path, line, col, newname): """ an ide friendly method which renames a class/fn/method pointed to by the coords and filename""" def extract(self, filename_path, begin_line, begin_col, end_line, end_col, name): """ extracts the region into the named method/function based on context""" def inlineLocalVariable(self,filename_path, line, col): """ Inlines the variable pointed to by line:col. (N.B. line:col can also point to a reference to the variable as well as the definition) """ def extractLocalVariable(self,filename_path, begin_line, begin_col, end_line, end_col, variablename): """ Extracts the region into a variable """ def setProgressLogger(self,logger): """ Sets the progress logger to an object with a write method """ def setWarningLogger(self,logger): """ Sets the warning logger to an object with a write method """ def undo(self): """ undoes the last refactoring. WARNING: this is dangerous if the user has modified files since the last refactoring. Raises UndoStackEmptyException""" def findReferencesByCoordinates(self, filename_path, line, column): """ given the coords of a function, class, method or variable returns a generator which finds references to it. """ def findDefinitionByCoordinates(self,filename_path,line,col): """ given the coordates to a reference, tries to find the definition of that reference """ def moveClassToNewModule(self,filename_path, line, newfilename): """ moves the class pointed to by (filename_path, line) to a new module """ class NotAPythonModuleOrPackageException: pass class CouldntLocateASTNodeFromCoordinatesException: pass # Wrapper to ensure that caches are purged on each request class BRMContext_wrapper: def __init__(self): self.brmctx = BRMContext_impl() def __getattr__(self,name): return BRMContext_callWrapper(self.brmctx,name) class BRMContext_callWrapper: def __init__(self,brmctx,methodname): self.name = methodname self.brmctx = brmctx def __call__(self,*args): Cache.instance.reset() try: return getattr(self.brmctx,self.name)(*args) finally: Cache.instance.reset() class BRMContext_impl(BRMContext): def __init__(self): self.ast = getRoot() # Used because some refactorings delegate back to the user. # this flag ensures that code isnt imported during those times self.readyToLoadNewCode = 1 self.paths = [] getUndoStack(1) # force new undo stack if not getRoot().unittestmode: log.warning = sys.stderr self.promptUserClientCallback = None def _getAST(self): return self.ast # returns a list of saved filenames def save(self): savedfiles = saveUpdates() return savedfiles def setRenameMethodPromptCallback(self, callback): self.promptUserClientCallback = callback def normalizeFilename(self,filename): filename = os.path.expanduser(filename) filename = os.path.normpath(os.path.abspath(filename)) return filename def extractMethod(self, filename_path, begin_line, begin_column, end_line, end_column, methodname): self.extract(filename_path, begin_line, begin_column, end_line, end_column,methodname) def extractFunction(self, filename_path, begin_line, begin_column, end_line, end_column, methodname): self.extract(filename_path, begin_line, begin_column, end_line, end_column,methodname) # does it based on context def extract(self, filename_path, begin_line, begin_col, end_line, end_col, name): filename_path = self.normalizeFilename(filename_path) extractMethod.extractMethod(filename_path, coords(begin_line, begin_col), coords(end_line, end_col), name) def inlineLocalVariable(self,filename_path, line, col): filename_path = self.normalizeFilename(filename_path) inlineVariable.inlineLocalVariable(filename_path,line,col) def extractLocalVariable(self,filename_path, begin_line, begin_col, end_line, end_col, variablename): filename_path = self.normalizeFilename(filename_path) extractVariable.extractLocalVariable(filename_path, coords(begin_line, begin_col), coords(end_line, end_col), variablename) def moveClassToNewModule(self,filename_path, line, newfilename): filename_path = self.normalizeFilename(filename_path) newfilename = self.normalizeFilename(newfilename) moveToModule.moveClassToNewModule(filename_path, line, newfilename) def undo(self): getUndoStack().undo() def _promptUser(self, filename, lineno, colbegin, colend): return self.promptUserClientCallback(filename, lineno, colbegin, colend) # must be an object with a write method def setProgressLogger(self,logger): log.progress = logger # must be an object with a write method def setWarningLogger(self,logger): log.warning = logger # filename_path must be absolute def renameByCoordinates(self, filename_path, line, col, newname): filename_path = self.normalizeFilename(filename_path) Cache.instance.reset() try: self._setNonLibPythonPath(filename_path) rename(filename_path,line,col,newname, self.promptUserClientCallback) finally: Cache.instance.reset() def _reverseCoordsIfWrongWayRound(self, colbegin, colend): if(colbegin > colend): colbegin,colend = colend,colbegin return colbegin,colend def findDefinitionByCoordinates(self,filename_path,line,col): filename_path = self.normalizeFilename(filename_path) self._setCompletePythonPath(filename_path) return findAllPossibleDefinitionsByCoords(filename_path,line,col) # filename_path must be absolute def findReferencesByCoordinates(self, filename_path, line, column): filename_path = self.normalizeFilename(filename_path) self._setNonLibPythonPath(filename_path) return findReferences(filename_path,line,column) def refreshASTFromFileSystem(self): for path in self.paths: self.ast = loadast(path, self.ast) def _setCompletePythonPath(self,filename): pythonpath = [] + sys.path # make a copy self.ast.pythonpath = pythonpath def _setNonLibPythonPath(self,filename): if getRoot().unittestmode: return pythonpath = self._removeLibdirsFromPath(sys.path) pythonpath = [os.path.abspath(p) for p in pythonpath] self.ast.pythonpath = pythonpath def _getCurrentSearchPath(self): return self.ast.pythonpath def _removeLibdirsFromPath(self, pythonpath): libdir = os.path.join(sys.prefix,"lib").lower() pythonpath = [p for p in pythonpath if not p.lower().startswith(libdir)] return pythonpath def _deducePackageOfFile(filename): package = "" dot = "" dir = os.path.dirname(filename) while dir != ""and \ os.path.exists(os.path.join(dir, "__init__.py")): dir, dirname = os.path.split(dir) package = dirname+dot+package dot = "." return package bicyclerepair-0.9/bike/globals.py0100664000076400007640000000006210007412654015236 0ustar pldpldtry: True = 1 False = 0 except: pass bicyclerepair-0.9/bike/log.py0100644000076400007640000000021407722363565014411 0ustar pldpldimport sys class SilentLogger: def write(*args): pass progress = SilentLogger() warning = SilentLogger() #warning = sys.stderr bicyclerepair-0.9/bike/logging.py0100644000076400007640000021110107523450013015235 0ustar pldpld#! /usr/bin/env python # # Copyright 2001-2002 by Vinay Sajip. All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, # provided that the above copyright notice appear in all copies and that # both that copyright notice and this permission notice appear in # supporting documentation, and that the name of Vinay Sajip # not be used in advertising or publicity pertaining to distribution # of the software without specific, written prior permission. # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # For the change history, see README.txt in the distribution. # # This file is part of the Python logging distribution. See # http://www.red-dove.com/python_logging.html # """ Logging module for Python. Based on PEP 282 and comments thereto in comp.lang.python, and influenced by Apache's log4j system. Should work under Python versions >= 1.5.2, except that source line information is not available unless 'inspect' is. Copyright (C) 2001-2002 Vinay Sajip. All Rights Reserved. To use, simply 'import logging' and log away! """ import sys, os, types, time, string, socket, cPickle, cStringIO from SocketServer import ThreadingTCPServer, StreamRequestHandler try: import thread except ImportError: thread = None try: import inspect except ImportError: inspect = None __author__ = "Vinay Sajip " __status__ = "alpha" __version__ = "0.4.5" __date__ = "4 June 2002" #--------------------------------------------------------------------------- # Miscellaneous module data #--------------------------------------------------------------------------- # #_srcfile is used when walking the stack to check when we've got the first # caller stack frame. #If run as a script, __file__ is not bound. # if __name__ == "__main__": _srcfile = None else: if string.lower(__file__[-4:]) in ['.pyc', '.pyo']: _srcfile = __file__[:-4] + '.py' else: _srcfile = __file__ _srcfile = os.path.normcase(_srcfile) # #_startTime is used as the base when calculating the relative time of events # _startTime = time.time() # # Some constants... # DEFAULT_TCP_LOGGING_PORT = 9020 DEFAULT_UDP_LOGGING_PORT = 9021 DEFAULT_HTTP_LOGGING_PORT = 9022 DEFAULT_SOAP_LOGGING_PORT = 9023 DEFAULT_LOGGING_CONFIG_PORT = 9030 SYSLOG_UDP_PORT = 514 #--------------------------------------------------------------------------- # Level related stuff #--------------------------------------------------------------------------- # # Default levels and level names, these can be replaced with any positive set # of values having corresponding names. There is a pseudo-level, ALL, which # is only really there as a lower limit for user-defined levels. Handlers and # loggers are initialized with ALL so that they will log all messages, even # at user-defined levels. # CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARN = 30 INFO = 20 DEBUG = 10 ALL = 0 _levelNames = { CRITICAL : 'CRITICAL', ERROR : 'ERROR', WARN : 'WARN', INFO : 'INFO', DEBUG : 'DEBUG', ALL : 'ALL', 'CRITICAL' : CRITICAL, 'ERROR' : ERROR, 'WARN' : WARN, 'INFO' : INFO, 'DEBUG' : DEBUG, 'ALL' : ALL, } def getLevelName(lvl): """ Return the textual representation of logging level 'lvl'. If the level is one of the predefined levels (CRITICAL, ERROR, WARN, INFO, DEBUG) then you get the corresponding string. If you have associated levels with names using addLevelName then the name you have associated with 'lvl' is returned. Otherwise, the string "Level %s" % lvl is returned. """ return _levelNames.get(lvl, ("Level %s" % lvl)) def addLevelName(lvl, levelName): """ Associate 'levelName' with 'lvl'. This is used when converting levels to text during message formatting. """ _acquireLock() try: #unlikely to cause an exception, but you never know... _levelNames[lvl] = levelName _levelNames[levelName] = lvl finally: _releaseLock() #--------------------------------------------------------------------------- # Thread-related stuff #--------------------------------------------------------------------------- # #_lock is used to serialize access to shared data structures in this module. #This needs to be an RLock because fileConfig() creates Handlers and so #might arbitrary user threads. Since Handler.__init__() updates the shared #dictionary _handlers, it needs to acquire the lock. But if configuring, #the lock would already have been acquired - so we need an RLock. #The same argument applies to Loggers and Manager.loggerDict. # _lock = None def _acquireLock(): """ Acquire the module-level lock for serializing access to shared data. This should be released with _releaseLock(). """ global _lock if (not _lock) and thread: import threading #this had better work _lock = threading.RLock() if _lock: _lock.acquire() def _releaseLock(): """ Release the module-level lock acquired by calling _acquireLock(). """ if _lock: _lock.release() #--------------------------------------------------------------------------- # The logging record #--------------------------------------------------------------------------- class LogRecord: """ LogRecord instances are created every time something is logged. They contain all the information pertinent to the event being logged. The main information passed in is in msg and args, which are combined using msg % args to create the message field of the record. The record also includes information such as when the record was created, the source line where the logging call was made, and any exception information to be logged. """ def __init__(self, name, lvl, pathname, lineno, msg, args, exc_info): """ Initialize a logging record with interesting information. """ ct = time.time() self.name = name self.msg = msg self.args = args self.levelname = getLevelName(lvl) self.levelno = lvl self.pathname = pathname try: self.filename = os.path.basename(pathname) self.module = os.path.splitext(self.filename)[0] except: self.filename = pathname self.module = "Unknown module" self.exc_info = exc_info self.lineno = lineno self.created = ct self.msecs = (ct - long(ct)) * 1000 self.relativeCreated = (self.created - _startTime) * 1000 if thread: self.thread = thread.get_ident() else: self.thread = None def __str__(self): return ''%(self.name, self.levelno, self.pathname, self.lineno, self.msg) def getMessage(self): """ Return the message for this LogRecord, merging any user-supplied arguments with the message. """ msg = str(self.msg) if self.args: msg = msg % self.args return msg #--------------------------------------------------------------------------- # Formatter classes and functions #--------------------------------------------------------------------------- class Formatter: """ Formatters need to know how a LogRecord is constructed. They are responsible for converting a LogRecord to (usually) a string which can be interpreted by either a human or an external system. The base Formatter allows a formatting string to be specified. If none is supplied, the default value of "%s(message)\\n" is used. The Formatter can be initialized with a format string which makes use of knowledge of the LogRecord attributes - e.g. the default value mentioned above makes use of the fact that the user's message and arguments are pre- formatted into a LogRecord's message attribute. Currently, the useful attributes in a LogRecord are described by: %(name)s Name of the logger (logging channel) %(levelno)s Numeric logging level for the message (DEBUG, INFO, WARN, ERROR, CRITICAL) %(levelname)s Text logging level for the message ("DEBUG", "INFO", "WARN", "ERROR", "CRITICAL") %(pathname)s Full pathname of the source file where the logging call was issued (if available) %(filename)s Filename portion of pathname %(module)s Module (name portion of filename) %(lineno)d Source line number where the logging call was issued (if available) %(created)f Time when the LogRecord was created (time.time() return value) %(asctime)s Textual time when the LogRecord was created %(msecs)d Millisecond portion of the creation time %(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded (typically at application startup time) %(thread)d Thread ID (if available) %(message)s The result of msg % args, computed just as the record is emitted """ def __init__(self, fmt=None, datefmt=None): """ Initialize the formatter either with the specified format string, or a default as described above. Allow for specialized date formatting with the optional datefmt argument (if omitted, you get the ISO8601 format). """ if fmt: self._fmt = fmt else: self._fmt = "%(message)s" self.datefmt = datefmt def formatTime(self, record, datefmt=None): """ This method should be called from format() by a formatter which wants to make use of a formatted time. This method can be overridden in formatters to provide for any specific requirement, but the basic behaviour is as follows: if datefmt (a string) is specified, it is used with time.strftime() to format the creation time of the record. Otherwise, the ISO8601 format is used. The resulting string is returned. """ ct = record.created if datefmt: s = time.strftime(datefmt, time.localtime(ct)) else: t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ct)) s = "%s,%03d" % (t, record.msecs) return s def formatException(self, ei): """ Format the specified exception information as a string. This default implementation just uses traceback.print_exception() """ import traceback sio = cStringIO.StringIO() traceback.print_exception(ei[0], ei[1], ei[2], None, sio) s = sio.getvalue() sio.close() if s[-1] == "\n": s = s[:-1] return s def format(self, record): """ The record's attribute dictionary is used as the operand to a string formatting operation which yields the returned string. Before formatting the dictionary, a couple of preparatory steps are carried out. The message attribute of the record is computed using msg % args. If the formatting string contains "%(asctime)", formatTime() is called to format the event time. If there is exception information, it is formatted using formatException() and appended to the message. """ record.message = record.getMessage() if string.find(self._fmt,"%(asctime)") >= 0: record.asctime = self.formatTime(record, self.datefmt) s = self._fmt % record.__dict__ if record.exc_info: if s[-1] != "\n": s = s + "\n" s = s + self.formatException(record.exc_info) return s # # The default formatter to use when no other is specified # _defaultFormatter = Formatter() class BufferingFormatter: """ A formatter suitable for formatting a number of records. """ def __init__(self, linefmt=None): """ Optionally specify a formatter which will be used to format each individual record. """ if linefmt: self.linefmt = linefmt else: self.linefmt = _defaultFormatter def formatHeader(self, records): """ Return the header string for the specified records. """ return "" def formatFooter(self, records): """ Return the footer string for the specified records. """ return "" def format(self, records): """ Format the specified records and return the result as a string. """ rv = "" if len(records) > 0: rv = rv + self.formatHeader(records) for record in records: rv = rv + self.linefmt.format(record) rv = rv + self.formatFooter(records) return rv #--------------------------------------------------------------------------- # Filter classes and functions #--------------------------------------------------------------------------- class Filter: """ The base filter class. Loggers and Handlers can optionally use Filter instances to filter records as desired. The base filter class only allows events which are below a certain point in the logger hierarchy. For example, a filter initialized with "A.B" will allow events logged by loggers "A.B", "A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If initialized with the empty string, all events are passed. """ def __init__(self, name=''): """ Initialize with the name of the logger which, together with its children, will have its events allowed through the filter. If no name is specified, allow every event. """ self.name = name self.nlen = len(name) def filter(self, record): """ Is the specified record to be logged? Returns 0 for no, nonzero for yes. If deemed appropriate, the record may be modified in-place. """ if self.nlen == 0: return 1 elif self.name == record.name: return 1 elif string.find(record.name, self.name, 0, self.nlen) != 0: return 0 return (record.name[self.nlen] == ".") class Filterer: """ A base class for loggers and handlers which allows them to share common code. """ def __init__(self): """ Initialize the list of filters to be an empty list. """ self.filters = [] def addFilter(self, filter): """ Add the specified filter to this handler. """ if not (filter in self.filters): self.filters.append(filter) def removeFilter(self, filter): """ Remove the specified filter from this handler. """ if filter in self.filters: self.filters.remove(filter) def filter(self, record): """ Determine if a record is loggable by consulting all the filters. The default is to allow the record to be logged; any filter can veto this and the record is then dropped. Returns a boolean value. """ rv = 1 for f in self.filters: if not f.filter(record): rv = 0 break return rv #--------------------------------------------------------------------------- # Handler classes and functions #--------------------------------------------------------------------------- _handlers = {} #repository of handlers (for flushing when shutdown called) class Handler(Filterer): """ The base handler class. Acts as a placeholder which defines the Handler interface. Handlers can optionally use Formatter instances to format records as desired. By default, no formatter is specified; in this case, the 'raw' message as determined by record.message is logged. """ def __init__(self, level=ALL): """ Initializes the instance - basically setting the formatter to None and the filter list to empty. """ Filterer.__init__(self) self.level = level self.formatter = None #get the module data lock, as we're updating a shared structure. _acquireLock() try: #unlikely to raise an exception, but you never know... _handlers[self] = 1 finally: _releaseLock() self.createLock() def createLock(self): """ Acquire a thread lock for serializing access to the underlying I/O. """ if thread: self.lock = thread.allocate_lock() else: self.lock = None def acquire(self): """ Acquire the I/O thread lock. """ if self.lock: self.lock.acquire() def release(self): """ Release the I/O thread lock. """ if self.lock: self.lock.release() def setLevel(self, lvl): """ Set the logging level of this handler. """ self.level = lvl def format(self, record): """ Do formatting for a record - if a formatter is set, use it. Otherwise, use the default formatter for the module. """ if self.formatter: fmt = self.formatter else: fmt = _defaultFormatter return fmt.format(record) def emit(self, record): """ Do whatever it takes to actually log the specified logging record. This version is intended to be implemented by subclasses and so raises a NotImplementedError. """ raise NotImplementedError, 'emit must be implemented '\ 'by Handler subclasses' def handle(self, record): """ Conditionally emit the specified logging record, depending on filters which may have been added to the handler. Wrap the actual emission of the record with acquisition/release of the I/O thread lock. """ if self.filter(record): self.acquire() try: self.emit(record) finally: self.release() def setFormatter(self, fmt): """ Set the formatter for this handler. """ self.formatter = fmt def flush(self): """ Ensure all logging output has been flushed. This version does nothing and is intended to be implemented by subclasses. """ pass def close(self): """ Tidy up any resources used by the handler. This version does nothing and is intended to be implemented by subclasses. """ pass def handleError(self): """ This method should be called from handlers when an exception is encountered during an emit() call. By default it does nothing, which means that exceptions get silently ignored. This is what is mostly wanted for a logging system - most users will not care about errors in the logging system, they are more interested in application errors. You could, however, replace this with a custom handler if you wish. """ #import traceback #ei = sys.exc_info() #traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr) #del ei pass class StreamHandler(Handler): """ A handler class which writes logging records, appropriately formatted, to a stream. Note that this class does not close the stream, as sys.stdout or sys.stderr may be used. """ def __init__(self, strm=None): """ If strm is not specified, sys.stderr is used. """ Handler.__init__(self) if not strm: strm = sys.stderr self.stream = strm self.formatter = None def flush(self): """ Flushes the stream. """ self.stream.flush() def emit(self, record): """ If a formatter is specified, it is used to format the record. The record is then written to the stream with a trailing newline [N.B. this may be removed depending on feedback]. If exception information is present, it is formatted using traceback.print_exception and appended to the stream. """ try: msg = self.format(record) self.stream.write("%s\n" % msg) self.flush() except: self.handleError() class FileHandler(StreamHandler): """ A handler class which writes formatted logging records to disk files. """ def __init__(self, filename, mode="a+"): """ Open the specified file and use it as the stream for logging. By default, the file grows indefinitely. You can call setRollover() to allow the file to rollover at a predetermined size. """ StreamHandler.__init__(self, open(filename, mode)) self.maxBytes = 0 self.backupCount = 0 self.baseFilename = filename #self.backupIndex = 0 self.mode = mode def setRollover(self, maxBytes, backupCount): """ Set the rollover parameters so that rollover occurs whenever the current log file is nearly maxBytes in length. If backupCount is >= 1, the system will successively create new files with the same pathname as the base file, but with extensions ".1", ".2" etc. appended to it. For example, with a backupCount of 5 and a base file name of "app.log", you would get "app.log", "app.log.1", "app.log.2", ... through to "app.log.5". When the last file reaches its size limit, the logging reverts to "app.log" which is truncated to zero length. If maxBytes is zero, rollover never occurs. """ self.maxBytes = maxBytes self.backupCount = backupCount if maxBytes > 0: self.mode = "a+" def doRollover(self): """ Do a rollover, as described in setRollover(). """ # Old algorithm # if self.backupIndex >= self.backupCount: # self.backupIndex = 0 # fn = self.baseFilename # else: # self.backupIndex = self.backupIndex + 1 # fn = "%s.%d" % (self.baseFilename, self.backupIndex) # self.stream.close() # self.stream = open(fn, "w+") self.stream.close() if self.backupCount > 0: for i in range(self.backupCount - 1, 0, -1): sfn = "%s.%d" % (self.baseFilename, i) dfn = "%s.%d" % (self.baseFilename, i + 1) if os.path.exists(sfn): #print "%s -> %s" % (sfn, dfn) if os.path.exists(dfn): os.remove(dfn) os.rename(sfn, dfn) dfn = self.baseFilename + ".1" if os.path.exists(dfn): os.remove(dfn) os.rename(self.baseFilename, dfn) self.stream = open(self.baseFilename, "w+") def emit(self, record): """ Output the record to the file, catering for rollover as described in setRollover(). """ if self.maxBytes > 0: # are we rolling over? msg = "%s\n" % self.format(record) if self.stream.tell() + len(msg) >= self.maxBytes: self.doRollover() StreamHandler.emit(self, record) def close(self): """ Closes the stream. """ self.stream.close() class SocketHandler(Handler): """ A handler class which writes logging records, in pickle format, to a streaming socket. The socket is kept open across logging calls. If the peer resets it, an attempt is made to reconnect on the next call. Note that the very simple wire protocol used means that packet sizes are expected to be encodable within 16 bits (i.e. < 32767 bytes). """ def __init__(self, host, port): """ Initializes the handler with a specific host address and port. """ Handler.__init__(self) self.host = host self.port = port self.sock = None self.closeOnError = 1 def makeSocket(self): """ A factory method which allows subclasses to define the precise type of socket they want. """ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.host, self.port)) return s def send(self, s): """ Send a pickled string to the socket. This function allows for partial sends which can happen when the network is busy. """ sentsofar = 0 left = len(s) while left > 0: sent = self.sock.send(s[sentsofar:]) sentsofar = sentsofar + sent left = left - sent def makePickle(self, record): """ Pickles the record in binary format with a length prefix, and returns it ready for transmission across the socket. """ s = cPickle.dumps(record.__dict__, 1) n = len(s) slen = "%c%c" % ((n >> 8) & 0xFF, n & 0xFF) return slen + s def handleError(self): """ An error has occurred during logging. Most likely cause - connection lost. Close the socket so that we can retry on the next event. """ if self.closeOnError and self.sock: self.sock.close() self.sock = None #try to reconnect next time def emit(self, record): """ Pickles the record and writes it to the socket in binary format. If there is an error with the socket, silently drop the packet. If there was a problem with the socket, re-establishes the socket. """ try: s = self.makePickle(record) if not self.sock: self.sock = self.makeSocket() self.send(s) except: self.handleError() def close(self): """ Closes the socket. """ if self.sock: self.sock.close() self.sock = None class DatagramHandler(SocketHandler): """ A handler class which writes logging records, in pickle format, to a datagram socket. Note that the very simple wire protocol used means that packet sizes are expected to be encodable within 16 bits (i.e. < 32767 bytes). """ def __init__(self, host, port): """ Initializes the handler with a specific host address and port. """ SocketHandler.__init__(self, host, port) self.closeOnError = 0 def makeSocket(self): """ The factory method of SocketHandler is here overridden to create a UDP socket (SOCK_DGRAM). """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) return s def send(self, s): """ Send a pickled string to a socket. This function allows for partial sends which can happen when the network is busy. """ sentsofar = 0 left = len(s) addr = (self.host, self.port) while left > 0: sent = self.sock.sendto(s[sentsofar:], addr) sentsofar = sentsofar + sent left = left - sent class SysLogHandler(Handler): """ A handler class which sends formatted logging records to a syslog server. Based on Sam Rushing's syslog module: http://www.nightmare.com/squirl/python-ext/misc/syslog.py Contributed by Nicolas Untz (after which minor refactoring changes have been made). """ # from : # ====================================================================== # priorities/facilities are encoded into a single 32-bit quantity, where # the bottom 3 bits are the priority (0-7) and the top 28 bits are the # facility (0-big number). Both the priorities and the facilities map # roughly one-to-one to strings in the syslogd(8) source code. This # mapping is included in this file. # # priorities (these are ordered) LOG_EMERG = 0 # system is unusable LOG_ALERT = 1 # action must be taken immediately LOG_CRIT = 2 # critical conditions LOG_ERR = 3 # error conditions LOG_WARNING = 4 # warning conditions LOG_NOTICE = 5 # normal but significant condition LOG_INFO = 6 # informational LOG_DEBUG = 7 # debug-level messages # facility codes LOG_KERN = 0 # kernel messages LOG_USER = 1 # random user-level messages LOG_MAIL = 2 # mail system LOG_DAEMON = 3 # system daemons LOG_AUTH = 4 # security/authorization messages LOG_SYSLOG = 5 # messages generated internally by syslogd LOG_LPR = 6 # line printer subsystem LOG_NEWS = 7 # network news subsystem LOG_UUCP = 8 # UUCP subsystem LOG_CRON = 9 # clock daemon LOG_AUTHPRIV = 10 # security/authorization messages (private) # other codes through 15 reserved for system use LOG_LOCAL0 = 16 # reserved for local use LOG_LOCAL1 = 17 # reserved for local use LOG_LOCAL2 = 18 # reserved for local use LOG_LOCAL3 = 19 # reserved for local use LOG_LOCAL4 = 20 # reserved for local use LOG_LOCAL5 = 21 # reserved for local use LOG_LOCAL6 = 22 # reserved for local use LOG_LOCAL7 = 23 # reserved for local use priority_names = { "alert": LOG_ALERT, "crit": LOG_CRIT, "critical": LOG_CRIT, "debug": LOG_DEBUG, "emerg": LOG_EMERG, "err": LOG_ERR, "error": LOG_ERR, # DEPRECATED "info": LOG_INFO, "notice": LOG_NOTICE, "panic": LOG_EMERG, # DEPRECATED "warn": LOG_WARNING, # DEPRECATED "warning": LOG_WARNING, } facility_names = { "auth": LOG_AUTH, "authpriv": LOG_AUTHPRIV, "cron": LOG_CRON, "daemon": LOG_DAEMON, "kern": LOG_KERN, "lpr": LOG_LPR, "mail": LOG_MAIL, "news": LOG_NEWS, "security": LOG_AUTH, # DEPRECATED "syslog": LOG_SYSLOG, "user": LOG_USER, "uucp": LOG_UUCP, "local0": LOG_LOCAL0, "local1": LOG_LOCAL1, "local2": LOG_LOCAL2, "local3": LOG_LOCAL3, "local4": LOG_LOCAL4, "local5": LOG_LOCAL5, "local6": LOG_LOCAL6, "local7": LOG_LOCAL7, } def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER): """ If address is specified as a string, UNIX socket is used. If facility is not specified, LOG_USER is used. """ Handler.__init__(self) self.address = address self.facility = facility if type(address) == types.StringType: self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.socket.connect(address) self.unixsocket = 1 else: self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.unixsocket = 0 self.formatter = None # curious: when talking to the unix-domain '/dev/log' socket, a # zero-terminator seems to be required. this string is placed # into a class variable so that it can be overridden if # necessary. log_format_string = '<%d>%s\000' def encodePriority (self, facility, priority): """ Encode the facility and priority. You can pass in strings or integers - if strings are passed, the facility_names and priority_names mapping dictionaries are used to convert them to integers. """ if type(facility) == types.StringType: facility = self.facility_names[facility] if type(priority) == types.StringType: priority = self.priority_names[priority] return (facility << 3) | priority def close (self): """ Closes the socket. """ if self.unixsocket: self.socket.close() def emit(self, record): """ The record is formatted, and then sent to the syslog server. If exception information is present, it is NOT sent to the server. """ msg = self.format(record) """ We need to convert record level to lowercase, maybe this will change in the future. """ msg = self.log_format_string % ( self.encodePriority(self.facility, string.lower(record.levelname)), msg) try: if self.unixsocket: self.socket.send(msg) else: self.socket.sendto(msg, self.address) except: self.handleError() class SMTPHandler(Handler): """ A handler class which sends an SMTP email for each logging event. """ def __init__(self, mailhost, fromaddr, toaddrs, subject): """ Initialize the instance with the from and to addresses and subject line of the email. To specify a non-standard SMTP port, use the (host, port) tuple format for the mailhost argument. """ Handler.__init__(self) if type(mailhost) == types.TupleType: host, port = mailhost self.mailhost = host self.mailport = port else: self.mailhost = mailhost self.mailport = None self.fromaddr = fromaddr self.toaddrs = toaddrs self.subject = subject def getSubject(self, record): """ If you want to specify a subject line which is record-dependent, override this method. """ return self.subject def emit(self, record): """ Format the record and send it to the specified addressees. """ try: import smtplib port = self.mailport if not port: port = smtplib.SMTP_PORT smtp = smtplib.SMTP(self.mailhost, port) msg = self.format(record) msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" % ( self.fromaddr, string.join(self.toaddrs, ","), self.getSubject(record), msg ) smtp.sendmail(self.fromaddr, self.toaddrs, msg) smtp.quit() except: self.handleError() class BufferingHandler(Handler): """ A handler class which buffers logging records in memory. Whenever each record is added to the buffer, a check is made to see if the buffer should be flushed. If it should, then flush() is expected to do the needful. """ def __init__(self, capacity): """ Initialize the handler with the buffer size. """ Handler.__init__(self) self.capacity = capacity self.buffer = [] def shouldFlush(self, record): """ Returns true if the buffer is up to capacity. This method can be overridden to implement custom flushing strategies. """ return (len(self.buffer) >= self.capacity) def emit(self, record): """ Append the record. If shouldFlush() tells us to, call flush() to process the buffer. """ self.buffer.append(record) if self.shouldFlush(record): self.flush() def flush(self): """ Override to implement custom flushing behaviour. This version just zaps the buffer to empty. """ self.buffer = [] class MemoryHandler(BufferingHandler): """ A handler class which buffers logging records in memory, periodically flushing them to a target handler. Flushing occurs whenever the buffer is full, or when an event of a certain severity or greater is seen. """ def __init__(self, capacity, flushLevel=ERROR, target=None): """ Initialize the handler with the buffer size, the level at which flushing should occur and an optional target. Note that without a target being set either here or via setTarget(), a MemoryHandler is no use to anyone! """ BufferingHandler.__init__(self, capacity) self.flushLevel = flushLevel self.target = target def shouldFlush(self, record): """ Check for buffer full or a record at the flushLevel or higher. """ return (len(self.buffer) >= self.capacity) or \ (record.levelno >= self.flushLevel) def setTarget(self, target): """ Set the target handler for this handler. """ self.target = target def flush(self): """ For a MemoryHandler, flushing means just sending the buffered records to the target, if there is one. Override if you want different behaviour. """ if self.target: for record in self.buffer: self.target.handle(record) self.buffer = [] def close(self): """ Flush, set the target to None and lose the buffer. """ self.flush() self.target = None self.buffer = [] class NTEventLogHandler(Handler): """ A handler class which sends events to the NT Event Log. Adds a registry entry for the specified application name. If no dllname is provided, win32service.pyd (which contains some basic message placeholders) is used. Note that use of these placeholders will make your event logs big, as the entire message source is held in the log. If you want slimmer logs, you have to pass in the name of your own DLL which contains the message definitions you want to use in the event log. """ def __init__(self, appname, dllname=None, logtype="Application"): Handler.__init__(self) try: import win32evtlogutil, win32evtlog self.appname = appname self._welu = win32evtlogutil if not dllname: dllname = os.path.split(self._welu.__file__) dllname = os.path.split(dllname[0]) dllname = os.path.join(dllname[0], r'win32service.pyd') self.dllname = dllname self.logtype = logtype self._welu.AddSourceToRegistry(appname, dllname, logtype) self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE self.typemap = { DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE, INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE, WARN : win32evtlog.EVENTLOG_WARNING_TYPE, ERROR : win32evtlog.EVENTLOG_ERROR_TYPE, CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, } except ImportError: print "The Python Win32 extensions for NT (service, event "\ "logging) appear not to be available." self._welu = None def getMessageID(self, record): """ Return the message ID for the event record. If you are using your own messages, you could do this by having the msg passed to the logger being an ID rather than a formatting string. Then, in here, you could use a dictionary lookup to get the message ID. This version returns 1, which is the base message ID in win32service.pyd. """ return 1 def getEventCategory(self, record): """ Return the event category for the record. Override this if you want to specify your own categories. This version returns 0. """ return 0 def getEventType(self, record): """ Return the event type for the record. Override this if you want to specify your own types. This version does a mapping using the handler's typemap attribute, which is set up in __init__() to a dictionary which contains mappings for DEBUG, INFO, WARN, ERROR and CRITICAL. If you are using your own levels you will either need to override this method or place a suitable dictionary in the handler's typemap attribute. """ return self.typemap.get(record.levelno, self.deftype) def emit(self, record): """ Determine the message ID, event category and event type. Then log the message in the NT event log. """ if self._welu: try: id = self.getMessageID(record) cat = self.getEventCategory(record) type = self.getEventType(record) msg = self.format(record) self._welu.ReportEvent(self.appname, id, cat, type, [msg]) except: self.handleError() def close(self): """ You can remove the application name from the registry as a source of event log entries. However, if you do this, you will not be able to see the events as you intended in the Event Log Viewer - it needs to be able to access the registry to get the DLL name. """ #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype) pass class HTTPHandler(Handler): """ A class which sends records to a Web server, using either GET or POST semantics. """ def __init__(self, host, url, method="GET"): """ Initialize the instance with the host, the request URL, and the method ("GET" or "POST") """ Handler.__init__(self) method = string.upper(method) if method not in ["GET", "POST"]: raise ValueError, "method must be GET or POST" self.host = host self.url = url self.method = method def emit(self, record): """ Send the record to the Web server as an URL-encoded dictionary """ try: import httplib, urllib h = httplib.HTTP(self.host) url = self.url data = urllib.urlencode(record.__dict__) if self.method == "GET": if (string.find(url, '?') >= 0): sep = '&' else: sep = '?' url = url + "%c%s" % (sep, data) h.putrequest(self.method, url) if self.method == "POST": h.putheader("Content-length", str(len(data))) h.endheaders() if self.method == "POST": h.send(data) h.getreply() #can't do anything with the result except: self.handleError() #--------------------------------------------------------------------------- # Manager classes and functions #--------------------------------------------------------------------------- class PlaceHolder: """ PlaceHolder instances are used in the Manager logger hierarchy to take the place of nodes for which no loggers have been defined [FIXME add example]. """ def __init__(self, alogger): """ Initialize with the specified logger being a child of this placeholder. """ self.loggers = [alogger] def append(self, alogger): """ Add the specified logger as a child of this placeholder. """ if alogger not in self.loggers: self.loggers.append(alogger) # # Determine which class to use when instantiating loggers. # _loggerClass = None def setLoggerClass(klass): """ Set the class to be used when instantiating a logger. The class should define __init__() such that only a name argument is required, and the __init__() should call Logger.__init__() """ if klass != Logger: if type(klass) != types.ClassType: raise TypeError, "setLoggerClass is expecting a class" if not issubclass(klass, Logger): raise TypeError, "logger not derived from logging.Logger: " + \ klass.__name__ global _loggerClass _loggerClass = klass class Manager: """ There is [under normal circumstances] just one Manager instance, which holds the hierarchy of loggers. """ def __init__(self, root): """ Initialize the manager with the root node of the logger hierarchy. """ self.root = root self.disable = 0 self.emittedNoHandlerWarning = 0 self.loggerDict = {} def getLogger(self, name): """ Get a logger with the specified name, creating it if it doesn't yet exist. If a PlaceHolder existed for the specified name [i.e. the logger didn't exist but a child of it did], replace it with the created logger and fix up the parent/child references which pointed to the placeholder to now point to the logger. """ rv = None _acquireLock() try: if self.loggerDict.has_key(name): rv = self.loggerDict[name] if isinstance(rv, PlaceHolder): ph = rv rv = _loggerClass(name) rv.manager = self self.loggerDict[name] = rv self._fixupChildren(ph, rv) self._fixupParents(rv) else: rv = _loggerClass(name) rv.manager = self self.loggerDict[name] = rv self._fixupParents(rv) finally: _releaseLock() return rv def _fixupParents(self, alogger): """ Ensure that there are either loggers or placeholders all the way from the specified logger to the root of the logger hierarchy. """ name = alogger.name i = string.rfind(name, ".") rv = None while (i > 0) and not rv: substr = name[:i] if not self.loggerDict.has_key(substr): self.loggerDict[substr] = PlaceHolder(alogger) else: obj = self.loggerDict[substr] if isinstance(obj, Logger): rv = obj else: assert isinstance(obj, PlaceHolder) obj.append(alogger) i = string.rfind(name, ".", 0, i - 1) if not rv: rv = self.root alogger.parent = rv def _fixupChildren(self, ph, alogger): """ Ensure that children of the placeholder ph are connected to the specified logger. """ for c in ph.loggers: if string.find(c.parent.name, alogger.name) <> 0: alogger.parent = c.parent c.parent = alogger #--------------------------------------------------------------------------- # Logger classes and functions #--------------------------------------------------------------------------- class Logger(Filterer): """ Instances of the Logger class represent a single logging channel. A "logging channel" indicates an area of an application. Exactly how an "area" is defined is up to the application developer. Since an application can have any number of areas, logging channels are identified by a unique string. Application areas can be nested (e.g. an area of "input processing" might include sub-areas "read CSV files", "read XLS files" and "read Gnumeric files"). To cater for this natural nesting, channel names are organized into a namespace hierarchy where levels are separated by periods, much like the Java or Python package namespace. So in the instance given above, channel names might be "input" for the upper level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels. There is no arbitrary limit to the depth of nesting. """ def __init__(self, name, level=ALL): """ Initialize the logger with a name and an optional level. """ Filterer.__init__(self) self.name = name self.level = level self.parent = None self.propagate = 1 self.handlers = [] self.disabled = 0 def setLevel(self, lvl): """ Set the logging level of this logger. """ self.level = lvl # def getRoot(self): # """ # Get the root of the logger hierarchy. # """ # return Logger.root def debug(self, msg, *args, **kwargs): """ Log 'msg % args' with severity 'DEBUG'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.debug("Houston, we have a %s", "thorny problem", exc_info=1) """ if self.manager.disable >= DEBUG: return if DEBUG >= self.getEffectiveLevel(): apply(self._log, (DEBUG, msg, args), kwargs) def info(self, msg, *args, **kwargs): """ Log 'msg % args' with severity 'INFO'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.info("Houston, we have a %s", "interesting problem", exc_info=1) """ if self.manager.disable >= INFO: return if INFO >= self.getEffectiveLevel(): apply(self._log, (INFO, msg, args), kwargs) def warn(self, msg, *args, **kwargs): """ Log 'msg % args' with severity 'WARN'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.warn("Houston, we have a %s", "bit of a problem", exc_info=1) """ if self.manager.disable >= WARN: return if self.isEnabledFor(WARN): apply(self._log, (WARN, msg, args), kwargs) def error(self, msg, *args, **kwargs): """ Log 'msg % args' with severity 'ERROR'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.error("Houston, we have a %s", "major problem", exc_info=1) """ if self.manager.disable >= ERROR: return if self.isEnabledFor(ERROR): apply(self._log, (ERROR, msg, args), kwargs) def exception(self, msg, *args): """ Convenience method for logging an ERROR with exception information """ apply(self.error, (msg,) + args, {'exc_info': 1}) def critical(self, msg, *args, **kwargs): """ Log 'msg % args' with severity 'CRITICAL'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.critical("Houston, we have a %s", "major disaster", exc_info=1) """ if self.manager.disable >= CRITICAL: return if CRITICAL >= self.getEffectiveLevel(): apply(self._log, (CRITICAL, msg, args), kwargs) fatal = critical def log(self, lvl, msg, *args, **kwargs): """ Log 'msg % args' with the severity 'lvl'. To pass exception information, use the keyword argument exc_info with a true value, e.g. logger.log(lvl, "We have a %s", "mysterious problem", exc_info=1) """ if self.manager.disable >= lvl: return if self.isEnabledFor(lvl): apply(self._log, (lvl, msg, args), kwargs) def findCaller(self): """ Find the stack frame of the caller so that we can note the source file name and line number. """ rv = (None, None) frame = inspect.currentframe().f_back while frame: sfn = inspect.getsourcefile(frame) if sfn: sfn = os.path.normcase(sfn) if sfn != _srcfile: #print frame.f_code.co_code lineno = inspect.getlineno(frame) rv = (sfn, lineno) break frame = frame.f_back return rv def makeRecord(self, name, lvl, fn, lno, msg, args, exc_info): """ A factory method which can be overridden in subclasses to create specialized LogRecords. """ return LogRecord(name, lvl, fn, lno, msg, args, exc_info) def _log(self, lvl, msg, args, exc_info=None): """ Low-level logging routine which creates a LogRecord and then calls all the handlers of this logger to handle the record. """ if inspect and _srcfile: _acquireLock() try: fn, lno = self.findCaller() finally: _releaseLock() else: fn, lno = "", 0 if exc_info: exc_info = sys.exc_info() record = self.makeRecord(self.name, lvl, fn, lno, msg, args, exc_info) self.handle(record) def handle(self, record): """ Call the handlers for the specified record. This method is used for unpickled records received from a socket, as well as those created locally. Logger-level filtering is applied. """ if (not self.disabled) and self.filter(record): self.callHandlers(record) def addHandler(self, hdlr): """ Add the specified handler to this logger. """ if not (hdlr in self.handlers): self.handlers.append(hdlr) def removeHandler(self, hdlr): """ Remove the specified handler from this logger. """ if hdlr in self.handlers: hdlr.close() self.handlers.remove(hdlr) def callHandlers(self, record): """ Loop through all handlers for this logger and its parents in the logger hierarchy. If no handler was found, output a one-off error message to sys.stderr. Stop searching up the hierarchy whenever a logger with the "propagate" attribute set to zero is found - that will be the last logger whose handlers are called. """ c = self found = 0 while c: for hdlr in c.handlers: found = found + 1 if record.levelno >= hdlr.level: hdlr.handle(record) if not c.propagate: c = None #break out else: c = c.parent if (found == 0) and not self.manager.emittedNoHandlerWarning: sys.stderr.write("No handlers could be found for logger" " \"%s\"\n" % self.name) self.manager.emittedNoHandlerWarning = 1 def getEffectiveLevel(self): """ Loop through this logger and its parents in the logger hierarchy, looking for a non-zero logging level. Return the first one found. """ logger = self while logger: if logger.level: return logger.level logger = logger.parent return ALL def isEnabledFor(self, lvl): """ Is this logger enabled for level lvl? """ if self.manager.disable >= lvl: return 0 return lvl >= self.getEffectiveLevel() class RootLogger(Logger): """ A root logger is not that different to any other logger, except that it must have a logging level and there is only one instance of it in the hierarchy. """ def __init__(self, lvl): """ Initialize the logger with the name "root". """ Logger.__init__(self, "root", lvl) _loggerClass = Logger root = RootLogger(DEBUG) Logger.root = root Logger.manager = Manager(Logger.root) #--------------------------------------------------------------------------- # Configuration classes and functions #--------------------------------------------------------------------------- BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s" def basicConfig(): """ Do basic configuration for the logging system by creating a StreamHandler with a default Formatter and adding it to the root logger. """ if len(root.handlers) == 0: hdlr = StreamHandler() fmt = Formatter(BASIC_FORMAT) hdlr.setFormatter(fmt) root.addHandler(hdlr) def fileConfig(fname): """ Read the logging configuration from a ConfigParser-format file. This can be called several times from an application, allowing an end user the ability to select from various pre-canned configurations (if the developer provides a mechanism to present the choices and load the chosen configuration). """ import ConfigParser cp = ConfigParser.ConfigParser() cp.read(fname) #first, do the formatters... flist = cp.get("formatters", "keys") if len(flist): flist = string.split(flist, ",") formatters = {} for form in flist: sectname = "formatter_%s" % form opts = cp.options(sectname) if "format" in opts: fs = cp.get(sectname, "format", 1) else: fs = None if "datefmt" in opts: dfs = cp.get(sectname, "datefmt", 1) else: dfs = None f = Formatter(fs, dfs) formatters[form] = f #next, do the handlers... #critical section... _acquireLock() try: try: #first, lose the existing handlers... _handlers.clear() #now set up the new ones... hlist = cp.get("handlers", "keys") if len(hlist): hlist = string.split(hlist, ",") handlers = {} fixups = [] #for inter-handler references for hand in hlist: sectname = "handler_%s" % hand klass = cp.get(sectname, "class") opts = cp.options(sectname) if "formatter" in opts: fmt = cp.get(sectname, "formatter") else: fmt = "" klass = eval(klass) args = cp.get(sectname, "args") args = eval(args) h = apply(klass, args) if "level" in opts: lvl = cp.get(sectname, "level") h.setLevel(_levelNames[lvl]) if len(fmt): h.setFormatter(formatters[fmt]) #temporary hack for FileHandler and MemoryHandler. if klass == FileHandler: maxsize = 0 if "maxsize" in opts: ms = cp.getint(sectname, "maxsize") if ms > 0: maxsize = ms if maxsize: backcount = 0 if "backcount" in opts: bc = cp.getint(sectname, "backcount") if bc > 0: backcount = bc h.setRollover(maxsize, backcount) elif klass == MemoryHandler: if "target" in opts: target = cp.get(sectname,"target") else: target = "" if len(target): #the target handler may not be loaded yet, so keep for later... fixups.append((h, target)) handlers[hand] = h #now all handlers are loaded, fixup inter-handler references... for fixup in fixups: h = fixup[0] t = fixup[1] h.setTarget(handlers[t]) #at last, the loggers...first the root... llist = cp.get("loggers", "keys") llist = string.split(llist, ",") llist.remove("root") sectname = "logger_root" log = root opts = cp.options(sectname) if "level" in opts: lvl = cp.get(sectname, "level") log.setLevel(_levelNames[lvl]) for h in root.handlers: root.removeHandler(h) hlist = cp.get(sectname, "handlers") if len(hlist): hlist = string.split(hlist, ",") for hand in hlist: log.addHandler(handlers[hand]) #and now the others... #we don't want to lose the existing loggers, #since other threads may have pointers to them. #existing is set to contain all existing loggers, #and as we go through the new configuration we #remove any which are configured. At the end, #what's left in existing is the set of loggers #which were in the previous configuration but #which are not in the new configuration. existing = root.manager.loggerDict.keys() #now set up the new ones... for log in llist: sectname = "logger_%s" % log qn = cp.get(sectname, "qualname") opts = cp.options(sectname) if "propagate" in opts: propagate = cp.getint(sectname, "propagate") else: propagate = 1 logger = getLogger(qn) if qn in existing: existing.remove(qn) if "level" in opts: lvl = cp.get(sectname, "level") logger.setLevel(_levelNames[lvl]) for h in logger.handlers: logger.removeHandler(h) logger.propagate = propagate logger.disabled = 0 hlist = cp.get(sectname, "handlers") if len(hlist): hlist = string.split(hlist, ",") for hand in hlist: logger.addHandler(handlers[hand]) #Disable any old loggers. There's no point deleting #them as other threads may continue to hold references #and by disabling them, you stop them doing any logging. for log in existing: root.manager.loggerDict[log].disabled = 1 except: import traceback ei = sys.exc_info() traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr) del ei finally: _releaseLock() #--------------------------------------------------------------------------- # Utility functions at module level. # Basically delegate everything to the root logger. #--------------------------------------------------------------------------- def getLogger(name=None): """ Return a logger with the specified name, creating it if necessary. If no name is specified, return the root logger. """ if name: return Logger.manager.getLogger(name) else: return root def getRootLogger(): """ Return the root logger. Note that getLogger('') now does the same thing, so this function is deprecated and may disappear in the future. """ return root def critical(msg, *args, **kwargs): """ Log a message with severity 'CRITICAL' on the root logger. """ if len(root.handlers) == 0: basicConfig() apply(root.critical, (msg,)+args, kwargs) fatal = critical def error(msg, *args, **kwargs): """ Log a message with severity 'ERROR' on the root logger. """ if len(root.handlers) == 0: basicConfig() apply(root.error, (msg,)+args, kwargs) def exception(msg, *args): """ Log a message with severity 'ERROR' on the root logger, with exception information. """ apply(error, (msg,)+args, {'exc_info': 1}) def warn(msg, *args, **kwargs): """ Log a message with severity 'WARN' on the root logger. """ if len(root.handlers) == 0: basicConfig() apply(root.warn, (msg,)+args, kwargs) def info(msg, *args, **kwargs): """ Log a message with severity 'INFO' on the root logger. """ if len(root.handlers) == 0: basicConfig() apply(root.info, (msg,)+args, kwargs) def debug(msg, *args, **kwargs): """ Log a message with severity 'DEBUG' on the root logger. """ if len(root.handlers) == 0: basicConfig() apply(root.debug, (msg,)+args, kwargs) def disable(level): """ Disable all logging calls less severe than 'level'. """ root.manager.disable = level def shutdown(): """ Perform any cleanup actions in the logging system (e.g. flushing buffers). Should be called at application exit. """ for h in _handlers.keys(): h.flush() h.close() # # The following code implements a socket listener for on-the-fly # reconfiguration of logging. # # _listener holds the server object doing the listening _listener = None def listen(port=DEFAULT_LOGGING_CONFIG_PORT): """ Start up a socket server on the specified port, and listen for new configurations. These will be sent as a file suitable for processing by fileConfig(). Returns a Thread object on which you can call start() to start the server, and which you can join() when appropriate. To stop the server, call stopListening(). """ if not thread: raise NotImplementedError, "listen() needs threading to work" import threading class ConfigStreamHandler(StreamRequestHandler): """ Handler for a logging configuration request. It expects a completely new logging configuration and uses fileConfig to install it. """ def handle(self): """ Each request is expected to be a 2-byte length, followed by the config file. Uses fileConfig() to do the needful. """ import tempfile try: conn = self.connection chunk = conn.recv(2) if len(chunk) == 2: slen = (ord(chunk[0]) << 8) | ord(chunk[1]) chunk = self.connection.recv(slen) while len(chunk) < slen: chunk = chunk + conn.recv(slen - len(chunk)) #Apply new configuration. We'd like to be able to #create a StringIO and pass that in, but unfortunately #1.5.2 ConfigParser does not support reading file #objects, only actual files. So we create a temporary #file and remove it later. file = tempfile.mktemp(".ini") f = open(file, "w") f.write(chunk) f.close() fileConfig(file) os.remove(file) except socket.error, e: if type(e.args) != types.TupleType: raise else: errcode = e.args[0] if errcode != RESET_ERROR: raise class ConfigSocketReceiver(ThreadingTCPServer): """ A simple TCP socket-based logging config receiver. """ allow_reuse_address = 1 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT, handler=None): ThreadingTCPServer.__init__(self, (host, port), handler) _acquireLock() self.abort = 0 _releaseLock() self.timeout = 1 def serve_until_stopped(self): import select abort = 0 while not abort: rd, wr, ex = select.select([self.socket.fileno()], [], [], self.timeout) if rd: self.handle_request() _acquireLock() abort = self.abort _releaseLock() def serve(rcvr, hdlr): server = rcvr(handler=hdlr) global _listener _acquireLock() _listener = server _releaseLock() server.serve_until_stopped() return threading.Thread(target=serve, args=(ConfigSocketReceiver, ConfigStreamHandler)) def stopListening(): """ Stop the listening server which was created with a call to listen(). """ if _listener: _acquireLock() _listener.abort = 1 _listener = None _releaseLock() # bicycle repair man stuff bike_logger_initialised = 0 def init(): global bike_logger_initialised if not bike_logger_initialised: log = getLogger("bike") h = StreamHandler() h.setFormatter(Formatter( fmt="%(pathname)s:%(lineno)s:%(levelname)s %(message)s")) log.addHandler(h) bike_logger_initialised = 1 if __name__ == "__main__": print __doc__ bicyclerepair-0.9/bike/mock.py0100644000076400007640000001124507405666440014563 0ustar pldpld# # (c) Dave Kirby 2001 # dkirby@bigfoot.com # # Call interceptor code by Phil Dawes (pdawes@users.sourceforge.net) ''' The Mock class emulates any other class for testing purposes. All method calls are stored for later examination. The class constructor takes a dictionary of method names and the values they return. Methods that are not in the dictionary will return None. ''' import inspect class Mock: def __init__(self, returnValues=None ): self.mockCalledMethods = {} self.mockAllCalledMethods = [] self.mockReturnValues = returnValues or {} self.setupMethodInterceptors() def setupMethodInterceptors(self): if self.__class__ != Mock: # check we've been subclassed methods = inspect.getmembers(self.__class__,inspect.ismethod) for m in methods: name = m[0] self.__dict__[name] = MethodCallInterceptor(name,self) def __getattr__( self, name ): return MockCaller( name, self ) def getAllCalls(self): '''return a list of MockCall objects, representing all the methods in the order they were called''' return self.mockAllCalledMethods def getNamedCalls(self, methodName ): '''return a list of MockCall objects, representing all the calls to the named method in the order they were called''' return self.mockCalledMethods.get(methodName, [] ) def assertNamedCall(self,methodName,*args): # assert call was made once assert(len(self.getNamedCalls(methodName)) == 1) # assert args are correct argsdict = inspect.getargvalues(inspect.currentframe())[3] i=0 for arg in argsdict['args']: assert(self.getNamedCalls(methodName)[0].getParam(i) == arg) i += 1 def assertCallInOrder(self,methodName,*args): ''' Convenience method to allow client to check that calls were made in the right order. Call once for each methodcall''' # initialise callIndex. (n.b. __getattr__ method complicates # this since attrs are MockCaller instances by default) if type(self.callIndex) != type(1): self.callIndex=0 call = self.getAllCalls()[self.callIndex] # assert method name is correct assert(call.getName() == methodName) # assert args are correct argsdict = inspect.getargvalues(inspect.currentframe())[3] i=0 for arg in argsdict['args']: assert(call.getParam(i) == arg) i += 1 # assert num args are correct assert(call.getNumParams() == i) self.callIndex += 1 def assertNoMoreCalls(self): assert(len(self.getAllCalls()) == self.callIndex) class MockCall: def __init__(self, name, params, kwparams ): self.name = name self.params = params self.kwparams = kwparams def getParam( self, n ): if type(n) == type(1): return self.params[n] elif type(n) == type(''): return self.kwparams[n] else: raise IndexError, 'illegal index type for getParam' def getNumParams(self): return len(self.params) def getName(self): return self.name #pretty-print the method call def __str__(self): s = self.name + "(" sep = '' for p in self.params: s = s + sep + repr(p) sep = ', ' for k,v in self.kwparams.items(): s = s + sep + k+ '='+repr(v) sep = ', ' s = s + ')' return s def __repr__(self): return self.__str__() class MockCaller: def __init__( self, name, mock): self.name = name self.mock = mock def __call__(self, *params, **kwparams ): self.recordCall(params,kwparams) return self.mock.mockReturnValues.get(self.name) def recordCall(self,params,kwparams): thisCall = MockCall( self.name, params, kwparams ) calls = self.mock.mockCalledMethods.get(self.name, [] ) if calls == []: self.mock.mockCalledMethods[self.name] = calls calls.append(thisCall) self.mock.mockAllCalledMethods.append(thisCall) # intercepts the call and records it, then delegates to the real call class MethodCallInterceptor(MockCaller): def __call__(self, *params, **kwparams ): self.recordCall(params,kwparams) return self.makeCall(params) def makeCall(self,params): argsstr="(self.mock" for i in range(len(params)): argsstr += ",params["+`i`+"]" argsstr+=")" return eval("self.mock.__class__."+self.name+argsstr) bicyclerepair-0.9/bike/setpath.py0100644000076400007640000000033407722362663015301 0ustar pldpldimport sys,os if not os.path.abspath("..") in sys.path: from bike import log print >> log.warning, "Appending to the system path. This should only happen in unit tests" sys.path.append(os.path.abspath("..")) bicyclerepair-0.9/bike/test_bikefacade.py0100755000076400007640000002644610011650414016720 0ustar pldpld#!/usr/bin/env python import unittest import setpath import sys from bike import testdata from bike.testutils import * import bike from bike.refactor.test_renameFunction import RenameFunctionTests, RenameFunctionTests_importsFunction, FunctionTestdata from bike.refactor.test_renameClass import RenameClassTests, RenameClassTests_importsClass, TheClassTestdata from bike.refactor.test_renameMethod import RenameMethodTests, RenameMethodTests_ImportsClass, RenameMethodReferenceTests, RenameMethodReferenceTests_ImportsClass, RenameMethodAfterPromptTests, TestDoesntRenameMethodIfPromptReturnsFalse,MethodTestdata from bike.refactor import test_extractMethod import bikefacade from bike import UndoStackEmptyException from bike.query.getTypeOf import getTypeOf class TestPathFunctions(BRMTestCase): def test_setCompletePythonPath_removesDuplicates(self): origpath = sys.path try: sys.path = ["foobah"] ctx = bike.init() ctx._setCompletePythonPath(sys.path[-1]) self.assertEqual(1,ctx._getCurrentSearchPath().count(sys.path[-1])) finally: sys.path = origpath def test_setNonLibPathonPath_removesLibDirectories(self): origpath = sys.path try: writeTmpTestFile("pass") libdir = os.path.join(sys.prefix,"lib","python"+sys.version[:3]) sys.path = [libdir,os.path.join(libdir,"site-packages")] ctx = bike.init() ctx._setNonLibPythonPath(tmproot) self.assertEqual([tmproot],ctx._getCurrentSearchPath()) finally: sys.path = origpath class TestRenameMethodAfterPrompt(BRMTestCase,RenameMethodAfterPromptTests): def callback(self, filename, line, colstart, colend): return 1 def renameMethod(self, src, line, col, newname): writeTmpTestFile(src) ctx = bike.init() ctx.setRenameMethodPromptCallback(self.callback) ctx.renameByCoordinates(tmpfile,line,col,newname) ctx.save() newsrc = readFile(tmpfile) return newsrc class TestDoesntRenameMethodIfPromptReturnsFalse(TestDoesntRenameMethodIfPromptReturnsFalse): def callback(self, filename, line, colstart, colend): return 0 def renameMethod(self, src, line, col, newname): writeTmpTestFile(src) ctx = bike.init() ctx.setRenameMethodPromptCallback(self.callback) ctx.renameByCoordinates(tmpfile,line,col,newname) ctx.save() newsrc = readFile(tmpfile) return newsrc class TestRenameByCoordinates2(RenameMethodTests,RenameMethodReferenceTests, RenameClassTests,RenameFunctionTests,BRMTestCase): def rename(self, src, line, col, newname): writeTmpTestFile(src) ctx = bike.init() ctx.renameByCoordinates(os.path.abspath(tmpfile),line,col,newname) ctx.save() newsrc = readFile(tmpfile) return newsrc class TestRenameByCoordinatesWithDirectoryStructure( RenameClassTests_importsClass, RenameFunctionTests_importsFunction, RenameMethodTests_ImportsClass, RenameMethodReferenceTests_ImportsClass, BRMTestCase): def renameClass(self, src, newname): try: createPackageStructure(src, TheClassTestdata) ctx = bike.init() ctx.renameByCoordinates(pkgstructureFile2,1,6,newname) ctx.save() newsrc = readFile(pkgstructureFile1) return newsrc finally: removePackageStructure() def renameMethod(self, src, line, col, newname): try: createPackageStructure(src, MethodTestdata) ctx = bike.init() ctx.renameByCoordinates(pkgstructureFile2,line,col,newname) ctx.save() newsrc = readFile(pkgstructureFile1) return newsrc finally: removePackageStructure() def renameFunction(self, src, newname): try: createPackageStructure(src, FunctionTestdata) ctx = bike.init() ctx.renameByCoordinates(pkgstructureFile2,1,4,newname) ctx.save() newsrc = readFile(pkgstructureFile1) return newsrc finally: removePackageStructure() class Test_deducePackageOfFile(BRMTestCase): def test_returnsEmptyStringIfFileNotInPackage(self): try: # this doesnt have __init__.py file, so # isnt package os.makedirs("a") writeFile(os.path.join("a","foo.py"),"pass") pkg = bikefacade._deducePackageOfFile(os.path.join("a","foo.py")) assert pkg == "" finally: os.remove(os.path.join("a","foo.py")) os.removedirs(os.path.join("a")) def test_returnsNestedPackage(self): try: os.makedirs(os.path.join("a","b")) writeFile(os.path.join("a","__init__.py"),"# ") writeFile(os.path.join("a","b","__init__.py"),"# ") writeFile(os.path.join("a","b","foo.py"),"pass") pkg = bikefacade._deducePackageOfFile(os.path.join("a","b","foo.py")) assert pkg == "a.b" finally: os.remove(os.path.join("a","__init__.py")) os.remove(os.path.join("a","b","__init__.py")) os.remove(os.path.join("a","b","foo.py")) os.removedirs(os.path.join("a","b")) class TestExtractMethod(test_extractMethod.TestExtractMethod): def test_extractsPass(self): srcBefore=trimLines(""" class MyClass: def myMethod(self): pass """) srcAfter=trimLines(""" class MyClass: def myMethod(self): self.newMethod() def newMethod(self): pass """) writeTmpTestFile(srcBefore) ctx = bike.init() ctx.extractMethod(os.path.abspath(tmpfile),3,8,3,12,"newMethod") ctx.save() self.assertEqual(readTmpTestFile(),srcAfter) ctx.undo() ctx.save() self.assertEqual(readTmpTestFile(),srcBefore) class TestExtractFunction(test_extractMethod.TestExtractFunction): def test_extractsFunction(self): srcBefore=trimLines(""" def myFunction(): # comment a = 3 c = a + 99 b = c * 1 print b """) srcAfter=trimLines(""" def myFunction(): # comment a = 3 b = newFunction(a) print b def newFunction(a): c = a + 99 b = c * 1 return b """) writeTmpTestFile(srcBefore) ctx = bike.init() ctx.extractMethod(os.path.abspath(tmpfile),3,4,4,13,"newFunction") ctx.save() self.assertEqual(readTmpTestFile(),srcAfter) ctx.undo() ctx.save() self.assertEqual(readTmpTestFile(),srcBefore) class TestUndo(BRMTestCase): def test_undoesTheTextOfASingleFile(self): src = trimLines(""" class a: def foo(self): pass """) writeTmpTestFile(src) #ctx = bike.init() ctx = bike.init() ctx.renameByCoordinates(tmpfile,2,8,"c") ctx.save() ctx.undo() ctx.save() newsrc = readFile(tmpfile) self.assertEqual(newsrc,src) def test_undoesTwoConsecutiveRefactorings(self): try: src = trimLines(""" class a: def foo(self): pass """) writeTmpTestFile(src) ctx = bike.init() ctx.renameByCoordinates(tmpfile,2,8,"c") ctx.save() newsrc1 = readFile(tmpfile) ctx.renameByCoordinates(tmpfile,2,8,"d") ctx.save() # 1st undo ctx.undo() ctx.save() newsrc = readFile(tmpfile) self.assertEqual(newsrc, newsrc1) # 2nd undo ctx.undo() ctx.save() newsrc = readFile(tmpfile) self.assertEqual(newsrc,src) finally: pass #deleteTmpTestFile() def test_undoesTheTextOfAFileTwice(self): for i in range(3): src = trimLines(""" class foo: def bah(self): pass """) writeTmpTestFile(src) ctx = bike.init() ctx.renameByCoordinates(tmpfile,2,8,"c") ctx.save() ctx.undo() ctx.save() newsrc = readFile(tmpfile) self.assertEqual(newsrc,src) raisedexception=0 try: ctx.undo() except UndoStackEmptyException: pass else: assert 0,"should have raised an exception" ''' def test_undoesManualModificationsToFiles(self): writeTmpTestFile("class foo: pass") origsrc = readFile(tmpfile) ctx = bike.init() writeTmpTestFile("pass") import os ctx.init() newsrc = readFile(tmpfile) assert newsrc != origsrc ctx.undo() ctx.save() newsrc = readFile(tmpfile) assert newsrc == origsrc ''' class TestGetReferencesToClass_Facade(BRMTestCase): def test_returnsReferences(self): src = trimLines(""" class TheClass: pass a = TheClass() """) writeTmpTestFile(src) ctx = bike.init() refs = [refs for refs in ctx.findReferencesByCoordinates(tmpfile,1,6)] self.assertEqual(refs[0].filename,os.path.abspath(tmpfile)) self.assertEqual(refs[0].lineno,3) assert hasattr(refs[0],"confidence") class TestFindDefinitionByCoordinates(BRMTestCase): def test_findsClassRef(self): src=trimLines(""" class TheClass: pass a = TheClass() """) writeTmpTestFile(src) ctx = bike.init() defn = [x for x in ctx.findDefinitionByCoordinates(tmpfile,3,6)] assert defn[0].filename == os.path.abspath(tmpfile) assert defn[0].lineno == 1 assert defn[0].confidence == 100 class TestBRM_InlineLocalVariable(BRMTestCase): def test_works(self): srcBefore=trimLines(""" def foo(): b = 'hello' print b """) srcAfter=trimLines(""" def foo(): print 'hello' """) writeTmpTestFile(srcBefore) ctx = bike.init() ctx.inlineLocalVariable(tmpfile,3,10) ctx.save() self.assertEqual(file(tmpfile).read(),srcAfter) class TestBRM_ExtractLocalVariable(BRMTestCase): def test_works(self): srcBefore=trimLines(""" def foo(): print 3 + 2 """) srcAfter=trimLines(""" def foo(): a = 3 + 2 print a """) try: writeTmpTestFile(srcBefore) ctx = bike.init() ctx.extractLocalVariable(tmpfile,2,10,2,15,'a') ctx.save() self.assertEqual(file(tmpfile).read(),srcAfter) finally: pass #deleteTmpTestFile() if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/test_testutils.py0100755000076400007640000000022710010132457016710 0ustar pldpld#!/usr/bin/env python import setpath import unittest from testutils import* import testdata import sys if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/testall.py0100755000076400007640000000016407606012243015270 0ustar pldpld#!/usr/bin/env python import setpath from test_bikefacade import * if __name__ == "__main__": unittest.main() bicyclerepair-0.9/bike/testdata.py0100644000076400007640000000034307573161015015432 0ustar pldpldTheClass = """ class TheClass: def theMethod(self): pass def differentMethod(self): pass class DifferentClass: def theMethod(self): pass """ Function = """ def theFunction(): pass """ bicyclerepair-0.9/bike/testutils.py0100644000076400007640000001076410012234234015653 0ustar pldpldfrom bike.globals import * import unittest import os import os.path from mock import Mock from bike.parsing.fastparserast import getRoot, Root, resetRoot from parsing.utils import fqn_rcar, fqn_rcdr import re from bike import log filesToDelete = None dirsToDelete = None class BRMTestCase(unittest.TestCase): def setUp(self): log.warning = log.SilentLogger() try: os.makedirs(tmproot) except: pass os.chdir(tmproot) resetRoot(Root([tmproot])) getRoot().unittestmode = True global filesToDelete global dirsToDelete filesToDelete = [] dirsToDelete = [] from bike.parsing.load import Cache Cache.instance.reset() def tearDown(self): global filesToDelete global dirsToDelete for path in filesToDelete: try: os.remove(path) except: pass filesToDelete = [] for path in dirsToDelete: try: os.removedirs(path) except: pass dirsToDelete = [] os.chdir("..") try: os.removedirs(tmproot) except: pass tmproot = os.path.abspath("tmproot") tmpfile = os.path.join(tmproot, "bicyclerepairman_tmp_testfile.py") tmpmodule = "bicyclerepairman_tmp_testfile" def writeFile(filename, src): f = open(filename, "w+") f.write(src) f.close() filesToDelete.append(filename) def readFile(filename): f = open(filename) src = f.read() f.close() return src def writeTmpTestFile(src): try: os.makedirs(tmproot) except OSError: pass writeFile(tmpfile, src) def readTmpTestFile(): return readFile(tmpfile) def deleteTmpTestFile(): os.remove(tmpfile) os.removedirs(tmproot) pkgstructureRootDir = tmproot pkgstructureBasedir = os.path.join(pkgstructureRootDir, "a") pkgstructureChilddir = os.path.join(pkgstructureBasedir, "b") pkgstructureFile0 = os.path.join(pkgstructureRootDir, "top.py") pkgstructureFile1 = os.path.join(pkgstructureBasedir, "foo.py") pkgstructureFile2 = os.path.join(pkgstructureChilddir, "bah.py") def createPackageStructure(src1, src2, src0="pass"): try: os.makedirs(pkgstructureChilddir) except: pass writeFile(os.path.join(pkgstructureBasedir, "__init__.py"), "#") writeFile(os.path.join(pkgstructureChilddir, "__init__.py"), "#") writeFile(pkgstructureFile0, src0) writeFile(pkgstructureFile1, src1) writeFile(pkgstructureFile2, src2) def removePackageStructure(): os.remove(os.path.join(pkgstructureBasedir, "__init__.py")) os.remove(os.path.join(pkgstructureChilddir, "__init__.py")) os.remove(pkgstructureFile0) os.remove(pkgstructureFile1) os.remove(pkgstructureFile2) os.removedirs(pkgstructureChilddir) pkgstructureBasedir2 = os.path.join(pkgstructureRootDir, "c") pkgstructureFile3 = os.path.join(pkgstructureBasedir2, "bing.py") def createSecondPackageStructure(src3): try: os.makedirs(pkgstructureBasedir2) except: pass writeFile(os.path.join(pkgstructureBasedir2, "__init__.py"), "#") writeFile(pkgstructureFile3, src3) def removeSecondPackageStructure(): os.remove(os.path.join(pkgstructureBasedir2, "__init__.py")) os.remove(pkgstructureFile3) os.removedirs(pkgstructureBasedir2) def createAST(src): from bike.parsing.load import getSourceNode writeFile(tmpfile,src) return getSourceNode(tmpfile) def createSourceNodeAt(src, fqn): modname = fqn_rcar(fqn) packagefqn = fqn_rcdr(fqn) dirpath = os.path.join(*packagefqn.split(".")) filepath = os.path.join(dirpath,modname+".py") try: os.makedirs(dirpath) except: pass dirsToDelete.append(dirpath) # add the __init__.py files path = "." for pathelem in packagefqn.split("."): path = os.path.join(path,pathelem) initfile = os.path.join(path,"__init__.py") writeFile(initfile,"#") filesToDelete.append(initfile) writeFile(filepath,src) filesToDelete.append(filepath) return getRoot() # takes the leading whitespace out of a multi line comment. # means you can imbed """ # text like # this # """ # in your code, and it will come out #"""text like #this""" def trimLines(src): lines = src.splitlines(1)[1:] tabwidth = re.match("\s*",lines[0]).end(0) newlines = [] for line in lines: if line == "\n" or line == "\r\n": newlines.append(line) else: newlines.append(line[tabwidth:]) return "".join(newlines) bicyclerepair-0.9/ide-integration/0040775000076400007640000000000010036707253015422 5ustar pldpldbicyclerepair-0.9/ide-integration/Pymacs-0.20/0040775000076400007640000000000010036707253017233 5ustar pldpldbicyclerepair-0.9/ide-integration/Pymacs-0.20/Pymacs/0040775000076400007640000000000010036707253020467 5ustar pldpldbicyclerepair-0.9/ide-integration/Pymacs-0.20/Pymacs/.cvsignore0100644000076400007640000000000607606002143022451 0ustar pldpld*.pyc bicyclerepair-0.9/ide-integration/Pymacs-0.20/Pymacs/__init__.py0100644000076400007640000000205007606002143022563 0ustar pldpld#!/usr/bin/env python # Copyright İ 2002 Progiciels Bourbeau-Pinard inc. # François Pinard , 2002. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ """\ Interface between Emacs Lisp and Python - Module initialisation. A few symbols are moved in here so they appear to be defined at this level. """ from pymacs import Let, lisp # Identification of version. package = 'Pymacs' version = '0.20' bicyclerepair-0.9/ide-integration/Pymacs-0.20/Pymacs/__init__.pyc0100664000076400007640000000066510020567234022743 0ustar pldpld;ò c>c@s)dZdklZlZdZdZdS(sŽInterface between Emacs Lisp and Python - Module initialisation. A few symbols are moved in here so they appear to be defined at this level. (sLetslispsPymacss0.20N(s__doc__spymacssLetslispspackagesversion(slispsversionsLetspackage((s./Pymacs/__init__.pys?sbicyclerepair-0.9/ide-integration/Pymacs-0.20/Pymacs/pymacs.py0100644000076400007640000004576707606002143022346 0ustar pldpld#!/usr/bin/env python # Copyright İ 2001, 2002 Progiciels Bourbeau-Pinard inc. # François Pinard , 2001. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ """\ Interface between Emacs Lisp and Python - Python part. Emacs may launch this module as a stand-alone program, in which case it acts as a server of Python facilities for that Emacs session, reading requests from standard input and writing replies on standard output. This module may also be usefully imported by those other Python modules. See the Pymacs documentation (in `README') for more information. """ ## Note: This code is currently compatible down to Python version 1.5.2. ## It is probably worth keeping it that way for a good while, still. import os, string, sys, types # Python services for Emacs applications. def main(*arguments): """\ Execute Python services for Emacs, and Emacs services for Python. This program is meant to be called from Emacs, using `pymacs.el'. The program arguments are additional search paths for Python modules. """ from Pymacs import version arguments = list(arguments) arguments.reverse() for argument in arguments: if os.path.isdir(argument): sys.path.insert(0, argument) lisp._protocol.send('(pymacs-version "%s")' % version) lisp._protocol.loop() class Protocol: # FIXME: The following should work, but does not: # # * pymacs.py (Protocol): Declare exceptions as classes, not strings. # #class ProtocolError(Exception): pass #class ReplyException(Exception): pass #class ErrorException(Exception): pass # # I get: # (pymacs-eval "lisp('\"abc\"').__class__.__name__") # "ReplyException" ProtocolError = 'ProtocolError' ReplyException = 'ReplyException' ErrorException = 'ErrorException' def __init__(self): self.freed = [] def loop(self): # The server loop repeatedly receives a request from Emacs and # returns a response, which is either the value of the received # Python expression, or the Python traceback if an error occurs # while evaluating the expression. # The server loop may also be executed, as a recursive invocation, # in the context of Emacs serving a Python request. In which # case, we might also receive a notification from Emacs telling # that the reply has been transmitted, or that an error occurred. # A reply notification from Emacs interrupts the loop: the result # of this function is the value returned from Emacs. while 1: try: text = self.receive() if text[:5] == 'exec ': exec eval(text[5:], {}, {}) status = 'reply' argument = None else: status = 'reply' argument = eval(text) except Protocol.ReplyException, value: return value except Protocol.ErrorException, message: status = 'error' argument = message except Protocol.ProtocolError, message: sys.stderr.write("Protocol error: %s\n" % message) sys.exit(1) except KeyboardInterrupt: raise except: import StringIO, traceback message = StringIO.StringIO() traceback.print_exc(file=message) status = 'error' argument = message.getvalue() # Send an expression to EMACS applying FUNCTION over ARGUMENT, # where FUNCTION is `pymacs-STATUS'. fragments = [] write = fragments.append if self.freed: write('(progn (pymacs-free-lisp') for index in self.freed: write(' %d' % index) write(') ') write('(pymacs-%s ' % status) print_lisp(argument, write, quoted=1) write(')') if self.freed: write(')') self.freed = [] self.send(string.join(fragments, '')) def receive(self): # Receive a Python expression from Emacs, return its text unevaluated. text = sys.stdin.read(3) if not text or text[0] != '>': raise Protocol.ProtocolError, "`>' expected." while text[-1] != '\t': text = text + sys.stdin.read(1) return sys.stdin.read(int(text[1:-1])) def send(self, text): # Send TEXT to Emacs, which is an expression to evaluate. if text[-1] == '\n': sys.stdout.write('<%d\t%s' % (len(text), text)) else: sys.stdout.write('<%d\t%s\n' % (len(text) + 1, text)) sys.stdout.flush() def reply(value): # This function implements the `reply' pseudo-function. raise Protocol.ReplyException, value def error(message): # This function implements the `error' pseudo-function. raise Protocol.ErrorException, "Emacs: %s" % message def pymacs_load_helper(file_without_extension, prefix): # This function imports a Python module, then returns a Lisp expression # which, when later evaluated, will install trampoline definitions in # Emacs for accessing the Python module facilities. MODULE may be a # full path, yet without the `.py' or `.pyc' extension, in which case # the directory is temporarily added to the Python search path for # the sole duration of that import. All defined symbols on the Lisp # side have have PREFIX prepended, and have Python underlines in Python # turned into dashes. If PREFIX is None, it then defaults to the base # name of MODULE with underlines turned to dashes, followed by a dash. directory, module_name = os.path.split(file_without_extension) module_components = string.split(module_name, '.') if prefix is None: prefix = string.replace(module_components[-1], '_', '-') + '-' try: object = sys.modules.get(module_name) if object: reload(object) else: try: if directory: sys.path.insert(0, directory) object = __import__(module_name) finally: if directory: del sys.path[0] # Whenever MODULE_NAME is of the form [PACKAGE.]...MODULE, # __import__ returns the outer PACKAGE, not the module. for component in module_components[1:]: object = getattr(object, component) except ImportError: return None load_hook = object.__dict__.get('pymacs_load_hook') if load_hook: load_hook() interactions = object.__dict__.get('interactions', {}) if type(interactions) != types.DictType: interactions = {} arguments = [] for name, value in object.__dict__.items(): if callable(value) and value is not lisp: arguments.append(allocate_python(value)) arguments.append(lisp[prefix + string.replace(name, '_', '-')]) try: interaction = value.interaction except AttributeError: interaction = interactions.get(value) if callable(interaction): arguments.append(allocate_python(interaction)) else: arguments.append(interaction) if arguments: return [lisp.progn, [lisp.pymacs_defuns, [lisp.quote, arguments]], object] return [lisp.quote, object] def doc_string(object): if hasattr(object, '__doc__'): return object.__doc__ # Garbage collection matters. # Many Python types do not have direct Lisp equivalents, and may not be # directly returned to Lisp for this reason. They are rather allocated in # a list of handles, below, and a handle index is used for communication # instead of the Python value. Whenever such a handle is freed from the # Lisp side, its index is added of a freed list for later reuse. python = [] freed_list = [] def allocate_python(value): assert type(value) != type(''), (type(value), `value`) # Allocate some handle to hold VALUE, return its index. if freed_list: index = freed_list[-1] del freed_list[-1] python[index] = value else: index = len(python) python.append(value) return index def free_python(*indices): # Return many handles to the pool. for index in indices: python[index] = None freed_list.append(index) def zombie_python(*indices): # Ensure that some handles are _not_ in the pool. for index in indices: while index >= len(python): freed_list.append(len(python)) python.append(None) python[index] = zombie freed_list.remove(index) # Merely to make `*Pymacs*' a bit more readable. freed_list.sort() def zombie(*arguments): error("Object vanished when helper was killed.") # Emacs services for Python applications. class Let: def __init__(self, **keywords): self.stack = [] apply(self.push, (), keywords) def __del__(self): while self.stack: method = self.stack[-1][0] if method == 'variables': self.pop() elif method == 'excursion': self.pop_excursion() elif method == 'match_data': self.pop_match_data() elif method == 'restriction': self.pop_restriction() elif method == 'selected_window': self.pop_selected_window() elif method == 'window_excursion': self.pop_window_excursion() def __nonzero__(self): # So stylistic `if let:' executes faster. return 1 def push(self, **keywords): pairs = [] for name, value in keywords.items(): pairs.append((name, getattr(lisp, name).value())) setattr(lisp, name, value) self.stack.append(('variables', pairs)) return self def pop(self): method, pairs = self.stack[-1] assert method == 'variables', self.stack[-1] del self.stack[-1] for name, value in pairs: setattr(lisp, name, value) def push_excursion(self): self.stack.append(('excursion', (lisp.current_buffer(), lisp.point_marker(), lisp.mark_marker()))) return self def pop_excursion(self): method, (buffer, point_marker, mark_marker) = self.stack[-1] assert method == 'excursion', self.stack[-1] del self.stack[-1] lisp.set_buffer(buffer) lisp.goto_char(point_marker) lisp.set_mark(mark_marker) lisp.set_marker(point_marker, None) lisp.set_marker(mark_marker, None) def push_match_data(self): self.stack.append(('match_data', lisp.match_data())) return self def pop_match_data(self): method, match_data = self.stack[-1] assert method == 'match_data', self.stack[-1] del self.stack[-1] lisp.set_match_data(match_data) def push_restriction(self): self.stack.append(('restriction', (lisp.point_min_marker(), lisp.point_max_marker()))) return self def pop_restriction(self): method, (point_min_marker, point_max_marker) = self.stack[-1] assert method == 'restriction', self.stack[-1] del self.stack[-1] lisp.narrow_to_region(point_min_marker, point_max_marker) lisp.set_marker(point_min_marker, None) lisp.set_marker(point_max_marker, None) def push_selected_window(self): self.stack.append(('selected_window', lisp.selected_window())) return self def pop_selected_window(self): method, selected_window = self.stack[-1] assert method == 'selected_window', self.stack[-1] del self.stack[-1] lisp.select_window(selected_window) def push_window_excursion(self): self.stack.append(('window_excursion', lisp.current_window_configuration())) return self def pop_window_excursion(self): method, current_window_configuration = self.stack[-1] assert method == 'window_excursion', self.stack[-1] del self.stack[-1] lisp.set_window_configuration(current_window_configuration) class Symbol: def __init__(self, text): self.text = text def __repr__(self): return 'lisp[%s]' % repr(self.text) def __str__(self): return '\'' + self.text def value(self): return lisp(self.text) def copy(self): return lisp('(pymacs-expand %s)' % self.text) def set(self, value): if value is None: lisp('(setq %s nil)' % self.text) else: fragments = [] write = fragments.append write('(progn (setq %s ' % self.text) print_lisp(value, write, quoted=1) write(') nil)') lisp(string.join(fragments, '')) def __call__(self, *arguments): fragments = [] write = fragments.append write('(%s' % self.text) for argument in arguments: write(' ') print_lisp(argument, write, quoted=1) write(')') return lisp(string.join(fragments, '')) class Lisp: def __init__(self, index): self.index = index def __del__(self): lisp._protocol.freed.append(self.index) def __repr__(self): return ('lisp(%s)' % repr(lisp('(prin1-to-string %s)' % self))) def __str__(self): return '(aref pymacs-lisp %d)' % self.index def value(self): return self def copy(self): return lisp('(pymacs-expand %s)' % self) class Buffer(Lisp): pass #def write(text): # # So you could do things like # # print >>lisp.current_buffer(), "Hello World" # lisp.insert(text, self) #def point(self): # return lisp.point(self) class List(Lisp): def __call__(self, *arguments): fragments = [] write = fragments.append write('(%s' % self) for argument in arguments: write(' ') print_lisp(argument, write, quoted=1) write(')') return lisp(string.join(fragments, '')) def __len__(self): return lisp('(length %s)' % self) def __getitem__(self, key): value = lisp('(nth %d %s)' % (key, self)) if value is None and key >= len(self): raise IndexError, key return value def __setitem__(self, key, value): fragments = [] write = fragments.append write('(setcar (nthcdr %d %s) ' % (key, self)) print_lisp(value, write, quoted=1) write(')') lisp(string.join(fragments, '')) class Table(Lisp): def __getitem__(self, key): fragments = [] write = fragments.append write('(gethash ') print_lisp(key, write, quoted=1) write(' %s)' % self) return lisp(string.join(fragments, '')) def __setitem__(self, key, value): fragments = [] write = fragments.append write('(puthash ') print_lisp(key, write, quoted=1) write(' ') print_lisp(value, write, quoted=1) write(' %s)' % self) lisp(string.join(fragments, '')) class Vector(Lisp): def __len__(self): return lisp('(length %s)' % self) def __getitem__(self, key): return lisp('(aref %s %d)' % (self, key)) def __setitem__(self, key, value): fragments = [] write = fragments.append write('(aset %s %d ' % (self, key)) print_lisp(value, write, quoted=1) write(')') lisp(string.join(fragments, '')) class Lisp_Interface: def __init__(self): self.__dict__['_cache'] = {'nil': None} self.__dict__['_protocol'] = Protocol() def __call__(self, text): self._protocol.send('(progn %s)' % text) return self._protocol.loop() def __getattr__(self, name): if name[0] == '_': raise AttributeError, name return self[string.replace(name, '_', '-')] def __setattr__(self, name, value): if name[0] == '_': raise AttributeError, name self[string.replace(name, '_', '-')] = value def __getitem__(self, name): try: return self._cache[name] except KeyError: symbol = self._cache[name] = Symbol(name) return symbol def __setitem__(self, name, value): try: symbol = self._cache[name] except KeyError: symbol = self._cache[name] = Symbol(name) symbol.set(value) lisp = Lisp_Interface() print_lisp_quoted_specials = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\t': '\\t'} def print_lisp(value, write, quoted=0): if value is None: write('nil') elif type(value) == types.IntType: write(repr(value)) elif type(value) == types.FloatType: write(repr(value)) elif type(value) == types.StringType: write('"') for character in value: special = print_lisp_quoted_specials.get(character) if special is not None: write(special) elif 32 <= ord(character) < 127: write(character) else: write('\\%.3o' % ord(character)) write('"') elif type(value) == types.ListType: if quoted: write("'") if len(value) == 0: write('nil') elif len(value) == 2 and value[0] == lisp.quote: write("'") print_lisp(value[1], write) else: write('(') print_lisp(value[0], write) for sub_value in value[1:]: write(' ') print_lisp(sub_value, write) write(')') elif type(value) == types.TupleType: write('[') if len(value) > 0: print_lisp(value[0], write) for sub_value in value[1:]: write(' ') print_lisp(sub_value, write) write(']') elif isinstance(value, Lisp): write(str(value)) elif isinstance(value, Symbol): if quoted: write("'") write(value.text) elif callable(value): write('(pymacs-defun %d)' % allocate_python(value)) else: write('(pymacs-python %d)' % allocate_python(value)) if __name__ == '__main__': apply(main, sys.argv[1:]) bicyclerepair-0.9/ide-integration/Pymacs-0.20/Pymacs/pymacs.pyc0100664000076400007640000005700310020567234022476 0ustar pldpld;ò c>c@s¸dZdkZdkZdkZdkZd„Zdfd„ƒYZd„Zd„Zd„Z d„Z gZ gZ d „Z d „Zd „Zd „Zd fd„ƒYZdfd„ƒYZdfd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdfd„ƒYZeƒZhdd<dd <d!d"<d#d$<d%d&<d'd(tZdZdZdZd„Zd„Zd„Zd„ZRS(Ns ProtocolErrorsReplyExceptionsErrorExceptioncCs g|_dS(N(sselfsfreed(sself((s./Pymacs/pymacs.pys__init__Gsc Bsûxônoìy[|iƒ}|d djo(e|dhhƒdUd}e}nd}e|ƒ}WnÒeij o }|Sn¸ei j o} d}| }n•ei j o*} e iid| ƒe idƒn]ej o ‚nHdk}dk}|iƒ} |id| ƒd}| iƒ}nXg} | i}|io:|dƒx|iD]}|d |ƒqgW|d ƒn|d |ƒe||d dƒ|d ƒ|io|d ƒg|_n|iei| dƒƒq WdS(Niisexec sreplyserrorsProtocol error: %s sfiles(progn (pymacs-free-lisps %ds) s (pymacs-%s squoteds)s(sselfsreceivestextsevalsstatussNonesargumentsProtocolsReplyExceptionsvaluesErrorExceptionsmessages ProtocolErrorssyssstderrswritesexitsKeyboardInterruptsStringIOs tracebacks print_excsgetvalues fragmentssappendsfreedsindexs print_lispssendsstringsjoin( sselfsstatusswritesStringIOstexts tracebacksargumentsvaluesindexsmessages fragments((s./Pymacs/pymacs.pysloopJsR             cCs‹tiidƒ}| p|ddjotid‚nx,|ddjo|tiidƒ}q>Wtiit|dd!ƒƒSdS(Niis>s `>' expected.i˙˙˙˙s i(ssyssstdinsreadstextsProtocols ProtocolErrorsint(sselfstext((s./Pymacs/pymacs.pysreceivescCsj|ddjo$tiidt|ƒ|fƒn%tiidt|ƒd|fƒtiiƒdS(Ni˙˙˙˙s s<%d %ss<%d %s i(stextssyssstdoutswriteslensflush(sselfstext((s./Pymacs/pymacs.pyssendŠs$$( s__name__s __module__s ProtocolErrorsReplyExceptionsErrorExceptions__init__sloopsreceivessend(((s./Pymacs/pymacs.pysProtocol5s  7 cCsti|‚dS(N(sProtocolsReplyExceptionsvalue(svalue((s./Pymacs/pymacs.pysreply’scCstid|‚dS(Ns Emacs: %s(sProtocolsErrorExceptionsmessage(smessage((s./Pymacs/pymacs.pyserror–sc Cs}tii|ƒ\} } ti| dƒ}|t jo!ti |dddƒd}ny–t i i | ƒ}|ot|ƒnlz.| ot iid| ƒnt| ƒ}Wd| ot id=nXx!|dD]}t||ƒ}qÔWWntj o t SnX|ii dƒ}|o |ƒn|ii dhƒ}t|ƒtijo h}ng}xĠ|iiƒD]Ä\} } t| ƒo | t j ož|i!t"| ƒƒ|i!t |ti | ddƒƒy | i#}Wn"t$j o|i | ƒ}nXt|ƒo|i!t"|ƒƒq;|i!|ƒqwqwW|o&t i%t i&t i'|gg|gSnt i'|gSdS( Ns.i˙˙˙˙s_s-iispymacs_load_hooks interactions((sosspathssplitsfile_without_extensions directorys module_namesstringsmodule_componentssprefixsNonesreplacessyssmodulessgetsobjectsreloadsinserts __import__s componentsgetattrs ImportErrors__dict__s load_hooks interactionsstypestypessDictTypes argumentssitemssnamesvaluescallableslispsappendsallocate_pythons interactionsAttributeErrorsprogns pymacs_defunssquote( sfile_without_extensionsprefixs argumentssmodule_componentss interactionssobjects components load_hooks interactionsnamesvalues directorys module_name((s./Pymacs/pymacs.pyspymacs_load_helperšsT  !     $  &cCst|dƒo |iSndS(Ns__doc__(shasattrsobjects__doc__(sobject((s./Pymacs/pymacs.pys doc_stringÓscCswt|ƒtdƒjptt|ƒ| f‚totd}td=|t|\}}|i|tt|ƒiƒfƒtt||ƒqW|i id|fƒ|SdS(Ns variables( spairsskeywordssitemssnamesvaluesappendsgetattrslispssetattrsselfsstack(sselfskeywordsspairssnamesvalue((s./Pymacs/pymacs.pyspushs "cCsf|id\}}|djpt|id‚|id=x$|D]\}}tt||ƒqBWdS(Ni˙˙˙˙s variables( sselfsstacksmethodspairssAssertionErrorsnamesvaluessetattrslisp(sselfspairssnamesvaluesmethod((s./Pymacs/pymacs.pyspop&s   cCs9|iidtiƒtiƒtiƒffƒ|SdS(Ns excursion(sselfsstacksappendslispscurrent_buffers point_markers mark_marker(sself((s./Pymacs/pymacs.pyspush_excursion-s1cCs|id\}\}}}|djpt|id‚|id=ti|ƒti |ƒti |ƒti |t ƒti |t ƒdS(Ni˙˙˙˙s excursion( sselfsstacksmethodsbuffers point_markers mark_markersAssertionErrorslisps set_buffers goto_charsset_marks set_markersNone(sselfsbuffers point_markers mark_markersmethod((s./Pymacs/pymacs.pys pop_excursion3s    cCs$|iidtiƒfƒ|SdS(Ns match_data(sselfsstacksappendslisps match_data(sself((s./Pymacs/pymacs.pyspush_match_data=scCsL|id\}}|djpt|id‚|id=ti|ƒdS(Ni˙˙˙˙s match_data(sselfsstacksmethods match_datasAssertionErrorslispsset_match_data(sselfs match_datasmethod((s./Pymacs/pymacs.pyspop_match_dataAs cCs0|iidtiƒtiƒffƒ|SdS(Ns restriction(sselfsstacksappendslispspoint_min_markerspoint_max_marker(sself((s./Pymacs/pymacs.pyspush_restrictionGs(cCsu|id\}\}}|djpt|id‚|id=ti||ƒti|t ƒti|t ƒdS(Ni˙˙˙˙s restriction( sselfsstacksmethodspoint_min_markerspoint_max_markersAssertionErrorslispsnarrow_to_regions set_markersNone(sselfspoint_min_markerspoint_max_markersmethod((s./Pymacs/pymacs.pyspop_restrictionLs  cCs$|iidtiƒfƒ|SdS(Nsselected_window(sselfsstacksappendslispsselected_window(sself((s./Pymacs/pymacs.pyspush_selected_windowTscCsL|id\}}|djpt|id‚|id=ti|ƒdS(Ni˙˙˙˙sselected_window(sselfsstacksmethodsselected_windowsAssertionErrorslisps select_window(sselfsmethodsselected_window((s./Pymacs/pymacs.pyspop_selected_windowXs cCs$|iidtiƒfƒ|SdS(Nswindow_excursion(sselfsstacksappendslispscurrent_window_configuration(sself((s./Pymacs/pymacs.pyspush_window_excursion^scCsL|id\}}|djpt|id‚|id=ti|ƒdS(Ni˙˙˙˙swindow_excursion(sselfsstacksmethodscurrent_window_configurationsAssertionErrorslispsset_window_configuration(sselfscurrent_window_configurationsmethod((s./Pymacs/pymacs.pyspop_window_excursioncs (s__name__s __module__s__init__s__del__s __nonzero__spushspopspush_excursions pop_excursionspush_match_dataspop_match_dataspush_restrictionspop_restrictionspush_selected_windowspop_selected_windowspush_window_excursionspop_window_excursion(((s./Pymacs/pymacs.pysLets             sSymbolcBsGtZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(NcCs ||_dS(N(stextsself(sselfstext((s./Pymacs/pymacs.pys__init__kscCsdt|iƒSdS(Nslisp[%s](sreprsselfstext(sself((s./Pymacs/pymacs.pys__repr__nscCsd|iSdS(Ns'(sselfstext(sself((s./Pymacs/pymacs.pys__str__qscCst|iƒSdS(N(slispsselfstext(sself((s./Pymacs/pymacs.pysvaluetscCstd|iƒSdS(Ns(pymacs-expand %s)(slispsselfstext(sself((s./Pymacs/pymacs.pyscopywscCsy|tjotd|iƒnTg}|i}|d|iƒt||ddƒ|dƒtt i |dƒƒdS(Ns (setq %s nil)s(progn (setq %s squotedis) nil)s( svaluesNoneslispsselfstexts fragmentssappendswrites print_lispsstringsjoin(sselfsvalueswrites fragments((s./Pymacs/pymacs.pyssetzs   cGsrg}|i}|d|iƒx+|D]#}|dƒt||ddƒq'W|dƒtt i |dƒƒSdS(Ns(%ss squotedis)s( s fragmentssappendswritesselfstexts argumentssarguments print_lispslispsstringsjoin(sselfs argumentssargumentswrites fragments((s./Pymacs/pymacs.pys__call__…s   ( s__name__s __module__s__init__s__repr__s__str__svaluescopyssets__call__(((s./Pymacs/pymacs.pysSymbolis      sLispcBs>tZd„Zd„Zd„Zd„Zd„Zd„ZRS(NcCs ||_dS(N(sindexsself(sselfsindex((s./Pymacs/pymacs.pys__init__‘scCstiii|iƒdS(N(slisps _protocolsfreedsappendsselfsindex(sself((s./Pymacs/pymacs.pys__del__”scCsdttd|ƒƒSdS(Nslisp(%s)s(prin1-to-string %s)(sreprslispsself(sself((s./Pymacs/pymacs.pys__repr__—scCsd|iSdS(Ns(aref pymacs-lisp %d)(sselfsindex(sself((s./Pymacs/pymacs.pys__str__šscCs|SdS(N(sself(sself((s./Pymacs/pymacs.pysvaluescCstd|ƒSdS(Ns(pymacs-expand %s)(slispsself(sself((s./Pymacs/pymacs.pyscopy s(s__name__s __module__s__init__s__del__s__repr__s__str__svaluescopy(((s./Pymacs/pymacs.pysLisps      sBuffercBstZRS(N(s__name__s __module__(((s./Pymacs/pymacs.pysBuffer£ssListcBs,tZd„Zd„Zd„Zd„ZRS(NcGsog}|i}|d|ƒx+|D]#}|dƒt||ddƒq$W|dƒtti |dƒƒSdS(Ns(%ss squotedis)s( s fragmentssappendswritesselfs argumentssarguments print_lispslispsstringsjoin(sselfs argumentssargumentswrites fragments((s./Pymacs/pymacs.pys__call__°s   cCstd|ƒSdS(Ns (length %s)(slispsself(sself((s./Pymacs/pymacs.pys__len__şscCsKtd||fƒ}|tjo|t|ƒjo t|‚n|SdS(Ns (nth %d %s)(slispskeysselfsvaluesNoneslens IndexError(sselfskeysvalue((s./Pymacs/pymacs.pys __getitem__½s  cCsZg}|i}|d||fƒt||ddƒ|dƒtti |dƒƒdS(Ns(setcar (nthcdr %d %s) squotedis)s( s fragmentssappendswriteskeysselfs print_lispsvalueslispsstringsjoin(sselfskeysvalueswrites fragments((s./Pymacs/pymacs.pys __setitem__s   (s__name__s __module__s__call__s__len__s __getitem__s __setitem__(((s./Pymacs/pymacs.pysLists  sTablecBstZd„Zd„ZRS(NcCsTg}|i}|dƒt||ddƒ|d|ƒtti|dƒƒSdS(Ns (gethash squotedis %s)s( s fragmentssappendswrites print_lispskeysselfslispsstringsjoin(sselfskeyswrites fragments((s./Pymacs/pymacs.pys __getitem__Ís   cCsqg}|i}|dƒt||ddƒ|dƒt||ddƒ|d|ƒtti |dƒƒdS(Ns (puthash squotedis s %s)s( s fragmentssappendswrites print_lispskeysvaluesselfslispsstringsjoin(sselfskeysvalueswrites fragments((s./Pymacs/pymacs.pys __setitem__Ġs   (s__name__s __module__s __getitem__s __setitem__(((s./Pymacs/pymacs.pysTableËs sVectorcBs#tZd„Zd„Zd„ZRS(NcCstd|ƒSdS(Ns (length %s)(slispsself(sself((s./Pymacs/pymacs.pys__len__áscCstd||fƒSdS(Ns (aref %s %d)(slispsselfskey(sselfskey((s./Pymacs/pymacs.pys __getitem__äscCsZg}|i}|d||fƒt||ddƒ|dƒtti |dƒƒdS(Ns (aset %s %d squotedis)s( s fragmentssappendswritesselfskeys print_lispsvalueslispsstringsjoin(sselfskeysvalueswrites fragments((s./Pymacs/pymacs.pys __setitem__çs   (s__name__s __module__s__len__s __getitem__s __setitem__(((s./Pymacs/pymacs.pysVectorßs  sLisp_InterfacecBs>tZd„Zd„Zd„Zd„Zd„Zd„ZRS(NcCs*hdt<|id, April 1991. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """\ Handling of boxed comments in various box styles. Introduction ------------ For comments held within boxes, it is painful to fill paragraphs, while stretching or shrinking the surrounding box "by hand", as needed. This piece of Python code eases my life on this. It may be used interactively from within Emacs through the Pymacs interface, or in batch as a script which filters a single region to be reformatted. I find only fair, while giving all sources for a package using such boxed comments, to also give the means I use for nicely modifying comments. So here they are! Box styles ---------- Each supported box style has a number associated with it. This number is arbitrary, yet by _convention_, it holds three non-zero digits such the the hundreds digit roughly represents the programming language, the tens digit roughly represents a box quality (or weight) and the units digit roughly a box type (or figure). An unboxed comment is merely one of box styles. Language, quality and types are collectively referred to as style attributes. When rebuilding a boxed comment, attributes are selected independently of each other. They may be specified by the digits of the value given as Emacs commands argument prefix, or as the `-s' argument to the `rebox' script when called from the shell. If there is no such prefix, or if the corresponding digit is zero, the attribute is taken from the value of the default style instead. If the corresponding digit of the default style is also zero, than the attribute is recognised and taken from the actual boxed comment, as it existed before prior to the command. The value 1, which is the simplest attribute, is ultimately taken if the parsing fails. A programming language is associated with comment delimiters. Values are 100 for none or unknown, 200 for `/*' and `*/' as in plain C, 300 for `//' as in C++, 400 for `#' as in most scripting languages, 500 for `;' as in LISP or assembler and 600 for `%' as in TeX or PostScript. Box quality differs according to language. For unknown languages (100) or for the C language (200), values are 10 for simple, 20 for rounded, and 30 or 40 for starred. Simple quality boxes (10) use comment delimiters to left and right of each comment line, and also for the top or bottom line when applicable. Rounded quality boxes (20) try to suggest rounded corners in boxes. Starred quality boxes (40) mostly use a left margin of asterisks or X'es, and use them also in box surroundings. For all others languages, box quality indicates the thickness in characters of the left and right sides of the box: values are 10, 20, 30 or 40 for 1, 2, 3 or 4 characters wide. With C++, quality 10 is not useful, it is not allowed. Box type values are 1 for fully opened boxes for which boxing is done only for the left and right but not for top or bottom, 2 for half single lined boxes for which boxing is done on all sides except top, 3 for fully single lined boxes for which boxing is done on all sides, 4 for half double lined boxes which is like type 2 but more bold, or 5 for fully double lined boxes which is like type 3 but more bold. The special style 221 is for C comments between a single opening `/*' and a single closing `*/'. The special style 111 deletes a box. Batch usage ----------- Usage is `rebox [OPTION]... [FILE]'. By default, FILE is reformatted to standard output by refilling the comment up to column 79, while preserving existing boxed comment style. If FILE is not given, standard input is read. Options may be: -n Do not refill the comment inside its box, and ignore -w. -s STYLE Replace box style according to STYLE, as explained above. -t Replace initial sequence of spaces by TABs on each line. -v Echo both the old and the new box styles on standard error. -w WIDTH Try to avoid going over WIDTH columns per line. So, a single boxed comment is reformatted by invocation. `vi' users, for example, would need to delimit the boxed comment first, before executing the `!}rebox' command (is this correct? my `vi' recollection is far away). Batch usage is also slow, as internal structures have to be reinitialised at every call. Producing a box in a single style is fast, but recognising the previous style requires setting up for all possible styles. Emacs usage ----------- For most Emacs language editing modes, refilling does not make sense outside comments, one may redefine the `M-q' command and link it to this Pymacs module. For example, I use this in my `.emacs' file: (add-hook 'c-mode-hook 'fp-c-mode-routine) (defun fp-c-mode-routine () (local-set-key "\M-q" 'rebox-comment)) (autoload 'rebox-comment "rebox" nil t) (autoload 'rebox-region "rebox" nil t) with a "rebox.el" file having this single line: (pymacs-load "Pymacs.rebox") Install Pymacs from `http://www.iro.umontreal.ca/~pinard/pymacs.tar.gz'. The Emacs function `rebox-comment' automatically discovers the extent of the boxed comment near the cursor, possibly refills the text, then adjusts the box style. When this command is executed, the cursor should be within a comment, or else it should be between two comments, in which case the command applies to the next comment. The function `rebox-region' does the same, except that it takes the current region as a boxed comment. Both commands obey numeric prefixes to add or remove a box, force a particular box style, or to prevent refilling of text. Without such prefixes, the commands may deduce the current box style from the comment itself so the style is preserved. The default style initial value is nil or 0. It may be preset to another value through calling `rebox-set-default-style' from Emacs LISP, or changed to anything else though using a negative value for a prefix, in which case the default style is set to the absolute value of the prefix. A `C-u' prefix avoids refilling the text, but forces using the default box style. `C-u -' lets the user interact to select one attribute at a time. Adding new styles ----------------- Let's suppose you want to add your own boxed comment style, say: //--------------------------------------------+ // This is the style mandated in our company. //--------------------------------------------+ You might modify `rebox.py' but then, you will have to edit it whenever you get a new release of `pybox.py'. Emacs users might modify their `.emacs' file or their `rebox.el' bootstrap, if they use one. In either cases, after the `(pymacs-load "Pymacs.rebox")' line, merely add: (rebox-Template NNN MMM ["//-----+" "// box " "//-----+"]) If you use the `rebox' script rather than Emacs, the simplest is to make your own. This is easy, as it is very small. For example, the above style could be implemented by using this script instead of `rebox': #!/usr/bin/env python import sys from Pymacs import rebox rebox.Template(226, 325, ('//-----+', '// box ', '//-----+')) apply(rebox.main, tuple(sys.argv[1:])) In all cases, NNN is the style three-digit number, with no zero digit. Pick any free style number, you are safe with 911 and up. MMM is the recognition priority, only used to disambiguate the style of a given boxed comments, when it matches many styles at once. Try something like 400. Raise or lower that number as needed if you observe false matches. On average, the template uses three lines of equal length. Do not worry if this implies a few trailing spaces, they will be cleaned up automatically at box generation time. The first line or the third line may be omitted to create vertically opened boxes. But the middle line may not be omitted, it ought to include the word `box', which will get replaced by your actual comment. If the first line is shorter than the middle one, it gets merged at the start of the comment. If the last line is shorter than the middle one, it gets merged at the end of the comment and is refilled with it. History ------- I first observed rounded corners, as in style 223 boxes, in code from Warren Tucker, a previous maintainer of the `shar' package, circa 1980. Except for very special files, I carefully avoided boxed comments for real work, as I found them much too hard to maintain. My friend Paul Provost was working at Taarna, a computer graphics place, which had boxes as part of their coding standards. He asked that we try something to get him out of his misery, and this how `rebox.el' was originally written. I did not plan to use it for myself, but Paul was so enthusiastic that I timidly started to use boxes in my things, very little at first, but more and more as time passed, still in doubt that it was a good move. Later, many friends spontaneously started to use this tool for real, some being very serious workers. This convinced me that boxes are acceptable, after all. I do not use boxes much with Python code. It is so legible that boxing is not that useful. Vertical white space is less necessary, too. I even avoid white lines within functions. Comments appear prominent enough when using highlighting editors like Emacs or nice printer tools like `enscript'. After Emacs could be extended with Python, in 2001, I translated `rebox.el' into `rebox.py', and added the facility to use it as a batch script. """ ## Note: This code is currently compatible down to Python version 1.5.2. ## It is probably worth keeping it that way for a good while, still. ## Note: a double hash comment introduces a group of functions or methods. import re, string, sys def main(*arguments): refill = 1 style = None tabify = 0 verbose = 0 width = 79 import getopt options, arguments = getopt.getopt(arguments, 'ns:tvw:', ['help']) for option, value in options: if option == '--help': sys.stdout.write(__doc__) sys.exit(0) elif option == '-n': refill = 0 elif option == '-s': style = int(value) elif option == '-t': tabify = 1 elif option == '-v': verbose = 1 elif option == '-w': width = int(value) if len(arguments) == 0: text = sys.stdin.read() elif len(arguments) == 1: text = open(arguments[0]).read() else: sys.stderr.write("Invalid usage, try `rebox --help' for help.\n") sys.exit(1) old_style, new_style, text, position = engine( text, style=style, width=width, refill=refill, tabify=tabify) if text is None: sys.stderr.write("* Cannot rebox to style %d.\n" % new_style) sys.exit(1) sys.stdout.write(text) if verbose: if old_style == new_style: sys.stderr.write("Reboxed with style %d.\n" % old_style) else: sys.stderr.write("Reboxed from style %d to %d.\n" % (old_style, new_style)) def pymacs_load_hook(): global interactions, lisp, Let, region, comment, set_default_style from Pymacs import lisp, Let emacs_rebox = Emacs_Rebox() # Declare functions for Emacs to import. interactions = {} region = emacs_rebox.region interactions[region] = 'P' comment = emacs_rebox.comment interactions[comment] = 'P' set_default_style = emacs_rebox.set_default_style class Emacs_Rebox: def __init__(self): self.default_style = None def set_default_style(self, style): """\ Set the default style to STYLE. """ self.default_style = style def region(self, flag): """\ Rebox the boxed comment in the current region, obeying FLAG. """ self.emacs_engine(flag, self.find_region) def comment(self, flag): """\ Rebox the surrounding boxed comment, obeying FLAG. """ self.emacs_engine(flag, self.find_comment) def emacs_engine(self, flag, find_limits): """\ Rebox text while obeying FLAG. Call FIND_LIMITS to discover the extent of the boxed comment. """ # `C-u -' means that box style is to be decided interactively. if flag == lisp['-']: flag = self.ask_for_style() # If FLAG is zero or negative, only change default box style. if type(flag) is type(0) and flag <= 0: self.default_style = -flag lisp.message("Default style set to %d" % -flag) return # Decide box style and refilling. if flag is None: style = self.default_style refill = 1 elif type(flag) == type(0): if self.default_style is None: style = flag else: style = merge_styles(self.default_style, flag) refill = 1 else: flag = flag.copy() if type(flag) == type([]): style = self.default_style refill = 0 else: lisp.error("Unexpected flag value %s" % flag) # Prepare for reboxing. lisp.message("Reboxing...") checkpoint = lisp.buffer_undo_list.value() start, end = find_limits() text = lisp.buffer_substring(start, end) width = lisp.fill_column.value() tabify = lisp.indent_tabs_mode.value() is not None point = lisp.point() if start <= point < end: position = point - start else: position = None # Rebox the text and replace it in Emacs buffer. old_style, new_style, text, position = engine( text, style=style, width=width, refill=refill, tabify=tabify, position=position) if text is None: lisp.error("Cannot rebox to style %d" % new_style) lisp.delete_region(start, end) lisp.insert(text) if position is not None: lisp.goto_char(start + position) # Collapse all operations into a single one, for Undo. self.clean_undo_after(checkpoint) # We are finished, tell the user. if old_style == new_style: lisp.message("Reboxed with style %d" % old_style) else: lisp.message("Reboxed from style %d to %d" % (old_style, new_style)) def ask_for_style(self): """\ Request the style interactively, using the minibuffer. """ language = quality = type = None while language is None: lisp.message("\ Box language is 100-none, 200-/*, 300-//, 400-#, 500-;, 600-%%") key = lisp.read_char() if key >= ord('0') and key <= ord('6'): language = key - ord('0') while quality is None: lisp.message("\ Box quality/width is 10-simple/1, 20-rounded/2, 30-starred/3 or 40-starred/4") key = lisp.read_char() if key >= ord('0') and key <= ord('4'): quality = key - ord('0') while type is None: lisp.message("\ Box type is 1-opened, 2-half-single, 3-single, 4-half-double or 5-double") key = lisp.read_char() if key >= ord('0') and key <= ord('5'): type = key - ord('0') return 100*language + 10*quality + type def find_region(self): """\ Return the limits of the region. """ return lisp.point(), lisp.mark(lisp.t) def find_comment(self): """\ Find and return the limits of the block of comments following or enclosing the cursor, or return an error if the cursor is not within such a block of comments. Extend it as far as possible in both directions. """ let = Let() let.push_excursion() # Find the start of the current or immediately following comment. lisp.beginning_of_line() lisp.skip_chars_forward(' \t\n') lisp.beginning_of_line() if not language_matcher[0](self.remainder_of_line()): temp = lisp.point() if not lisp.re_search_forward('\\*/', None, lisp.t): lisp.error("outside any comment block") lisp.re_search_backward('/\\*') if lisp.point() > temp: lisp.error("outside any comment block") temp = lisp.point() lisp.beginning_of_line() lisp.skip_chars_forward(' \t') if lisp.point() != temp: lisp.error("text before start of comment") lisp.beginning_of_line() start = lisp.point() language = guess_language(self.remainder_of_line()) # Find the end of this comment. if language == 2: lisp.search_forward('*/') if not lisp.looking_at('[ \t]*$'): lisp.error("text after end of comment") lisp.end_of_line() if lisp.eobp(): lisp.insert('\n') else: lisp.forward_char(1) end = lisp.point() # Try to extend the comment block backwards. lisp.goto_char(start) while not lisp.bobp(): if language == 2: lisp.skip_chars_backward(' \t\n') if not lisp.looking_at('[ \t]*\n[ \t]*/\\*'): break if lisp.point() < 2: break lisp.backward_char(2) if not lisp.looking_at('\\*/'): break lisp.re_search_backward('/\\*') temp = lisp.point() lisp.beginning_of_line() lisp.skip_chars_forward(' \t') if lisp.point() != temp: break lisp.beginning_of_line() else: lisp.previous_line(1) if not language_matcher[language](self.remainder_of_line()): break start = lisp.point() # Try to extend the comment block forward. lisp.goto_char(end) while language_matcher[language](self.remainder_of_line()): if language == 2: lisp.re_search_forward('[ \t]*/\\*') lisp.re_search_forward('\\*/') if lisp.looking_at('[ \t]*$'): lisp.beginning_of_line() lisp.forward_line(1) end = lisp.point() else: lisp.forward_line(1) end = lisp.point() return start, end def remainder_of_line(self): """\ Return all characters between point and end of line in Emacs buffer. """ return lisp('''\ (buffer-substring (point) (save-excursion (skip-chars-forward "^\n") (point))) ''') def clean_undo_after_old(self, checkpoint): """\ Remove all intermediate boundaries from the Undo list since CHECKPOINT. """ # Declare some LISP functions. car = lisp.car cdr = lisp.cdr eq = lisp.eq setcdr = lisp.setcdr # Remove any `nil' delimiter recently added to the Undo list. cursor = lisp.buffer_undo_list.value() if not eq(cursor, checkpoint): tail = cdr(cursor) while not eq(tail, checkpoint): if car(tail): cursor = tail tail = cdr(cursor) else: tail = cdr(tail) setcdr(cursor, tail) def clean_undo_after(self, checkpoint): """\ Remove all intermediate boundaries from the Undo list since CHECKPOINT. """ lisp(""" (let ((undo-list %s)) (if (not (eq buffer-undo-list undo-list)) (let ((cursor buffer-undo-list)) (while (not (eq (cdr cursor) undo-list)) (if (car (cdr cursor)) (setq cursor (cdr cursor)) (setcdr cursor (cdr (cdr cursor))))))) nil) """ % (checkpoint or 'nil')) def engine(text, style=None, width=79, refill=1, tabify=0, position=None): """\ Add, delete or adjust a boxed comment held in TEXT, according to STYLE. STYLE values are explained at beginning of this file. Any zero attribute in STYLE indicates that the corresponding attribute should be recovered from the currently existing box. Produced lines will not go over WIDTH columns if possible, if refilling gets done. But if REFILL is false, WIDTH is ignored. If TABIFY is true, the beginning of produced lines will have spaces replace by TABs. POSITION is either None, or a character position within TEXT. Returns four values: the old box style, the new box style, the reformatted text, and either None or the adjusted value of POSITION in the new text. The reformatted text is returned as None if the requested style does not exist. """ last_line_complete = text and text[-1] == '\n' if last_line_complete: text = text[:-1] lines = string.split(string.expandtabs(text), '\n') # Decide about refilling and the box style to use. new_style = 111 old_template = guess_template(lines) new_style = merge_styles(new_style, old_template.style) if style is not None: new_style = merge_styles(new_style, style) new_template = template_registry.get(new_style) # Interrupt processing if STYLE does not exist. if not new_template: return old_template.style, new_style, None, None # Remove all previous comment marks, and left margin. if position is not None: marker = Marker() marker.save_position(text, position, old_template.characters()) lines, margin = old_template.unbuild(lines) # Ensure only one white line between paragraphs. counter = 1 while counter < len(lines) - 1: if lines[counter] == '' and lines[counter-1] == '': del lines[counter] else: counter = counter + 1 # Rebuild the boxed comment. lines = new_template.build(lines, width, refill, margin) # Retabify to the left only. if tabify: for counter in range(len(lines)): tabs = len(re.match(' *', lines[counter]).group()) / 8 lines[counter] = '\t' * tabs + lines[counter][8*tabs:] # Restore the point position. text = string.join(lines, '\n') if last_line_complete: text = text + '\n' if position is not None: position = marker.get_position(text, new_template.characters()) return old_template.style, new_style, text, position def guess_language(line): """\ Guess the language in use for LINE. """ for language in range(len(language_matcher) - 1, 1, -1): if language_matcher[language](line): return language return 1 def guess_template(lines): """\ Find the heaviest box template matching LINES. """ best_template = None for template in template_registry.values(): if best_template is None or template > best_template: if template.match(lines): best_template = template return best_template def left_margin_size(lines): """\ Return the width of the left margin for all LINES. Ignore white lines. """ margin = None for line in lines: counter = len(re.match(' *', line).group()) if counter != len(line): if margin is None or counter < margin: margin = counter if margin is None: margin = 0 return margin def merge_styles(original, update): """\ Return style attributes as per ORIGINAL, in which attributes have been overridden by non-zero corresponding style attributes from UPDATE. """ style = [original / 100, original / 10 % 10, original % 10] merge = update / 100, update / 10 % 10, update % 10 for counter in range(3): if merge[counter]: style[counter] = merge[counter] return 100*style[0] + 10*style[1] + style[2] def refill_lines(lines, width): """\ Refill LINES, trying to not produce lines having more than WIDTH columns. """ # Try using GNU `fmt'. import tempfile, os name = tempfile.mktemp() open(name, 'w').write(string.join(lines, '\n') + '\n') process = os.popen('fmt -cuw %d %s' % (width, name)) text = process.read() os.remove(name) if process.close() is None: return map(string.expandtabs, string.split(text, '\n')[:-1]) # If `fmt' failed, do refilling more naively, wihtout using the # Knuth algorithm, nor protecting full stops at end of sentences. lines.append(None) new_lines = [] new_line = '' start = 0 for end in range(len(lines)): if not lines[end]: margin = left_margin_size(lines[start:end]) for line in lines[start:end]: counter = len(re.match(' *', line).group()) if counter > margin: if new_line: new_lines.append(' ' * margin + new_line) new_line = '' indent = counter - margin else: indent = 0 for word in string.split(line): if new_line: if len(new_line) + 1 + len(word) > width: new_lines.append(' ' * margin + new_line) new_line = word else: new_line = new_line + ' ' + word else: new_line = ' ' * indent + word indent = 0 if new_line: new_lines.append(' ' * margin + new_line) new_line = '' if lines[end] is not None: new_lines.append('') start = end + 1 return new_lines class Marker: ## Heuristic to simulate a marker while reformatting boxes. def save_position(self, text, position, ignorable): """\ Given a TEXT and a POSITION in that text, save the adjusted position by faking that all IGNORABLE characters before POSITION were removed. """ ignore = {} for character in ' \t\r\n' + ignorable: ignore[character] = None counter = 0 for character in text[:position]: if ignore.has_key(character): counter = counter + 1 self.position = position - counter def get_position(self, text, ignorable, latest=0): """\ Given a TEXT, return the value that would yield the currently saved position, if it was saved by `save_position' with IGNORABLE. Unless the position lies within a series of ignorable characters, LATEST has no effect in practice. If LATEST is true, return the biggest possible value instead of the smallest. """ ignore = {} for character in ' \t\r\n' + ignorable: ignore[character] = None counter = 0 position = 0 if latest: for character in text: if ignore.has_key(character): counter = counter + 1 else: if position == self.position: break position = position + 1 elif self.position > 0: for character in text: if ignore.has_key(character): counter = counter + 1 else: position = position + 1 if position == self.position: break return position + counter ## Template processing. class Template: def __init__(self, style, weight, lines): """\ Digest and register a single template. The template is numbered STYLE, has a parsing WEIGHT, and is described by one to three LINES. STYLE should be used only once through all `declare_template' calls. One of the lines should contain the substring `box' to represent the comment to be boxed, and if three lines are given, `box' should appear in the middle one. Lines containing only spaces are implied as necessary before and after the the `box' line, so we have three lines. Normally, all three template lines should be of the same length. If the first line is shorter, it represents a start comment string to be bundled within the first line of the comment text. If the third line is shorter, it represents an end comment string to be bundled at the end of the comment text, and refilled with it. """ assert not template_registry.has_key(style), \ "Style %d defined more than once" % style self.style = style self.weight = weight # Make it exactly three lines, with `box' in the middle. start = string.find(lines[0], 'box') if start >= 0: line1 = None line2 = lines[0] if len(lines) > 1: line3 = lines[1] else: line3 = None else: start = string.find(lines[1], 'box') if start >= 0: line1 = lines[0] line2 = lines[1] if len(lines) > 2: line3 = lines[2] else: line3 = None else: assert 0, "Erroneous template for %d style" % style end = start + len('box') # Define a few booleans. self.merge_nw = line1 is not None and len(line1) < len(line2) self.merge_se = line3 is not None and len(line3) < len(line2) # Define strings at various cardinal directions. if line1 is None: self.nw = self.nn = self.ne = None elif self.merge_nw: self.nw = line1 self.nn = self.ne = None else: if start > 0: self.nw = line1[:start] else: self.nw = None if line1[start] != ' ': self.nn = line1[start] else: self.nn = None if end < len(line1): self.ne = string.rstrip(line1[end:]) else: self.ne = None if start > 0: self.ww = line2[:start] else: self.ww = None if end < len(line2): self.ee = line2[end:] else: self.ee = None if line3 is None: self.sw = self.ss = self.se = None elif self.merge_se: self.sw = self.ss = None self.se = string.rstrip(line3) else: if start > 0: self.sw = line3[:start] else: self.sw = None if line3[start] != ' ': self.ss = line3[start] else: self.ss = None if end < len(line3): self.se = string.rstrip(line3[end:]) else: self.se = None # Define parsing regexps. if self.merge_nw: self.regexp1 = re.compile(' *' + regexp_quote(self.nw) + '.*$') elif self.nw and not self.nn and not self.ne: self.regexp1 = re.compile(' *' + regexp_quote(self.nw) + '$') elif self.nw or self.nn or self.ne: self.regexp1 = re.compile( ' *' + regexp_quote(self.nw) + regexp_ruler(self.nn) + regexp_quote(self.ne) + '$') else: self.regexp1 = None if self.ww or self.ee: self.regexp2 = re.compile( ' *' + regexp_quote(self.ww) + '.*' + regexp_quote(self.ee) + '$') else: self.regexp2 = None if self.merge_se: self.regexp3 = re.compile('.*' + regexp_quote(self.se) + '$') elif self.sw and not self.ss and not self.se: self.regexp3 = re.compile(' *' + regexp_quote(self.sw) + '$') elif self.sw or self.ss or self.se: self.regexp3 = re.compile( ' *' + regexp_quote(self.sw) + regexp_ruler(self.ss) + regexp_quote(self.se) + '$') else: self.regexp3 = None # Save results. template_registry[style] = self def __cmp__(self, other): return cmp(self.weight, other.weight) def characters(self): """\ Return a string of characters which may be used to draw the box. """ characters = '' for text in (self.nw, self.nn, self.ne, self.ww, self.ee, self.sw, self.ss, self.se): if text: for character in text: if character not in characters: characters = characters + character return characters def match(self, lines): """\ Returns true if LINES exactly match this template. """ start = 0 end = len(lines) if self.regexp1 is not None: if start == end or not self.regexp1.match(lines[start]): return 0 start = start + 1 if self.regexp3 is not None: if end == 0 or not self.regexp3.match(lines[end-1]): return 0 end = end - 1 if self.regexp2 is not None: for line in lines[start:end]: if not self.regexp2.match(line): return 0 return 1 def unbuild(self, lines): """\ Remove all comment marks from LINES, as hinted by this template. Returns the cleaned up set of lines, and the size of the left margin. """ margin = left_margin_size(lines) # Remove box style marks. start = 0 end = len(lines) if self.regexp1 is not None: lines[start] = unbuild_clean(lines[start], self.regexp1) start = start + 1 if self.regexp3 is not None: lines[end-1] = unbuild_clean(lines[end-1], self.regexp3) end = end - 1 if self.regexp2 is not None: for counter in range(start, end): lines[counter] = unbuild_clean(lines[counter], self.regexp2) # Remove the left side of the box after it turned into spaces. delta = left_margin_size(lines) - margin for counter in range(len(lines)): lines[counter] = lines[counter][delta:] # Remove leading and trailing white lines. start = 0 end = len(lines) while start < end and lines[start] == '': start = start + 1 while end > start and lines[end-1] == '': end = end - 1 return lines[start:end], margin def build(self, lines, width, refill, margin): """\ Put LINES back into a boxed comment according to this template, after having refilled them if REFILL. The box should start at column MARGIN, and the total size of each line should ideally not go over WIDTH. """ # Merge a short end delimiter now, so it gets refilled with text. if self.merge_se: if lines: lines[-1] = lines[-1] + ' ' + self.se else: lines = [self.se] # Reduce WIDTH according to left and right inserts, then refill. if self.ww: width = width - len(self.ww) if self.ee: width = width - len(self.ee) if refill: lines = refill_lines(lines, width) # Reduce WIDTH further according to the current right margin, # and excluding the left margin. maximum = 0 for line in lines: if line: if line[-1] in '.!?': length = len(line) + 1 else: length = len(line) if length > maximum: maximum = length width = maximum - margin # Construct the top line. if self.merge_nw: lines[0] = ' ' * margin + self.nw + lines[0][margin:] start = 1 elif self.nw or self.nn or self.ne: if self.nn: line = self.nn * width else: line = ' ' * width if self.nw: line = self.nw + line if self.ne: line = line + self.ne lines.insert(0, string.rstrip(' ' * margin + line)) start = 1 else: start = 0 # Construct all middle lines. for counter in range(start, len(lines)): line = lines[counter][margin:] line = line + ' ' * (width - len(line)) if self.ww: line = self.ww + line if self.ee: line = line + self.ee lines[counter] = string.rstrip(' ' * margin + line) # Construct the bottom line. if self.sw or self.ss or self.se and not self.merge_se: if self.ss: line = self.ss * width else: line = ' ' * width if self.sw: line = self.sw + line if self.se and not self.merge_se: line = line + self.se lines.append(string.rstrip(' ' * margin + line)) return lines def regexp_quote(text): """\ Return a regexp matching TEXT without its surrounding space, maybe followed by spaces. If STRING is nil, return the empty regexp. Unless spaces, the text is nested within a regexp parenthetical group. """ if text is None: return '' if text == ' ' * len(text): return ' *' return '(' + re.escape(string.strip(text)) + ') *' def regexp_ruler(character): """\ Return a regexp matching two or more repetitions of CHARACTER, maybe followed by spaces. Is CHARACTER is nil, return the empty regexp. Unless spaces, the ruler is nested within a regexp parenthetical group. """ if character is None: return '' if character == ' ': return ' +' return '(' + re.escape(character + character) + '+) *' def unbuild_clean(line, regexp): """\ Return LINE with all parenthetical groups in REGEXP erased and replaced by an equivalent number of spaces, except for trailing spaces, which get removed. """ match = re.match(regexp, line) groups = match.groups() for counter in range(len(groups)): if groups[counter] is not None: start, end = match.span(1 + counter) line = line[:start] + ' ' * (end - start) + line[end:] return string.rstrip(line) ## Template data. # Matcher functions for a comment start, indexed by numeric LANGUAGE. language_matcher = [] for pattern in (r' *(/\*|//+|#+|;+|%+)', r'', # 1 r' */\*', # 2 r' *//+', # 3 r' *#+', # 4 r' *;+', # 5 r' *%+'): # 6 language_matcher.append(re.compile(pattern).match) # Template objects, indexed by numeric style. template_registry = {} def make_generic(style, weight, lines): """\ Add various language digit to STYLE and generate one template per language, all using the same WEIGHT. Replace `?' in LINES accordingly. """ for language, character in ((300, '/'), # C++ style comments (400, '#'), # scripting languages (500, ';'), # LISP and assembler (600, '%')): # TeX and PostScript new_style = language + style if 310 < new_style <= 319: # Disallow quality 10 with C++. continue new_lines = [] for line in lines: new_lines.append(string.replace(line, '?', character)) Template(new_style, weight, new_lines) # Generic programming language templates. make_generic(11, 115, ('? box',)) make_generic(12, 215, ('? box ?', '? --- ?')) make_generic(13, 315, ('? --- ?', '? box ?', '? --- ?')) make_generic(14, 415, ('? box ?', '???????')) make_generic(15, 515, ('???????', '? box ?', '???????')) make_generic(21, 125, ('?? box',)) make_generic(22, 225, ('?? box ??', '?? --- ??')) make_generic(23, 325, ('?? --- ??', '?? box ??', '?? --- ??')) make_generic(24, 425, ('?? box ??', '?????????')) make_generic(25, 525, ('?????????', '?? box ??', '?????????')) make_generic(31, 135, ('??? box',)) make_generic(32, 235, ('??? box ???', '??? --- ???')) make_generic(33, 335, ('??? --- ???', '??? box ???', '??? --- ???')) make_generic(34, 435, ('??? box ???', '???????????')) make_generic(35, 535, ('???????????', '??? box ???', '???????????')) make_generic(41, 145, ('???? box',)) make_generic(42, 245, ('???? box ????', '???? --- ????')) make_generic(43, 345, ('???? --- ????', '???? box ????', '???? --- ????')) make_generic(44, 445, ('???? box ????', '?????????????')) make_generic(45, 545, ('?????????????', '???? box ????', '?????????????')) # Textual (non programming) templates. Template(111, 113, ('box',)) Template(112, 213, ('| box |', '+-----+')) Template(113, 313, ('+-----+', '| box |', '+-----+')) Template(114, 413, ('| box |', '*=====*')) Template(115, 513, ('*=====*', '| box |', '*=====*')) Template(121, 123, ('| box |',)) Template(122, 223, ('| box |', '`-----\'')) Template(123, 323, ('.-----.', '| box |', '`-----\'')) Template(124, 423, ('| box |', '\\=====/')) Template(125, 523, ('/=====\\', '| box |', '\\=====/')) Template(141, 143, ('| box ',)) Template(142, 243, ('* box *', '*******')) Template(143, 343, ('*******', '* box *', '*******')) Template(144, 443, ('X box X', 'XXXXXXX')) Template(145, 543, ('XXXXXXX', 'X box X', 'XXXXXXX')) # C language templates. Template(211, 118, ('/* box */',)) Template(212, 218, ('/* box */', '/* --- */')) Template(213, 318, ('/* --- */', '/* box */', '/* --- */')) Template(214, 418, ('/* box */', '/* === */')) Template(215, 518, ('/* === */', '/* box */', '/* === */')) Template(221, 128, ('/* ', ' box', '*/')) Template(222, 228, ('/* .', '| box |', '`----*/')) Template(223, 328, ('/*----.', '| box |', '`----*/')) Template(224, 428, ('/* \\', '| box |', '\\====*/')) Template(225, 528, ('/*====\\', '| box |', '\\====*/')) Template(231, 138, ('/* ', ' | box', ' */ ')) Template(232, 238, ('/* ', ' | box | ', ' *-----*/')) Template(233, 338, ('/*-----* ', ' | box | ', ' *-----*/')) Template(234, 438, ('/* box */', '/*-----*/')) Template(235, 538, ('/*-----*/', '/* box */', '/*-----*/')) Template(241, 148, ('/* ', ' * box', ' */ ')) Template(242, 248, ('/* * ', ' * box * ', ' *******/')) Template(243, 348, ('/******* ', ' * box * ', ' *******/')) Template(244, 448, ('/* box */', '/*******/')) Template(245, 548, ('/*******/', '/* box */', '/*******/')) Template(251, 158, ('/* ', ' * box', ' */ ')) if __name__ == '__main__': apply(main, sys.argv[1:]) bicyclerepair-0.9/ide-integration/Pymacs-0.20/ChangeLog0100644000076400007640000003351307606002142020777 0ustar pldpld2002-11-23 François Pinard * : Release 0.20. * Pymacs/__init__.py: Integrate version.py. * Pymacs/version.py: Deleted. * setup.py, Pymacs/pymacs.py: Adjusted. 2002-11-15 François Pinard * pymacs.el (pymacs-python-reference): Handle when function is defined as a mere variable, or when a function is being advised. 2002-11-14 François Pinard * : Release 0.19. * Pymacs/pymacs.py (List.__getitem__): Raise IndexError when out of bounds. This should allow for iterating over a list. * README.html: New, merely a template for Webert. 2002-11-13 François Pinard * pymacs.el (pymacs-call): New. Use it whenever adequate. 2002-09-26 François Pinard * Makefile (publish): Revised. 2002-08-18 François Pinard * : Release 0.18. 2002-08-09 François Pinard * Pymacs/rebox.py (Emacs_Rebox.find_comment): Correctly spell backward_char, not backward-char. 2002-08-08 François Pinard * Pymacs/rebox.py (pymacs_load_hook): Compute the interactions map from the bound methods, instead of from the generic ones. 2002-07-14 François Pinard * Pymacs/pymacs.py (Lisp_Interface.__call__): Wrap argument in progn, so lisp() could accept a sequence of expressions. 2002-07-01 François Pinard * pymacs.el (pymacs-start-services): Disable undo for *Pymacs*. 2002-06-25 François Pinard * : Release 0.17. * pymacs.py: Deleted, this was the compatibility module. * setup: Simplified to handle the Emacs Lisp part only. Deleted -P, -p and -x, as well as compile_python. * Makefile: Adjusted. Removed pythondir and pymacsdir. * pymacs.el (pymacs-load-path): Merely preset to nil. * setup: Changes for easing installation on Win32. Reported by Syver Enstad. * Pymacs/pymacs.py (print_lisp): Produce Emacs strings more explicitly, avoiding hexadecimal sequences generated by Python 2.2. Those hexadecimal sequences confused Emacs when immediately followed by more hexadecimal looking characters. 2002-01-30 François Pinard * pymacs.el (pymacs-load-path): Initialise with pymacsdir. * pymacs-services: Do not handle a patched pymacsdir anymore. * setup (complete_install): Set pymacsdir for Lisp, not Python. Do not accept a -b option anymore, do not install pymacs-services, as this is now to be done through setup.py. * Makefile (install): Do not use -b while calling setup. 2002-01-29 François Pinard * : Release 0.16. * Pymacs/pymacs.py: New file, previously top-level. * pymacs.py: Now a mere bootstrap for Pymacs/pymacs.py. * Pymacs/__init__.py: Define lisp and Let. * Makefile (pythondir): Documentation amended. * setup: Distinguish between empty arguments, which ask for autoconfiguration, and None arguments, which inhibit it. * pymacs-services: Import pymacs from Pymacs. * Pymacs/version.py: New file. Rename pymacs to Pymacs. * setup, setup.py, Pymacs/pymacs.py (main): Use it. * setup: Substitute None for pymacsdir instead of the empty string. * pymacs-services: Adjusted. * Pymacs/pymacs.py (Let): Have all push_* methods to return self. 2002-01-20 François Pinard * pymacs.el, pymacs.el: Replace LISP by Lisp in comments. Reported by Paul Foley. 2002-01-10 François Pinard * : Release 0.15. * pymacs.el (pymacs-start-services): Properly diagnose a timeout, using the timeout parameter value instead of a fixed string. 2002-01-07 François Pinard * : Release 0.14. * pymacs.py: Set various __repr__() to yield Python code, containing the corresponding expanded LISP expression. Set various __str__() to yield mutable LISP code. * pymacs.py (Let): Point markers to nowhere once done with them. 2002-01-06 François Pinard * : Release 0.13. * pymacs.el (pymacs-load): Imply prefix correctly when the module is part of a package, that is, when its name has at least one dot. * pymacs.py (pymacs_load_helper): Idem. * pymacs.py (Protocol): New name for Server. * pymacs.py (pymacs_load_helper): Implement pymacs_load_hook. * MANIFEST.in, setup.py, Pymacs/__init__.py: New files. * Makefile: Adjusted and simplified. 2002-01-03 François Pinard * pymacs.py (pymacs_load_helper): Handle module within package. Reported by Syver Enstad. 2001-12-18 François Pinard * pymacs.bat: New file. 2001-11-29 François Pinard * : Release 0.12. * pymacs.el (pymacs-timeout-at-start, pymacs-timeout-at-reply, pymacs-timeout-at-line): New variables. Use them. 2001-10-17 François Pinard * pymacs.py (pymacs_load_helper): Check the function attribute before the interactions dictionary, for people having Python 2.x. Reported by Carel Fellinger. * pymacs.el, pymacs.py, pymacs-services: Add the usual GPL notices. Reported by Richard Stallman. 2001-10-16 François Pinard * : Release 0.11. * pymacs.el (pymacs-defuns): Accept interaction specifications. (pymacs-defun): Process an interaction specification. (pymacs-python-reference): Adjust for interactive functions. * pymacs.py (pymacs_load_helper): Transmit interaction specifications. Reported by Christian Tanzer and Stefan Reichör. 2001-10-15 François Pinard * pymacs.py (pymacs_load_helper): Accept dashed module names. Reported by Stefan Reichör. * pymacs.el (pymacs-python-reference): Rewrite, as it was broken. (documentation): Say it is a Python function, even if no docstring. Reported by Stefan Reichör. 2001-10-12 François Pinard * : Release 0.10. * pymacs.el (pymacs-print-for-eval): Handle multi-line strings. Reported by Dave Sellars. * pymacs.el (pymacs-print-for-eval): Remove string text properties. Reported by Eli Zaretskii. 2001-10-06 François Pinard * pymacs.py (Let.__nonzero__): New. 2001-09-28 François Pinard * : Release 0.9. 2001-09-26 François Pinard * pymacs.py (Let.push): Save the value of the symbol, not the symbol itself. 2001-09-25 François Pinard * : Release 0.8. * pymacs.py (Let): New class. * pymacs.el: New variable pymacs-use-hash-tables, set to t when hash tables are available, or nil otherwise. Use it. This is so older Emacs would work. Reported by Dirk Vleugels. 2001-09-21 François Pinard * pymacs.el (pymacs-defun): Ensure the function is registered at definition, not at call time. Otherwise, it would never be garbage-collected if it is never called. 2001-09-20 François Pinard * : Release 0.7. * pymacs.el (pymacs-print-for-apply): Also accept Python objects for a function, instead of requiring strings. (pymacs-defun): Use a Python object, not an explicit string reference. (pymacs-python): Merge pymacs-save-index. (pymacs-save-index): Deleted. 2001-09-18 François Pinard * pymacs.el (pymacs-load): Accept a noerror argument. 2001-09-17 François Pinard * setup: New script. * Makefile: Use it. * pymacs.py (Symbol.set): Make things simpler when value is None. * pymacs.el (pymacs-print-for-eval): Use Python lists to represent LISP proper lists and Python tuples to represent LISP vectors, instead of the other way around. * pymacs.py (pymacs_load_helper, print_lisp): Similar changes. Reported by John Wiegley. 2001-09-16 François Pinard * : Release 0.6. * pymacs.el (pymacs-start-services, pymacs-print-for-eval, pymacs-round-trip): Protect match data. 2001-09-15 François Pinard * pymacs.el (documentation): Completed. Now into service. (pymacs-documentation): Deleted. (pymacs-python-reference): New. * pymacs.el (pymacs-print-for-eval): Use car-safe. 2001-09-14 François Pinard * pymacs.el (pymacs-print-for-eval): replace-regexp-in-string does not exist in older Emacs versions, so use paraphrases. Reported by Carey Evans. * pymacs.el (pymacs-start-services): Set pymacs-transit-buffer permanently only at end of the function, in case anything fails. Reported by Carey Evans. 2001-09-13 François Pinard * : Release 0.5. * pymacs.el (documentation, pymacs-documentation): New, experimental. * pymacs.py (doc_string): New. (pymacs_load_helper): The result should evaluate to the module. 2001-09-12 François Pinard * pymacs.py (pymacs_load_helper): Use reload instead of __import__ whenever the module was already loaded. * pymacs.py (pymacs_load_helper): Return t when there is nothing to define, instead of returning a noisy pymacs-defuns noop. * Makefile (dist): Update a version-less symbolic link. * pymacs.el (pymacs-python, pymacs-defun): New functions. (pymacs-defuns): Use pymacs-defun. * pymacs.py (print_lisp): Use the above. * pymacs.py (Server): Free all accumulated LISP indices, while replying for another reason. This should decrease overhead. (Lisp.__del__): Delay freeing LISP, do not free one index at a time. * pymacs.el (pymacs-free-lisp): Free many indices at once. * pymacs.el (pymacs-start-services, pymacs-round-trip): Recognise reply even when not at beginning of line. The Python module may print incomplete lines, unrelated to the communication protocol. 2001-09-11 François Pinard * : Release 0.4. * pymacs.py (zombie): New, so to get a clear diagnostic. (zombie_python): Link objects to the above function. * pymacs.el (pymacs-terminate-services): Ask for confirmation if any object in LISP space is still in use on the Python side. * pymacs-test.el (try-lisp): Do not terminate the helper. * pymacs.py (Buffer): New class, yet empty for now. * pymacs.el (pymacs-print-for-eval): Use it. Reported by Brian McErlean. * pymacs.py (Table): New class. * pymacs.el (pymacs-print-for-eval): Use it. Reported by Brian McErlean. * pymacs.py (List, Vector): New classes, split out of Lisp class. * pymacs.el (pymacs-print-for-eval): Use them. (pymacs-lisp-length, pymacs-lisp-ref, pymacs-list-set): Deleted. Reported by Brian McErlean. * pymacs.py (Server.loop): Allow keyboard interrupts through. * pymacs.el: Use Lisp instead of Handle. Rename pymacs-handle-length to pymacs-lisp-length, pymacs-handle-ref to pymacs-lisp-ref, pymacs-handle-set o pymacs-lisp-set, pymacs-allocate-handle to pymacs-allocate-lisp and pymacs-free-handle to pymacs-free-lisp. * pymacs.py: Rename Lisp to Lisp_Interface, and Handle to Lisp. Adjust for other renamings above. * pymacs.el: Rename pymacs-id to pymacs-python. Ajust for below. * pymacs.py: Rename handles to python, free_handles to free_python, zombie_handles to zombie_python and allocate_handle to allocate_python. * pymacs.el (pymacs-proper-list-p): New function. Use it everywhere instead of listp, which is not what I thought it was! * pymacs.el (pymacs-serve-until-reply): In case of LISP error, transmit a list of one argument, instead of the argument itself, to print-for-apply. This was preventing proper diagnostic. Correct a similar error for when expansion is requested. * pymacs.el (pymacs-print-for-eval): Do not transmit a symbol by its name, when it comes from another oblist than the main one. * pymacs.py (print_lisp): Transmit pymacs-id as a dotted pair. * pymacs.el (pymacs-print-for-eval): Adjusted. * pymacs.el (pymacs-print-for-eval): Use lisp[], not sym[]. Avoid double escaping of the transmitted string in this case. Reported by Brian McErlean. 2001-09-10 François Pinard * : Release 0.3. * pymacs.py (Server.send): Ensure an end of line after reply. * pymacs.el (pymacs-round-trip): Do not add one after Python replies. * pymacs.el (pymacs-round-trip): Check for vanishing helper process. (pymacs-serve-until-reply): Get text without catching errors, than eval. Else, protocol errors get reported back to Python. * pymacs.py (Server.ProtocolError): New. Better than AssertError. If it occurs, get out of program, do not keep returning errors. Reported by Carey Evans. * pymacs.el (pymacs-round-trip): If point coincides with marker, just keep it that way as the buffer grows. * pymacs.el (pymacs-start-services): If the hash table already exists, inform the Python side of IDs that it should not reuse. Otherwise, old lambdas may randomly refer to new Python objects. (pymacs-terminate-services): Remember Python IDs, do not reset them. * pymacs.py (zombie_handles): New. * Makefile: Transmit $(pymacsdir) to pymacs-services. * pymacs-services: Handle it. * pymacs.py (print_lisp): Process an empty tuple properly. Reported by Carey Evans. * pymacs.el (pymacs-start-services): With run-at-time, use `20 20' instead of `t 20', so XEmacs is happy. Reported by Carey Evans. * pymacs.el (pymacs-start-services, pymacs-terminate-services): Use `post-gc-hook' if available, instead of using a timer. Reported by Gerd Möllman. * pymacs.py (Symbol.value, Symbol.copy): Add argument self. (print_lisp): Quote symbols if quoted=1. 2001-09-09 François Pinard * pymacs.el (pymacs-defuns): New function. * pymacs.py: Use it. This should allow faster imports. * Makefile, pymacs.el, pymacs.py: Use `(pymacs-version VERSION)', not `(started)'. Check for version discrepancies. * Makefile: A bit more parameterization. * : Release 0.2, including ideas and suggestions from others. Reported by Brian McErlean, Carel Fellinger, Cedric Adjih, Marcin Qrczak Kowalczyk, Paul Winkler and Steffen Ries. bicyclerepair-0.9/ide-integration/Pymacs-0.20/ChangeLog-rebox0100644000076400007640000001164107606002142022112 0ustar pldpld2002-01-29 François Pinard * Pymacs/rebox.py: Use an interactions map instead of the interaction attribute, so it works with earlier Python versions. * Pymacs/rebox.py: Import lisp and Let from Pymacs. 2002-01-13 François Pinard * Pymacs/rebox.py (Emacs_Rebox.emacs_engine): Expand flag value, when it is neither the - symbol nor a number. 2002-01-08 François Pinard * Pymacs/rebox.py (Template.build): Subtract margin from width just before actually rebuilding the box. Reported by Paul Provost. 2002-01-07 François Pinard * Pymacs/rebox.py (main): Implement -v option. * Pymacs/rebox.py (pymacs_load_hook): Declare set_default_style. * Pymacs/rebox.py (Emacs_Rebox.clean_undo_after): Debugged. * Pymacs/rebox.py (Template): New class. Reorgnise all code. * Pymacs/rebox.py (engine): Moved out of Rebox class. * Pymacs/rebox.py (Rebox, Batch_Rebox): Deleted, as they got empty. * Pymacs/rebox.py (Emacs_Rebox.clean_undo_after): Rewrite in LISP. 2002-01-06 François Pinard * rebox: New file. 2002-01-03 François Pinard * Pymacs/rebox.py: New file, translated from Libit/rebox.el. 2000-09-28 François Pinard * rebox.el: Replace statistical heuristics for box style recognition by more precise checks and explicit priorities between styles. To do so, add weights to rebox-templates, replace rebox-building-data by rebox-style-data holding regexps, delete rebox-recognition-data. * rebox.el (rebox-regexp-ruler): New function. (rebox-regexp-quote): Add matching for following white space. Don't force two characters on each middle line, nor in blank rulers. Reported by Paul Provost. 2000-04-28 François Pinard * rebox.el (rebox-guess-style): When two styles have equal weight, retain the highest numbered, as it probably is the richest. Otherwise, simple C++ comments end up with a single slash. Reported by Akim Demaille. 2000-04-19 François Pinard * rebox.el: Reorganize from bottom-up into top-down. (taarna-mode): Deleted. 2000-04-18 François Pinard * rebox.el (rebox-show-style, rebox-help-string-for-language, rebox-help-string-for-quality, rebox-help-string-for-type): Deleted. (rebox-rstrip, rebox-regexp-quote, rebox-unbuild): New functions. (rebox-build): New name for rebox-reconstruct. 2000-04-15 François Pinard * rebox.el (rebox-guess-style): New function. (rebox-engine): Use it. Simplified by using template information. 2000-04-14 François Pinard * rebox.el (rebox-templates): New variable. (rebox-register-template): New function. (rebox-reconstruct): Much simplified by using the above. 2000-04-12 François Pinard * rebox.el: Rework the initial documentation block. (rebox-reconstruct): Guarantee newline at end for style 241. Reported by Marc Feeley and Paul Provost. 2000-02-22 François Pinard * rebox.el: Little speed cleanup. Avoid looking-at when easy. 2000-02-10 François Pinard * rebox.el: Adjust comment to suggest add-hook instead of setq. Reported by Akim Demaille. 2000-01-30 François Pinard * rebox.el: Prefer when, unless and cond over if and progn. Combine successive setq. * rebox.el (rebox-engine): Recognise quality for shell boxes. Reported by Akim Demaille. 1999-06-30 François Pinard * rebox.el: Add GPL comment. Reported by Paul Eggert. 1998-03-28 François Pinard * rebox.el (rebox-reconstruct): Refill a closing */ with the rest. Do not add spaces to a line which is otherwise empty. 1997-12-01 François Pinard * rebox.el (rebox-engine): Simplify two regexps, for XEmacs. Reported by Ulrich Drepper. 1997-02-17 François Pinard * rebox.el (rebox-reconstruct): Ensure indent-tabs-mode is nil. 1997-02-14 François Pinard * rebox.el: Corrected a bug demonstrated as the beginning line of a paragraph spuriously jumping right spuriously. The full match of the beginning of comment was replaced by spaces on the initial line, while only \1 needed replacement. This shortened this line, causing later nasty effects. 1996-07-10 François Pinard * rebox.el: Recognise style 241, so margin does not get doubled. Reported by Marc Feeley. 1996-07-09 François Pinard * rebox.el: Use symbolic constants for language, quality and type. 1996-06-09 François Pinard * rebox.el (rebox-find-and-narrow): Take care of a missing end of line after a comment being at end of buffer. Reported by Ulrich Drepper. bicyclerepair-0.9/ide-integration/Pymacs-0.20/Makefile0100644000076400007640000000167507606002142020671 0ustar pldpld# Interface between Emacs LISP and Python - Makefile. # Copyright İ 2001, 2002 Progiciels Bourbeau-Pinard inc. # François Pinard , 2001. # The `README' file provides a few good hints about installation. ### Start of customisation. # # Somewhere on your Emacs LISP load-path. lispdir = # ### End of customisation. PYSETUP = python setup.py DISTRIBUTION := $(shell ./setup -V) all: $(PYSETUP) build install: all @./setup -l '$(lispdir)' $(PYSETUP) install tags: (find bin -type f; find -name '*.py') | grep -v '~$$' | etags - dist: $(PYSETUP) sdist mv dist/$(DISTRIBUTION).tar.gz . rmdir dist ls -l *.gz publish: dist traiter README.html > index.html chmod 644 index.html $(DISTRIBUTION).tar.gz scp -p index.html $(DISTRIBUTION).tar.gz bor:w/pymacs/ rm index.html $(DISTRIBUTION).tar.gz ssh bor rm -vf w/pymacs/Pymacs.tar.gz ssh bor ln -vs $(DISTRIBUTION).tar.gz w/pymacs/Pymacs.tar.gz ssh bor ls -Llt w/pymacs bicyclerepair-0.9/ide-integration/Pymacs-0.20/PKG-INFO0100644000076400007640000000041007606002142020310 0ustar pldpldMetadata-Version: 1.0 Name: Pymacs Version: 0.20 Summary: Interface between Emacs LISP and Python. Home-page: http://www.iro.umontreal.ca/~pinard Author: François Pinard Author-email: pinard@iro.umontreal.ca License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN bicyclerepair-0.9/ide-integration/Pymacs-0.20/README0100644000076400007640000015127107606002142020107 0ustar pldpld* README for `Pymacs' allout -*- outline -*- `http://www.iro.umontreal.ca/~pinard/pymacs/' contains a copy of this `README' file in HTML form. The canonical Pymacs distribution is available as `http://www.iro.umontreal.ca/~pinard/pymacs/Pymacs.tar.gz'. Report problems and suggestions to `mailto:pinard@iro.umontreal.ca'. .. Presentation. . : What is Pymacs? Pymacs is a powerful tool which, once started from Emacs, allows both-way communication between Emacs Lisp and Python. Yet, Pymacs aims Python as an extension language for Emacs rather than the other way around; this assymetry is reflected in some design choices. Within Emacs Lisp code, one may load and use Python modules. Python functions may themselves use Emacs services, and handle Emacs Lisp objects kept in Emacs Lisp space. The goals are to write "naturally" in both languages, debug with ease, fall back gracefully on errors, and allow full cross-recursivity. It is very easy to install Pymacs, as neither Emacs nor Python need to be compiled nor relinked. Emacs merely starts Python as a subprocess, and Pymacs implements a communication protocol between both processes. . : Warning to Pymacs users. I expect average Pymacs users to have a deeper knowledge of Python than Emacs Lisp. Some examples at the end of this file are meant for Python users having a limited experience with the Emacs API. Currently, there are only contains two examples, one is too small, the other is too big :-). As there is no dedicated mailing list nor discussion group for Pymacs, let's use `python-list@python.org' for asking questions or discussing Pymacs related matters. This is beta status software: specifications are slightly frozen, yet changes may still happen that would require small adaptations in your code. Report problems to François Pinard at `pinard@iro.umontreal.ca'. For discussing specifications or making suggestions, please also copy the `python-list@python.org' mailing list, to help brain-storming! :-) . : History and references. I once starved for a Python-extensible editor, and pondered the idea of dropping Emacs for other avenues, but found nothing much convincing. Moreover, looking at all LISP extensions I wrote for myself, and considering all those superb tools written by others and that became part of my computer life, it would have been a huge undertaking for me to reprogram these all in Python. So, when I began to see that something like Pymacs was possible, I felt strongly motivated! :-) Pymacs revisits previous Cedric Adjih's works about running Python as a process separate from Emacs. See `http://www.crepuscule.com/pyemacs/', or write Cedric at `adjih-pam@crepuscule.com'. Cedric presented `pyemacs' to me as a proof of concept. As I simplified that concept a bit, I dropped the `e' in `pyemacs' :-). Cedric also told me that there exist some older patches for linking Python right into XEmacs. Brian McErlean independently and simultaneously wrote a tool similar to this one, we decided to join our projects. Amusing coincidence, he even chose `pymacs' as a name. Brian paid good attention to complex details that escaped my courage, so his help and collaboration have been beneficial. You may reach Brian at `brianmce@crosswinds.net'. One other reference of interest is Doug Bagley shoot out project, which compares the relative speed of many popular languages. See `http://www.bagley.org/~doug/shootout/' for more information. .. Installation. . : Install the Pymacs proper. Currently, there are two installation scripts, and both should be run. If you prefer, you may use `make install lispdir=LISPDIR', where LISPDIR is some directory along the list kept in your Emacs `load-path'. The first installation script installs the Python package, including the Pymacs examples, using the Python standard Distutils tool. Merely `cd' into the Pymacs distribution, then execute `python setup.py install'. To get an option reminder, do `python setup.py install --help'. Check the Distutils documentation if you need more information about this. The second installation script installs the Emacs Lisp part only. (It used to do everything, but is now doomed to disappear completely.) Merely `cd' into the Pymacs distribution, then run `python setup -ie'. This will invite you to interactively confirm the Lisp installation directory. Without `-ie', the Lisp part of Pymacs will be installed in some automatically guessed place. Use `-n' to known about the guess without proceeding to the actual installation. `./setup -E xemacs ...' may be useful to XEmacs lovers. See `./setup -H' for all options. About Win32 systems, Syver Enstad says: "For Pymacs to operate correctly, one should create a batch file with `pymacs-services.bat' as a name, which runs the `pymacs-services' script. The `.bat' file could be placed along with `pymacs-services', wherever that maybe.". To check that `pymacs.el' is properly installed, start Emacs and give it the command `M-x load-library RET pymacs': you should not receive any error. To check that `pymacs.py' is properly installed, start an interactive Python session and type `from Pymacs import lisp': you should not receive any error. To check that `pymacs-services' is properly installed, type `pymacs-services ' expected.". Currently, there is only one installed Pymacs example, which comes in two parts: a batch script `rebox' and a `Pymacs.rebox' module. To check that both are properly installed, type `rebox '). To bind the F1 key to the `helper' function in some `module': lisp.global_set_key((lisp.f1,), lisp.module_helper) (item,) is a Python tuple yielding an Emacs Lisp vector. `lisp.f1' translates to the Emacs Lisp symbol `f1'. So, Python `(lisp.f1,)' is Emacs Lisp `[f1]'. Keys like `[M-f2]' might require some more ingenuity, one may write either (lisp['M-f2'],) or (lisp.M_f2,) on the Python side. .. Debugging. . : The `*Pymacs*' buffer. Emacs and Python are two separate processes (well, each may use more than one process). Pymacs implements a simple communication protocol between both, and does whatever needed so the programmers do not have to worry about details. The main debugging tool is the communication buffer between Emacs and Python, which is named `*Pymacs*'. To make good use of it, first set `pymacs-trace-transit' to `t', so all exchanges are accumulated in that buffer. As it is sometimes helpful to understand the communication protocol, it is briefly explained here, using an artificially complex example to do so. Consider: ----------------------------------------------------------------------> (pymacs-eval "lisp('(pymacs-eval \"`2L**111`\")')") "2596148429267413814265248164610048L" ----------------------------------------------------------------------< Here, Emacs asks Python to ask Emacs to ask Python for a simple bignum computation. Note that Emacs does not natively know how to handle big integers, nor has an internal representation for them. This is why I use backticks, so Python returns a string representation of the result, instead of the result itself. Here is a trace for this example. The `<' character flags a message going from Python to Emacs and is followed by an expression written in Emacs Lisp. The '>' character flags a message going from Emacs to Python and is followed by a expression written in Python. The number gives the length of the message. ----------------------------------------------------------------------> <22 (pymacs-version "0.3") >49 eval("lisp('(pymacs-eval \"`2L**111`\")')") <25 (pymacs-eval "`2L**111`") >18 eval("`2L**111`") <47 (pymacs-reply "2596148429267413814265248164610048L") >45 reply("2596148429267413814265248164610048L") <47 (pymacs-reply "2596148429267413814265248164610048L") ----------------------------------------------------------------------< Python evaluation is done in the context of the `Pymacs.pymacs' module, so for example a mere `reply' really means `Pymacs.pymacs.reply'. On the Emacs Lisp side, there is no concept of module namespaces, so we use the `pymacs-' prefix as an attempt to stay clean. Users should ideally refrain from naming their Emacs Lisp objects with a `pymacs-' prefix. `reply' and `pymacs-reply' are special functions meant to indicate that an expected result is finally transmitted. `error' and `pymacs-error' are special functions that introduce a string which explains an exception which recently occurred. `pymacs-expand' is a special function implementing the `copy()' methods of Emacs Lisp handles or symbols. In all other cases, the expression is a request for the other side, that request stacks until a corresponding reply is received. Part of the protocol manages memory, and this management generates some extra-noise in the `*Pymacs*' buffer. Whenever Emacs passes a structure to Python, an extra pointer is generated on the Emacs side to inhibit garbage collection by Emacs. Python garbage collector detects when the received structure is no longer needed on the Python side, at which time the next communication will tell Emacs to remove the extra pointer. It works symmetrically as well, that is, whenever Python passes a structure to Emacs, an extra Python reference is generated to inhibit garbage collection on the Python side. Emacs garbage collector detects when the received structure is no longer needed on the Emacs side, after which Python will be told to remove the extra reference. For efficiency, those allocation-related messages are delayed, merged and batched together within the next communication having another purpose. . : Emacs usual debugging. If cross-calls between Emacs Lisp and Python nest deeply, an error will raise successive exceptions alternatively on both sides as requests unstack, and the diagnostic gets transmitted back and forth, slightly growing as we go. So, errors will eventually be reported by Emacs. I made no kind of effort to transmit the Emacs Lisp backtrace on the Python side, as I do not see a purpose for it: all debugging is done within Emacs windows anyway. On recent Emacses, the Python backtrace gets displayed in the mini-buffer, and the Emacs Lisp backtrace is simultaneously shown in the `*Backtrace*' window. One useful thing is to allow to mini-buffer to grow big, so it has more chance to fully contain the Python backtrace, the last lines of which are often especially useful. Here, I use: (setq resize-mini-windows t max-mini-window-height .85) in my `.emacs' file, so the mini-buffer may use 85% of the screen, and quickly shrinks when fewer lines are needed. The mini-buffer contents disappear at the next keystroke, but you can recover the Python backtrace by looking at the end of the `*Messages*' buffer. In which case the `ffap' package in Emacs may be yet another friend! From the `*Messages*' buffer, once `ffap' activated, merely put the cursor on the file name of a Python module from the backtrace, and `C-x C-f RET' will quickly open that source for you. . : Auto-reloading on save. I found useful to automatically `pymacs-load' some Python files whenever they get saved from Emacs. Here is how I do it. The code below assumes that Python files meant for Pymacs are kept in `~/share/emacs/python'. (defun fp-maybe-pymacs-reload () (let ((pymacsdir (expand-file-name "~/share/emacs/python/"))) (when (and (string-equal (file-name-directory buffer-file-name) pymacsdir) (string-match "\\.py\\'" buffer-file-name)) (pymacs-load (substring buffer-file-name 0 -3))))) (add-hook 'after-save-hook 'fp-maybe-pymacs-reload) .. Exemples. . : Paul Winkler's. . , The problem. Let's say I have a a module, call it `manglers.py', containing this simple python function: def break_on_whitespace(some_string): words = some_string.split() return '\n'.join(words) The goal is telling Emacs about this function so that I can call it on a region of text and replace the region with the result of the call. And bind this action to a key, of course, let's say `[f7]'. The Emacs buffer ought to be handled in some way. If this is not on the Emacs Lisp side, it has to be on the Python side, but we cannot escape handling the buffer. So, there is an equilibrium in the work to do for the user, that could be displaced towards Emacs Lisp or towards Python. . , Python side. Here is a first draft for the Python side of the problem: from Pymacs import lisp def break_on_whitespace(): start = lisp.point() end = lisp.mark(lisp.t) if start > end: start, end = end, start text = lisp.buffer_substring(start, end) words = text.split() replacement = '\n'.join(words) lisp.delete_region(start, end) lisp.insert(replacement) interactions = {break_on_whitespace: ''} For various stylistic reasons, this could be rewritten into: from Pymacs import lisp interactions = {} def break_on_whitespace(): start, end = lisp.point(), lisp.mark(lisp.t) words = lisp.buffer_substring(start, end).split() lisp.delete_region(start, end) lisp.insert('\n'.join(words)) interactions[break_on_whitespace] = '' The above relies, in particular, on the fact that for those Emacs Lisp functions used here, `start' and `end' may be given in any order. . , Emacs side. On the Emacs side, one would do: (pymacs-load "manglers") (global-set-key [f7] 'manglers-break-on-whitespace) . : The `rebox' tool. . , The problem. For comments held within boxes, it is painful to fill paragraphs, while stretching or shrinking the surrounding box "by hand", as needed. This piece of Python code eases my life on this. It may be used interactively from within Emacs through the Pymacs interface, or in batch as a script which filters a single region to be reformatted. In batch, the reboxing is driven by command options and arguments and expects a complete, self-contained boxed comment from a file. Emacs function `rebox-region' also presumes that the region encloses a single boxed comment. Emacs `rebox-comment' is different, as it has to chase itself the extent of the surrounding boxed comment. . , Python side. The Python code is too big to be inserted in this documentation: see file `Pymacs/rebox.py' in the Pymacs distribution. You will observe in the code that Pymacs specific features are used exclusively from within the `pymacs_load_hook' function and the `Emacs_Rebox' class. In batch mode, `Pymacs' is not even imported. Here, we mean to discuss some of the design choices in the context of Pymacs. In batch mode, as well as with `rebox-region', the text to handle is turned over to Python, and fully processed in Python, with practically no Pymacs interaction while the work gets done. On the other hand, `rebox-comment' is rather Pymacs intensive: the comment boundaries are chased right from the Emacs buffer, as directed by the function `Emacs_Rebox.find_comment'. Once the boundaries are found, the remainder of the work is essentially done on the Python side. Once the boxed comment has been reformatted in Python, the old comment is removed in a single delete operation, the new comment is inserted in a second operation, this occurs in `Emacs_Rebox.process_emacs_region'. But by doing so, if point was within the boxed comment before the reformatting, its precise position is lost. To well preserve point, Python might have driven all reformatting details directly in the Emacs buffer. We really preferred doing it all on the Python side: as we gain legibility by expressing the algorithms in pure Python, the same Python code may be used in batch or interactively, and we avoid the slowdown that would result from heavy use of Emacs services. To avoid completely loosing point, I kludged a `Marker' class, which goal is to estimate the new value of point from the old. Reformatting may change the amount of white space, and either delete or insert an arbitrary number characters meant to draw the box. The idea is to initially count the number of characters between the beginning of the region and point, while ignoring any problematic character. Once the comment has been reboxed, point is advanced from the beginning of the region until we get the same count of characters, skipping all problematic characters. This `Marker' class works fully on the Python side, it does not involve Pymacs at all, but it does solve a problem that resulted from my choice of keeping the data on the Python side instead of handling it directly in the Emacs buffer. We want a comment reformatting to appear as a single operation, in the context of Emacs Undo. The method `Emacs_Rebox.clean_undo_after' handles the general case for this. Not that we do so much in practice: a reformatting implies one `delete-region' and one `insert', and maybe some other little adjustements at `Emacs_Rebox.find_comment' time. Even if this method scans and mofifies an Emacs Lisp list directly in the Emacs memory, the code doing this stays neat and legible. However, I found out that the undo list may grow quickly when the Emacs buffer use markers, with the consequence of making this routine so Pymacs intensive that most of the CPU is spent there. I rewrote that routine in Emacs Lisp so it executes in a single Pymacs interaction. Function `Emacs_Rebox.remainder_of_line' could have been written in Python, but it was probably not worth going away from this one-liner in Emacs Lisp. Also, given this routine is often called by `find_comment', a few Pymacs protocol interactions are spared this way. This function is useful when there is a need to apply a regexp already compiled on the Python side, it is probably better fetching the line from Emacs and do the pattern match on the Python side, than transmitting the source of the regexp to Emacs for it to compile and apply it. For refilling, I could have either used the refill algorithm built within in Emacs, programmed a new one in Python, or relied on Ross Paterson's `fmt', distributed by GNU and available on most Linuxes. In fact, `refill_lines' prefers the latter. My own Emacs setup is such that the built-in refill algorithm is _already_ overridden by GNU `fmt', and it really does a much better job. Experience taught me that calling an external program is fast enough to be very bearable, even interactively. If Python called Emacs to do the refilling, Emacs would itself call GNU `fmt' in my case, I preferred that Python calls GNU `fmt' directly. I could have reprogrammed GNU `fmt' in Python. Despite interesting, this is an uneasy project: `fmt' implements the Knuth refilling algorithm, which depends on dynamic programming techniques; Ross fine tuned them, and took care of many details. If GNU `fmt' fails, for not being available, say, `refill_lines' falls back on a dumb refilling algorithm, which is better than none. . , Emacs side. The Emacs recipe appears under the `Emacs usage' section, near the beginning of `Pymacs/rebox.py', so I do not repeat it here. .. François Pinard, pinard@iro.umontreal.ca bicyclerepair-0.9/ide-integration/Pymacs-0.20/README.html0100644000076400007640000000035507606002142021046 0ustar pldpld %(title)s %(text)s bicyclerepair-0.9/ide-integration/Pymacs-0.20/THANKS0100644000076400007640000000157607606002142020144 0ustar pldpldPymacs has been written by François Pinard after an idea from Cedric Adjih, and much influenced by Brian McErlean. Here is the list of contributors. Brian McErlean b.mcerlean@kainos.com Carel Fellinger cfelling@iae.nl Carey Evans careye@spamcop.net Cedric Adjih adjih-pam@crepuscule.com http://www.crepuscule.com/pyemacs/ Christian Tanzer tanzer@swing.co.at Dave Sellars dsellars@windriver.com Dirk Vleugels dvl@2scale.net Eli Zaretskii eliz@is.elta.co.il François Pinard pinard@iro.umontreal.ca http://www.iro.umontreal.ca/~pinard Gerd Möllmann gerd@gnu.org John Wiegley johnw@gnu.org Marcin Qrczak Kowalczyk qrczak@knm.org.pl Paul Foley mycroft@actrix.gen.nz http://users.actrix.co.nz/mycroft/ Paul Winkler slinkp23@yahoo.com Richard Stallman rms@gnu.org Stefan Reichör xsteve@riic.at Steffen Ries steffen.ries@sympatico.ca Syver Enstad syver.enstad@asker.online.no bicyclerepair-0.9/ide-integration/Pymacs-0.20/THANKS-rebox0100644000076400007640000000046507606002142021255 0ustar pldpld`rebox' has been written by François Pinard. Here is the list of contributors. Also see `ChangeLog-rebox' for a more details. Akim Demaille demaille@inf.enst.fr Marc Feeley feeley@iro.umontreal.ca Paul Eggert eggert@twinsun.com Paul Provost provost@virtualprototypes.ca Ulrich Drepper drepper@gnu.org bicyclerepair-0.9/ide-integration/Pymacs-0.20/TODO0100644000076400007640000000110107606002142017701 0ustar pldpld* TODO for `Pymacs' allout -*- outline -*- .. Internal cleanup. . : `(pymacs-eval "dir()")' shows too many things. .. Type conversions. . : Convert LISP hash tables into Python dicts and vice-versa. .. Iterator protocol. . : Allow iterating over vectors. .. Rebox. . : Debug the Undo list cleanup! . : Try the fall back refiller. . : Unicode boxes (suggested by Bruno). . , U+231C..U+231F . , U+25xx .. Debugging facilities. . : *Pymacs* . , Indent. . , Interpret numbers. . , Highlight. . : Test suite. (Brian has one.) . : Python shell link to helper. bicyclerepair-0.9/ide-integration/Pymacs-0.20/pymacs-services0100755000076400007640000000202107606002142022256 0ustar pldpld#!/usr/bin/env python # Copyright İ 2001, 2002 Progiciels Bourbeau-Pinard inc. # François Pinard , 2001. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ """\ Interface between Emacs LISP and Python - Python process starter. This small bootstrap is so Pymacs modules can be kept in compiled form. """ import sys from Pymacs import pymacs apply(pymacs.main, sys.argv[1:]) bicyclerepair-0.9/ide-integration/Pymacs-0.20/pymacs-services.bat0100644000076400007640000000002407606002142023021 0ustar pldpld@python "%~dpn0" %* bicyclerepair-0.9/ide-integration/Pymacs-0.20/pymacs.el0100644000076400007640000006161507606002142021047 0ustar pldpld;;; Interface between Emacs Lisp and Python - Lisp part. ;;; Copyright İ 2001, 2002 Progiciels Bourbeau-Pinard inc. ;;; François Pinard , 2001. ;;; This program is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2, or (at your option) ;;; any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software Foundation, ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ ;;; See the Pymacs documentation (in `README') for more information. ;;; Published functions. (defvar pymacs-load-path nil "List of additional directories to search for Python modules. The directories listed will be searched first, in the order given.") (defvar pymacs-trace-transit nil "Keep the communication buffer growing, for debugging. When this variable is nil, the `*Pymacs*' communication buffer gets erased before each communication round-trip. Setting it to `t' guarantees that the full communication is saved, which is useful for debugging.") (defvar pymacs-forget-mutability nil "Transmit copies to Python instead of Lisp handles, as much as possible. When this variable is nil, most mutable objects are transmitted as handles. This variable is meant to be temporarily rebound to force copies.") (defvar pymacs-mutable-strings nil "Prefer transmitting Lisp strings to Python as handles. When this variable is nil, strings are transmitted as copies, and the Python side thus has no way for modifying the original Lisp strings. This variable is ignored whenever `forget-mutability' is set.") (defvar pymacs-timeout-at-start 30 "Maximum reasonable time, in seconds, for starting `pymacs-services'. A machine should be pretty loaded before one needs to increment this.") (defvar pymacs-timeout-at-reply 5 "Expected maximum time, in seconds, to get the first line of a reply. The status of `pymacs-services' is checked at every such timeout.") (defvar pymacs-timeout-at-line 2 "Expected maximum time, in seconds, to get another line of a reply. The status of `pymacs-services' is checked at every such timeout.") (defun pymacs-load (module &optional prefix noerror) "Import the Python module named MODULE into Emacs. Each function in the Python module is made available as an Emacs function. The Lisp name of each function is the concatenation of PREFIX with the Python name, in which underlines are replaced by dashes. If PREFIX is not given, it defaults to MODULE followed by a dash. If NOERROR is not nil, do not raise error when the module is not found." (interactive (let* ((module (read-string "Python module? ")) (default (concat (car (last (split-string module "\\."))) "-")) (prefix (read-string (format "Prefix? [%s] " default) nil nil default))) (list module prefix))) (message "Pymacs loading %s..." module) (let ((lisp-code (pymacs-call "pymacs_load_helper" module prefix))) (cond (lisp-code (let ((result (eval lisp-code))) (message "Pymacs loading %s...done" module) result)) (noerror (message "Pymacs loading %s...failed" module) nil) (t (error "Pymacs loading %s...failed" module))))) (defun pymacs-eval (text) "Compile TEXT as a Python expression, and return its value." (interactive "sPython expression? ") (let ((value (pymacs-call "eval" text))) (when (interactive-p) (message "%S" value)) value)) (defun pymacs-exec (text) "Compile and execute TEXT as a sequence of Python statements. This functionality is experimental, and does not appear to be useful." (interactive "sPython statements? ") (let ((value (pymacs-serve-until-reply `(progn (princ "exec ") (prin1 ,text))))) (when (interactive-p) (message "%S" value)) value)) (defun pymacs-call (function &rest arguments) "Return the result of calling a Python function FUNCTION over ARGUMENTS. FUNCTION is a string denoting the Python function, ARGUMENTS are separate Lisp expressions, one per argument. Immutable Lisp constants are converted to Python equivalents, other structures are converted into Lisp handles." (pymacs-apply function arguments)) (defun pymacs-apply (function arguments) "Return the result of calling a Python function FUNCTION over ARGUMENTS. FUNCTION is a string denoting the Python function, ARGUMENTS is a list of Lisp expressions. Immutable Lisp constants are converted to Python equivalents, other structures are converted into Lisp handles." (pymacs-serve-until-reply `(pymacs-print-for-apply ',function ',arguments))) ;;; Integration details. ;; Python functions and modules should ideally look like Lisp functions and ;; modules. This page tries to increase the integration seamlessness. (defadvice documentation (around pymacs-ad-documentation activate) ;; Integration of doc-strings. (let* ((reference (pymacs-python-reference function)) (python-doc (when reference (pymacs-eval (format "doc_string(%s)" reference))))) (if (or reference python-doc) (setq ad-return-value (concat "It interfaces to a Python function.\n\n" (when python-doc (if raw python-doc (substitute-command-keys python-doc))))) ad-do-it))) (defun pymacs-python-reference (object) ;; Return the text reference of a Python object if possible, else nil. (when (functionp object) (let* ((definition (indirect-function object)) (body (and (pymacs-proper-list-p definition) (> (length definition) 2) (eq (car definition) 'lambda) (cddr definition)))) (when (and body (listp (car body)) (eq (caar body) 'interactive)) ;; Skip the interactive specification of a function. (setq body (cdr body))) (when (and body ;; Advised functions start with a string. (not (stringp (car body))) ;; Python trampolines hold exactly one expression. (= (length body) 1)) (let ((expression (car body))) ;; EXPRESSION might now hold something like: ;; (pymacs-apply (quote (pymacs-python . N)) ARGUMENT-LIST) (when (and (pymacs-proper-list-p expression) (= (length expression) 3) (eq (car expression) 'pymacs-apply) (eq (car (cadr expression)) 'quote)) (setq object (cadr (cadr expression)))))))) (when (eq (car-safe object) 'pymacs-python) (format "python[%d]" (cdr object)))) ;; The following functions are experimental -- they are not satisfactory yet. (defun pymacs-file-handler (operation &rest arguments) ;; Integration of load-file, autoload, etc. ;; Emacs might want the contents of some `MODULE.el' which does not exist, ;; while there is a `MODULE.py' or `MODULE.pyc' file in the same directory. ;; The goal is to generate a virtual contents for this `MODULE.el' file, as ;; a set of Lisp trampoline functions to the Python module functions. ;; Python modules can then be loaded or autoloaded as if they were Lisp. ;(message "** %S %S" operation arguments) (cond ((and (eq operation 'file-readable-p) (let ((module (substring (car arguments) 0 -3))) (or (pymacs-file-force operation arguments) (file-readable-p (concat module ".py")) (file-readable-p (concat module ".pyc")))))) ((and (eq operation 'load) (not (pymacs-file-force 'file-readable-p (list (car arguments)))) (file-readable-p (car arguments))) (let ((lisp-code (pymacs-call "pymacs_load_helper" (substring (car arguments) 0 -3) nil))) (unless lisp-code (error "Python import error")) (eval lisp-code))) ((and (eq operation 'insert-file-contents) (not (pymacs-file-force 'file-readable-p (list (car arguments)))) (file-readable-p (car arguments))) (let ((lisp-code (pymacs-call "pymacs_load_helper" (substring (car arguments) 0 -3) nil))) (unless lisp-code (error "Python import error")) (insert (prin1-to-string lisp-code)))) (t (pymacs-file-force operation arguments)))) (defun pymacs-file-force (operation arguments) ;; Bypass the file handler. (let ((inhibit-file-name-handlers (cons 'pymacs-file-handler (and (eq inhibit-file-name-operation operation) inhibit-file-name-handlers))) (inhibit-file-name-operation operation)) (apply operation arguments))) ;(add-to-list 'file-name-handler-alist '("\\.el\\'" . pymacs-file-handler)) ;;; Gargabe collection of Python IDs. ;; Python objects which have no Lisp representation are allocated on the ;; Python side as `python[INDEX]', and INDEX is transmitted to Emacs, with ;; the value to use on the Lisp side for it. Whenever Lisp does not need a ;; Python object anymore, it should be freed on the Python side. The ;; following variables and functions are meant to fill this duty. (defvar pymacs-use-hash-tables nil "Automatically set to t if hash tables are available.") (defvar pymacs-used-ids nil "List of received IDs, currently allocated on the Python side.") (defvar pymacs-weak-hash nil "Weak hash table, meant to find out which IDs are still needed.") (defvar pymacs-gc-wanted nil "Flag if it is time to clean up unused IDs on the Python side.") (defvar pymacs-gc-running nil "Flag telling that a Pymacs garbage collection is in progress.") (defvar pymacs-gc-timer nil "Timer to trigger Pymacs garbage collection at regular time intervals. The timer is used only if `post-gc-hook' is not available.") (defun pymacs-schedule-gc () (unless pymacs-gc-running (setq pymacs-gc-wanted t))) (defun pymacs-garbage-collect () ;; Clean up unused IDs on the Python side. (when pymacs-use-hash-tables (let ((pymacs-gc-running t) (pymacs-forget-mutability t) (ids pymacs-used-ids) used-ids unused-ids) (while ids (let ((id (car ids))) (setq ids (cdr ids)) (if (gethash id pymacs-weak-hash) (setq used-ids (cons id used-ids)) (setq unused-ids (cons id unused-ids))))) ;;(message "** pymacs-garbage-collect %d %d" ;; (length used-ids) (length unused-ids)) (setq pymacs-used-ids used-ids pymacs-gc-wanted nil) (when unused-ids (pymacs-apply "free_python" unused-ids))))) (defun pymacs-defuns (arguments) ;; Take one argument, a list holding a number of items divisible by 3. The ;; first argument is an INDEX, the second is a NAME, the third is the ;; INTERACTION specification, and so forth. Register Python INDEX with a ;; function with that NAME and INTERACTION on the Lisp side. The strange ;; calling convention is to minimise quoting at call time. (while (>= (length arguments) 3) (let ((index (nth 0 arguments)) (name (nth 1 arguments)) (interaction (nth 2 arguments))) (fset name (pymacs-defun index interaction)) (setq arguments (nthcdr 3 arguments))))) (defun pymacs-defun (index interaction) ;; Register INDEX on the Lisp side with a Python object that is a function, ;; and return a lambda form calling that function. If the INTERACTION ;; specification is nil, the function is not interactive. Otherwise, the ;; function is interactive, INTERACTION is then either a string, or the ;; index of an argument-less Python function returning the argument list. (let ((object (pymacs-python index))) (cond ((null interaction) `(lambda (&rest arguments) (pymacs-apply ',object arguments))) ((stringp interaction) `(lambda (&rest arguments) (interactive ,interaction) (pymacs-apply ',object arguments))) (t `(lambda (&rest arguments) (interactive (pymacs-call ',(pymacs-python interaction))) (pymacs-apply ',object arguments)))))) (defun pymacs-python (index) ;; Register on the Lisp side a Python object having INDEX, and return it. ;; The result is meant to be recognised specially by `print-for-eval', and ;; in the function position by `print-for-apply'. (let ((object (cons 'pymacs-python index))) (when pymacs-use-hash-tables (puthash index object pymacs-weak-hash) (setq pymacs-used-ids (cons index pymacs-used-ids))) object)) ;;; Generating Python code. ;; Many Lisp expressions cannot fully be represented in Python, at least ;; because the object is mutable on the Lisp side. Such objects are allocated ;; somewhere into a vector of handles, and the handle index is used for ;; communication instead of the expression itself. (defvar pymacs-lisp nil "Vector of handles to hold transmitted expressions.") (defvar pymacs-freed-list nil "List of unallocated indices in Lisp.") ;; When the Python CG is done with a Lisp object, a communication occurs so to ;; free the object on the Lisp side as well. (defun pymacs-allocate-lisp (expression) ;; This function allocates some handle for an EXPRESSION, and return its ;; index. (unless pymacs-freed-list (let* ((previous pymacs-lisp) (old-size (length previous)) (new-size (if (zerop old-size) 100 (+ old-size (/ old-size 2)))) (counter new-size)) (setq pymacs-lisp (make-vector new-size nil)) (while (> counter 0) (setq counter (1- counter)) (if (< counter old-size) (aset pymacs-lisp counter (aref previous counter)) (setq pymacs-freed-list (cons counter pymacs-freed-list)))))) (let ((index (car pymacs-freed-list))) (setq pymacs-freed-list (cdr pymacs-freed-list)) (aset pymacs-lisp index expression) index)) (defun pymacs-free-lisp (&rest indices) ;; This function is triggered from Python side for Lisp handles which lost ;; their last reference. These references should be cut on the Lisp side as ;; well, or else, the objects will never be garbage-collected. (while indices (let ((index (car indices))) (aset pymacs-lisp index nil) (setq pymacs-freed-list (cons index pymacs-freed-list) indices (cdr indices))))) (defun pymacs-print-for-apply-expanded (function arguments) ;; This function acts like `print-for-apply', but produce arguments which ;; are expanded copies whenever possible, instead of handles. Proper lists ;; are turned into Python lists, vectors are turned into Python tuples. (let ((pymacs-forget-mutability t)) (pymacs-print-for-apply function arguments))) (defun pymacs-print-for-apply (function arguments) ;; This function prints a Python expression calling FUNCTION, which is a ;; string naming a Python function, or a Python reference, over all its ;; ARGUMENTS, which are Lisp expressions. (let ((separator "") argument) (if (eq (car-safe function) 'pymacs-python) (princ (format "python[%d]" (cdr function))) (princ function)) (princ "(") (while arguments (setq argument (car arguments) arguments (cdr arguments)) (princ separator) (setq separator ", ") (pymacs-print-for-eval argument)) (princ ")"))) (defun pymacs-print-for-eval (expression) ;; This function prints a Python expression out of a Lisp EXPRESSION. (let (done) (cond ((not expression) (princ "None") (setq done t)) ((numberp expression) (princ expression) (setq done t)) ((stringp expression) (when (or pymacs-forget-mutability (not pymacs-mutable-strings)) (let ((text (copy-sequence expression))) (set-text-properties 0 (length text) nil text) (princ (mapconcat 'identity (split-string (prin1-to-string text) "\n") "\\n"))) (setq done t))) ((symbolp expression) (let ((name (symbol-name expression))) ;; The symbol can only be transmitted when in the main oblist. (when (eq expression (intern-soft name)) (cond ((save-match-data (string-match "^[A-Za-z][-A-Za-z0-9]*$" name)) (princ "lisp.") (princ (mapconcat 'identity (split-string name "-") "_"))) (t (princ "lisp[") (prin1 name) (princ "]"))) (setq done t)))) ((vectorp expression) (when pymacs-forget-mutability (let ((limit (length expression)) (counter 0)) (princ "(") (while (< counter limit) (unless (zerop counter) (princ ", ")) (pymacs-print-for-eval (aref expression counter))) (when (= limit 1) (princ ",")) (princ ")") (setq done t)))) ((eq (car-safe expression) 'pymacs-python) (princ "python[") (princ (cdr expression)) (princ "]")) ((pymacs-proper-list-p expression) (when pymacs-forget-mutability (princ "[") (pymacs-print-for-eval (car expression)) (while (setq expression (cdr expression)) (princ ", ") (pymacs-print-for-eval (car expression))) (princ "]") (setq done t)))) (unless done (let ((class (cond ((vectorp expression) "Vector") ((and pymacs-use-hash-tables (hash-table-p expression)) "Table") ((bufferp expression) "Buffer") ((pymacs-proper-list-p expression) "List") (t "Lisp")))) (princ class) (princ "(") (princ (pymacs-allocate-lisp expression)) (princ ")"))))) ;;; Communication protocol. (defvar pymacs-transit-buffer nil "Communication buffer between Emacs and Python.") ;; The principle behind the communication protocol is that it is easier to ;; generate than parse, and that each language already has its own parser. ;; So, the Emacs side generates Python text for the Python side to interpret, ;; while the Python side generates Lisp text for the Lisp side to interpret. ;; About nothing but expressions are transmitted, which are evaluated on ;; arrival. The pseudo `reply' function is meant to signal the final result ;; of a series of exchanges following a request, while the pseudo `error' ;; function is meant to explain why an exchange could not have been completed. ;; The protocol itself is rather simple, and contains human readable text ;; only. A message starts at the beginning of a line in the communication ;; buffer, either with `>' for the Lisp to Python direction, or `<' for the ;; Python to Lisp direction. This is followed by a decimal number giving the ;; length of the message text, a TAB character, and the message text itself. ;; Message direction alternates systematically between messages, it never ;; occurs that two successive messages are sent in the same direction. The ;; first message is received from the Python side, it is `(version VERSION)'. (defun pymacs-start-services () ;; This function gets called automatically, as needed. (let ((buffer (get-buffer-create "*Pymacs*"))) (with-current-buffer buffer (buffer-disable-undo) (save-match-data ;; Launch the Python helper. (let ((process (apply 'start-process "pymacs" buffer "pymacs-services" (mapcar 'expand-file-name pymacs-load-path)))) (process-kill-without-query process) ;; Receive the synchronising reply. (while (progn (goto-char (point-min)) (not (re-search-forward "<\\([0-9]+\\)\t" nil t))) (unless (accept-process-output process pymacs-timeout-at-start) (error "Pymacs helper did not start within %d seconds." pymacs-timeout-at-start))) (let ((marker (process-mark process)) (limit-position (+ (match-end 0) (string-to-number (match-string 1))))) (while (< (marker-position marker) limit-position) (unless (accept-process-output process pymacs-timeout-at-start) (error "Pymacs helper probably was interrupted at start."))))) ;; Check that synchronisation occurred. (goto-char (match-end 0)) (let ((reply (read (current-buffer)))) (if (and (pymacs-proper-list-p reply) (= (length reply) 2) (eq (car reply) 'pymacs-version)) (unless (string-equal (cadr reply) "@VERSION@") (error "Pymacs Lisp version is @VERSION@, Python is %s." (cadr reply))) (error "Pymacs got an invalid initial reply."))))) (setq pymacs-use-hash-tables (and (fboundp 'make-hash-table) (fboundp 'gethash) (fboundp 'puthash))) (when pymacs-use-hash-tables (if pymacs-weak-hash ;; A previous Pymacs session occurred in *this* Emacs session. Some ;; IDs may hang around, which do not correspond to anything on the ;; Python side. Python should not recycle such IDs for new objects. (when pymacs-used-ids (let ((pymacs-transit-buffer buffer) (pymacs-forget-mutability t)) (pymacs-apply "zombie_python" pymacs-used-ids))) (setq pymacs-weak-hash (make-hash-table :weakness 'value))) (if (boundp 'post-gc-hook) (add-hook 'post-gc-hook 'pymacs-schedule-gc) (setq pymacs-gc-timer (run-at-time 20 20 'pymacs-schedule-gc)))) ;; If nothing failed, only then declare the Pymacs has started! (setq pymacs-transit-buffer buffer))) (defun pymacs-terminate-services () ;; This function is mainly provided for documentation purposes. (interactive) (garbage-collect) (pymacs-garbage-collect) (when (or (not pymacs-used-ids) (yes-or-no-p "\ Killing the helper might create zombie objects. Kill? ")) (cond ((boundp 'post-gc-hook) (remove-hook 'post-gc-hook 'pymacs-schedule-gc)) ((timerp pymacs-gc-timer) (cancel-timer pymacs-gc-timer))) (when pymacs-transit-buffer (kill-buffer pymacs-transit-buffer)) (setq pymacs-gc-running nil pymacs-gc-timer nil pymacs-transit-buffer nil pymacs-lisp nil pymacs-freed-list nil))) (defun pymacs-serve-until-reply (inserter) ;; This function evals INSERTER to print a Python request. It sends it to ;; the Python helper, and serves all sub-requests coming from the ;; Python side, until either a reply or an error is finally received. (unless (and pymacs-transit-buffer (buffer-name pymacs-transit-buffer) (get-buffer-process pymacs-transit-buffer)) (pymacs-start-services)) (when pymacs-gc-wanted (pymacs-garbage-collect)) (let (done value) (while (not done) (let* ((text (pymacs-round-trip inserter)) (reply (condition-case info (eval text) (error (cons 'pymacs-oops (prin1-to-string info)))))) (cond ((not (consp reply)) (setq inserter `(pymacs-print-for-apply 'reply '(,reply)))) ((eq 'pymacs-reply (car reply)) (setq done t value (cdr reply))) ((eq 'pymacs-error (car reply)) (error "Python: %s" (cdr reply))) ((eq 'pymacs-oops (car reply)) (setq inserter `(pymacs-print-for-apply 'error '(,(cdr reply))))) ((eq 'pymacs-expand (car reply)) (setq inserter `(pymacs-print-for-apply-expanded 'reply '(,(cdr reply))))) (t (setq inserter `(pymacs-print-for-apply 'reply '(,reply))))))) value)) (defun pymacs-reply (expression) ;; This pseudo-function returns `(pymacs-reply . EXPRESSION)'. ;; `serve-until-reply' later recognises this form. (cons 'pymacs-reply expression)) (defun pymacs-error (expression) ;; This pseudo-function returns `(pymacs-error . EXPRESSION)'. ;; `serve-until-reply' later recognises this form. (cons 'pymacs-error expression)) (defun pymacs-expand (expression) ;; This pseudo-function returns `(pymacs-expand . EXPRESSION)'. ;; `serve-until-reply' later recognises this form. (cons 'pymacs-expand expression)) (defun pymacs-round-trip (inserter) ;; This function evals INSERTER to print a Python request. It sends it to ;; the Python helper, awaits for any kind of reply, and returns it. (with-current-buffer pymacs-transit-buffer (unless pymacs-trace-transit (erase-buffer)) (let* ((process (get-buffer-process pymacs-transit-buffer)) (status (process-status process)) (marker (process-mark process)) (moving (= (point) marker)) send-position reply-position reply) (save-excursion (save-match-data ;; Encode request. (setq send-position (marker-position marker)) (let ((standard-output marker)) (eval inserter)) (goto-char marker) (unless (= (preceding-char) ?\n) (princ "\n" marker)) ;; Send request text. (goto-char send-position) (insert (format ">%d\t" (- marker send-position))) (setq reply-position (marker-position marker)) (process-send-region process send-position marker) ;; Receive reply text. (while (and (eq status 'run) (progn (goto-char reply-position) (not (re-search-forward "<\\([0-9]+\\)\t" nil t)))) (unless (accept-process-output process pymacs-timeout-at-reply) (setq status (process-status process)))) (when (eq status 'run) (let ((limit-position (+ (match-end 0) (string-to-number (match-string 1))))) (while (and (eq status 'run) (< (marker-position marker) limit-position)) (unless (accept-process-output process pymacs-timeout-at-line) (setq status (process-status process)))))) ;; Decode reply. (if (not (eq status 'run)) (error "Pymacs helper status is `%S'." status) (goto-char (match-end 0)) (setq reply (read (current-buffer)))))) (when (and moving (not pymacs-trace-transit)) (goto-char marker)) reply))) (defun pymacs-proper-list-p (expression) ;; Tell if a list is proper, id est, that it is `nil, or ends with `nil'. (cond ((not expression)) ((consp expression) (not (cdr (last expression)))))) (provide 'pymacs) bicyclerepair-0.9/ide-integration/Pymacs-0.20/rebox0100644000076400007640000000040407606002142020260 0ustar pldpld#!/usr/bin/env python # Copyright İ 2002 Progiciels Bourbeau-Pinard inc. # François Pinard , 2002. """\ Handling of boxed comments in various box styles. """ import sys from Pymacs import rebox apply(rebox.main, tuple(sys.argv[1:])) bicyclerepair-0.9/ide-integration/Pymacs-0.20/setup-emacs.py0100755000076400007640000001620207606002142022024 0ustar pldpld#!/usr/bin/env python # Copyright İ 2001, 2002 Progiciels Bourbeau-Pinard inc. # François Pinard , 2001. """\ Installer tool for Pymacs `pymacs.el'. Usage: setup [OPTION] -H Display this help, then exit. -V Display package name and version, then exit. -i Interactively check selected options with user. -n Dry run: merely display selected options. -g GROUP Install with write permissions for that user GROUP. -e Load `.emacs' before checking Emacs `load-path'. -l LISPDIR Install `pymacs.el' in LISPDIR. -E EMACS Use that executable for EMACS, if not `emacs'. """ import os, string, sys sys.path.insert(0, '.') from Pymacs import package, version del sys.path[0] AUTOCONF = () # neither a string nor None class run: interactive = 0 dry = 0 group = None dot_emacs = 0 lispdir = AUTOCONF emacs = 'emacs' def main(*arguments): import getopt options, arguments = getopt.getopt(arguments, 'E:HVeg:il:n') for option, value in options: if option == '-E' and value: run.emacs = value elif option == '-H': sys.stdout.write(__doc__) sys.exit(0) elif option == '-V': sys.stdout.write('%s-%s' % (package, version)) sys.exit(0) elif option == '-e': run.dot_emacs = 1 elif option == '-g' and value: run.group = value elif option == '-i': run.interactive = 1 elif option == '-l' and value: if value in ('none', 'None'): run.lispdir = None else: run.lispdir = [value] auto_configure() if run.interactive: check_with_user() check_choices() if not run.dry: complete_install() def auto_configure(): if run.lispdir is AUTOCONF: run.lispdir = [] import tempfile script = tempfile.mktemp() if sys.platform == 'win32': # Win32 names starting with tilde and Emacs are unhappy together. path, file = os.path.split(script) script = os.path.join(path, 'a' + file) try: open(script, 'w').write('(message "%S" load-path)') load_config = '' if run.dot_emacs: config = os.path.join(os.environ['HOME'], '.emacs') for name in config, config + '.el', config + '.elc': if os.path.isfile(name): # Quote! Spaces are common in Win32 file names. load_config = ' -l "%s"' % name break # Quote! Spaces are common in Win32 file names. text = os.popen('%s -batch%s -l "%s" 2>&1' % (run.emacs, load_config, script)).read() finally: os.remove(script) position = string.find(text, '("') if position >= 0: text = text[position:] if text[-1] == '\n': text = text[:-1] assert text[0] == '(' and text[-1] == ')', text for path in string.split(text[1:-1]): assert path[0] == '"' and path[-1] == '"', path path = path[1:-1] if os.access(path, 7): run.lispdir.append(path) def check_with_user(): sys.stderr.write("""\ Install tool for %s version %s. """ % (package, version)) run.lispdir = user_select('lispdir', run.lispdir, """\ This is where `pymacs.el', the Emacs side code of Pymacs, should go: somewhere on your Emacs `load-path'. """) def user_select(name, values, message): write = sys.stderr.write readline = sys.stdin.readline if values is None: write("""\ Enter a value for `%s', or merely type `Enter' if you do not want any. """ % name) write(message) while 1: write('%s? ' % name) text = string.strip(readline()) if not text: return None if os.access(os.path.expanduser(text), 7): return [text] write("""\ This directory does not exist, or is not writable. Please reenter it. """) if len(values) == 1: return values if values == []: write("""\ Pymacs is not likely to install properly, as the installer may not currently write in any directory for `%s'. Running as `root' might help you. Or else, you will most probably have to revise a bit your work setup. """ % name) write(message) return values write("""\ There are many possibilities for `%s', please select one of them by typing its number followed by `Enter'. A mere `Enter' selects the first. """ % name) write(message) write('\n') for counter in range(len(values)): write('%d. %s\n' % (counter + 1, values[counter])) while 1: write('[1-%d]? ' % len(values)) text = string.strip(readline()) if not text: return [values[0]] try: counter = int(text) except ValueError: pass else: if 1 <= counter <= len(values): return [values[counter-1]] write("""\ This is not a valid choice. Please retry. """) def check_choices(): write = sys.stderr.write error = 0 if run.lispdir is not None: if run.lispdir and os.access(os.path.expanduser(run.lispdir[0]), 7): run.lispdir = run.lispdir[0] else: write("\ Use `-l LISPDIR' to select where `pymacs.el' should go.\n") error = 1 if error: write("ERROR: Installation aborted!\n" " Try `%s -i'.\n" % sys.argv[0]) sys.exit(1) write( '\n' "Directory selection for installing Pymacs:\n" " lispdir = %(lispdir)s\n" '\n' % run.__dict__) def complete_install(): run.substitute = {'PACKAGE': package, 'VERSION': version} if run.lispdir: goal = os.path.join(run.lispdir, 'pymacs.el') install('pymacs.el', goal, 0644) compile_lisp(goal) def install(source, destination, permissions): sys.stderr.write('Installing %s\n' % destination) write = open(destination, 'w').write produce_at = 0 #print '*', run.substitute for fragment in string.split(open(source).read(), '@'): #print '**', produce_at, `fragment` if produce_at: replacement = run.substitute.get(fragment) #print '***', replacement if replacement is None: write('@') write(fragment) else: write(replacement) produce_at = 0 else: write(fragment) produce_at = 1 write = None set_attributes(destination, permissions) def compile_lisp(name): sys.stderr.write('Compiling %s\n' % name) os.system('%s -batch -f batch-byte-compile %s' % (run.emacs, name)) set_attributes(name + 'c', 0644) def set_attributes(name, permissions): if run.group: os.chown(name, run.group) permissions = permissions | 0020 os.chmod(name, permissions) if __name__ == '__main__': apply(main, sys.argv[1:]) bicyclerepair-0.9/ide-integration/Pymacs-0.20/setup.py0100755000076400007640000000072707606002142020743 0ustar pldpld#!/usr/bin/env python import sys sys.path.insert(0, '.') from Pymacs import package, version del sys.path[0] from distutils.core import setup setup(name=package, version=version, description='Interface between Emacs LISP and Python.', author='François Pinard', author_email='pinard@iro.umontreal.ca', url='http://www.iro.umontreal.ca/~pinard', scripts=['pymacs-services', 'pymacs-services.bat', 'rebox'], packages=['Pymacs']) bicyclerepair-0.9/ide-integration/test/0040775000076400007640000000000010036707253016401 5ustar pldpldbicyclerepair-0.9/ide-integration/test/README0100644000076400007640000000462707722316276017276 0ustar pldpldTest Steps for testing idle integration --------------------------------------- N.B. this test script is not expected to test all of bicyclerepairman - the pyunit tests do that. It is merely there to test the integration with idle (which doesnt get tested by pyunit). ------------- Rename * ----------------------------------------- - Load scrap.py - Rename Class to MyRenamedClass (Check it renames the class) - Undo the rename - Create scrap3.py, containing the following: --------------------------- import scrap a = scrap.MyClass() a.myMethod() -------------------------- - Go back to scrap.py and rename the method to MyRenamedMethod (Check that it prompts for rename in scrap2.py - rename the first, but not the second) (Check that it renames all the methods in scrap2.py and scrap3.py except for the one you said no to) - Undo the rename (Check that it undid all the renamings in all the files) ------------- Find References ---------------------------------- - Goto scrap.py, select 'find references'. Check that it tells you to highlight a class/function/method. - Highlight 'myMethod' and try again Check that it displays a list of references to this method. ------------- Find Definition ---------------------------------- - Goto scrap2.py, click 'myMethod' on d.myMethod(), then select 'find definition'. Check that it displays both the myMethod in MyClass and in AnotherClass. - Goto scrap2.py, click 'myMethod' on e.myMethod(), then select 'find definition'. Check that it takes you to the definition, and doesn't display a list of myMethod() references. ------------- Extract Method / Function ------------------------ - Load extractMethod.py into idle - select 'Extract Method' without first selecting a region (Check that it tells you to select the region) - Use extractMethod to extract the marked line from the function - Use extractMethod to extract the lines of code from the method - Undo the extract Method - Undo the extract Function - Undo again - confirm that a dialog box pops up telling you the stack is empty. ------------- Extract / Inline local variable ------------------ - Load extractMethod.py into the ide - In the function 'inlineVariableTest', extract the marked code into a variable - Inline the variable back into the code ---------------------------------------------------------------- - exit from the ide, and delete scrap3.py from the directory bicyclerepair-0.9/ide-integration/test/extractmethod.py0100644000076400007640000000045707722316036021632 0ustar pldplddef myFunction(): a = 3 print "hello"+a # extract me class MyClass: def myMethod(self): b = 12 # extract me c = 3 # and me d = 2 # and me print b, c def inlineVariableTest(): a = b + 3 - 5 # --^^^^^ - Extract this into variable bicyclerepair-0.9/ide-integration/test/scrap.py0100644000076400007640000000031510014661536020054 0ustar pldpldclass MyClass: def myMethod(self, foo): pass a = MyClass() a.myMethod() class AnotherClass: def myMethod(self,foo): pass def testFunction(): print "hello" print "hello" bicyclerepair-0.9/ide-integration/test/scrap2.py0100644000076400007640000000023310014661536020135 0ustar pldpldfrom scrap import MyClass, testFunction b = MyClass() b.myMethod() c = abcde() c.myMethod() d = defgh() d.myMethod() e = MyClass() e.myMethod() bicyclerepair-0.9/ide-integration/BicycleRepairMan_Idle.py0100644000076400007640000003311010010500237022054 0ustar pldpld# bicycle repair man idle extension import bike from bike.transformer.undo import UndoStackEmptyException import bike.parsing.load import os from Tkinter import * import tkFileDialog import tkMessageBox import tkSimpleDialog import sys import string try: from idlelib.PathBrowser import * from idlelib.WindowList import ListedToplevel from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas from idlelib import EditorWindow from idlelib.PyShell import PyShell from idlelib.OutputWindow import OutputWindow try: from idlelib.configHandler import idleConf except ImportError: pass except ImportError: from PathBrowser import* from WindowList import ListedToplevel from TreeWidget import TreeNode, TreeItem, ScrolledCanvas import EditorWindow from PyShell import PyShell from OutputWindow import OutputWindow try: from configHandler import idleConf except ImportError: pass brmctx = None shellwin = None loadingFiles = 0 matchwin = None class NotHighlightedException(Exception): pass class BicycleRepairMan_Idle: menudefs = [ ('bicycleRepairMan', [ ('----- Queries -----',''), ('_Find References','<>'), ('_Find Definition','<>'), None, ('--- Refactoring ---',''), ('_Rename', '<>'), ('_Extract Method', '<>'), None, ('_Undo', '<>'), ]) ] keydefs = { '<>':[], '<>':[], '<>':[], '<>':[], '<>':[], } try: TRACE = idleConf.GetOption('extensions','BicycleRepairMan_Idle', 'trace',default=1) except NameError: # hasnt imported idleconf - probably python 22 TRACE = 1 def __init__(self, editwin): self.editwin = editwin if self.TRACE == 1: self.progressLogger = ProgressLogger(self.editwin.flist) if not isinstance(editwin, PyShell): # sly'ly add the refactor menu to the window name, label = ("bicycleRepairMan", "_BicycleRepairMan") underline, label = EditorWindow.prepstr(label) mbar = editwin.menubar editwin.menudict[name] = menu = Menu(mbar, name = name) mbar.add_cascade(label = label, menu = menu, underline = underline) # Initialize Bicyclerepairman and import the code path = self.editwin.io.filename if path is not None: global brmctx if brmctx is None: self.initbrm() else: global shellwin shellwin = editwin def initbrm(self): global brmctx brmctx = bike.init() if self.TRACE == 1: brmctx.setProgressLogger(self.progressLogger) def brm_find_references_event(self,event): try: if not self.confirm_all_buffers_saved(): return if self.editwin.text.index("sel.first") == "": self.errorbox("Not highlighted", "Highlight the name of a Function, Class or Method and try again") return filename = os.path.normpath(self.editwin.io.filename) line, column = string.split(self.editwin.text.index("sel.first"),'.') numMatches = 0 global matchwin if matchwin is None: matchwin = BRMMatchesWindow(self.editwin.flist, self) matchwin.clear() for ref in brmctx.findReferencesByCoordinates(filename,int(line),int(column)): print >>matchwin, "File \""+ref.filename+"\", line "+str(ref.lineno)+", "+str(ref.confidence)+"% confidence" numMatches +=1 print >>matchwin, numMatches," matches" print >>matchwin, "(Hint: right-click to open locations.)" except: self._handleUnexpectedException() def brm_find_definition_event(self,event): try: if not self.confirm_all_buffers_saved(): return filename = os.path.normpath(self.editwin.io.filename) if self.editwin.text.index("sel.first") != "": line, column = string.split(self.editwin.text.index("sel.first"),'.') else: line, column = string.split(self.editwin.text.index("insert"), '.') defns = brmctx.findDefinitionByCoordinates(filename,int(line), int(column)) try: firstref = defns.next() editwin = self.editwin.flist.open(firstref.filename) editwin.gotoline(firstref.lineno) except StopIteration: self.errorbox("Couldn't Find definition","Couldn't Find definition") pass else: numRefs = 1 global matchwin if matchwin is None: matchwin = BRMMatchesWindow(self.editwin.flist, self) for ref in defns: if numRefs == 1: print >>matchwin, firstref.filename+":"+str(firstref.lineno)+": "+str(firstref.confidence)+"% confidence" numRefs += 1 print >>matchwin,ref.filename+":"+str(ref.lineno)+": "+str(ref.confidence)+"% confidence" if matchwin is not None: print >>matchwin, "(Hint: right-click to open locations.)" except: self._handleUnexpectedException() def brm_rename_event(self, event): try: self.renameItemByCoordinates() except: self._handleUnexpectedException() def brm_extract_method_event(self, event): try: if not self.confirm_all_buffers_saved(): return try: filename, newname, beginline, begincolumn, endline, endcolumn = self._getExtractionInformation("Method") except NotHighlightedException: return brmctx.extractMethod(filename, int(beginline), int(begincolumn), int(endline), int(endcolumn), newname) savedfiles = brmctx.save() self.refreshWindows(savedfiles, beginline) except: self._handleUnexpectedException() def brm_undo_event(self, event): try: line, column = string.split(self.editwin.text.index("insert"), '.') brmctx.undo() savedfiles = brmctx.save() self.refreshWindows(savedfiles, line) except UndoStackEmptyException: self.errorbox("Undo Stack Empty", "Undo Stack is empty") except: self._handleUnexpectedException() def _handleUnexpectedException(self): import traceback traceback.print_exc() self.errorbox("Caught Exception", "Caught Exception "+str(sys.exc_info()[0])) def _getExtractionInformation(self, extracttype): if self.editwin.text.index("sel.first") == "": self.errorbox("Code not highlighted", "Highlight the region of code you want to extract and try again") raise NotHighlightedException() filename = os.path.normpath(self.editwin.io.filename) newname = tkSimpleDialog.askstring("Extract Method ", "New "+extracttype+" Name:", parent = self.editwin.text) beginline, begincolumn = string.split(self.editwin.text.index("sel.first"), '.') endline, endcolumn = string.split(self.editwin.text.index("sel.last"), '.') return filename, newname, beginline, begincolumn, endline, endcolumn def renameMethodPromptCallback(self, filename, line, colbegin, colend): editwin = self.editwin.flist.open(filename) originaltop = self.editwin.getwindowlines()[0] # select the method call and position the window editwin.text.tag_remove("sel", "1.0", "end") editwin.text.tag_add("sel", str(line)+"."+str(colbegin), str(line)+"."+str(colend)) line, column = string.split(editwin.text.index("sel.first"), '.') editwin.text.yview(str(int(line)-2)+".0") d = NoFocusDialog("Rename?", "Cannot deduce the type of highlighted object reference.\nRename this declaration?", parent = editwin.text) # put the window back where it was self.editwin.text.yview(float(originaltop)) return d.answer def renameItemByCoordinates(self): if not self.confirm_all_buffers_saved(): return if self.editwin.text.index("sel.first") == "": self.errorbox("Name not highlighted", "Double click the name of the declaration you want to rename (to highlight it) and try again") return brmctx.setRenameMethodPromptCallback(self.renameMethodPromptCallback) line, column = string.split(self.editwin.text.index("sel.first"), '.') filename = os.path.normpath(self.editwin.io.filename) newname = tkSimpleDialog.askstring("Rename", "Rename to:", parent = self.editwin.text) if newname is None: # cancel clicked return brmctx.renameByCoordinates(filename, int(line), int(column), newname) savedfiles = brmctx.save() self.refreshWindows(savedfiles, line) def refreshWindows(self, savedfiles, line): # refresh editor windows oldtop = self.editwin.getwindowlines()[0] global loadingFiles loadingFiles = 1 for sf in savedfiles: normsf = os.path.normcase(sf) if normsf in self.editwin.flist.dict: editwin = self.editwin.flist.dict[normsf] editwin.io.loadfile(sf) loadingFiles = 0 self.editwin.text.mark_set("insert", float(line)) self.editwin.text.yview(float(oldtop)) def confirm_all_buffers_saved(self): filelist = self.editwin.flist.dict.keys() for f in filelist: #editwin = self.editwin.flist.open(f) editwin = self.editwin.flist.dict[f] if self.confirm_buffer_is_saved(editwin) == 0: return 0 return 1     def confirm_buffer_is_saved(self, editwin):         if not editwin.get_saved():             name = (editwin.short_title()or             editwin.long_title()or             "Untitled")             reply = tkMessageBox.askokcancel("Bicycle Repair Man",                 "The buffer for %s is not saved.\n\n"%name+                 "Save it and continue?",                 master = self.editwin.text)           &nbs p; self.editwin.text.focus_set()             if reply:                 editwin.io.save(None)             else:                 return 0         return 1 def errorbox(self, title, message): tkMessageBox.showerror(title, message, master = self.editwin.text) self.editwin.text.focus_set() class BRMTraceWindow(OutputWindow): def short_title(self): return "BicycleRepairMan Trace" class ProgressLogger: def __init__(self,flist): self.flist = flist def write(self,txt): if not hasattr(self,"io"): self.io = BRMTraceWindow(self.flist) try: self.io.write(txt) self.io.flush() except IOError: pass class NoFocusDialog(tkSimpleDialog._QueryDialog): def __init__(self, title, prompt, initialvalue = None, minvalue = None, maxvalue = None, parent = None): self.answer = 0 if not parent: import Tkinter parent = Tkinter._default_root self.prompt = prompt self.minvalue = minvalue self.maxvalue = maxvalue self.initialvalue = initialvalue Toplevel.__init__(self, parent) if title: self.title(title) self.parent = parent self.result = None body = Frame(self) self.initial_focus = self.body(body) body.pack(padx = 5, pady = 5) self.buttonbox() self.grab_set() self.protocol("WM_DELETE_WINDOW", self.cancel) if self.parent is not None: self.geometry("+%d+%d"%(parent.winfo_rootx()+50, parent.winfo_rooty()+50)) self.wait_window(self) def getresult(self): self.answer = 1 def body(self, master): w = Label(master, text = self.prompt, justify = LEFT) w.grid(row = 0, padx = 5, sticky = W) def buttonbox(self): box = Frame(self) w = Button(box, text = "Yes", width = 10, command = self.ok, default = ACTIVE) w.pack(side = LEFT, padx = 5, pady = 5) w = Button(box, text = "No", width = 10, command = self.cancel) w.pack(side = LEFT, padx = 5, pady = 5) self.bind("", self.ok) self.bind("", self.cancel) box.pack() class BRMMatchesWindow(OutputWindow): def __init__(self,flist,masterwin): OutputWindow.__init__(self,flist) self.masterwin = masterwin def close(self): global matchwin matchwin = None OutputWindow.close(self) def short_title(self): return "BicycleRepairMan Matches" def clear(self): self.text.delete("1.0","end-1c") bicyclerepair-0.9/ide-integration/bike.vim0100644000076400007640000003127407721131551017052 0ustar pldpld" Bicycle Repair Man integration for Vim " Version 0.3 " Copyright (c) 2003 Marius Gedminas " " Needs Vim 6.x with python interpreter (Python 2.2 or newer) " Installation instructions: just drop it into $HOME/.vim/plugin and you " should see the Bicycle Repair Menu appear in GVim (or you can check if " any of the BikeXxx commands are defined in console mode Vim). If bike.vim " fails to load and you want to see the reason, try " let g:bike_exceptions = 1 " source ~/.vim/plugin/bike.vim " " Configuration options you can add to your .vimrc: " let g:bike_exceptions = 1 " show tracebacks on exceptions " let g:bike_progress = 1 " show import progress " " Commands defined: " " BikeShowScope " Show current scope. Sample of usage: " autocmd CursorHold *.py BikeShowScope " " BikeShowType " Show selected expression type. The range is ignored, '<,'> is always " used. Use this in visual block mode. " " BikeFindRefs " Finds all references of the function/method/class defined in current " line. " " BikeFindDef " Finds the definition of the function/method/class under cursor. " " BikeRename " Renames the function/method/class defined in current line. Updates " all references in all files known (i.e. imported) to Bicycle Repair " Man. " " BikeExtract " Extracts a part of the function/method into a new function/method. " The range is ignored, '<,'> is always used. Use this in visual mode. " " BikeUndo " Globally undoes the previous refactoring. " " Issues: " - Saves all modified buffers to disk without asking the user -- not nice " - Does not reimport files that were modified outside of Vim " - Uses mktemp() -- I think that produces deprecation warnings in Python 2.3 " - BikeShowType, BikeExtract ignores the specified range, that's confusing. " At least BikeExtract ought to work... " - BikeShowType/BikeExtract or their GUI counterparts work when not in " visual mode by using the previous values of '<,'>. Might be confusing. " - Would be nice if :BikeImport asked to enter package " - Would be nice if :BikeRename myNewName worked " - The code is not very robust (grep for XXX) " " Default settings for global configuration variables {{{1 " " Set to 1 to see full tracebacks if !exists("g:bike_exceptions") let g:bike_exceptions = 0 endif " Set to 1 to see import progress if !exists("g:bike_progress") let g:bike_progress = 0 endif " " Initialization {{{1 " " First check that Vim is sufficiently recent, that we have python interpreted " support, and that Bicycle Repair Man is available. " " If something is wrong, fail silently, as error messages every time vim " starts are very annoying. But if the user wants to see why bike.vim failed " to load, she can let g:bike_exceptions = 1 if version < 600 if g:bike_exceptions echo 'Bicycle Repair Man needs Vim 6.0' endif finish endif if !has("python") if g:bike_exceptions echo 'Bicycle Repair Man needs Vim with Python interpreter support (2.2 or newer)' endif finish endif let s:has_bike=1 python << END import vim import sys try: if sys.version_info < (2, 2): raise ImportError, 'Bicycle Repair Man needs Python 2.2 or newer' import bike bikectx = bike.init() bikectx.isLoaded # make sure bike package is recent enough except ImportError: vim.command("let s:has_bike=0") if vim.eval('g:bike_exceptions') not in (None, '', '0'): raise END if !s:has_bike finish endif " Use sane cpoptions let s:cpo_save = &cpo set cpo&vim " " Menu {{{1 " silent! aunmenu Bicycle\ Repair\ Man " Shortcuts available: ab-----h-jk--nopq----vw--z amenu Bicycle\ &Repair\ Man.-SEP1- \ : amenu Bicycle\ &Repair\ Man.&Find\ References \ :call BikeFindRefs() amenu Bicycle\ &Repair\ Man.Find\ &Definition \ :call BikeFindDef() amenu Bicycle\ &Repair\ Man.Resu<s.&List:cl \ :cl amenu Bicycle\ &Repair\ Man.Resu<s.&Current:cc \ :cc amenu Bicycle\ &Repair\ Man.Resu<s.&Next:cn \ :cn amenu Bicycle\ &Repair\ Man.Resu<s.&Previous:cp \ :cp amenu Bicycle\ &Repair\ Man.Resu<s.&First:cfirst \ :cfirst amenu Bicycle\ &Repair\ Man.Resu<s.Las&t:clast \ :clast amenu Bicycle\ &Repair\ Man.Resu<s.&Older\ List:colder \ :colder amenu Bicycle\ &Repair\ Man.Resu<s.N&ewer\ List:cnewer \ :cnewer amenu Bicycle\ &Repair\ Man.Resu<s.&Window.&Update:cw \ :cw amenu Bicycle\ &Repair\ Man.Resu<s.&Window.&Open:copen \ :copen amenu Bicycle\ &Repair\ Man.Resu<s.&Window.&Close:cclose \ :cclose amenu Bicycle\ &Repair\ Man.-SEP2- \ : amenu Bicycle\ &Repair\ Man.&Rename \ :call BikeRename() amenu Bicycle\ &Repair\ Man.E&xtract\ Method \ :call BikeExtract('method') amenu Bicycle\ &Repair\ Man.&Extract\ Function \ :call BikeExtract('function') amenu Bicycle\ &Repair\ Man.&Undo \ :call BikeUndo() amenu Bicycle\ &Repair\ Man.-SEP3- \ : amenu Bicycle\ &Repair\ Man.Settin&gs.Import\ &Progress.&Enable \ :let g:bike_progress = 1 amenu Bicycle\ &Repair\ Man.Settin&gs.Import\ &Progress.&Disable \ :let g:bike_progress = 0 amenu Bicycle\ &Repair\ Man.Settin&gs.Full\ &Exceptions.&Enable \ :let g:bike_exceptions = 1 amenu Bicycle\ &Repair\ Man.Settin&gs.Full\ &Exceptions.&Disable \ :let g:bike_exceptions = 0 " Note: The three rename commands are basically identical. The two extract " commands are also identical behind the scenes. " " Commands {{{1 " command! BikeShowScope call BikeShowScope() command! -range BikeShowType call BikeShowType() command! BikeFindRefs call BikeFindRefs() command! BikeFindDef call BikeFindDef() command! BikeRename call BikeRename() command! -range BikeExtract call BikeExtract('function') command! BikeUndo call BikeUndo() " " Implementation {{{1 " " Query functions {{{2 function! s:BikeShowScope() " Shows the scope under cursor python << END fn = vim.current.buffer.name row, col = vim.current.window.cursor try: print bikectx.getFullyQualifiedNameOfScope(fn, row) except: show_exc() END endf function! s:BikeShowType() " Shows the inferred type of the selected expression if col("'<") == 0 " mark not set echo "Select a region first!" return endif if line("'<") != line("'>") " multiline selection echo "Multi-line regions not supported" " XXX deficiency of bikefacade interface, expressions can easily span " several lines in Python return endif python << END fn = vim.current.buffer.name row1, col1 = vim.current.buffer.mark('<') row2, col2 = vim.current.buffer.mark('>') try: print bikectx.getTypeOfExpression(fn, row1, col1, col2) except: show_exc() END endf function! s:BikeFindRefs() " Find all references to the item defined on current line wall python << END fn = vim.current.buffer.name row, col = vim.current.window.cursor try: refs = bikectx.findReferencesByCoordinates(fn, row, col) quickfixdefs(refs) except: show_exc() END endf function! s:BikeFindDef() " Find all definitions of the item under cursor. Ideally there should be only " one. wall python << END fn = vim.current.buffer.name row, col = vim.current.window.cursor try: defs = bikectx.findDefinitionByCoordinates(fn, row, col) quickfixdefs(defs) except: show_exc() END endf " Refactoring commands {{{2 function! s:BikeRename() " Rename a function/method/class. let newname = inputdialog('Rename to: ') wall python << END fn = vim.current.buffer.name row, col = vim.current.window.cursor newname = vim.eval("newname") if newname: try: bikectx.setRenameMethodPromptCallback(renameMethodPromptCallback) bikectx.renameByCoordinates(fn, row, col, newname) except: show_exc() saveChanges() else: print "Aborted" END endf function! s:BikeExtract(what) " Extract a piece of code into a separate function/method. The argument " a:what can be 'method' or 'function'. if col("'<") == 0 " mark not set echo "Select a region first!" return endif let newname = inputdialog('New function name: ') wall python << END fn = vim.current.buffer.name row1, col1 = vim.current.buffer.mark('<') row2, col2 = vim.current.buffer.mark('>') newname = vim.eval("newname") if newname: try: bikectx.extractMethod(fn, row1, col1, row2, col2, newname) except: show_exc() saveChanges() else: print "Aborted" END endf function! s:BikeUndo() " Undoes the last refactoring wall python << END try: bikectx.undo() saveChanges() except bike.UndoStackEmptyException, e: print "Nothing to undo" END endf " " Helper functions {{{1 " " Python helpers python << END import tempfile import os import linecache modified = {} # a dictionary whose keys are the names of files modifed # in Vim since the last import def show_exc(): """Print exception according to bike settings.""" if vim.eval('g:bike_exceptions') not in (None, '', '0'): import traceback traceback.print_exc() else: type, value = sys.exc_info()[:2] if value is not None: print "%s: %s" % (type, value) else: print type def writedefs(defs, filename): """Write a list of file locations to a file.""" ef = None curdir = os.getcwd() if not curdir.endswith(os.sep): curdir = curdir + os.sep for d in defs: if not ef: ef = open(filename, "w") fn = d.filename if fn.startswith(curdir): fn = fn[len(curdir):] line = linecache.getline(fn, d.lineno).strip() res ="%s:%d: %3d%%: %s" % (fn, d.lineno, d.confidence, line) print res print >> ef, res if ef: ef.close() return ef is not None def quickfixdefs(defs): """Import a list of file locations into vim error list.""" fn = tempfile.mktemp() # XXX unsafe if writedefs(defs, fn): vim.command('let old_errorfile = &errorfile') vim.command('let old_errorformat = &errorformat') vim.command(r'set errorformat=\%f:\%l:\ \%m') vim.command("cfile %s" % fn) vim.command('let &errorformat = old_errorformat') vim.command('let &errorfile = old_errorfile') os.unlink(fn) else: print "Not found" def renameMethodPromptCallback(filename, line, col1, col2): """Verify that the call in a given file position should be renamed.""" vim.command('e +%d %s' % (line, filename)) vim.command('normal %d|' % col1) vim.command('match Search /\%%%dl\%%>%dc\%%<%dc' % (line, col1, col2+1)) vim.command('redraw') ans = vim.eval('confirm("Cannot deduce instance type. Rename this call?",' ' "&Yes\n&No", 1, "Question")') vim.command('match none') if ans == '1': return 1 else: return 0 def saveChanges(): """Save refactoring changes to the file system and reload the modified files in Vim.""" # bikectx.save() returns a list of modified file names. We should make # sure that all those files are reloaded iff they were open in vim. files = map(os.path.abspath, bikectx.save()) opened_files = {} for b in vim.buffers: if b.name is not None: opened_files[os.path.abspath(b.name)] = b.name for f in files: try: # XXX might fail when file name contains funny characters vim.command("sp %s | q" % opened_files[f]) except KeyError: pass # Just in case: vim.command("checktime") vim.command("e") END " Make sure modified files are reimported into BRM autocmd BufWrite * python modified[os.path.abspath(vim.current.buffer.name)] = 1 " " Cleanup {{{1 " " Restore cpoptions let &cpo = s:cpo_save unlet s:cpo_save bicyclerepair-0.9/ide-integration/bikeemacs.py0100755000076400007640000002245310036706552017724 0ustar pldpld# Bicycle Repair Man integration with (X)Emacs # By Phil Dawes (2002) # Uses the fabulous Pymacs package by François Pinard from Pymacs import lisp, Let import bike reload(bike) from bike import bikefacade import StringIO, traceback import sys class EmacsLogger: def __init__(self): self.msg = "" def write(self,output): self.msg +=output if output.endswith("\n"): lisp.message(self.msg) self.msg = "" class NullLogger: def write(self,output): pass logger = EmacsLogger() class ExceptionCatcherWrapper: def __init__(self,myobj): self.myobj = myobj def __getattr__(self,name): return ExceptionInterceptor(self.myobj,name) class ExceptionInterceptor: def __init__(self,targetobj,name): self.targetobj = targetobj self.name = name def __call__(self, *params, **kwparams ): try: return self.makeCall(params) except: traceback.print_exc() lisp.error(str(sys.exc_info()[1])) def makeCall(self,params): argsstr="(self.targetobj" for i in range(len(params)): argsstr += ",params["+`i`+"]" argsstr+=")" return eval("self.targetobj.__class__."+self.name+argsstr) class BRMEmacs(object): def __init__(self,brmctx): self.ctx = brmctx lisp.require(lisp["python-mode"]) lisp(""" (defvar brm-menu nil "Menu for Bicycle Repair Man") (easy-menu-define brm-menu py-mode-map "Bicycle Repair Man" '("BicycleRepairMan" "Queries" ["Find-References" brm-find-references] ["Find-Definition" brm-find-definition] "---" "Refactoring" ["Rename" brm-rename t] ["Extract-Method" brm-extract-method t] ["Extract-Local-Variable" brm-extract-local-variable t] ["Inline-Local-Variable" brm-inline-local-variable t] ["Undo Last Refactoring" brm-undo t] )) (add-hook 'python-mode-hook (lambda () (easy-menu-add brm-menu))) """) # ["Move-Class-To-New-Module" brm-move-class t] # ["Move-Function-To-New-Module" brm-move-class t] self.ctx.setProgressLogger(logger) def rename(self,newname): lisp.save_some_buffers() filename = lisp.buffer_file_name() line,col = _getCoords() brmctx.setRenameMethodPromptCallback(promptCallback) try: self.ctx.renameByCoordinates(filename,line,col,newname) savedFiles = brmctx.save() _revertSavedFiles(savedFiles) lisp.set_marker(lisp.mark_marker(),None) except bikefacade.CouldntLocateASTNodeFromCoordinatesException: print >>logger,"Couldn't find AST Node. Are you renaming the declaration?" def kill(self): self.ctx = None self.ctx = bike.init() self.ctx.setProgressLogger(logger) def undo(self): brmctx.undo() savedFiles = brmctx.save() _revertSavedFiles(savedFiles) def find_references(self): lisp.save_some_buffers() filename = lisp.buffer_file_name() line,col = _getCoords() refs = brmctx.findReferencesByCoordinates(filename,line,col) _switchToConsole() numRefs = 0 for ref in refs: _insertRefLineIntoConsole(ref) numRefs +=1 lisp.insert("Done - %d refs found\n"%numRefs) def find_definition(self): lisp.save_some_buffers() filename = lisp.buffer_file_name() line,col = _getCoords() defns = brmctx.findDefinitionByCoordinates(filename,line,col) try: firstdefn = defns.next() lisp.find_file_other_window(firstdefn.filename) lisp.goto_line(firstdefn.lineno) lisp.forward_char(firstdefn.colno) except StopIteration: pass else: numRefs = 1 for defn in defns: if numRefs == 1: _switchToConsole() _insertRefLineIntoConsole(firstdefn) _insertRefLineIntoConsole(defn) numRefs += 1 def inline_local_variable(self): lisp.save_some_buffers() filename = lisp.buffer_file_name() line,col = _getCoords() brmctx.inlineLocalVariable(filename,line,col) lisp.set_marker(lisp.mark_marker(),None) _revertSavedFiles(brmctx.save()) def extract_local_variable(self,name): lisp.save_some_buffers() filename = lisp.buffer_file_name() bline,bcol = _getPointCoords() lisp.exchange_point_and_mark() eline,ecol = _getPointCoords() lisp.exchange_point_and_mark() brmctx.extractLocalVariable(filename,bline,bcol,eline,ecol,name) lisp.set_marker(lisp.mark_marker(),None) _revertSavedFiles(brmctx.save()) def extract_method(self,name): lisp.save_some_buffers() filename = lisp.buffer_file_name() bline,bcol = _getPointCoords() lisp.exchange_point_and_mark() eline,ecol = _getPointCoords() lisp.exchange_point_and_mark() brmctx.extract(filename,bline,bcol,eline,ecol,name) lisp.set_marker(lisp.mark_marker(),None) _revertSavedFiles(brmctx.save()) def move_class(self,newfilename): lisp.save_some_buffers() filename = lisp.buffer_file_name() line,col = _getCoords() brmctx.moveClassToNewModule(filename,line,newfilename) _revertSavedFiles(brmctx.save()) brmctx = bike.init() brmemacs = None is_xemacs = (lisp.emacs_version().find("GNU") == -1) currently_saving=0 # fix pop_excursion to work with xemacs class Let(Let): def pop_excursion(self): method, (buffer, point_marker, mark_marker) = self.stack[-1] assert method == 'excursion', self.stack[-1] del self.stack[-1] lisp.set_buffer(buffer) lisp.goto_char(point_marker) lisp.set_mark(mark_marker) lisp.set_marker(point_marker, None) if mark_marker is not None: # needed for xemacs lisp.set_marker(mark_marker, None) def init(): global brmemacs brmemacs = ExceptionCatcherWrapper(BRMEmacs(brmctx)) def kill(): """ Removes the bicyclerepairman context (freeing the state), and reinitialises it. """ brmemacs.kill() kill.interaction="" def promptCallback(filename,line,colbegin,colend): let = Let().push_excursion() # gets popped when let goes out of scope buffer = lisp.current_buffer() if let: ans = 0 lisp.find_file(filename) lisp.goto_line(line) lisp.move_to_column(colbegin) lisp.set_mark(lisp.point() + (colend - colbegin)) if is_xemacs: lisp.activate_region() ans = lisp.y_or_n_p("Couldn't deduce object type - rename this method reference? ") del let lisp.switch_to_buffer(buffer) return ans def rename(newname): return brmemacs.rename(newname) rename.interaction="sNew name: " def undo(): return brmemacs.undo() undo.interaction="" def _revertSavedFiles(savedFiles): global currently_saving currently_saving = 1 for file in savedFiles: buf = lisp.find_buffer_visiting(file) if buf: lisp.set_buffer(buf) lisp.revert_buffer(None,1) currently_saving = 0 def find_references(): brmemacs.find_references() find_references.interaction="" def _getCoords(): line = lisp.count_lines(1,lisp.point()) col = lisp.current_column() if col == 0: line += 1 # get round 'if col == 0, then line is 1 out' problem if mark_exists() and lisp.point() > lisp.mark(): lisp.exchange_point_and_mark() col = lisp.current_column() lisp.exchange_point_and_mark() return line,col def mark_exists(): if is_xemacs: return lisp.mark() else: return lisp("mark-active") and lisp.mark() def _switchToConsole(): consolebuf = lisp.get_buffer_create("BicycleRepairManConsole") lisp.switch_to_buffer_other_window(consolebuf) lisp.compilation_mode("BicycleRepairMan") lisp.erase_buffer() lisp.insert("Bicycle Repair Man\n") lisp.insert("(Hint: Press Return a Link)\n") def find_definition(): brmemacs.find_definition() find_definition.interaction="" def _insertRefLineIntoConsole(ref): lisp.insert(ref.filename+":"+str(ref.lineno)+": "+str(ref.confidence)+"% confidence\n") _redisplayFrame() def _redisplayFrame(): lisp.sit_for(0) def inline_local_variable(): brmemacs.inline_local_variable() inline_local_variable.interaction="" def extract_local_variable(name): brmemacs.extract_local_variable(name) extract_local_variable.interaction="sVariable name: " def extract_method(name): brmemacs.extract_method(name) extract_method.interaction="sName of function: " def move_class(newfilename): return brmemacs.move_class(newfilename) move_class.interaction="fTarget file: " def _getPointCoords(): bline = lisp.count_lines(1,lisp.point()) bcol = lisp.current_column() if bcol == 0: # get round line is one less if col is 0 problem bline += 1 return bline,bcol bicyclerepair-0.9/AUTHORS0100664000076400007640000000156210012212306013371 0ustar pldpldAuthors ------- Phil Dawes - Current Maintainer and Main Author Shae Erisson - Original Maintainer The following people have contributed code, bugfixes and patches: Jürgen Hermann Canis Lupus Syver Enstad Windows emacs patches Mathew Yeates VIM support and bug fixes Marius Gedminas More VIM support François Pinard Pymacs + help with emacs integration Ender Jonathan Steve Peter Astrand See ChangeLog for more details. bicyclerepair-0.9/COPYING0100644000076400007640000000310207413624223013362 0ustar pldpldCOPYRIGHT AND PERMISSION NOTICE Copyright (c) 2000 by Shae Erisson Copyright (c) 2001 by Phil Dawes All rights reserved. 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, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. 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 OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. bicyclerepair-0.9/ChangeLog0100644000076400007640000021020410014636512014077 0ustar pldpld2004-02-18 Phil Dawes * bike/query/getTypeOf.py: Added feature to resolveImportedModuleOrPackage that searches the package hierarchy of the scope even if it isn't in the pythonpath. 2004-02-11 Phil Dawes * bike/query/getTypeOf.py: rewrote resolveImportedModuleOrPackage to use purely getModuleOrPackageUsingFQN searches. (doesnt use getTypeOf Root searching any more) * bike/query/common.py: rewrote getLogicalLine to handle multilines that parse seperately. (But was submitted by Peter Astrand) 2004-02-10 Phil Dawes * bike/query/getTypeOf.py: Added functionality to search from the current directory if looking for an import and scope is a module 2004-02-10 Phil Dawes * bike/refactor/moveToModule.py: Added handling of individual 'from a.foo import theFunction'. * bike/parsing/fastparserast.py: Added Peter Astrand's patch to fix following bug: 'Find-References (and probably others) fails if there is more whitespace than a single space between the "class"/"def" keyword and the class/def name.' * bike/query/common.py: fixed bug in just-written-code where no dest in Printnl causes npe. * bike/query/common.py: Fixed a bug in MatchFinder. Printnl ast nodes (print >>foo, bah) look at there children the opposito way to the way the text comes. (i.e. they do visit(bah), visit(foo) in the example above). Wrote code to get round this. 2004-02-09 Phil Dawes * bike/refactor/moveToModule.py: added more functionality to moveFunction * bike/bikefacade.py: added expanduser to normalizeFilename so that ~/src/foo gets turned into a proper full path. * bike/bikefacade.py: exposed moveClassToNewModule. * bike/refactor/moveClass.py: started move-class-to-new-module refactoring. 2004-02-05 Phil Dawes * bike/parsing/pathutils.py: new module containing all the functions that search all the files in the pythonpath. * bike/parsing/extended_ast.py: Removed this module. SourceFile is now in load.py, the rest is in fastparserast.py * ide-integration/BicycleRepairMan_Idle.py : Added patch from Steve to allow easier saving of files before refactoring in IDLE 2004-02-04 Phil Dawes * bike/parsing/extended_ast.py: lots of cleanup. Renamed Source class to SourceFile. Slimmed down Package and Root. Am planning on removing this module completely, and moving the classes into other modules. * bike/refactor/extractVariable.py: refactored and removed extractLocalVariable_old * bike/refactor/extractMethod.py: refactored and removed extractMethod_old * bike/refactor/test_rename*.py: Removed the 'rename twice' tests. They are no longer applicable (since BRM saves to disk after each refactoring) * bike/transformer/: Moved save.py and undo.py into the transformer package * bike/parsing/newstuff.py: moved generatePackageDependencies into the parsing package, because it needs to be used by other modules in the parsing package. 2004-02-03 Phil Dawes * bike/query/relationships.py: Added generatePackageDependencies which given a file in a package hierarchy, yields a list of external packages and modules used by the package. * bike/parsing/newstuff.py: Rewrote generateModuleFilenamesInPythonPath wrt new ref search scheme. Scheme: If the module you are searching from is in a package hierarchy, scan every module in the hierarchy, and the files in the directory above it (since they can reach the package without including it in PYTHONPATH). If the module is in a non-package directory, search in all files in the directory, and in any packages below that directory. N.B. this is in addition to directories in the PYTHONPATH. 2004-02-02 Phil Dawes * bike/parsing/newstuff.py: Removed getRegexMatchesInPythonPath * bike/parsing/newstuff.py: Modified getSourceNodesContainingRegex to search the pythonpath + the set of python files in the directory above the root package of the current file. This should remove the requirement to add the root directory to the python path, and thus prevent BRM from finding references in other package structures. * bike/query/common.py: Removed scanASTSourceNodesForMatches and walkSourceNodes * bike/globals.py: Added True/False declaration for python 2.2 back-compatibility. 2004-01-26 Phil Dawes * bike/query/common.py: Added code to check for \ in a previous line when locating logical lines 2004-01-25 Phil Dawes * bike/query/common.py: Added a test and fixed a bug that meant find-definitions from multiline statements wouldn't work if the braces were equalised on the following line. (thanks again to Detlev for reporting this bug) 2004-01-14 Phil Dawes * bike/query/common.py: Added a test and fixed a bug that meant find-definitions from multiline statements wouldn't work. (thanks to Detlev for reporting this bug) 2004-01-12 Phil Dawes * bike/parsing/load.py: Added Detlevs patch to not recurse into subversion directories. 2004-01-11 Phil Dawes * bike/parsing/newstuff.py: Added code in generateModuleFilenamesInPythonPath to ensure each filename is generated once. (and added test to check for it) (Thanks to Detlev & Syver for this bug report) * bike/query/common.py (globalScanForMatches): Removed old ast-based code from globalScanForMatches 2004-01-08 Phil Dawes * bike/query/getTypeOf.py: Fixed bug where 'from foo import aou' would cause an assert to fail if foo didn't exist. (Thanks Syver) 2004-01-06 Phil Dawes * bike/bikefacade.py: Added code to normalize paths coming into BRM. (hopefully fixes Syver's problems on windows - Thanks Syver) 2003-09-04 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Fixed bug in Bicyclerepair_idle. ----------------------- 0.9 BETA4 ------------------------------- 2003-09-02 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Fixed bug where brmctx was being tested for a __nonzero__ operator due to new wrapper stuff. Also made the matches window a singleton * bike/query/findReferences.py: Rewrote attribute reference finding algorithm to locate a possible match, and then check if the match class shares a common ancestor with the target. (rather than finding all classes in the target hierarchy and then testing each match class against the hierarchy). This results in a 10000% 2003-09-02 Phil Dawes * bike/query/relationships.py: Added buildClassHierarchy functionality to guess a class hierarchy, but found that it's still much too slow to handle the wxpython class hierarchy of 1900 odd classes under 1 root! 2003-09-01 Phil Dawes * bike/parsing/fastparserast.py: Added maskedsrc to the cache * bike/query/getTypeOf.py: Added caching of type lookups * bike/bikefacade.py: Added a generic wrapper which purges the caches before and after each brm ctx call 2003-08-31 Phil Dawes * bike/parsing/newstuff.py: Added simple caching mechanism for sourceNodes 2003-08-30 Phil Dawes * bike/parsing/extended_ast.py: Removed some crufty methods and base classes for managing the ast tree hierarchy * bike/bikefacade.py: Removed a couple of ast related methods * bike/query/findReferences.py: Modified the findRefs functionality to exclude matches where the confidence is high that it's *not* the right type. * bike/parsing/load.py: removed the load function (and made necessary refactorings to remove it) 2003-08-25 Phil Dawes * bike/bikefacade.py: Added setWarningLogger 2003-08-25 Phil Dawes * bike/parsing/extended_ast.py: Fixed bug where "../.." was being added to sys.path. This was because extended_ast.py was adding importing setpath.py. 2003-08-25 Phil Dawes * bike/bikefacade.py: Added warning code to print message if sys.path is changed by setpath.py * ide-integration/test/README: Added ide tests for inline and extract variable * bike/bikefacade.py: Removed the load() function, and fixed inline and extract local variable ----------------------- 0.9 BETA3 ------------------------------- 2003-08-22 Phil Dawes * bike/bikefacade.py: Fixed removeLibdirsFromPath to work with windows python lib directories ----------------------- 0.9 BETA2 ------------------------------- 2003-08-21 Phil Dawes * bike/*: Made it backward compatible with python 2.2 * ide-integration/bike.vim: Adapted vim plugin to work with new api. ----------------------- 0.9 BETA1 ------------------------------- 2003-08-20 Phil Dawes * bike/test_*: Misc fixes for windows paths * ide-integration/BicycleRepairMan_Idle.py: Cleaned up error handling for undo stack * bike/query/*: Misc fixes for tests * bike/refactor/extractMethod.py: Updated to work with new non-tree system 2003-08-19 Phil Dawes * bike/testutils.py: Converted createSourceNodeAt to create a directory structure rather than an ast. (and broke a load of tests) 2003-08-18 Phil Dawes * bike/testutils.py: added test setup fixture to change directory before executing tests 2003-08-17 Phil Dawes * bike/query/getTypeOf.py: Fixed bug which was stopping sub packages from being found properly * bike/query/relationships.py: Fixed getAllClassesInHierarchy and getAllDerivedClasses to work without AST. Uses a smart algorithm which scans the text of files with regexes for baseclassnames and import aliases to narrow down the likely candidates. 2003-08-12 Phil Dawes * README.idle: Updated docs for python2.3/idlefork 2003-08-11 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Added code to just use 1 window for matches, and to reopen it if needed after it is closed 2003-08-04 Phil Dawes * bike/parsing/load.py: Fixed bug which cause BRM to descend into non-package directorys * bike/parsing/extended_ast.py: Added a couple of new functions to handle pythonpaths for queries and refactorings 2003-08-01 Phil Dawes * bike/query/common.py: modified globalScanForMatches to scan files in addition to walking the ast. * bike/query/findReferences.py: Added code to work with new stateless design. 2003-07-31 Phil Dawes * bike/query/findDefinition.py: Removed module locating code from findDefinition, and made it use resolveImportedModuleOrPackage instead. * bike/query/getTypeOf.py: Modified resolveImportedModuleOrPackage to use the newstuff function getModuleOrPackageUsingFQN if it cant locate the module in the AST tree. * bike/parsing/newstuff.py: modified getModuleUsingFQN to handle packages. renamed to getModuleOrPackageUsingFQN 2003-07-25 Phil Dawes * bike/query/findDefinition.py: Made necessary modifications enable finding of function definitions without importing code (uses the python path to determine where to look). Doesnt work for methods yet. 2003-07-10 Phil Dawes * bike/refactor/inlineVariable.py: Applied Jonathan's patch which fixes bug when multiple instances of a variable are on one line. 2003-06-12 Phil Dawes * bike/query/findReferences.py: changed findReferences interface to take a filename instead of a srcnode * bike/query/findReferences.py: Fixed bug where error wasnt being reported properly 2003-06-11 Phil Dawes * bike/query/findReferences.py: renamed findReferences to findReferences_old, and created a new findReferences which takes a filename instead of the srcnode * bike/bikefacade.py: Removed getFullyQualifiedNameOfScope - it's not used anywhere, and relies on the fqn stuff working. * all tests: attempted to remove all usage of fqn from tests. Instead, replaced with name and filename tests. This is because fqn is one of the major things binding the scopes together. * bike/bikefacade.py: removed getTypeOfExpression. It's not used anywhere. Will reinstate when it is required again. * bike/query/getTypeOf.py: Fixed bug where a recursive function could cause BRM to stack overflow 2003-06-10 Phil Dawes * bike/query/getTypeOf.py: Added resolveImportedModuleOrPackage fn to try and split the number of calls to getTypeOf() * bike/query/getTypeOf.py: Fixed bug where a[0].theMethod() would cause brm to barf. * bike/parsing/fastparserast.py: Beefed up the __str__ members for fastparser nodes 2003-06-06 Phil Dawes * bike/parsing/output.py: Removed output.py. (functionality now in save.py) 2003-06-05 Phil Dawes * bike/refactor/extractMethod.py: removed the ismodified stuff, in an attempt to make the xast hierarchy a read-only resource. Added a hook in save.py to update the xast whenever somebody queues an update to be saved. * bike/parsing/save.py: Added new save module which maintains its own queue of src to be written back to disk. Replaces output.py 2003-05-30 Phil Dawes * bike/parsing/undo.py (UndoStack.undo): refactored to use dictionary * bike/query/findDefinition.py: Refactored main function to have a 'stateless' interface (i.e. no need to pass a context). findAllPossibleDefinitionsByCoords(filepath,lineno,col) 2003-05-29 Phil Dawes * bike/parsing/extended_ast.py (getRoot): Made the Root object a singleton. Use the getRoot() method to get it. This is an intermediate step in making the parser stateless. (idea is to make it pretend to be stateless by being a singleton, then once the interfaces have changed, transition the parser code to actually make it stateless) 2003-04-02 Phil Dawes * ide-integration/bikeemacs.py: Added exception catching and error reporting to bike-emacs integration 2003-03-31 Phil Dawes * Applied Jonathan's patches - InlineVariable handles multiline statements - Fix to bike vim integration 2003-03-31 Marius Gedminas * ide-integration/bike.vim: Removed unnecessary argument to BikeRename(). * bike/query/common.py: Fixed handling of lambdas in MatchFinder. 2003-03-17 Phil Dawes * bike/refactor/extractVariable.py: Made a start on the extract-local-variable refactoring 2003-03-13 Phil Dawes * bike/refactor/inlineVariable.py: Made a start on the inline-local-variable refactoring ----------------------- 0.8 BETA2 ------------------------------- 2003-03-11 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Fixed bug where paths with spaces meant that you couldn't select a reference when doing a findReferences. (Thanks to Andy Bulka for the report) * bike/query/getTypeOf.py: Added infinite recursion protection to getTypeOf * bike/query/relationships.py: Added some performance improving code when searching for a classhierarchy. Rather than doing a getTypeOf() on every base class, it finds the strings likely to be base classes (i.e. the name of the base class, and any 'import name as foo' lines) to narrow the search. 2003-03-10 Phil Dawes * bike/refactor/extractMethod.py: Fixed bug in extract method, where blank lines were messing up the indentation in the resultant block ----------------------- 0.8 BETA1 ------------------------------- 2003-03-10 Phil Dawes * ide-integration/bikeemacs.py: Fixed bug in brm with emacs on windows, where the mark can be active and nil at the same time. * bike/bikefacade.py (BRMContext_impl.load): Fixed bug which affected windows users. Path needs to be saved after it's been normalized. 2003-03-06 Phil Dawes * bike/query/findDefinition.py: fixed bug in attribute finding code - was only searching the first function * bike/query/getTypeOf.py: Fixed bug where x = x.bah() would cause recursion error. (just catches the stack overflow) 2003-03-05 Phil Dawes * bike/*: implemented automatic importing of changed files. This means that ide integration stuff no longer has to import code into brm whenever it is saved. 2003-02-25 Phil Dawes * ide-integration/bike.vim: Consolidated RenameMethod, RenameFunction and RenameClass into 1 menu option. This is because brm now supports renaming of variables and attributes and I didn't want to add another 2 menu items. 2003-02-24 Phil Dawes * ide-integration/bikeemacs.py: Fixed rename 'prompt' bug. Consolidated all the rename stuff into 1 menu option. 2003-02-19 Phil Dawes * bike/query/*: removed getReferencesToClass/Function/Method and replaced with findReferences 2003-02-13 Phil Dawes * bike/query/findReferences.py: Added findReferencesIncludingDefn and made 'vanilla' findReferences not return the definition. This paves the way for a unified rename that can be used to rename anything. 2003-02-11 Phil Dawes * bike/query/findDefinition.py: Fixed bug reported by Marius - if class is declared in __init__.py, it blows up. * bike/query/findReferences.py: Fixed bugs in 'getDefinitionAndScope' logic, so can handle nested classes etc.. 2003-02-10 Phil Dawes * bike/query/getReferencesToClass.py: Replaced module logic with call to findReferences() * tests: modified or removed tests that tested for the erroneous 'import a.b.bah.TheClass'. (A class or function can't be imported - only a module) 2003-01-24 Marius Gedminas * ide-integration/bike.vim: Show the line itself after finding references/definition. 2003-01-23 Marius Gedminas * bike/query/common.py: Fixed a trivial NameError in MatchFinder.visitGlobal(). 2003-01-23 Phil Dawes * bike/query/getTypeOf.py: Refactored and cleaned up the code in this module. 2003-01-22 Marius Gedminas * bike/query/common.py: Fixed another ValueError, this time on "from foo import bar" lines. 2003-01-20 Marius Gedminas * bike/query/common.py, bike/query/findDefinition.py, bike/query/findReferences.py: Fixed ValueError: list.index(x): x not in list" error caused by several visitFunction methods visiting their child nodes (argument names and default values) out-of-order. 2003-01-16 Marius Gedminas * bike/refactor/extractMethod.py: Now puts spaces after commas in generated code. 2003-01-15 Marius Gedminas * ide-integration/bike.vim: Better load failure diagnostics. 2003-01-14 Marius Gedminas * bike/bikefacade.py, bike/parsing/fastparserast.py, ide-integration/BicycleRepairMan_Idle.py: CRLF -> LF translation 2003-01-13 Marius Gedminas * bike/bikefacade.py: Added a function to check whether a given file was ever imported. * bike/test_bikefacade.py: Unit test for the above. * ide-integration/bike.vim: Added code to watch for modified files and reimport them into if they had been imported previously. 2003-01-13 Phil Dawes * bike/query/getReferencesToModule.py: Added Ender's getReferencesToModule module (and test module) 2003-01-10 Phil Dawes * bike/query/findDefinition.py: Added some find class attribute definition functionality 2003-01-09 Phil Dawes * bike/query/findDefinition.py: fixed bug where 'import' statements in fn scopes weren't being searched * ide-integration/bike.vim: Modified Marius' bike.vim vi integration to support new rename and findReferences calls. 2003-01-06 Phil Dawes * ide-integration/bikeemacs.py: Added fix to redisplay frame in emacs, and to test for mark correctly in emacs. 2003-01-05 Phil Dawes * AUTHORS: Added Mathew and Marius 2003-01-03 Phil Dawes * README.emacs: Added simple instructions for installing pymacs. * ide-integration/Pymacs-0.20: I'm going to ship pymacs-0.20 with bicyclerepairman. I've modified it a little to make installation easier. * ide-integration/bikeemacs.py: Moved bikeemacs back to its original place. 2003-01-02 Phil Dawes * bike/parsing/extended_ast.py: Cleaned this up a bit * bike/parsing/fastparser.py: Fixed bug reported by Mathew Yeates where more than one space between 'class' and the name foxed the parser. 2002-12-22 Phil Dawes * bike/query/findReferences.py: Started work on unified findReferences code. (uses findDefinition to compare matches) * ide-integration/emacs/bikeemacs.py: Rewrote emacs integration using the excellent pymacs package 2002-12-09 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Fixed findDefinition cosmetic bug * ide-integration/bike.el, bikeemacs: Added Syver Enstad's patch for allowing filenames with spaces 2002-12-06 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Completed support for findDefinition * ide-integration/bike.el: added code to handle the fact that if you select a region, the point is one greater than the end of the region and so misses it. 2002-12-03 Phil Dawes * bike/query/findDefinition.py: Added code to scan for other method matches after locating the 100% one 2002-12-02 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Added basic finddefinition support to idle * ide-integration/bikeemacs.py: Added finddefinition support to xemacs * bike/bikefacade.py: Exposed findDefinition through bikefacade * bike/query/findDefinition.py: Added new query interface for finding the definition of a reference, given its line/col position in a module. Just supports function, method, and class references for now. ---------------------- 0.7 --------------------------------------- 2002-11-27 Phil Dawes * ide-integration/bike.el: Fixed bug where saving buffers caused the new emacs protocol to get in a tangle. My solution was to add a disableMessages capability ---------------------- 0.7RC3 ------------------------------------ 2002-11-22 Phil Dawes * ide-integration/bikeemacs.py: Added acknowledgement protocol, to get round synchronisation problems in windows emacs 2002-11-20 Phil Dawes * bike/parsing: decommissioned addtypeinfo, typeinfo, tokenutils, tokenhandler, matchTokensToAST, doublelinkedlist, testdata * bike/query/getReferencesToClass.py: Fixed bug where 'from foo import *' would cause an exception if searching for a class called 'foo' * bike/*/test_*.py: Removed dependency on brmtransformer. 2002-11-15 Phil Dawes * bike/refactor/extractMethod.py: Rewrote extract method module to use all the new cool stuff (and operate or strings). Doesnt use linked lists, iterators, tokens, brmtransformer or any of that shite any more. 2002-11-11 Phil Dawes * bike/query/getTypeOf.py: Fixed bug where classscope was being searched if the name wasnt found in methodscope. This is incorrect because class scope isn't visible except through 'self' or a fully qualified classname. 2002-11-10 Phil Dawes * bike/query/common.py: Re-wrote MatchFinder to scan along the source code text when visiting ast nodes. This removes the need to match tokens in order to find the source code location of an ast node. ---------------------- 0.7RC1 ------------------------------------ 2002-11-01 Phil Dawes * bike/query/relationships.py: Added performance enhancement to getAllDerivedClasses. Now does string search on file before ast searching the classes. 2002-10-30 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Finished findReferencesByCoordinates support in idle. 2002-10-29 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Added trace console. Started integration of findReferencesByCoordinates into idle. * ide-integration/bike-emacs.py: Added xemacs support for getFullyQualifiedNameOfScope, getTypeOfExpression and findReferencesByCoordinates 2002-10-28 Phil Dawes * bike/bikefacade.py: Added getFullyQualifiedNameOfScope, getTypeOfExpression and findReferencesByCoordinates 2002-10-08 Phil Dawes * bike/bikefacade.py: changed promptForRename callback signature to be linenumbers and columnnumbers * bike/refactor/renameMethod.py: Added prompt functionality. 2002-10-02 Phil Dawes * bike/query/getTypeOf.py: Added functionality to handle scanning for types of references (needed by getReferencesToMethod) * bike/query/getReferencesToMethod.py: Implemented to use common.py stuff 2002-09-30 Phil Dawes * bike/query/common.py: Split out common query code into common.py 2002-09-27 Phil Dawes * bike/refactor/renameFunction.py: Now uses the getReferences stuff 2002-09-26 Phil Dawes * bike/transformer/WordRewriter.py: Split this class out of the renameClass module. * bike/transformer: Added new transformer package to contain code which rewrites the internal representation of the sourcecode * bike/bikefacade.py: Implemented locateNode to use the new fastparserast stuff. * bike/parsing/extended_ast.py: Added code to set fqn (fully qualified name) attributes on the fastparser ast 2002-09-25 Phil Dawes * bike/refactor/renameClass.py: Finished modification to use getReferencesToClass. 2002-09-06 Phil Dawes * bike/query/getTypeOf.py: Refactored getType() functionality out of ast classes and into a query module. * bike/parsing/fastparser.py: Added parsing of import and from lines to fastparser, and optimised a little. 2002-09-04 Phil Dawes * bike/refactor/renameClass.py: Started modification to use getReferencesToClass. 2002-08-30 Phil Dawes * bike/query/getReferencesToClass.py: New module which returns a sequence of references to a class * bike/parsing/fastparser.py: Wrote new parser which builds a tree of functions and classes, very quickly. 2002-08-14 Phil Dawes * bike/parsing/tokenutils.py: Fixed bug where attempting to tokenize small strings would result in attributeerror 2002-08-05 Phil Dawes * bike/logging.py: Logging module. Eventually will be replaced by import logging, but for now is a copy of the code by Vinay Sajip (to save people from having to download the module) * bike/parsing/brmtransformer.py: Added simple fix - tuple being concatinated to list was causing problems ---------------------- 0.6.7 ------------------------------------ 2002-07-31 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Added Canis fix for the bug which stopped brm from displaying the menu when starting idle via right-click file. * bike/bikefacade.py: Added Canis windows compatability fix for dealing with .pyw files 2002-07-25 Phil Dawes * bike/refactor/extractMethod.py: Fixed bug where extracting an expression from a line just below a block opener (e.g. if foo:) would cause a parse error. 2002-07-21 Phil Dawes * README.idle: Doc about problems on windows ---------------------- 0.6.6 ------------------------------------ 2002-07-19 Phil Dawes * bike/parsing/doublelinkedlist.py: Optimised to use a list rather than a class instance for each link element. 2002-07-17 Phil Dawes * bike/parsing/typeinfo.py: Fixed bug - package wasn't checking parent packages (and Root) for types. This meant that modules imported from higher up the tree weren't being found. * bike/parsing/extended_ast.py: Renamed 'Top' to 'Root' ---------------------- 0.6.5 ------------------------------------ 2002-07-15 Phil Dawes * ide-integration/bike.el: Minor bug fix 2002-07-12 Phil Dawes * ide-integration/bikeemacs.py: Added code so that it waits for buffer to be reloaded in emacs before sending the request to update the next one. * ide-integration/bike.el: Added bug fix - if prompt for rename was on file not loaded, emacs would send a message back to brm when it ran find-file, which buggered up the 'rename-this-method?' interaction. 2002-07-11 Phil Dawes * ide-integration/bike.el: Added support for GNU emacs * README.emacs: renamed from README.xemacs * 2002-07-09 Phil Dawes * bike/bikefacade.py: Added code to handle more windows filename brokenness 2002-07-05 Phil Dawes * bike/refactor/renameMethod.py: Added same optimisation to renameMethod. * bike/refactor/renameClass.py: Added optimisation: source string is scanned for classname before visiting ast. (with the lazy ast creation this means that files aren't parsed unless they contain the keyword (or are linked to the source via function call). * bike/parsing/extended_ast.py: Refactored .source member into .getSource() method and implemented so that tokens are synchronised with source if they're modified. 2002-07-03 Phil Dawes * bike/parsing/extended_ast.py: Added lazy parsing of ast when source.getModule() is called. * bike/parsing/typeinfo.py: Added PackageTypeInfo which deduces child types by interrogating the package rather than relying on children to register themselves in the typeinfo. This was required to facilitate lazy parsing of ast. (since parse would have had to be done to add module node to package). A lookup of a module now causes getModule() to be called on the sourcefile, thus triggering parse of source into ast. ---------------------- 0.6.4 ------------------------------------ 2002-06-27 Phil Dawes * bike/parsing/tokenutils.py: Added bug fix for superflous brackets. Basically, if brackets are unecessary (e.g. '(a).foo()') then they aren't in the parse tree but are in the token stream. This buggers up the token matching. This fix just ignores unmatching ')'s. It was the simplist thing that could possibly work, but may cause problems later - needs lots of testing. ---------------------- 0.6.3 ------------------------------------ 2002-06-26 Phil Dawes * bike/parsing/tokenhandler.py: Fixed bug where getattr renamed all occurences of the methodattr in the rest of the tokens 2002-06-21 Phil Dawes * bike/refactor/extractMethod.py: Fixed bug where commend before the extracted block would make brm think it should be extracting an expression rather than a block. * bike/parsing/addtypeinfo.py: Fixed the same bug in _getImportedType * bike/refactor/renameClass.py: Fixed bug where the module search routine in the 'From foo import *' code was using the parent scope as the search position. This doesnt work if 'from' is nested in a class since parent scope is a module, not a package. Added method _getParentPackage() to handle this correctly. 2002-06-13 Phil Dawes * bike/refactor/extractMethod.py: Fixed the 'loop' bug. extract method will now recognise variables assigned to by the extracted function and used in a loop by the calling function. 2002-06-12 Phil Dawes * bike/refactor/extractMethod.py: Lots of refactoring prior to fixing a bug. 2002-06-10 Phil Dawes * bike/parsing/doublelinkedlist.py: Rewrote 'insert' so that it doesnt move the iterator. Renamed old method to insertAndPositionIteratorOnNewElement. 2002-06-07 Phil Dawes * bike/refactor/extractMethod.py: Fixed the parse error when extracting code from an if...else... block. The problem was that the code below the extraction contained the 'else', but not the 'if'. This is hacked around by inserting an 'if 1: pass'. This seems to work, but I welcome a better way of handling this. * bike/parsing/output.py: Added code to ensure that indent never dips below 0. This fixes a bug in parsing during method extracts out of nested blocks. * bike/refactor/extractMethod.py: Fixed the 'don't know what type this isAssAttr(Name('item'), 'decl', 'OP_ASSIGN')' bug ---------------------- 0.6 -------------------------------------- 2002-05-28 Phil Dawes * ide-integration/test/*: Added manual test script for testing ide integration before a release * ide-integration/BicycleRepairMan_Idle.py: Now imports code into brm when loaded, or saved for the first time. * ide-integration/bike.el: Now imports all loaded python files into brm. Also checks to see if file was just updated to avoid superflous reloads. 2002-05-27 Phil Dawes * ide-integration/bike.el: Added menu for brm * bike/parsing/undo.py: Added an internal undo buffer size * ide-integration/BicycleRepairMan_Idle.py: Added generic exception reporting to idle * ide-integration/bikeemacs.py: Added generic exception reporting. All exceptions are handled and reported back to the user. 2002-05-24 Phil Dawes * ide-integration/BicycleRepairMan_Idle.py: Exposed undo to idle * ide-integration/bikeemacs.py: Exposed undo to emacs * bike/bikefacade.py: Exposed undo functionality to facade * bike/parsing/undo.py: Added undo functionality 2002-05-15 Phil Dawes * bike/refactor/test_extractMethod.py: Fixed bug in expression extracting code (which caused a parse exception with correct code) * bike/bikefacade.py: Added public interface ---------------------- 0.5 -------------------------------------- * ide-integration: Created new directory for integration modules 2002-05-14 Phil Dawes * bike/ui/BicycleRepairMan_Idle.py: Exposed extract function to idle * bike/ui/bikeemacs.py: Exposed extract function to emacs * bike/refactor/extractMethod.py: Added support for extracting expressions into methods (e.g. a = 1+2 -> a = self.oneplustwo()) Added support for extracting functions. 2002-05-13 Phil Dawes * bike/refactor/extractMethod.py: Extract Method pretty much finished. Now on with the testing... 2002-05-03 Phil Dawes * bike/ui/BicycleRepairMan_Idle.py: Added extract method support for idle 2002-05-02 Phil Dawes * bike/ui/bike.el: implemented extract-method for emacs * bike/bikefacade.py: Exposed extract-method to the outside world 2002-05-01 Phil Dawes * bike/refactor/extractMethod.py: Implemented simple extract method 2002-04-28 Phil Dawes * bike/parsing/matchTokensToAST.py: The 'new' addtokens. Instead of each AST node getting its own slice of tokens, there is only one token list and each node has 2 linked list iterators pointing to the start and end of its tokens in within it. Thus the process of calculating the position of these iterators is now called 'token matching' rather than 'adding tokens to ast'. Having only 1 token stream vastly simplifies things. There are no consistency problems when renaming or moving tokens, and no need to 'divide' the comments amongst the AST nodes. This enables one matching algorithm to be able to cater for all ast nodes without specialized logic. * bike/parsing/vector.py, bike/parsing/doublelinkedlist.py: containers which support the same bi-directional iterator interface. Used in refactoring the parsing module to use a linked list of tokens 2002-04-12 Phil Dawes * bike/bikefacade.py: Removed use of tokens ---------------------- 0.4.3 ------------------------------------ 2002-04-11 Phil Dawes * bike/ui/BicycleRepairMan_Idle.py: fixed import problem in windows * bike/parsing/load.py: Added code so that if you select a directory it will load all the modules and packages in it even if it isnt a package (i.e. doesnt have a __init__.py), but would recurse into non-package sub directories. * bike/ui/*: This is no longer a package, since the only 2 files are installed elsewhere. * setup.py: Made BicycleRepairMan_Idle.py a root level module. This means it can be used with idle by just adding [BicycleRepairMan_Idle] to your .idle file. 2002-04-06 Phil Dawes * bike/parsing/addtokens.py: Addtokens class now takes tokens in constructor. The addTokens function is renamed to addTokensToASTFromSrc. This will pave the way for Source nodes to hold tokens rather than the source as a string * bike/parsing/tokenhandler.py: Fixed bug which caused name ast node to not be tokenized * bike/parsing/load.py: Added check for __init__.py before recursing into sub directories. This means that non-package sub directories will not be recursed into. (i.e. the user must load those seperately) 2002-03-29 Phil Dawes * NEWS: consolidated all README-version files into one NEWS file * README*: updated documentation * bike/ui/BicycleRepairMan_Idle.py: Fixed another windows filename isnt normalized bug * bike/ui/bike.el: Added same functionality as for idle * bike/ui/BicycleRepairMan_Idle.py: Added load on save functionality - means each saved python file gets automatically imported into bicyclerepairman. 2002-03-27 Phil Dawes * bike/parsing/load.py: Added functionality to deduce the package of the loaded module/package by inspecting parent directories for __init__.py modules. This allows adding of new modules to an existing AST. 2002-03-24 Phil Dawes * README.idle: Added Evelyn's ammendment to doc ---------------------- 0.4.2 ------------------------------------ 2002-03-23 Phil Dawes * setup.py: Removed Icons stuff from setup * bike/ui/BicycleRepairMan_Idle.py: Fixed some bugs with idle integration in windows: - Ask if should rename dialogs dont take focus (which makes selection disappear in windows) - Filename normalizing means that filenames get compared correctly. 2002-03-21 Phil Dawes * bike/testutils.py: Changed the test package structure generation code to add __init__.py modules to the packages. This will be used to work with the new loader code (when I write it). 2002-03-20 Phil Dawes * bike/ui/BicycleRepairMan_Idle.py: Fixed bug which was stopping changed files from being reloaded on windows ---------------------- 0.4.1 ------------------------------------ 2002-03-19 Phil Dawes * bike/ui/BicycleRepairMan_Idle.py: Fixed bug where rename method prompts weren't visible to the user. * bike/parsing/addtypeinfo.py: Fixed a bug which meant that sourcenode wasnt getting added to default arguments, which caused brm to crash when attempting to tokenize these. ---------------------- 0.4 -------------------------------------- 2002-03-18 Phil Dawes * setup.py: Removed bikegui and pyxmi from the setup program. These are now deprecated. * bike/ui/BicycleRepairMan_Idle.py: Added the idle support module to cvs. * bike/parsing/tokenhandler.py: Simplified SimpleTokenHandler * bike/parsing/extended_ast.py: Removed ExtendedNode base class - it isnt needed (thanks Evelyn) * bike/parsing/tokenutils.py: fixed bug in _getOffsetOfTokensCorrespondingToParseTree which was including the NL token in the offset it returned. * bike/bikefacade.py: Changed signature of rename method callback. It now sends filename and coords as args rather than exposing the ast nodes to the client. 2002-03-07 Phil Dawes * bike/__init__.py: Moved bikefacade.py (from the ui package) into the bike package. This is now referenced from __init__.py so a client can do: import bike; ctx = bike.load("mypath"); ctx.renameMethod() * bike/parsing/typeinfo.py: ModuleTypeinfo.getTypeOf() functionality now uses the imported module *name* to check other modules, rather than a reference to the module itself. This is to allow module reloading. * bike/parsing/extended_ast.py: Added 'addtypeinfo' call to Source node. This is now done when the sourcenode is added to the parent node rather than being run once over the whole tree. This facilitates adding additional trees to the ast without having to run the whole addtypeinfo step again. * bike/parsing/addtypeinfo.py: Removed the subclasses stuff from the classtypeinfo functionality. Doing this at initial-parse time is too brittle, since a reload of a child source node could remove (or add) a subclass from a base class. Instead, subclasses are calculated at refactoring time. 2002-03-07 Phil Dawes * bike/parsing/addtypeinfo.py: Removed addfqn, addparentscopeToNode and addSourcenode to nodes functionality from the SourceModule, and integrated them with the first 'addtypeinfo' pass. This almost halves the initial parsing time. 2002-03-04 Phil Dawes * bike/refactor/renameMethod.py: Changed renameMethod signature to take a method fqn rather than class and method parameters. This allows the tests to take advantage of the same signature for renameMethod,renameFunction and renameClass. * bike/ui/bikeemacs.py: refactored to use the new bikefacade module * bike/ui/bikefacade.py: New module which provides easy interface into brm, for integrating into IDEs. * bike/parsing/test_addtypeinfo.py: Added recursion handling for things like a=a() (just catches the runtime error) 2002-02-28 Phil Dawes * bike/parsing/addtypeinfo.py: found a problem with lambdas - they take the reference from the outer scope. If the reference is a loop variable (e.g. for i in blah) then it is reassigned which knackers the closure. Solution - lambda i=i: doSomethingWith(i) * bike/ui/bikegui.py: removed reliance on tokens * bike/parsing/tokenhandler.py: added getStartCoords() and getEndCoords() functions. 2002-02-27 Phil Dawes * */setpath.py: code which insures pythonpath includes path to the bike module (used for running test scripts). This is imported by most modules. * bike/parsing/extended_ast.py: moved addParentScopeToNodes() functionality from addtypeinfo into Source class. * bike/parsing/addtypeinfo.py: For deduced types (e.g. types got from function calls), the closure of the type deducing function is put in the typeinfo rather than the type itself. The closure is then run at lookup. This facilitates lazy loading, and means source modules can be reloaded without invalidating the typeinfo objects. 2002-02-18 Phil Dawes * bike/ui/bikeemacs.py: Added reload source functionality * bike/ui/bike.el: Added support for renameMethod - prompts user for types that brm can't deduce 2002-02-06 Phil Dawes * bike/ui/bikeemacs.py: New support for emacs * bike/parsing/addtypeinfo.py: Fixed bug which was causing inheritance specs not to be renamed * bike/parsing/output.py: Added some code to preserve level of indent in multiline (comma ended) statements (e.g. function arguments, class inheritence args etc...). This really needs reworking into a general solution, but works 70% of the time for now. 2002-01-31 Phil Dawes * bike/parsing/output.py: Added comment indenting code which should preserve the indent level of comments 2002-01-28 Phil Dawes * bike/refactor/renameClass.py: Added functionality to rename the function and class ref in 'import a.b.theFunction and from a import theClass' type statements * bike/parsing/renameFunction.py: new refactoring * bike/parsing/tokenhandler.py: Added code to update the typeinfo objects of the parent scope and the Top node. This enables renames to be carried out more than once on the same ast * bike/parsing/addtypeinfo.py: Removed node.typeinfo.fqn. refactor code should now use node.fqn 2002-01-25 Phil Dawes * bike/parsing/tokenhandler.py: Added token functionality to Name. Removed the getPreceedingTokens() stuff - the getTokens() now returns all tokens including preceeding comments, tokens etc... getNodeTokens() returns just the tokens associated with the node. This is consistent across all the tokenhandler base classes. * bike/parsing/tokenutils.py: Fixed bug in _appendTokens where the src 'raise foo' would result in 'raise' being picked up when searching for 'foo' because both tokens are of the same type (name). 2002-01-24 Phil Dawes * bike/parsing/addtokens.py: Added code to class tokenizer to delegate tokens to the baseclass spec (i.e. bah in 'class foo(bah):'). * bike/parsing/tokenhandler.py: Class handler stuff to output tokens (see above). This means that renameClass now renames class refs in baseclass specs. 2002-01-23 Phil Dawes * bike/refactor/renameMethod.py: Fixed a bug which stopped classes that inherited from classes not in the ast from having method declarations renamed. 2002-01-22 Phil Dawes * bike/parsing/output.py: Added support for handling line breaks. i.e. if foo and \ bah: * bike/refactor/renameClass.py: Started new refactoring - renameClass * bike/parsing/addtypeinfo.py: removed 'typing indirect recursive functions causes stack overflow' bug * bike/refactor/renameMethod.py: Refactored the code so that the renameMethodReferences is done once, with a list of all the classes (in the hierarchy) to match. 2002-01-21 Phil Dawes * bike/refactor/renameMethod.py: Now renames methods on all related classes (base classes and sub classes). * doc/*: added some html documentation 2002-01-19 Phil Dawes * bike/refactor/renameMethod.py: Did some major refactoring and removed all the code which is functionally duplicated in the addtypeinfo module. * bike/parsing/addtypeinfo.py: Spruced up the type inference stuff. ModuleTypeInfo now handles from foo import * by storing a list of other modules to search. 2002-01-17 Phil Dawes * bike/parsing/output.py: Save now returns a list of the files modified 2002-01-16 Phil Dawes * bike/parsing/extended_ast.py: Added lazy tokenization. Source node now parses the source (rather than being handed the ast). 2002-01-15 Phil Dawes * bike/parsing/output.py: Added lazy saving. Files are only saved if source modified. * bike/testutils.py: Added new utility to find an ast node in a tree based on attributes of the node. e.g. getMatchingASTNode(ast,"Function",name="foo") 2002-01-14 Phil Dawes * bike/refactor/renameMethod.py: Added functionality to look for method and ref renames in sub classes 2002-01-11 Phil Dawes * bike/parsing/addtypeinfo.py: Added support for imports, import from and import as statements 2002-01-09 Phil Dawes * bike/parsing/tokenutils.py: Fixed bug where method call was split over 2 lines. The problem is that the tokenlist contains the NL, but the parsetree doesnt, so the matching wasnt working. See addtokens test_doesntBarfWhenMethodCallSplitOverTwoLines for details. * bike/parsing/brmtransformer.py: Added 2 classes which override Print and Println, and return the child nodes in the desired order (they are the wrong way round as they come out of ast.py). The print_stmt method returns these. * bike/parsing/output.py: Added code to handle spacing with commas and stream operators 2002-01-08 Phil Dawes * bike/refactor/test_renameMethod.py: Refactored tests so that BikeGUI can use them (through template method). Tests are now split into RenameMethodTests, RenameMethodReferenceTests_ImportsClass and RenameMethodReferenceTests_doesntImportClass. This is because bikegui 2002-01-07 Phil Dawes * bike/ui/bikegui.py: Added dialog to ask user if want to rename method reference (for cases where the type engine can't deduce the type of the instance) 2002-01-04 Phil Dawes * bike/ui/bikegui.py: Added a simple gui for renameMethod 2002-01-03 Phil Dawes * bike/parsing/load.py: Added functionality to strip of preceeding directories from package structures. (so if you load '/usr/local/python2.2/compiler', the root package is 'compiler') * bike/parsing/load.py: Added load_readonly(), which doesn't do the addtokens step. Also added code to enable load() to be called more than once, to add new files and packages to the tree. Moved ui messages into constants module 2002-01-02 Phil Dawes * bike/parsing/brmtransformer.py: removed addTokens() step from parse(), so that it must be called seperately. This is because it isn't complete, and imposes quite a performance penalty. (and it's not needed by pyxmi). Updated all the tests to reflect this. 2002-01-01 Phil Dawes * bike/parsing/addtypeinfo.py : Added getSubClasses() functionality to class typeinfo. 2001-12-30 Phil Dawes * bike/*: Fixed path bugs for windows platform. All tests run on win32 python now. 2001-12-29 Phil Dawes * bike/parsing/addtypeinfo.py: Added 'self' type to function typeinfo. This means renameMethod can handle self.theMethod() automatically. 2001-12-28 Phil Dawes * pyxmi: Wrote little tool to create xmi representation of python code. Suitable for loading into argouml. * bike/ui/bikecli.py: Added progress meter support. (also in addtokens.py, renameMethod.py, load.py and output.py 2001-12-26 Phil Dawes * bike/refactor/renameMethod.py: Fixed bug where function call returning instance resulted in crash. 'e.g. e().f()' 2001-12-24 Phil Dawes * bike/parsing/addtokens.py: Reworked addtokens scheme to 'get all tokens for node, and then pass them to children to take their nodes'. This makes for a much easier time processing tokens. 2001-12-16 Phil Dawes * bike/parsing/tokenhandler.py: Changed Getattr rename functionality to work when attribute token isn't second token of node tokenlist * bike/refactor/test_renameMethod.py: refactored renameMethod tests into generic abstract base class. These are now used by the ui package (rather than vice versa) * bike/ui/bikecli.py: Added code to tell you how many changes have been made, and advice on what might be wrong if this number is 0. 2001-12-15 Phil Dawes * bike/parsing/tokenhandler.py: Added getNodeTokens() implementation to miss out any preceeding cruft not picked up by addtoken functionality on previous nodes. 2001-12-14 Phil Dawes * bike/refactor/renameMethod.py: Changed renameMethodDefinition to work with nested methods (utilising the new typeinfo.fqn stuff). Removed findclass and findmethod. 2001-12-12 Phil Dawes * bike/ui/bikecli.py: Modified prompt message a bit. Added actual standard io stuff so that it works with a user input as well as through the test harness. * bike/refactor/renameMethod.py: Modified RenameMethodReferences to create a stack of scopes. The code uses the typeinfo from each scope to determine the type of references, and if it can't find the type there, it checks its own references deduced from the classname passed in. I.e. it assumes that the classname is a class, even if it can't find the definition, and renames instances derived from it. Also ammended the 'prompt user callback' code to only ask the user if the type engine doesnt know the type of the instance expression. (before now it was prompting if the type was not the same as the input classname) 2001-12-11 Phil Dawes * bike/ui/bikecli.py: Added code to handle prompting the user for whether the method reference should be renamed. * bike/refactor/renameMethod.py: Added code to prompt user when type of object instance isn't known 2001-12-08 Phil Dawes * bike/refactor.py: now takes advantage of typeinfo stuff to deduce types of references 2001-12-03 Phil Dawes * bike/parsing/addtypeinfo.py: New classes to deduce the types of references. Adds a typeinfo attribute to each scope ast node, which maps reference names to their types (currently as strings). 2001-11-26 Phil Dawes * bike/refactor/renameMethod.py: Added stack to maintain references to object instances while visiting package structure 2001-11-25 Phil Dawes * bike/refactor/renameMethod.py: Added support for 'from foo import *' * bike/ui/test_bikecli.py: Refactored tests so that all the single file tests are exercised in a package hierarchy context. 2001-11-24 Phil Dawes * bike/refactor/renameMethod.py: Added support to rename methods based on imported classes 2001-11-23 Phil Dawes * bike/refactor/renameMethod.py: Refactored code (took out RenameMethod class, since wasnt using instance state). Added support for compound references (a.b.c.foo) 2001-11-21 Phil Dawes * bike/parsing/load_application.py: removed. (see load.py instead) * bike/refactor/renameMethod.py: Added support for fully qualified classnames in _findClass. e.g. a.b.bah.foo. This means that the ui now supports renaming package nested methods (but not their references). 2001-11-20 Phil Dawes * bike/parsing: Added fixes to parser module to reflect changes in python 2.2 compiler module 2001-11-16 Phil Dawes * bike/parsing/output.py: new function 'save' saves an entire ast tree * bike/refactor/renameMethod.py: Adapted to take an ast rather than source file 2001-11-14 Phil Dawes * bike/parsing/load.py: Replacement for load_application.py using Juergen's pythius loader code. load() hierarchically loads a bunch of packages and modules into a big AST. * bike/parsing/extended_ast.py: Fleshed out Package and Top nodes to support hierarchical module ast 2001-11-08 Phil Dawes * bike/parsing/load_application.py: Fixed import visitor code to handle comma seperated imports. (i.e. import foo,bar). Added code to recognise imports via a from clause (i.e. from foo import bar) Removed Imports.py. Integrated code into load_application 2001-11-06 Phil Dawes * bike/parsing/load_application.py: Added load_application and Imports modules back into build. Started writing tests for them. 2001-11-05 Phil Dawes * bike/parsing/brmtransformer.py : Rewrote this to override the methods which create nodes at the root of the parse. This means that a few methods adding parser nodelists to ast nodes should cater for practically all the AST nodes. * bike/parsing/tokenhandler.py (GetattrTokenHander.renameAttr): added renameAttr method which renames the attribute in the object state, and in the corresponding tokens. This is a very brittle implementation, but will be fixed as new functionality is added. (it's the simplist thing right now) * bike/parsing/addtokens.py (AddTokens.visitGetattr): added token support for getattr * bike/parsing/tokenutils.py: Added getTokensPreceedingNodes, which takes a parsetree of nodes and returns all the tokens before the node represented by the parsetree. This is important because until there is an token-handling implementation of all the ast nodes, some tokens will be missed. By eating the tokens in between the implemented ast nodes, this ensures all the tokens will be outputted at the end. 2001-10-30 Phil Dawes Created new CVS module, which makes use of distutils much more uniform. * setup.py: Created distutils setup stript: 2001-10-25 Phil Dawes * parsing/output.py: Code to handle spacing in inline comments * refactor/renameMethod.py: Started code to handle references when object is created in same scope 2001-10-24 Phil Dawes * parsing/output.py: spaces in conditionals (a == b etc..) * parsing/tokenutils.py: Refactored code in TokenList to handle rearranging indent/dedent tokens so that whole-line comments are indented with the things they comment. The transformations are now done after the initial scan (slower, but easier to follow) 2001-10-23 Phil Dawes * parsing/output.py: ensured a.b not seperated by spaces. 2001-10-22 Phil Dawes * parsing/output.py: Code to seperate a=b with spaces (a = b) * ui/test_cli.py: Merged cli tests with renameMethod tests to ensure all tests work through the cli 2001-10-19 Phil Dawes * */testall.py: unified testing scripts so that all tests can be run from one place 2001-10-15 Phil Dawes * ui/cli.py: Added simple command line interface. 2001-10-14 Phil Dawes * parsing/output.py: Finished simple ast->string functionality 2001-10-13 Phil Dawes * testall.py: added master test script * parsing/tokenhandler.py (FunctionTokenHandler.rename): added rename a function and keep tokens in sync functionality 2001-10-12 Phil Dawes * parsing/output.py: started tokens -> string functionality 2001-10-07 Phil Dawes * refactor/renameMethod.py: very basic findClass, findMethod functionality. Needs refactoring into analysis package, and merging with existing code 2001-10-05 Phil Dawes * refactor/simplediff.py: wrote a simple diff utility to support writing top down tests. (i.e. do a renameMethod, then assert that the diff is correct) 2001-10-04 Phil Dawes * refactor/renameMethod.py: Got fedup with trying to secondguess what the parser module should do. Decided to do some top-down proper XP style development. Started with implementing the renameMethod story. 2001-10-01 Phil Dawes * parsing/test_addtokens.py: refactored tests so that they use brmtransformer to get a node, rather than mocking it. 2001-09-29 Phil Dawes * parsing/brmtransformer.py: Added nodelist to pass_stmt 2001-09-28 Phil Dawes * parsing/addtokens.py (AddTokens.visitModule): finished method * parsing/tokenutils.py (TokenConverter.getTokensUptoLastNewlineBeforeCode): method added 2001-09-27 Phil Dawes * parsing/addtokens.py: started visitmodule() impl * parsing/extended_ast.py: Added tokens support to Module 2001-09-26 Phil Dawes * parsing/test_addtokens.py Refactored tests. Made up better names. 2001-09-25 Phil Dawes Laptop broke. Continuing with an older version of the code (hence the gap in changelog) * parsing/tokenutils.py (TokenConverter.getTokensToColon): Added function which just gobbles tokens to the next colon. Refactored getTokensStartBlock() to use this instead of walking the nodelist, since I think all * parsing/addtokens.py (AddTokens.visitFunction): Now that function and class no longer need to pass in partial nodelists, the code is common. Refactored common code into visitCompoundNode(). (hence the gap in the changelog) * parsing/tokenhandler.py: removed the preceeding tokens code - you ain't gonna need it! * parsing/test_addtokens.py: changed visitclass tests to use mockobjects 2001-09-10 Phil Dawes * parsing/tokenhandler.py: * parsing/*: Integrated code with bike parsing package Split tokenhandler base classes into seperate module Merged brmast stuff into extended_ast package 2001-09-08 Phil Dawes * test_tokenutils.py: Added more unit tests 2001-09-06 Phil Dawes * test_tokenutils.py: Added more unit tests 2001-09-04 Phil Dawes * test_addtokens.py: Added some mock objects and tests 2001-08-31 Phil Dawes * test_tokenutils.py: beefed up the unit tests a bit 2001-08-29 Phil Dawes * brmtransformer.py: This module now just contains code to add the nodelist member to each ast node as the compiler generates it. This is so the code in addtokens has access to the original python parser nodes to generate tokens from. * addtokens.py: Moved the code to add the tokens to the ast nodes to this module. It now relies on there being a 'nodelist' member on each ast object, containing the original python parser nodelist. * brmast.py: Added hack to retrofit TokenHandler base class to existing compiler.ast classes. This means that we no longer have to include compiler classes with the distribution. Not sure if this is completely legal - doesn't work with jython. Removed stock Python compiler classes from src tree. 2001-08-27 Phil Dawes Set about reimplementing the token stuff - will now add tokens to ast objects *after* the nodes have been processed by the compiler module. 2001-08-22 Phil Dawes * tokenutils.py: Added support for tokenising strings as well as files. * test_tokenutils.py: removed testdata directory, and moved all the testdata into this module, now that the tokenutils stuff can take strings as well as files * brmtransformer.py: Renamed TransformerWithTokens to Transformer, now that it's in a seperate module. * brmast.py: Refactored the added token code out of ast module into this one. Created a seperate mixin called TokenHandler rather than sticking this code in the Node base class. Each class now re-inherits this baseclass. Unfortunately this means that I had to modify the transformer.py file to import brmast rather than ast. Need to think of a better way of doing this. 2001-08-21 Phil Dawes * brmtransformer.py: Created new brmtransformer module and refactored token code from transformer.Transformer into seperate class TransformerWithTokens. Refactored common code from funcdef() and classdef() into do_compound_stmt() * transformer.py: Now that all new code is factored out, reverted to original transformer module from Python-2.1/Tools/compiler. * tokenutils.py: Added code to alter DEDENT token line numbers when they are rearranged 2001-08-19 Phil Dawes * tokenutils.py: Added code to do token reordering to ensure that comments immediately before a block of code are placed after the previous block DEDENT token. 2001-08-15 Phil Dawes * transformer.py: tidied up code, and released prototype 2001-08-14 Phil Dawes * ast.py: Refactored management of tokens into base Node class. Removed code from Class, and moved node->token conversion to transformer.py * transformer.py: See above 2001-08-13 Phil Dawes * ast.py: Added support for tokens in 'Class' nodes. 'Class' uses nodes passed to convert to tokens using TokenConverter. 2001-08-10 Phil Dawes * tokenutils.py: Started TokenConverter class to convert python parser nodes to tokens. * test_tokenutils.py: unit tests for above ------------------ One year later... ------------------------- 2000-10-21 19:02 gwilder * context.py, refactor/.cvsignore, refactor/NewClass.py, refactor/Refactor.py, refactor/__init__.py: first refactoring: NewClass 2000-10-21 18:56 gwilder * kickstand/: test_HasFromStar.py, test_IsClass.py, test_IsGlobal.py, test_all.py, test_context.py, test_load_application.py, testdata/HasFromStar_tf1.py, testdata/HasFromStar_tf2.py, testdata/IsGlobal_tf1.py, testdata/load_application_tf1.expected, testdata/load_application_tf1.py, testdata/rf_addclass_tf1.expected, testdata/rf_addclass_tf1.py: more tests 2000-10-21 18:55 gwilder * analysis/: HasFromStar.py, IsClass.py, IsGlobal.py, Superclasses.py, TODO: more analysis functions 2000-10-21 18:38 gwilder * parsing/: .cvsignore, Imports.py, README, TODO, __init__.py, extended_ast.py, load_application.py: parse and load a whole app. 2000-10-13 19:32 gwilder * INSTALL, analysis/.cvsignore, analysis/IsClass.py, analysis/IsGlobal.py, analysis/README, analysis/TODO, analysis/__init__.py, assembly/genpy.py, kickstand/test_IsClass.py, kickstand/test_IsGlobal.py, kickstand/test_all.py, kickstand/testdata/.cvsignore, kickstand/testdata/IsClass_tf1.py, kickstand/testdata/IsGlobal_tf1.py: new analysis functions; install instructions; testsuite additions. 2000-09-28 00:59 eris * .cvsignore, CHANGES, README, assembly/.cvsignore, disassembly/.cvsignore, kickstand/.cvsignore, kickstand/test_common.py, kickstand/treefailtest.py, kickstand/treematchtest.py, kickstand/unequalvars.py, sprocket/.cvsignore, sprocket/common.py, sprocket/common.txt: added .cvsignore files in each dir got the unit test for sprocket/common.py working, added files for that test. 2000-09-06 01:21 jhermann * CHANGES, INSTALL, README, TODO: Added administrative files 2000-09-05 22:13 jhermann * assembly/__init__.py, disassembly/__init__.py, kickstand/__init__.py, sprocket/__init__.py: Made the inits non-empty 2000-09-05 22:11 jhermann * __init__.py: Minor correction 2000-09-01 18:33 jhermann * kickstand/__init__.py, kickstand/test_fsm.py, kickstand/test_regast.py, sprocket/__init__.py, sprocket/common.py, sprocket/fsm.py, sprocket/regast.py: Initial source checkin (2000-09-01) 2000-09-01 18:26 jhermann * .cvsignore, __init__.py, assembly/__init__.py, assembly/genpy.py, disassembly/__init__.py, disassembly/ast.py, disassembly/consts.py, disassembly/transformer.py: Initial source checkin (2000-09-01) 2000-09-01 18:22 jhermann * README: Additions by shae 2000-08-19 01:35 eris * README: new dir structure by snibril aka Jürgen Herrman first stab at a tree matcher in common.py 2000-08-01 01:44 jhermann * README: Added a readme dummy bicyclerepair-0.9/INSTALL0100644000076400007640000000074607451063731013376 0ustar pldpldBicycle Repair Man - a Python Refactoring Browser ================================================= $Id: INSTALL,v 1.4 2002/03/29 13:10:49 pdawes Exp $ ----------------------------------------------------------------------------- Bicycle Repair Man requires Python 2.2 and above. Run % python setup.py install Then look for at the relevant README file to integrate Bicycle Repair Man with your IDE. ----------------------------------------------------------------------------- bicyclerepair-0.9/NEWS0100644000076400007640000000535507720604137013045 0ustar pldpldMain highlights of each new version. See ChangeLog for a more detailed description of changes. Version 0.9 ----------- This version removes the requirement to load files into bicyclerepairman before refactoring. Instead, it searches the PYTHONPATH (and the path in which the file being queried is in). This allows 'findDefinition' queries to be made on references (e.g. classes, methods) where the definition is in the python library. Version 0.8 ----------- This release improves on the internal type-deduction engine to handle variables and attributes. To reflect this, 'rename' now works on variables and attributes, in addition to methods, functions and classes. This release also adds vim support (thanks to Marius Gedminas and Matt Yeates for this) Version 0.7 ----------- This release includes a totally re-written type querying engine, which is much faster and paves the way for new refactorings. It also adds the 'FindReferences' and 'FindDefinition' query to emacs and idle. Version 0.6 ----------- This release adds undo functionality to the mix, and beefs up the idle and emacs integration so that code is automatically imported into brm when you load a file into a buffer. Version 0.5 ----------- This release adds the ExtractMethod refactoring Version 0.4 ----------- This release adds support for IDLE (see README.idle), and fixes a few bugs. The CLI and GUI interfaces are now deprecated and have been removed from this release. Version 0.3 ----------- This release adds the RenameClass and RenameFunction refactorings. It also contains the initial xemacs integration functionality (see README.xemacs). Version 0.2 ----------- This release adds a simple GUI for renaming methods - run bikegui.py after installation. There's also some upgrades to pyxmi. It should now be able to generate xmi to model all generalizations (including cross-package ones). N.B. pyxmi.py is now called py2xmi.py. See changelog for reasons! Version 0.1 ----------- This is the first release of Bicycle Repair Man. It requires python version 2.2 and above. This version supports a partial implementation of the RenameMethod refactoring through a command line interface. It automatically renames the method and references to the method that it can deduce. It asks you about method references it can't deduce the instance type of. This software should be considered alpha, and may damage your source files - backup your sources before use! See INSTALL for installation and usage instructions. N.B. This package also contains pyxmi - a little python -> xmi tool I cobbled together out of the bicycle repair man parsing package. It generates an xmi file out of a source-file or package-structure, suitable for loading into the argouml tool. See http://argouml.tigris.org. bicyclerepair-0.9/README0100644000076400007640000000531107722213233013211 0ustar pldpldBicycle Repair Man - a Python Refactoring Browser ================================================= Copyright (c) 2000 by Shae Erisson Copyright (c) 2001-3 by Phil Dawes All rights reserved, see COPYING for details. $Id: README,v 1.7 2003/08/24 19:48:43 pdawes Exp $ ----------------------------------------------------------------------------- Bicycle Repair Man is the Python Refactoring Browser, helping Pythonistas everywhere glide over the gory details of refactoring their code. Watch him extract jumbled code into well ordered classes. Gasp, as he renames all occurrences of a method. Thank You, Bicycle Repair Man! execute ./testall.py to run all the tests see INSTALL for installation instructions (uses distutils). see README.idle, README.emacs etc.. for instructions on how to integrate bicyclerepairman into supported IDEs. ----------------------------------------------------------------------------- What's Python? Python is a programming language. To find out more about it, go to the Python Homepage at http://www.python.org/ What's a Refactoring Browser? A Refactoring Browser is an editor that automates Refactorings. The first Refactoring Browser was written by Dr. Don Roberts and Dr. John Brant at the University of Illinois in Urbana-Champagne. Dr. Don Roberts wrote his Ph.D. thesis on the design and implementation of the Refactoring Browser. For more detail, read the aforementioned thesis at http://st-www.cs.uiuc.edu/~droberts/thesis.pdf What's a Refactoring? A Refactoring is a behaviour preserving change to source code. Some Refactorings are RenameVariable, RenameClass, RenameMethod, PullUpMethod, and PushDownVariable. Lots of people say it's very easy to just type a different name in where your class, method, or variable is defined. That's not always a refactoring though. The Refactoring Browser is smart enough to rename every reference to your class, method or variable. If you've ever renamed a variable and broken classes in widely scattered parts of your system, you might be happier using a Refactoring Browser. A Refactoring Browser operates on any of method, function, class, or variable. It can add, delete, rename, move up down or sideways, inline and abstract. There are some operations that are specific to one of the three types, such as abstracting a variable into accessors, or turing several lines of code into a separate method. For more information on Refactoring, check out the websites of Martin Fowler at http://www.martinfowler.com/ and his Refactoring Site at http://www.refactoring.com/ . Why Bicycle Repair Man? The Bicycle Repair Man was a superhero in a Monty Python skit, his special power was repairing bicycles. bicyclerepair-0.9/README.emacs0100644000076400007640000000632107671436114014312 0ustar pldpldInstructions for running Bicycle Repair Man through emacs/xemacs ---------------------------------------------------------------- N.B. You need xemacs / emacs 21 or above. The emacs integration utilises the excellent 'Pymacs' package, written by François Pinard, which integrates python with emacs. A copy of this software is included with this package. There are 3 steps to installing bicyclerepairman for emacs: 1) Install the base bicyclerepairman package 2) Install the Pymacs package (if you haven't already got it) 3) Modify your .emacs to active the bicyclerepairman functionality See the sections below for instructions on doing each of these. WINDOWS USERS: You need to have both the python executable and the scripts directory (e.g. c:\Python22/Scripts) in your path for Bicyclerepairman to work. There are a couple of niggles with brm/emacs on windows. See the comments at the end of this file. 1) Installation of Base Bicyclerepairman: ----------------------------------------- - install bicyclerepair man as per INSTALL 2) Installation of Pymacs: -------------------------- (You can skip this if you already have pymacs installed. - Go to the ide-integration/Pymacs-0.20 directory - Run python setup.py install - Run python setup-emacs.py -l OR python setup-emacs.py -E xemacs -l Depending on your version of emacs. - Add the following into your .emacs or .xemacs/init.el: ;; pymacs (autoload 'pymacs-load "pymacs" nil t) (autoload 'pymacs-eval "pymacs" nil t) (autoload 'pymacs-apply "pymacs") (autoload 'pymacs-call "pymacs") - Check that it has installed correctly: (Taken from the pymacs README) To check that `pymacs.el' is properly installed, start Emacs and give it the command `M-x load-library RET pymacs': you should not receive any error. To check that `pymacs.py' is properly installed, start an interactive Python session (e.g. from a command shell) and type `from Pymacs import lisp': you should not receive any error. To check that `pymacs-services' is properly installed, type `pymacs-services' in a shell; you should then get a line ending with "(pymacs-version VERSION)". Press ctrl-c to exit. If you have any problems, consult the README file included with the pymacs distribution. N.B. I renamed the setup script to setup-emacs.py to make it more intuitive and easier for windows users. I've also added a pymacs-services.bat file to allow it to run on windows. 3) Activating the Bike/Emacs integration ---------------------------------------- Add the following to your .emacs or .xemacs/init.el, after the pymacs stuff: (pymacs-load "bikeemacs" "brm-") (brm-init) You need to be using python-mode for the bicyclerepairman menu to appear. If you haven't already, enable this with: (autoload 'python-mode "python-mode" "Python editing mode." t) (setq auto-mode-alist (cons '("\\.py$" . python-mode) auto-mode-alist)) Usage: ------ Load a python file into emacs. A BicycleRepairMan menu should appear. Windows GNU-Emacs users ----------------------- The load dialog in windows GNU-Emacs doesn't seem to allow selection of directories. If this is the case for you, use 'M-x brm-load' to import a package hierarchy. bicyclerepair-0.9/README.idle0100644000076400007640000000203007720604137014125 0ustar pldpldBicycleRepairMan_Idle.py is the name of the idle integration module. Installation on Python 2.3 or idlefork -------------------------------------- 1) Install Bicycle Repair Man (see INSTALL) 2) Add the following to your ~/.idlerc/config-extensions.cfg: (NOTE - I had to put this in my /idlelib/config-extensions.def for it to work correctly) [BicycleRepairMan_Idle] enable=1 trace=0 [BicycleRepairMan_Idle_cfgBindings] brm-find-references= brm-find-definition= brm-rename= brm-extract-method= brm-undo= Python sourcefile editor windows will now have a 'BicycleRepairMan' menu. Installation on python 2.2 version of idle ------------------------------------------ 1) Install Bicycle Repair Man (see INSTALL) 2) Add the following line to your /config.txt or ~/.idle: [BicycleRepairMan_Idle] Python sourcefile editor windows will now have a 'BicycleRepairMan' menu. Caveats ------- Sometimes dialogs get lost behind windows. If things seem to have paused in a rename-method, try looking for a dialog. bicyclerepair-0.9/README.vim0100664000076400007640000000013107671436704014015 0ustar pldpldInstructions for installing bike.vim are in the file itself. See ide-integration/bike.vimbicyclerepair-0.9/setup.py0100755000076400007640000000123010036706653014047 0ustar pldpld#!/usr/bin/env python import sys import os,glob from distutils.core import setup # check version if sys.version_info[0] < 2 or sys.version_info[0] == 2 and sys.version_info[1] < 2: print "Python versions below 2.2 not supported" sys.exit(0) setup(name="bicyclerepair", version="0.9", description="Bicycle Repair Man, the Python refactoring tool", maintainer="Phil Dawes", maintainer_email="pdawes@users.sourceforge.net", url="http://bicyclerepair.sourceforge.net", packages=['','bike','bike.refactor','bike.parsing','bike.query','bike.transformer'], package_dir = {'bike':'bike','':'ide-integration'} ) bicyclerepair-0.9/testall.py0100755000076400007640000000120607573161014014360 0ustar pldpld#!/usr/bin/env python import sys,os # check version if sys.version_info[0] < 2 or sys.version_info[0] == 2 and sys.version_info[1] < 2: print "Python versions below 2.2 not supported" sys.exit(0) if not os.path.abspath(".") in sys.path: sys.path.append(os.path.abspath(".")) from bike import logging from bike.test_testutils import * from bike.parsing.testall import * from bike.query.testall import * from bike.refactor.testall import * from bike.testall import * if __name__ == "__main__": from bike import logging logging.init() log = logging.getLogger("bike") log.setLevel(logging.WARN) unittest.main() bicyclerepair-0.9/PKG-INFO0100664000076400007640000000042610036707253013434 0ustar pldpldMetadata-Version: 1.0 Name: bicyclerepair Version: 0.9 Summary: Bicycle Repair Man, the Python refactoring tool Home-page: http://bicyclerepair.sourceforge.net Author: Phil Dawes Author-email: pdawes@users.sourceforge.net License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN