rfs 0.1

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 (57) hide show
  1. data/MIT-LICENSE +7 -0
  2. data/Rakefile.rb +193 -0
  3. data/bin/RenameFileSet.rbw +14 -0
  4. data/bin/rfs.rb +214 -0
  5. data/bin/rfsd.rb +8 -0
  6. data/description.txt +24 -0
  7. data/lib/RenameFileSet.bak.rb +372 -0
  8. data/lib/errors.rb +15 -0
  9. data/lib/filters.rb +68 -0
  10. data/lib/gui.rb +216 -0
  11. data/lib/innate/array.rb +104 -0
  12. data/lib/innate/coverage.rb +315 -0
  13. data/lib/innate/debug.rb +12 -0
  14. data/lib/innate/debugger.rb +944 -0
  15. data/lib/innate/file.rb +5 -0
  16. data/lib/innate/filelines.rb +148 -0
  17. data/lib/innate/kernel.rb +41 -0
  18. data/lib/innate/metaid.rb +30 -0
  19. data/lib/innate/mkdirs.rb +12 -0
  20. data/lib/innate/regexp.rb +80 -0
  21. data/lib/innate/reload.rb +11 -0
  22. data/lib/innate/roman.rb +72 -0
  23. data/lib/innate/scriptlines.rb +60 -0
  24. data/lib/innate/string.rb +56 -0
  25. data/lib/innate/test/all_tests.rb +11 -0
  26. data/lib/innate/test/files/mkdirs_dummy.file +1 -0
  27. data/lib/innate/test/files/reloadtarget.rb +5 -0
  28. data/lib/innate/test/files/reloadtarget1.rb +5 -0
  29. data/lib/innate/test/files/reloadtarget2.rb +5 -0
  30. data/lib/innate/test/test_coverage.rb +13 -0
  31. data/lib/innate/test/testarray.rb +98 -0
  32. data/lib/innate/test/testfile.rb +15 -0
  33. data/lib/innate/test/testfilelines.rb +106 -0
  34. data/lib/innate/test/testkernel.rb +30 -0
  35. data/lib/innate/test/testmkdirs.rb +19 -0
  36. data/lib/innate/test/testregexp.rb +86 -0
  37. data/lib/innate/test/testreload.rb +61 -0
  38. data/lib/innate/test/testroman.rb +54 -0
  39. data/lib/innate/test/testscriptlines.rb +39 -0
  40. data/lib/innate/test/teststring.rb +52 -0
  41. data/lib/innate/test/testtitlecase.rb +20 -0
  42. data/lib/innate/titlecase.rb +64 -0
  43. data/lib/innate/tracerequire.rb +22 -0
  44. data/lib/namesource.rb +53 -0
  45. data/lib/options.rb +64 -0
  46. data/lib/providers.rb +93 -0
  47. data/lib/regexp.rb +56 -0
  48. data/lib/rename_functions.rb +142 -0
  49. data/lib/renamer.rb +210 -0
  50. data/lib/results.rb +83 -0
  51. data/lib/test/test_helper.rb +147 -0
  52. data/tests/dir/file1 +0 -0
  53. data/tests/dir/file2 +0 -0
  54. data/tests/dir/file3 +0 -0
  55. data/tests/test_helper.rb +147 -0
  56. data/tests/test_rename_functions.rb +605 -0
  57. metadata +105 -0
