#### Source code for ./examples/findarg.py ####


#!/usr/bin/env python
"""my first sysadmin module

added a find option function
added config exclude_dirs with ConfigParser
added edit file function
added backup function

add commandline argument handling
"""

# *****************************************************************************

from __future__ import generators

import sys
import os
import stat
import re
from ConfigParser import ConfigParser
import tarfile

# *****************************************************************************

# read config file defaults

RCFILE = os.path.join(os.getenv('HOME'), 'pyadmin.rc')
PARSER = ConfigParser()
PARSER.read([RCFILE])
EXCLUDE_DIRS = PARSER.get('walk', 'exclude')
BACKUP_DIR = PARSER.get('backups', 'budir')
BACKUP_FILE = PARSER.get('backups', 'bufile')

# -----------------------------------------------------------------------------

def walktree(top='.', depthfirst=True, ): 
    """Directory tree generator.
    
    Traverses filesystem from directory 'top' downwards, returning each
    directory found and a list of files in that directory.
    
    If depthfirst is True, returns files found from bottom of tree first.

    See also os.walk, available in Python 2.3 on.

    Thanks to Noah Spurrier and Doug Fort.
    """

    names = os.listdir(top)
    if not depthfirst: 
        yield top, names
    for name in names: 
        try: 
            state = os.lstat(os.path.join(top, name))
        except os.error: 
            continue
        if stat.S_ISDIR(state.st_mode)and (name not in EXCLUDE_DIRS): 
            for (newtop, children)in \
                     walktree(os.path.join(top, name), depthfirst): 
                yield newtop, children
    if depthfirst: 
        yield top, names

# -----------------------------------------------------------------------------

def find(paths=None, inc_pattern='.*', 
          exc_pattern='', newer_than=None, prt=False, ): 
    """Find files below path(s) matching inc_pattern.

    exc_pattern = pattern to exclude files.
    paths = list of paths to search.

    Returns a list of files found.
    If prt is True, prints full pathname of files found on stdout.
    If newer_than is set to the name of a file, only files newer than that
    file will be returned.
    """

    wanted = True
    if newer_than: 
        # get ctime of stamp file
        try: 
            stamp_date = os.lstat(newer_than)[stat.ST_CTIME]
        except os.error: 
            raise
    results = []
    inc_rgx = re.compile(inc_pattern)
    exc_rgx = None
    if exc_pattern: 
        exc_rgx = re.compile(exc_pattern)
    if not paths: 
        paths = ['.', ]
    if isinstance(paths, str): 
        paths = [paths, ]
    for path in paths: 
        for basepath, children in walktree(path, False, ): 
            for child in children: 
                if inc_rgx.match(child, ): 
                    if not exc_rgx or not exc_rgx.match(child): 
                        fullpath = os.path.join(basepath, child, )
                        if newer_than: 
                            # see if file mod time is later than stamp file
                            state = os.lstat(fullpath)
                            wanted = (state[stat.ST_CTIME]>stamp_date)
                        if wanted: 
                            if prt: 
                                sys.stdout.write('%s\n'%fullpath)
                            results.append(fullpath)
    return results

# -----------------------------------------------------------------------------

def edit_files(file_list, old_string, new_string, update=False, ): 
    """replace all occurrences of old_string in files/file_list by new_string

    Returns list of files which were changed.
    If update is False, the files will not be updated (useful to see what
    files would have been changed).
    """

    changed_list = []
    for fname in file_list: 
        try: 
            fobj = open(fname, )
        except IOError: 
            sys.stderr.write('Error opening file %s\n'%fname)
            raise
        lines = fobj.readlines()
        fobj.close()
        newlines = []
        for line in lines: 
            line = line.replace(old_string, new_string, )
            newlines.append(line)
        if newlines != lines: 
            # we have changed the text, write it out
            if update: 
                fobj = open(fname, 'w', )
                fobj.writelines(newlines)
                fobj.close()
            changed_list.append(fname)
    return changed_list

# -----------------------------------------------------------------------------

def backup(file_list, backup_file=None, verbose=False, ): 
    """make tar backup of files listed in file_list to backup_file"""

    if not file_list: 
        return

    if not backup_file: 
        backup_file = os.sep.join([BACKUP_DIR, BACKUP_FILE, ])
    else: 
        backup_file = os.sep.join([BACKUP_DIR, backup_file, ])
    tgz = tarfile.open(backup_file, 'w:gz', )
    for file_name in file_list: 
        if file_name.find(os.sep) == 0: 
            # store file name as relative path in archive
            rel_name = file_name[1: ]
        else: 
            rel_name = None
        tgz.add(name = file_name, arcname = rel_name, recursive = False, )
        if verbose: 
            sys.stdout.write('%s\n'%file_name)
    tgz.close()
    return

# *****************************************************************************

if __name__ == '__main__': 
    import argparse

    PARSER =  \
     argparse.ArgumentParser(description = 'find files and optionally edit them')
    PARSER.add_argument('-p', action = 'store', dest = 'paths', 
                          default = '.', 
                          help = 'paths to search', 
                 )
    PARSER.add_argument('-i', action = 'store', dest = 'inc_patt', 
                         default = '.*', 
                         help = 'file name pattern to include', 
                 )
    PARSER.add_argument('-x', action = 'store', dest = 'exc_patt', 
                         default = None, 
                         help = 'file name pattern to exclude', 
                 )
    PARSER.add_argument('-o', action = 'store', dest = 'old', 
                         default = None, 
                         help = 'string to search for', 
                 )
    PARSER.add_argument('-n', action = 'store', dest = 'new', 
                         default = None, 
                         help = 'string to replace with', 
                 )
    PARSER.add_argument('-b', action = 'store', dest = 'bufile', 
                         default = 'editbak.tgz', 
                         help = 'backup file name', 
                 )
    VALUES = PARSER.parse_args()

    if not VALUES.paths: 
        sys.exit()
    FILE_LIST = find(VALUES.paths, VALUES.inc_patt, VALUES.exc_patt, 
                      newer_than = None, prt = True)
    if not FILE_LIST: 
        sys.stdout.write('no matching files\n')
    sys.stdout.write('%s matching files found\n'%len(FILE_LIST))

    if not VALUES.old: 
        sys.exit()
    if not VALUES.new: 
        sys.stdout.write('please supply a replacement string\n')
        sys.exit()

    CHANGED_FILES = edit_files(FILE_LIST, VALUES.old, VALUES.new, 
                                update = False, )
    if not CHANGED_FILES: 
        sys.stdout.write('no files with matching strings (%s)\n'%VALUES.old)
        sys.exit()
    sys.stdout.write('%s files with matching strings found\n'% \
                      len(CHANGED_FILES))

    sys.stdout.write('OK to edit files ? [y|n] [n] >> ')
    OK = sys.stdin.readline().upper().strip()
    if OK != 'Y': 
        sys.exit()
    sys.stdout.write('Taking backup...\n')
    backup(CHANGED_FILES, VALUES.bufile)
    sys.stdout.write('Backup completed, carrying out edit...\n')
    CHANGED_FILES = edit_files(FILE_LIST, VALUES.old, VALUES.new, 
                                update = True, )

# *****************************************************************************

[Created with py2html Ver:0.62]

Valid HTML 4.01!