Class: FileOverwrite

Inherits:
Object
  • Object
show all
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)

Author:

  • Masa Sakano

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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.

Parameters:

  • fname (String)

    Input filename

  • backup: (String, NilClass) (defaults to: nil)

    File name to which the original file is backed up. If non-Nil, suffix is ignored.

  • suffix: (String, TrueClass, FalseClass, NilClass) (defaults to: true)

    Suffix of the backup file. True for Def, or false if no backup.

  • noop: (Boolean) (defaults to: false)

    no-operationor dryrun

  • verbose: (Boolean, NilClass) (defaults to: $VERBOSE)

    the same as $VERBOSE or the command-line option -W, i.e., the verbosity is (true > false > nil). Forced to be true if $DEBUG

  • clobber: (Boolean) (defaults to: false)

    raise Exception if false(Def) and fname exists and suffix is non-null.

  • touch: (Boolean) (defaults to: false)

    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)

  • last_match: (MatchData, NilClass, FalseClass) (defaults to: false)

    To pass Regexp.last_match in Caller’s scope.



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

Parameters:

  • suffix (String, TrueClass, FalseClass, NilClass) (defaults to: nil)

    Suffix of the backup file. True for Def, or false if no backup.

  • backupfile: (String, NilClass) (defaults to: nil)

    Explicilty specify the backup filename, when suffix is nil. (For internal use)

Returns:

  • (String, NilClass)


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_encEncoding

Returns:

  • (Encoding)


96
# File 'lib/file_overwrite.rb', line 96

attr_accessor :ext_enc_old

#ext_enc_newObject

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_oldObject

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_encEncoding

Returns:

  • (Encoding)


106
107
108
# File 'lib/file_overwrite.rb', line 106

def int_enc
  @int_enc
end

#last_matchMatchData

Returns:

  • (MatchData)


117
118
119
# File 'lib/file_overwrite.rb', line 117

def last_match
  @last_match
end

#sizesObject (readonly)

Hash of the file sizes of before (:old) and after (:new).

This is set after #run! and if setsize option in #run! is given true (Default) or if verbose option is true (Def: false). Else, nil is returned.



86
87
88
# File 'lib/file_overwrite.rb', line 86

def sizes
  @sizes
end

#verboseBoolean

