file_overwrite 0.1
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 +7 -0
- data/.gitignore +27 -0
- data/ChangeLog +5 -0
- data/Makefile +22 -0
- data/README.en.rdoc +237 -0
- data/Rakefile +9 -0
- data/file_overwrite.gemspec +45 -0
- data/lib/file_overwrite/file_overwrite.rb +1103 -0
- data/lib/file_overwrite/file_overwrite_error.rb +4 -0
- data/test/test_file_overwrite.rb +469 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e6552bc81a8cfaf8164a2f1913c03bdb6d72d6df4138fe912b7336e355f8d495
|
4
|
+
data.tar.gz: 1feabdf441fdb9f6ecb148257c1bb03ac38b653d6ef528f966367b21bedd412a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25f57ff0bd31eab7acba8265309a0a47fa230c758396cd1eb04cb398ab30713197c54cdfd80e6ed7d0a1578e9185ffbd3c4e13ec886afaadfc80990cf0570693
|
7
|
+
data.tar.gz: a9db5e53923388d9f0710034806d639d03a44a9a287d7d66f3308f14cc27017421f2f156f108fc94c8f1acdaecf576c8dbb050cb27dd3e4cb694a8906e1df163
|
data/.gitignore
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
*.[oa]
|
2
|
+
*.so
|
3
|
+
*.dylib
|
4
|
+
*~
|
5
|
+
*.nogem
|
6
|
+
*nogem.*
|
7
|
+
*.bak
|
8
|
+
*.org
|
9
|
+
*.orig
|
10
|
+
*.gem
|
11
|
+
*.pyc
|
12
|
+
*.yardoc
|
13
|
+
/doc/*
|
14
|
+
|
15
|
+
# Ignore bundler config.
|
16
|
+
/.bundle
|
17
|
+
|
18
|
+
# Ignore all logfiles and tempfiles.
|
19
|
+
/log/*
|
20
|
+
/tmp/*
|
21
|
+
!/log/.keep
|
22
|
+
!/tmp/.keep
|
23
|
+
|
24
|
+
/node_modules
|
25
|
+
/yarn-error.log
|
26
|
+
|
27
|
+
.byebug_history
|
data/ChangeLog
ADDED
data/Makefile
ADDED
data/README.en.rdoc
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
|
2
|
+
= FileOverwrite - Controller class to backup a file and overwrite it
|
3
|
+
|
4
|
+
== Summary
|
5
|
+
|
6
|
+
This class provides a Ruby-oriented scheme to safely overwrite an existing file, leaving a backup file unless specified otherwise. It writes a temporary file first, which is renamed to the original file in one action. It accepts a block like some IO class-methods (e.g., each_line) and chaining like String methods (e.g., sub and gsub).
|
7
|
+
|
8
|
+
This library realises a simple and secure handling to overwrite a file
|
9
|
+
on the spot like "-i" command-line option in Ruby, but inside a Ruby
|
10
|
+
script (rather than globally as in "-i" option) and with much more flexibility.
|
11
|
+
|
12
|
+
== Install
|
13
|
+
|
14
|
+
This script requires {Ruby}[http://www.ruby-lang.org] Version 2.0
|
15
|
+
or above.
|
16
|
+
|
17
|
+
Although it is preferable to set the RUBYLIB environment
|
18
|
+
variable to the library directory to this gem, which is
|
19
|
+
/THIS/GEM/LIBRARY/PATH/file_overwrite/lib
|
20
|
+
it is not essential. You can simply require the main library file,
|
21
|
+
file_overwrite.rb, from your Ruby script and it should work, except
|
22
|
+
some error messages can be less helpful in that case.
|
23
|
+
|
24
|
+
== Examples
|
25
|
+
|
26
|
+
=== Chaining example (with noop, meaning dryrun)
|
27
|
+
|
28
|
+
f1 = FileOverwrite.new('a.txt', noop: true, verbose: true)
|
29
|
+
# Treat the content as String
|
30
|
+
f1.sub(/abc/, 'xyz').gsub(/(.)ef/){|i| $1}.run!
|
31
|
+
f1.completed? # => true
|
32
|
+
f1.sizes # => { :old => 40, :new => 50 }
|
33
|
+
f1.backup # => 'a.txt.20180915.bak'
|
34
|
+
# However, the file has not been created
|
35
|
+
# and the original file has not been modified, either,
|
36
|
+
# due to the noop option
|
37
|
+
|
38
|
+
=== IO.read type manipulation (String-based block)
|
39
|
+
|
40
|
+
f2 = FileOverwrite.new('a.txt', suffix: '~')
|
41
|
+
f2.backup # => 'a.txt~'
|
42
|
+
f2.completed? # => false
|
43
|
+
# Treat the content as String inside the block
|
44
|
+
f2.read{ |str| "\n" + i + "\n" }.gsub(/a\nb/m, '').run!
|
45
|
+
f2.completed? # => true
|
46
|
+
FileOverwrite.new('a.txt', suffix: '~').sub(/a/, '').run!
|
47
|
+
# => RuntimeError, because the backup file 'a.txt~' exists.
|
48
|
+
FileOverwrite.new('a.txt', suffix: '~').sub(/a/, '').run!(clobber: true)
|
49
|
+
# => The backup file is overwritten.
|
50
|
+
|
51
|
+
Note the suffix for the backup file in default consists of the date
|
52
|
+
and time of the day up to 1 second precision. Therefore, if you stick
|
53
|
+
to the default, even if you run the same process to the same file with
|
54
|
+
some interval (longer than 1 second), the original file will not be overwritten,
|
55
|
+
although that also means there will be multiple backup files for
|
56
|
+
different versions of the file.
|
57
|
+
|
58
|
+
=== File.open type manipulation (IO-based block)
|
59
|
+
|
60
|
+
f3 = FileOverwrite.new('a.txt', backup: '/tmp/b.txt')
|
61
|
+
# Backup file can be explicitly specified.
|
62
|
+
f3.backup # => '/tmp/b.txt'
|
63
|
+
f3.backup = 'original.txt'
|
64
|
+
f3.backup # => 'original.txt'
|
65
|
+
# Treat the file as IO inside the block
|
66
|
+
f3.open{ |ior, iow| i + "XYZ" }
|
67
|
+
f3.reset # The modification is discarded
|
68
|
+
f3.reset? # => true
|
69
|
+
f3.open{ |ior, iow| i + "XYZ"; raise FileOverwriteError, 'I stop.' }
|
70
|
+
# To discard the modification inside the block
|
71
|
+
f3.reset? # => true
|
72
|
+
f3.open{ |ior, iow| "\n" + i + "\n" }
|
73
|
+
f3.run!(noop: true, verbose: true) # Dryrun
|
74
|
+
f3.completed? # => true
|
75
|
+
f3.backup = 'change.d' # => FrozenError (the state can not be modified after run!(), including dryrun)
|
76
|
+
|
77
|
+
=== IO.readlines type manipulation (Array-based block)
|
78
|
+
|
79
|
+
f4 = FileOverwrite.new('a.txt', suffix: nil)
|
80
|
+
f4.backup # => nil (No backup file is created.)
|
81
|
+
f4.readlines{|ary| ary+["last\n"]}.each{|i| 'XX'+i}.run!
|
82
|
+
# The content of the file is,
|
83
|
+
IO.readlines('a.txt')[-1] # => "XXlast\n"
|
84
|
+
|
85
|
+
=== If the file is examined but if the content is not updated?
|
86
|
+
|
87
|
+
f5 = FileOverwrite.new('a.txt', suffix: '.bak')
|
88
|
+
f5.backup # => 'a.txt.bak'
|
89
|
+
f5.read{|i| i}.run!
|
90
|
+
FileUtils.identical? 'a.txt', 'a.txt.bak' # => true
|
91
|
+
File.mtime('a.txt') == File.mtime('a.txt.bak') # => true
|
92
|
+
# To forcibly update the Timestamp, give touch option as true
|
93
|
+
# either in new() or run!(), ie., run!(touch: true)
|
94
|
+
|
95
|
+
Note if the input file is not touched at all, the file is never
|
96
|
+
*touch*-ed, regardless of the touch option:
|
97
|
+
|
98
|
+
FileOverwrite.new('a.txt', touch: true).run! # => No operation
|
99
|
+
|
100
|
+
== Description
|
101
|
+
|
102
|
+
=== Content manipulation
|
103
|
+
|
104
|
+
Three types of manipulation for the content of the file to update are allowed: IO, String, and Array.
|
105
|
+
|
106
|
+
==== IO-type manipulation
|
107
|
+
|
108
|
+
IO-type manipulation includes
|
109
|
+
|
110
|
+
* open (or modify)
|
111
|
+
|
112
|
+
Two block parameters of IO instances are given (read and write in this order).
|
113
|
+
What is output (i.e., IO#print method) with the write-descriptor
|
114
|
+
inside the block will be the content of the overwritten file.
|
115
|
+
|
116
|
+
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 the manipulation from
|
118
|
+
the beginning. For convenience, the same method names with the
|
119
|
+
trailing '!' are defined ({FileOverwrite#open!} and {FileOverwrite#modify!}), with which {FileOverwrite#run!} will be performed automatically.
|
120
|
+
|
121
|
+
==== String-type manipulation
|
122
|
+
|
123
|
+
String-type manipulation includes
|
124
|
+
|
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)
|
132
|
+
|
133
|
+
These methods must return String (or its equivalent) that will be written in the updated file.
|
134
|
+
Those that take block never return Enumerator (like String#sub).
|
135
|
+
|
136
|
+
The biggest advantage is you can chain these methods, before calling
|
137
|
+
{FileOverwrite#run!}, as in the examples above.
|
138
|
+
|
139
|
+
Note that "each"-something-type methods in this class are the
|
140
|
+
short-hands of the following, and expect each iterator returns String:
|
141
|
+
fo.read{ |allstr|
|
142
|
+
outstr = ''
|
143
|
+
allstr.each_line do |i|
|
144
|
+
# Do whatever
|
145
|
+
outstr << result
|
146
|
+
end
|
147
|
+
outstr
|
148
|
+
}
|
149
|
+
|
150
|
+
Just like IO-type methods, they can be called with a trailing '!' to
|
151
|
+
perform {FileOverwrite#run!} straightaway.
|
152
|
+
|
153
|
+
Note that once you call one of manipulations of this type, the entire
|
154
|
+
content of the input file is stored in the memory until {FileOverwrite#run!} is
|
155
|
+
called and the object is GC-ed. For a very large file, IO-type
|
156
|
+
manipulation is more appropriate.
|
157
|
+
|
158
|
+
==== Array-type manipulation
|
159
|
+
|
160
|
+
Similarly, an Array-type manipulation is defined:
|
161
|
+
|
162
|
+
* readlines
|
163
|
+
|
164
|
+
The "readlines" method must return Array (or its equivalent) that will be
|
165
|
+
written in the updated file, where all the elements are simply concatenated.
|
166
|
+
|
167
|
+
This method can be concatenated with this method only, and can not be
|
168
|
+
combined with manipulations in the other types.
|
169
|
+
|
170
|
+
Just like IO-type methods, this can be called with a trailing '!' to
|
171
|
+
perform {FileOverwrite#run!} straightaway.
|
172
|
+
|
173
|
+
=== Other methods
|
174
|
+
|
175
|
+
==== Those related to the state
|
176
|
+
|
177
|
+
{FileOverwrite#fresh?}:: True if the process has not begun (the file is not read, yet)
|
178
|
+
{FileOverwrite#ready?}:: True if it is ready to output (overwriting the file)
|
179
|
+
{FileOverwrite#reset}:: Cancel the current processing and start the text processing from the beginning, as read from the original file.
|
180
|
+
{FileOverwrite#chainable?}:: True if the current state is chainable with other methods, namely in the String- or Array-type manipulations.
|
181
|
+
{FileOverwrite#completed?}:: True if the overwriting process has been completed, and the instance is frozen.
|
182
|
+
{FileOverwrite#state}:: True if completed, nil if {FileOverwrite#fresh?}, or Class (IO, String, Array), depending on which type of manipulation has been in place.
|
183
|
+
{FileOverwrite#empty?}:: Synonym of {FileOverwrite#dump}.empty?
|
184
|
+
{FileOverwrite#end_with?}:: Synonym of {FileOverwrite#dump}.end_with?
|
185
|
+
{FileOverwrite#force_encoding}(enc):: Sets the encoding of the current or to-be-read String (or Array). The encoding set is not affected even after {FileOverwrite#reset}
|
186
|
+
{FileOverwrite#valid_encoding?}:: As in <tt>String#valid_encoding?</tt> Note this returns nil if {FileOverwrite#completed?}
|
187
|
+
|
188
|
+
==== Those related to the parameters
|
189
|
+
|
190
|
+
Note none of the write-methods are available once {FileOverwrite#completed?}
|
191
|
+
|
192
|
+
{FileOverwrite#backup}:: (read/write) Filename (String) of the backup file.
|
193
|
+
{FileOverwrite#dump}:: Dump the String of the original file if {FileOverwrite#fresh?}, the current one to be output if in the middle of process, or that of the written file if {FileOverwrite#completed?}
|
194
|
+
{FileOverwrite#encode}:: Converts the internal encoding of the current or to-be-read String (or Array), i.e., the encoding of String passed to the user would be this encoding, providing the conversion goes successfully.
|
195
|
+
{FileOverwrite#sizes}:: (read-only) Hash of :old and :new files of the sizes. This is set only after {FileOverwrite#run!} If both setsize and verbose options are false in {FileOverwrite#run!} (the former is true in default), the file size calculation is suppressed and hence this returns nil.
|
196
|
+
{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
|
+
{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
|
+
{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.
|
199
|
+
|
200
|
+
== Developer's note
|
201
|
+
|
202
|
+
The source codes are annotated in the {YARD}[https://yardoc.org/] format.
|
203
|
+
You can view it in {RubyGems/file_overwrite}[https://rubygems.org/gems/file_overwrite].
|
204
|
+
|
205
|
+
=== Algorithm
|
206
|
+
|
207
|
+
(1) It first writes a temporary file according to your manipulation.
|
208
|
+
(2) Then when you perform {FileOverwrite#run!}, the following is done in one go:
|
209
|
+
* the original file is backed up to the specified backup file,
|
210
|
+
* the temporary file is renamed to the original file,
|
211
|
+
* if it is instructed to leave no backup file, the backup file is deleted.
|
212
|
+
(3) After {FileOverwrite#run!}, the instance of this class is still accessible but frozen.
|
213
|
+
|
214
|
+
If {FileOverwrite#run!} or its equivalent is not performed, the temporary file will be
|
215
|
+
deleted by GC.
|
216
|
+
|
217
|
+
=== Tests
|
218
|
+
|
219
|
+
Ruby codes under the directory <tt>test/</tt> are the test scripts.
|
220
|
+
You can run them from the top directory as <tt>ruby test/test_****.rb</tt>
|
221
|
+
or simply run <tt>make test</tt>.
|
222
|
+
|
223
|
+
|
224
|
+
== Known bugs
|
225
|
+
|
226
|
+
None.
|
227
|
+
|
228
|
+
|
229
|
+
== Copyright
|
230
|
+
|
231
|
+
Author:: Masa Sakano < info a_t wisebabel dot com >
|
232
|
+
Versions:: The versions of this package follow Semantic Versioning (2.0.0) http://semver.org/
|
233
|
+
|
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/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{file_overwrite}
|
7
|
+
s.version = "0.1"
|
8
|
+
# s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
# s.bindir = 'bin'
|
10
|
+
s.authors = ["Masa Sakano"]
|
11
|
+
s.date = Time.now.strftime("%Y-%m-%d")
|
12
|
+
s.summary = %q{Class to overwrite an existing file safely}
|
13
|
+
s.description = %q{This class provides a Ruby-oriented scheme to safely overwrite an existing file, leaving a backup file unless specified otherwise. It writes a temporary file first, which is renamed to the original file in one action. It accepts a block like some IO class-methods (e.g., each_line) and chaining like String methods (e.g., sub and gsub).}
|
14
|
+
# s.email = %q{abc@example.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
# "LICENSE",
|
17
|
+
"README.en.rdoc",
|
18
|
+
]
|
19
|
+
s.license = 'MIT'
|
20
|
+
s.files = FileList['.gitignore','lib/**/*.rb','[A-Z]*','test/**/*.rb', '*.gemspec'].to_a.delete_if{ |f|
|
21
|
+
ret = false
|
22
|
+
arignore = IO.readlines('.gitignore')
|
23
|
+
arignore.map{|i| i.chomp}.each do |suffix|
|
24
|
+
if File.fnmatch(suffix, File.basename(f))
|
25
|
+
ret = true
|
26
|
+
break
|
27
|
+
end
|
28
|
+
end
|
29
|
+
ret
|
30
|
+
}
|
31
|
+
s.files.reject! { |fn| File.symlink? fn }
|
32
|
+
# s.add_runtime_dependency 'rails'
|
33
|
+
# s.add_development_dependency "bourne", [">= 0"]
|
34
|
+
s.homepage = %q{https://www.wisebabel.com}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
|
37
|
+
# s.require_paths = ["lib"] # Default "lib"
|
38
|
+
s.required_ruby_version = '>= 2.0'
|
39
|
+
s.test_files = Dir['test/**/*.rb']
|
40
|
+
s.test_files.reject! { |fn| File.symlink? fn }
|
41
|
+
# s.requirements << 'libmagick, v6.0' # Simply, info to users.
|
42
|
+
# s.rubygems_version = %q{1.3.5} # This is always set automatically!!
|
43
|
+
s.metadata["yard.run"] = "yri" # use "yard" to build full HTML docs.
|
44
|
+
end
|
45
|
+
|