rfs 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,110 @@
1
+ :main:README
2
+
3
+ = Rename File Set
4
+
5
+ Rename File Set is a utility for transforming the name of matching files using the full power of regular expressions. I originally created this program to help organize my mp3 collection, but it has proven to be generally useful.
6
+
7
+ As I have added features, the program has been extesively refactored. It is DRY and very modular. It is also very well tested. Features can now be added or changed very easily.
8
+
9
+ Author:: Darrick Wiebe
10
+ Copyright:: Copyright (c) 2005 Darrick Wiebe
11
+ License:: Distributed under the MIT Licence (see MIT-LICENCE file)
12
+
13
+ == Command Line Interface
14
+
15
+ There are two entry points to the command line version of the program. The primary one is +rfs.rb+. +rfsd.rb+ just adds debugging options.
16
+
17
+ === Usage
18
+
19
+ rfs.rb [options] [path] [path] ...
20
+
21
+ If no path is specified, the current directory is used.
22
+
23
+ ==== The Four Primary Modes of Operation
24
+
25
+ [<tt>--list</tt>]
26
+ List Mode: List all files in the folder
27
+ [<tt>--match</tt>]
28
+ Match Mode: List all matching files but don't make any changes
29
+ [<tt>--test</tt>]
30
+ Testing Mode: Display the results without making any changes
31
+ [<tt>--commit</tt>]
32
+ Commit Mode: Actually perform the operation on the target files and
33
+ display the results. By default, <tt>--confirm</tt> is turned on, see below.
34
+
35
+ ==== Search
36
+
37
+ [<tt>--search</tt> <i>regular expression</i>]
38
+ This option is required for
39
+
40
+ ==== Behaviour Modification Options
41
+
42
+ [<tt>--confirm</tt>]
43
+ Ask for confirmation before committing a change with a prompt like:
44
+
45
+ <tt>"./original" => "new_name"? [Yes/No/All/Cancel] (yes) _</tt>
46
+
47
+ or
48
+
49
+ <tt>"./original" => existing file "new_name"? [Yes/No/All/Cancel] (yes) _</tt>
50
+
51
+ This option is on by default.
52
+ [<tt>--noconfirm</tt>]
53
+ Don't confirm changes.
54
+ [<tt>--capture</tt> <i>capture number</i>]
55
+ Specifies which capture in the regular expression should
56
+ be used or replaced. By default the <b>first</b> capture
57
+ is used. This option affects all regular expressions used.
58
+ If the expression has no captures, the whole match is
59
+ used, and this setting has no effect.
60
+ [<tt>--filter</tt> <i>regular expression</i>]
61
+ Filters out all <b>matching</b> files or directories.
62
+ Multiple filters may be specified.
63
+ [<tt>--keep</tt> <i>regular expression</i>]
64
+ Filters out all <b>non-matching</b> files or directories.
65
+ Multiple filters may be specified.
66
+ [<tt>--force</tt>]
67
+ Overwrite existing files. If <tt>--confirm</tt> is on, the
68
+ confirmation message will
69
+ [<tt>--ignorecase</tt>]
70
+ Make all regular expressions case-insensitive.
71
+ [<tt>--recursive</tt>]
72
+ Recursively descend into the paths specified.
73
+ [<tt>--reverse</tt>]
74
+ Iterates through files in reverse order.
75
+
76
+ ==== Primary Functions
77
+
78
+ [<tt>--add</tt> <i>number</i>]
79
+ Add <tt>number</tt> to <tt>the capture</tt>.
80
+ [<tt>--count</tt> <i>start</i>]
81
+ Replace <tt>the capture</tt> with the count. <tt>succ</tt> is
82
+ used to increment, so you can use either letters or numbers.
83
+ [<tt>--</tt> <i></i>]
84
+
85
+ [<tt>--</tt> <i></i>]
86
+
87
+ [<tt>--</tt> <i></i>]
88
+
89
+ [<tt>--</tt> <i></i>]
90
+
91
+ [<tt>--</tt> <i></i>]
92
+
93
+ [<tt>--</tt> <i></i>]
94
+
95
+
96
+
97
+ ==== Display Options
98
+
99
+ [<tt>--</tt>]
100
+
101
+ [<tt>--</tt>]
102
+
103
+ [<tt>--</tt>]
104
+
105
+ [<tt>--</tt>]
106
+
107
+
108
+
109
+ The program
110
+
@@ -4,7 +4,7 @@ require 'rake/gempackagetask'
4
4
  require 'rake/contrib/rubyforgepublisher'
