cosine-advanced-fileutils 0.0.2 → 0.0.3

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.
@@ -0,0 +1,347 @@
1
+ #
2
+ # = advanced-fileutils.rb
3
+ #
4
+ # Copyright (c) 2009 Michael H. Buselli
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the same terms of Ruby.
8
+ #
9
+ # == module AdvFileUtils
10
+ #
11
+ # Advanced FileUtils contains more methods you might have wished were in
12
+ # FileUtils.
13
+ #
14
+ # === Module Functions
15
+ #
16
+ # append(filename, data, options)
17
+ # append(filename, options) {|file| .... }
18
+ # truncate(filename, options)
19
+ # truncate(filename, data, options)
20
+ # truncate(filename, options) {|file| .... }
21
+ # replace(filename, data, options) # does "atomic" file change
22
+ # replace(filename, options) {|file| .... }
23
+ # x_insert(filename, data, options) # :line => 7
24
+ # x_insert(filename, options) {|file| .... }
25
+ # x_update(filename, data, options) # :line => 7
26
+ # x_update(filename, options) {|file| .... } # :lines => 7..10
27
+ # # :separator => ':'
28
+ # # :where => '$1 = mbuselli'
29
+ # edit(filename, options) # :visual => true
30
+ # edit(filename, data, options)
31
+ # system(command_list, options)
32
+ # shell(command_list, options) # alias of system
33
+ # sh(command_list, options) # alias of system
34
+ # x_sudo(command_list, options) # :runas => 0
35
+ #
36
+ # Many options are not implemented yet.
37
+ #
38
+
39
+ require 'sha1'
40
+ require 'escape'
41
+
42
+ module AdvFileUtils
43
+
44
+ #
45
+ # A superclass of all errors that can be raised from this module's
46
+ # functions.
47
+ #
48
+ class Error < Exception; end
49
+
50
+ #
51
+ # CommandError is raised when an external command has a problem.
52
+ #
53
+ class CommandError < AdvFileUtils::Error; end
54
+
55
+ #
56
+ # FileLockError is raised when trying to open an agreed upon "lockfile"
57
+ # and the file already exists.
58
+ #
59
+ class FileLockError < AdvFileUtils::Error; end
60
+
61
+
62
+ # Hash table to hold command options.
63
+ OPT_TABLE = {} #:nodoc: internal use only
64
+
65
+
66
+ # Method for internal use to prep output for file output verbose messages.
67
+ def write_echo_message (data, op, filename) #:nodoc:
68
+ [ 'echo',
69
+ Escape.shell_single_word(
70
+ if data[-1] == "\n"[0]
71
+ data[0...-1]
72
+ else
73
+ data + '\\c'
74
+ end),
75
+ op,
76
+ Escape.shell_single_word(filename)
77
+ ].join(' ')
78
+ end
79
+ module_function :write_echo_message
80
+ private_class_method :write_echo_message
81
+
82
+
83
+ # Method for internal use to intercept write calls when we are being verbose.
84
+ def hook_write (object, filename, tail_msg = nil) #:nodoc:
85
+ object.instance_eval <<-__EOM__, __FILE__, __LINE__ + 1
86
+ class << self
87
+ def write (data, *args)
88
+ $stderr.puts AdvFileUtils.__send__(:write_echo_message, data, '>>', #{filename.inspect})
89
+ super
90
+ ensure
91
+ send #{tail_msg.inspect} if #{tail_msg.inspect}
92
+ end
93
+ end
94
+ __EOM__
95
+ end
96
+ module_function :hook_write
97
+ private_class_method :hook_write
98
+
99
+
100
+ def parse_data_and_options (data_and_options) #:nodoc#
101
+ if data_and_options.size == 1
102
+ if data_and_options[0].respond_to? :has_key?
103
+ options = data_and_options[0]
104
+ else
105
+ data = data_and_options[0]
106
+ end
107
+ else
108
+ data, options = *data_and_options
109
+ end
110
+
111
+ data ||= ''
112
+ options ||= {}
113
+ [data, options]
114
+ end
115
+ module_function :parse_data_and_options
116
+ private_class_method :parse_data_and_options
117
+
118
+
119
+ #
120
+ # Internally used function to implement .append and .truncate.
121
+ #
122
+ def generic_write (open_arg, filename, *data_and_options) #:nodoc:
123
+ if block_given?
124
+ options = data_and_options[0] || {}
125
+
126
+ if options[:verbose] and open_arg == 'w'
127
+ $stderr.puts "cat /dev/null > #{Escape.shell_single_word(filename)}"
128
+ end
129
+
130
+ if not options[:noop]
131
+ File.open(filename, open_arg) do |f|
132
+ hook_write(f, filename) if options[:verbose]
133
+ yield f
134
+ end
135
+
136
+ elsif options[:verbose]
137
+ f = StringIO.new
138
+ hook_write(f, filename, :rewind)
139
+ yield f
140
+ end
141
+
142
+ else
143
+ data, options = *parse_data_and_options(data_and_options)
144
+
145
+ if options[:verbose]
146
+ if open_arg == 'w'
147
+ $stderr.puts AdvFileUtils.__send__(:write_echo_message, data, '>', filename)
148
+ else
149
+ $stderr.puts AdvFileUtils.__send__(:write_echo_message, data, '>>', filename)
150
+ end
151
+ end
152
+
153
+ if not options[:noop]
154
+ File.open(filename, open_arg) do |f|
155
+ f.write(data)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ module_function :generic_write
161
+ private_class_method :generic_write
162
+
163
+
164
+ #
165
+ # Options: verbose, noop, force, backup
166
+ #
167
+ # Append the given +data+ to the file named by +filename+.
168
+ #
169
+ # If called with a block then the File object is yielded to the block
170
+ # for appending data intead of the data being passed as an argument.
171
+ #
172
+ # AdvFileUtils.append('data.log', "some data for log entry\n")
173
+ # AdvFileUtils.append('data.log') { |f| f.puts "some data for log entry" }
174
+ #
175
+ def append (filename, *data_and_options, &block)
176
+ generic_write 'a', filename, *data_and_options, &block
177
+ end
178
+ module_function :append
179
+ public :append
180
+
181
+ OPT_TABLE['append'] = [:verbose, :noop, :force, :backup]
182
+
183
+
184
+ #
185
+ # Options: verbose, noop, force, backup
186
+ #
187
+ # Replace the given +data+ in the file named by +filename+.
188
+ #
189
+ # If called with a block then the File object is yielded to the block
190
+ # for writing data intead of the data being passed as an argument.
191
+ #
192
+ # AdvFileUtils.truncate('data.log', "some data\n")
193
+ # AdvFileUtils.truncate('data.log') { |f| f.puts "some data" }
194
+ #
195
+ def truncate (filename, *data_and_options, &block)
196
+ generic_write 'w', filename, *data_and_options, &block
197
+ end
198
+ module_function :truncate
199
+ public :truncate
200
+
201
+ OPT_TABLE['truncate'] = [:verbose, :noop, :force, :backup]
202
+
203
+
204
+ #
205
+ # Options: verbose, noop
206
+ #
207
+ # An alternative to Kernel.system that accepts options for verbosity
208
+ # and dry runs.
209
+ #
210
+ def system (*command_and_options)
211
+ if command_and_options[-1].respond_to?(:has_key?)
212
+ command = command_and_options[0...-1]
213
+ options = command_and_options[-1]
214
+ else
215
+ command = command_and_options
216
+ options = {}
217
+ end
218
+
219
+ raise ArgumentError.new('wrong number of arguments') if command.empty?
220
+
221
+ if options[:verbose]
222
+ if command.size == 1
223
+ $stderr.puts command[0]
224
+ else
225
+ $stderr.puts command.collect { |word|
226
+ Escape.shell_single_word word
227
+ }.join(' ')
228
+ end
229
+ end
230
+
231
+ if not options[:noop]
232
+ Kernel.system(*command)
233
+ end
234
+ end
235
+ module_function :system
236
+ public :system
237
+
238
+ alias sh system
239
+ module_function :sh
240
+ public :sh
241
+
242
+ alias shell system
243
+ module_function :shell
244
+ public :shell
245
+
246
+ OPT_TABLE['sh'] = OPT_TABLE['shell'] =
247
+ OPT_TABLE['system'] = [:verbose, :noop]
248
+
249
+
250
+ #
251
+ # Options: verbose, noop, force, backup
252
+ #
253
+ # Invoke an external editor to edit some text or a file.
254
+ #
255
+ # edit(filename, options)
256
+ # edit(filename, data, options)
257
+ # edit(nil, data, options)
258
+ #
259
+ # Return values
260
+ #
261
+ # true, if successful and file was edited
262
+ # false, if successful and file was not edited
263
+ # nil, if successful and file was not saved
264
+ #
265
+ def edit (filename, *data_and_options)
266
+ data, options = *parse_data_and_options(data_and_options)
267
+ editor =
268
+ ENV.has_key?('VISUAL') ? ENV['VISUAL'] :
269
+ ENV.has_key?('EDITOR') ? ENV['EDITOR'] : 'vi'
270
+
271
+ file_stat = File.stat(filename)
272
+ file_checksum = SHA1.file(filename)
273
+ system(editor, filename, options)
274
+ proc_status = $?
275
+
276
+ if options[:noop]
277
+ return true
278
+
279
+ elsif proc_status.success?
280
+ return nil if file_stat == File.stat(filename)
281
+ return false if file_checksum == SHA1.file(filename)
282
+ return true
283
+
284
+ elsif proc_status.signaled?
285
+ raise AdvFileUtils::CommandError.new("editor terminated on signal #{proc_status.termsig}")
286
+
287
+ else
288
+ raise AdvFileUtils::CommandError.new("editor had non-zero exit code #{proc_status.exitstatus}")
289
+ end
290
+ end
291
+ module_function :edit
292
+ public :edit
293
+
294
+ OPT_TABLE['edit'] = [:verbose, :noop, :force, :backup]
295
+
296
+
297
+ #
298
+ # Options: verbose, noop, force, backup, lockfile, retry
299
+ #
300
+ # Edit a file, but open a temporary lockfile instead and move it in place
301
+ # after editting is complete.
302
+ #
303
+ def replace (filename, *data_and_options)
304
+ data, options = *parse_data_and_options(data_and_options)
305
+ lockfile = options[:lockfile] ? options[:lockfile] : "#{filename}.lock"
306
+
307
+ begin
308
+ if not options[:noop]
309
+ fd = IO.sysopen(lockfile, IO::WRONLY | IO::CREAT | IO::EXCL, 0700)
310
+ f = IO.new(fd, 'w')
311
+ hook_write(f, lockfile) if block_given? and options[:verbose]
312
+ else
313
+ f = StringIO.new
314
+ hook_write(f, lockfile, :rewind) if block_given? and options[:verbose]
315
+ end
316
+
317
+ file_stat = File.stat(filename) rescue nil
318
+
319
+ if block_given?
320
+ $stderr.puts "cat /dev/null > #{Escape.shell_single_word(lockfile)}" if options[:verbose]
321
+ yield f
322
+ else
323
+ $stderr.puts AdvFileUtils.__send__(:write_echo_message, data, '>', lockfile) if options[:verbose]
324
+ f.write(data)
325
+ end
326
+
327
+ f.close
328
+
329
+ if file_stat
330
+ FileUtils.chown(file_stat.uid.to_s, file_stat.gid.to_s, lockfile, options)
331
+ FileUtils.chmod(file_stat.mode & 07777, lockfile, options)
332
+ end
333
+ FileUtils.mv(lockfile, filename, options)
334
+
335
+ ensure
336
+ f.close if f and not f.closed?
337
+ begin
338
+ File.delete(lockfile) if fd
339
+ rescue Errno::ENOENT
340
+ end
341
+ end
342
+ end
343
+ module_function :replace
344
+ public :replace
345
+
346
+ OPT_TABLE['replace'] = [:verbose, :noop, :force, :backup, :lockfile, :retry]
347
+ end
@@ -0,0 +1,19 @@
1
+
2
+ require 'advanced-fileutils'
3
+
4
+ module FileUtils
5
+ extend ::AdvFileUtils
6
+ end
7
+
8
+ #module FileUtils::Verbose
9
+ # extend ::AdvFileUtils::Verbose
10
+ #end
11
+
12
+ #module FileUtils::NoWrite
13
+ # extend ::AdvFileUtils::NoWrite
14
+ #end
15
+
16
+ #module FileUtils::DryRun
17
+ # extend ::AdvFileUtils::DryRun
18
+ #end
19
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cosine-advanced-fileutils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael H Buselli
@@ -32,8 +32,10 @@ extensions: []
32
32
 
33
33
  extra_rdoc_files: []
34
34
 
35
- files: []
36
-
35
+ files:
36
+ - lib/fileutils
37
+ - lib/fileutils/advanced.rb
38
+ - lib/advanced-fileutils.rb
37
39
  has_rdoc: true
38
40
  homepage: http://cosine.org/ruby/advanced-fileutils/
39
41
  post_install_message: