5
\$\begingroup\$

A simple interactive Python script for organizing messy folders on the computer, supports macOS and linux but not windows. The mini-program has the following features:

  • Creation of Alpha-numeric (A-Z / 0-9) folders according to available set of letters present in the folder given in the path and move folders/files into relevant newly created folders.
  • Creation of Type specific folders (Audio, Video ...) and move files in relevant type folders.
  • Run directly from shell or from Python interpreter.
  • Interactive.

It is intended to add more features to the code and maybe perform further optimizations / updates in the future, here's my implementation in Python, I hope this is to your liking, any suggestions for improvements/features to add/problems encountered while using the code are more than welcome. Program works perfectly well, no bugs found so far however, you might want to test it before use on a folder containing test files so you can get used to it.

#!/usr/bin/env python
import os
import string
import shutil
import random


def thank_user():
    """Print a thank you message and end program."""
    print(5 * ' ' + 10 * '=')
    print('Thank you for using file organizer.')
    print(5 * ' ' + 10 * '=')
    exit()


def get_folder_contents(path):
    """Return folder contents."""
    while not os.path.exists(path):
        path = input('Invalid path, please enter a valid path or q to exit: ').rstrip()
        if path == 'q':
            thank_user()
    os.chdir(path)
    return [file for file in os.listdir(path) if file != '.DS_Store']


def welcome_user():
    """Print a welcome message."""
    print(5 * ' ' + 10 * '=')
    print('Welcome to file organizer.')
    print(5 * ' ' + 10 * '=')
    action = input('Do you wish to continue? y/n: ')
    while action != 'y' and action != 'n':
        print(f'Invalid command {action}')
        action = input('Do you wish to continue? y/n: ')
    if action == 'n':
        thank_user()
    if action == 'y':
        pass


def create_alpha_num_folders(path):
    """Create alpha-numeric folders for first characters present in files of the given path ."""
    folder_contents = get_folder_contents(path)
    if not folder_contents:
        print(f"0 files found in {str(path).split('/')[-1]}")
        print(105 * '=')
        thank_user()
    chars_to_produce = set([item[0].upper() for item in folder_contents])
    invalid_folders = [folder for folder in folder_contents if folder in string.ascii_uppercase
                       or folder in string.digits]
    if invalid_folders:
        print(f'Found the following {len(invalid_folders)} folders named from A - Z/ 1 - n:')
        for invalid_folder in invalid_folders:
            print(invalid_folder)
        while invalid_folders:
            action = input(f'Enter q to exit. Do you want to to rename the {len(invalid_folders)}'
                           f' invalid folder(s)? y/n/q: ')
            if action == 'q' or action == 'n':
                print('Creating A - Z / 1 - n folders unsuccessful.')
                thank_user()
            if action == 'y':
                confirm = input(f'Are you sure, do you want to rename {len(invalid_folders)} invalid folders? y/n: ')
                while not confirm == 'y' and not confirm == 'n':
                    print(f'Invalid command {confirm}')
                    confirm = input(f'Are you sure, do you want to rename {len(invalid_folders)} '
                                    f'invalid folders? y/n: ')
                if confirm == 'y':
                    count = random.randint(10 ** 6, 10 ** 7)
                    for filename in invalid_folders:
                        new_name = filename + '_' + str(count)
                        os.rename(filename, new_name)
                    print(f'Changed {len(invalid_folders)} folder name(s) successful.')
                    break
                if confirm == 'n':
                    thank_user()
            else:
                print(f'Invalid command {action}')
    for char in sorted(chars_to_produce):
        os.mkdir(char)
        print(f'Creation of folder {char} successful.')
    print(105 * '=')


def get_supported_formats():
    """Return supported formats."""
    formats = {
        'AUDIO': ['aif', 'cda', 'mid', 'midi', 'mp3', 'mpa', 'ogg', 'wav', 'wma', 'wpl'],
        'COMPRESSED': ['arj', 'deb', 'pkg', 'rar', 'rpm', 'tar.gz', 'z', 'zip'],
        'DISC': ['bin', 'dmg', 'iso', 'toast', 'vcd'],
        'DATA': ['csv', 'dat', 'db', 'dbf', 'log', 'mdb', 'sav', 'sql', 'tar', 'xml'],
        'EXECUTABLE': ['apk', 'bat', 'bin', 'cgi', 'pl', 'com', 'exe', 'gadget', 'jar', 'wsf'],
        'FONT': ['fnt', 'fon', 'otf', 'ttf'],
        'IMAGE': ['ai', 'bmp', 'gif', 'ico', 'jpeg', 'jpg', 'png', 'ps', 'psd', 'svg', 'tif', 'tiff'],
        'INTERNET': ['asp', 'aspx', 'cer', 'cfm', 'css', 'htm', 'html', 'js', 'jsp',
                     'part', 'php', 'rss', 'xhtml'],
        'PRESENTATION': ['key', 'odp', 'pps', 'ppt', 'pptx'],
        'PROGRAMMING': ['c', 'class', 'cpp', 'cs', 'h', 'java', 'sh', 'swift', 'vb', 'py'],
        'SPREADSHEET': ['ods', 'xlr', 'xls', 'xlsx'],
        'SYSTEM': ['bak', 'cab', 'cfg', 'cpl', 'cur', 'dll', 'dmp', 'drv', 'icns', 'ini',
                   'lnk', 'msi', 'sys', 'tmp'],
        'VIDEO': ['3g2', '3gp', 'avi', 'flv', 'h264', 'm4v', 'mkv', 'mov', 'mp4', 'mpg', 'mpeg',
                  'rm', 'swf', 'vob', 'wmv', 'webm'],
        'WORD_PROCESSOR': ['doc', 'docx', 'odt', 'pdf', 'rtf', 'tex', 'txt', 'wks', 'wps', 'wpd'],
        'SUBTITLES': ['srt', 'sub', 'sbv'],
        'UNCLASSIFIED': []
    }
    return formats