5
5
 
6
6
  PKG_NAME = 'rfs'
7
- PKG_VERSION = '0.1'
7
+ PKG_VERSION = '0.2'
8
8
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
9
9
 
10
10
  RELEASE_NAME = "REL #{PKG_VERSION}"
@@ -34,7 +34,10 @@ dist_dirs = [ "lib", "tests"]
34
34
  spec = Gem::Specification.new do |s|
35
35
  s.name = PKG_NAME
36
36
  s.version = PKG_VERSION
37
- s.summary = "A utility that allows you to use regular expressions to manage large sets of files or folders."
37
+ s.summary = <<-EOD
38
+ A utility that allows you to use regular expressions to rename
39
+ large sets of files or folders. Fxruby and cmd-line interfaces.
40
+ EOD
38
41
  s.description = <<-EOD
39
42
  Rename File Set is a powerful way to manage large or small
40
43
  sets of files such as mp3 collections etc. It uses the full
@@ -47,7 +50,7 @@ spec = Gem::Specification.new do |s|
47
50
  ).delete_if { |item| item.include?( "\.svn" ) || item.include?("\pkg") }
48
51
  s.require_path = 'lib'
49
52
  s.has_rdoc = false
50
- # s.test_files = ['./tests/test_rename_functions.rb', './lib/innate/test/all_tests.rb']
53
+ s.test_files = ['./tests/test_rename_functions.rb', './lib/innate/test/all_tests.rb']
51
54
 
52
55
  s.bindir = 'bin'
53
56
  s.executables << 'rfs.rb'
@@ -63,13 +66,12 @@ end
63
66
  Rake::GemPackageTask.new(spec) do |p|
64
67
  p.gem_spec = spec
65
68
  p.need_tar = true
66
- # p.need_zip = true
69
+ p.need_zip = true
67
70
  end
68
71
 
69
72
  desc "Publish the beta gem"
70
73
  task :pgem => [:package] do
