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