ratch 1.1.0 → 1.2.0
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/.ruby +99 -0
- data/COPYING +203 -21
- data/History.rdoc +35 -0
- data/License.txt +204 -0
- data/README.rdoc +113 -0
- data/Version +1 -0
- data/bin/ludo +16 -0
- data/bin/ratch +1 -8
- data/lib/ratch.rb +28 -0
- data/lib/ratch.yml +99 -0
- data/lib/ratch/batch.rb +500 -0
- data/lib/ratch/console.rb +199 -0
- data/lib/ratch/core_ext.rb +1 -4
- data/lib/ratch/core_ext/facets.rb +15 -1
- data/lib/ratch/core_ext/filetest.rb +29 -0
- data/lib/ratch/core_ext/{string.rb → to_actual_filename.rb} +0 -23
- data/lib/ratch/core_ext/to_console.rb +30 -12
- data/lib/ratch/core_ext/{object.rb → to_yamlfrag.rb} +1 -0
- data/lib/ratch/core_ext/unfold_paragraphs.rb +27 -0
- data/lib/ratch/file_list.rb +411 -0
- data/lib/ratch/script.rb +99 -5
- data/lib/ratch/script/help.rb +84 -0
- data/lib/ratch/shell.rb +783 -0
- data/lib/ratch/system.rb +15 -0
- data/lib/ratch/utils/cli.rb +49 -0
- data/lib/ratch/utils/config.rb +52 -0
- data/lib/ratch/{emailer.rb → utils/email.rb} +43 -6
- data/lib/ratch/utils/ftp.rb +134 -0
- data/lib/ratch/utils/pom.rb +22 -0
- data/lib/ratch/utils/rdoc.rb +48 -0
- data/lib/ratch/utils/tar.rb +88 -0
- data/lib/ratch/utils/xdg.rb +39 -0
- data/lib/ratch/utils/zlib.rb +54 -0
- data/spec/01_shell.rdoc +198 -0
- data/spec/02_script.rdoc +34 -0
- data/spec/03_batch.rdoc +71 -0
- data/spec/04_system.rdoc +3 -0
- data/spec/applique/array.rb +8 -0
- data/spec/applique/setup.rb +20 -0
- data/test/case_batch.rb +63 -0
- data/test/case_shell.rb +46 -0
- data/test/core_ext/case_pathname.rb +361 -0
- data/test/helper.rb +4 -0
- data/test/utils/case_cli.rb +6 -0
- data/test/utils/case_config.rb +12 -0
- data/test/utils/case_email.rb +10 -0
- data/test/utils/case_ftp.rb +6 -0
- data/test/utils/case_pom.rb +17 -0
- data/test/utils/case_rdoc.rb +23 -0
- data/test/utils/case_tar.rb +6 -0
- data/test/utils/case_zlib.rb +11 -0
- data/test/utils/fixtures/pom_sample/Profile +4 -0
- data/test/utils/fixtures/rdoc_sample/README.rdoc +4 -0
- data/test/utils/fixtures/rdoc_sample/lib/rdoc_sample/rdoc_sample.rb +9 -0
- metadata +139 -82
- data/HISTORY +0 -6
- data/MANIFEST +0 -53
- data/NEWS +0 -12
- data/README +0 -87
- data/VERSION +0 -1
- data/demo/tryme-task.ratch +0 -12
- data/demo/tryme1.ratch +0 -6
- data/doc/log/basic_stats/index.html +0 -39
- data/doc/log/notes.xml +0 -18
- data/doc/log/stats.log +0 -14
- data/doc/log/syntax.log +0 -0
- data/doc/log/testunit.log +0 -156
- data/lib/ratch/commandline.rb +0 -16
- data/lib/ratch/core_ext/pathname.rb +0 -38
- data/lib/ratch/dsl.rb +0 -420
- data/lib/ratch/index.rb +0 -4
- data/lib/ratch/io.rb +0 -116
- data/lib/ratch/plugin.rb +0 -65
- data/lib/ratch/service.rb +0 -33
- data/lib/ratch/task.rb +0 -249
- data/lib/ratch/task2.rb +0 -298
- data/meta/abstract +0 -4
- data/meta/author +0 -1
- data/meta/contact +0 -1
- data/meta/homepage +0 -1
- data/meta/name +0 -1
- data/meta/requires +0 -4
- data/meta/summary +0 -1
- data/test/README +0 -1
- data/test/test_helper.rb +0 -4
- data/test/test_task.rb +0 -46
data/lib/ratch/script.rb
CHANGED
@@ -1,14 +1,108 @@
|
|
1
|
-
require 'ratch/
|
1
|
+
require 'ratch/core_ext'
|
2
|
+
require 'ratch/shell'
|
3
|
+
#require 'ratch/plugin'
|
4
|
+
|
5
|
+
# CLI extension is required.
|
6
|
+
require 'ratch/utils/cli'
|
2
7
|
|
3
8
|
module Ratch
|
4
9
|
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# The Ratch Script class is used to run stand-alone ratch scripts.
|
10
|
+
# The Ratch Script class is used to run stand-alone Ratch scripts.
|
8
11
|
# Yep, this is actaully a class named exactly for what it is.
|
9
12
|
# How rare.
|
10
13
|
#
|
11
|
-
|
14
|
+
#--
|
15
|
+
# Previous versions of this class subclassed Module and called `self extend`
|
16
|
+
# in the +initialize+ method. Then used +method_missing+ to route to an
|
17
|
+
# instance of Shell. The current design works the other way round.
|
18
|
+
# Both approaches seem to work just as well, though perhaps one is more
|
19
|
+
# robust than another? I have chosen this design simply b/c the shell
|
20
|
+
# methods should dispatch a bit faster.
|
21
|
+
#++
|
22
|
+
class Script < Shell #< Module
|
23
|
+
|
24
|
+
include CLI
|
25
|
+
|
26
|
+
#
|
27
|
+
def self.execute(script, *args)
|
28
|
+
script = new(script, *args)
|
29
|
+
script.execute!
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
def initialize(file, *args)
|
34
|
+
@_file = file.to_s
|
35
|
+
|
36
|
+
#extend self
|
37
|
+
|
38
|
+
super(*args)
|
39
|
+
|
40
|
+
#@_stdout = options[:stdout] || $stdout
|
41
|
+
#@_stderr = options[:stderr] || $stderr
|
42
|
+
#@_stdin = options[:stdin] || $stdin
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the file name of the script.
|
46
|
+
def script_file
|
47
|
+
@_file
|
48
|
+
end
|
49
|
+
|
50
|
+
# Be cautious about calling this in a script --an infinite loop could
|
51
|
+
# easily ensue.
|
52
|
+
def execute!
|
53
|
+
old = $0
|
54
|
+
begin
|
55
|
+
$0 = script_file
|
56
|
+
instance_eval(File.read(script_file), script_file, 1)
|
57
|
+
ensure
|
58
|
+
$0 = old
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :run!, :execute!
|
63
|
+
|
64
|
+
#
|
65
|
+
#def print(str=nil)
|
66
|
+
# super(str.to_s) unless quiet?
|
67
|
+
#end
|
68
|
+
|
69
|
+
#
|
70
|
+
#def puts(str=nil)
|
71
|
+
# super(str.to_s) unless quiet?
|
72
|
+
#end
|
73
|
+
|
74
|
+
# TODO: Deprecate one of the three #report, #status, #trace.
|
75
|
+
def report(message)
|
76
|
+
#@_stdout.puts(message) unless quiet?
|
77
|
+
puts(message) unless quiet?
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
def status(message)
|
82
|
+
#@_stdout.puts message unless quiet?
|
83
|
+
puts message unless quiet? # dryrun? or trace?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal status report. Only output if in trace mode.
|
87
|
+
#
|
88
|
+
def trace(message)
|
89
|
+
#@_stdout.puts message if trace?
|
90
|
+
puts message if trace?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Pass-thru to singleton class.
|
94
|
+
def define_method(name, &block)
|
95
|
+
(class << self; self; end).__send__(:define_method, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
## If method is missing, try the singleton class. This allows the script
|
99
|
+
## to use methods like +define_method+.
|
100
|
+
##
|
101
|
+
## TODO: Perhaps it would be best to limit the selection of methods?
|
102
|
+
#def method_missing(sym, *args, &blk)
|
103
|
+
# (class << self; self; end).__send__(sym, *args, &blk)
|
104
|
+
#end
|
105
|
+
|
12
106
|
end
|
13
107
|
|
14
108
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Ratch
|
2
|
+
|
3
|
+
# Support class for displaying command help lists.
|
4
|
+
class Help
|
5
|
+
|
6
|
+
#
|
7
|
+
def self.list(*dirs)
|
8
|
+
tasks = script_descriptions(*dirs)
|
9
|
+
tmax = tasks.keys.max{ |a,b| a.size <=> b.size }.size
|
10
|
+
#dmax = dirs.flatten.max{ |a,b| a.size <=> b.size }.size
|
11
|
+
#if dir == ''
|
12
|
+
# max += 4 + 2
|
13
|
+
#else
|
14
|
+
max = tmax + 4
|
15
|
+
#end
|
16
|
+
tasks = tasks.sort_by{|k,v| k }
|
17
|
+
tasks.each do |name, sum|
|
18
|
+
#if dir == ''
|
19
|
+
# cmd = "ratch #{name}"
|
20
|
+
#else
|
21
|
+
cmd = name
|
22
|
+
#end
|
23
|
+
puts "%-#{max}s # %s" % [cmd, sum]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Scan task scripts for descriptions.
|
28
|
+
def self.script_descriptions(*dirs)
|
29
|
+
opts = Hash === dirs.last ? dirs.pop : {}
|
30
|
+
dirs = dirs.flatten
|
31
|
+
help = {}
|
32
|
+
dirs.each do |dir|
|
33
|
+
files = Dir.glob(File.join(dir,'**/*'))
|
34
|
+
files.each do |fname|
|
35
|
+
next if FileTest.directory?(fname)
|
36
|
+
next if opts[:exe] and !FileTest.executable?(fname)
|
37
|
+
desc = ''
|
38
|
+
File.open(fname) do |f|
|
39
|
+
line = ''
|
40
|
+
until f.eof?
|
41
|
+
line = f.gets
|
42
|
+
case line
|
43
|
+
when /^(#!|\s*$)/
|
44
|
+
next
|
45
|
+
when /^\s*#(.*)/
|
46
|
+
desc = $1.strip; break
|
47
|
+
else
|
48
|
+
desc = nil; break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
key = opts[:exe] ? fname : fname.sub(dir+'/', '')
|
53
|
+
help[key] = desc
|
54
|
+
end
|
55
|
+
end
|
56
|
+
help
|
57
|
+
end
|
58
|
+
|
59
|
+
# Scan script for description header.
|
60
|
+
def self.header(file, opts={})
|
61
|
+
#next if FileTest.directory?(file)
|
62
|
+
#next if opts[:exe] and !FileTest.executable?(file)
|
63
|
+
desc = "\n"
|
64
|
+
File.open(file) do |f|
|
65
|
+
line = ''
|
66
|
+
until f.eof?
|
67
|
+
line = f.gets
|
68
|
+
case line
|
69
|
+
when /^(#!|\s*$)/
|
70
|
+
next
|
71
|
+
when /^\s*#\s?(.*)/
|
72
|
+
desc << $1.rstrip + "\n"
|
73
|
+
else
|
74
|
+
break
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
desc + "\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
data/lib/ratch/shell.rb
ADDED
@@ -0,0 +1,783 @@
|
|
1
|
+
require 'thread'
|
2
|
+
#require 'rbconfig'
|
3
|
+
require 'ratch/core_ext'
|
4
|
+
require 'ratch/batch'
|
5
|
+
|
6
|
+
module Ratch
|
7
|
+
|
8
|
+
# TODO: Better base class?
|
9
|
+
class FileNotFound < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# = Shell Prompt class
|
13
|
+
#
|
14
|
+
# Ratch Shell object provides a limited file system shell in code.
|
15
|
+
# It is similar to having a shell prompt available to you in Ruby.
|
16
|
+
#
|
17
|
+
# NOTE: We have used the term *trace* in place of *verbose* for command
|
18
|
+
# line options. Even though Ruby itself uses the term *verbose* with respect
|
19
|
+
# to FileUtils, the term is commonly used for command specific needs, so we
|
20
|
+
# want to leave it open for such cases.
|
21
|
+
class Shell
|
22
|
+
|
23
|
+
# New Shell object.
|
24
|
+
#
|
25
|
+
# Shell.new(:noop=>true)
|
26
|
+
# Shell.new('..', :quiet=>true)
|
27
|
+
#
|
28
|
+
def initialize(*args)
|
29
|
+
path, opts = parse_arguments(*args)
|
30
|
+
|
31
|
+
opts.rekey!(&:to_sym)
|
32
|
+
|
33
|
+
set_options(opts)
|
34
|
+
|
35
|
+
if path.empty?
|
36
|
+
path = Dir.pwd
|
37
|
+
else
|
38
|
+
path = File.join(*path)
|
39
|
+
end
|
40
|
+
|
41
|
+
raise FileNotFound, "#{path}" unless ::File.exist?(path)
|
42
|
+
raise FileNotFound, "#{path}" unless ::File.directory?(path)
|
43
|
+
|
44
|
+
@_work = Pathname.new(path).expand_path
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
#
|
50
|
+
def parse_arguments(*args)
|
51
|
+
opts = (Hash===args.last ? args.pop : {})
|
52
|
+
return args, opts
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
def set_options(opts)
|
57
|
+
@_quiet = opts[:quiet]
|
58
|
+
@_noop = opts[:noop] || opts[:dryrun]
|
59
|
+
@_trace = opts[:trace] || opts[:dryrun]
|
60
|
+
#@_force = opts[:force]
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
def mutex
|
65
|
+
@mutex ||= Mutex.new
|
66
|
+
end
|
67
|
+
|
68
|
+
public
|
69
|
+
|
70
|
+
# Opertaton mode. This can be :noop, :verbose or :dryrun.
|
71
|
+
# The later is the same as the first two combined.
|
72
|
+
#def mode(opts=nil)
|
73
|
+
# return @mode unless opts
|
74
|
+
# opts.each do |key, val|
|
75
|
+
# next unless val
|
76
|
+
# case key
|
77
|
+
# when :noop
|
78
|
+
# @mode = (@mode == :verbose ? :dryrun : :noop)
|
79
|
+
# when :verbose
|
80
|
+
# @mode = (@mode == :noop ? :dryrun : :verbose)
|
81
|
+
# when :dryrun
|
82
|
+
# @mode = :dryrun
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#end
|
86
|
+
|
87
|
+
def quiet? ; @_quiet ; end
|
88
|
+
def trace? ; @_trace ; end
|
89
|
+
def noop? ; @_noop ; end
|
90
|
+
#def force? ; @_force ; end
|
91
|
+
|
92
|
+
def dryrun? ; noop? && trace? ; end
|
93
|
+
|
94
|
+
# String representation is work directory path.
|
95
|
+
def to_s ; work.to_s ; end
|
96
|
+
|
97
|
+
# Two Shell's are equal if they have the same working path.
|
98
|
+
def ==(other)
|
99
|
+
return false unless other.is_a?(self.class)
|
100
|
+
return false unless work == other.work
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
# Same as #== except that #noop? must also be the same.
|
105
|
+
def eql?(other)
|
106
|
+
return false unless other.is_a?(self.class)
|
107
|
+
return false unless work == other.work
|
108
|
+
return false unless noop? == other.noop?
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Provides convenient starting points in the file system.
|
113
|
+
#
|
114
|
+
# root #=> #<Pathname:/>
|
115
|
+
# home #=> #<Pathname:/home/jimmy>
|
116
|
+
# work #=> #<Pathname:/home/jimmy/Documents>
|
117
|
+
#
|
118
|
+
# TODO: Replace these with Folio when Folio's is as capable.
|
119
|
+
|
120
|
+
# Current root path.
|
121
|
+
#def root(*args)
|
122
|
+
# Pathname['/', *args]
|
123
|
+
#end
|
124
|
+
|
125
|
+
# Current home path.
|
126
|
+
#def home(*args)
|
127
|
+
# Pathname['~', *args].expand_path
|
128
|
+
#end
|
129
|
+
|
130
|
+
# Current working path.
|
131
|
+
#def work(*args)
|
132
|
+
# Pathname['.', *args]
|
133
|
+
#end
|
134
|
+
|
135
|
+
# TODO: Should these take *args?
|
136
|
+
|
137
|
+
# Root location.
|
138
|
+
def root(*args)
|
139
|
+
dir('/', *args)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Current home path.
|
143
|
+
def home(*args)
|
144
|
+
dir(File.expand_path('~'), *args)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Current working path.
|
148
|
+
def work(*args)
|
149
|
+
return @_work if args.empty?
|
150
|
+
return dir(@_work, *args)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Alias for #work.
|
154
|
+
#alias_method :pwd, :work
|
155
|
+
|
156
|
+
# Return a new prompt with the same location.
|
157
|
+
# NOTE: Use #dup or #clone ?
|
158
|
+
#def new ; Shell.new(work) ; end
|
159
|
+
|
160
|
+
def parent
|
161
|
+
dir('..')
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
#def [](name)
|
166
|
+
# #FileObject[localize(name)]
|
167
|
+
# #Pathname.new(localize(path))
|
168
|
+
# Pathlist.new(localize(path))
|
169
|
+
#end
|
170
|
+
|
171
|
+
# Returns a Batch of file +patterns+.
|
172
|
+
def batch(*patterns)
|
173
|
+
Batch.new patterns.map{|pattern| localize(pattern)}
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns a Batch of file +patterns+, without any exclusions.
|
177
|
+
def batch_all(*patterns)
|
178
|
+
Batch.all patterns.map{|pattern| localize(pattern)}
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
def path(path)
|
183
|
+
Pathname.new(localize(path))
|
184
|
+
end
|
185
|
+
alias_method :pathname, :path
|
186
|
+
|
187
|
+
#
|
188
|
+
def file(path)
|
189
|
+
#FileObject[name]
|
190
|
+
path = localize(path)
|
191
|
+
raise FileNotFound unless File.file?(path)
|
192
|
+
Pathname.new(path)
|
193
|
+
end
|
194
|
+
|
195
|
+
#def doc(name)
|
196
|
+
# Document.new(name)
|
197
|
+
#end
|
198
|
+
|
199
|
+
#
|
200
|
+
def dir(path)
|
201
|
+
#Directory.new(name)
|
202
|
+
path = localize(path)
|
203
|
+
raise FileNotFound unless File.directory?(path)
|
204
|
+
Pathname.new(path)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Lists all entries.
|
208
|
+
def entries
|
209
|
+
work.entries
|
210
|
+
end
|
211
|
+
|
212
|
+
#alias_method :ls, :entries
|
213
|
+
|
214
|
+
# Lists directory entries.
|
215
|
+
def directory_entries
|
216
|
+
entries.select{ |d| d.directory? }
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
alias_method :dir_entries, :directory_entries
|
221
|
+
|
222
|
+
# Lists file entries.
|
223
|
+
def file_entries
|
224
|
+
entries.select{ |f| f.file? }
|
225
|
+
end
|
226
|
+
|
227
|
+
# Likes entries but omits '.' and '..' paths.
|
228
|
+
def pathnames
|
229
|
+
work.entries - %w{. ..}.map{|f|Pathname.new(f)}
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns list of directories.
|
233
|
+
def directories
|
234
|
+
pathnames.select{ |f| f.directory? }
|
235
|
+
end
|
236
|
+
alias_method :dirs, :directories
|
237
|
+
alias_method :folders, :directories
|
238
|
+
|
239
|
+
# Returns list of files.
|
240
|
+
def files
|
241
|
+
pathnames.select{ |f| f.file? }
|
242
|
+
end
|
243
|
+
|
244
|
+
# Glob pattern. Returns matches as strings.
|
245
|
+
def glob(*patterns, &block)
|
246
|
+
opts = (::Integer===patterns.last ? patterns.pop : 0)
|
247
|
+
matches = []
|
248
|
+
locally do
|
249
|
+
matches = patterns.map{ |pattern| ::Dir.glob(pattern, opts) }.flatten
|
250
|
+
end
|
251
|
+
if block_given?
|
252
|
+
matches.each(&block)
|
253
|
+
else
|
254
|
+
matches
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Glob files.
|
259
|
+
#def glob(*args, &blk)
|
260
|
+
# Dir.glob(*args, &blk)
|
261
|
+
#end
|
262
|
+
|
263
|
+
# TODO: Ultimately merge #glob and #multiglob.
|
264
|
+
def multiglob(*args, &blk)
|
265
|
+
Dir.multiglob(*args, &blk)
|
266
|
+
end
|
267
|
+
|
268
|
+
def multiglob_r(*args, &blk)
|
269
|
+
Dir.multiglob_r(*args, &blk)
|
270
|
+
end
|
271
|
+
|
272
|
+
=begin
|
273
|
+
# Match pattern. Like #glob but returns file objects.
|
274
|
+
# TODO: There is no FileObject any more. Should there be?
|
275
|
+
def match(*patterns, &block)
|
276
|
+
opts = (::Integer===patterns.last ? patterns.pop : 0)
|
277
|
+
patterns = localize(patterns)
|
278
|
+
matches = patterns.map{ |pattern| ::Dir.glob(pattern, opts) }.flatten
|
279
|
+
matches = matches.map{ |f| FileObject[f] }
|
280
|
+
if block_given?
|
281
|
+
matches.each(&block)
|
282
|
+
else
|
283
|
+
matches
|
284
|
+
end
|
285
|
+
end
|
286
|
+
=end
|
287
|
+
|
288
|
+
# Join paths.
|
289
|
+
# TODO: Should this return a new directory object? Or should it change directories?
|
290
|
+
def /(path)
|
291
|
+
#@_work += dir # did not work, why?
|
292
|
+
@_work = dir(localize(path))
|
293
|
+
self
|
294
|
+
end
|
295
|
+
|
296
|
+
# Alias for #/.
|
297
|
+
alias_method '+', '/'
|
298
|
+
|
299
|
+
# TODO: Tie this into the System class.
|
300
|
+
def system(cmd)
|
301
|
+
locally do
|
302
|
+
super(cmd)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Shell runner.
|
307
|
+
def sh(cmd)
|
308
|
+
#puts "--> system call: #{cmd}" if trace?
|
309
|
+
puts cmd if trace?
|
310
|
+
return true if noop?
|
311
|
+
#locally do
|
312
|
+
if quiet?
|
313
|
+
silently{ system(cmd) }
|
314
|
+
else
|
315
|
+
system(cmd)
|
316
|
+
end
|
317
|
+
#end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Shell runner.
|
321
|
+
#def sh(cmd)
|
322
|
+
# if dryrun?
|
323
|
+
# puts cmd
|
324
|
+
# true
|
325
|
+
# else
|
326
|
+
# puts "--> system call: #{cmd}" if trace?
|
327
|
+
# if quiet?
|
328
|
+
# silently{ system(cmd) }
|
329
|
+
# else
|
330
|
+
# system(cmd)
|
331
|
+
# end
|
332
|
+
# end
|
333
|
+
#end
|
334
|
+
|
335
|
+
# Change working directory.
|
336
|
+
#
|
337
|
+
# TODO: Make thread safe.
|
338
|
+
#
|
339
|
+
def cd(path, &block)
|
340
|
+
if block
|
341
|
+
work_old = @_work
|
342
|
+
begin
|
343
|
+
@_work = dir(localize(path))
|
344
|
+
locally(&block)
|
345
|
+
#mutex.synchronize do
|
346
|
+
# Dir.chdir(@_work){ block.call }
|
347
|
+
#end
|
348
|
+
ensure
|
349
|
+
@_work = work_old
|
350
|
+
end
|
351
|
+
else
|
352
|
+
@_work = dir(localize(path))
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
#
|
357
|
+
alias_method :chdir, :cd
|
358
|
+
|
359
|
+
# Bonus FileUtils features.
|
360
|
+
#def cd(*a,&b)
|
361
|
+
# puts "cd #{a}" if dryrun? or trace?
|
362
|
+
# fileutils.chdir(*a,&b)
|
363
|
+
#end
|
364
|
+
|
365
|
+
# -- File IO Shortcuts -----------------------------------------------
|
366
|
+
|
367
|
+
# Read file.
|
368
|
+
def read(path)
|
369
|
+
File.read(localize(path))
|
370
|
+
end
|
371
|
+
|
372
|
+
# Write file.
|
373
|
+
def write(path, text)
|
374
|
+
$stderr.puts "write #{path}" if trace?
|
375
|
+
File.open(localize(path), 'w'){ |f| f << text } unless noop?
|
376
|
+
end
|
377
|
+
|
378
|
+
# Append to file.
|
379
|
+
def append(path, text)
|
380
|
+
$stderr.puts "append #{path}" if trace?
|
381
|
+
File.open(localize(path), 'a'){ |f| f << text } unless noop?
|
382
|
+
end
|
383
|
+
|
384
|
+
|
385
|
+
#############
|
386
|
+
# FileTest #
|
387
|
+
#############
|
388
|
+
|
389
|
+
#
|
390
|
+
def size(path) ; FileTest.size(localize(path)) ; end
|
391
|
+
def size?(path) ; FileTest.size?(localize(path)) ; end
|
392
|
+
def directory?(path) ; FileTest.directory?(localize(path)) ; end
|
393
|
+
def symlink?(path) ; FileTest.symlink?(localize(path)) ; end
|
394
|
+
def readable?(path) ; FileTest.readable?(localize(path)) ; end
|
395
|
+
def chardev?(path) ; FileTest.chardev?(localize(path)) ; end
|
396
|
+
def exist?(path) ; FileTest.exist?(localize(path)) ; end
|
397
|
+
def exists?(path) ; FileTest.exists?(localize(path)) ; end
|
398
|
+
def zero?(path) ; FileTest.zero?(localize(path)) ; end
|
399
|
+
def pipe?(path) ; FileTest.pipe?(localize(path)) ; end
|
400
|
+
def file?(path) ; FileTest.file?(localize(path)) ; end
|
401
|
+
def sticky?(path) ; FileTest.sticky?(localize(path)) ; end
|
402
|
+
def blockdev?(path) ; FileTest.blockdev?(localize(path)) ; end
|
403
|
+
def grpowned?(path) ; FileTest.grpowned?(localize(path)) ; end
|
404
|
+
def setgid?(path) ; FileTest.setgid?(localize(path)) ; end
|
405
|
+
def setuid?(path) ; FileTest.setuid?(localize(path)) ; end
|
406
|
+
def socket?(path) ; FileTest.socket?(localize(path)) ; end
|
407
|
+
def owned?(path) ; FileTest.owned?(localize(path)) ; end
|
408
|
+
def writable?(path) ; FileTest.writable?(localize(path)) ; end
|
409
|
+
def executable?(path) ; FileTest.executable?(localize(path)) ; end
|
410
|
+
|
411
|
+
def safe?(path) ; FileTest.safe?(localize(path)) ; end
|
412
|
+
|
413
|
+
def relative?(path) ; FileTest.relative?(path) ; end
|
414
|
+
def absolute?(path) ; FileTest.absolute?(path) ; end
|
415
|
+
|
416
|
+
def writable_real?(path) ; FileTest.writable_real?(localize(path)) ; end
|
417
|
+
def executable_real?(path) ; FileTest.executable_real?(localize(path)) ; end
|
418
|
+
def readable_real?(path) ; FileTest.readable_real?(localize(path)) ; end
|
419
|
+
|
420
|
+
def identical?(path, other)
|
421
|
+
FileTest.identical?(localize(path), localize(other))
|
422
|
+
end
|
423
|
+
alias_method :compare_file, :identical?
|
424
|
+
|
425
|
+
# Assert that a path exists.
|
426
|
+
#def exists?(path)
|
427
|
+
# paths = Dir.glob(path)
|
428
|
+
# paths.not_empty?
|
429
|
+
#end
|
430
|
+
#alias_method :exist?, :exists? #; module_function :exist?
|
431
|
+
#alias_method :path?, :exists? #; module_function :path?
|
432
|
+
|
433
|
+
# Is a given path a regular file? If +path+ is a glob
|
434
|
+
# then checks to see if all matches are regular files.
|
435
|
+
#def file?(path)
|
436
|
+
# paths = Dir.glob(path)
|
437
|
+
# paths.not_empty? && paths.all?{ |f| FileTest.file?(f) }
|
438
|
+
#end
|
439
|
+
|
440
|
+
# Is a given path a directory? If +path+ is a glob
|
441
|
+
# checks to see if all matches are directories.
|
442
|
+
#def dir?(path)
|
443
|
+
# paths = Dir.glob(path)
|
444
|
+
# paths.not_empty? && paths.all?{ |f| FileTest.directory?(f) }
|
445
|
+
#end
|
446
|
+
#alias_method :directory?, :dir? #; module_function :directory?
|
447
|
+
|
448
|
+
|
449
|
+
#############
|
450
|
+
# FileUtils #
|
451
|
+
#############
|
452
|
+
|
453
|
+
# Low-level Methods Omitted
|
454
|
+
# -------------------------
|
455
|
+
# getwd -> pwd
|
456
|
+
# compare_file -> cmp
|
457
|
+
# remove_file -> rm
|
458
|
+
# copy_file -> cp
|
459
|
+
# remove_dir -> rmdir
|
460
|
+
# safe_unlink -> rm_f
|
461
|
+
# makedirs -> mkdir_p
|
462
|
+
# rmtree -> rm_rf
|
463
|
+
# copy_stream
|
464
|
+
# remove_entry
|
465
|
+
# copy_entry
|
466
|
+
# remove_entry_secure
|
467
|
+
# compare_stream
|
468
|
+
|
469
|
+
# Present working directory.
|
470
|
+
def pwd
|
471
|
+
work.to_s
|
472
|
+
end
|
473
|
+
|
474
|
+
# Same as #identical?
|
475
|
+
def cmp(a,b)
|
476
|
+
fileutils.compare_file(a,b)
|
477
|
+
end
|
478
|
+
|
479
|
+
#
|
480
|
+
def mkdir(dir, options={})
|
481
|
+
dir = localize(dir)
|
482
|
+
fileutils.mkdir(dir, options)
|
483
|
+
end
|
484
|
+
|
485
|
+
def mkdir_p(dir, options={})
|
486
|
+
dir = localize(dir)
|
487
|
+
unless File.directory?(dir)
|
488
|
+
fileutils.mkdir_p(dir, options)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
alias_method :mkpath, :mkdir_p
|
492
|
+
|
493
|
+
def rmdir(dir, options={})
|
494
|
+
dir = localize(dir)
|
495
|
+
fileutils.rmdir(dir, options)
|
496
|
+
end
|
497
|
+
|
498
|
+
# ln(list, destdir, options={})
|
499
|
+
def ln(old, new, options={})
|
500
|
+
old = localize(old)
|
501
|
+
new = localize(new)
|
502
|
+
fileutils.ln(old, new, options)
|
503
|
+
end
|
504
|
+
alias_method :link, :ln
|
505
|
+
|
506
|
+
# ln_s(list, destdir, options={})
|
507
|
+
def ln_s(old, new, options={})
|
508
|
+
old = localize(old)
|
509
|
+
new = localize(new)
|
510
|
+
fileutils.ln_s(old, new, options)
|
511
|
+
end
|
512
|
+
alias_method :symlink, :ln_s
|
513
|
+
|
514
|
+
def ln_sf(old, new, options={})
|
515
|
+
old = localize(old)
|
516
|
+
new = localize(new)
|
517
|
+
fileutils.ln_sf(old, new, options)
|
518
|
+
end
|
519
|
+
|
520
|
+
# cp(list, dir, options={})
|
521
|
+
def cp(src, dest, options={})
|
522
|
+
src = localize(src)
|
523
|
+
dest = localize(dest)
|
524
|
+
fileutils.cp(src, dest, options)
|
525
|
+
end
|
526
|
+
alias_method :copy, :cp
|
527
|
+
|
528
|
+
# cp_r(list, dir, options={})
|
529
|
+
def cp_r(src, dest, options={})
|
530
|
+
src = localize(src)
|
531
|
+
dest = localize(dest)
|
532
|
+
fileutils.cp_r(src, dest, options)
|
533
|
+
end
|
534
|
+
|
535
|
+
# mv(list, dir, options={})
|
536
|
+
def mv(src, dest, options={})
|
537
|
+
src = localize(src)
|
538
|
+
dest = localize(dest)
|
539
|
+
fileutils.mv(src, dest, options)
|
540
|
+
end
|
541
|
+
alias_method :move, :mv
|
542
|
+
|
543
|
+
def rm(list, options={})
|
544
|
+
list = localize(list)
|
545
|
+
fileutils.rm(list, options)
|
546
|
+
end
|
547
|
+
alias_method :remove, :rm
|
548
|
+
|
549
|
+
def rm_r(list, options={})
|
550
|
+
list = localize(list)
|
551
|
+
fileutils.rm_r(list, options)
|
552
|
+
end
|
553
|
+
|
554
|
+
def rm_f(list, options={})
|
555
|
+
list = localize(list)
|
556
|
+
fileutils.rm_f(list, options)
|
557
|
+
end
|
558
|
+
|
559
|
+
def rm_rf(list, options={})
|
560
|
+
list = localize(list)
|
561
|
+
fileutils.rm_rf(list, options)
|
562
|
+
end
|
563
|
+
|
564
|
+
def install(src, dest, mode, options={})
|
565
|
+
src = localize(src)
|
566
|
+
dest = localize(dest)
|
567
|
+
fileutils.install(src, dest, mode, options)
|
568
|
+
end
|
569
|
+
|
570
|
+
def chmod(mode, list, options={})
|
571
|
+
list = localize(list)
|
572
|
+
fileutils.chmod(mode, list, options)
|
573
|
+
end
|
574
|
+
|
575
|
+
def chmod_r(mode, list, options={})
|
576
|
+
list = localize(list)
|
577
|
+
fileutils.chmod_r(mode, list, options)
|
578
|
+
end
|
579
|
+
#alias_method :chmod_R, :chmod_r
|
580
|
+
|
581
|
+
def chown(user, group, list, options={})
|
582
|
+
list = localize(list)
|
583
|
+
fileutils.chown(user, group, list, options)
|
584
|
+
end
|
585
|
+
|
586
|
+
def chown_r(user, group, list, options={})
|
587
|
+
list = localize(list)
|
588
|
+
fileutils.chown_r(user, group, list, options)
|
589
|
+
end
|
590
|
+
#alias_method :chown_R, :chown_r
|
591
|
+
|
592
|
+
def touch(list, options={})
|
593
|
+
list = localize(list)
|
594
|
+
fileutils.touch(list, options)
|
595
|
+
end
|
596
|
+
|
597
|
+
#
|
598
|
+
# TODO: should this have SOURCE diectory?
|
599
|
+
# stage(directory, source_dir, files)
|
600
|
+
#
|
601
|
+
def stage(stage_dir, files)
|
602
|
+
#dir = localize(directory)
|
603
|
+
#files = localize(files)
|
604
|
+
locally do
|
605
|
+
fileutils.stage(stage_dir, work, files)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
# An intergrated glob like method that takes a set of include globs,
|
610
|
+
# exclude globs and ignore globs to produce a collection of paths.
|
611
|
+
#
|
612
|
+
# Ignore_globs differ from exclude_globs in that they match by
|
613
|
+
# the basename of the path rather than the whole pathname.
|
614
|
+
#
|
615
|
+
def amass(include_globs, exclude_globs=[], ignore_globs=[])
|
616
|
+
locally do
|
617
|
+
fileutils.amass(include_globs, exclude_globs, ignore_globs)
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
#
|
622
|
+
def outofdate?(path, *sources)
|
623
|
+
#fileutils.outofdate?(localize(path), localize(sources)) # DIDN'T WORK, why?
|
624
|
+
locally do
|
625
|
+
fileutils.outofdate?(path, sources.flatten)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
# # Does a path need updating, based on given +sources+?
|
630
|
+
# # This compares mtimes of give paths. Returns false
|
631
|
+
# # if the path needs to be updated.
|
632
|
+
# #
|
633
|
+
# # TODO: Put this in FileTest instead?
|
634
|
+
#
|
635
|
+
# def out_of_date?(path, *sources)
|
636
|
+
# return true unless File.exist?(path)
|
637
|
+
#
|
638
|
+
# sources = sources.collect{ |source| Dir.glob(source) }.flatten
|
639
|
+
# mtimes = sources.collect{ |file| File.mtime(file) }
|
640
|
+
#
|
641
|
+
# return true if mtimes.empty? # TODO: This the way to go here?
|
642
|
+
#
|
643
|
+
# File.mtime(path) < mtimes.max
|
644
|
+
# end
|
645
|
+
|
646
|
+
#
|
647
|
+
def uptodate?(path, *sources)
|
648
|
+
locally do
|
649
|
+
fileutils.uptodate?(path, sources.flatten)
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
#
|
654
|
+
#def uptodate?(new, old_list, options=nil)
|
655
|
+
# new = localize(new)
|
656
|
+
# old = localize(old_list)
|
657
|
+
# fileutils.uptodate?(new, old, options)
|
658
|
+
#end
|
659
|
+
|
660
|
+
=begin
|
661
|
+
# TODO: Deprecate these?
|
662
|
+
|
663
|
+
# Assert that a path exists.
|
664
|
+
def exists!(*paths)
|
665
|
+
abort "path not found #{path}" unless paths.any?{|path| exists?(path)}
|
666
|
+
end
|
667
|
+
alias_method :exist!, :exists! #; module_function :exist!
|
668
|
+
alias_method :path!, :exists! #; module_function :path!
|
669
|
+
|
670
|
+
# Assert that a given path is a file.
|
671
|
+
def file!(*paths)
|
672
|
+
abort "file not found #{path}" unless paths.any?{|path| file?(path)}
|
673
|
+
end
|
674
|
+
|
675
|
+
# Assert that a given path is a directory.
|
676
|
+
def dir!(*paths)
|
677
|
+
paths.each do |path|
|
678
|
+
abort "Directory not found: '#{path}'." unless dir?(path)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
alias_method :directory!, :dir! #; module_function :directory!
|
682
|
+
=end
|
683
|
+
|
684
|
+
#private ?
|
685
|
+
|
686
|
+
# Returns a path local to the current working path.
|
687
|
+
def localize(local_path)
|
688
|
+
# some path arguments are optional
|
689
|
+
return local_path unless local_path
|
690
|
+
#
|
691
|
+
case local_path
|
692
|
+
when Array
|
693
|
+
local_path.collect do |lp|
|
694
|
+
if absolute?(lp)
|
695
|
+
lp
|
696
|
+
else
|
697
|
+
File.expand_path(File.join(work.to_s, lp))
|
698
|
+
end
|
699
|
+
end
|
700
|
+
else
|
701
|
+
# do not localize an absolute path
|
702
|
+
return local_path if absolute?(local_path)
|
703
|
+
File.expand_path(File.join(work.to_s, local_path))
|
704
|
+
#(work + local_path).expand_path.to_s
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
# Change directory to the shell's work directory,
|
709
|
+
# process the +block+ and then return to user directory.
|
710
|
+
def locally(&block)
|
711
|
+
if work.to_s == Dir.pwd
|
712
|
+
block.call
|
713
|
+
else
|
714
|
+
mutex.synchronize do
|
715
|
+
#work.chdir(&block)
|
716
|
+
Dir.chdir(work, &block)
|
717
|
+
end
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
# TODO: Should naming policy be in a utility extension module?
|
722
|
+
|
723
|
+
#
|
724
|
+
#
|
725
|
+
def naming_policy(*policies)
|
726
|
+
if policies.empty?
|
727
|
+
@naming_policy ||= ['down', 'ext']
|
728
|
+
else
|
729
|
+
@naming_policy = policies
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
#
|
734
|
+
#
|
735
|
+
def apply_naming_policy(name, ext)
|
736
|
+
naming_policy.each do |policy|
|
737
|
+
case policy.to_s
|
738
|
+
when /^low/, /^down/
|
739
|
+
name = name.downcase
|
740
|
+
when /^up/
|
741
|
+
name = name.upcase
|
742
|
+
when /^cap/
|
743
|
+
name = name.capitalize
|
744
|
+
when /^ext/
|
745
|
+
name = name + ".#{ext}"
|
746
|
+
end
|
747
|
+
end
|
748
|
+
name
|
749
|
+
end
|
750
|
+
|
751
|
+
private
|
752
|
+
|
753
|
+
# Returns FileUtils module based on mode.
|
754
|
+
def fileutils
|
755
|
+
if dryrun?
|
756
|
+
::FileUtils::DryRun
|
757
|
+
elsif noop?
|
758
|
+
::FileUtils::Noop
|
759
|
+
elsif trace?
|
760
|
+
::FileUtils::Verbose
|
761
|
+
else
|
762
|
+
::FileUtils
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
# This may be used by script commands to allow for per command
|
767
|
+
# noop and trace options. Global options have precedence.
|
768
|
+
def util_options(options)
|
769
|
+
noop = noop? || options[:noop] || options[:dryrun]
|
770
|
+
trace = trace? || options[:trace] || options[:dryrun]
|
771
|
+
return noop, trace
|
772
|
+
end
|
773
|
+
|
774
|
+
public#class
|
775
|
+
|
776
|
+
def self.[](path)
|
777
|
+
new(path)
|
778
|
+
end
|
779
|
+
|
780
|
+
end
|
781
|
+
|
782
|
+
end
|
783
|
+
|