""" Use: "python ...\Tools\visitor_collect.py searchstring rootdir". CollectVisitor simply collects a list of all files containing a search string, for display or later processing (e.g., replacement, auto-editing); pass in a test filename extensions list to constructor to override the default in SearchVisitor; this is roughy like find+grep for text exts; """
import sys from visitor import SearchVisitor
class CollectVisitor(SearchVisitor): """ collect names of files containing a string; run this and then fetch its obj.matches list """ def init(self, searchstr, testexts=None, trace=1): self.matches = [] if testexts != None: self.testexts = testexts SearchVisitor.init(self, searchstr, trace) def visitmatch(self, fname, text): self.matches.append(fname)
if name == 'main': visitor = CollectVisitor(sys.argv[1]) visitor.run(startDir=sys.argv[2]) matches = visitor.matches print('Found', len(matches), 'files:') for fname in matches: print(fname)
""" Change all "#!...python" source lines at the top of Unix scripts to new line in all files in all dirs at and below a root; command line args are the root (default='.'), new #! line text (default=changeToDefault), and any text to run list-only mode (default=no);
could skip binary filename extensions explicitly, but try handler works; changes all #! first lines that name python, which is more accurate than a simple visitor_replace.py; example usage -- convert all scripts in book examples tree, list file to be changed in a tree, convert all to default: C:...\PP4E>python Tools\visitor_poundbang.py . #!\MyPy31\python > out.txt C:...\PP4E\Tools>python visitor_poundbang.py C:\temp\PP3E - - | more C:...\PP4E\Tools>python visitor_poundbang.py C:\temp\PP3E """
import sys from visitor import FileVisitor # reuse the walker classes changeToDefault = '#!\Python31\python.exe' # used if no cmdline arg
class PoundBangFixer(FileVisitor): def init(self, changeTo=changeToDefault, listonly=False, trace=0): FileVisitor.init(self, trace=trace) self.changeTo = changeTo self.listOnly = listonly self.clist = []
def visitfile(self, fullname):
FileVisitor.visitfile(self, fullname)
try:
lines = open(fullname, 'r').readlines() # fails for binary files
except UnicodeDecodeError:
if self.trace > 0: print('Skipped non-text file:', fullname)
else:
if (len(lines) > 0 and
lines[0].startswith('#!') and # or lines[0][0:2] == '#!'
'python' in lines[0] # or lines[0].find() != -1
):
self.clist.append(fullname)
if not self.listOnly:
lines[0] = self.changeTo + '\n'
open(fullname, 'w').writelines(lines)
if name == 'main': if input('Are you sure?') != 'y': sys.exit() rootdir = sys.argv[1] if len(sys.argv) > 1 else '.' changeto = sys.argv[2] if len(sys.argv) > 2 else changeToDefault listonly = len(sys.argv) > 3 walker = PoundBangFixer(changeto, listonly) walker.run(rootdir) print('Visited %d files and %d dirs,' % (walker.fcount, walker.dcount), end=' ') print('found' if listonly else 'changed', len(walker.clist), 'files') for fname in walker.clist: print(fname)
"Remove all .pyc bytecode files in a tree (visitor version)"
import sys, os from visitor import FileVisitor
class CleanPyc(FileVisitor): def init(self, trace=0): FileVisitor.init(self, context=0, trace=trace)
def visitfile(self, filepath):
FileVisitor.visitfile(self, filepath)
if filepath.endswith('.pyc'):
print(filepath)
#os.remove(filepath)
self.context += 1
if name == 'main': walker = CleanPyc() walker.run(sys.argv[1] if len(sys.argv) > 1 else '.') print('Visited %d files and %d dirs' % (walker.fcount, walker.dcount)) print('Removed %d files' % walker.context)
"find biggest/smallest .py files in a tree (visitor version)"
import sys, os, pprint from visitor import FileVisitor
class BigPy(FileVisitor): def init(self, trace=0): FileVisitor.init(self, context=[], trace=trace)
def visitfile(self, filepath):
FileVisitor.visitfile(self, filepath)
if filepath.endswith('.py'):
print(filepath)
self.context.append((os.path.getsize(filepath), filepath))
if name == 'main': walker = BigPy() walker.run(sys.argv[1] if len(sys.argv) > 1 else '.') print('Visited %d files and %d dirs' % (walker.fcount, walker.dcount)) walker.context.sort() pprint.pprint(walker.context[:2]) pprint.pprint(walker.context[-2:])
""" #################################################################################### Test: "python ...\Tools\visitor.py dir testmask [string]". Uses classes and subclasses to wrap some of the details of os.walk call usage to walk and search; testmask is an integer bitmask with 1 bit per available self-test; see also: visitor_*/.py subclasses use cases; frameworks should generally use__X pseudo private names, but all names here are exported for use in subclasses and clients; redefine reset to support multiple independent walks that require subclass updates; #################################################################################### """
import os, sys
class FileVisitor: """ Visits all nondirectory files below startDir (default '.'); override visit* methods to provide custom file/dir handlers; context arg/attribute is optional subclass-specific state; trace switch: 0 is silent, 1 is directories, 2 adds files """ def init(self, context=None, trace=2): self.fcount = 0 self.dcount = 0 self.context = context self.trace = trace
def run(self, startDir=os.curdir, reset=True):
if reset: self.reset()
for (thisDir, dirsHere, filesHere) in os.walk(startDir):
self.visitdir(thisDir)
for fname in filesHere: # for non-dir files
fpath = os.path.join(thisDir, fname) # fnames have no path
self.visitfile(fpath)
def reset(self): # to reuse walker
self.fcount = self.dcount = 0 # for independent walks
def visitdir(self, dirpath): # called for each dir
self.dcount += 1 # override or extend me
if self.trace > 0: print(dirpath, '...')
def visitfile(self, filepath): # called for each file
self.fcount += 1 # override or extend me
if self.trace > 1: print(self.fcount, '=>', filepath)
class SearchVisitor(FileVisitor): """ Search files at and below startDir for a string; subclass: redefine visitmatch, extension lists, candidate as needed; subclasses can use testexts to specify file types to search (but can also redefine candidate to use mimetypes for text content: see ahead) """
skipexts = []
testexts = ['.txt', '.py', '.pyw', '.html', '.c', '.h'] # search these exts
def __init__(self, searchkey, trace=2):
FileVisitor.__init__(self, searchkey, trace)
self.scount = 0
def reset(self): # on independent walks
self.scount = 0
def candidate(self, fname): # redef for mimetypes
ext = os.path.splitext(fname)[1]
if self.testexts:
return ext in self.testexts # in test list
else: # or not in skip list
return ext not in self.skipexts
def visitfile(self, fname): # test for a match
FileVisitor.visitfile(self, fname)
if not self.candidate(fname):
if self.trace > 0: print('Skipping', fname)
else:
text = open(fname).read() # 'rb' if undecodable
if self.context in text: # or text.find() != -1
self.visitmatch(fname, text)
self.scount += 1
def visitmatch(self, fname, text): # process a match
print('%s has %s' % (fname, self.context)) # override me lower
if name == 'main': # self-test logic dolist = 1 dosearch = 2 # 3=do list and search donext = 4 # when next test added
def selftest(testmask):
if testmask & dolist:
visitor = FileVisitor(trace=2)
visitor.run(sys.argv[2])
print('Visited %d files and %d dirs' % (visitor.fcount, visitor.dcount))
if testmask & dosearch:
visitor = SearchVisitor(sys.argv[3], trace=0)
visitor.run(sys.argv[2])
print('Found in %d files, visited %d' % (visitor.scount, visitor.fcount))
selftest(int(sys.argv[1])) # e.g., 3 = dolist | dosearch