def get_extension_type(extension):
    """Return extension type ex: mp3 --> AUDIO."""
    formats = get_supported_formats()
    possible_extensions = tuple(ext for ext, exts in formats.items() if extension.lower() in exts)
    if not possible_extensions:
        return ('UNCLASSIFIED',)
    return possible_extensions


def get_file_extension(filename):
    """Return file extension"""
    return filename.split('.')[-1].lower()


def get_folder_extensions(path):
    """Return all folder extensions."""
    folder_contents = get_folder_contents(path)
    return set([get_file_extension(filename) for filename in folder_contents])


def create_folders(path):
    """Create alpha-numeric folders or type specific folders in the given path."""
    folder_contents = get_folder_contents(path)
    print(f"Current folder: {str(path).split('/')[-1]}")
    print(f'Folder contents ({len(folder_contents)}) items:')
    print(105 * '=')
    if not folder_contents:
        print(f"0 files found in {str(path).split('/')[-1]}")
        print(105 * '=')
        thank_user()
    for number, filename in enumerate(sorted(folder_contents), 1):
        if os.path.isdir(filename):
            print(f'{number}. Folder: {filename}')
        if os.path.isfile(filename):
            print(f'{number}. File: {filename}')
    print(105 * '=')
    action1 = input(f'Enter q to exit. Do you want to group the {len(folder_contents)}'
                    f' items above Alpha-numerically (A-Z / 0-9) or by type? a/t/q: ')
    while action1 != 'a' and action1 != 't' and action1 != 'q':
        print(f'Invalid command {action1}')
        action1 = input(f'Enter q to exit. Do you want to group the {len(folder_contents)}'
                        f' items above Alpha-numerically (A-Z / 0-9) or by type? a/t/q: ')
    if action1 == 'a':
        action2 = input(f"Enter q to exit. Are you sure, do you want to create Alpha-numeric (A-Z / 0-9) folders in "
                        f"{str(path).split('/')[-1]}? y/n/q: ")
        while action2 != 'y' and action2 != 'n' and action2 != 'q':
            print(f'Invalid command {action2}')
            action2 = input(
                f"Enter q to exit. Are you sure, do you want to create Alpha-numeric (A-Z / 0-9) folders in "
                f"{str(path).split('/')[-1]}? y/n/q: ")
        if action2 == 'n' or action2 == 'q':
            print('Creation of new Alpha-numeric (A-Z / 0-9) folders unsuccessful.')
            thank_user()
        if action2 == 'y':
            create_alpha_num_folders(path)
            return 'alpha'
    if action1 == 't':
        folder_extensions = get_folder_extensions(path)
        new_folders = set([get_extension_type(extension) for extension in folder_extensions])
        new_folder_names = [folder_name for folder_names in new_folders for folder_name in folder_names]
        print('New type folders to create: ')
        for number, name in enumerate(sorted(new_folder_names), 1):
            print(number, name)
        action2 = input(f"Enter q to exit. Are you sure do you want to create the {len(new_folder_names)} folders"
                        f" above in {str(path).split('/')[-1]}? y/n/q: ")
        while action2 != 'y' and action2 != 'n' and action2 != 'q':
            print(f'Invalid command {action2}')
            action2 = input(f"Enter q to exit. Are you sure do you want to create the {len(new_folder_names)} folders"
                            f" above in {str(path).split('/')[-1]}? y/n/q: ")
        if action2 == 'y':
            for folder in sorted(new_folder_names):
                try:
                    os.mkdir(folder)
                    print(f'Creation of {folder} folder successful.')
                except FileExistsError:
                    print(f'Filename {folder} already exists.')
                    print(f'Creation of {folder} folder unsuccessful.')
            return 'type'
        if action2 == 'n' or action2 == 'q':
            print(f'Creation of {len(new_folder_names)} folders unsuccessful.')
            thank_user()
    if action1 == 'q':
        print('Creation of new folders unsuccessful.')
        thank_user()


