rfs 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,372 @@
1
+ require 'rubygems'
2
+ require_gem 'fxruby'
3
+ require 'innate/mkdirs'
4
+ require 'innate/roman'
5
+ require 'innate/titlecase'
6
+
7
+ include Fox
8
+
9
+ #TO DO:
10
+ # change pattern to combobox with some useful patterns
11
+ # \d.+(\d+).+
12
+ # (\d+[AB])
13
+
14
+ #user interface
15
+ class RenameWindow < FXMainWindow
16
+ def initialize(app)
17
+ super(app, 'Rename File Set', nil, nil, DECOR_ALL, 20, 100, 1000, 500)
18
+
19
+ FXHorizontalFrame.new(self, LAYOUT_FILL | PACK_UNIFORM_WIDTH) { |mainFrame|
20
+ # navigation on left
21
+ @dirList = FXDirList.new mainFrame, nil, 0, LAYOUT_FILL | FRAME_NORMAL
22
+ # controls in centre
23
+ FXVerticalFrame.new(mainFrame, LAYOUT_FILL | PACK_UNIFORM_WIDTH) { |controls|
24
+ FXLabel.new controls, 'Search (replace first capture):', nil, LAYOUT_FILL_X | JUSTIFY_LEFT
25
+ @search = FXTextField.new controls, 0, nil, LAYOUT_FILL
26
+
27
+ FXGroupBox.new(controls, 'Replacement Method', LAYOUT_FILL | FRAME_GROOVE | JUSTIFY_LEFT ) { |group|
28
+ @selReplace = FXRadioButton.new(group, 'Replace with:') { |r|
29
+ connectRadio r
30
+ }
31
+ @replace = FXTextField.new group, 30, nil, PACK_UNIFORM_WIDTH
32
+ @selAdd = FXRadioButton.new(group, 'Add to captured digits: ', nil, LAYOUT_FILL_Y) { |r|
33
+ connectRadio r
34
+ }
35
+ @add = FXSpinner.new(group, 10, nil, FRAME_NORMAL) { |s| s.range = (-500..500)}
36
+ @selRoman = FXRadioButton.new(group, 'Convert to Roman Numerals', nil, LAYOUT_FILL_X ) { |r|
37
+ connectRadio r
38
+ }
39
+ @selTapeNumbers = FXRadioButton.new(group, 'Convert Tape Numbers', nil, LAYOUT_FILL_X) { |r|
40
+ connectRadio r
41
+ }
42
+ @selFill = FXRadioButton.new(group, 'Replace capture with last match capture:', nil, LAYOUT_FILL_X) { |r|
43
+ connectRadio r
44
+ }
45
+ @fill = FXTextField.new group, 30, nil, PACK_UNIFORM_WIDTH
46
+ @selFile = FXRadioButton.new(group, 'Replace from files.txt', nil, LAYOUT_FILL_X) { |r|
47
+ connectRadio r
48
+ }
49
+ @selCount = FXRadioButton.new(group, 'Count', nil, LAYOUT_FILL_X) { |r|
50
+ connectRadio r
51
+ }
52
+ @selMarkChange = FXRadioButton.new(group, 'Mark Changes', nil, LAYOUT_FILL_X) { |r|
53
+ connectRadio r
54
+ }
55
+ @selFromFullName = FXRadioButton.new(group, 'Replace with capture in full name:', nil, LAYOUT_FILL_X) { |r|
56
+ connectRadio r
57
+ }
58
+ @fullNamePattern = FXTextField.new group, 30, nil, PACK_UNIFORM_WIDTH
59
+ @selMoveUp = FXRadioButton.new(group, 'Move to Parent Directory', nil, LAYOUT_FILL_X) { |r|
60
+ connectRadio r
61
+ }
62
+ @selCapitalize = FXRadioButton.new(group, 'Capitalize', nil, LAYOUT_FILL_X) { |r|
63
+ connectRadio r
64
+ }
65
+ }
66
+
67
+ FXButton.new(controls, '&List Files', nil, nil, 0, LAYOUT_FILL_X | BUTTON_NORMAL) { |b|
68
+ b.connect SEL_COMMAND, method(:onListClick)
69
+ }
70
+ FXButton.new(controls, '&Preview', nil, nil, 0, LAYOUT_FILL_X | FRAME_RAISED | FRAME_THICK) { |b|
71
+ b.connect SEL_COMMAND, method(:onPreviewClick)
72
+ }
73
+ FXButton.new(controls, '&Apply', nil, nil, 0, LAYOUT_FILL_X | FRAME_RAISED | FRAME_THICK) { |b|
74
+ b.connect SEL_COMMAND, method(:onReplaceClick)
75
+ }
76
+ }
77
+ # results on right
78
+ @results = FXText.new mainFrame, nil, 0, LAYOUT_FILL | FRAME_NORMAL | TEXT_READONLY
79
+ }
80
+
81
+ @selReplace.checkState = TRUE
82
+ onRadioUpdate @selReplace, nil, nil
83
+ end
84
+
85
+ def connectRadio(r)
86
+ r.connect SEL_COMMAND, method(:onRadioUpdate)
87
+ end
88
+
89
+ def onPreviewClick(sender, sel, ptr)
90
+ start false
91
+ end
92
+
93
+ def onReplaceClick(sender, sel, ptr)
94
+ start true
95
+ end
96
+
97
+ def onListClick(sender, sel, ptr)
98
+ start false, true
99
+ @dirList.setFocus
100
+ end
101
+
102
+ def onRadioUpdate(sender, sel, ptr)
103
+ sender.parent.each_child do |c|
104
+ if FXRadioButton === c and c != sender
105
+ c.checkState = FALSE
106
+ end
107
+ end
108
+ @selReplace.checked? ? @replace.enable : @replace.disable
109
+ @selAdd.checked? ? @add.enable : @add.disable
110
+ @selFill.checked? ? @fill.enable : @fill.disable
111
+ @selFromFullName.checked? ? @fullNamePattern.enable : @fullNamePattern.disable
112
+ end
113
+ end
114
+
115
+ #the real work of it
116
+ class RenameWindow
117
+
118
+ def start(commit, listonly = false)
119
+ @results.text = ''
120
+ traverseList @dirList, commit, listonly
121
+ end
122
+
123
+ def traverseList(list, commit, listonly)
124
+ list.each do |i|
125
+ if i.selected?
126
+ doReplace @dirList.itemPathname(i), commit, listonly
127
+ end
128
+ traverseList(i, commit, listonly) if i.expanded?
129
+ end
130
+ end
131
+
132
+ def subst(src, matchdata, repl)
133
+ return src unless matchdata and matchdata[1]
134
+ t = repl
135
+ c = 1
136
+ matchdata.to_a[c..-1].each do |m|
137
+ t.gsub!("%#{c}", m ? m : '')
138
+ c += 1
139
+ end
140
+ src[0, matchdata.offset(1)[0]] + (t == nil ? '' : t) + src[matchdata.offset(1)[1], src.length]
141
+ end
142
+
143
+ def result(t)
144
+ @results.text += t.to_s + "\n"
145
+ end
146
+
147
+ def rename(path, oldfn, newfn, commit)
148
+ unless oldfn == newfn
149
+ t = newfn
150
+ if commit
151
+ begin
152
+ fn = File.join(path, newfn)
153
+ mkdirs File.split(fn).first
154
+ File.rename(File.join(path, oldfn), fn)
155
+ rescue
156
+ t += ' - Error'
157
+ else
158
+ t += ' - OK'
159
+ end
160
+ end
161
+ result t
162
+ end
163
+ end
164
+
165
+ def rename_each_match(re, path, commit)
166
+ return unless Regexp === re
167
+ dir = Dir.new(path) rescue return
168
+ dir.each do |file|
169
+ next if /^\.+$/ =~ file
170
+ md = re.match(file)
171
+ if md
172
+ newfn = yield(file, md)
173
+ rename path, file, newfn, commit if newfn
174
+ end
175
+ end
176
+ end
177
+
178
+ def new_regexp(pattern)
179
+ begin
180
+ Regexp.new(pattern)
181
+ rescue
182
+ result "Error parsing regular expression:\n#{$!}\n------------"
183
+ nil
184
+ end
185
+ end
186
+
187
+ def doReplace(path, commit, listonly)
188
+ re = new_regexp @search.text
189
+
190
+ if listonly
191
+ Dir.new(path).each do |file, md|
192
+ result "#{(File.directory?(File.join(path, file)) ? '\\' : '')}#{file}"
193
+ end
194
+ return
195
+ end
196
+
197
+ if @selReplace.checked?
198
+ rename_each_match(re, path, commit) do |file, md|
199
+ if md.length >= 2
200
+ subst(file, md, @replace.text)
201
+ else
202
+ file.sub(re, @replace.text)
203
+ end
204
+ end
205
+ elsif @selFile.checked?
206
+ fn = File.join(path, 'files.txt')
207
+ return unless File.exist? fn
208
+ File.open(fn) do |f|
209
+ rename_each_match(re, path, commit) do |file, md|
210
+ if md.length >= 2
211
+ t = f.readline.chomp rescue '--no data--'
212
+ subst(file, md, t)
213
+ end
214
+ end
215
+ end
216
+ elsif @selAdd.checked?
217
+ rename_each_match(re, path, commit) do |file, md|
218
+ if md.length >= 2
219
+ int = md[1].to_i rescue int = md[1] rescue int = 0
220
+ subst(file, md, (int + @add.value).to_s)
221
+ end
222
+ end
223
+ elsif @selCount.checked?
224
+ count = 0
225
+ rename_each_match(re, path, commit) do |file, md|
226
+ if md.length >= 2
227
+ count += 1
228
+ subst(file, md, count.to_s)
229
+ end
230
+ end
231
+ elsif @selFromFullName.checked?
232
+ r = new_regexp(@fullNamePattern.text)
233
+ if r
234
+ rename_each_match(re, path, commit) do |file, md|
235
+ fn = path + '\\' + file
236
+ m = r.match(fn)
237
+ if m
238
+ if m.length >= 2
239
+ replace = m[1]
240
+ else
241
+ replace = m
242
+ end
243
+ if md.length >= 2
244
+ subst(file, md, replace)
245
+ else
246
+ file.sub(re, replace)
247
+ end
248
+ else
249
+ result "no match: #{fn}"
250
+ nil
251
+ end
252
+ end
253
+ end
254
+ elsif @selMoveUp.checked?
255
+ p = File.split(path).first
256
+ unless p == '\\'
257
+ rename_each_match(re, path, commit) do |file, md|
258
+ d = File.join(p, file)
259
+ File.rename File.join(path, file), d if commit
260
+ result d
261
+ nil
262
+ end
263
+ else
264
+ nil
265
+ end
266
+ elsif @selRoman.checked?
267
+ rename_each_match(re, path, commit) do |file, md|
268
+ if md.length >= 2
269
+ t = md[1].to_i rescue t = md[1] rescue t = 0
270
+ subst(file, md, t.roman) if Integer === t
271
+ end
272
+ end
273
+ elsif @selMarkChange.checked?
274
+ prev = ''
275
+ rename_each_match(re, path, commit) do |file, md|
276
+ if md.length >= 2
277
+ unless prev == md[1]
278
+ prev = md[1]
279
+ subst(file, /()\..+?$/.match(file), '-change-')
280
+ end
281
+ end
282
+ end
283
+ elsif @selTapeNumbers.checked?
284
+ rename_each_match(re, path, commit) do |file, md|
285
+ if md.length >= 2
286
+ t = md[1]
287
+ n = t.to_i * 2 + (/a/i =~ t[-1,1] ? -1 : 0)
288
+ n += 2 if n < 1
289
+ subst(file, md, n.to_s)
290
+ end
291
+ end
292
+ elsif @selCapitalize.checked?
293
+ always_lower = %w{ vol
294
+ mp3 wav txt avi nfo pdf rar zip rb ape sfk mpg rm qt doc }
295
+ lower = %w{ as of in the and at or a to is on by for with an } + always_lower
296
+ split_chars = /[ �\-().]/
297
+ upper = /^[MDCLXVIW]+$/
298
+ rename_each_match(re, path, commit) do |file, md|
299
+ o = md[1]
300
+ prev = ''
301
+ if md.length >= 2
302
+ res = o.split(split_chars).inject([]) do |r, w|
303
+ if r.empty? or prev =~ /^([\d.]+|.*[;.!_]|)$/
304
+ if always_lower.include?(w.downcase)
305
+ r << w.downcase
306
+ elsif w =~ upper
307
+ r << w.upcase
308
+ else
309
+ r << w.capitalize
310
+ end
311
+ else
312
+ if lower.include?(w.downcase)
313
+ r << w.downcase
314
+ elsif w =~ upper
315
+ r << w.upcase
316
+ else
317
+ r << w.capitalize
318
+ end
319
+ end
320
+ prev = w
321
+ r
322
+ end
323
+ r = res.inject('') do |s, w|
324
+ unless w.empty?
325
+ until o[s.length, 1] =~ Regexp.new(Regexp.escape(w[0,1]), 'i') do
326
+ s << o[s.length, 1]
327
+ end
328
+ s << w
329
+ end
330
+ s
331
+ end
332
+ subst(file, md, r)
333
+ end
334
+ end
335
+ elsif @selFill.checked?
336
+ begin
337
+ fillRE = Regexp.new(@fill.text)
338
+ rescue
339
+ result "Error parsing regular expression:\n#{$!}\n------------"
340
+ return
341
+ end
342
+ fillText = ''
343
+ dir = Dir.new(path) rescue return
344
+ dir.each do |file|
345
+ next if /^\.+$/ =~ file
346
+ md = re.match(file)
347
+ if md or fillText
348
+ if md and md.length >= 2 and md[1].length > 0 and not (fillRE and fillRE.match(file))
349
+ fillText = md[1]
350
+ result "source: #{file}"
351
+ else
352
+ md = fillRE.match(file)
353
+ if md and md.length >= 2
354
+ t = subst(file, md, fillText)
355
+ rename path, file, t, commit
356
+ else
357
+ result "* No capture to fill * #{file}"
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ if $0 == __FILE__
367
+ theApp = FXApp.new
368
+ theMainWindow = RenameWindow.new theApp
369
+ theApp.create
370
+ theMainWindow.show
371
+ theApp.run
372
+ end
data/lib/errors.rb ADDED
@@ -0,0 +1,15 @@
1
+ class RenamerError < RuntimeError
2
+ end
3
+
4
+ class ActionError < RenamerError
5
+ def initialize *opts
6
+ if opts.length > 0
7
+ super
8
+ else
9
+ super 'Unrecognized or missing action'
10
+ end
11
+ end
12
+ end
13
+
14
+ class DirectoryExistsError < RuntimeError
15
+ end
data/lib/filters.rb ADDED
@@ -0,0 +1,68 @@
1
+
2
+ class Filter
3
+ def self.add(orig, nxt)
4
+ if orig
5
+ orig.add nxt
6
+ else
7
+ nxt
8
+ end
9
+ end
10
+
11
+ attr_accessor :next, :name, :regexp, :full_name
12
+
13
+ def initialize(regexp, nxt = nil, full_name = false)
14
+ @next = nxt
15
+ @regexp = regexp
16
+ @full_name = full_name
17
+ end
18
+
19
+ def add(nxt)
20
+ if @next
21
+ @next.add nxt
22
+ else
23
+ @next = nxt
24
+ end
25
+ self
26
+ end
27
+
28
+ def filter(path, file)
29
+ unless @regexp =~ text(path, file)
30
+ next_filter(path, file)
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def each(&b)
37
+ yield self
38
+ @next.each(&b) if @next
39
+ end
40
+
41
+ protected
42
+ def text path, file
43
+ if @full_name
44
+ File.expand_path(File.join(path, file))
45
+ else
46
+ file
47
+ end
48
+ end
49
+
50
+ def next_filter path, file
51
+ if @next
52
+ @next.filter path, file
53
+ else
54
+ true
55
+ end
56
+ end
57
+ end
58
+
59
+ class FilterNonMatches < Filter
60
+ def filter(path, file)
61
+ if @regexp =~ text(path, file)
62
+ next_filter(path, file)
63
+ else
64
+ false
65
+ end
66
+ end
67
+ end
68
+