Class: FileOverwrite
- Inherits:
-
Object
- Object
- FileOverwrite
- Includes:
- FileUtils
- Defined in:
- lib/file_overwrite.rb
Overview
Controller class to backup a file and overwrite it
Ruby iterators and chaining-methods are fully exploited to edit the file.
Examples
f1 = FileOverwrite.new('a.txt', noop: true, verbose: true)
# Treat the content as String
f1.sub(/abc/, 'xyz').gsub(/(.)ef/){|i| $1}.run!
f1.sizes # => { :old => 40, :new => 50 }
f1.backup # => 'a.txt.20180915.bak'
# However, the file has not been created
# and the original file has not been modified, either,
# due to the noop option
f2 = FileOverwrite.new('a.txt', suffix: '~')
f2.backup # => 'a.txt~'
f2.completed? # => false
# Treat the content as String inside the block
f2.read{ |str| "\n" + i + "\n" }.gsub(/a\nb/m, '').run!
f2.completed? # => true
FileOverwrite.new('a.txt', suffix: '~').sub(/a/, '').run!
# => RuntimeError, because the backup file 'a.txt~' exists.
FileOverwrite.new('a.txt', suffix: '~').sub(/a/, '').run!(clobber: true)
# => The backup file is overwritten.
f3 = FileOverwrite.new('a.txt', backup: '/tmp/b.txt')
# Backup file can be explicitly specified.
f3.backup # => '/tmp/b.txt'
f3.backup = 'original.txt'
f3.backup # => 'original.txt'
# Treat the file as IO inside the block
f3.open{ |ior, iow| i + "XYZ" }
f3.reset # The modification is discarded
f3.reset? # => true
f3.open{ |ior, iow| i + "XYZ"; raise FileOverwriteError, 'I stop.' }
# To discard the modification inside the block
f3.reset? # => true
f3.open{ |ior, iow| "\n" + i + "\n" }
f3.run!(noop: true, verbose: true) # Dryrun
f3.completed? # => true
f3.backup = 'change.d' # => FrozenError (the state can not be modified after run!(), including dryrun)
f4 = FileOverwrite.new('a.txt', suffix: nil)
f4.backup # => nil (No backup file is created.)
f4.readlines{|ary| ary+["last\n"]}.each{|i| 'XX'+i}.run!
IO.readlines('a.txt')[-1] # => "XXlast\n"
f5 = FileOverwrite.new('a.txt', suffix: '.bak')
f5.backup # => 'a.txt.bak'
f5.read{|i| i}.run!
FileUtils.identical? 'a.txt', 'a.txt.bak' # => true
File.mtime('a.txt') == File.mtime('a.txt.bak') # => true
# To forcibly update the Timestamp, give touch option as true
# either in new() or run!(), ie., run!(touch: true)
Instance Attribute Summary collapse
-
#backup(suffix = nil, backupfile: nil) ⇒ String, NilClass
Gets a path of the filename for backup.
- #ext_enc ⇒ Encoding
-
#ext_enc_new ⇒ Object
Encoding of the content of the output file.
-
#ext_enc_old ⇒ Object
Encoding of the content of the input file.
- #int_enc ⇒ Encoding
- #last_match ⇒ MatchData
-
#sizes ⇒ Object
readonly
Hash of the file sizes of before (:old) and after (:new).
- #verbose ⇒ Boolean
Class Method Summary collapse
-
.each_line(fname, *rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#each_line.
-
.each_line!(fname, *rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#each_line!.
-
.modify!(*rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#modify!.
-
.read(*rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#read.
-
.read!(*rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#read!.
-
.readlines(fname, *rest, **kwd) { ... } ⇒ FileOverwrite
Shorthand of #initialize.#readlines, taking parameters for both.
-
.readlines!(*rest, **kwd) { ... } ⇒ FileOverwrite
Shorthand of FileOverwrite.readlines.#run!.
Instance Method Summary collapse
-
#chainable? ⇒ Boolean, NilClass
Returns true if the instance is chainable.
-
#completed? ⇒ Boolean
Returns true if the process has been completed.
-
#dump ⇒ String
Returns the (current) content as String to supercede the input file.
-
#each_line(*rest, **kwd) {|str| ... } ⇒ self
Takes a block in which each line of the file (or current content) is passed.
- #each_line!(*rest, **kwd) { ... } ⇒ self
-
#empty? ⇒ String
True if the (current) content to supercede the input file is empty.
-
#encode(*rest, **kwd) ⇒ String
Implement String#encode.
-
#end_with?(*rest) ⇒ String
True if the (current) content to supercede the input file end with the specified.
-
#force_encoding(enc) ⇒ Encoding
Implement String#force_encoding.
-
#fresh? ⇒ Boolean
(also: #reset?)
Returns true if the process has not yet started.
-
#gsub(*rest, max: 0, **kwd) { ... } ⇒ self
Similar to String#gsub.
- #gsub!(*rest, **kwd) { ... } ⇒ self
-
#initialize(fname, backup: nil, suffix: true, noop: false, verbose: $VERBOSE, clobber: false, touch: false, last_match: false) ⇒ FileOverwrite
constructor
A new instance of FileOverwrite.
-
#modify(**kwd) {|ioin, @iotmp| ... } ⇒ self
(also: #open)
Modify the content in the block (though not committed, yet).
- #modify!(**kwd) {|ioin, @iotmp| ... } ⇒ self
- #open! {|ioin, @iotmp| ... } ⇒ self
-
#path ⇒ String
Returns the (duplicate of the) filename to be (or to have been) updated.
-
#read(**kwd) {|str| ... } ⇒ self
Handler to process the entire string of the file (or current content).
- #read!(**kwd) { ... } ⇒ self
-
#readlines(*rest, **kwd) {|str| ... } ⇒ self
Takes a block in which the entire String of the file is passed.
-
#ready? ⇒ Boolean
Returns true if the instance is ready to run (to execute overwriting the file).
-
#replace_with(str) ⇒ self
Replaces the file content with the given argument like String#replace.
-
#replace_with!(str, **kwd) { ... } ⇒ self
Alias to self.#replace_with.#run!.
-
#reset ⇒ NilClass
Reset all the modification which is to be applied.
-
#run!(backup: @backup, suffix: @suffix, noop: @noop, verbose: @verbose, clobber: @clobber, touch: @touch, setsize: true, **kwd) ⇒ NilClass, self
(also: #run, #save, #save!)
Actually performs the file modification.
-
#state ⇒ Class, ...
Returns the current state.
-
#sub(*rest, max: 1, **kwd) { ... } ⇒ self
Similar to String#sub.
- #sub!(*rest, **kwd) { ... } ⇒ self
-
#temporary_filename ⇒ String, NilClass
Returns the temporary filename (or nil), maybe for debugging.
-
#tr(*rest, **kwd) ⇒ self
Similar to String#tr.
- #tr!(*rest, **kwd) ⇒ self
-
#tr_s(*rest, **kwd) ⇒ self
Similar to String#tr_s.
- #tr_s!(*rest, **kwd) ⇒ self
-
#valid_encoding? ⇒ Boolean, NilClass
String#valid_encoding?().
Constructor Details
#initialize(fname, backup: nil, suffix: true, noop: false, verbose: $VERBOSE, clobber: false, touch: false, last_match: false) ⇒ FileOverwrite
Returns a new instance of FileOverwrite.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/file_overwrite.rb', line 127 def initialize(fname, backup: nil, suffix: true, noop: false, verbose: $VERBOSE, clobber: false, touch: false, last_match: false) @fname = fname @backup = backup @suffix = (backup ? true : suffix) @noop = noop @verbose = $DEBUG || verbose @clobber = clobber @touch = touch @last_match = last_match @ext_enc_old = nil @ext_enc_new = nil @int_enc = nil @outstr = nil # String to write. This is nil if the temporary file was already created with modify(). @outary = nil # or Array to write @iotmp = nil # Temporary file IO to replace the original @is_edit_finished = false # true if the file modification is finished. @is_completed = false # true after all the process has been completed. @sizes = nil end |
Instance Attribute Details
#backup(suffix = nil, backupfile: nil) ⇒ String, NilClass
Gets a path of the filename for backup
If suffix is given, the default suffix and backup filename are ignored, and the (backup) filename with the given suffix is returned (so you can tell what the backup filename would be if the suffix was set).
162 163 164 165 166 167 168 |
# File 'lib/file_overwrite.rb', line 162 def backup(suffix=nil, backupfile: nil) return backup_from_suffix(suffix) if suffix # non-nil suffix explicitly given return backupfile if backupfile return @backup if @backup return backup_from_suffix(@suffix) if @suffix nil end |
#ext_enc ⇒ Encoding
96 |
# File 'lib/file_overwrite.rb', line 96 attr_accessor :ext_enc_old |
#ext_enc_new ⇒ Object
Encoding of the content of the output file. Default is nil (unspecified).
101 102 103 |
# File 'lib/file_overwrite.rb', line 101 def ext_enc_new @ext_enc_new end |
#ext_enc_old ⇒ Object
Encoding of the content of the input file. Default is nil (unspecified).
96 97 98 |
# File 'lib/file_overwrite.rb', line 96 def ext_enc_old @ext_enc_old end |
#int_enc ⇒ Encoding
106 107 108 |
# File 'lib/file_overwrite.rb', line 106 def int_enc @int_enc end |
#last_match ⇒ MatchData
117 118 119 |
# File 'lib/file_overwrite.rb', line 117 def last_match @last_match end |
#sizes ⇒ Object (readonly)
86 87 88 |
# File 'lib/file_overwrite.rb', line 86 def sizes @sizes end |
#verbose ⇒ Boolean
91 92 93 |
# File 'lib/file_overwrite.rb', line 91 def verbose @verbose end |
Class Method Details
.each_line(fname, *rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#each_line
931 932 933 |
# File 'lib/file_overwrite.rb', line 931 def self.each_line(fname, *rest, **kwd, &bloc) new(fname, **kwd).send(__method__, *rest, **kwd, &bloc) end |
.each_line!(fname, *rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#each_line!
939 940 941 |
# File 'lib/file_overwrite.rb', line 939 def self.each_line!(fname, *rest, **kwd, &bloc) new(fname, **kwd).send(__method__, *rest, **kwd, &bloc) end |
.modify!(*rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#modify!
881 882 883 |
# File 'lib/file_overwrite.rb', line 881 def self.modify!(*rest, **kwd, &bloc) new(*rest, **kwd).modify!(**kwd, &bloc) end |
.read(*rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#read
915 916 917 |
# File 'lib/file_overwrite.rb', line 915 def self.read(*rest, **kwd, &bloc) new(*rest, **kwd).send(__method__, **kwd, &bloc) end |
.read!(*rest, **kwd, &bloc) ⇒ Object
Class method for #initialize.#read!
923 924 925 |
# File 'lib/file_overwrite.rb', line 923 def self.read!(*rest, **kwd, &bloc) new(*rest, **kwd).send(__method__, **kwd, &bloc) end |
.readlines(fname, *rest, **kwd) { ... } ⇒ FileOverwrite
Shorthand of #initialize.#readlines, taking parameters for both
895 896 897 |
# File 'lib/file_overwrite.rb', line 895 def self.readlines(fname, *rest, **kwd, &bloc) new(fname, *rest, **kwd).send(__method__, *rest, **kwd, &bloc) end |
.readlines!(*rest, **kwd) { ... } ⇒ FileOverwrite
906 907 908 |
# File 'lib/file_overwrite.rb', line 906 def self.readlines!(*rest, **kwd, &bloc) readlines(*rest, **kwd, &bloc).run!(**kwd) end |
Instance Method Details
#chainable? ⇒ Boolean, NilClass
177 178 179 180 181 |
# File 'lib/file_overwrite.rb', line 177 def chainable? return nil if fresh? return false if completed? return !@is_edit_finished # ie., (@outary || @outstr) b/c one of the three must be non-false after the 2 clauses above. end |
#completed? ⇒ Boolean
Returns true if the process has been completed.
185 186 187 |
# File 'lib/file_overwrite.rb', line 185 def completed? @is_completed end |
#dump ⇒ String
Returns the (current) content as String to supercede the input file
If the file has been already overwritten, this returns the content of the new one. Note it would be impossible to return the old one anyway, if no backup is left, as the user chooses.
Even if the returned string is destructively modified, it has no effect on the final output to the overwritten file.
200 201 202 203 204 205 |
# File 'lib/file_overwrite.rb', line 200 def dump return @outstr.dup if @outstr return join_outary() if @outary return File.read(@iotmp.path) if @is_edit_finished && !completed? File.read(@fname) end |
#each_line(*rest, **kwd) {|str| ... } ⇒ self
Takes a block in which each line of the file (or current content) is passed.
In the block each line as String is given as a block argument. Each iterator must return a String (or an object having to_s method), which replaces the input String to be output to the overwritten file later.
This method can be chained, as String-type processing.
574 575 576 577 578 579 |
# File 'lib/file_overwrite.rb', line 574 def each_line(*rest, **kwd, &bloc) raise ArgumentError, 'Block must be given.' if !block_given? read(**kwd){ |outstr| outstr.each_line(*rest).map{|i| yield(i).to_s}.join('') } end |
#each_line!(*rest, **kwd) { ... } ⇒ self
587 588 589 |
# File 'lib/file_overwrite.rb', line 587 def each_line!(*rest, **kwd, &bloc) send(__method__.to_s.chop, *rest, **kwd, &bloc).run!(**kwd) end |
#empty? ⇒ String
True if the (current) content to supercede the input file is empty.
211 212 213 |
# File 'lib/file_overwrite.rb', line 211 def empty? dump.empty? end |
#encode(*rest, **kwd) ⇒ String
Implement String#encode
If it is in the middle of the process, the internal encoding for String (or Array) changes. Note if the current proces is in the IO-mode, everything has been already written in a temporary file, and hence there is no effect.
Once this is called, @int_enc is overwritten (or set), and it remains so even after reset() is called.
It is advisable to call #force_encoding or #ext_enc_old= before this is called to set the encoding of the input file.
233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/file_overwrite.rb', line 233 def encode(*rest, **kwd) enc = (rest[0] || Encoding.default_internal) @int_enc = enc # raises an Exception if called after "completed" return enc if @is_edit_finished || fresh? return @outstr.encode(*rest, **kwd) if @outstr if @outary @outary.map!{|i| i.encode(*rest, **kwd)} return enc end raise 'Should not happen. Contact the code developer.' end |
#end_with?(*rest) ⇒ String
True if the (current) content to supercede the input file end with the specified.
Wrapper of String#end_with?
251 252 253 |
# File 'lib/file_overwrite.rb', line 251 def end_with?(*rest) dump.end_with?(*rest) end |
#force_encoding(enc) ⇒ Encoding
Implement String#force_encoding
Once this is called, @ext_enc_old is overwritten (or set), and it remains so even after reset() is called.
263 264 265 266 267 268 269 270 271 272 |
# File 'lib/file_overwrite.rb', line 263 def force_encoding(enc) @ext_enc_old = enc # raises an Exception if called after "completed" return enc if @is_edit_finished || fresh? return @outstr.force_encoding(enc) if @outstr if @outary @outary.map!{|i| i.force_encoding(enc)} return enc end raise 'Should not happen. Contact the code developer.' end |
#fresh? ⇒ Boolean Also known as: reset?
Returns true if the process has not yet started.
276 277 278 |
# File 'lib/file_overwrite.rb', line 276 def fresh? !state end |
#gsub(*rest, max: 0, **kwd) { ... } ⇒ self
Algorithm See #sub for the basic algorithm. This method emulates String#gsub as much as possible (duck-typing). In String#gsub, the variable $~ after the method has the last matched characters as the matched string and the original string before the last matched characters as pre_match. For example,
'abc'.gsub(/./){$1.upcase}
returns
'ABC'
and leaves
$& == 'c'
Regexp.pre_match == 'ab'
It is the same in this method.
Disclaimer When a block is not given but arguments only (and not expecting Enumerator to return), this method simply calls String#gsub . However, when only 1 argument and a block is given, this method must iterate on its own, which is implemented. I am not 100% confident if this method works in the completely same way as String#gsub in every single situation, given the regular expression has so many possibilities; so far I have not found any cases where this method breaks. This method is more inefficient and slower than the original String#gsub as the iteration is implemented in pure Ruby.
Similar to String#gsub
This method can be chained. This method never returns an Enumerator.
Being different from the standard Srrint#gsub, this method accepts the optional parameter max, which specifies the maximum number of times of the matches and is valid ONLY WHEN a block is given.
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 |
# File 'lib/file_overwrite.rb', line 771 def gsub(*rest, max: 0, **kwd, &bloc) return sub(*rest, max: 1, **kwd, &bloc) if 1 == max # Note: Error message would be labelled as 'sub' return self if sub_gsub_args_only(*rest, max: max, **kwd) if !block_given? raise ArgumentError, full_method_name+' does not support the format to return an enumerator.' end max = 5.0/0 if max.to_i <= 0 regbase_str = rest[0].to_s regex = Regexp.new( sprintf('(%s)', regbase_str) ) # to guarantee the entire string is picked up by String#scan scans = @outstr.scan(regex) return self if scans.empty? # no matches scans.map!{|i| [i].flatten} # Originally, it can be a double array. prematch = '' ret = '' imatch = 0 # Number of matches scans.each do |ea_sc| str_matched = ea_sc[0] imatch += 1 pre_size = prematch.size pos_end_p1 = @outstr.index(str_matched, pre_size) # End+1 str_between = @outstr[pre_size...pos_end_p1] prematch << str_between ret << str_between regex = Regexp.new( sprintf('(?<=\A%s)%s', Regexp.quote(prematch), regbase_str) ) #regex = rest[0] if prematch.empty? # The first run @last_match = regex.match(@outstr) prematch << str_matched # Sets $~ (Regexp.last_match) in the given block. # @see https://stackoverflow.com/questions/52359278/how-to-pass-regexp-last-match-to-a-block-in-ruby/52385870#52385870 bloc.binding.tap do |b| b.local_variable_set(:_, $~) b.eval("$~=_") end # The first (and only) argument for the block is $& . # Returning nil, Integer etc is accepted in the block of sub/gsub ret << yield(@last_match[0]).to_s break if imatch >= max end ret << Regexp.last_match.post_match # Guaranteed to be non-nil. @outstr = ret return self end |
#gsub!(*rest, **kwd) { ... } ⇒ self
827 828 829 |
# File 'lib/file_overwrite.rb', line 827 def gsub!(*rest, **kwd, &bloc) gsub(*rest, &bloc).run!(**kwd) end |
#modify(**kwd) {|ioin, @iotmp| ... } ⇒ self Also known as: open
Modify the content in the block (though not committed, yet)
Two parameters are passed to the block: io_r and io_w. The former is the read-descriptor to read from the original file and the latter is the write-descriptor to write whatever to the temporary file, which is later moved back to the original file when you #run!.
Note the IO pointer for the input file is reset after this method. Hence, chaining this method makes no effect (warning is issued), but only the last one is taken into account.
If you want to halt, undo and reset your modification process in the middle, issue
raise FileOverwriteError [Your_Message]
and it will be rescued. Your_Message is printed to STDERR if verbose was specified in #initialize or $DEBUG
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'lib/file_overwrite.rb', line 483 def modify(**kwd) raise ArgumentError, 'Block must be given.' if !block_given? normalize_status(:@is_edit_finished) kwd_open = {} kwd_open[:external_encoding] = @ext_enc_old if @ext_enc_old kwd_open[:internal_encoding] = @int_enc if @int_enc kwd_open[:external_encoding] = (kwd[:ext_enc] || kwd_open[:external_encoding]) kwd_open[:internal_encoding] = (kwd[:int_enc] || kwd_open[:internal_encoding]) [:mode, :flags, :encoding, :textmode, :binmode, :autoclose].each do |es| # Method list from https://ruby-doc.org/core-2.5.1/IO.html#method-c-new kwd_open[es] = kwd[es] if kwd.key?(es) end begin File.open(@fname, **kwd_open) { |ioin| @iotmp = tempfile_io yield(ioin, @iotmp) } rescue FileOverwriteError => err warn err. if @verbose reset end self end |
#modify!(**kwd) {|ioin, @iotmp| ... } ⇒ self
518 519 520 |
# File 'lib/file_overwrite.rb', line 518 def modify!(**kwd, &bloc) modify(&bloc).run!(**kwd) end |
#open! {|ioin, @iotmp| ... } ⇒ self
521 522 523 |
# File 'lib/file_overwrite.rb', line 521 def modify!(**kwd, &bloc) modify(&bloc).run!(**kwd) end |
#path ⇒ String
Returns the (duplicate of the) filename to be (or to have been) updated.
To destructively modify this value would affect nothing in the parent object.
287 288 289 |
# File 'lib/file_overwrite.rb', line 287 def path @fname.dup end |
#read(**kwd) {|str| ... } ⇒ self
Handler to process the entire string of the file (or current content)
If block is not given, just sets the processing-state as String.
Else, IO.read(infile) is given to the block. No other options, such as length, as in IO.read are accepted. Then, the returned value is held as a String, while self is returned; hence this method can be chained. If the block returns nil (or Boolean), FileOverwriteError is raised. Make sure for the block to return a String.
Note this method does not take arguments as in IO.read .
625 626 627 628 629 630 631 632 633 634 635 636 |
# File 'lib/file_overwrite.rb', line 625 def read(**kwd, &bloc) if :first == normalize_status(:@outstr) adjust_input_encoding(**kwd){ |f| # @fname @outstr = File.read f } end @outstr = yield(@outstr) if block_given? raise FileOverwriteError, 'ERROR: The returned value from the block in read() has to be String.' if !defined?(@outstr.gsub) warn "WARNING: Empty string returned from a block in #{__method__}" if !@verbose.nil? && @outstr.empty? self end |
#read!(**kwd) { ... } ⇒ self
644 645 646 |
# File 'lib/file_overwrite.rb', line 644 def read!(**kwd, &bloc) read(**kwd, &bloc).run!(**kwd) end |
#readlines(*rest, **kwd) {|str| ... } ⇒ self
Takes a block in which the entire String of the file is passed.
IO.readlines(infile) is given to the block, where Encode may be taken into account if specified already.
The block must return an Array, the number of the elements of which can be allowed to differ from the input. The elements of the Array will be joined to output to the overwritten file in the end.
542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/file_overwrite.rb', line 542 def readlines(*rest, **kwd, &bloc) raise ArgumentError, 'Block must be given.' if !block_given? if :first == normalize_status(:@outary) adjust_input_encoding(**kwd){ |f| # @fname @outary = IO.readlines(f, *rest) } end @outary = yield(@outary) self end |
#ready? ⇒ Boolean
Returns true if the instance is ready to run (to execute overwriting the file).
293 294 295 |
# File 'lib/file_overwrite.rb', line 293 def ready? !fresh? && !completed? end |
#replace_with(str) ⇒ self
Replaces the file content with the given argument like String#replace
This method can be chained.
655 656 657 658 659 |
# File 'lib/file_overwrite.rb', line 655 def replace_with(str) read @outstr = str.to_s self end |
#replace_with!(str, **kwd) { ... } ⇒ self
Alias to self.#replace_with.#run!
666 667 668 |
# File 'lib/file_overwrite.rb', line 666 def replace_with!(str, **kwd) replace_with(str).run!(**kwd) end |
#reset ⇒ NilClass
Reset all the modification which is to be applied
302 303 304 305 306 307 308 309 |
# File 'lib/file_overwrite.rb', line 302 def reset @outstr = nil @outary = nil @is_edit_finished = nil close_iotmp # @iotmp=nil; immediate deletion of the temporary file warn "The modification process is reset." if $DEBUG nil end |
#run!(backup: @backup, suffix: @suffix, noop: @noop, verbose: @verbose, clobber: @clobber, touch: @touch, setsize: true, **kwd) ⇒ NilClass, self Also known as: run, save, save!
Actually performs the file modification
If setsize option is true (Default) or verbose, method #sizes is activated after this method, which returns a hash of file sizes in bytes before and after, so you can chain it. Note this method returns nil if the input file is not opened at all.
The folloing optional parameters are taken into account. Any other options are ignored.
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/file_overwrite.rb', line 406 def run!(backup: @backup, suffix: @suffix, noop: @noop, verbose: @verbose, clobber: @clobber, touch: @touch, setsize: true, **kwd) raise FileOverwriteError, 'The process has been already completed.' if completed? bkupname = get_bkupname(backup, suffix, noop, verbose, clobber) sizes = write_new(verbose, setsize) return nil if !sizes return self if run_identical?(noop, verbose, touch) if bkupname msg4bkup = ", Backup: " + bkupname if verbose else io2del = tempfile_io io2delname = io2del.path end fname_to = (bkupname || io2delname) mv( @fname, fname_to, noop: noop, verbose: $DEBUG) # defined in FileUtils begin mv(@iotmp.path, @fname, noop: noop, verbose: $DEBUG) # defined in FileUtils rescue msg = sprintf("Process halted! File system error in renaming the temporary file %s back to the original %s", @iotmp.path, @fname) warn msg raise end # @iotmp.close(true) # to immediate delete the temporary file # If commented out, GC looks after it. File.unlink io2delname if io2delname && !noop # if noop, GC will delete it. if verbose msg = sprintf("%sFile %s updated (Size: %d => %d bytes%s)\n", prefix(noop), @fname, sizes[:old], sizes[:new], msg4bkup) msg end @is_completed = true self.freeze return self end |
#state ⇒ Class, ...
Returns the current state
nil if no modification has been attempted. IO if the modification has been made and it is wating to run. String or Array (or their equivalent), depending how it has been chained so far. true if the process has been completed.
330 331 332 333 334 335 336 |
# File 'lib/file_overwrite.rb', line 330 def state return true if completed? return IO if @is_edit_finished return @outstr.class if @outstr return @outary.class if @outary nil end |
#sub(*rest, max: 1, **kwd) { ... } ⇒ self
Algorithm To realise the local-scope variables like $~, $1, and Regexp.last_match to be usable inside the block as in String#sub, it overwrites them when a block is given (See the linked article for the phylosophy of how to do it). Once a block is read, those variables remain as updated values even after the block in the caller’s scope, in the same way as String#sub. However, when a block is not given, those variables are NOT updated, which is different from String#sub. You can retrieve the MatchData by this method via #last_match after #sub is called, if need be.
Similar to String#sub
This method can be chained. This method never returns an Enumerator.
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 |
# File 'lib/file_overwrite.rb', line 692 def sub(*rest, max: 1, **kwd, &bloc) return self if sub_gsub_args_only(*rest, max: max, **kwd) if !block_given? raise ArgumentError, full_method_name+' does not support the format to return an enumerator.' end if max.to_i != 1 return gsub(*rest, max: max, **kwd, &bloc) end @last_match = rest[0].match(@outstr) return self if !@last_match # Sets $~ (Regexp.last_match) in the given block. # @see https://stackoverflow.com/questions/52359278/how-to-pass-regexp-last-match-to-a-block-in-ruby/52385870#52385870 bloc.binding.tap do |b| b.local_variable_set(:_, $~) b.eval("$~=_") end # The first (and only) argument for the block is $& . # Returning nil, Integer etc is accepted in the block of sub/gsub @outstr = @last_match.pre_match + yield(@last_match[0]).to_s + @last_match.post_match return self end |
#sub!(*rest, **kwd) { ... } ⇒ self
726 727 728 |
# File 'lib/file_overwrite.rb', line 726 def sub!(*rest, **kwd, &bloc) sub(*rest, &bloc).run!(**kwd) end |
#temporary_filename ⇒ String, NilClass
Returns the temporary filename (or nil), maybe for debugging
It may not be open?
317 318 319 |
# File 'lib/file_overwrite.rb', line 317 def temporary_filename @iotmp ? @iotmp.path : nil end |
#tr(*rest, **kwd) ⇒ self
Similar to String#tr
This method can be chained.
839 840 841 842 843 |
# File 'lib/file_overwrite.rb', line 839 def tr(*rest, **kwd) read(**kwd){ |outstr| outstr.tr!(*rest) || outstr } end |
#tr!(*rest, **kwd) ⇒ self
848 849 850 |
# File 'lib/file_overwrite.rb', line 848 def tr!(*rest, **kwd) tr(*rest, **kwd).run!(**kwd) end |
#tr_s(*rest, **kwd) ⇒ self
Similar to String#tr_s
This method can be chained.
859 860 861 862 863 |
# File 'lib/file_overwrite.rb', line 859 def tr_s(*rest, **kwd) read(**kwd){ |outstr| outstr.tr_s!(*rest) || outstr } end |
#tr_s!(*rest, **kwd) ⇒ self
868 869 870 |
# File 'lib/file_overwrite.rb', line 868 def tr_s!(*rest, **kwd) tr_s(*rest, **kwd).run!(**kwd) end |
#valid_encoding? ⇒ Boolean, NilClass
returns nil if the process has been already completed.
String#valid_encoding?()
343 344 345 346 |
# File 'lib/file_overwrite.rb', line 343 def valid_encoding?() return nil if completed? dump.valid_encoding? end |