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 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
@@ -0,0 +1,5 @@
1
+ -----
2
+ (Version: 0.1)
3
+ 2018-09-19 Masa Sakano
4
+
5
+ * Initial preliminary commit.
data/Makefile ADDED
@@ -0,0 +1,22 @@
1
+ ALL =
2
+
3
+ objs =
4
+
5
+ .SUFFIXES: .so .o .c .f
6
+
7
+ #.o.so:
8
+ # ${LD} ${LFLAGS} -o $@ $< ${LINK_LIB}
9
+
10
+ all: ${ALL}
11
+
12
+
13
+ .PHONY: clean test doc
14
+ clean:
15
+ $(RM) bin/*~
16
+
17
+ test:
18
+ rake test
19
+
20
+ doc:
21
+ yard doc
22
+
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,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
9
+
@@ -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
+