file_overwrite 0.1 → 1.0
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.
- checksums.yaml +4 -4
- data/ChangeLog +15 -0
- data/Makefile +1 -1
- data/README.en.rdoc +105 -31
- data/file_overwrite.gemspec +1 -1
- data/lib/{file_overwrite/file_overwrite.rb → file_overwrite.rb} +167 -69
- data/test/test_file_overwrite.rb +164 -11
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6db0d19a4e47ac84f622ac13f773b5eebe35187dd988e45e378ff60ad5062f48
|
4
|
+
data.tar.gz: 506a41b185811a8ac654141785e7b0022e54f9fcec30b6a59fd6b5fc3d432324
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbc80fba6539d7257e7c8bf8ce48ba56191d0083de67dddc1b5c8b678e7ed5dab0ea8459946310a78e9732181ca184107d90ee43855a7d40af4bb06762b0018a
|
7
|
+
data.tar.gz: b4a8bfc7e2c31ba80000f937ed04c77a5e1374aeeae3a4ed102073ff74f5c467cf054597807b7149957910275c365b0104cd95cd07d39a72d9bd521cda02770a
|
data/ChangeLog
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
-----
|
2
|
+
(Version: 1.0)
|
3
|
+
2018-10-13 Masa Sakano
|
4
|
+
* Major upgrade. sub and gsub now accept local-scope variables like $1.
|
5
|
+
|
6
|
+
* A bug in dump is fixed.
|
7
|
+
* Added alias save, the methods path and `last_match`, class methods modify! (open!), read, `each_line`.
|
8
|
+
* Added `.github/README.md`
|
9
|
+
* Major update in README
|
10
|
+
* Tests added accordingly.
|
11
|
+
|
12
|
+
-----
|
13
|
+
2018-10-12 Masa Sakano
|
14
|
+
|
15
|
+
* mv lib/file_overwrite/file_overwrite.rb lib/file_overwrite.rb
|
16
|
+
-----
|
2
17
|
(Version: 0.1)
|
3
18
|
2018-09-19 Masa Sakano
|
4
19
|
|
data/Makefile
CHANGED
data/README.en.rdoc
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
|
2
|
-
= FileOverwrite - Controller class to
|
2
|
+
= FileOverwrite - Controller class to overwrite a file with/without a backup file
|
3
3
|
|
4
4
|
== Summary
|
5
5
|
|
6
|
-
This class provides a Ruby-oriented scheme to safely overwrite an existing file,
|
6
|
+
This class provides a Ruby-oriented scheme to safely overwrite an existing file,
|
7
|
+
leaving a backup file unless specified otherwise. It writes a temporary file
|
8
|
+
first, which is renamed to the original file in one action. It accepts a block
|
9
|
+
like some IO or Array class-methods (e.g., +each_line+ and +readlines+) and
|
10
|
+
chaining like String methods (e.g., +read+, +sub+ and +gsub+).
|
11
|
+
|
7
12
|
|
8
13
|
This library realises a simple and secure handling to overwrite a file
|
9
14
|
on the spot like "-i" command-line option in Ruby, but inside a Ruby
|
10
15
|
script (rather than globally as in "-i" option) and with much more flexibility.
|
11
16
|
|
17
|
+
The master of this README file, as well as the document for all the methods, is found in
|
18
|
+
{RubyGems/file_overwrite}[https://rubygems.org/gems/file_overwrite] where all
|
19
|
+
the hyperlinks are active.
|
20
|
+
|
12
21
|
== Install
|
13
22
|
|
14
23
|
This script requires {Ruby}[http://www.ruby-lang.org] Version 2.0
|
@@ -23,18 +32,70 @@ some error messages can be less helpful in that case.
|
|
23
32
|
|
24
33
|
== Examples
|
25
34
|
|
35
|
+
=== Basic examples (short-hand versions)
|
36
|
+
|
37
|
+
The following six examples perform the identical operations.
|
38
|
+
|
39
|
+
f1 = FileOverwrite.new('a.txt'); f1.open{|ior, iow| iow.print ior.read.upcase}.save!
|
40
|
+
f2 = FileOverwrite.open!('b.txt'){|ior, iow| iow.print ior.read.upcase}
|
41
|
+
f3 = FileOverwrite.read!('c.txt'){|s| s.upcase}
|
42
|
+
f4 = FileOverwrite.read('d.txt').sub!(/.*/m){$&.upcase}
|
43
|
+
f5 = FileOverwrite.each_line!('e.txt'){|s| s.upcase}
|
44
|
+
f6 = FileOverwrite.readlines!('f.txt'){|a| a.map{|i| i.upcase}}
|
45
|
+
|
46
|
+
f6.path # => "f.txt"
|
47
|
+
f6.backup # => "f.txt.20180915.bak"
|
48
|
+
f6.sizes # => { :old => 40, :new => 40 }
|
49
|
+
|
50
|
+
The first example (f1) is the basic form of the use of this class. You create a class instance by the
|
51
|
+
constructor, perform a manipulataion(s) to modify the content, and *save* it,
|
52
|
+
which means *run* the overwriting operation of the original file. Until you
|
53
|
+
*run*, the original file unchanges, while any modifications are stored either in
|
54
|
+
the memory or a temporary file, either of which would be garbage-collected if
|
55
|
+
the process is not *run* in the end. A backup file is automatically
|
56
|
+
created, unless explicity suppressed, whose filename or suffix is either
|
57
|
+
automatically chosen or specified explicitly by the caller.
|
58
|
+
Any parts of the operations are chainable or separatable, as you like.
|
59
|
+
|
60
|
+
In this example, the main manipuration of modification is *IO-type*, or
|
61
|
+
File.open-type. You are responsible to manually read the content of the
|
62
|
+
existing file and write the updated content. This is the least memory-heavy
|
63
|
+
manipulation and so is most suitable when the original file is huge.
|
64
|
+
|
65
|
+
The second example (f2) is just a short hand of it.
|
66
|
+
|
67
|
+
The third and fourth examples (f3 and f4) are *String-type*, or IO.read-like.
|
68
|
+
You handle the entire content of the file as a single String. The fourth
|
69
|
+
example demonstrates an example of chaining manipulations.
|
70
|
+
|
71
|
+
The fifth example (f5) is another *String-type* manipulation. You manipulate
|
72
|
+
the content of the file line by line as a String in the iterator (aka block), as in +IO.each_line+.
|
73
|
+
Although technically {FileOverwrite#read} is all you need for the String-type
|
74
|
+
manipulation, given you can do any manipulation you like, including
|
75
|
+
String#sub and the equivalent one to {FileOverwrite#each_line}, in the block, a
|
76
|
+
few more String-type manipulations are defined for convenience (see below).
|
77
|
+
|
78
|
+
The sixth example (f6) is *Array-type* manipulation. You get the entire
|
79
|
+
content of the file as an Array, each element of which corresponds to a line in
|
80
|
+
the file just as +IO.readlines+. You manipulate it and return an Array, which
|
81
|
+
will be then joined and output to the original file to overwrite it when you save.
|
82
|
+
|
83
|
+
Any methods with the name ending with a bang sign '!' implies it automatically
|
84
|
+
+run!+ (or +save!+) the final overwriting process, as soon as the manipulation
|
85
|
+
of content modification finishes.
|
86
|
+
|
26
87
|
=== Chaining example (with noop, meaning dryrun)
|
27
88
|
|
28
89
|
f1 = FileOverwrite.new('a.txt', noop: true, verbose: true)
|
29
90
|
# Treat the content as String
|
30
|
-
f1.sub(/abc/, 'xyz').gsub(/(.)ef/){
|
91
|
+
f1.sub(/abc/, 'xyz').gsub(/(.)ef/){$1}.run!
|
31
92
|
f1.completed? # => true
|
32
93
|
f1.sizes # => { :old => 40, :new => 50 }
|
33
94
|
f1.backup # => 'a.txt.20180915.bak'
|
34
95
|
# However, the file has not been created
|
35
96
|
# and the original file has not been modified, either,
|
36
97
|
# due to the noop option
|
37
|
-
|
98
|
+
|
38
99
|
=== IO.read type manipulation (String-based block)
|
39
100
|
|
40
101
|
f2 = FileOverwrite.new('a.txt', suffix: '~')
|
@@ -99,36 +160,41 @@ Note if the input file is not touched at all, the file is never
|
|
99
160
|
|
100
161
|
== Description
|
101
162
|
|
163
|
+
Output is via +FileUtils#fu_output_message+ and in practice messages are output to STDERR.
|
164
|
+
|
102
165
|
=== Content manipulation
|
103
166
|
|
104
167
|
Three types of manipulation for the content of the file to update are allowed: IO, String, and Array.
|
105
168
|
|
106
169
|
==== IO-type manipulation
|
107
170
|
|
108
|
-
IO-type manipulation
|
171
|
+
The only IO-type manipulation is
|
109
172
|
|
110
|
-
* open (or modify)
|
173
|
+
* +open+ (or +modify+)
|
111
174
|
|
112
175
|
Two block parameters of IO instances are given (read and write in this order).
|
113
176
|
What is output (i.e., IO#print method) with the write-descriptor
|
114
177
|
inside the block will be the content of the overwritten file.
|
115
178
|
|
116
179
|
This method can *not* be chained. Once you call the method for manipulation,
|
117
|
-
you have to either {FileOverwrite#run!}, or {FileOverwrite#reset} and do
|
180
|
+
you have to either {FileOverwrite#run!}, or {FileOverwrite#reset} and do manipulation from
|
118
181
|
the beginning. For convenience, the same method names with the
|
119
182
|
trailing '!' are defined ({FileOverwrite#open!} and {FileOverwrite#modify!}), with which {FileOverwrite#run!} will be performed automatically.
|
120
183
|
|
184
|
+
The class method {FileOverwrite.open!} (or {FileOverwrite.modify!}) is also available
|
185
|
+
to skip the constructor and perform {FileOverwrite.run!} (or +save!+) straightaway.
|
186
|
+
|
121
187
|
==== String-type manipulation
|
122
188
|
|
123
189
|
String-type manipulation includes
|
124
190
|
|
125
|
-
* read (with block)
|
126
|
-
* sub (with or without block)
|
127
|
-
* gsub (with or without block)
|
128
|
-
* tr
|
129
|
-
* tr_s
|
130
|
-
* replace_with (same as String#replace, but can be chained)
|
131
|
-
* each_line (with block)
|
191
|
+
* +read+ (with block; like IO.read)
|
192
|
+
* +sub+ (with or without block)
|
193
|
+
* +gsub+ (with or without block; nb., as an extension from String#gsub the maximum number of matches can be specified with the optional argument max.)
|
194
|
+
* +tr+
|
195
|
+
* +tr_s+
|
196
|
+
* +replace_with+ (same as String#replace, but can be chained)
|
197
|
+
* +each_line+ (with block)
|
132
198
|
|
133
199
|
These methods must return String (or its equivalent) that will be written in the updated file.
|
134
200
|
Those that take block never return Enumerator (like String#sub).
|
@@ -155,21 +221,26 @@ content of the input file is stored in the memory until {FileOverwrite#run!} is
|
|
155
221
|
called and the object is GC-ed. For a very large file, IO-type
|
156
222
|
manipulation is more appropriate.
|
157
223
|
|
224
|
+
For +read+ (+read!+) and +each_line+ (+each_line!+), the class methods of the same names are is available to skip the constructor.
|
225
|
+
|
158
226
|
==== Array-type manipulation
|
159
227
|
|
160
228
|
Similarly, an Array-type manipulation is defined:
|
161
229
|
|
162
|
-
* readlines
|
230
|
+
* +readlines+
|
163
231
|
|
164
|
-
The
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
232
|
+
The block for the +readlines+ method must return either Array (or its
|
233
|
+
equivalent) or String. If an Array returned, another +readlines+ method can be
|
234
|
+
called again (or chained), and in the end the elements are simply concatnated
|
235
|
+
when they are written in the updated file. If a String is returned, any
|
236
|
+
subsequent methods of manipulation must be String-type manipulations before
|
237
|
+
{FileOverwrite#run!}.
|
169
238
|
|
170
239
|
Just like IO-type methods, this can be called with a trailing '!' to
|
171
240
|
perform {FileOverwrite#run!} straightaway.
|
172
241
|
|
242
|
+
The class method {FileOverwrite.readlines} (and +readlines!+) is available to skip the constructor.
|
243
|
+
|
173
244
|
=== Other methods
|
174
245
|
|
175
246
|
==== Those related to the state
|
@@ -196,6 +267,7 @@ Note none of the write-methods are available once {FileOverwrite#completed?}
|
|
196
267
|
{FileOverwrite#verbose}:: (read/write) The default value is set in the constructor, which can be overwritten any time with this method, and for temporarily in {FileOverwrite#run!}
|
197
268
|
{FileOverwrite#ext_enc_old}, {FileOverwrite#ext_enc_new}:: (read/write) Character-encoding of the file to read and write, respectively. The former can be overwritten with the {FileOverwrite#force_encoding}(enc) method, too.
|
198
269
|
{FileOverwrite#int_enc}:: (read/write) If set, Character-encoding of the String read from the file is (attempted to be) converted into this before user's processing. This can be overwritten with the {FileOverwrite#encode}() method, too.
|
270
|
+
{FileOverwrite#last_match}:: (read/write) To read (and write if need be) Regexp.last_match after {FileOverwrite#sub} or {FileOverwrite#gsub}. If a block is given to them, +Regexp.last_match+ in the caller's scope reflects the result. However, if a block is not given, it does not, and hence this method is the only way to access the last match. See Section "Known bugs" for detail.
|
199
271
|
|
200
272
|
== Developer's note
|
201
273
|
|
@@ -204,12 +276,13 @@ You can view it in {RubyGems/file_overwrite}[https://rubygems.org/gems/file_over
|
|
204
276
|
|
205
277
|
=== Algorithm
|
206
278
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
279
|
+
1. The manipulated results are held either in the memory or in a temporary file if IO-based manipulation.
|
280
|
+
2. Then when you perform {FileOverwrite#run!}, the following is done in one go:
|
281
|
+
1. the manipulated results are output to a temporary file if not done so, yet (String- or Array-based manipulations),
|
282
|
+
2. the original file is backed up to the specified backup file,
|
283
|
+
3. the temporary file is renamed to the original file,
|
284
|
+
4. if it is instructed to leave no backup file, the backup file is deleted.
|
285
|
+
3. After {FileOverwrite#run!}, the instance of this class is still accessible but frozen.
|
213
286
|
|
214
287
|
If {FileOverwrite#run!} or its equivalent is not performed, the temporary file will be
|
215
288
|
deleted by GC.
|
@@ -223,7 +296,12 @@ or simply run <tt>make test</tt>.
|
|
223
296
|
|
224
297
|
== Known bugs
|
225
298
|
|
226
|
-
|
299
|
+
1. After every execution of {FileOverwrite#sub} and {FileOverwrite#gsub},
|
300
|
+
if a block is given to them, +Regexp.last_match+ (or +$~+) in the caller's scope
|
301
|
+
reflects the result. However, if a block is not given, it does not, and they
|
302
|
+
retain the values before calling {FileOverwrite#sub} etc in the local scope.
|
303
|
+
The value of MatchData of the last match in such cases is accessible via
|
304
|
+
{FileOverwrite#last_match}.
|
227
305
|
|
228
306
|
|
229
307
|
== Copyright
|
@@ -231,7 +309,3 @@ None.
|
|
231
309
|
Author:: Masa Sakano < info a_t wisebabel dot com >
|
232
310
|
Versions:: The versions of this package follow Semantic Versioning (2.0.0) http://semver.org/
|
233
311
|
|
234
|
-
|
235
|
-
LocalWords: FileOverwrite gsub RUBYLIB rb noop dryrun txt ior iow
|
236
|
-
LocalWords: RuntimeError XYZ FileOverwriteError FrozenError ary
|
237
|
-
LocalWords: readlines FileUtils mtime allstr outstr chainable
|
data/file_overwrite.gemspec
CHANGED
@@ -4,7 +4,7 @@ require 'rake'
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = %q{file_overwrite}
|
7
|
-
s.version = "0
|
7
|
+
s.version = "1.0"
|
8
8
|
# s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
9
|
# s.bindir = 'bin'
|
10
10
|
s.authors = ["Masa Sakano"]
|
@@ -105,14 +105,26 @@ class FileOverwrite
|
|
105
105
|
# @return [Encoding]
|
106
106
|
attr_accessor :int_enc
|
107
107
|
|
108
|
+
# last_match from {#sub} ({#sub!}) and {#gsub}
|
109
|
+
#
|
110
|
+
# This values is false when uninitialised.
|
111
|
+
# To set this value is for user's convenience only, and
|
112
|
+
# has no effect on processing or any of the methods.
|
113
|
+
# Every time a user runs {#sub} or {#gsub}, this value is reset.
|
114
|
+
#
|
115
|
+
# @!attribute [rw] last_match
|
116
|
+
# @return [MatchData]
|
117
|
+
attr_accessor :last_match
|
118
|
+
|
108
119
|
# @param fname [String] Input filename
|
109
120
|
# @param backup: [String, NilClass] File name to which the original file is backed up. If non-Nil, suffix is ignored.
|
110
121
|
# @param suffix: [String, TrueClass, FalseClass, NilClass] Suffix of the backup file. True for Def, or false if no backup.
|
111
122
|
# @param noop: [Boolean] no-operationor dryrun
|
112
123
|
# @param verbose: [Boolean, NilClass] the same as $VERBOSE or the command-line option -W, i.e., the verbosity is (true > false > nil). Forced to be true if $DEBUG
|
113
124
|
# @param clobber: [Boolean] raise Exception if false(Def) and fname exists and suffix is non-null.
|
114
|
-
# @param touch: [Boolean] if true (non-Def), when the file content does not change, the timestamp is updated,
|
115
|
-
|
125
|
+
# @param touch: [Boolean] if true (non-Def), even when the file content does not change, the timestamp is updated, as long as the file is attempted to be save (#{run!} or #{save})
|
126
|
+
# @param last_match: [MatchData, NilClass, FalseClass] To pass Regexp.last_match in Caller's scope.
|
127
|
+
def initialize(fname, backup: nil, suffix: true, noop: false, verbose: $VERBOSE, clobber: false, touch: false, last_match: false)
|
116
128
|
@fname = fname
|
117
129
|
@backup = backup
|
118
130
|
@suffix = (backup ? true : suffix)
|
@@ -120,6 +132,7 @@ class FileOverwrite
|
|
120
132
|
@verbose = $DEBUG || verbose
|
121
133
|
@clobber = clobber
|
122
134
|
@touch = touch
|
135
|
+
@last_match = last_match
|
123
136
|
|
124
137
|
@ext_enc_old = nil
|
125
138
|
@ext_enc_new = nil
|
@@ -144,7 +157,7 @@ class FileOverwrite
|
|
144
157
|
# (so you can tell what the backup filename would be if the suffix was set).
|
145
158
|
#
|
146
159
|
# @param suffix [String, TrueClass, FalseClass, NilClass] Suffix of the backup file. True for Def, or false if no backup.
|
147
|
-
# @param backupfile: [String, NilClass] Explicilty specify the backup filename.
|
160
|
+
# @param backupfile: [String, NilClass] Explicilty specify the backup filename, when suffix is nil. (For internal use)
|
148
161
|
# @return [String, NilClass]
|
149
162
|
def backup(suffix=nil, backupfile: nil)
|
150
163
|
return backup_from_suffix(suffix) if suffix # non-nil suffix explicitly given
|
@@ -187,7 +200,7 @@ class FileOverwrite
|
|
187
200
|
def dump
|
188
201
|
return @outstr.dup if @outstr
|
189
202
|
return join_outary() if @outary
|
190
|
-
return File.read(@iotmp.path) if @is_edit_finished
|
203
|
+
return File.read(@iotmp.path) if @is_edit_finished && !completed?
|
191
204
|
File.read(@fname)
|
192
205
|
end
|
193
206
|
|
@@ -266,6 +279,16 @@ class FileOverwrite
|
|
266
279
|
alias_method :reset?, :fresh? if ! self.method_defined?(:reset?)
|
267
280
|
|
268
281
|
|
282
|
+
# Returns the (duplicate of the) filename to be (or to have been) updated.
|
283
|
+
#
|
284
|
+
# To destructively modify this value would affect nothing in the parent object.
|
285
|
+
#
|
286
|
+
# @return [String]
|
287
|
+
def path
|
288
|
+
@fname.dup
|
289
|
+
end
|
290
|
+
|
291
|
+
|
269
292
|
# Returns true if the instance is ready to run (to execute overwriting the file).
|
270
293
|
def ready?
|
271
294
|
!fresh? && !completed?
|
@@ -314,7 +337,7 @@ class FileOverwrite
|
|
314
337
|
|
315
338
|
# String#valid_encoding?()
|
316
339
|
#
|
317
|
-
# returns nil if the process has been already completed.
|
340
|
+
# @note returns nil if the process has been already completed.
|
318
341
|
#
|
319
342
|
# @return [Boolean, NilClass]
|
320
343
|
def valid_encoding?()
|
@@ -422,7 +445,9 @@ class FileOverwrite
|
|
422
445
|
|
423
446
|
return self
|
424
447
|
end
|
425
|
-
alias_method :run,
|
448
|
+
alias_method :run, :run! if ! self.method_defined?(:run)
|
449
|
+
alias_method :save, :run! if ! self.method_defined?(:save)
|
450
|
+
alias_method :save!, :run! if ! self.method_defined?(:save!)
|
426
451
|
|
427
452
|
|
428
453
|
########################################################
|
@@ -459,13 +484,18 @@ class FileOverwrite
|
|
459
484
|
raise ArgumentError, 'Block must be given.' if !block_given?
|
460
485
|
normalize_status(:@is_edit_finished)
|
461
486
|
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
487
|
+
kwd_open = {}
|
488
|
+
kwd_open[:external_encoding] = @ext_enc_old if @ext_enc_old
|
489
|
+
kwd_open[:internal_encoding] = @int_enc if @int_enc
|
490
|
+
kwd_open[:external_encoding] = (kwd[:ext_enc] || kwd_open[:external_encoding])
|
491
|
+
kwd_open[:internal_encoding] = (kwd[:int_enc] || kwd_open[:internal_encoding])
|
492
|
+
[:mode, :flags, :encoding, :textmode, :binmode, :autoclose].each do |es|
|
493
|
+
# Method list from https://ruby-doc.org/core-2.5.1/IO.html#method-c-new
|
494
|
+
kwd_open[es] = kwd[es] if kwd.key?(es)
|
495
|
+
end
|
466
496
|
|
467
497
|
begin
|
468
|
-
File.open(@fname, **
|
498
|
+
File.open(@fname, **kwd_open) { |ioin|
|
469
499
|
@iotmp = tempfile_io
|
470
500
|
yield(ioin, @iotmp)
|
471
501
|
}
|
@@ -577,14 +607,15 @@ class FileOverwrite
|
|
577
607
|
|
578
608
|
# Handler to process the entire string of the file (or current content)
|
579
609
|
#
|
580
|
-
# If block is not given, just sets the state as String
|
610
|
+
# If block is not given, just sets the processing-state as String.
|
581
611
|
#
|
582
|
-
# Else,
|
583
|
-
# Then, the returned value is held as a String,
|
612
|
+
# Else, IO.read(infile) is given to the block. No other options, such as length,
|
613
|
+
# as in IO.read are accepted. Then, the returned value is held as a String,
|
614
|
+
# while self is returned; hence this method can be chained.
|
584
615
|
# If the block returns nil (or Boolean), {FileOverwriteError} is raised.
|
585
|
-
# Make sure to return a String
|
616
|
+
# Make sure for the block to return a String.
|
586
617
|
#
|
587
|
-
# Note this method does not take arguments as in IO.read
|
618
|
+
# Note this method does not take arguments as in IO.read .
|
588
619
|
#
|
589
620
|
# @param **kwd [Hash] ext_enc, int_enc
|
590
621
|
# @return [self]
|
@@ -599,7 +630,8 @@ class FileOverwrite
|
|
599
630
|
end
|
600
631
|
|
601
632
|
@outstr = yield(@outstr) if block_given?
|
602
|
-
raise FileOverwriteError, 'ERROR: The returned value from the block in read()
|
633
|
+
raise FileOverwriteError, 'ERROR: The returned value from the block in read() has to be String.' if !defined?(@outstr.gsub)
|
634
|
+
warn "WARNING: Empty string returned from a block in #{__method__}" if !@verbose.nil? && @outstr.empty?
|
603
635
|
self
|
604
636
|
end
|
605
637
|
|
@@ -614,7 +646,7 @@ class FileOverwrite
|
|
614
646
|
end
|
615
647
|
|
616
648
|
|
617
|
-
#
|
649
|
+
# Replaces the file content with the given argument like {String#replace}
|
618
650
|
#
|
619
651
|
# This method can be chained.
|
620
652
|
#
|
@@ -641,18 +673,22 @@ class FileOverwrite
|
|
641
673
|
# This method can be chained.
|
642
674
|
# This method never returns an Enumerator.
|
643
675
|
#
|
644
|
-
#
|
645
|
-
#
|
646
|
-
#
|
647
|
-
#
|
648
|
-
#
|
649
|
-
#
|
676
|
+
# @note Algorithm
|
677
|
+
# To realise the local-scope variables like $~, $1, and Regexp.last_match to
|
678
|
+
# be usable inside the block as in String#sub, it overwrites them when a block
|
679
|
+
# is given (See the linked article for the phylosophy of how to do it).
|
680
|
+
# Once a block is read, those variables remain as updated values even after the block
|
681
|
+
# in the caller's scope, in the same way as String#sub. However, when a block is not given,
|
682
|
+
# those variables are *NOT* updated, which is different from String#sub.
|
683
|
+
# You can retrieve the MatchData by this method via {#last_match} after {#sub}
|
684
|
+
# is called, if need be.
|
650
685
|
#
|
651
686
|
# @param *rest [Array<Regexp,String>]
|
652
687
|
# @param max: [Integer] the number of the maximum matches. If it is not 1, {#gsub} is called, instead. See {#gsub} for detail.
|
653
688
|
# @param **kwd [Hash] ext_enc, int_enc
|
654
689
|
# @return [self]
|
655
690
|
# @yield the same as String#sub
|
691
|
+
# @see https://stackoverflow.com/questions/52359278/how-to-pass-regexp-last-match-to-a-block-in-ruby/52385870#52385870
|
656
692
|
def sub(*rest, max: 1, **kwd, &bloc)
|
657
693
|
return self if sub_gsub_args_only(*rest, max: max, **kwd)
|
658
694
|
|
@@ -664,16 +700,20 @@ class FileOverwrite
|
|
664
700
|
return gsub(*rest, max: max, **kwd, &bloc)
|
665
701
|
end
|
666
702
|
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
raise
|
703
|
+
@last_match = rest[0].match(@outstr)
|
704
|
+
return self if !@last_match
|
705
|
+
|
706
|
+
# Sets $~ (Regexp.last_match) in the given block.
|
707
|
+
# @see https://stackoverflow.com/questions/52359278/how-to-pass-regexp-last-match-to-a-block-in-ruby/52385870#52385870
|
708
|
+
bloc.binding.tap do |b|
|
709
|
+
b.local_variable_set(:_, $~)
|
710
|
+
b.eval("$~=_")
|
676
711
|
end
|
712
|
+
|
713
|
+
# The first (and only) argument for the block is $& .
|
714
|
+
# Returning nil, Integer etc is accepted in the block of sub/gsub
|
715
|
+
@outstr = @last_match.pre_match + yield(@last_match[0]).to_s + @last_match.post_match
|
716
|
+
return self
|
677
717
|
end
|
678
718
|
|
679
719
|
|
@@ -693,26 +733,33 @@ class FileOverwrite
|
|
693
733
|
# This method can be chained.
|
694
734
|
# This method never returns an Enumerator.
|
695
735
|
#
|
696
|
-
# This method supplies the MatchData of the match as the second block parameter
|
697
|
-
# in addition to the matched string as in String#sub.
|
698
|
-
#
|
699
736
|
# Being different from the standard Srrint#gsub, this method accepts
|
700
737
|
# the optional parameter max, which specifies the maximum number of times
|
701
738
|
# of the matches and is valid ONLY WHEN a block is given.
|
702
739
|
#
|
740
|
+
# @note Algorithm
|
741
|
+
# See {#sub} for the basic algorithm.
|
742
|
+
# This method emulates String#gsub as much as possible (duck-typing).
|
743
|
+
# In String#gsub, the variable $~ after the method has the last matched characters
|
744
|
+
# as the matched string and the original string before the last matched characters
|
745
|
+
# as pre_match. For example,
|
746
|
+
# 'abc'.gsub(/./){$1.upcase}
|
747
|
+
# returns
|
748
|
+
# 'ABC'
|
749
|
+
# and leaves
|
750
|
+
# $& == 'c'
|
751
|
+
# Regexp.pre_match == 'ab'
|
752
|
+
# It is the same in this method.
|
753
|
+
#
|
703
754
|
# @note Disclaimer
|
704
755
|
# When a block is not given but arguments only (and not expecting Enumerator to return),
|
705
756
|
# this method simply calls String#gsub . However, when only 1 argument
|
706
757
|
# and a block is given, this method must iterate on its own, which is implemented.
|
707
758
|
# I am not 100% confident if this method works in the completely same way
|
708
|
-
# as String#gsub in every single situation
|
709
|
-
#
|
710
|
-
# has so many possibilities; I have made the best effort and so far
|
711
|
-
# I have not found any cases where this method breaks.
|
759
|
+
# as String#gsub in every single situation, given the regular expression
|
760
|
+
# has so many possibilities; so far I have not found any cases where this method breaks.
|
712
761
|
# This method is more inefficient and slower than the original String#gsub
|
713
|
-
# as
|
714
|
-
# (which is unavoidable to implement it properly, I think), and the implementation
|
715
|
-
# is in pure Ruby.
|
762
|
+
# as the iteration is implemented in pure Ruby.
|
716
763
|
#
|
717
764
|
# @param *rest [Array<Regexp,String>]
|
718
765
|
# @param max: [Integer] the number of the maximum matches. 0 means no limit (as in String#gsub). Valid only if a block is given.
|
@@ -720,6 +767,7 @@ class FileOverwrite
|
|
720
767
|
# @return [self]
|
721
768
|
# @yield the same as String#gsub
|
722
769
|
# @see #sub
|
770
|
+
# @see https://stackoverflow.com/questions/52359278/how-to-pass-regexp-last-match-to-a-block-in-ruby/52385870#52385870
|
723
771
|
def gsub(*rest, max: 0, **kwd, &bloc)
|
724
772
|
return sub(*rest, max: 1, **kwd, &bloc) if 1 == max # Note: Error message would be labelled as 'sub'
|
725
773
|
return self if sub_gsub_args_only(*rest, max: max, **kwd)
|
@@ -730,39 +778,45 @@ class FileOverwrite
|
|
730
778
|
|
731
779
|
max = 5.0/0 if max.to_i <= 0
|
732
780
|
|
733
|
-
|
781
|
+
regbase_str = rest[0].to_s
|
782
|
+
regex = Regexp.new( sprintf('(%s)', regbase_str) ) # to guarantee the entire string is picked up by String#scan
|
783
|
+
scans = @outstr.scan(regex)
|
734
784
|
return self if scans.empty? # no matches
|
735
785
|
|
736
786
|
scans.map!{|i| [i].flatten} # Originally, it can be a double array.
|
737
|
-
regbase_str = rest[0].to_s
|
738
787
|
prematch = ''
|
739
788
|
ret = ''
|
740
789
|
imatch = 0 # Number of matches
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
790
|
+
scans.each do |ea_sc|
|
791
|
+
str_matched = ea_sc[0]
|
792
|
+
imatch += 1
|
793
|
+
pre_size = prematch.size
|
794
|
+
pos_end_p1 = @outstr.index(str_matched, pre_size) # End+1
|
795
|
+
str_between = @outstr[pre_size...pos_end_p1]
|
796
|
+
prematch << str_between
|
797
|
+
ret << str_between
|
798
|
+
regex = Regexp.new( sprintf('(?<=\A%s)%s', Regexp.quote(prematch), regbase_str) )
|
799
|
+
#regex = rest[0] if prematch.empty? # The first run
|
800
|
+
@last_match = regex.match(@outstr)
|
801
|
+
prematch << str_matched
|
802
|
+
|
803
|
+
# Sets $~ (Regexp.last_match) in the given block.
|
804
|
+
# @see https://stackoverflow.com/questions/52359278/how-to-pass-regexp-last-match-to-a-block-in-ruby/52385870#52385870
|
805
|
+
bloc.binding.tap do |b|
|
806
|
+
b.local_variable_set(:_, $~)
|
807
|
+
b.eval("$~=_")
|
757
808
|
end
|
758
|
-
ret << Regexp.last_match.post_match # Guaranteed to be non-nil.
|
759
809
|
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
810
|
+
# The first (and only) argument for the block is $& .
|
811
|
+
# Returning nil, Integer etc is accepted in the block of sub/gsub
|
812
|
+
ret << yield(@last_match[0]).to_s
|
813
|
+
|
814
|
+
break if imatch >= max
|
765
815
|
end
|
816
|
+
ret << Regexp.last_match.post_match # Guaranteed to be non-nil.
|
817
|
+
|
818
|
+
@outstr = ret
|
819
|
+
return self
|
766
820
|
end
|
767
821
|
|
768
822
|
|
@@ -820,6 +874,16 @@ class FileOverwrite
|
|
820
874
|
# Class methods
|
821
875
|
########################################################
|
822
876
|
|
877
|
+
# Class method for {FileOverwrite#initialize}.{#modify!}
|
878
|
+
#
|
879
|
+
# @see #initialize
|
880
|
+
# @see #modify
|
881
|
+
def self.modify!(*rest, **kwd, &bloc)
|
882
|
+
new(*rest, **kwd).modify!(**kwd, &bloc)
|
883
|
+
end
|
884
|
+
singleton_class.send(:alias_method, :open!, :modify!)
|
885
|
+
|
886
|
+
|
823
887
|
# Shorthand of {FileOverwrite#initialize}.{#readlines}, taking parameters for both
|
824
888
|
#
|
825
889
|
# @param fname [String] Input and overwriting filename
|
@@ -844,6 +908,39 @@ class FileOverwrite
|
|
844
908
|
end
|
845
909
|
|
846
910
|
|
911
|
+
# Class method for {FileOverwrite#initialize}.{#read}
|
912
|
+
#
|
913
|
+
# @see #initialize
|
914
|
+
# @see #read
|
915
|
+
def self.read(*rest, **kwd, &bloc)
|
916
|
+
new(*rest, **kwd).send(__method__, **kwd, &bloc)
|
917
|
+
end
|
918
|
+
|
919
|
+
# Class method for {FileOverwrite#initialize}.{#read!}
|
920
|
+
#
|
921
|
+
# @see #initialize
|
922
|
+
# @see #read!
|
923
|
+
def self.read!(*rest, **kwd, &bloc)
|
924
|
+
new(*rest, **kwd).send(__method__, **kwd, &bloc)
|
925
|
+
end
|
926
|
+
|
927
|
+
# Class method for {FileOverwrite#initialize}.{#each_line}
|
928
|
+
#
|
929
|
+
# @see #initialize
|
930
|
+
# @see #each_line
|
931
|
+
def self.each_line(fname, *rest, **kwd, &bloc)
|
932
|
+
new(fname, **kwd).send(__method__, *rest, **kwd, &bloc)
|
933
|
+
end
|
934
|
+
|
935
|
+
# Class method for {FileOverwrite#initialize}.{#each_line!}
|
936
|
+
#
|
937
|
+
# @see #initialize
|
938
|
+
# @see #each_line!
|
939
|
+
def self.each_line!(fname, *rest, **kwd, &bloc)
|
940
|
+
new(fname, **kwd).send(__method__, *rest, **kwd, &bloc)
|
941
|
+
end
|
942
|
+
|
943
|
+
|
847
944
|
########################################################
|
848
945
|
private
|
849
946
|
########################################################
|
@@ -1018,13 +1115,14 @@ class FileOverwrite
|
|
1018
1115
|
return if 1 == rest.size
|
1019
1116
|
|
1020
1117
|
method = caller_locations()[0].label
|
1021
|
-
if (max != 1 && 'sub' == method) || (max != 0 && 'gsub' == method)
|
1022
|
-
msg = sprintf "WARNING: max option (%s)
|
1118
|
+
if !@verbose.nil? && ((max != 1 && 'sub' == method) || (max != 0 && 'gsub' == method))
|
1119
|
+
msg = sprintf "WARNING: max option (%s) of neither 0 nor 1 is given. It is ignored in %s(). Give a block (instead of just arguments) for the max option to be taken into account.", max, method
|
1023
1120
|
warn msg
|
1024
1121
|
end
|
1025
1122
|
|
1026
1123
|
# Note: When 2 arguments are given, the block is simply ignored in default (in Ruby 2.5).
|
1027
1124
|
@outstr.send(method+'!', *rest) # sub! or gsub! => String|nil
|
1125
|
+
@last_match = Regexp.last_match # $~
|
1028
1126
|
@outstr
|
1029
1127
|
end
|
1030
1128
|
private :sub_gsub_args_only
|
data/test/test_file_overwrite.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
# require 'tempfile'
|
7
7
|
# require 'fileutils'
|
8
|
-
require 'file_overwrite
|
8
|
+
require 'file_overwrite'
|
9
9
|
|
10
10
|
$stdout.sync=true
|
11
11
|
$stderr.sync=true
|
@@ -13,6 +13,11 @@ $stderr.sync=true
|
|
13
13
|
|
14
14
|
#################################################
|
15
15
|
# Unit Test
|
16
|
+
#
|
17
|
+
# Note: $VERBOSE is set true in default in Testing.
|
18
|
+
# Then, the VERBOSE option in FileOverwite.new is
|
19
|
+
# also set TRUE in the tests in default, being different
|
20
|
+
# from the Ruby default environment ($VERBOSE==false).
|
16
21
|
#################################################
|
17
22
|
|
18
23
|
#if $0 == __FILE__
|
@@ -33,7 +38,7 @@ $stderr.sync=true
|
|
33
38
|
|
34
39
|
@orig_path = @tmpioin.path
|
35
40
|
@orig_content = "1 line A\n2 line B\n3 line C\n"
|
36
|
-
@orig_mtime = Time.now - 86400 # A day earlier
|
41
|
+
@orig_mtime = Time.now.round - 86400 # A day earlier
|
37
42
|
|
38
43
|
@tmpioin.print @orig_content
|
39
44
|
reset_mtime
|
@@ -77,6 +82,7 @@ $stderr.sync=true
|
|
77
82
|
assert_equal @tmpioin.path, m[1]
|
78
83
|
end
|
79
84
|
|
85
|
+
# Testing backup file names
|
80
86
|
def test_backup
|
81
87
|
file1 = create_template_file
|
82
88
|
fpath = file1.path
|
@@ -91,12 +97,16 @@ $stderr.sync=true
|
|
91
97
|
assert_equal fpath+suffix1, f1.backup # Reverted back
|
92
98
|
end
|
93
99
|
|
94
|
-
|
100
|
+
# FileOverwrite#modify (or #open as an alias)
|
101
|
+
def test_open
|
95
102
|
file1 = create_template_file
|
96
103
|
fpath = file1.path
|
97
104
|
f1 = FO.new(fpath)
|
98
105
|
s = 'tekito'
|
99
106
|
|
107
|
+
# Testing non-block
|
108
|
+
assert_raises(ArgumentError){ f1.modify }
|
109
|
+
|
100
110
|
# Testing open
|
101
111
|
f1.open{|ior, iow|
|
102
112
|
# Line 1
|
@@ -143,10 +153,33 @@ $stderr.sync=true
|
|
143
153
|
assert_nil f1.temporary_filename
|
144
154
|
end
|
145
155
|
|
156
|
+
|
157
|
+
# FileOverwrite.#open! (an alias of #modify!)
|
158
|
+
def test_open_classmethod
|
159
|
+
file1 = create_template_file
|
160
|
+
fpath = file1.path
|
161
|
+
s = 'tekito'
|
162
|
+
|
163
|
+
# Testing FileOverwrite.open!
|
164
|
+
f1 = nil
|
165
|
+
assert_output('', /File.+updated/){
|
166
|
+
f1 = FO.open!(fpath){|ior, iow|
|
167
|
+
line = ior.gets
|
168
|
+
ior.gets line
|
169
|
+
iow.print s+s
|
170
|
+
}
|
171
|
+
}
|
172
|
+
assert_equal fpath, f1.path
|
173
|
+
assert_equal s+s, f1.dump # Testing dump after completed.
|
174
|
+
assert_equal s+s, File.read(f1.path)
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# Tests of read, read!, replace_with
|
146
179
|
def test_read
|
147
180
|
file1 = create_template_file
|
148
181
|
fpath = file1.path
|
149
|
-
f1 = FO.new(fpath)
|
182
|
+
f1 = FO.new(fpath, verbose: true)
|
150
183
|
|
151
184
|
s = 'tekito'
|
152
185
|
f1.read{|i| s}
|
@@ -167,6 +200,20 @@ $stderr.sync=true
|
|
167
200
|
assert_output('', / not opened,/){ f1.run!(verbose: true) }
|
168
201
|
assert_equal @orig_content, File.read(fpath)
|
169
202
|
assert_in_delta(@orig_mtime, File.mtime(fpath), 1) # 1 sec allowance
|
203
|
+
|
204
|
+
# Test of invalid returns from the block
|
205
|
+
f1.reset
|
206
|
+
assert_raises(FileOverwriteError){ f1.read{} }
|
207
|
+
f1.reset
|
208
|
+
assert_raises(FileOverwriteError){ f1.read{nil} }
|
209
|
+
f1.reset
|
210
|
+
assert_raises(FileOverwriteError){ f1.read{5} }
|
211
|
+
f1.reset
|
212
|
+
assert_output('', /Empty string[^\n]*\n.*File .+updated.+Size.* => 0 bytes/im){ f1.read!(){''} }
|
213
|
+
# Warning is issued if an empty string is returned, but it is saved nonetheless.
|
214
|
+
assert_equal 0, f1.sizes[:new] # The new size is zero.
|
215
|
+
|
216
|
+
assert_raises(FrozenError){ f1.reset } # Prohibited to reset, once saved.
|
170
217
|
end
|
171
218
|
|
172
219
|
def test_open_run_noop # modify
|
@@ -289,13 +336,30 @@ $stderr.sync=true
|
|
289
336
|
assert_equal @orig_content+s, File.read(fpath)
|
290
337
|
assert_nil f1.sizes
|
291
338
|
end
|
292
|
-
|
339
|
+
|
340
|
+
# FileOverwrite.#read!
|
341
|
+
def test_read_classmethod
|
342
|
+
file1 = create_template_file
|
343
|
+
fpath = file1.path
|
344
|
+
s = 'tekito'
|
345
|
+
|
346
|
+
# Testing FileOverwrite.read!
|
347
|
+
f1 = nil
|
348
|
+
assert_output('', /File.+updated/){
|
349
|
+
f1 = FO.read!(fpath){|i| s+s}
|
350
|
+
}
|
351
|
+
assert_equal fpath, f1.path
|
352
|
+
assert_equal s+s, f1.dump # Testing dump after completed.
|
353
|
+
assert_equal s+s, File.read(f1.path)
|
354
|
+
end
|
355
|
+
|
293
356
|
def test_sub
|
294
357
|
file1 = create_template_file
|
295
358
|
fpath = file1.path
|
296
359
|
f1 = FO.new(fpath, suffix: nil, verbose: true)
|
297
360
|
|
298
361
|
f1.sub(/line/, 'xyz')
|
362
|
+
assert_equal 'line', f1.last_match[0]
|
299
363
|
assert_match(/xyz/, f1.dump.split(/\n/)[0])
|
300
364
|
refute_match(/xyz/, f1.dump.split(/\n/)[1])
|
301
365
|
f1.reset
|
@@ -308,6 +372,9 @@ $stderr.sync=true
|
|
308
372
|
refute_match(/xyz/, f1.dump.split(/\n/)[0])
|
309
373
|
|
310
374
|
f1.sub(/(li)(n)/, '\1' + 'k')
|
375
|
+
assert_equal 'lin', f1.last_match[0]
|
376
|
+
assert_equal 'li', f1.last_match[1]
|
377
|
+
assert_equal 'n', f1.last_match[2]
|
311
378
|
assert_match(/like/, f1.dump.split(/\n/)[0])
|
312
379
|
refute_match(/like/, f1.dump.split(/\n/)[1])
|
313
380
|
f1.reset
|
@@ -315,16 +382,17 @@ $stderr.sync=true
|
|
315
382
|
|
316
383
|
# Failed-match case
|
317
384
|
f1.sub(/naiyo(li)(n)/, '\1' + 'k')
|
385
|
+
assert_nil f1.last_match
|
318
386
|
assert_match(/line/, f1.dump.split(/\n/)[0])
|
319
387
|
assert_equal @orig_content, f1.instance_eval{@outstr}
|
320
388
|
f1.reset
|
321
389
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
f1.
|
327
|
-
|
390
|
+
# With a block
|
391
|
+
f1.sub(/(li)(n)/){$1.upcase+$2}
|
392
|
+
# f1.sub(/(li)(n)/){|_,m| m[1].upcase+m[2]} # $1.upcase + $2 # Old version
|
393
|
+
assert_equal 'lin', f1.last_match[0]
|
394
|
+
assert_equal 'li', f1.last_match[1]
|
395
|
+
assert_equal 'n', f1.last_match[2]
|
328
396
|
assert_match(/LIne/, f1.dump.split(/\n/)[0], "DEBUG: "+f1.dump)
|
329
397
|
refute_match(/LIne/, f1.dump.split(/\n/)[1])
|
330
398
|
f1.sub(/I/){nil} # nil.to_s (as in String#sub)
|
@@ -335,8 +403,19 @@ $stderr.sync=true
|
|
335
403
|
refute_match(/LI?ne/, f1.dump.split(/\n/)[0])
|
336
404
|
assert_nil f1.instance_eval{@outstr}
|
337
405
|
|
406
|
+
# With a block with an argument
|
407
|
+
f1.sub(/(li)(n)/){|ms| ms.upcase} # ms == $&
|
408
|
+
assert_equal 'lin', f1.last_match[0]
|
409
|
+
assert_equal 'li', f1.last_match[1]
|
410
|
+
assert_equal 'n', f1.last_match[2]
|
411
|
+
assert_match(/LINe/, f1.dump.split(/\n/)[0], "DEBUG: "+f1.dump)
|
412
|
+
f1.reset
|
413
|
+
refute_match(/LI?ne/, f1.dump.split(/\n/)[0])
|
414
|
+
assert_nil f1.instance_eval{@outstr}
|
415
|
+
|
338
416
|
# Failed-match case
|
339
417
|
f1.sub(/naiyo(li)(n)/){|_,m| m[1].upcase+m[2]} # $1.upcase + $2
|
418
|
+
assert_nil f1.last_match
|
340
419
|
assert_match(/line/, f1.dump.split(/\n/)[0], "DEBUG: "+f1.dump)
|
341
420
|
f1.reset
|
342
421
|
end
|
@@ -347,7 +426,10 @@ $stderr.sync=true
|
|
347
426
|
fpath = file1.path
|
348
427
|
f1 = FO.new(fpath, suffix: nil, verbose: true)
|
349
428
|
|
429
|
+
f1.gsub(/naiyo/, 'xyz') # Failed match
|
430
|
+
assert_nil f1.last_match
|
350
431
|
assert_output('', ''){ f1.gsub(/line/, 'xyz') }
|
432
|
+
assert_equal 'line', f1.last_match[0]
|
351
433
|
assert_match(/xyz/, f1.dump.split(/\n/)[0])
|
352
434
|
assert_match(/xyz/, f1.dump.split(/\n/)[1])
|
353
435
|
assert_match(/xyz/, f1.dump.split(/\n/)[2])
|
@@ -355,6 +437,7 @@ $stderr.sync=true
|
|
355
437
|
refute_match(/xyz/, f1.dump.split(/\n/)[0])
|
356
438
|
|
357
439
|
assert_output('', ''){ f1.gsub(/line/, 'xyz', max: 1) }
|
440
|
+
assert_equal 'line', f1.last_match[0]
|
358
441
|
assert_match(/xyz/, f1.dump.split(/\n/)[0])
|
359
442
|
refute_match(/xyz/, f1.dump.split(/\n/)[1])
|
360
443
|
f1.reset
|
@@ -364,6 +447,7 @@ $stderr.sync=true
|
|
364
447
|
assert_output('', /WARNING\b.+\bmax/i){ f1.gsub(/line/, 'xyz', max: 2) }
|
365
448
|
f1.reset
|
366
449
|
|
450
|
+
# With a block
|
367
451
|
assert_output(nil){ f1.gsub(/line/){ 'xyz' } }
|
368
452
|
assert_match(/xyz/, f1.dump.split(/\n/)[0])
|
369
453
|
assert_match(/xyz/, f1.dump.split(/\n/)[1])
|
@@ -371,12 +455,44 @@ $stderr.sync=true
|
|
371
455
|
f1.reset
|
372
456
|
refute_match(/xyz/, f1.dump.split(/\n/)[0])
|
373
457
|
|
458
|
+
# With a block with an argument and $1 etc
|
459
|
+
mat=nil
|
460
|
+
f1.gsub(/lin(e)/){ |ms| ms+$1.upcase } # lineE
|
461
|
+
mat=$~
|
462
|
+
assert_equal mat, f1.last_match
|
463
|
+
assert_match(/lineE/, f1.dump.split(/\n/)[0])
|
464
|
+
assert_match(/lineE/, f1.dump.split(/\n/)[1])
|
465
|
+
assert_match(/lineE/, f1.dump.split(/\n/)[2])
|
466
|
+
assert_equal 'e', mat[1]
|
467
|
+
assert_equal 2, mat.pre_match.scan(/\bline\b/).size # "1 line A\n2 line B\n3 "
|
468
|
+
assert_match(/[^\n]+\n$/m, mat.post_match) # " C\n"
|
469
|
+
f1.reset
|
470
|
+
refute_match(/lineE/, f1.dump.split(/\n/)[0])
|
471
|
+
|
472
|
+
# With a block with max option
|
374
473
|
assert_output('', ''){ f1.gsub(/line/, max: 2){ 'xyz' } }
|
474
|
+
assert_equal 'line', f1.last_match[0]
|
375
475
|
assert_match(/xyz/, f1.dump.split(/\n/)[0])
|
376
476
|
assert_match(/xyz/, f1.dump.split(/\n/)[1])
|
377
477
|
refute_match(/xyz/, f1.dump.split(/\n/)[2])
|
378
478
|
f1.reset
|
379
479
|
refute_match(/xyz/, f1.dump.split(/\n/)[0])
|
480
|
+
|
481
|
+
# With a block with an argument and $1 etc
|
482
|
+
# assert_output(nil){ f1.gsub(/lin(e)/){ |ms| ms+$1.upcase } } # lineE
|
483
|
+
mat=nil
|
484
|
+
f1.gsub(/lin(e)/, max: 2){ |ms| ms+$1.upcase } # lineE
|
485
|
+
mat=$~
|
486
|
+
assert_equal mat, f1.last_match
|
487
|
+
assert_match(/lineE/, f1.dump.split(/\n/)[0])
|
488
|
+
assert_match(/lineE/, f1.dump.split(/\n/)[1])
|
489
|
+
refute_match(/lineE/, f1.dump.split(/\n/)[2])
|
490
|
+
assert_equal 'e', mat[1]
|
491
|
+
assert_equal 1, mat.pre_match.scan(/\bline\b/).size # "1 line A\n2 "
|
492
|
+
assert_equal " B\n3 line C\n", mat.post_match
|
493
|
+
assert_match(/[^\n]+\n[^\n]+\n$/m, mat.post_match) # " B\n3 line C\n"
|
494
|
+
f1.reset
|
495
|
+
refute_match(/lineE/, f1.dump.split(/\n/)[0])
|
380
496
|
end
|
381
497
|
|
382
498
|
|
@@ -437,6 +553,25 @@ $stderr.sync=true
|
|
437
553
|
end
|
438
554
|
|
439
555
|
|
556
|
+
# FileOverwrite.#each_line
|
557
|
+
def test_each_line_classmethod
|
558
|
+
file1 = create_template_file
|
559
|
+
fpath = file1.path
|
560
|
+
s = 'tekito'
|
561
|
+
nlines = File.read(fpath).count("\n") # == 3
|
562
|
+
|
563
|
+
# Testing FileOverwrite.each_line
|
564
|
+
f1 = nil
|
565
|
+
f1 = FO.each_line(fpath){|i| s}
|
566
|
+
assert_equal fpath, f1.path
|
567
|
+
assert_equal s*nlines, f1.dump # Testing dump after completed.
|
568
|
+
refute_equal s*nlines, File.read(f1.path)
|
569
|
+
assert_output('', /File.+updated/){
|
570
|
+
f1.save
|
571
|
+
}
|
572
|
+
assert_equal s*nlines, File.read(f1.path)
|
573
|
+
end
|
574
|
+
|
440
575
|
def test_readlines
|
441
576
|
file1 = create_template_file
|
442
577
|
fpath = file1.path
|
@@ -462,6 +597,24 @@ $stderr.sync=true
|
|
462
597
|
assert_equal s.size, sizes[:new]
|
463
598
|
end
|
464
599
|
|
600
|
+
def test_touch
|
601
|
+
file1 = create_template_file
|
602
|
+
fpath = file1.path
|
603
|
+
f1 = FO.new(fpath, suffix: nil, verbose: true, touch: true)
|
604
|
+
|
605
|
+
assert_equal fpath, f1.path
|
606
|
+
assert_equal @orig_mtime.to_s, File.mtime(f1.path).to_s
|
607
|
+
f1.path.replace('naiyo')
|
608
|
+
assert_equal fpath, f1.path
|
609
|
+
Time.stub :now, Time.now do
|
610
|
+
assert_output('', /^(\s*warning:?\s*)?No change\b.+timestamp is updated/i){ f1.read!{|i| i} }
|
611
|
+
assert_equal fpath, f1.path
|
612
|
+
|
613
|
+
refute_equal @orig_mtime.to_s, File.mtime(f1.path).to_s # b/c touched
|
614
|
+
assert_equal Time.now.to_s, File.mtime(f1.path).to_s # nb., Time#round would add a second.
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
465
618
|
end # class TestUnitFileOverwrite < MiniTest::Test
|
466
619
|
|
467
620
|
#end # if $0 == __FILE__
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: file_overwrite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0
|
4
|
+
version: '1.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masa Sakano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: This class provides a Ruby-oriented scheme to safely overwrite an existing
|
14
14
|
file, leaving a backup file unless specified otherwise. It writes a temporary file
|
@@ -27,7 +27,7 @@ files:
|
|
27
27
|
- README.en.rdoc
|
28
28
|
- Rakefile
|
29
29
|
- file_overwrite.gemspec
|
30
|
-
- lib/file_overwrite
|
30
|
+
- lib/file_overwrite.rb
|
31
31
|
- lib/file_overwrite/file_overwrite_error.rb
|
32
32
|
- test/test_file_overwrite.rb
|
33
33
|
homepage: https://www.wisebabel.com
|