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
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
%w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
2
|
+
require File.dirname(__FILE__) + '/lib/absolute_renamer'
|
3
|
+
|
4
|
+
$hoe = Hoe.new('AbsoluteRenamer', AbsoluteRenamer::VERSION) do |p|
|
5
|
+
p.developer('Simon COURTOIS', 'happynoff@free.fr')
|
6
|
+
p.changes = p.paragraphs_of("ChangeLog", 0..1).join("\n\n")
|
7
|
+
p.rubyforge_name = p.name
|
8
|
+
|
9
|
+
p.extra_dev_deps = [
|
10
|
+
['newgem', ">= #{::Newgem::VERSION}"]
|
11
|
+
]
|
12
|
+
|
13
|
+
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
14
|
+
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
15
|
+
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
16
|
+
p.rsync_args = '-av --delete --ignore-errors'
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'newgem/tasks' # load /tasks/*.rake
|
20
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
data/bin/absrenamer
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# = Synopsis
|
5
|
+
#
|
6
|
+
# Does a batch renaming on files and directories
|
7
|
+
#
|
8
|
+
# = Usage
|
9
|
+
#
|
10
|
+
# absrenamer [options] [file]...
|
11
|
+
#
|
12
|
+
# = Description
|
13
|
+
#
|
14
|
+
# AbsoluteRenamer is a very powerful tool that helps files and directories
|
15
|
+
# renaming using the Krename syntax.
|
16
|
+
#
|
17
|
+
# It is extendable by adding new command line parsers,
|
18
|
+
# new renaming modules and plugins.
|
19
|
+
#
|
20
|
+
# Parsers allow to add new command line options.
|
21
|
+
# Modules allow to add new renaming patterns (like ID3 tags).
|
22
|
+
# Plugins allow to add new features like file listing instead of renaming.
|
23
|
+
#
|
24
|
+
# = Exemple
|
25
|
+
#
|
26
|
+
# absrenamer-f '[1;2]_[*4-]' *.mp3
|
27
|
+
# # => takes the first two characters of the original name
|
28
|
+
# # and Camelizes from the fourth to the end.
|
29
|
+
#
|
30
|
+
# = Copyright (c) 2009 Simon COURTOIS
|
31
|
+
# Licensed under the GNU Public License
|
32
|
+
|
33
|
+
$:.unshift File.dirname(__FILE__) << '/../lib'
|
34
|
+
|
35
|
+
require 'absolute_renamer'
|
36
|
+
require 'absolute_renamer/config'
|
37
|
+
require 'absolute_renamer/parser'
|
38
|
+
require 'absolute_renamer/external'
|
39
|
+
|
40
|
+
config_path = File.dirname(__FILE__)+'/../conf/absrenamer/absrenamer.conf'
|
41
|
+
config_path = '/etc/absrenamer/absrenamer.conf' if File.exists?('/etc/absrenamer/absrenamer.conf')
|
42
|
+
custom_conf = '~/.absrenamerrc'
|
43
|
+
|
44
|
+
AbsoluteRenamer::Config.load(config_path)
|
45
|
+
AbsoluteRenamer::Config.load(custom_conf) if File.exists? custom_conf
|
46
|
+
|
47
|
+
AbsoluteRenamer::Parser.parse_cmd_line
|
48
|
+
|
49
|
+
AbsoluteRenamer::External.load_modules
|
50
|
+
AbsoluteRenamer::External.load_plugins
|
51
|
+
|
52
|
+
AbsoluteRenamer::Processor.load_plugins
|
53
|
+
AbsoluteRenamer::Processor.create_names_list
|
54
|
+
AbsoluteRenamer::Processor.do_renaming
|
@@ -0,0 +1,34 @@
|
|
1
|
+
:debug: false
|
2
|
+
|
3
|
+
:options:
|
4
|
+
# Prompt :never, :once or :always before renaming
|
5
|
+
:interactive: :never
|
6
|
+
:format: '$'
|
7
|
+
:ext_format: '$'
|
8
|
+
|
9
|
+
# Renaming mode: :rename, :copy, :move, :link
|
10
|
+
:mode: :rename
|
11
|
+
:maxdepth: 0
|
12
|
+
|
13
|
+
# used when a value is not available (pdfAuthor for a PNG file, ...)
|
14
|
+
:default_string: '---'
|
15
|
+
|
16
|
+
# Word separator, regular expression statements can be used
|
17
|
+
# default: '\W_'
|
18
|
+
# This string will be used in AbsoluteRenamer just like this :
|
19
|
+
# [^WORD_SEPARATOR]
|
20
|
+
# Then, \W_ => [^\W_]
|
21
|
+
:word_separator: '\W_'
|
22
|
+
|
23
|
+
:path:
|
24
|
+
:modules: absolute_renamer/external/modules
|
25
|
+
:parsers: absolute_renamer/external/parsers
|
26
|
+
:plugins: absolute_renamer/external/plugins
|
27
|
+
|
28
|
+
:parsers:
|
29
|
+
- listing
|
30
|
+
- interactive
|
31
|
+
|
32
|
+
:plugins:
|
33
|
+
- listing
|
34
|
+
- interactive
|
@@ -0,0 +1,119 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'absolute_renamer/config'
|
5
|
+
require 'absolute_renamer/with_children'
|
6
|
+
require 'absolute_renamer/external'
|
7
|
+
require 'absolute_renamer/parser'
|
8
|
+
require 'absolute_renamer/file_info'
|
9
|
+
require 'absolute_renamer/libs/file'
|
10
|
+
require 'absolute_renamer/libs/string'
|
11
|
+
require 'absolute_renamer/use_config'
|
12
|
+
|
13
|
+
# top level module of AbsoluteRenamer.
|
14
|
+
module AbsoluteRenamer
|
15
|
+
VERSION = "0.9.0"
|
16
|
+
|
17
|
+
# The main class of AbsoluteRenamer.
|
18
|
+
#
|
19
|
+
# Organizes the files and directories renaming process.
|
20
|
+
class Processor
|
21
|
+
class << self
|
22
|
+
include AbsoluteRenamer::UseConfig
|
23
|
+
|
24
|
+
# Creates the new names for each file passed to AbsoluteRenamer.
|
25
|
+
#
|
26
|
+
# Asks to each module if he as something to replace
|
27
|
+
# in the name of each file.
|
28
|
+
#
|
29
|
+
# Calls the +before_names_generation+ entry point.
|
30
|
+
def create_names_list
|
31
|
+
call_entry_point(:before_names_generation)
|
32
|
+
return if conf[:files].empty?
|
33
|
+
mods = {}
|
34
|
+
conf[:files].each do |file|
|
35
|
+
name = conf[:options][:format].clone
|
36
|
+
ext = conf[:options][:ext_format].clone
|
37
|
+
do_replacements(file.name, :before)
|
38
|
+
AbsoluteRenamer::IModule.children.each do |mod|
|
39
|
+
mod_sym = mod.symbol
|
40
|
+
mods[mod_sym] ||= mod.new
|
41
|
+
name = mods[mod_sym].process(file, name)
|
42
|
+
ext = mods[mod_sym].process(file, ext, :ext) unless file.dir
|
43
|
+
end
|
44
|
+
do_replacements(name, :after)
|
45
|
+
file.new_name = name
|
46
|
+
file.new_name << '.' << ext unless (file.dir or file.ext.empty?)
|
47
|
+
end
|
48
|
+
mods.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
# For each file/dir replaces his name by his new name.
|
52
|
+
#
|
53
|
+
# Calls the following entry points :
|
54
|
+
# - before_batch_renaming ;
|
55
|
+
# - before_file_renaming ;
|
56
|
+
# - after_file_renaming ;
|
57
|
+
# - after_batch_renaming.
|
58
|
+
def do_renaming
|
59
|
+
if call_entry_point(:before_batch_renaming)
|
60
|
+
conf[:files].each do |file|
|
61
|
+
if call_entry_point(:before_file_renaming, :file => file)
|
62
|
+
if file.respond_to?(conf[:options][:mode])
|
63
|
+
file.send(conf[:options][:mode])
|
64
|
+
end
|
65
|
+
call_entry_point(:after_file_renaming, :file => file)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
call_entry_point(:after_batch_renaming)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Loads the plugins list in the @plugins variable.
|
73
|
+
def load_plugins
|
74
|
+
@plugins = {}
|
75
|
+
AbsoluteRenamer::IPlugin.children.each do |plugin|
|
76
|
+
@plugins[plugin.symbol] = plugin.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Calls the given entry point for each plugin available
|
81
|
+
#
|
82
|
+
# Ask to each plugin if it implements the entry point
|
83
|
+
# and calls it with params if it isn't null.
|
84
|
+
# It keeps going will all plugins return true.
|
85
|
+
#
|
86
|
+
# returns true if all plugins returned true
|
87
|
+
def call_entry_point(ep, params = nil)
|
88
|
+
puts "Plugin Entry Point: #{ep}" if conf[:debug]
|
89
|
+
keep_going = true
|
90
|
+
@plugins.each_value do |plugin|
|
91
|
+
if plugin.respond_to?(ep)
|
92
|
+
keep_going &= params.nil? ? plugin.send(ep) : plugin.send(ep, params)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
keep_going
|
96
|
+
end
|
97
|
+
|
98
|
+
# Applies replacements
|
99
|
+
#
|
100
|
+
# The replacements are stored in conf[options][replacements][moment]
|
101
|
+
# and are provided as command line paramters.
|
102
|
+
#
|
103
|
+
# format represents the string in which the replacements are done
|
104
|
+
# moment determines which replacements are done (on old or new name)
|
105
|
+
def do_replacements(format, moment)
|
106
|
+
begin
|
107
|
+
replacements = conf[:options][:replacements][moment]
|
108
|
+
rescue NoMethodError
|
109
|
+
replacements = nil
|
110
|
+
end
|
111
|
+
unless replacements.nil?
|
112
|
+
replacements.each do |repl|
|
113
|
+
format.gsub!(repl[:pattern], repl[:replace])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module AbsoluteRenamer
|
4
|
+
# Class handeling the configuration.
|
5
|
+
class Config
|
6
|
+
class << self
|
7
|
+
# Open and load a Yaml file into the +@conf+ variable.
|
8
|
+
# config_path: path of the file to load.
|
9
|
+
def load(config_path)
|
10
|
+
@conf = YAML::load_file(config_path)
|
11
|
+
|
12
|
+
@conf[:options] ||= {}
|
13
|
+
@conf[:options][:format] ||= '$'
|
14
|
+
@conf[:options][:ext_format] ||= '$'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a configuration value identified by +key+.
|
18
|
+
# If +key+ is ignored, returns the +@conf+ hash.
|
19
|
+
def get(key = nil)
|
20
|
+
return @conf[key] if @conf.has_key?(key)
|
21
|
+
return @conf
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets a configuration value in the +@conf+ variable.
|
25
|
+
def set(key, value = '')
|
26
|
+
@conf[key] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a configuration value identified by +key+.
|
30
|
+
def [](key)
|
31
|
+
@conf[key]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets a configuration value in the +@conf+ variable.
|
35
|
+
def []=(key, value)
|
36
|
+
@conf[key] = value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'absolute_renamer/imodule'
|
2
|
+
require 'absolute_renamer/iplugin'
|
3
|
+
require 'absolute_renamer/use_config'
|
4
|
+
|
5
|
+
module AbsoluteRenamer
|
6
|
+
# Class in charge of loading +modules+ and +plugins+.
|
7
|
+
class External
|
8
|
+
class << self
|
9
|
+
include AbsoluteRenamer::UseConfig
|
10
|
+
|
11
|
+
# Loads the additional and core modules.
|
12
|
+
# The modules list is get from the conf[:modules] variable.
|
13
|
+
# The core modules are loaded after the additional ones.
|
14
|
+
#
|
15
|
+
# See also load
|
16
|
+
def load_modules
|
17
|
+
puts "[Loading modules]" if conf[:debug]
|
18
|
+
|
19
|
+
modules = conf[:modules]
|
20
|
+
load(modules, :modules, 'module.rb') unless modules.nil?
|
21
|
+
|
22
|
+
core_modules = ['case', 'general'].map! { |core_module| File.join('core', core_module) }
|
23
|
+
load(core_modules, :modules, 'module.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Loads the plugins.
|
27
|
+
# The plugins list is get from the conf[:plugins] variable.
|
28
|
+
#
|
29
|
+
# See also load
|
30
|
+
def load_plugins
|
31
|
+
puts "[Loading plugins]" if conf[:debug]
|
32
|
+
|
33
|
+
load(conf[:plugins], :plugins, 'plugin.rb')
|
34
|
+
end
|
35
|
+
|
36
|
+
# Loads an external list (+modules+ or +plugins+)
|
37
|
+
# externals: a list of +externals+ names to load.
|
38
|
+
# type: a symbol defining which type of external to load
|
39
|
+
# file: the filename to require to load the +externals+
|
40
|
+
def load(externals, type, file)
|
41
|
+
ext_dir = conf[:path][type]
|
42
|
+
|
43
|
+
externals.each do |external|
|
44
|
+
ext_to_load = File.join(ext_dir, external, file)
|
45
|
+
begin
|
46
|
+
if require ext_to_load
|
47
|
+
puts "Loaded: #{ext_to_load}" if conf[:debug]
|
48
|
+
end
|
49
|
+
rescue LoadError => e
|
50
|
+
STDERR.puts(e)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AbsoluteRenamer
|
2
|
+
class CaseModule < AbsoluteRenamer::IModule
|
3
|
+
class << self
|
4
|
+
attr_reader :actions
|
5
|
+
|
6
|
+
def actions
|
7
|
+
@actions ||= {'*' => :camelize,
|
8
|
+
'&' => :upper,
|
9
|
+
'%' => :lower,
|
10
|
+
'$' => :original
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def camelize(str)
|
15
|
+
str.camelize
|
16
|
+
end
|
17
|
+
|
18
|
+
def original(str)
|
19
|
+
str
|
20
|
+
end
|
21
|
+
|
22
|
+
def lower(str)
|
23
|
+
str.downcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def upper(str)
|
27
|
+
str.upcase
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module AbsoluteRenamer
|
2
|
+
class GeneralModule < AbsoluteRenamer::IModule
|
3
|
+
def initialize
|
4
|
+
@actions = {'*' => :file_camelize,
|
5
|
+
'$' => :file_original,
|
6
|
+
'%' => :file_downcase,
|
7
|
+
'&' => :file_upcase,
|
8
|
+
'\\' => :file_strip,
|
9
|
+
'#' => :count
|
10
|
+
}
|
11
|
+
|
12
|
+
@case_filters = ['(\\\\\*)', # \*
|
13
|
+
'(\\\\\$)', # \$
|
14
|
+
'(\\\%)', # \%
|
15
|
+
'(\\\&)', # \&
|
16
|
+
'(\\\\\\\)', # \\
|
17
|
+
'(\*)', # *
|
18
|
+
'(\$)', # $
|
19
|
+
'(%)', # %
|
20
|
+
'(&)', # &
|
21
|
+
'(\\\)' # \
|
22
|
+
]
|
23
|
+
|
24
|
+
# matches strings like [42-43] [42-] [*42-43] [42;43] etc...
|
25
|
+
@part_filters = ['(\[(.)?(\d+)(((-)(\d+)?)|((;)(\d+)))?\])']
|
26
|
+
|
27
|
+
# matches counters like # ### #{2} ##{2;42} or [length-42]
|
28
|
+
@misc_filters = ['(/)', '(#+(\{.*\})?)', '(\[length(--?\d+)?\])']
|
29
|
+
|
30
|
+
@filters = @case_filters + @part_filters + @misc_filters
|
31
|
+
end
|
32
|
+
|
33
|
+
def interpret(file, infos, type)
|
34
|
+
if (infos[0].length == 1)
|
35
|
+
self.method(@actions[infos[0][0].chr]).call(file, infos, type)
|
36
|
+
elsif (infos[0][1..6] == 'length')
|
37
|
+
self.length(file, infos, type)
|
38
|
+
elsif (infos[0][0].chr == '[')
|
39
|
+
self.file_part(file, infos, type)
|
40
|
+
elsif (infos[0][0].chr == '#')
|
41
|
+
self.count(file, infos, type)
|
42
|
+
else
|
43
|
+
infos[0][1].chr
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def file_camelize(file, infos, type)
|
48
|
+
file.send(type).camelize
|
49
|
+
end
|
50
|
+
|
51
|
+
def file_original(file, infos, type)
|
52
|
+
file.send(type)
|
53
|
+
end
|
54
|
+
|
55
|
+
def file_downcase(file, infos, type)
|
56
|
+
file.send(type).downcase
|
57
|
+
end
|
58
|
+
|
59
|
+
def file_upcase(file, infos, type)
|
60
|
+
file.send(type).upcase
|
61
|
+
end
|
62
|
+
|
63
|
+
def file_strip(file, infos, type)
|
64
|
+
file.send(type).strip
|
65
|
+
end
|
66
|
+
|
67
|
+
def file_part(file, infos, type)
|
68
|
+
matched = infos[0].match(/(\[([^\d])?(\d+)(((;)(\d+))|((-)(\d+)?))?\])/)
|
69
|
+
|
70
|
+
modifier = matched[2]
|
71
|
+
x = matched[3].to_i - 1
|
72
|
+
y = matched[7] || matched[10]
|
73
|
+
y = y.to_i unless y.nil?
|
74
|
+
action = matched[6] || matched[9]
|
75
|
+
|
76
|
+
str = file.send(type)
|
77
|
+
|
78
|
+
if (action == '-')
|
79
|
+
y -= 1 unless y.nil?
|
80
|
+
y ||= str.length
|
81
|
+
val = str[x..y]
|
82
|
+
elsif (action == ';')
|
83
|
+
val = str[x, y]
|
84
|
+
else
|
85
|
+
val = str[x].chr
|
86
|
+
end
|
87
|
+
|
88
|
+
unless modifier.nil?
|
89
|
+
mp = CaseModule.method(CaseModule.actions[modifier])
|
90
|
+
val = mp.call(val)
|
91
|
+
end
|
92
|
+
val
|
93
|
+
end
|
94
|
+
|
95
|
+
def count(file, infos, type)
|
96
|
+
matched = infos[0].match(/(#+)(\{((-?\d+)(;(-?\d+)?)?)?\})?/)
|
97
|
+
@counter ||= []
|
98
|
+
@last ||= nil
|
99
|
+
@current ||= 0
|
100
|
+
|
101
|
+
@current = 0 if @last != file
|
102
|
+
@current += 1 if @last == file
|
103
|
+
|
104
|
+
start = matched[4] || 1
|
105
|
+
step = matched[6] || 1
|
106
|
+
start = start.to_i
|
107
|
+
step = step.to_i
|
108
|
+
|
109
|
+
@counter[@current] ||= {:start => start,
|
110
|
+
:step => step,
|
111
|
+
:current => start - step
|
112
|
+
}
|
113
|
+
|
114
|
+
@counter[@current][:current] += @counter[@current][:step]
|
115
|
+
@last = file
|
116
|
+
val = @counter[@current][:current].to_s.rjust(matched[1].length, '0')
|
117
|
+
val.gsub!(/(0+)-/, '-\1')
|
118
|
+
val
|
119
|
+
end
|
120
|
+
|
121
|
+
def length(file, infos, type)
|
122
|
+
matched = infos[0].match(/\[length(-(-?\d+))?\]/)
|
123
|
+
file.name.length - matched[2].to_i
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|