file_editor 0.5

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.txt ADDED
@@ -0,0 +1,57 @@
1
+ File Editor makes it easy to edit a file in place. Just send it a file name and a short block with some instructions, and it will perform string substitution as indicated in the block
2
+
3
+ ===Using File Editor
4
+
5
+ Your program block can use File Editor in two modes, with or without an explicit receiver:
6
+
7
+ FileEditor.edit('test.txt') do |editor|
8
+ editor.regex = /java/i
9
+ editor.substitution_string = 'ruby'
10
+ editor.run
11
+ end
12
+
13
+ OR
14
+
15
+ FileEditor.edit('test.txt') do
16
+ @regex = /java/i
17
+ @substitution_string = 'ruby'
18
+ run
19
+ end
20
+
21
+ The first version does not use instance_eval internally and is therefore able to use variables in the program's local scope. The second version does use instance_eval internally.
22
+
23
+ Here are some examples, alternating between approaches:
24
+
25
+ ====Standard substitution. File editor will do a line-by-line gsub on the given file
26
+
27
+ FileEditor.edit('test.txt') do |editor|
28
+ editor.regex = /java/i
29
+ editor.substitution_string = 'ruby'
30
+ editor.run
31
+ end
32
+
33
+ ====Setting global to false triggers the use of sub instead of gsub (in effect replacing only the first match on each line)
34
+
35
+ FileEditor.edit('test.txt') do
36
+ @regex = /java/i
37
+ @substitution_string = 'ruby'
38
+ @global = false
39
+ run
40
+ end
41
+
42
+ ====Setting keep_backup = true ensures that you'll have a backup of your original file
43
+
44
+ FileEditor.edit('test.txt') do |editor|
45
+ editor.regex = /java/i
46
+ editor.substitution_string = 'ruby'
47
+ editor.keep_backup = true
48
+ editor.run
49
+ end
50
+
51
+ ====If you pass in a multiline regex, FileEditor will detect it, read the entire file into a string, and match across new lines
52
+
53
+ FileEditor.edit('test.txt') do
54
+ @regex = /ja.*?va/mi
55
+ @substitution_string = 'ruby'
56
+ run
57
+ end
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ task :default => [:test]
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name="file_editor"
3
+ s.version="0.5"
4
+ s.platform=Gem::Platform::RUBY
5
+ s.date="2013-02-14"
6
+ s.summary="Edit a file in place"
7
+ s.description="File editor makes it easy to edit a file in place, with options for saving a backup"
8
+ s.author="Mark Buechler"
9
+ s.files=Dir["file_editor.gemspec", "Rakefile", "README.txt", "lib/**/*", "test/test*.rb"]
10
+ s.homepage="http://rubygems.org/sqlfinder"
11
+ s.require_paths=["lib"]
12
+ s.has_rdoc=true
13
+ s.extra_rdoc_files=["README.txt"]
14
+ s.rdoc_options=["--main", "README.txt"]
15
+ end
@@ -0,0 +1,84 @@
1
+ class FileEditor
2
+
3
+ attr_accessor :file_name, :regex, :substitution_string, :keep_backup, :global
4
+
5
+ def initialize(file_name)
6
+ @file_name = file_name
7
+ @keep_backup = false
8
+ @global = true #this will yield the equivalent of gsub instead of sub
9
+ end
10
+
11
+ #You can send an explicit receiver as an argument to this block
12
+ #and set variables on it (useful inside another object where you need to access its
13
+ #own methods inside the block), or simply set variables without an explicit receiver.
14
+ #Either way, the block must call the run method. See Ruby Best Practices by Gregory Brown, p. 63-64,
15
+ #for elaboration of this approach
16
+ def self.edit(file_name, &b)
17
+ editor = FileEditor.new(file_name)
18
+ b.arity == 0 ? editor.instance_eval(&b) : b.call(editor)
19
+ end
20
+
21
+ #runs file editor
22
+ def run
23
+ raise "You need to provide a regular expression to the editor" unless @regex
24
+ raise "You need to provide a substitution string" unless @substitution_string
25
+ raise "The file #{@file_name} does not exist" unless File.exist?(@file_name)
26
+ raise "The path #{@file_name} does not point to a file" unless File.file?(@file_name)
27
+ raise "You do not have permission to read #{@file_name}" unless File.readable?(@file_name)
28
+ raise "You do not have permission to edit #{@file_name}" unless File.writable?(@file_name)
29
+ begin
30
+ if @keep_backup
31
+ temp_file_name = @file_name + ".backup"
32
+ File.delete(temp_file_name) if File.exist?(temp_file_name)
33
+ else
34
+ temp_file_name = @file_name + "." + get_random_extension
35
+ end
36
+ File.rename(@file_name, temp_file_name)
37
+ read_handle = File.new(temp_file_name, "r")
38
+ write_handle = File.new(@file_name, "w")
39
+ write_to_file(read_handle, write_handle)
40
+ ensure
41
+ read_handle.close if read_handle
42
+ write_handle.close if write_handle
43
+ File.delete(temp_file_name) unless @keep_backup
44
+ end
45
+ end
46
+
47
+ #testable write method, using io handles as arguments
48
+ #to enable the use of String.IO in testing
49
+ def write_to_file(read_handle, write_handle)
50
+ if is_multiline(@regex.to_s)
51
+ file_string = read_handle.read #no sense doing a line-by-line multiline
52
+ if (@global)
53
+ write_handle.puts(file_string.gsub(@regex, @substitution_string))
54
+ else
55
+ write_handle.puts(file_string.sub(@regex, @substitution_string))
56
+ end
57
+ else
58
+ if (@global)
59
+ read_handle.each do |line|
60
+ write_handle.puts (line.gsub(@regex, @substitution_string))
61
+ end
62
+ else
63
+ read_handle.each do |line|
64
+ write_handle.puts (line.sub(@regex, @substitution_string))
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ #Determines if a string derived from regexp.to_s
71
+ #represents a regular expression with the multiline option turned on
72
+ def is_multiline(regex_string)
73
+ regex_string.match(/\?.*?m.*?-/) || regex_string.match(/\?[^-]+:/)
74
+ end
75
+
76
+ #returns a random 6-letter extension (e.g., 'kjeihl');
77
+ #this ensures that we don't inadvertantly delete a pre-existing
78
+ #backup file if we're not saving the backup
79
+ def get_random_extension
80
+ ('a'..'z').to_a.shuffle.slice(0..5).join
81
+ end
82
+
83
+ end
84
+
@@ -0,0 +1,469 @@
1
+ #ruby -Ilib -I. test/test_file_editor.rb
2
+
3
+ require 'test/unit'
4
+ require 'file_editor'
5
+ require 'stringio'
6
+
7
+ #NOTE: these tests are not examples of how to use FileEditor in code;
8
+ #the gem was designed with a separate method for the actual IO operations,
9
+ #precisely so it could be tested without recourse to external files,
10
+ #using StringIO
11
+ #See README.txt for actual code examples
12
+
13
+ class FileEditorTest < Test::Unit::TestCase
14
+
15
+ def setup
16
+ @original = %q[
17
+ I like java and java likes me.
18
+ Java is a great language
19
+
20
+ Vote for ja
21
+ va.
22
+
23
+ ]
24
+ end
25
+
26
+ #NO OPTIONS
27
+
28
+ #defaults to global, don't ignore case
29
+ def test_no_options_without_explicit_receiver
30
+ expected = %q[
31
+ I like ruby and ruby likes me.
32
+ Java is a great language
33
+
34
+ Vote for ja
35
+ va.
36
+
37
+ ]
38
+ read_handle = StringIO.new(@original)
39
+ write_handle = StringIO.new("")
40
+ FileEditor.edit("dummy.txt") do
41
+ @regex = /java/
42
+ @substitution_string = "ruby"
43
+ write_to_file(read_handle, write_handle)
44
+ end
45
+ read_handle.close()
46
+ write_handle.close()
47
+ assert_equal(expected, write_handle.string)
48
+
49
+ end
50
+
51
+ def test_no_options_with_explicit_receiver
52
+ expected = %q[
53
+ I like ruby and ruby likes me.
54
+ Java is a great language
55
+
56
+ Vote for ja
57
+ va.
58
+
59
+ ]
60
+ read_handle = StringIO.new(@original)
61
+ write_handle = StringIO.new("")
62
+ FileEditor.edit("dummy.txt") do |editor|
63
+ editor.regex = /java/
64
+ editor.substitution_string = "ruby"
65
+ editor.write_to_file(read_handle, write_handle)
66
+ end
67
+ read_handle.close()
68
+ write_handle.close()
69
+ assert_equal(expected, write_handle.string)
70
+
71
+ end
72
+
73
+ #IGNORE CASE
74
+
75
+ def test_ignore_case_without_explicit_receiver
76
+ expected = %q[
77
+ I like ruby and ruby likes me.
78
+ ruby is a great language
79
+
80
+ Vote for ja
81
+ va.
82
+
83
+ ]
84
+ read_handle = StringIO.new(@original)
85
+ write_handle = StringIO.new("")
86
+ FileEditor.edit("dummy.txt") do
87
+ @regex = /java/i
88
+ @substitution_string = "ruby"
89
+ write_to_file(read_handle, write_handle)
90
+ end
91
+ read_handle.close()
92
+ write_handle.close()
93
+ assert_equal(expected, write_handle.string)
94
+
95
+ end
96
+
97
+ def test_ignore_case_with_explicit_receiver
98
+ expected = %q[
99
+ I like ruby and ruby likes me.
100
+ ruby is a great language
101
+
102
+ Vote for ja
103
+ va.
104
+
105
+ ]
106
+ read_handle = StringIO.new(@original)
107
+ write_handle = StringIO.new("")
108
+ FileEditor.edit("dummy.txt") do |editor|
109
+ editor.regex = /java/i
110
+ editor.substitution_string = "ruby"
111
+ editor.write_to_file(read_handle, write_handle)
112
+ end
113
+ read_handle.close()
114
+ write_handle.close()
115
+ assert_equal(expected, write_handle.string)
116
+
117
+ end
118
+
119
+ #IGNORE CASE AND GLOBAL SUBSTITUTION
120
+
121
+ def test_ignore_case_and_global_false_without_explicit_receiver
122
+ expected = %q[
123
+ I like ruby and java likes me.
124
+ ruby is a great language
125
+
126
+ Vote for ja
127
+ va.
128
+
129
+ ]
130
+ read_handle = StringIO.new(@original)
131
+ write_handle = StringIO.new("")
132
+ FileEditor.edit("dummy.txt") do
133
+ @regex = /java/i
134
+ @substitution_string = "ruby"
135
+ @global = false
136
+ write_to_file(read_handle, write_handle)
137
+ end
138
+ read_handle.close()
139
+ write_handle.close()
140
+ assert_equal(expected, write_handle.string)
141
+
142
+ end
143
+
144
+ def test_ignore_case_and_global_substitution_with_explicit_receiver
145
+ expected = %q[
146
+ I like ruby and java likes me.
147
+ ruby is a great language
148
+
149
+ Vote for ja
150
+ va.
151
+
152
+ ]
153
+ read_handle = StringIO.new(@original)
154
+ write_handle = StringIO.new("")
155
+ FileEditor.edit("dummy.txt") do |editor|
156
+ editor.regex = /java/i
157
+ editor.substitution_string = "ruby"
158
+ editor.global = false
159
+ editor.write_to_file(read_handle, write_handle)
160
+ end
161
+ read_handle.close()
162
+ write_handle.close()
163
+ assert_equal(expected, write_handle.string)
164
+
165
+ end
166
+
167
+ #MULTILINE (can't imagine doing this with global set to false, but we test that option anyway)
168
+
169
+ def test_multiline_and_global_without_explicit_receiver
170
+ expected = %q[
171
+ I like ruby and ruby likes me.
172
+ ruby is a great language
173
+
174
+ Vote for ruby.
175
+
176
+ ]
177
+ read_handle = StringIO.new(@original)
178
+ write_handle = StringIO.new("")
179
+ FileEditor.edit("dummy.txt") do
180
+ @regex = /ja.*?va/mi
181
+ @substitution_string = "ruby"
182
+ write_to_file(read_handle, write_handle)
183
+ end
184
+ read_handle.close()
185
+ write_handle.close()
186
+ assert_equal(expected, write_handle.string)
187
+
188
+ end
189
+
190
+ def test_multiline_and_not_global_without_explicit_receiver
191
+ expected = %q[
192
+ I like ruby and java likes me.
193
+ Java is a great language
194
+
195
+ Vote for ja
196
+ va.
197
+
198
+ ]
199
+ read_handle = StringIO.new(@original)
200
+ write_handle = StringIO.new("")
201
+ FileEditor.edit("dummy.txt") do
202
+ @regex = /ja.*?va/mi
203
+ @substitution_string = "ruby"
204
+ @global = false
205
+ write_to_file(read_handle, write_handle)
206
+ end
207
+ read_handle.close()
208
+ write_handle.close()
209
+ assert_equal(expected, write_handle.string)
210
+
211
+ end
212
+
213
+ def test_multiline_global_with_explicit_receiver
214
+ expected = %q[
215
+ I like ruby and ruby likes me.
216
+ Java is a great language
217
+
218
+ Vote for ruby.
219
+
220
+ ]
221
+ read_handle = StringIO.new(@original)
222
+ write_handle = StringIO.new("")
223
+ FileEditor.edit("dummy.txt") do |editor|
224
+ editor.regex = /ja.*?va/m
225
+ editor.substitution_string = "ruby"
226
+ editor.write_to_file(read_handle, write_handle)
227
+ end
228
+ read_handle.close()
229
+ write_handle.close()
230
+ assert_equal(expected, write_handle.string)
231
+
232
+ end
233
+
234
+ def test_no_regex
235
+ non_existent_file = 'no_file.txt'
236
+ e = assert_raise(RuntimeError) do
237
+ FileEditor.edit("#{non_existent_file}") do
238
+ #@regex = /a/
239
+ @substitution_string = 'b'
240
+ run
241
+ end
242
+ end
243
+ assert_equal("You need to provide a regular expression to the editor", e.message)
244
+ end
245
+
246
+ def test_no_substitution_string
247
+ non_existent_file = 'no_file.txt'
248
+ e = assert_raise(RuntimeError) do
249
+ FileEditor.edit("#{non_existent_file}") do
250
+ @regex = /a/
251
+ #@substitution_string = 'b'
252
+ run
253
+ end
254
+ end
255
+ assert_equal("You need to provide a substitution string", e.message)
256
+ end
257
+
258
+ def test_file_not_found
259
+ non_existent_file = 'no_file.txt'
260
+ e = assert_raise(RuntimeError) do
261
+ FileEditor.edit("#{non_existent_file}") do
262
+ @regex = /a/
263
+ @substitution_string = 'b'
264
+ run
265
+ end
266
+ end
267
+ assert_equal("The file #{non_existent_file} does not exist", e.message)
268
+ end
269
+
270
+ def test_not_a_file
271
+ path_to_dir = 'lib'
272
+ e = assert_raise(RuntimeError) do
273
+ FileEditor.edit("#{path_to_dir}") do
274
+ @regex = /a/
275
+ @substitution_string = 'b'
276
+ run
277
+ end
278
+ end
279
+ assert_equal("The path #{path_to_dir} does not point to a file", e.message)
280
+ end
281
+
282
+ #THE FOLLOWING TESTS DO NOT TEST THE APPLICATION;
283
+ #THEY WERE AD HOC TEST TO DETERMINE HOW MULTILINE REGEXES WORK, AMONG OTHER THINGS
284
+
285
+ def test_is_multiline
286
+
287
+ editor = FileEditor.new("dummy.txt")
288
+
289
+ #these should not match
290
+ ignore = /anything/i.to_s #(?i-mx:anything)
291
+ extended = /anything/x.to_s #(?x-mi:anything)
292
+ ignore_plus_extended = /anything/ix.to_s #(?ix-m:anything)
293
+
294
+ not_multi = [ignore, extended, ignore_plus_extended]
295
+ not_multi.each do |r|
296
+ assert(!editor.is_multiline(r))
297
+ end
298
+
299
+ #these should match
300
+ multi = /anything/m.to_s #(?m-ix:anything)
301
+ multi_plus_ignore = /anything/mi.to_s #(?mi-x:anything)
302
+ multi_plus_extended = /anything/mx.to_s #(?mx-i:anything)
303
+
304
+ #special case that should match
305
+ all = /anything/mix.to_s #(?mix:anything)
306
+
307
+ is_multi = [multi, multi_plus_ignore, multi_plus_extended, all]
308
+ is_multi.each do |r|
309
+ assert(editor.is_multiline(r))
310
+ end
311
+
312
+ end
313
+
314
+ #second test is for special case of all: if there's no hyphen, the we know all options are included
315
+ def is_multiline(regex_string)
316
+ regex_string.match(/\?.*?m.*?-/) || regex_string.match(/\?[^-]+:/)
317
+ end
318
+
319
+ def test_regex_object_and_modifiers
320
+ r = Regexp.new("ell", Regexp::IGNORECASE)
321
+ #puts "ignore case: #{r.to_s}"
322
+ assert_equal(1, r.options)
323
+
324
+ r = Regexp.new("ell", Regexp::EXTENDED)
325
+ #puts "extended: #{r.to_s}"
326
+ assert_equal(2, r.options)
327
+
328
+ r = Regexp.new("ell", Regexp::MULTILINE)
329
+ #puts "MULTILINE: #{r.to_s}"
330
+ assert_equal(4, r.options)
331
+
332
+ r = Regexp.new("ell", (Regexp::EXTENDED | Regexp::IGNORECASE))
333
+ #puts "EXTENDED AND IGNORE: #{r.to_s}"
334
+ assert_equal(3, r.options)
335
+
336
+ r = Regexp.new("ell", (Regexp::MULTILINE | Regexp::IGNORECASE))
337
+ #puts "MULTILINE AND IGNORE: #{r.to_s}"
338
+ assert_equal(5, r.options)
339
+
340
+ r = Regexp.new("ell", (Regexp::MULTILINE | Regexp::EXTENDED))
341
+ #puts "MULTILINE AND EXTENDED: #{r.to_s}"
342
+ assert_equal(6, r.options)
343
+
344
+ r = Regexp.new("ell", (Regexp::EXTENDED | Regexp::IGNORECASE | Regexp::MULTILINE))
345
+ #puts "all three: #{r.to_s}"
346
+ assert_equal(7, r.options)
347
+
348
+ #puts
349
+
350
+ ops_orred = [Regexp::IGNORECASE, Regexp::EXTENDED].reduce{|a,b| a|b }
351
+ assert_equal(3, ops_orred) #0001 | 0010 => 0011
352
+
353
+ ops_orred = [Regexp::IGNORECASE, Regexp::MULTILINE].reduce{|a,b| a|b }
354
+ assert_equal(5, ops_orred) #0001 | 0100 => 0101
355
+
356
+ ops_orred = [Regexp::IGNORECASE, Regexp::EXTENDED, Regexp::MULTILINE].reduce{|a,b| a|b }
357
+ assert_equal(7, ops_orred) #0001 | 0010 | 0100 => 0111
358
+ end
359
+
360
+ def test_multiline
361
+ #note we have to use non-greedy quantifiers (probably always with multiline, or results could be way unexpected)
362
+ multiline_string = %q[
363
+ You are fine.
364
+ I like you.
365
+ Y
366
+ ou are weird.
367
+ ]
368
+ expected_multiline_string = %q[
369
+ yous are fine.
370
+ I like yous.
371
+ yous are weird.
372
+ ]
373
+ edited_multiline_string = multiline_string.gsub(/y.*?ou/im, 'yous');
374
+ assert_equal(expected_multiline_string, edited_multiline_string)
375
+
376
+ m = "you".match (/y.*ou/)
377
+ assert m
378
+ m = "you".match (/y.*ou/m)
379
+ assert m
380
+ m = "y\nou".match (/y.*ou/m)
381
+ assert m
382
+ s = %q[
383
+ y
384
+ ou
385
+ ]
386
+ m = s.match (/y.*ou/m)
387
+ assert m
388
+
389
+ result = "you".gsub(/y.*ou/m, 'Z')
390
+ assert_equal('Z', result)
391
+
392
+ #shows one of the tricks that greedy interpretations will play on multiline searches
393
+ test = %q[
394
+ you
395
+ y
396
+ ou
397
+ ]
398
+ expected =
399
+ %q[
400
+ Z
401
+ ]
402
+ result = test.gsub(/y.*ou/m, 'Z')
403
+ assert_equal(expected, result)
404
+ #note that we end up with one replacement instead of two,
405
+ #because the greedy interpretation matches the first y and then the last ou
406
+
407
+ #here's what we probably intended
408
+ test = %q[
409
+ you
410
+ y
411
+ ou
412
+ ]
413
+ expected =
414
+ %q[
415
+ Z
416
+ Z
417
+ ]
418
+ result = test.gsub(/y.*?ou/m, 'Z')
419
+ assert_equal(expected, result)
420
+
421
+
422
+ end
423
+
424
+ end
425
+
426
+
427
+ =begin
428
+
429
+ #TEMP
430
+ def test_ell
431
+ FileEditor.new("test.txt") do |editor|
432
+ editor.regex = /ell/i
433
+ editor.substitution_string = "ojj"
434
+ editor.keep_backup = false
435
+ editor.run
436
+ end
437
+ end
438
+
439
+ def test_ojj
440
+ FileEditor.new("test.txt") do |editor|
441
+ editor.regex = /ojj/i
442
+ editor.substitution_string = "ell"
443
+ editor.keep_backup = false
444
+ editor.run
445
+ end
446
+ end
447
+
448
+ #this feels like proper course going forward,
449
+ #pending the problem with including it in another class
450
+ def test_ell_with_instance_eval
451
+ FileEditor.edit("test.txt") do
452
+ @regex = /ell/i
453
+ @substitution_string = "ojj"
454
+ @keep_backup = false
455
+ end
456
+ end
457
+
458
+
459
+ def test_regex
460
+ s = "heLlo"
461
+ r = Regexp.new("ell", Regexp::IGNORECASE)
462
+ #r = Regexp.new(/ell/ix)
463
+ puts "r.to_s: #{r.to_s}"
464
+ puts "r.options: #{r.options}"
465
+ result = s.sub(r, "ojj")
466
+ puts "result: #{result}"
467
+ end
468
+
469
+ =end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: file_editor
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.5'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Buechler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-14 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: File editor makes it easy to edit a file in place, with options for saving
15
+ a backup
16
+ email:
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files:
20
+ - README.txt
21
+ files:
22
+ - file_editor.gemspec
23
+ - Rakefile
24
+ - README.txt
25
+ - lib/file_editor.rb
26
+ - test/test_file_editor.rb
27
+ homepage: http://rubygems.org/sqlfinder
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options:
31
+ - --main
32
+ - README.txt
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.24
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Edit a file in place
53
+ test_files: []