71
74
  Rake::SshFilePublisher.new("pangloss@rfs.rubyforge.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
72
- #`ssh davidhh@wrath.rubyonrails.com './gemupdate.sh'`
73
75
  end
74
76
 
75
77
  desc "Publish the API documentation"
@@ -3,12 +3,10 @@ require 'gui'
3
3
  require 'renamer'
4
4
 
5
5
 
6
- if $0 == __FILE__
7
- $>.sync = true
8
- theApp = FXApp.new
9
- theMainWindow = RenameWindow.new theApp
10
- theMainWindow.renamer = Renamer.new
11
- theApp.create
12
- theMainWindow.show
13
- theApp.run
14
- end
6
+ $>.sync = true
7
+ theApp = FXApp.new
8
+ theMainWindow = RenameWindow.new theApp
9
+ theMainWindow.renamer = Renamer.new
10
+ theApp.create
11
+ theMainWindow.show
12
+ theApp.run
data/bin/rfs.rb CHANGED
@@ -28,7 +28,11 @@ class CommandLineInterface
28
28
  op = OptionParser.new
29
29
  file_provider = Provider::File::NonRecursive
30
30
  @@options = options = {}
31
- @@results = CollectResults.new $>
31
+ width = 80
32
+ # if require 'curses'
33
+ # width = Curses::stdscr.maxx
34
+ # end
35
+ @@results = CollectResults.new $>, width
32
36
  renamer = Renamer.new
33
37
 
34
38
  # basic default settings.
@@ -44,38 +48,49 @@ class CommandLineInterface
44
48
  # more defaults... make configurable in the future
45
49
  CommandLineInterface.confirm # confirm changes
46
50
  options[:action] = :preview # dry-run mode
47
-
48
51
 
49
- op.on_head('--noconfirm', 'Don\'t confirm.') {
50
- CommandLineInterface.noconfirm
51
- }
52
- op.on_head('--confirm', 'Confirm before rename') {
53
- CommandLineInterface.confirm
52
+ op.banner = 'Usage: rfs.rb [options] [path] [path] ...'
53
+ op.separator "Fundamentals:"
54
+ op.on('-l', '--list', 'List original file names in folders to',
55
+ 'operate on only.') {
56
+ function = :rename_replace
57
+ options[:action] = :list
58
+ @@results.list = true
54
59
  }
55
- op.on_head('-t', '--test', 'Test run only, don\'t acually rename anything.') {
60
+ op.on('-t', '--test', 'Test run only, don\'t do anything.') {
56
61
  options[:action] = :preview
57
62
  }
58
- op.on_head('-c', '--commit', 'Commit changes and actually rename the files.') {
63
+ op.on('-c', '--commit', 'Commit changes: actually rename the files.') {
59
64
  options[:action] = :commit
60
65
  }
61
- op.on_head('-l', '--list', 'List original file names in folders to operate on only.') {
62
- function = :rename_replace
63
- options[:action] = :list
64
- @@results.list = true
66
+ op.on('--confirm', 'Confirm before rename.') {
67
+ CommandLineInterface.confirm
68
+ }
69
+ op.on('--noconfirm', 'Don\'t confirm.') {
70
+ CommandLineInterface.noconfirm
71
+ }
72
+
73
+ op.separator "Search:"
74
+ op.on('-s', '--search REGEXP', String, 'Search expression') {|s|
75
+ options[:search] = PrepareRegexp.new s
65
76
  }
66
77
 
78
+ op.separator "Primary Functions:"
67
79
  op.on('-a', '--add NUMBER', Integer, 'Add to capture.') {|options[:add]|
68
80
  function = :rename_add
69
81
  }
70
- op.on('-C', '--count', 'Replace capture with count.') {
82
+ op.on('-C', '--count [START]', 'Replace capture with count.') {|s|
71
83
  function = :rename_count
84
+ options[:count_start] = s || 1
72
85
  }
73
86
  op.on('--capture NUM', Integer, 'Replace the text in a specific capture.',
74
- 'Default: 1 (or whole match if there are no captures)') {|n|
87
+ 'Default: 1 (or whole match if',
88
+ 'there are no captures)') {|n|
75
89
  options[:capture_num] = n
76
90
  }
77
91
  op.on('--file [FILE]', String,
78
- 'Use files.txt or FILE as source for replace strings.') {|file|
92
+ 'Use files.txt or FILE as source for',
93
+ 'replace strings.') {|file|
79
94
  options[:source] = NameFileSource.new(file || 'files.txt')
80
95
  function = :rename_name_source
81
96
  options[:filter] = Filter.add(options[:filter], Filter.new(/^#{file || 'files.txt'}$/))
@@ -83,29 +98,20 @@ class CommandLineInterface
83
98
  options[:filter] = Filter.add(options[:filter], Filter.new(/^#{file}$/, nil, true))
84
99
  end
85
100
  }
86
- op.on('-f', '--filter REGEXP', String,
87
- 'Remove matching files from the search.') {|f|
88
- options[:filter] = Filter.add(options[:filter], Filter.new(PrepareRegexp.new(f)))
89
- }
90
- op.on('-F', '--keep REGEXP', String,
91
- 'Include only matching files in the search.') {|f|
92
- options[:filter] = Filter.add(options[:filter], FilterNonMatches.new(PrepareRegexp.new(f)))
93
- }
94
- op.on('--force', 'Force rename even if a file will be replaced.') {
95
- options[:force] = true
96
- }
97
- op.on('-i', '--fill REGEXP', String, 'Copy -s match from previous files and fill downwards, replacing the match for this pattern.') {|f|
101
+ op.on('-i', '--fill REGEXP', String,
102
+ 'Copy -s match from previous files and fill',
103
+ 'downwards, replacing the match for this',
104
+ 'pattern.') {|f|
98
105
  options[:fill] = PrepareRegexp.new f
99
106
  function = :rename_fill
100
107
  }
101
- op.on('-I', '--ignorecase', 'Make all REGEXP options case-insensitive.') {
102
- insensitive = true
103
- }
104
108
  op.on('-n', '--roman', 'Toggle match to or from roman numerals.') {
105
109
  function = :rename_roman
106
110
  }
107
- op.on('-m', '--mark [PATTERN]', 'Mark files if the captured pattern changes.',
108
- 'The capture in PATTERN will be replaced with --mark_text.',
111
+ op.on('-m', '--mark [PATTERN]',
112
+ 'Mark files if the captured pattern',
113
+ 'changes. The capture in PATTERN will',
114
+ 'be replaced with --mark_text.',
109
115
  'Default: /()$/') {|p|
110
116
  function = :rename_mark_change
111
117
  options[:replace_pattern] = PrepareRegexp.new(p || '()$')
@@ -114,6 +120,16 @@ class CommandLineInterface
114
120
  op.on('--mark_text TEXT', 'Default: --changed--') {|t|
115
121
  options[:replace_text] = t
116
122
  }
123
+ op.on('--eval EXPR', String, 'CAUTION! The expression will be',
124
+ 'evaluated in the same context as',
125
+ 'the rest of the program.',
126
+ 'Perform eval on EXPR after',
127
+ 'it has been interpolated with',
128
+ 'the search captures.'
129
+ ) {|expr|
130
+ options[:eval_expr] = expr
131
+ function = :rename_eval
132
+ }
117
133
  op.on('-p', '--inpath REGEXP', String, 'Rename with match in full path.') {|p|
118
134
  options[:pattern] = PrepareRegexp.new p
119
135
  function = :rename_from_full_name
@@ -126,33 +142,53 @@ class CommandLineInterface
126
142
  options[:replace] = r || ''
127
143
  function = :rename_replace
128
144
  }
129
- op.on('-R', '--recursive', 'Operate on all files and folders recursively.') {
130
- file_provider = Provider::File::Recursive
131
- }
132
- op.on('--reverse', 'Operate on files in reverse order.') {
133
- reverse = true
134
- }
135
145
  op.on('--rm', 'Remove matching files.') {
136
146
  function = :rename_remove
137
147
  }
138
- op.on('-s', '--search REGEXP', String, 'Search expression') {|s|
139
- options[:search] = PrepareRegexp.new s
140
- }
141
- op.on('--sort', 'Sort output') { @@results.sorted = true }
142
- op.on('--tape', 'Convert tape numbers ie. 1A, 1B, 2A, ... to regular numbers.') {
148
+ op.on('--tape', 'Convert tape numbers.',
149
+ 'ie. 3A, 3B, 4A, ... to 5, 6, 7.') {
143
150
  function = :rename_tape_numbers
144
151
  }
145
152
  op.on('-T', '--title', 'Convert capture to title case.') {
146
- functon = :rename_capitalize
153
+ function = :rename_capitalize
147
154
  }
148
- op.on('-u', '--up', 'Move files with match up to parent directory.') {
155
+ op.on('-u', '--up', 'Move matching files up to parent directory.') {
149
156
  function = :rename_move_up
150
157
  }
158
+
159
+ op.separator "Behaviour Modifications:"
160
+ op.on('-f', '--filter REGEXP', String,
161
+ 'Remove matching files from the search.') {|f|
162
+ options[:filter] = Filter.add(options[:filter], Filter.new(PrepareRegexp.new(f)))
163
+ }
164
+ op.on('-F', '--keep REGEXP', String,
165
+ 'Include only matching files in the search.') {|f|
166
+ options[:filter] = Filter.add(options[:filter], FilterNonMatches.new(PrepareRegexp.new(f)))
167
+ }
168
+ op.on('--force', 'Force rename even if a file will be lost.') {
169
+ options[:force] = true
170
+ }
171
+ op.on('-I', '--ignorecase', 'Make all REGEXP options case-insensitive.') {
172
+ insensitive = true
173
+ }
174
+ op.on('-R', '--recursive',
175
+ 'Recursively descend the directory',
176
+ 'tree.') {
177
+ file_provider = Provider::File::Recursive
178
+ }
179
+ op.on('--reverse', 'Operate on files in reverse order.') {
180
+ reverse = true
181
+ }
182
+
183
+
184
+ op.separator "Output Options:"
185
+ op.on('--sort', 'Sort output') { @@results.sorted = true }
151
186
  op.on('-q', '--quiet') { quiet = @@results.quiet = true }
152
187
  op.on('-Q', '--silent') {
153
188
  @@results.quiet = @@results.silent = silent = quiet = true;
154
189
  options[:verbose] = print_matches = false }
155
- op.on('-v', '--verbose', 'Vebose. Useful if you aren\'t getting the results you expect.') {
190
+ op.on('-v', '--verbose', 'Vebose. Useful if you aren\'t getting',
191
+ 'the results you expect.') {
156
192
  @@results.verbose = options[:verbose] = true
157
193
  }
158
194
  op.on('-V', '--more_verbose', 'Verbose + show breakdown of matchdata.') {
@@ -161,10 +197,11 @@ class CommandLineInterface
161
197
  op.on('-x', '--sideways', 'Display in rows instead of in columns.') {
162
198
  @@results.sideways = true
163
199
  }
164
- op.on('-X', '--raw_output', 'Don\'t sort and keep everything on seperate lines.') {
200
+ op.on('-X', '--raw_output', 'Don\'t sort and keep everything on separate',
201
+ 'lines.') {
165
202
  @@results.lineways = true
166
203
  }
167
-
204
+
168
205
  paths = op.parse ARGV
169
206
 
170
207
  if paths.empty?
@@ -8,6 +8,7 @@ This is a comprehensive utility for renaming sets of files. It makes full use o
8
8
  * in path (replace with text captured from the file's full path)
9
9
  * prompt (prompt for a new name for each file)
10
10
  * replace (regular search and replace)
11
+ * remove (delete matching files)
11
12
  * tape (replace 1A, 1B, 2A, 2B, ... with 1, 2, 3, 4, ...)
12
13
  * title (Do Title Casing in the Proper English Way. It can Handle Punctuation too.)
13
14
  * up (move files to parent directory)
@@ -12,4 +12,10 @@ class ActionError < RenamerError
12
12
  end
13
13
 
14
14
  class DirectoryExistsError < RuntimeError
15
+ end
16
+
17
+ class NameLengthError < RuntimeError
18
+ def initialize(len)
19
+ super "File name too long (#{len} chars)."
20
+ end
15
21
  end
data/lib/gui.rb CHANGED
@@ -77,6 +77,7 @@ class RenameWindow < FXMainWindow
77
77
  }
78
78
  # results on right
79
79
  @results = FXText.new mainFrame, nil, 0, LAYOUT_FILL | FRAME_NORMAL | TEXT_READONLY
80
+ @results.setFont(FXFont.new(getApp(), "courier new", 7))
80
81
  }
81
82
 
82
83
  @selReplace.checkState = TRUE
@@ -146,9 +147,12 @@ class RenameWindow
146
147
  end
147
148
 
148
149
  def start(action)
150
+ # require 'innate/reload'
151
+ # reload 'providers'
152
+
149
153
  s = StringIO.new
150
- @r = CollectResults.new s
151
- @r.lineways = true
154
+ @r = CollectResults.new s, 60
155
+ # @r.lineways = true
152
156
  @r.list = action == :list
153
157
 
154
158
  begin
@@ -169,8 +173,8 @@ class RenameWindow
169
173
  def rename(name, args = {})
170
174
  args[:search] ||= PrepareRegexp.new @search.text
171
175
  args[:filter] = Filter.add(args[:filter], Filter.new(/^\.+$/))
172
- args[:file_provider] = Provider::File::NonRecursive.new(Provider::Folder::Tree.new(@dirList))
173
-
176
+ args[:file_provider] =
177
+ Provider::File::NonRecursive.new(Provider::Folder::Tree.new(@dirList))
174
178
  PrepareRegexp.each(args) do |k, v|
175
179
  args[k] = t = v.create(false, false, true) do |mode, output|
176
180
  @r.output(mode, output)
@@ -182,7 +186,12 @@ class RenameWindow
182
186
  end
183
187
  end
184
188
 
185
- #special functions
189
+ #--
190
+ # special functions
191
+ #
192
+ # these should be changed to just return a hash of special
193
+ # options for the function. The rest is all wet.
194
+ #++
186
195
 
187
196
  def rename_replace action
188
197
  rename :rename_replace, :replace => @replace.text, :action => action
@@ -202,15 +211,21 @@ class RenameWindow
202
211
 
203
212
  def rename_mark_change action
204
213
  rename(:rename_mark_change,
205
- :replace_pattern => PrepareRegexp.new('()$/'), # I could add boxes for these options
214
+ :replace_pattern => PrepareRegexp.new('()$/'), # could add boxes for these options
206
215
  :replace_text => '--changed--',
207
216
  :action => action)
208
217
  end
209
218
 
210
219
  def rename_files_txt action
220
+ # note the name change
211
221
  rename(:rename_name_source,
212
222
  :source => NameFileSource.new('files.txt'),
213
223
  :filter => Filter.new(/^files.txt$/),
214
224
  :action => action)
215
225
  end
226
+
227
+ def rename_count action
228
+ rename(:rename_count, :count_start => 1, # could add a box for this option.
229
+ :action => action)
230
+ end
216
231
  end