Add move_to_hierarchy.rb
[cmccabe-bin] / move_to_hierarchy.rb
1 #!/usr/bin/ruby -w
2
3 #
4 # move_to_hierarchy.rb
5 #
6 # Moves all the files in a directory with the given file extension into a
7 # hierarchy.  The maximum number of entries at each level is fixed.
8 #
9 # This is useful for arranging mp3s in a way that makes them easy to play on
10 # devices where large directories are impractical.
11 #
12 # Colin Mccabe
13 #
14
15 require 'fileutils'
16 require 'optparse'
17 require 'ostruct'
18
19 class MyOptions
20   DEFAULT_FILES_PER_LEVEL = 10
21
22   def self.parse(args)
23     opts = OpenStruct.new
24     opts.dry_run = false
25     opts.files_per_level = DEFAULT_FILES_PER_LEVEL 
26     opts.extension = nil
27     $fu_args = { :verbose => true }
28     opts.preserve_names = false
29
30     # Fill in $opts values
31     parser = OptionParser.new do |myparser|
32       myparser.banner = "Usage: #{ File.basename($0) } [opts]"
33       myparser.separator("Specific options:")
34       myparser.on("--files-per-level NFILES", "-L",
35               "Set the number of files per directory to use (default " +
36               DEFAULT_FILES_PER_LEVEL.to_s() + ").") do |d|
37         opts.files_per_level = d.to_i
38       end
39       myparser.on("--dry-run", "-d",
40               "Show what would be done, without doing it.") do |d|
41         $fu_args = { :verbose => true, :noop => true }
42         opts.dry_run = true
43       end
44       myparser.on("--file-extension EXTENSION", "-e",
45               "The file extension for the files to rename.") do |e|
46         opts.extension = e
47       end
48     end
49
50     parser.parse!(args)
51     raise "invalid number of files per level: #{opts.files_per_level}" unless
52       opts.files_per_level > 1
53     raise "must give an extension" unless opts.extension != nil
54     return opts
55   end
56 end
57
58 def idx_to_path(idx)
59   num_digits = Math.log10($opts.files_per_level + 1).ceil()
60   path = "."
61   (0..idx.length - 2).each do |i|
62     v = idx[i]
63     path = path + "/" + (("%0" + num_digits.to_s + "d") % v)
64   end
65   return path
66 end
67
68 def idx_increment(idx)
69   i = idx.length - 1
70   while (idx[i] > $opts.files_per_level)
71     idx[i] = 1
72     i = i - 1
73     idx[i] = idx[i] + 1
74   end
75 end
76
77 # MAIN
78 begin
79   $opts = MyOptions.parse(ARGV)
80 rescue Exception => msg
81   $stderr.print("#{msg}.\nType --help to see usage information.\n")
82   exit 1
83 end
84
85 # get number of files
86 num_files = 0
87 Dir.glob("*.#{$opts.extension}").sort.each do |f| 
88   num_files = num_files + 1
89 end
90 if (num_files == 0):
91   raise "No files found in the current working directory with extension " + 
92     $opts.extension
93 end
94 num_levels = Math.log(num_files) / Math.log($opts.files_per_level)
95 num_levels = num_levels.ceil()
96 if ($fu_args[:verbose]) then
97   print "Number of files: " + num_files.to_s() + ".  Number of levels: " +
98     num_levels.to_s() + "\n"
99 end
100
101 idx = Array.new
102 (1..num_levels).each { |n| idx.push(1) }
103
104 files_in_dir = 0
105 Dir.glob("*.#{$opts.extension}").sort.each do |f| 
106   path = idx_to_path(idx)
107   FileUtils.mkdir_p(path, $fu_args)
108   FileUtils.mv(f, path + "/" + f, $fu_args)
109   idx[idx.length - 1] = idx[idx.length - 1] + 1
110   if (idx[idx.length - 1] > $opts.files_per_level)
111     idx_increment(idx)
112   end
113 end
114
115 exit 0