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
@@ -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
+