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.
- 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
|