rfs 0.1 → 0.2

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.
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