# HG changeset patch # User Steve Losh # Date 1290454341 18000 # Node ID cfd5d659d73704d22785e22ed32929aca1e02405 # Parent 8bd0f75b7689de4f25f2dbf647fef4acb9be9aec vim: sadness diff -r 8bd0f75b7689 -r cfd5d659d737 vim/.vimrc --- a/vim/.vimrc Tue Nov 16 17:53:55 2010 -0500 +++ b/vim/.vimrc Mon Nov 22 14:32:21 2010 -0500 @@ -272,8 +272,10 @@ map :TlistToggle map T :!/usr/local/bin/ctags --exclude='**/ckeditor' -R . $(test -f .venv && echo ~/lib/virtualenvs/`cat .venv`) -" Rope -source $HOME/.vim/sadness/ropevim/rope.vim +" Rope and Bike. +let g:bike_exceptions=1 +source $HOME/.vim/sadness/sadness.vim + let ropevim_enable_shortcuts = 0 let ropevim_guess_project = 1 noremap rr :RopeRename diff -r 8bd0f75b7689 -r cfd5d659d737 vim/colors/molokai.vim --- a/vim/colors/molokai.vim Tue Nov 16 17:53:55 2010 -0500 +++ b/vim/colors/molokai.vim Mon Nov 22 14:32:21 2010 -0500 @@ -58,7 +58,7 @@ hi Macro guifg=#C4BE89 gui=italic hi SpecialKey guifg=#66D9EF gui=italic -hi MatchParen guifg=#E4E400 guibg=NONE gui=bold +hi MatchParen guifg=#E4E400 guibg=#232728 gui=bold hi ModeMsg guifg=#E6DB74 hi MoreMsg guifg=#E6DB74 hi Operator guifg=#F92672 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/AUTHORS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/AUTHORS Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,24 @@ +Authors +------- + +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. + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/COPYING Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,32 @@ +COPYRIGHT 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. + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ChangeLog --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ChangeLog Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,2034 @@ +2004-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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/INSTALL --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/INSTALL Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,17 @@ +Bicycle 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. + +----------------------------------------------------------------------------- diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/NEWS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/NEWS Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,98 @@ +Main 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. diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/PKG-INFO Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,10 @@ +Metadata-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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/README Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,70 @@ +Bicycle 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. + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/README.emacs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/README.emacs Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,115 @@ +Instructions 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. diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/README.idle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/README.idle Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,47 @@ +BicycleRepairMan_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. diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/README.vim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/README.vim Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,2 @@ +Instructions for installing bike.vim are in the file itself. +See ide-integration/bike.vim \ No newline at end of file diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/__init__.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,10 @@ +# The root bicyclerepairman package +# do: +# --------------------- +# import bike +# ctx = bike.load() +# --------------------- +# to instantiate a bicyclerepairman context object + + +from bikefacade import init, NotAPythonModuleOrPackageException, CouldntLocateASTNodeFromCoordinatesException, UndoStackEmptyException diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/bikefacade.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/bikefacade.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,274 @@ +import 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/globals.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/globals.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,6 @@ +try: + True = 1 + False = 0 +except: + pass + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/log.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/log.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,8 @@ +import sys +class SilentLogger: + def write(*args): + pass + +progress = SilentLogger() +warning = SilentLogger() +#warning = sys.stderr diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/logging.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/logging.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1995 @@ +#! /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__ diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/mock.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/mock.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,143 @@ +# +# (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) + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/__init__.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,3 @@ +#from addtypeinfo import addtypeinfo +#from load import load +#from brmtransformer import parse diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/constants.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/constants.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,3 @@ +messages={"PARSING":"Parsing","ADDTYPEINFO":"Deducing Type Information"} + +MAXCALLDEPTH = 10 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/fastparser.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/fastparser.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,70 @@ +#!/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 + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/fastparserast.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/fastparserast.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,327 @@ +from __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 + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/load.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/load.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,94 @@ +from 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 + + + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/newstuff.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/newstuff.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,114 @@ +from __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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/parserutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/parserutils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,194 @@ +from __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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/pathutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/pathutils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,165 @@ + +# 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 + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/setpath.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/setpath.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,5 @@ +import 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("../..")) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_fastparser.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_fastparser.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,160 @@ +#!/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() + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_fastparserast.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_fastparserast.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,151 @@ +#!/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() + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_load.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_load.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,18 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_newstuff.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_newstuff.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,284 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_parserutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_parserutils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,135 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_pathutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_pathutils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,128 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_utils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,41 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/test_visitor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/test_visitor.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,114 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/testall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/testall.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,18 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/utils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,30 @@ +# 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 "" diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/parsing/visitor.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/parsing/visitor.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,49 @@ +from __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) + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/__init__.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1 @@ +# \ No newline at end of file diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/common.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,333 @@ +from __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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/findDefinition.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/findDefinition.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,277 @@ +from __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 + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/findReferences.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/findReferences.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,224 @@ +from __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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/getAllRelatedClasses.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/getAllRelatedClasses.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,47 @@ + + +""" + +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 + +""" diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/getPackageDependencies.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/getPackageDependencies.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +# fileInPackage is the filename of a file in the package hierarchy +def getPackageDependencies(fileInPackage): + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/getReferencesToModule.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/getReferencesToModule.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,62 @@ +from __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) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/getTypeOf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/getTypeOf.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,377 @@ +# 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/relationships.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/relationships.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,26 @@ +# 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 + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/setpath.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/setpath.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,5 @@ +import 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("..")) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_common.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,122 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_findDefinition.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_findDefinition.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,632 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_findReferences.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_findReferences.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,516 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_getPackageDependencies.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_getPackageDependencies.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,10 @@ +#!/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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_getReferencesToClass.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_getReferencesToClass.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,280 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_getReferencesToMethod.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_getReferencesToMethod.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,194 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_getReferencesToModule.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_getReferencesToModule.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,179 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_getTypeOf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_getTypeOf.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,191 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/test_relationships.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/test_relationships.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,94 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/query/testall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/query/testall.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,15 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/__init__.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1 @@ + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/extractMethod.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/extractMethod.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,363 @@ +import re +import compiler +from bike.parsing import visitor +from bike.query.common import getScopeForLine +from bike.parsing.parserutils import generateLogicalLines, \ + makeLineParseable, maskStringsAndRemoveComments +from parser import ParserError +from bike.parsing.fastparserast import Class +from bike.transformer.undo import getUndoStack +from bike.refactor.utils import getTabWidthOfLine, getLineSeperator, \ + reverseCoordsIfWrongWayRound +from bike.transformer.save import queueFileToSave +from bike.parsing.load import getSourceNode +TABSIZE = 4 + +class coords: + def __init__(self, line, column): + self.column = column + self.line = line + def __str__(self): + return "("+str(self.column)+","+str(self.line)+")" + +commentRE = re.compile(r"#.*?$") + +class ParserException(Exception): pass + +def extractMethod(filename, startcoords, endcoords, newname): + ExtractMethod(getSourceNode(filename), + startcoords, endcoords, newname).execute() + +class ExtractMethod(object): + def __init__(self,sourcenode, startcoords, endcoords, newname): + self.sourcenode = sourcenode + + startcoords, endcoords = \ + reverseCoordsIfWrongWayRound(startcoords,endcoords) + + self.startline = startcoords.line + self.endline = endcoords.line + self.startcol = startcoords.column + self.endcol= endcoords.column + + self.newfn = NewFunction(newname) + + self.getLineSeperator() + self.adjustStartColumnIfLessThanTabwidth() + self.adjustEndColumnIfStartsANewLine() + self.fn = self.getFunctionObject() + self.getRegionToBuffer() + #print "-"*80 + #print self.extractedLines + #print "-"*80 + self.deduceIfIsMethodOrFunction() + + def execute(self): + self.deduceArguments() + getUndoStack().addSource(self.sourcenode.filename, + self.sourcenode.getSource()) + srclines = self.sourcenode.getLines() + newFnInsertPosition = self.fn.getEndLine()-1 + self.insertNewFunctionIntoSrcLines(srclines, self.newfn, + newFnInsertPosition) + self.writeCallToNewFunction(srclines) + + src = "".join(srclines) + queueFileToSave(self.sourcenode.filename,src) + + def getLineSeperator(self): + line = self.sourcenode.getLines()[self.startline-1] + linesep = getLineSeperator(line) + self.linesep = linesep + + def adjustStartColumnIfLessThanTabwidth(self): + tabwidth = getTabWidthOfLine(self.sourcenode.getLines()[self.startline-1]) + if self.startcol < tabwidth: self.startcol = tabwidth + + def adjustEndColumnIfStartsANewLine(self): + if self.endcol == 0: + self.endline -=1 + nlSize = len(self.linesep) + self.endcol = len(self.sourcenode.getLines()[self.endline-1])-nlSize + + + def getFunctionObject(self): + return getScopeForLine(self.sourcenode,self.startline) + + + def getTabwidthOfParentFunction(self): + line = self.sourcenode.getLines()[self.fn.getStartLine()-1] + match = re.match("\s+",line) + if match is None: + return 0 + else: + return match.end(0) + + # should be in the transformer module + def insertNewFunctionIntoSrcLines(self,srclines,newfn,insertpos): + tabwidth = self.getTabwidthOfParentFunction() + + while re.match("\s*"+self.linesep,srclines[insertpos-1]): + insertpos -= 1 + + srclines.insert(insertpos, self.linesep) + insertpos +=1 + + fndefn = "def "+newfn.name+"(" + + if self.isAMethod: + fndefn += "self" + if newfn.args != []: + fndefn += ", "+", ".join(newfn.args) + else: + fndefn += ", ".join(newfn.args) + + fndefn += "):"+self.linesep + + + srclines.insert(insertpos,tabwidth*" "+fndefn) + insertpos +=1 + + tabwidth += TABSIZE + + + if self.extractedCodeIsAnExpression(srclines): + assert len(self.extractedLines) == 1 + + fnbody = [tabwidth*" "+ "return "+self.extractedLines[0]] + + + else: + fnbody = [tabwidth*" "+line for line in self.extractedLines] + if newfn.retvals != []: + fnbody.append(tabwidth*" "+"return "+ + ", ".join(newfn.retvals) + self.linesep) + + for line in fnbody: + srclines.insert(insertpos,line) + insertpos +=1 + + + def writeCallToNewFunction(self, srclines): + startline = self.startline + endline = self.endline + startcol = self.startcol + endcol= self.endcol + + fncall = self.constructFunctionCallString(self.newfn.name, self.newfn.args, + self.newfn.retvals) + + self.replaceCodeWithFunctionCall(srclines, fncall, + startline, endline, startcol, endcol) + + + def replaceCodeWithFunctionCall(self, srclines, fncall, + startline, endline, startcol, endcol): + if startline == endline: # i.e. extracted code part of existing line + line = srclines[startline-1] + srclines[startline-1] = self.replaceSectionOfLineWithFunctionCall(line, + startcol, endcol, fncall) + else: + self.replaceLinesWithFunctionCall(srclines, startline, endline, fncall) + + + def replaceLinesWithFunctionCall(self, srclines, startline, endline, fncall): + tabwidth = getTabWidthOfLine(srclines[startline-1]) + line = tabwidth*" " + fncall + self.linesep + srclines[startline-1:endline] = [line] + + + + def replaceSectionOfLineWithFunctionCall(self, line, startcol, endcol, fncall): + line = line[:startcol] + fncall + line[endcol:] + if not line.endswith(self.linesep): + line+=self.linesep + return line + + + + def constructFunctionCallString(self, fnname, fnargs, retvals): + fncall = fnname + "("+", ".join(fnargs)+")" + if self.isAMethod: + fncall = "self." + fncall + + if retvals != []: + fncall = ", ".join(retvals) + " = "+fncall + return fncall + + + def deduceArguments(self): + lines = self.fn.getLinesNotIncludingThoseBelongingToChildScopes() + + # strip off comments + lines = [commentRE.sub(self.linesep,line) for line in lines] + extractedLines = maskStringsAndRemoveComments("".join(self.extractedLines)).splitlines(1) + + linesbefore = lines[:(self.startline - self.fn.getStartLine())] + linesafter = lines[(self.endline - self.fn.getStartLine()) + 1:] + + # split into logical lines + linesbefore = [line for line in generateLogicalLines(linesbefore)] + extractedLines = [line for line in generateLogicalLines(extractedLines)] + linesafter = [line for line in generateLogicalLines(linesafter)] + + if self.startline == self.endline: + # need to include the line code is extracted from + line = generateLogicalLines(lines[self.startline - self.fn.getStartLine():]).next() + linesbefore.append(line[:self.startcol] + "dummyFn()" + line[self.endcol:]) + assigns = getAssignments(linesbefore) + fnargs = getFunctionArgs(linesbefore) + candidateArgs = assigns + fnargs + refs = getVariableReferencesInLines(extractedLines) + self.newfn.args = [ref for ref in refs if ref in candidateArgs] + + assignsInExtractedBlock = getAssignments(extractedLines) + usesAfterNewFunctionCall = getVariableReferencesInLines(linesafter) + usesInPreceedingLoop = getVariableReferencesInLines( + self.getPreceedingLinesInLoop(linesbefore,line)) + self.newfn.retvals = [ref for ref in usesInPreceedingLoop+usesAfterNewFunctionCall + if ref in assignsInExtractedBlock] + + def getPreceedingLinesInLoop(self,linesbefore,firstLineToExtract): + if linesbefore == []: return [] + tabwidth = getTabWidthOfLine(firstLineToExtract) + rootTabwidth = getTabWidthOfLine(linesbefore[0]) + llines = [line for line in generateLogicalLines(linesbefore)] + startpos = len(llines)-1 + loopTabwidth = tabwidth + for idx in range(startpos,0,-1): + line = llines[idx] + if re.match("(\s+)for",line) is not None or \ + re.match("(\s+)while",line) is not None: + candidateLoopTabwidth = getTabWidthOfLine(line) + if candidateLoopTabwidth < loopTabwidth: + startpos = idx + return llines[startpos:] + + + + + + + def getRegionToBuffer(self): + startline = self.startline + endline = self.endline + startcol = self.startcol + endcol= self.endcol + + + self.extractedLines = self.sourcenode.getLines()[startline-1:endline] + + match = re.match("\s*",self.extractedLines[0]) + tabwidth = match.end(0) + + self.extractedLines = [line[startcol:] for line in self.extractedLines] + + # above cropping can take a blank line's newline off. + # this puts it back + for idx in range(len(self.extractedLines)): + if self.extractedLines[idx] == '': + self.extractedLines[idx] = self.linesep + + if startline == endline: + # need to crop the end + # (n.b. if region is multiple lines, then whole lines are taken) + self.extractedLines[-1] = self.extractedLines[-1][:endcol-startcol] + + if self.extractedLines[-1][-1] != '\n': + self.extractedLines[-1] += self.linesep + + def extractedCodeIsAnExpression(self,lines): + if len(self.extractedLines) == 1: + charsBeforeSelection = lines[self.startline-1][:self.startcol] + if re.match("^\s*$",charsBeforeSelection) is not None: + return 0 + if re.search(":\s*$",charsBeforeSelection) is not None: + return 0 + return 1 + return 0 + + def deduceIfIsMethodOrFunction(self): + if isinstance(self.fn.getParent(),Class): + self.isAMethod = 1 + else: + self.isAMethod = 0 + + +# holds information about the new function +class NewFunction: + def __init__(self,name): + self.name = name + + +# lines = list of lines. +# Have to have strings masked and comments removed +def getAssignments(lines): + class AssignVisitor: + def __init__(self): + self.assigns = [] + + def visitAssTuple(self, node): + for a in node.nodes: + if a.name not in self.assigns: + self.assigns.append(a.name) + + def visitAssName(self, node): + if node.name not in self.assigns: + self.assigns.append(node.name) + + def visitAugAssign(self, node): + if isinstance(node.node, compiler.ast.Name): + if node.node.name not in self.assigns: + self.assigns.append(node.node.name) + + assignfinder = AssignVisitor() + for line in lines: + doctoredline = makeLineParseable(line) + try: + ast = compiler.parse(doctoredline) + except ParserError: + raise ParserException("couldnt parse:"+doctoredline) + visitor.walk(ast, assignfinder) + return assignfinder.assigns + + +# lines = list of lines. +# Have to have strings masked and comments removed +def getFunctionArgs(lines): + if lines == []: return [] + + class FunctionVisitor: + def __init__(self): + self.result = [] + def visitFunction(self, node): + for n in node.argnames: + if n != "self": + self.result.append(n) + fndef = generateLogicalLines(lines).next() + doctoredline = makeLineParseable(fndef) + try: + ast = compiler.parse(doctoredline) + except ParserError: + raise ParserException("couldnt parse:"+doctoredline) + return visitor.walk(ast, FunctionVisitor()).result + + + +# lines = list of lines. Have to have strings masked and comments removed +def getVariableReferencesInLines(lines): + class NameVisitor: + def __init__(self): + self.result = [] + def visitName(self, node): + if node.name not in self.result: + self.result.append(node.name) + reffinder = NameVisitor() + for line in lines: + doctoredline = makeLineParseable(line) + try: + ast = compiler.parse(doctoredline) + except ParserError: + raise ParserException("couldnt parse:"+doctoredline) + visitor.walk(ast, reffinder) + return reffinder.result diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/extractVariable.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/extractVariable.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,32 @@ +from 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())) + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/inlineVariable.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/inlineVariable.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,94 @@ +from 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())) + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/moveToModule.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/moveToModule.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,148 @@ +import 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 + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/rename.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/rename.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,19 @@ +from 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 + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/setpath.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/setpath.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,5 @@ +import 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("../..")) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_extractMethod.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_extractMethod.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,791 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_extractVariable.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_extractVariable.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,40 @@ +#!/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() + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_inlineVariable.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_inlineVariable.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,114 @@ +#!/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() + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_moveToModule.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_moveToModule.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,241 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_rename.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_rename.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,35 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_renameClass.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_renameClass.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,257 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_renameFunction.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_renameFunction.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,118 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/test_renameMethod.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/test_renameMethod.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,710 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/testall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/testall.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,13 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/refactor/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/refactor/utils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,24 @@ +import 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/setpath.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/setpath.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,5 @@ +import 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("..")) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/test_bikefacade.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/test_bikefacade.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,377 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/test_testutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/test_testutils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,9 @@ +#!/usr/bin/env python +import setpath +import unittest +from testutils import* +import testdata +import sys + +if __name__ == "__main__": + unittest.main() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/testall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/testall.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import setpath +from test_bikefacade import * + +if __name__ == "__main__": + unittest.main() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/testdata.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/testdata.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,17 @@ +TheClass = """ +class TheClass: + def theMethod(self): + pass + def differentMethod(self): + pass + +class DifferentClass: + def theMethod(self): + pass +""" + + +Function = """ +def theFunction(): + pass +""" diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/testutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/testutils.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,170 @@ +from 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) + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/transformer/WordRewriter.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/transformer/WordRewriter.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,48 @@ +from 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]) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/transformer/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/transformer/__init__.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,4 @@ +""" +Package containing modules which transform the internal +representation of the sourcecode. +""" diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/transformer/save.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/transformer/save.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,36 @@ +from 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/transformer/setpath.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/transformer/setpath.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,5 @@ +import 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("..")) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/transformer/testall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/transformer/testall.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +#from test_undo import * + +if __name__ == "__main__": + unittest.main() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/bike/transformer/undo.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/bike/transformer/undo.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,50 @@ +from 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] diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/BicycleRepairMan_Idle.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/BicycleRepairMan_Idle.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,422 @@ +# 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") + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/ChangeLog --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/ChangeLog Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,417 @@ +2002-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. + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/ChangeLog-rebox --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/ChangeLog-rebox Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,146 @@ +2002-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. diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/Makefile Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,40 @@ +# 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/PKG-INFO Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,10 @@ +Metadata-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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/.cvsignore Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1 @@ +*.pyc diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/__init__.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,30 @@ +#!/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' diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/pymacs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/pymacs.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,587 @@ +#!/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:]) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/rebox.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/Pymacs/rebox.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1235 @@ +#!/usr/bin/env python +# Copyright © 1991-1998, 2000, 2002 Progiciels Bourbeau-Pinard inc. +# François Pinard , 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:]) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/README Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1030 @@ +* 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/README.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/README.html Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,11 @@ + + + + %(title)s + +%(text)s + + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/THANKS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/THANKS Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,24 @@ +Pymacs 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/THANKS-rebox --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/THANKS-rebox Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,8 @@ +`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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/TODO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/TODO Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,34 @@ +* 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. diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/pymacs-services --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/pymacs-services Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,27 @@ +#!/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:]) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/pymacs-services.bat --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/pymacs-services.bat Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,1 @@ +@python "%~dpn0" %* diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/pymacs.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/pymacs.el Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,631 @@ +;;; 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) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/rebox --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/rebox Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,11 @@ +#!/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:])) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/setup-emacs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/setup-emacs.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,235 @@ +#!/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:]) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/Pymacs-0.20/setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/Pymacs-0.20/setup.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,17 @@ +#!/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']) diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/bike.vim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/bike.vim Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,430 @@ +" 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 diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/bikeemacs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/bikeemacs.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,333 @@ +# 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 + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/test/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/test/README Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,89 @@ +Test 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 + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/test/extractmethod.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/test/extractmethod.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,16 @@ +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 + + +def inlineVariableTest(): + a = b + 3 - 5 + # --^^^^^ - Extract this into variable + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/test/scrap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/test/scrap.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,19 @@ +class MyClass: + def myMethod(self, foo): + pass + +a = MyClass() + +a.myMethod() + + +class AnotherClass: + def myMethod(self,foo): + pass + +def testFunction(): + print "hello" + + +print "hello" + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/ide-integration/test/scrap2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/ide-integration/test/scrap2.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,20 @@ +from scrap import MyClass, testFunction + +b = MyClass() + +b.myMethod() + + +c = abcde() + +c.myMethod() + + +d = defgh() + +d.myMethod() + + +e = MyClass() + +e.myMethod() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/setup.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,22 @@ +#!/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'} + ) + diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/bike/testall.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/bike/testall.py Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,27 @@ +#!/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() diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/ropevim/rope.vim --- a/vim/sadness/ropevim/rope.vim Tue Nov 16 17:53:55 2010 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -let $PYTHONPATH = "/Users/sjl/lib/dotfiles/vim/sadness/ropevim/pylibs:".$PYTHONPATH -source /Users/sjl/lib/dotfiles/vim/sadness/ropevim/src/ropevim/ropevim.vim diff -r 8bd0f75b7689 -r cfd5d659d737 vim/sadness/sadness.vim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vim/sadness/sadness.vim Mon Nov 22 14:32:21 2010 -0500 @@ -0,0 +1,7 @@ +let $rope_pypath = $HOME."/.vim/sadness/ropevim/pylibs" +let $bike_pypath = $HOME."/.vim/sadness/bike" + +let $PYTHONPATH = $rope_pypath.":".$bike_pypath.":".$PYTHONPATH +source $HOME/.vim/sadness/ropevim/src/ropevim/ropevim.vim + +source $HOME/.vim/sadness/bike/ide-integration/bike.vim