AbsoluteRenamer 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/AbsoluteRenamer.gemspec +40 -0
  2. data/COPYING +340 -0
  3. data/ChangeLog +0 -0
  4. data/LICENCE +17 -0
  5. data/Manifest.txt +42 -0
  6. data/README +34 -0
  7. data/Rakefile +20 -0
  8. data/bin/absrenamer +54 -0
  9. data/conf/absrenamer/absrenamer.conf +34 -0
  10. data/lib/absolute_renamer.rb +119 -0
  11. data/lib/absolute_renamer/config.rb +40 -0
  12. data/lib/absolute_renamer/external.rb +56 -0
  13. data/lib/absolute_renamer/external/modules/core/case/module.rb +31 -0
  14. data/lib/absolute_renamer/external/modules/core/general/module.rb +126 -0
  15. data/lib/absolute_renamer/external/parsers/core/general/parser.rb +74 -0
  16. data/lib/absolute_renamer/external/parsers/interactive/parser.rb +20 -0
  17. data/lib/absolute_renamer/external/parsers/listing/parser.rb +9 -0
  18. data/lib/absolute_renamer/external/plugins/interactive/plugin.rb +35 -0
  19. data/lib/absolute_renamer/external/plugins/listing/plugin.rb +14 -0
  20. data/lib/absolute_renamer/file_info.rb +90 -0
  21. data/lib/absolute_renamer/imodule.rb +68 -0
  22. data/lib/absolute_renamer/iparser.rb +8 -0
  23. data/lib/absolute_renamer/iplugin.rb +11 -0
  24. data/lib/absolute_renamer/libs/file.rb +11 -0
  25. data/lib/absolute_renamer/libs/string.rb +27 -0
  26. data/lib/absolute_renamer/parser.rb +91 -0
  27. data/lib/absolute_renamer/use_config.rb +11 -0
  28. data/lib/absolute_renamer/with_children.rb +22 -0
  29. data/man/man8/absrenamer.8 +67 -0
  30. data/script/console +10 -0
  31. data/script/destroy +14 -0
  32. data/script/generate +14 -0
  33. data/setup.rb +1585 -0
  34. data/test/unit/features/test_absrenamer.conf +34 -0
  35. data/test/unit/tc_config.rb +28 -0
  36. data/test/unit/tc_file.rb +16 -0
  37. data/test/unit/tc_fileinfo.rb +17 -0
  38. data/test/unit/tc_imodule.rb +29 -0
  39. data/test/unit/tc_iplugin.rb +8 -0
  40. data/test/unit/tc_parser.rb +13 -0
  41. data/test/unit/tc_string.rb +24 -0
  42. data/test/unit/test_suite.rb +28 -0
  43. metadata +120 -0
