#!/usr/bin/env python
# Filename: winrename.py
# Author: Timothy Stotts (timotheus)
#
# $HeadURL: https://secure.tstotts.net/pubvc/mini-apps/000_rename_for_windows/winrename.py $
# $Id: winrename.py 1171 2010-02-20 01:01:13Z timotheus $

"""
This script prototypes an actual C program.

winrename.py is a script that renames all files and directories recursively,
making character substitutions for all file name patters that are illegal on
the Windows NTFS filesystem or cause trouble with the Samba file server.

All control characters are illegal; for example: ^D
The illegal graphic characters are:   / \ : * ? \" < > |

It is also problematic when serving files over Samba for a file to
    - begin with white space
    - end with white space
    - contain more than one period character
    - start or end with a period character
    - contain a non-alphanumeric file extension
"""

import os, re, string, sys

PERIOD = "."
DIR_CUR = "."
DIR_UP = ".."

class DirWalker(object):

    def walk(self, path, mcall):
        """ Walks a file or directory and calls a function on each entry. """
        for file in [f for f in os.listdir(path) if not f in [DIR_CUR, DIR_UP]]:

            # depth first is needed in case of directory rename
            nfile = os.path.join(path,file)
            if os.path.isdir(nfile):
                self.walk(nfile, mcall)

            mcall(path, file)


def winsub(file):
    """ Finds and substitutes illegal file name characters
        and problematic file name patterns. """
    newfile = file

    # Replace some illegal characters with space.
    repl = r' '
    for patt in [r'[\000-\037]']:
        newfile = re.sub(patt, repl, newfile)

    # Replace some illegal characters with dash.
    repl = r'-'
    for patt in [r'[/]', r'[\\]', r'[:]']:
        newfile = re.sub(patt, repl, newfile)

    # Replace some illegal characters with underscore.
    repl = r'_'
    for patt in [r'[*]', r'[?]', r'["]', r'[<]', r'[>]', r'[|]']:
        newfile = re.sub(patt, repl, newfile)
    
    # Remove leading and trailing whitespace.
    newfile = string.strip(newfile)

    # Replace multiple periods with underscore.
    repl = r'_'
    c = string.count(newfile, PERIOD)
    while (c > 1):
        i = string.find(newfile, PERIOD)
        newfile = newfile[0:i] + repl + newfile[i+1:len(newfile)]
        c = c - 1
        
    # Replace single remaining period with underscore
    # if failing certain tests.
    repl = r'_'
    i = string.find(newfile, PERIOD)
    test = True
    # if extension is not ASCII alphanumeric.
    if (i >= 0):
        ext = newfile[i+1:len(newfile)]
        for char in ext:
            if char not in string.ascii_letters + string.digits:
                test = False
    # if period is first or last character.
    if (i == 0 or i == len(newfile) - 1):
        test = False
    # replace if test failed
    if not test:
        newfile = re.sub(r'[.]', repl, newfile)


    return newfile


def visit(dir, file):
    """ Visits a directory, enumerates the contents, and executes winsub
        on each entry """
    oldfile = file
    oldpath = os.path.join(dir, oldfile)

    if os.path.isdir(oldpath) or os.path.isfile(oldpath):
        newfile = winsub(file)
        newpath = os.path.join(dir, newfile)

        if (oldpath != newpath):
            while(os.path.isfile(newpath)):
                newfile = "1" + newfile
                newpath = os.path.join(dir, newfile)

            print("Rename to: %s" % newpath)
            os.rename(oldpath, newpath)

# MAIN
if __name__ == "__main__":
    """ The main method that invokes walking either the PWD
        or command-line arguments """
    dw = DirWalker()
    
    if len(sys.argv) > 1:
        for arg in sys.argv[1:]:
            if os.path.isfile(arg):
                [head, tail] = os.path.split(arg)
                visit(head, tail, arg)
            elif os.path.isdir(arg):
                dw.walk(arg, visit)
    else:
        dw.walk(DIR_CUR, visit)

    sys.exit(0)

