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.
- data/lib/advanced-fileutils.rb +394 -0
- data/lib/fileutils/advanced.rb +19 -0
- metadata +67 -0
@@ -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
|
+
|