filter_rename 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +82 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/filter_rename +5 -0
- data/filter_rename.gemspec +44 -0
- data/lib/filter_rename.rb +137 -0
- data/lib/filter_rename.yaml +165 -0
- data/lib/filter_rename/cli.rb +145 -0
- data/lib/filter_rename/config.rb +120 -0
- data/lib/filter_rename/filename.rb +142 -0
- data/lib/filter_rename/filename_factory.rb +36 -0
- data/lib/filter_rename/filetype/image_filename.rb +28 -0
- data/lib/filter_rename/filetype/mp3_filename.rb +82 -0
- data/lib/filter_rename/filetype/pdf_filename.rb +24 -0
- data/lib/filter_rename/filter_base.rb +191 -0
- data/lib/filter_rename/filter_pipe.rb +64 -0
- data/lib/filter_rename/filters.rb +660 -0
- data/lib/filter_rename/utils.rb +283 -0
- data/lib/filter_rename/version.rb +3 -0
- metadata +202 -0
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# FilterRename
|
2
|
+
|
3
|
+
FilterRename is a CLI tool created to rename a bunch of files at once applying a cascade of
|
4
|
+
self-explainatory options called _filters_, in order to substitute all the other shell commands
|
5
|
+
(like _sed_, _awk_, _trim_, ... ) and personal shell script to achieve that goal safely and
|
6
|
+
in comfortable manner.
|
7
|
+
|
8
|
+
To simplify the whole process through a command line, the full filename is logically organized
|
9
|
+
in _targets_ like:
|
10
|
+
|
11
|
+
<path>/<folder>/<name><ext>
|
12
|
+
|
13
|
+
Considering the file `/home/fabio/Documents/Ruby/filter_rename/Gemfile.lock` we have:
|
14
|
+
|
15
|
+
- path: */home/fabio/Documents/Ruby*
|
16
|
+
- folder: *filter_rename*
|
17
|
+
- name: *Gemfile*
|
18
|
+
- ext: *.lock*
|
19
|
+
|
20
|
+
The chain of filters will be applied to the current _target_ which can be selected using the *--select*
|
21
|
+
option, the default target is _name_.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Install it yourself as:
|
26
|
+
|
27
|
+
$ gem install filter_rename
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
The main concept about FilterRename is the _target_ manipulation with a chain of _filters_
|
32
|
+
executed one by one as they appear in the argument's list.
|
33
|
+
|
34
|
+
There are three main operations:
|
35
|
+
* _preview_: show the results without make any change (default);
|
36
|
+
* _dry-run_: executes a simulation warning when a conflict is raised;
|
37
|
+
* _apply_: confirm the changes and rename the files unless the destination file exists.
|
38
|
+
|
39
|
+
Having the files:
|
40
|
+
|
41
|
+
home
|
42
|
+
fabio
|
43
|
+
Documents
|
44
|
+
Photos
|
45
|
+
Vacations
|
46
|
+
image_portofino_1.jpg
|
47
|
+
image_portofino_2.jpg
|
48
|
+
image_portofino_3.jpg
|
49
|
+
...
|
50
|
+
|
51
|
+
to replace _underscores_ with _spaces_ and _capitalize_ each word let's execute:
|
52
|
+
|
53
|
+
filter_rename /home/fabio/Documents/Photos/Vacations/*.jpg --spacify '_' --capitalize --apply
|
54
|
+
|
55
|
+
and the result will be:
|
56
|
+
|
57
|
+
home
|
58
|
+
fabio
|
59
|
+
Documents
|
60
|
+
Photos
|
61
|
+
Vacations
|
62
|
+
Image Portofino 1.jpg
|
63
|
+
Image Portofino 2.jpg
|
64
|
+
Image Portofino 3.jpg
|
65
|
+
...
|
66
|
+
|
67
|
+
|
68
|
+
## Get help
|
69
|
+
|
70
|
+
Where to start
|
71
|
+
|
72
|
+
$ filter_rename --help
|
73
|
+
|
74
|
+
More help
|
75
|
+
|
76
|
+
- The wiki page: https://github.com/fabiomux/filter_rename/wiki
|
77
|
+
|
78
|
+
|
79
|
+
## Contributing
|
80
|
+
|
81
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fabio_mux/filter_rename. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
82
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "filter_rename"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/filter_rename
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'filter_rename/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "filter_rename"
|
8
|
+
spec.version = FilterRename::VERSION
|
9
|
+
spec.authors = ["Fabio Mucciante"]
|
10
|
+
spec.email = ["fabio.mucciante@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Rename filenames through a chain of filter.}
|
13
|
+
spec.description = %q{Rename filenames applying a cascade of commands called filters in specified portion of it called targets.}
|
14
|
+
spec.homepage = "https://github.com/fabiomux/filter_rename"
|
15
|
+
spec.license = "GPL-3.0"
|
16
|
+
|
17
|
+
spec.metadata = {
|
18
|
+
"bug_tracker_uri" => "https://github.com/fabiomux/filter_rename/issues",
|
19
|
+
#"changelog_uri" => "",
|
20
|
+
"documentation_uri" => "https://www.rubydoc.info/gems/filter_rename/#{spec.version}",
|
21
|
+
"homepage_uri" => "https://github.com/fabiomux/filter_rename",
|
22
|
+
#"mailing_list_uri" => "",
|
23
|
+
"source_code_uri" => "https://github.com/fabiomux/filter_rename",
|
24
|
+
"wiki_uri" => "https://github.com/fabiomux/filter_rename/wiki"
|
25
|
+
}
|
26
|
+
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
28
|
+
f.match(%r{^(test|spec|features)/})
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
37
|
+
|
38
|
+
spec.add_runtime_dependency "mp3info"
|
39
|
+
spec.add_runtime_dependency "mimemagic"
|
40
|
+
spec.add_runtime_dependency "differ"
|
41
|
+
spec.add_runtime_dependency "exiv2"
|
42
|
+
spec.add_runtime_dependency "fastimage"
|
43
|
+
spec.add_runtime_dependency "pdf-reader"
|
44
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'filter_rename/version'
|
3
|
+
require 'filter_rename/config'
|
4
|
+
require 'filter_rename/filter_base'
|
5
|
+
require 'filter_rename/filters'
|
6
|
+
require 'filter_rename/filter_pipe'
|
7
|
+
require 'filter_rename/filename'
|
8
|
+
require 'filter_rename/filename_factory'
|
9
|
+
require 'filter_rename/utils'
|
10
|
+
|
11
|
+
module FilterRename
|
12
|
+
|
13
|
+
class Builder
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@cfg = Config.new(options.global)
|
17
|
+
@filters = FilterList.new(options.filters)
|
18
|
+
@files = options.files
|
19
|
+
@show_macro = options.show_macro
|
20
|
+
end
|
21
|
+
|
22
|
+
def preview
|
23
|
+
raise MissingFiles if @files.empty?
|
24
|
+
|
25
|
+
@filters.expand_macros!(@cfg.macro)
|
26
|
+
|
27
|
+
Messages.label "Preview:"
|
28
|
+
|
29
|
+
@files.each do |src|
|
30
|
+
fp = FilterPipe.new(src, @filters, @cfg).apply
|
31
|
+
Messages.diff(fp)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def apply
|
36
|
+
raise MissingFiles if @files.empty?
|
37
|
+
|
38
|
+
@filters.expand_macros!(@cfg.macro)
|
39
|
+
|
40
|
+
Messages.label "Apply:"
|
41
|
+
@files.each do |src|
|
42
|
+
|
43
|
+
fp = FilterPipe.new(src, @filters, @cfg).apply
|
44
|
+
|
45
|
+
if fp.changed?
|
46
|
+
old_data = fp.rename!
|
47
|
+
|
48
|
+
if old_data[:full_filename]
|
49
|
+
Messages.renamed!(old_data, fp.dest)
|
50
|
+
Messages.changed_tags(fp, old_data, false) if fp.source.class.has_writable_tags
|
51
|
+
else
|
52
|
+
Messages.changed_tags(fp, old_data)
|
53
|
+
end
|
54
|
+
|
55
|
+
else
|
56
|
+
Messages.skipping(fp)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def globals
|
62
|
+
Messages.label 'Global configurations:'
|
63
|
+
Messages.config_list @cfg.global
|
64
|
+
end
|
65
|
+
|
66
|
+
def configs
|
67
|
+
Messages.label 'Filter\'s configurations:'
|
68
|
+
Messages.config_list @cfg.filter
|
69
|
+
end
|
70
|
+
|
71
|
+
def words
|
72
|
+
Messages.label 'Groups and subgroups of words available for translation:'
|
73
|
+
Messages.config_multilist @cfg.words
|
74
|
+
end
|
75
|
+
|
76
|
+
def dry_run
|
77
|
+
raise MissingFiles if @files.empty?
|
78
|
+
|
79
|
+
@filters.expand_macros!(@cfg.macro)
|
80
|
+
cache = []
|
81
|
+
|
82
|
+
Messages.label "Dry Run:"
|
83
|
+
@files.each do |src|
|
84
|
+
|
85
|
+
fp = FilterPipe.new(src, @filters, @cfg).apply
|
86
|
+
|
87
|
+
if fp.unchanged?
|
88
|
+
Messages.skipping(fp)
|
89
|
+
elsif (cache.include?(fp.dest.full_filename) || fp.dest.exists?)
|
90
|
+
if fp.source.full_filename == fp.dest.full_filename
|
91
|
+
Messages.changed_tags(fp)
|
92
|
+
else
|
93
|
+
Messages.file_exists(fp)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
Messages.renamed(fp)
|
97
|
+
Messages.changed_tags(fp, {}, false) if fp.source.class.has_writable_tags
|
98
|
+
cache << fp.dest.full_filename
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def targets
|
104
|
+
raise MissingFiles if @files.empty?
|
105
|
+
|
106
|
+
Messages.label 'Targets:'
|
107
|
+
@files.each do |src|
|
108
|
+
|
109
|
+
fname = FilenameFactory.create(src, @cfg.global)
|
110
|
+
|
111
|
+
Messages.multi fname.full_filename
|
112
|
+
Messages.send(@cfg.global.targets == :short ? :short_targets : :long_targets, fname)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def macros
|
117
|
+
Messages.label 'Macros:'
|
118
|
+
Messages.list @cfg.macro.get_macros
|
119
|
+
end
|
120
|
+
|
121
|
+
def show_macro
|
122
|
+
Messages.label "Macro: #{@show_macro}"
|
123
|
+
macro = @cfg.macro.get_macro(@show_macro)
|
124
|
+
if macro.class == Array
|
125
|
+
macro.each do |k|
|
126
|
+
Messages.item k.keys.first.to_s + ': ' + k.values.first.map { |x| '"' + x.to_s.green + '"' }.join(',')
|
127
|
+
end
|
128
|
+
elsif macro.class == Hash
|
129
|
+
macro.each do |k, v|
|
130
|
+
Messages.item k.to_s + ': ' + v.map { |x| '"' + x.to_s.green + '"' }.join(', ')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
---
|
2
|
+
:global:
|
3
|
+
:hash_type: :none
|
4
|
+
:date_format: '%Y-%m-%d'
|
5
|
+
:counter_length: 3
|
6
|
+
:counter_start: 0
|
7
|
+
:targets: :short
|
8
|
+
:pdf_metadata: false
|
9
|
+
:image_metadata: false
|
10
|
+
:filter:
|
11
|
+
:lang: :en
|
12
|
+
:word_separator: " "
|
13
|
+
:target: :name
|
14
|
+
:ignore_case: true
|
15
|
+
:grep: '.*'
|
16
|
+
:grep_on: :source
|
17
|
+
:grep_exclude: false
|
18
|
+
# full_filename, :full_path, :filename, <target>
|
19
|
+
:grep_target: :full_filename
|
20
|
+
:words:
|
21
|
+
:shortcut:
|
22
|
+
:short: ['Vol', 'Num']
|
23
|
+
:long: ['Volume', 'Number']
|
24
|
+
:general:
|
25
|
+
:en: ['Hello']
|
26
|
+
:it: ['Ciao']
|
27
|
+
:fr: ['Salut']
|
28
|
+
:es: ['Hola']
|
29
|
+
:pt: ['Olá']
|
30
|
+
:short_months:
|
31
|
+
:en: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
32
|
+
:it: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
|
33
|
+
:fr: ['Janv', 'Févr', 'Mars', 'Avril', 'Mai', 'Juin', 'Juil', 'Août|Aout', 'Sept', 'Oct', 'Nov', 'Déc|Dec']
|
34
|
+
:es: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']
|
35
|
+
:pt: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']
|
36
|
+
:de: ['Jan', 'Feb', 'Mär|Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
|
37
|
+
:hr: ['Sij', 'Vel', 'Ožu|Ozu', 'Tra', 'Svi', 'Lip', 'Srp', 'Kol', 'Ruj', 'Lis', 'Stu', 'Pro']
|
38
|
+
:long_months:
|
39
|
+
:en: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
40
|
+
:it: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre']
|
41
|
+
:fr: ['Janvier', 'Février|Fevrier', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août|Aout', 'Septembre', 'Octobre', 'Novembre', 'Décembre|Decembre']
|
42
|
+
:es: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']
|
43
|
+
:pt: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro']
|
44
|
+
:de: ['Januar[^y]', 'Februar[^y]', 'März|Marz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
|
45
|
+
:hr: ['Siječanj|Sijecanj', 'Veljača|Veljaca', 'Ožujak|Ozujak', 'Travanj', 'Svibanj', 'Lipanj', 'Srpanj', 'Kolovoz', 'Rujan', 'Listopad', 'Studeni', 'Prosinac']
|
46
|
+
:short_days:
|
47
|
+
:en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
48
|
+
:it: ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom']
|
49
|
+
:fr: ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']
|
50
|
+
:es: ['Lun', 'Mar', 'Mié|Mie', 'Jue', 'Vie', 'Sáb|Sab', 'Dom']
|
51
|
+
:pt: ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb|Sab', 'Dom']
|
52
|
+
:de: ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
|
53
|
+
:hr: ['Pon', 'Uto', 'Sri', 'čet|cet', 'Pet', 'Sub', 'Ned']
|
54
|
+
:long_days:
|
55
|
+
:en: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
56
|
+
:it: ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica']
|
57
|
+
:fr: ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
|
58
|
+
:es: ['Lunes', 'Martes', 'Miércoles|Miercoles', 'Jueves', 'Viernes', 'Sábado|Sabado', 'Domingo']
|
59
|
+
:pt: ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado|Sabado', 'Domingo']
|
60
|
+
:de: ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
61
|
+
:hr: ['Ponedjeljak', 'Utorak', 'Srijeda', 'četvrtak|Cetvrtak', 'Petak', 'Subota', 'Nedjelja']
|
62
|
+
|
63
|
+
:macro:
|
64
|
+
humanize:
|
65
|
+
-
|
66
|
+
:spacify:
|
67
|
+
- '_'
|
68
|
+
- "\\."
|
69
|
+
-
|
70
|
+
:squeeze:
|
71
|
+
- ' '
|
72
|
+
-
|
73
|
+
:translate_words:
|
74
|
+
- :long_months
|
75
|
+
- :it
|
76
|
+
- :en
|
77
|
+
-
|
78
|
+
:translate_words:
|
79
|
+
- :long_months
|
80
|
+
- :es
|
81
|
+
- :en
|
82
|
+
-
|
83
|
+
:translate_words:
|
84
|
+
- :long_months
|
85
|
+
- :fr
|
86
|
+
- :en
|
87
|
+
-
|
88
|
+
:translate_words:
|
89
|
+
- :long_months
|
90
|
+
- :de
|
91
|
+
- :en
|
92
|
+
-
|
93
|
+
:translate_words:
|
94
|
+
- :long_months
|
95
|
+
- :hr
|
96
|
+
- :en
|
97
|
+
-
|
98
|
+
:translate_words:
|
99
|
+
- :long_months
|
100
|
+
- :pt
|
101
|
+
- :en
|
102
|
+
-
|
103
|
+
:replace_date:
|
104
|
+
- '<B> <Y>'
|
105
|
+
- '<Y>-<m>'
|
106
|
+
-
|
107
|
+
:replace:
|
108
|
+
- ' ,[ ]*'
|
109
|
+
- ', '
|
110
|
+
-
|
111
|
+
:replace:
|
112
|
+
- '([a-z]) - ([0-9])'
|
113
|
+
- '\1 \2'
|
114
|
+
append_issue:
|
115
|
+
:move_to:
|
116
|
+
- '(Issue|Volume) ([0-9]+)'
|
117
|
+
- 'tmp'
|
118
|
+
:template:
|
119
|
+
- '<name> #<tmp>'
|
120
|
+
:delete:
|
121
|
+
- ' #$'
|
122
|
+
:squeeze:
|
123
|
+
- ' '
|
124
|
+
file_to_id3:
|
125
|
+
-
|
126
|
+
:copy_to:
|
127
|
+
- "([^-]+) -"
|
128
|
+
- "artist"
|
129
|
+
-
|
130
|
+
:copy_to:
|
131
|
+
- "- ([^-]+)"
|
132
|
+
- "title"
|
133
|
+
id3_to_file:
|
134
|
+
:template:
|
135
|
+
- "<artist> - <title>"
|
136
|
+
remove_id3_comments:
|
137
|
+
:select:
|
138
|
+
- "comments"
|
139
|
+
:set:
|
140
|
+
- ''
|
141
|
+
camels_to_words:
|
142
|
+
-
|
143
|
+
:config:
|
144
|
+
- ignore_case:false
|
145
|
+
-
|
146
|
+
:replace:
|
147
|
+
- "([A-Z])([a-z!']+)"
|
148
|
+
- " \\1\\2 "
|
149
|
+
-
|
150
|
+
:replace:
|
151
|
+
- '\( +'
|
152
|
+
- '('
|
153
|
+
-
|
154
|
+
:replace:
|
155
|
+
- ' +\)'
|
156
|
+
- ')'
|
157
|
+
-
|
158
|
+
:squeeze:
|
159
|
+
- " "
|
160
|
+
-
|
161
|
+
:trim: []
|
162
|
+
remove_dash_separator:
|
163
|
+
:replace:
|
164
|
+
- '([a-z]) - ([0-9])'
|
165
|
+
- '\1 \2'
|