def organize_files(path):
    """Move files and folders into new organized relevant folders."""
    folder_types = create_folders(path)
    if folder_types == 'alpha':
        new_folder_contents = get_folder_contents(path)
        files_to_move = [folder for folder in new_folder_contents if folder not in string.ascii_uppercase
                         and folder not in string.digits]
        new_alpha_folders = [folder for folder in new_folder_contents if folder in string.ascii_uppercase
                             or folder in string.digits]
        print(f"Current folder: {str(path).split('/')[-1]}")
        print(f'Folder contents to be moved ({len(files_to_move)}) items:')
        print(105 * '=')
        for number, filename in enumerate(sorted(files_to_move), 1):
            if os.path.isdir(filename):
                print(f'{number}. Folder: {filename}')
            if os.path.isfile(filename):
                print(f'{number}. File: {filename}')
        print(105 * '=')
        action = input(f'Enter q to exit. Are you sure, do you want to organize the {len(files_to_move)} files above'
                       f' to Alpha-numeric (A-Z / 0-9) folders? y/n/q: ')
        while action != 'q' and action != 'y' and action != 'n':
            print(f'Invalid command {action}')
            action = input(
                f'Enter q to exit. Are you sure, do you want to organize the {len(files_to_move)} files above'
                f' to Alpha-numeric (A-Z / 0-9) folders? y/n/q: ')
        if action == 'q' or action == 'n':
            print(f'Organization of {len(files_to_move)} Alpha-numerically (A-Z / 0-9) unsuccessful.')
            thank_user()
        if action == 'y':
            for filename in files_to_move:
                for folder_name in new_alpha_folders:
                    if filename.startswith(folder_name.lower()) or filename.startswith(folder_name):
                        shutil.move(filename, folder_name)
                        print(f'Moving successful ... {filename} to {folder_name}')
    if folder_types == 'type':
        new_folder_contents = get_folder_contents(path)
        files_to_move = [folder for folder in new_folder_contents if folder not in get_supported_formats()]
        new_type_folders = [folder for folder in new_folder_contents if folder in get_supported_formats()]
        print(f"Current folder: {str(path).split('/')[-1]}")
        print(f'Folder contents to be moved ({len(files_to_move)}) items:')
        print(105 * '=')
        for number, filename in enumerate(sorted(files_to_move, key=get_file_extension), 1):
            if os.path.isdir(filename):
                print(f'{number}. Folder: {filename}')
            if os.path.isfile(filename):
                print(f'{number}. File: {filename}')
        print(105 * '=')
        action = input(f'Enter q to exit. Are you sure, do you want to organize the {len(files_to_move)} files above'
                       f' into type specific folders? y/n/q: ')
        while action != 'y' and action != 'n' and action != 'q':
            print(f'Invalid command {action}')
            action = input(
                f'Enter q to exit. Are you sure, do you want to organize the {len(files_to_move)} files above'
                f' into type specific folders? y/n/q: ')
        if action == 'n' or action == 'q':
            print(f'Organization of {len(files_to_move)} by type unsuccessful.')
            thank_user()
        if action == 'y':
            for filename in files_to_move:
                file_type = get_extension_type(get_file_extension(filename))
                for folder_name in new_type_folders:
                    if file_type[0] == folder_name:
                        shutil.move(filename, folder_name)
                        print(f'Moving successful ... {filename} to {folder_name}')
    thank_user()


if __name__ == '__main__':
    welcome_user()
    path_to_folder = input('Please enter a valid path or q to exit: ').rstrip()
    if path_to_folder == 'q':
        thank_user()
    organize_files(path_to_folder)
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The methods are too long. Strive to make smaller and more specific methods. This will improve the readability of you code, and it will allow you to reuse code. For example on a lot of places you ask the user to confirm his action. This is done with a call to the input method in a while loop until correct input is entered and then if-else statment, where you are checking the result. Wouldn't it be easier if you create a method like so:

def confirm_action(message):
    confirm = input(message)
    while confirm != 'y' and confirm != 'n':
       confirm = input(message)
    return confirm  == 'y'

Now you can use only if conditon and a method call to get the user confirmation. Also if tomorrow for example, you want to quit the program on the 5th wrong confirm, you have to do it only on 1 place not on 100.
There is a wrong logical condition in create_alpha_num_folders method.

while not confirm == 'y' or confirm == 'n':

must be

while not (confirm == 'y' or confirm == 'n'):

Avoid naming variables like "action1", "action2" (found in increate_folders method). An example of better names are action_input and action_confirm
Avoid returning things like 'alpha', it doesn't describe what is the intention and it's easy to braek the consistency. If I'm writing another module, which uses yours I can easily return 'Alpha' which will result in error. Better use enum
Try avoid nesting a lot of if statements and loops. It becames hard to read.
Try creating some modules or classes. This will allow you to group similar methods and state(think of it like global variable in your module).
In my opinion it's better to create array with valid inputs and use it to check whever an input is valid. For example the user confirm method can be done in the following way:

def confirm_action(message):
  valid_inputs = ['y', 'n']
  confirm = input(message)
  while confirm not in valid_inputs:
    confirm = input(message)
  return confirm  == 'y'
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.