AbsoluteRenamer 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AbsoluteRenamer.gemspec +40 -0
- data/COPYING +340 -0
- data/ChangeLog +0 -0
- data/LICENCE +17 -0
- data/Manifest.txt +42 -0
- data/README +34 -0
- data/Rakefile +20 -0
- data/bin/absrenamer +54 -0
- data/conf/absrenamer/absrenamer.conf +34 -0
- data/lib/absolute_renamer.rb +119 -0
- data/lib/absolute_renamer/config.rb +40 -0
- data/lib/absolute_renamer/external.rb +56 -0
- data/lib/absolute_renamer/external/modules/core/case/module.rb +31 -0
- data/lib/absolute_renamer/external/modules/core/general/module.rb +126 -0
- data/lib/absolute_renamer/external/parsers/core/general/parser.rb +74 -0
- data/lib/absolute_renamer/external/parsers/interactive/parser.rb +20 -0
- data/lib/absolute_renamer/external/parsers/listing/parser.rb +9 -0
- data/lib/absolute_renamer/external/plugins/interactive/plugin.rb +35 -0
- data/lib/absolute_renamer/external/plugins/listing/plugin.rb +14 -0
- data/lib/absolute_renamer/file_info.rb +90 -0
- data/lib/absolute_renamer/imodule.rb +68 -0
- data/lib/absolute_renamer/iparser.rb +8 -0
- data/lib/absolute_renamer/iplugin.rb +11 -0
- data/lib/absolute_renamer/libs/file.rb +11 -0
- data/lib/absolute_renamer/libs/string.rb +27 -0
- data/lib/absolute_renamer/parser.rb +91 -0
- data/lib/absolute_renamer/use_config.rb +11 -0
- data/lib/absolute_renamer/with_children.rb +22 -0
- data/man/man8/absrenamer.8 +67 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/setup.rb +1585 -0
- data/test/unit/features/test_absrenamer.conf +34 -0
- data/test/unit/tc_config.rb +28 -0
- data/test/unit/tc_file.rb +16 -0
- data/test/unit/tc_fileinfo.rb +17 -0
- data/test/unit/tc_imodule.rb +29 -0
- data/test/unit/tc_iplugin.rb +8 -0
- data/test/unit/tc_parser.rb +13 -0
- data/test/unit/tc_string.rb +24 -0
- data/test/unit/test_suite.rb +28 -0
- 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,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,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
|