venerdì 14 gennaio 2011

Ruby: flatten directory [ENG]

We've organized files in subdirectories according to tags and according to creation date. What if you would like to flatten a tree, moving all files from the sub-directories into a single directory?

"Move all the files from the tree starting at source_dir"
"into dest_dir out of the tree"
def flatten(source_dir, dest_dir)
 Dir.foreach(source_dir) do |filename|
    # exclude current dir and parent dir
    if (!filename.eql?(".") and !filename.eql?("..")) 
     if File.directory?(filename) # subdirectory
       flatten(filename, dest_dir) # recursively flatten subdir
     else #file
       dirname = File.dirname(filename)
       filenameonly = filename[(dirname.length-1)..-1]
       old_name = source_dir + "/" + filename
       new_name = dest_dir + "/" + filenameonly
       puts("moving '#{old_name}' to '#{new_name}'")
       File.rename(old_name, new_name) 
     end
    end
  end
end

# read line parameters

source_dir = ARGV.shift
# default source dir is current dir
if (source_dir == nil) 
  source_dir = "."
end

dest_dir = ARGV.shift
# default destination dir current dir
if (dest_dir == nil)
  dest_dir = "."
end 

flatten(source_dir, dest_dir)

2 commenti:

sLaughter ha detto...

what if you have files with same names in different subfolders

Alex Stout ha detto...

Here's my take on it. You can set max depth of folders and max attempts of renaming a file. It could be optimized still a little with support for more CL arguments, but...meh.


"Move all the files from the tree starting at source_dir"
"into dest_dir out of the tree"

# directory listings to skip
$excludes = ['.', '..', '.DS_Store']

# max file rename attempts
$max_attempts = 20

# initial directory tree depth
$depth = 0

# max allowed directory tree depth
$max_depth = 10


def flatten(source_dir, dest_dir)
# increment the tree depth
$depth += 1

# if surpassed max allowed tree depth, exit the directory
if $depth > $max_depth
puts "\n\n-- Max folder depth reached in '#{source_dir}' --\n\n"
$depth -= 1 # exit this directory
return
end

# ensure dir paths end with '/'
source_dir += "/" if source_dir[-1] != "/"
dest_dir += "/" if dest_dir[-1] != "/"


# read the items of the directory
puts "Flattening contents of '#{source_dir}' to '#{dest_dir}'\n"
Dir.entries(source_dir).each do |item|
next if $excludes.include?(item) # skip excludes
item = source_dir + item # realize full path
if File.directory?(item) # subdirectory
flatten(item, dest_dir) # recursively flatten subdir
else #file
rename_file(item, dest_dir, 0) # attempt to rename the file
end
end
# check if the directory is empty, if so delete it
# special case, ruby won't consider a dir empty if it contains .DS_Store
# delete .DS_Store first if dir otherwise empty
# also don't delete the directory if it's the root directory
begin
if (Dir.entries(source_dir) - $excludes).empty? and $depth > 1
puts "deleting #{source_dir}"
if File.file?("#{source_dir}.DS_Store")
_ = `rm "#{source_dir}.DS_Store"`
end
Dir.delete(source_dir)
end
rescue # do nothing
end
$depth -= 1 # exit this directory
end

# renames the file, moving it to the dest_dir if the file
# isn't already in the directory at dest_dir
def rename_file(filename, dest_dir, version)
# file already in dest_dir?
if File.dirname(filename) + "/" == dest_dir
puts "file #{filename} already in #{dest_dir}"
return
end

# get the straight filename, no path
fname = File.basename(filename)

# set what the new full path ought to be
new_path = dest_dir + fname

# if we are attempting new versions append the version to the basename before the extension
if version > 0
new_path = dest_dir + File.basename(filename, ".*") + "_" + version.to_s + File.extname(filename)
end

# if the new path already exists, try again
if File.file?( new_path )
puts("File '#{new_path}' exists..")
if (version > $max_attempts)
puts "Max rename attempts reached for '#{filename}'"
end
rename_file(filename, dest_dir, version += 1)
else
puts("moving '#{filename}' to '#{new_path}'")
File.rename(filename, new_path)
end
end

# START

# source dir is first argument, otherwise current directory
source_dir = ARGV.shift
# default source dir is current dir
if (source_dir == nil)
source_dir = "."
end

#destination is second argument, otherwise same as source
dest_dir = ARGV.shift
# default destination dir = source dir
if (dest_dir == nil)
dest_dir = source_dir
end

# begin the flattening process
flatten(source_dir, dest_dir)