Source

scripts / wipe.py

Full commit
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
A simple maintenance script for removing temporary files from given directories.

2007-2008 (c) Andy Mikhailenko

CHANGELOG:
    2007-07-29	basic five-liner script with recursive cleanup
    2008-02-02	+argument processing (incl. '-r' which is now off by default)
"""

import getopt
import os
import sys

USAGE = """
NAME
    wipe.py - a simple maintenance script for removing temporary files from given directories

SYNOPSIS
    wipe [OPTION]

OPTIONS
    Remove "trashable" files from current (or given) directory.

    -h, --help
            displays this message

    -d DIRECTORY, --dir=DIRECTORY
            set root directory to DIRECTORY (by default it's current working directory)

    -n NAME, --name=NAME, --name="NAME1 NAME2"
            override default list of trashable file names with given name(s)

    -e ENDING, --ending=ENDING, --ending="ENDING1 ENDING2"
            override default list of trashable file endings (e.g. extensions) with given name(s)

    -r, --recursive
            clean the dirs recursively, starting with current root dir

    -v, --verbose
            report each removed file. Ignored if '--silent' is in use

    -s, --silent
            suppress all messages

    -t, --test
            test mode: do not delete anything, just pretend

AUTHOR
    Written by Andy Mikhailenko.
"""
    
TRASHABLE_NAMES   = ORIGINAL_TRASHABLE_NAMES   = ('thumbs.db', 'Thumbs.db')
TRASHABLE_ENDINGS = ORIGINAL_TRASHABLE_ENDINGS = ('.pyc', '.pyo', '~',)

ROOT_DIR  = os.getcwd()
RECURSIVE = False
VERBOSE   = False
SILENT    = False
DRY_RUN   = False

SIGIL_FOUND = '*'
SIGIL_REMOVED = '-'
SIGIL_ERROR = '!'

# Routines

def is_file_trashable(filename):
    """ Here are the rules. Returns True if file with given filename should be deleted """
    if filename in TRASHABLE_NAMES  or  filename.endswith(TRASHABLE_ENDINGS):
        return True

def report_file(filepath, sigil=SIGIL_FOUND, force=False):
    """ Writes file report to stdout. Used to inform user about a file being found/removed/etc.
    Silent by default. Can be triggered by --verbose option (overridden by --silent).
    If "force" argument is True, then the global options are ignored and file is reported anyway.
    """
    if force or (VERBOSE and not SILENT):
        sys.stdout.write('%s %s\n' % (sigil, filepath))

# Process the arguments, tune our behaviour

try:
    opts, args = getopt.getopt(sys.argv[1:],'dne:rvstnh',['dir=','name=','ending=','recursive','verbose','silent','test','help'])
except getopt.GetoptError, e:
    sys.stderr.write(e.msg)
    sys.stdout.write(USAGE)
    sys.exit()
for opt, arg in opts:
    if opt in ('-h', '--help'):
        sys.stdout.write(USAGE)
        sys.exit()
    elif opt in ('-d','--dir'):
        ROOT_DIR = arg
    elif opt in ('-n','--name'):
        TRASHABLE_NAMES = tuple(arg.split())
    elif opt in ('-e','--ending'):
        TRASHABLE_ENDINGS = tuple(arg.split())
    elif opt in ('-r','--recursive'):
        RECURSIVE = True
    elif opt in ('-v','--verbose'):
        VERBOSE = True
    elif opt in ('-s','--silent'):
        SILENT = True
    elif opt in ('-t','--test'):
        DRY_RUN = True

# Report what exactly we are going to do

if VERBOSE and not SILENT:
    sys.stdout.write('Cleaning up "%s" %s...\n' % (ROOT_DIR, (RECURSIVE and 'recursively' or '') ))
    if TRASHABLE_NAMES != ORIGINAL_TRASHABLE_NAMES  or  TRASHABLE_ENDINGS != ORIGINAL_TRASHABLE_ENDINGS:
        sys.stdout.write('* ')
    msg = DRY_RUN and 'Searching for files with names matching %s or ending with %s...\n' \
                   or 'Removing files with names matching %s or ending with %s...\n' 
    sys.stdout.write(msg % (TRASHABLE_NAMES, TRASHABLE_ENDINGS))
    #if DRY_RUN:
    #    sys.stdout.write('*** Note: test mode, no file is actually deleted.\n')

# Walk and wipe

matched_cnt = removed_cnt = 0
for root, dirs, files in os.walk(ROOT_DIR):
    for fname in files:
        if not is_file_trashable(fname):
            continue
        matched_cnt += 1
        filepath = os.path.join(root, fname)
        if DRY_RUN:
            report_file(filepath)
            continue
        try:
            os.remove(filepath)
            report_file(filepath, SIGIL_REMOVED)
            removed_cnt += 1
        except OSError, e:
            report_file(filepath, SIGIL_ERROR, force=True)
            sys.stderr.write('    %s\n' % e.strerror)
    if not RECURSIVE:
        break

# Report results

if not SILENT:
    if matched_cnt == 0:
        sys.stdout.write('Already clean.\n')
    else:
        if DRY_RUN:
            sys.stdout.write('Found %d item(s). Nothing removed.\n' % matched_cnt)
        else:
            if matched_cnt == removed_cnt:
                sys.stdout.write('Removed %d item(s). Now clean.\n' % matched_cnt)
            else:
                sys.stdout.write('Tried to remove %d item(s): %s OK, %s could not be removed (see log for details).\n' % (
                                    matched_cnt, removed_cnt, matched_cnt-removed_cnt))