AbsoluteRenamer 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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