morristech
5/10/2018 - 9:09 AM

Python script to facilitate the hardlinking of directories and their files.

Python script to facilitate the hardlinking of directories and their files.

#!/usr/bin/env python
#
# Python script to facilitate the hardlinking of directories and their files.

import sys
if sys.version_info < (2, 3):
    raise RuntimeError('Python 2.3+ is required.')

import logging
import optparse
import os

version = '\nhardlinkify-1.0 - by Jake Wharton <jakewharton@gmail.com>\n'

#CLI argument parser
parser = optparse.OptionParser(usage='Usage: %prog [options] source [source ...] destination', version=version)
parser.add_option('-d', '--debug', dest='is_debug', action='store_true', default=False, help='print detailed execution information')
parser.add_option('-q', '--quiet', dest='is_quiet', action='store_true', default=False, help='no output except warnings and errors')
options, targets = parser.parse_args()

#Print version if not quiet
if not options.is_quiet:
    parser.print_version()

#Logging setup
level = logging.INFO
if options.is_quiet:
    level = logging.WARN
if options.is_debug:
    level = logging.DEBUG
logging.basicConfig(format='%(asctime)s %(levelname)-7s %(message)s')
logger = logging.getLogger(os.path.basename(__file__))
logger.setLevel(level)

#Error checking
if options.is_quiet and options.is_debug:
    logger.warn('Options --quiet and --debug are mutually exclusive. Ignoring --quiet.')
if len(targets) < 2:
    logger.error('You must specify at least one source and the destination.')
    sys.exit(1)


def link(file=None, dest=None, unroll=False):
    if file is None:
        raise ValueError('File parameter is required.')
    if dest is None:
        raise ValueError('Destination parameter is required.')

    if not os.path.exists(file):
        logger.warn('File "%s" does not exist. Skipping.', file)
        return
    elif os.path.isdir(file):
        if unroll:
            logger.debug('Unrolling directory "%s" in "%s".', file, dest)
        new_dest = dest if unroll else os.path.join(dest, os.path.basename(file))
        for new_file_name in os.listdir(file):
            new_file = os.path.join(file, new_file_name)
            logger.debug('> link("%s", "%s")', new_file, new_dest)
            link(new_file, new_dest)
        return

    if not os.path.exists(dest):
        logger.info('Directory "%s" does not exist. Creating.', dest)
        os.makedirs(dest)

    new_file = os.path.join(dest, os.path.basename(file))

    if os.path.exists(new_file):
        logger.warn('File "%s" already exists. Skipping.', new_file)
        return

    logger.info('Linking "%s" to "%s"...', new_file, file)
    os.link(file, new_file)


#Get arguments and perform linking
destination = os.path.abspath(targets[-1])
logger.debug('Destination: %s', destination)
files = [os.path.abspath(file) for file in targets[:-1]]
for file in files:
    logger.debug('File: %s', file)
for file in files:
    logger.debug('> link("%s", "%s")', file, destination)
    link(file, destination, True)