midwire
7/26/2014 - 1:39 AM

bulk_file_rename

bulk_file_rename

#!/usr/bin/env ruby
# A Ruby bulk file renaming script

require 'fileutils'
require 'pry'
require 'colored'
require 'trollop'

class BulkFileRename
  include FileUtils

  attr_accessor :file_count, :renamed_count, :would_count, :options, :elapsed

  def self.run(*args)
    start_time = Time.now
    opts = Trollop::options do
      opt(:directories, 'A colon-separated list of directories to search', type: :string, short: 'd', required: true)
      opt(:file_pattern, 'A globular expression filename search pattern', type: :string, short: 'f', required: true)
      opt(:replace_pattern, "A SED-type replace pattern: 's/search/replace/'", type: :string, short: 'p', required: true)
      opt(:alt_output, 'Alternative output', short: 'a')
      opt(:recursive, 'Recursively search directories', short: 'r')
      opt(:color, 'Print colored output', default: true, short: 'c')
      opt(:test_run, 'Test (Dry) run: Only report what would be renamed', short: 't')
    end
    unless opts[:replace_pattern] && opts[:replace_pattern].match(/s\/[^\/]+\/[^\/]*\//)
      Trollop::die("Use a SED-like expression for --replace-pattern='s/search/replace/'")
    end
    unless ARGV.empty?
      opts[:directories] = ARGV.first.split(':')
    end
    opts[:directories] = [opts[:directories]] unless opts[:directories].respond_to?(:each)

    instance = BulkFileRename.new(opts)
    instance.process
    instance.elapsed = Time.now - start_time
    instance.report_summary
  end

  def initialize(opts)
    self.file_count    = 0
    self.renamed_count = 0
    self.would_count   = 0
    self.options       = opts
  end

  def color(string, clr)
    if self.options[:color]
      string.send(clr)
    else
      string
    end
  end

  def report_summary
    puts
    puts(color(">>> Processed #{file_count.to_i} files in [#{elapsed}] seconds", :red))
    puts(color(">>> Renamed #{renamed_count.to_i} files", :red))
    if self.options[:test_run]
      puts(color(">>> Would have renamed #{would_count.to_i} files", :red))
    end
  end

  def report_move(source, destination)
    if self.options[:alt_output]
      puts(%Q{"#{color(source, :green)}","#{color(destination, :yellow)}"})
    else
      verb = self.options[:test_run] ? 'Would rename' : 'Renaming'
      puts(%Q{#{verb} "#{color(source, :green)}" to "#{color(destination, :yellow)}"})
    end
  end

  def rename_file(current, regex, replace, global)
    if !!global
      current.gsub(regex, replace)
    else
      current.sub(regex, replace)
    end
  end

  def process(directories = self.options[:directories])
    x, search, replace, global = self.options[:replace_pattern].split(/\//)

    directories.each do |directory|
      cd(directory)

      Dir.glob(self.options[:file_pattern]).each do |current_title|
        if self.options[:recursive] && File.directory?(current_title)
          process([current_title])
          break
        end
        self.file_count += 1

        searchx = Regexp.new(search)
        new_name = rename_file(current_title, searchx, replace, global)
        self.would_count += 1 unless new_name == current_title
        report_move(current_title, new_name)
        unless self.options[:test_run]
          binding.pry
          mv(current_title, new_name)
          self.renamed_count += 1
        end
      end
    end
  end
end

BulkFileRename.run(ARGV)