@@ -0,0 +1,74 @@
1
+ # TODO ajouter une option pour la gestion du nombre de points avant l'extension
2
+ module AbsoluteRenamer
3
+ class GeneralParser < AbsoluteRenamer::IParser
4
+ def self.add_options(parser, options)
5
+ parser.banner << ' [file]...'
6
+ parser.on_tail('-h', '--help', 'Display this help screen') do
7
+ puts parser
8
+ exit 0
9
+ end
10
+
11
+ parser.on('-r', '--replace PATTERN,REPLACEMENT', Array,
12
+ 'String replacement ex:"pattern/replacement" ') do |data|
13
+ self.add_replacement(data, options)
14
+ end
15
+
16
+ parser.on('-e', '--regexp-replace PATTERN,REPLACEMENT', Array,
17
+ 'String replacement using regexp') do |data|
18
+ self.add_replacement(data, options, true)
19
+ end
20
+
21
+ parser.on('-f', '--format FORMAT',
22
+ 'Format string used as model') do |format|
23
+ options[:format] = format
24
+ @format_given = true
25
+ end
26
+
27
+ parser.on('-x', '--ext-format FORMAT',
28
+ 'Format string used as model for the extension') do |format|
29
+ options[:ext_format] = format
30
+ end
31
+
32
+ parser.on('-R', '--recursive',
33
+ 'Rename files in subdirectories recursively') do
34
+ options[:rec] = true
35
+ end
36
+
37
+ parser.on('--maxdepth N', Integer, 'Maximum recursion depth') do |depth|
38
+ options[:maxdepth] = depth
39
+ end
40
+
41
+ parser.on('-m', '--mode MODE', [:rename, :copy, :move, :link],
42
+ 'Renaming mode. Can be used with --dest DEST.',
43
+ ' rename: simply rename files',
44
+ ' copy: make a copy of each file in DEST with its new name',
45
+ ' move: move each file in DEST with its new name',
46
+ ' link: create a symbolic link to each file in DEST' <<
47
+ ' with its new name') do |mode|
48
+ options[:mode] = mode
49
+ end
50
+
51
+ parser.on('--dest DEST', 'Destination directory' <<
52
+ ' for copy, move and link modes') do |dest|
53
+ options[:dest] = File.expand_path(dest)
54
+ end
55
+
56
+ parser.on('-d', '--directories', 'Directories handling') do
57
+ options[:dir] = true
58
+ end
59
+ end
60
+
61
+ def self.add_replacement(data, options, regexp = false)
62
+ pattern,replace = data[0..1]
63
+ replace ||= ''
64
+ pattern = Regexp.new(pattern) if regexp
65
+ moment = @format_given.nil? ? :before : :after
66
+ options[:replacements] ||= {}
67
+ options[:replacements][moment] ||= []
68
+ options[:replacements][moment] << {:type => pattern.class,
69
+ :pattern => pattern,
70
+ :replace => replace
71
+ }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,20 @@
1
+ module AbsoluteRenamer
2
+ class InteractiveParser < AbsoluteRenamer::IParser
3
+ def self.add_options(parser, options)
4
+ parser.on('-i', 'Prompt before each renamming') do
5
+ options[:interactive] = :always
6
+ end
7
+
8
+ parser.on('-I', 'Prompt once before batch renamming') do
9
+ options[:interactive] = :once
10
+ end
11
+
12
+ parser.on('--interactive [WHEN]', [:always, :never, :once],
13
+ 'Prompt according to WHEN: never, once (-I), or always (-i).',
14
+ 'Without WHEN, prompt always') do |w|
15
+ w = :always if w.nil?
16
+ options[:interactive] = w
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module AbsoluteRenamer
2
+ class ListingParser < AbsoluteRenamer::IParser
3
+ def self.add_options(parser, options)
4
+ parser.on('-l', '--list', "Only display how files will be renamed") do
5
+ options[:listing] = true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ module AbsoluteRenamer
2
+ class InteractivePlugin < AbsoluteRenamer::IPlugin
3
+ def before_batch_renaming
4
+ if conf[:options][:interactive] == :once
5
+ conf[:files].each do |file|
6
+ file.display_change
7
+ end
8
+ print "Do you want to rename this files ? [y/N] "
9
+ begin
10
+ resp = STDIN.readline.chomp.downcase
11
+ rescue Exception => e
12
+ puts "\nExiting renamer"
13
+ exit(0)
14
+ end
15
+ return resp == "y"
16
+ end
17
+ true
18
+ end
19
+
20
+ def before_file_renaming(params)
21
+ if conf[:options][:interactive] == :always
22
+ params[:file].display_change
23
+ print "Do you want to rename this file ? [y/N] "
24
+ begin
25
+ resp = STDIN.readline.chomp.downcase
26
+ rescue Exception => e
27
+ puts "\nExiting renamer"
28
+ exit(0)
29
+ end
30
+ return resp == "y"
31
+ end
32
+ true
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ module AbsoluteRenamer
2
+ class ListingPlugin < AbsoluteRenamer::IPlugin
3
+ def before_batch_renaming
4
+ listing = conf[:options][:listing] || false
5
+ if listing
6
+ conf[:files].each do |file|
7
+ file.display_change
8
+ end
9
+ return false
10
+ end
11
+ true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,90 @@
1
+ require 'ftools'
2
+ require 'fileutils'
3
+ require 'absolute_renamer/use_config'
4
+ require 'absolute_renamer/libs/file'
5
+
6
+ module AbsoluteRenamer
7
+ # Class that represents each file to be renamed.
8
+ # It contains all informations about a file and, in the end,
9
+ # processes to its renaming.
10
+ class FileInfo
11
+ include AbsoluteRenamer::UseConfig
12
+
13
+ attr_accessor :name, :new_name,
14
+ :path, :real_path,
15
+ :ext, :dir, :dir_path
16
+
17
+ # Initializes a FileInfo.
18
+ # path: the relative or absolute path of the file.
19
+ def initialize(path)
20
+ @path = path
21
+ @real_path = File.expand_path(@path)
22
+ @dir_path = File.dirname(@real_path)
23
+ @dir = File.directory?(@real_path)
24
+ @name = File.basename(@real_path)
25
+ unless @dir
26
+ # TODO utiliser une conf :dot
27
+ @ext = File.extname(@name)
28
+ @name.gsub!(Regexp.new('.' << @ext << '$'), '') unless @ext.empty?
29
+ end
30
+ end
31
+
32
+ # Returns a description of a FileInfo
33
+ # some_fileinfo.inspect # => "File: hello_world pdf"
34
+ def inspect
35
+ "File: #{@name} #{@ext}"
36
+ end
37
+
38
+ # Displays the action that will be done on the file.
39
+ # some_fileinfo.display_change # => "rename a_file.txt --> A_File.TXT"
40
+ def display_change
41
+ puts "#{conf[:options][:mode]} #{@real_path.sub(Dir.pwd+'/', '')} --> #{new_path.sub(Dir.pwd+'/', '')}"
42
+ end
43
+
44
+ # Returns the new path of the file.
45
+ def new_path
46
+ if conf[:options][:dest].nil? or conf[:options][:dest].empty?
47
+ File.join(@dir_path, @new_name)
48
+ else
49
+ File.join(conf[:options][:dest], @new_name)
50
+ end
51
+ end
52
+
53
+ # Renames the file.
54
+ def rename
55
+ display_change
56
+ File.rename(@real_path, new_path)
57
+ end
58
+
59
+ # Copies the file.
60
+ def copy
61
+ display_change
62
+ if @dir
63
+ if @real_path != conf[:options][:dest]
64
+ FileUtils.cp_r(@real_path, conf[:options][:dest])
65
+ else
66
+ puts "#{real_path} ignored"
67
+ end
68
+ else
69
+ File.copy(@real_path, new_path, false)
70
+ end
71
+ end
72
+
73
+ # Moves a file. Moving to the same directories is just like renaming.
74
+ def move
75
+ display_change
76
+ File.move(@real_path, new_path, false)
77
+ end
78
+
79
+ # Creates a symbolic link to the file.
80
+ def link
81
+ display_change
82
+ begin
83
+ File.symlink(@real_path, new_path)
84
+ rescue NotImplemented
85
+ # TODO trouver mieux
86
+ puts "Error: cannot create symlinks"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,68 @@
1
+ require 'absolute_renamer/with_children'
2
+
3
+ module AbsoluteRenamer
4
+ # Modules parent class.
5
+ # Modules must inherit of it.
6
+ class IModule < AbsoluteRenamer::WithChildren
7
+ def initialize
8
+ @filters = []
9
+ end
10
+
11
+ # Returns the classname symbol
12
+ def self.symbol
13
+ name.intern
14
+ end
15
+
16
+ # Process a +file+ by searching for a known pattern in its name
17
+ # and replacing it by the corresponding value.
18
+ # The pattern is a regular expression obtained by concatening
19
+ # the +@filters+ variable with "|".
20
+ #
21
+ # file: a FileInfo instance
22
+ # format: the format string used to rename the file
23
+ # type: the type of the renaming format (:name or :ext)
24
+ def process(file, format, type = :name)
25
+ return format if @filters.empty?
26
+
27
+ str = format
28
+ result = []
29
+ pattern = Regexp.new(@filters.join('|'))
30
+
31
+ idx = str.index(pattern)
32
+ while idx
33
+ matched = pattern.match(str).to_a.compact
34
+ part = str.partition(matched[0])
35
+ result.push(part[0])
36
+ val = self.interpret(file, matched, type)
37
+ result.push(val)
38
+ str = part[2]
39
+ idx = str.index(pattern)
40
+ end
41
+ result.push(str) unless str.empty?
42
+ format.replace(result.join)
43
+ format
44
+ end
45
+
46
+ # Interprets a matched pattern.
47
+ # Searchs for the corresponding callback
48
+ # in the current module and call it.
49
+ #
50
+ # file: a FileInfo instance
51
+ # infos: the matched values depending of the pattern
52
+ # type: the type of the renaming format (:name or :ext)
53
+ def interpret(file, infos, type)
54
+ modifier = infos[2]
55
+ action = infos[3]
56
+
57
+ return conf[:options][:default_string] unless self.respond_to?(action.intern)
58
+
59
+ ap = self.method(action.intern)
60
+ val = ap.call(file, infos, type)
61
+ unless modifier.empty?
62
+ mp = CaseModule.method(CaseModule.actions[modifier])
63
+ val = mp.call(val)
64
+ end
65
+ val.empty? ? conf[:options][:default_string] : val
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,8 @@
1
+ require 'absolute_renamer/with_children'
2
+
3
+ module AbsoluteRenamer
4
+ # Parsers parent class.
5
+ # Parsers must inherit of it.
6
+ class IParser < AbsoluteRenamer::WithChildren
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ require 'absolute_renamer/with_children'
2
+
3
+ module AbsoluteRenamer
4
+ # Plugins parent class.
5
+ # Plugins must inherit of it.
6
+ class IPlugin < AbsoluteRenamer::WithChildren
7
+ def self.symbol
8
+ name.intern
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # Extension of existing File class.
2
+ class << File
3
+ # Returns the extension of a file.
4
+ # path: the path of the file
5
+ # dot: starting from the end, number of dots to count before cuting extension.
6
+ def extname(path, dot = 1)
7
+ pattern = (0...dot).inject('') { |pat,x| pat << '\.[^\.]+' } << '$'
8
+ ext = File.basename(path).match(pattern).to_s
9
+ ext.empty? ? "" : ext[1..ext.length]
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # Extension of existing String class
2
+ class String
3
+ # Returns a camelized version of a string
4
+ # word_separator: a regular expression used to separate words.
5
+ # str = "hello.THE world"
6
+ # str.camelize # => "Hello.The World"
7
+ # str.camelize(/[\.]/) # => "Hello.The world"
8
+ # str # => "hello.THE world"
9
+ def camelize(word_separators = /[\W_]/)
10
+ self.clone.camelize!(word_separators)
11
+ end
12
+
13
+ # Camelizes a string and returns it.
14
+ # str = "Hello.THE World"
15
+ # str.camelize! # => "Hello.The World"
16
+ # str.camelize!(/[\.]/) # => "Hello.The World"
17
+ # str # => "Hello.The World"
18
+ def camelize!(word_separators = /[\W_]/)
19
+ self.downcase!
20
+ self.each_char.each_with_index do |c,i|
21
+ if self[i-1].chr =~ word_separators or i.zero?
22
+ self[i] = c.upcase if c =~ /[a-z]/
23
+ end
24
+ end
25
+ self
26
+ end
27
+ end
@@ -0,0 +1,91 @@
1
+ require 'optparse'
2
+ require 'absolute_renamer/iparser'
3
+ require 'absolute_renamer/use_config'
4
+ require 'pp'
5
+
6
+ module AbsoluteRenamer
7
+ # Class in charge of the command line parsing.
8
+ class Parser
9
+ class << self
10
+ include AbsoluteRenamer::UseConfig
11
+
12
+ # Calls all registred parsers.
13
+ # The parsers are written in configuration files.
14
+ # The core parsers are automaticaly added.
15
+ def parse_cmd_line
16
+ parsers_dir = conf[:path][:parsers]
17
+ parsers = conf[:parsers]
18
+
19
+ core_parsers = ['general']
20
+
21
+ parsers += core_parsers.map! { |core_parser| File.join('core', core_parser) }
22
+
23
+ parsers.each do |parser|
24
+ parser_file = File.join(parsers_dir, parser, 'parser.rb')
25
+ begin
26
+ if require parser_file
27
+ puts "Loaded: #{parser_file}" if conf[:debug]
28
+ end
29
+ rescue LoadError => e
30
+ STDERR.puts(e)
31
+ end
32
+ end
33
+
34
+ ARGV.options do |parser|
35
+ begin
36
+ list = AbsoluteRenamer::IParser.children
37
+ list.each do |class_name|
38
+ class_name.add_options(parser, conf[:options])
39
+ end
40
+ parser.parse!
41
+ rescue RuntimeError => ex
42
+ STDERR.puts(ex)
43
+ exit 1
44
+ end
45
+ conf[:files] = self.get_files(ARGV, 0) || []
46
+ conf[:options][:maxdepth] ||= 0
47
+ conf[:options][:interactive] ||= :never
48
+ conf[:options][:mode] ||= :rename
49
+ pp conf.get if conf[:debug]
50
+ end
51
+ end
52
+
53
+ # Creates a list of all files and directories to rename.
54
+ # All options that have not been matched are considered as path.
55
+ # For directories, if the recursive otpion is set to true (conf[:rec] == true),
56
+ # files are searched in sub directories.
57
+ #
58
+ # list: a list of path to explore
59
+ # depth: maximum recursion depth
60
+ #
61
+ # Returns the files/directories list
62
+ def get_files(list, depth)
63
+ files = []
64
+ options = conf[:options]
65
+
66
+ list.each do |entry|
67
+ return files unless File.exists?(entry)
68
+ is_dir = File.directory?(entry)
69
+ mod_dir = options[:dir]
70
+ depth_ok = (depth < options[:maxdepth] or options[:maxdepth].zero?)
71
+ mod_rec = (options[:rec] and depth_ok)
72
+
73
+ add_dir = (is_dir and mod_dir)
74
+ add_file = (!is_dir and !mod_dir)
75
+ add_sub = (is_dir and (mod_rec or (!mod_dir and depth < 1)))
76
+
77
+ files << FileInfo.new(entry) if (add_dir or add_file)
78
+ files += self.get_files(self.get_subentries(entry), depth + 1) if (add_sub)
79
+ end
80
+ files
81
+ end
82
+
83
+ # Returns files and directories contained in +path+.
84
+ def get_subentries(path)
85
+ files = Dir.entries(path)
86
+ files.delete_if { |file| file[0,1] == '.' }
87
+ files.collect! { |file| path + '/' + file }
88
+ end
89
+ end
90
+ end
91
+ end