Returns:

  • (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!

See Also:



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

See Also:



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!

See Also:



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

Parameters:

Yields:

Returns:

See Also:



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

Shorthand of readlines.#run!

Parameters:

Yields:

Returns:

See Also:



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

Returns true if the instance is chainable.

In other words, whether a further process like #gsub can be run. This returns nil if #fresh? is true.

Returns:

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

Returns:

  • (Boolean)


185
186
187
# File 'lib/file_overwrite.rb', line 185

def completed?
  @is_completed
end

#dumpString

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.

Returns:

  • (String)


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.

Parameters:

  • *rest (Array)

    separator etc

  • **kwd (Hash)

    ext_enc, int_enc

Yield Parameters:

  • str (String)

Yield Returns:

  • (String)

    to be written back to the original file

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if a block is not given



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

Alias to self.#sub.#run!

Parameters:

  • *rest (Array<Regexp,String>)
  • **kwd (Hash)

    setsize: etc

Yields:

  • the same as String#sub!

Returns:

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

Returns:

  • (String)


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.

Parameters:

  • *rest (Array)
  • **kwd (Hash)

Returns:

  • (String)

See Also:



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?

Returns:

  • (String)


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.

Returns:

  • (Boolean)


276
277
278
# File 'lib/file_overwrite.rb', line 276

def fresh?
  !state
end

#gsub(*rest, max: 0, **kwd) { ... } ⇒ self

Note:

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.

Note:

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.

Parameters:

  • *rest (Array<Regexp,String>)
  • max: (Integer) (defaults to: 0)

    the number of the maximum matches. 0 means no limit (as in String#gsub). Valid only if a block is given.

  • **kwd (Hash)

    ext_enc, int_enc

Yields:

  • the same as String#gsub

Returns:

  • (self)

See Also:



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

Alias to self.#gsub.#run!

Yields:

  • the same as String#gsub!

Returns:

  • (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

Examples:

fo.modify do |io_r, io_w|
  io_w.print( "\n" + io_r.read + "\n" )
end

Parameters:

  • **kwd (Hash)

    keyword parameters passed to File.open. Notably, ext_enc and int_enc .

Yield Parameters:

  • ioin (IO)

    Read IO instance from the original file

  • @iotmp (IO)

    Write IO instance to the temporary file

Yield Returns:

  • (Object)

    ignored

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if a block is not given



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.message if @verbose
    reset
  end
  self
end

#modify!(**kwd) {|ioin, @iotmp| ... } ⇒ self

Alias to self.#modify.#run!

Yield Parameters:

  • ioin (IO)

    Read IO instance from the original file

  • @iotmp (IO)

    Write IO instance to the temporary file

Yield Returns:

  • (Object)

    ignored

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if a block is not given



518
519
520
# File 'lib/file_overwrite.rb', line 518

def modify!(**kwd, &bloc)
  modify(&bloc).run!(**kwd)
end

#open! {|ioin, @iotmp| ... } ⇒ self

Alias to self.#modify.#run!

Yield Parameters:

  • ioin (IO)

    Read IO instance from the original file

  • @iotmp (IO)

    Write IO instance to the temporary file

Yield Returns:

  • (Object)

    ignored

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if a block is not given



521
522
523
# File 'lib/file_overwrite.rb', line 521

def modify!(**kwd, &bloc)
  modify(&bloc).run!(**kwd)
end

#pathString

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.

Returns:

  • (String)


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 .

Parameters:

  • **kwd (Hash)

    ext_enc, int_enc

Yield Parameters:

  • str (String)

Yield Returns:

  • (String)

    to be written back to the original file

Returns:

  • (self)

Raises:



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

Alias to self.#read.#run!

Parameters:

  • **kwd (Hash)

    ext_enc, int_enc

Yields:

  • Should return string

Returns:

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

Parameters:

  • *rest (Array)

    separator etc

  • **kwd (Hash)

    ext_enc, int_enc

Yield Parameters:

  • str (String)

Yield Returns:

  • (String)

    to be written back to the original file

Returns:

  • (self)

Raises:

  • (ArgumentError)


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

Returns:

  • (Boolean)


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.

Parameters:

  • str (String)

    the content will be replaced with this

Returns:

  • (self)


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!

Yields:

  • the same as String#gsub!

Returns:

  • (self)


666
667
668
# File 'lib/file_overwrite.rb', line 666

def replace_with!(str, **kwd)
  replace_with(str).run!(**kwd)
end

#resetNilClass

Reset all the modification which is to be applied

Returns:

  • (NilClass)


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.

Examples:

With setsize option

fo.run!(setsize: true).sizes
  # => { :old => 40, :new => 50 }

One case where this returns nil

fo.new('test.f').run!  # => nil

Parameters:

  • backup: (String, NilClass) (defaults to: @backup)

    File name to which the original file is backed up. If non-Nil, suffix is ignored.

  • suffix: (String, TrueClass, FalseClass, NilClass) (defaults to: @suffix)

    Suffix of the backup file. True for Def, or false if no backup.

  • noop: (Boolean) (defaults to: @noop)
  • verbose: (Boolean) (defaults to: @verbose)
  • clobber: (Boolean) (defaults to: @clobber)

    raise Exception if false(Def) and fname exists and suffix is non-null.

  • touch: (Boolean) (defaults to: @touch)

    Even if true (non-Def), when the file content does not change, the timestamp is updated, unless aboslutely no action has been taken for the file.

  • setsize: (Boolean) (defaults to: true)

Returns:

  • (NilClass, self)

    If the input file is not touched, nil is returned, else self.

Raises:



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)
    fu_output_message msg
  end

  @is_completed = true
  self.freeze

  return self
end

#stateClass, ...

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.

Returns:

  • (Class, TrueClass, NilClass)


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

Note:

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.

Parameters:

  • *rest (Array<Regexp,String>)
  • max: (Integer) (defaults to: 1)

    the number of the maximum matches. If it is not 1, #gsub is called, instead. See #gsub for detail.

  • **kwd (Hash)

    ext_enc, int_enc

Yields:

  • the same as String#sub

Returns:

  • (self)

See Also:



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

Alias to self.#sub.#run!

Parameters:

  • *rest (Array<Regexp,String>)
  • **kwd (Hash)

    setsize: etc

Yields:

  • the same as String#sub!

Returns:

  • (self)


726
727
728
# File 'lib/file_overwrite.rb', line 726

def sub!(*rest, **kwd, &bloc)
  sub(*rest, &bloc).run!(**kwd)
end

#temporary_filenameString, NilClass

Returns the temporary filename (or nil), maybe for debugging

It may not be open?

Returns:

  • (String, NilClass)

    Filename if exists, else nil



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.

Parameters:

  • *rest (Array)

    replacers etc

  • **kwd (Hash)

    ext_enc, int_enc

Returns:

  • (self)


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

Alias to self.#tr.#run!

Returns:

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

Parameters:

  • *rest (Array)

    replacers etc

  • **kwd (Hash)

    ext_enc, int_enc

Returns:

  • (self)


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

Alias to self.#tr.#run!

Returns:

  • (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

Note:

returns nil if the process has been already completed.

String#valid_encoding?()

Returns:

  • (Boolean, NilClass)


343
344
345
346
# File 'lib/file_overwrite.rb', line 343

def valid_encoding?()
  return nil if completed?
  dump.valid_encoding?
end