data/lib/regexp.rb ADDED
@@ -0,0 +1,56 @@
1
+ class PrepareRegexp
2
+ def initialize(pattern)
3
+ @s = pattern
4
+ end
5
+
6
+ #def new_regexp(pattern)
7
+ #begin
8
+ #Regexp.new(pattern)
9
+ #end
10
+
11
+ def create(ignore_case, verbose, roman, &b)
12
+ begin
13
+ c = verbose ? VerboseRegexp : Regexp
14
+ o = ignore_case ? Regexp::IGNORECASE : nil
15
+ s = roman ? @s.gsub('(roman)', "(#{Regexp.ROMAN_PATTERN})") : @s
16
+ r = c.new s, o
17
+ r.result_proc = b if verbose
18
+ r
19
+ rescue
20
+ b.call :error, "Error parsing regular expression:\n#{$!}"
21
+ nil
22
+ end
23
+ end
24
+
25
+ def self.each(options)
26
+ options[:filter].each do |f|
27
+ if f.regexp.is_a? PrepareRegexp
28
+ f.regexp = yield :filter, f.regexp
29
+ end
30
+ end
31
+ options.each do |k, v|
32
+ if v.is_a? PrepareRegexp
33
+ options[k] = yield k, v
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ class VerboseRegexp < Regexp
40
+ attr_accessor :k
41
+ attr_accessor :result_proc
42
+
43
+ def match(*a)
44
+ m = super
45
+ s = "MatchData breakdown for #{@k}: "
46
+ if m
47
+ s += "\n" + m.display_breakdown
48
+ else
49
+ s += 'no match'
50
+ end
51
+ @result_proc.call :match, s
52
+ m
53
+ end
54
+ end
55
+
56
+
@@ -0,0 +1,142 @@
1
+
2
+ module RenameFunctions
3
+ protected
4
+ def rename_replace
5
+ rename_each_match do |file, md|
6
+ subst(file, md, opt(:replace, String))
7
+ end
8
+ end
9
+
10
+ def rename_name_source
11
+ rename_each_match do |file, md, path|
12
+ subst(file, md, opt(:source, NameSource).name(file, md, path))
13
+ end
14
+ end
15
+
16
+ def rename_add
17
+ if opt(:add, Numeric) > 0
18
+ opt(:file_provider, Provider::File::Base).reverse
19
+ end
20
+ rename_each_match do |file, md|
21
+ int = active_capture(md).to_i rescue int = active_capture(md) rescue int = 0
22
+ subst(file, md, (int + opt(:add, Numeric)).to_s)
23
+ end
24
+ end
25
+
26
+ def rename_count
27
+ count = 0
28
+ rename_each_match do |file, md|
29
+ count += 1
30
+ subst(file, md, count.to_s)
31
+ end
32
+ end
33
+
34
+ def rename_from_full_name
35
+ r = opt(:pattern, Regexp)
36
+ rename_each_match do |file, md, path|
37
+ fn = File.expand_path(File.join(path, file))
38
+ m = r.match(fn)
39
+ verbose {"match on fullname: #{m.display}"}
40
+ if m
41
+ subst(file, md, active_capture(m))
42
+ else
43
+ [:message, "no match in full name: #{fn}"]
44
+ end
45
+ end
46
+ end
47
+
48
+ def rename_move_up
49
+ rename_each_match do |file, md, path|
50
+ p = File.split(path).first
51
+ unless p == '\\'
52
+ [:full_path, File.join(p, file)]
53
+ else
54
+ nil
55
+ end
56
+ end
57
+ end
58
+
59
+ def rename_roman
60
+ s = @options[:search]
61
+ if s.is_a? Regexp
62
+ @options[:search] = s.interpolate_roman
63
+ verbose {"search: #{@options[:search].to_s}"}
64
+ end
65
+ rename_each_match do |file, md|
66
+ subst(file, md, active_capture(md).toggle_roman) if active_capture(md).length > 0
67
+ end
68
+ end
69
+
70
+ def rename_mark_change
71
+ prev = ''
72
+ rename_each_match do |file, md|
73
+ m = active_capture(md)
74
+ unless prev == m
75
+ prev = m
76
+ subst(file, opt(:replace_pattern, Regexp).match(file), opt(:replace_text, String))
77
+ end
78
+ end
79
+ end
80
+
81
+ def rename_tape_numbers
82
+ rename_each_match do |file, md|
83
+ t = active_capture(md)
84
+ n = t.to_i * 2 + (/a/i =~ t[-1,1] ? -1 : 0)
85
+ n += 2 if n < 1
86
+ subst(file, md, n.to_s)
87
+ end
88
+ end
89
+
90
+ def rename_capitalize
91
+ al = opt(:always_lower, Array) rescue %w{ vol
92
+ mp3 wav txt avi nfo pdf rar
93
+ zip rb ape sfk mpg rm qt doc }
94
+ l = opt(:lower, Array) rescue String.LOWER
95
+ dc = opt(:double_cap) rescue String.DOUBLE_CAP
96
+ ca = opt(:cap_after) rescue String.CAP_AFTER
97
+ sp = opt(:split_pattern) rescue String.SPLIT_PATTERN
98
+ u = opt(:upper) rescue String.UPPER
99
+
100
+ rename_each_match do |file, md|
101
+ o = active_capture(md)
102
+ r = o.title_case(:always_lower => al,
103
+ :lower => l,
104
+ :double_cap => dc,
105
+ :cap_after => ca,
106
+ :split_pattern => sp,
107
+ :upper => u)
108
+ subst(file, md, r)
109
+ end
110
+ end
111
+
112
+ def rename_fill
113
+ fillRE = opt :fill, Regexp
114
+ fillText = ''
115
+ verbose {"fill pattern: #{fillRE.to_s}"}
116
+ rename_each do |file, md|
117
+ if md or fillText
118
+ m = active_capture(md) if md
119
+ if md and m.length > 0 and not (fillRE and fillRE.match(file))
120
+ fillText = m
121
+ [:message, "source: #{file}"]
122
+ else
123
+ md = fillRE.match(file)
124
+ if md
125
+ [:rename, subst(file, md, fillText)]
126
+ else
127
+ [:message, "* No capture to fill * #{file}"]
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ def rename_remove
135
+ rename_each_match do |file, md, path|
136
+ :delete
137
+ end
138
+ end
139
+ end
140
+
141
+
142
+
data/lib/renamer.rb ADDED
@@ -0,0 +1,210 @@
1
+ require 'innate/mkdirs'
2
+ require 'innate/roman'
3
+ require 'innate/titlecase'
4
+ require 'providers'
5
+ require 'rename_functions'
6
+ require 'stringio'
7
+ require 'errors'
8
+ require 'options'
9
+ require 'filters'
10
+ require 'regexp'
11
+ require 'namesource'
12
+
13
+ class Renamer
14
+ def run(name, options, &block)
15
+ @result_proc = block
16
+ @options = options
17
+ method(name).call
18
+ end
19
+
20
+ def rename_functions
21
+ methods.grep(/^rename_/)
22
+ end
23
+
24
+ include RenameFunctions
25
+ include Options
26
+
27
+ protected
28
+
29
+ def subst(src, matchdata, repl)
30
+ return src unless matchdata
31
+ matchdata.sub repl, options[:capture_num]
32
+ end
33
+
34
+ def result(type, t, path = nil, file = nil)
35
+ @result_proc.call type, t.to_s, path, file
36
+ end
37
+
38
+ def verbose
39
+ @result_proc.call(:verbose, yield) if @options[:verbose]
40
+ end
41
+
42
+ def actual_rename(o, n)
43
+ File.mkdirs File.split(n).first
44
+ if File.directory? n
45
+ raise DirectoryExistsError.new("Can't overwrite an existing directory.")
46
+ else
47
+ File.rename(o, n)
48
+ end
49
+ end
50
+
51
+ def actual_delete(fn)
52
+ if File.directory? fn
53
+ Dir.delete fn
54
+ else
55
+ File.delete fn
56
+ end
57
+ end
58
+
59
+ def confirm(path, oldfn, newfn, full_path)
60
+ return true if @confirm == :all || !@options[:confirm]
61
+ exists = @options[:force] && already_exists?(path, oldfn, newfn, full_path)
62
+ @confirm = opt(:confirm, Proc, Method).call path, oldfn, newfn, full_path, exists, (@confirm || :yes)
63
+ verbose {"confirmation: #{@confirm}"}
64
+ case @confirm
65
+ when :all, :yes
66
+ return true
67
+ when :no
68
+ return false
69
+ when :cancel
70
+ throw :cancel
71
+ end
72
+ end
73
+
74
+ def dest_file(path, newfn, full_path)
75
+ if full_path
76
+ fn = newfn
77
+ else
78
+ fn = File.join(path, newfn)
79
+ end
80
+ end
81
+
82
+ def active_capture(matchdata)
83
+ n = @options[:capture_num] || 1
84
+ matchdata[n] || matchdata[0]
85
+ end
86
+
87
+ def already_exists?(path, oldfn, newfn, full_path)
88
+ fn = dest_file(path, newfn, full_path)
89
+ if File.exists? fn
90
+ fn.downcase != File.join(path, oldfn).downcase
91
+ else
92
+ false
93
+ end
94
+ end
95
+
96
+ def rename(path, oldfn, newfn, full_path = false)
97
+ old_full = File.join path, oldfn
98
+ verbose {"rename: `#{old_full}` -> `#{newfn}`"}
99
+ unless oldfn == newfn or (full_path and old_full == newfn)
100
+ t = newfn
101
+ fn = dest_file path, newfn, full_path
102
+ if !@options[:force] and already_exists?(path, oldfn, newfn, full_path)
103
+ result :fileerror, 'File already exists', path, oldfn
104
+ elsif @options[:action] == :commit
105
+ if confirm path, oldfn, newfn, full_path
106
+ begin
107
+ actual_rename old_full, fn
108
+ rescue => e
109
+ result :fileerror, "#{e.class}: #{e.message}", path, oldfn
110
+ else
111
+ result :ok, t, path, oldfn
112
+ end
113
+ else
114
+ result :skipped, oldfn, path, oldfn
115
+ end
116
+ else
117
+ result :ok, t, path, oldfn
118
+ end
119
+ end
120
+ end
121
+
122
+ def delete(path, file)
123
+ fn = File.join path, file
124
+ verbose {"delete: `#{fn}` "}
125
+ if @options[:action] == :commit
126
+ if confirm path, file, '<<deleted>>', false
127
+ begin
128
+ actual_delete fn
129
+ rescue => e
130
+ result :fileerror, "#{e.class}: #{e.message}", path, file
131
+ else
132
+ result :ok, "<<#{file}>>", path, file
133
+ end
134
+ else
135
+ result :skipped, file, path, file
136
+ end
137
+ else
138
+ result :ok, "<<#{file}>>", path, file
139
+ end
140
+ end
141
+
142
+ def rename_each
143
+ if @options[:action] == :list
144
+ @options[:file_provider].each do |path, file|
145
+ opt_if(:filter, Filter) do |filter|
146
+ unless filter.filter path, file
147
+ verbose { "filtered: #{file}" }
148
+ next
149
+ end
150
+ end
151
+ result :ok, "#{file}#{(File.directory?(File.join(path, file)) ? '/' : '')}", path, file
152
+ end
153
+ elsif @options[:action] == :commit || @options[:action] == :preview
154
+ catch :cancel do
155
+ search = opt :search, Regexp
156
+ filter = opt_if(:filter, Filter)
157
+ @options[:file_provider].each do |path, file|
158
+ verbose {'-------------------------'}
159
+ if filter; unless filter.filter path, file
160
+ verbose { "filtered: #{file}" }
161
+ next
162
+ end end
163
+ verbose {"file: #{File.join path, file}"}
164
+ md = search.match(file)
165
+ verbose {"match: #{md.display}"}
166
+ begin
167
+ action, text = r = yield(file, md, path)
168
+ verbose {"action: #{action.inspect}"}
169
+ if action == :rename
170
+ rename path, file, text if text
171
+ elsif action == :full_path
172
+ rename path, file, text, true if text
173
+ elsif action == :message
174
+ result :message, text, path, file
175
+ elsif r == :delete
176
+ delete path, file
177
+ elsif r
178
+ result :fileerror, "Unrecognised response: #{r.inspect}", path, file
179
+ end
180
+ rescue => e
181
+ if e.is_a? RenamerError or (!$DEBUG and !$TESTING)
182
+ t = file
183
+ if md
184
+ t = md.display
185
+ end
186
+ result :fileerror, "Error: #{e.class} on #{t}\n#{e.message}", path, file
187
+ else
188
+ raise e
189
+ end
190
+ end
191
+ end
192
+ end
193
+ else
194
+ raise ActionError.new
195
+ end
196
+ end
197
+
198
+ def rename_each_match
199
+ rename_each do |file, md, path|
200
+ r = yield(file, md, path) if md
201
+ if r.is_a? String
202
+ [:rename, r]
203
+ else
204
+ r
205
+ end
206
+ end
207
+ end
208
+
209
+ end
210
+
data/lib/results.rb ADDED
@@ -0,0 +1,83 @@
1
+ class CollectResults
2
+ attr_accessor :verbose, :quiet, :silent, :sideways, :lineways, :sorted, :list
3
+ attr_accessor :out
4
+ attr_reader :results
5
+
6
+ def initialize(out_io)
7
+ @verbose = false
8
+ @quiet = false
9
+ @silent = false
10
+ @sideways = false
11
+ @lineways = false
12
+ @sorted = false
13
+ @list = false
14
+
15
+ @out = out_io
16
+
17
+ @results = Hash.new do |h, k|
18
+ case k when :fileerror, :ok, :skipped
19
+ h[k] = Hash.new {|hh, kk| hh[kk] = []}
20
+ else
21
+ h[k] = []
22
+ end
23
+ end
24
+ end
25
+
26
+ def output(mode, text, path = nil, file = nil)
27
+ if @verbose
28
+ @out.puts "#{mode}: #{text}"
29
+ else
30
+ case mode when :ok, :skipped
31
+ @results[mode][path] << text
32
+ when :fileerror
33
+ @results[mode][path] << "#{file}: #{text}"
34
+ else
35
+ @results[mode] << text
36
+ end
37
+ end
38
+ end
39
+
40
+ def display
41
+ result = ""
42
+ unless @silent
43
+ m = @sideways ? :rows : :columns
44
+ ok = 0
45
+ skipped = 0
46
+ errors = 0
47
+ @results.each do |mode, value|
48
+ case mode when :fileerror, :ok, :skipped
49
+ value.collect.sort.each do |k, v|
50
+ case mode when :ok: ok += v.length
51
+ when :skipped: skipped += v.length
52
+ when :fileerror: errors += v.length
53
+ end
54
+ s = "\n#{File.expand_path(k)}"
55
+ s += ": #{mode.to_s.upcase} - #{v.length} files" unless @quiet or @list
56
+ @out.puts s
57
+ v = v.sort if @sorted
58
+ if @lineways
59
+ @out.puts v.join("\n")
60
+ else
61
+ @out.puts v.send(m, 80)
62
+ end
63
+ end
64
+ else
65
+ @out.puts "\n#{mode.to_s.upcase}"
66
+ if @lineways
67
+ @out.puts value.join("\n")
68
+ else
69
+ @out.puts value.send(m, 80)
70
+ end
71
+ end
72
+ end
73
+ unless @quiet
74
+ @out.print "\nOK: #{ok}" if ok.nonzero?
75
+ @out.print "\nSKIPPED: #{skipped}" if skipped.nonzero?
76
+ @out.print "\nERRORS: #{errors}" if errors.nonzero?
77
+ @out.puts "\nTOTAL: #{ok + skipped + errors}"
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+