angry_mob_common_targets 0.1.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.
Files changed (104) hide show
  1. data/LICENSE +21 -0
  2. data/README.md +38 -0
  3. data/lib/common_mob.rb +9 -0
  4. data/lib/common_mob/digest.rb +43 -0
  5. data/lib/common_mob/erb.rb +72 -0
  6. data/lib/common_mob/file.rb +55 -0
  7. data/lib/common_mob/patch.rb +51 -0
  8. data/lib/common_mob/resource_locator.rb +9 -0
  9. data/lib/common_mob/shell.rb +323 -0
  10. data/lib/common_mob/template.rb +23 -0
  11. data/lib/common_mob/version.rb +3 -0
  12. data/targets/crontab_patch.rb +37 -0
  13. data/targets/extract.rb +40 -0
  14. data/targets/fetch.rb +40 -0
  15. data/targets/files.rb +244 -0
  16. data/targets/git.rb +84 -0
  17. data/targets/group.rb +33 -0
  18. data/targets/packages.rb +94 -0
  19. data/targets/ruby.rb +13 -0
  20. data/targets/services.rb +184 -0
  21. data/targets/shell.rb +43 -0
  22. data/targets/user.rb +108 -0
  23. data/vendor/mustache/CONTRIBUTORS +9 -0
  24. data/vendor/mustache/HISTORY.md +135 -0
  25. data/vendor/mustache/LICENSE +20 -0
  26. data/vendor/mustache/README.md +405 -0
  27. data/vendor/mustache/Rakefile +103 -0
  28. data/vendor/mustache/benchmarks/complex.erb +15 -0
  29. data/vendor/mustache/benchmarks/complex.haml +12 -0
  30. data/vendor/mustache/benchmarks/helper.rb +20 -0
  31. data/vendor/mustache/benchmarks/simple.erb +5 -0
  32. data/vendor/mustache/benchmarks/speed.rb +78 -0
  33. data/vendor/mustache/bin/mustache +90 -0
  34. data/vendor/mustache/contrib/mustache-mode.el +278 -0
  35. data/vendor/mustache/contrib/mustache.vim +69 -0
  36. data/vendor/mustache/examples/hash.rb +16 -0
  37. data/vendor/mustache/examples/hash.yml +5 -0
  38. data/vendor/mustache/examples/projects.mustache +26 -0
  39. data/vendor/mustache/examples/projects.yml +28 -0
  40. data/vendor/mustache/examples/self.mustache +4 -0
  41. data/vendor/mustache/examples/self.yml +3 -0
  42. data/vendor/mustache/examples/simple.mustache +10 -0
  43. data/vendor/mustache/examples/simple.rb +24 -0
  44. data/vendor/mustache/lib/mustache.rb +358 -0
  45. data/vendor/mustache/lib/mustache/context.rb +108 -0
  46. data/vendor/mustache/lib/mustache/generator.rb +160 -0
  47. data/vendor/mustache/lib/mustache/parser.rb +230 -0
  48. data/vendor/mustache/lib/mustache/sinatra.rb +180 -0
  49. data/vendor/mustache/lib/mustache/template.rb +59 -0
  50. data/vendor/mustache/lib/mustache/version.rb +3 -0
  51. data/vendor/mustache/lib/rack/bug/panels/mustache_panel.rb +81 -0
  52. data/vendor/mustache/lib/rack/bug/panels/mustache_panel/mustache_extension.rb +27 -0
  53. data/vendor/mustache/lib/rack/bug/panels/mustache_panel/view.mustache +46 -0
  54. data/vendor/mustache/man/mustache.1 +180 -0
  55. data/vendor/mustache/man/mustache.1.html +204 -0
  56. data/vendor/mustache/man/mustache.1.ron +127 -0
  57. data/vendor/mustache/man/mustache.5 +576 -0
  58. data/vendor/mustache/man/mustache.5.html +415 -0
  59. data/vendor/mustache/man/mustache.5.ron +324 -0
  60. data/vendor/mustache/mustache.gemspec +32 -0
  61. data/vendor/mustache/test/autoloading_test.rb +52 -0
  62. data/vendor/mustache/test/fixtures/comments.mustache +1 -0
  63. data/vendor/mustache/test/fixtures/comments.rb +14 -0
  64. data/vendor/mustache/test/fixtures/complex_view.mustache +17 -0
  65. data/vendor/mustache/test/fixtures/complex_view.rb +34 -0
  66. data/vendor/mustache/test/fixtures/crazy_recursive.mustache +9 -0
  67. data/vendor/mustache/test/fixtures/crazy_recursive.rb +31 -0
  68. data/vendor/mustache/test/fixtures/delimiters.mustache +8 -0
  69. data/vendor/mustache/test/fixtures/delimiters.rb +23 -0
  70. data/vendor/mustache/test/fixtures/double_section.mustache +7 -0
  71. data/vendor/mustache/test/fixtures/double_section.rb +14 -0
  72. data/vendor/mustache/test/fixtures/escaped.mustache +1 -0
  73. data/vendor/mustache/test/fixtures/escaped.rb +14 -0
  74. data/vendor/mustache/test/fixtures/inner_partial.mustache +1 -0
  75. data/vendor/mustache/test/fixtures/inner_partial.txt +1 -0
  76. data/vendor/mustache/test/fixtures/inverted_section.mustache +7 -0
  77. data/vendor/mustache/test/fixtures/inverted_section.rb +14 -0
  78. data/vendor/mustache/test/fixtures/lambda.mustache +7 -0
  79. data/vendor/mustache/test/fixtures/lambda.rb +31 -0
  80. data/vendor/mustache/test/fixtures/namespaced.mustache +1 -0
  81. data/vendor/mustache/test/fixtures/namespaced.rb +25 -0
  82. data/vendor/mustache/test/fixtures/nested_objects.mustache +17 -0
  83. data/vendor/mustache/test/fixtures/nested_objects.rb +35 -0
  84. data/vendor/mustache/test/fixtures/node.mustache +8 -0
  85. data/vendor/mustache/test/fixtures/partial_with_module.mustache +3 -0
  86. data/vendor/mustache/test/fixtures/partial_with_module.rb +37 -0
  87. data/vendor/mustache/test/fixtures/passenger.conf +5 -0
  88. data/vendor/mustache/test/fixtures/passenger.rb +27 -0
  89. data/vendor/mustache/test/fixtures/recursive.mustache +4 -0
  90. data/vendor/mustache/test/fixtures/recursive.rb +14 -0
  91. data/vendor/mustache/test/fixtures/simple.mustache +5 -0
  92. data/vendor/mustache/test/fixtures/simple.rb +26 -0
  93. data/vendor/mustache/test/fixtures/template_partial.mustache +2 -0
  94. data/vendor/mustache/test/fixtures/template_partial.rb +18 -0
  95. data/vendor/mustache/test/fixtures/template_partial.txt +4 -0
  96. data/vendor/mustache/test/fixtures/unescaped.mustache +1 -0
  97. data/vendor/mustache/test/fixtures/unescaped.rb +14 -0
  98. data/vendor/mustache/test/fixtures/utf8.mustache +3 -0
  99. data/vendor/mustache/test/fixtures/utf8_partial.mustache +1 -0
  100. data/vendor/mustache/test/helper.rb +7 -0
  101. data/vendor/mustache/test/mustache_test.rb +536 -0
  102. data/vendor/mustache/test/parser_test.rb +54 -0
  103. data/vendor/mustache/test/partial_test.rb +168 -0
  104. metadata +167 -0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 PLUS2 Pty. Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,38 @@
1
+ # AngryMob Common Targets
2
+
3
+ AngryMob Common Targets are a set of essential, reusable targets to get you started with AngryMob.
4
+
5
+ ## file targets
6
+
7
+ ### file
8
+
9
+ ### template
10
+
11
+ ### symlink
12
+
13
+ ### patch
14
+
15
+ ### tarball
16
+
17
+ ### fetch
18
+
19
+ ### git
20
+
21
+ ## package targets
22
+
23
+ ### apt
24
+
25
+ ### gem
26
+
27
+ ## os targets
28
+
29
+ ### service
30
+
31
+ ### sh
32
+
33
+ ### user
34
+
35
+ ### group
36
+
37
+ ### crontab
38
+
@@ -0,0 +1,9 @@
1
+ require 'pathname'
2
+ root = Pathname('../../').expand_path(__FILE__)
3
+ $LOAD_PATH << root+'vendor/mustache/lib'
4
+
5
+ require 'common_mob/file'
6
+ require 'common_mob/patch'
7
+ require 'common_mob/digest'
8
+ require 'common_mob/template'
9
+ require 'common_mob/shell'
@@ -0,0 +1,43 @@
1
+ require 'pathname'
2
+ require 'digest/sha1'
3
+ require 'digest/sha2'
4
+
5
+ module CommonMob
6
+ module DigestHelper
7
+ def sha(string_or_io)
8
+ digest_with Digest::SHA1.new, string_or_io
9
+ end
10
+ def sha512(string_or_io)
11
+ digest_with Digest::SHA512.new, string_or_io
12
+ end
13
+ def sha256(string_or_io)
14
+ digest_with Digest::SHA256.new, string_or_io
15
+ end
16
+
17
+ def digest_with(digest,string_or_io)
18
+ if Pathname === string_or_io
19
+ if string_or_io.exist?
20
+ digest << string_or_io.read
21
+ else
22
+ return nil
23
+ end
24
+
25
+ elsif string_or_io.nil?
26
+ return nil
27
+ elsif string_or_io.respond_to?(:read)
28
+ digest << string_or_io.read
29
+ else
30
+ digest << string_or_io
31
+ end
32
+
33
+ digest.hexdigest
34
+ end
35
+
36
+ def generate_htpasswd(password)
37
+ pool = [ 'A'..'Z', 'a'..'z', '0'..'9' ].map {|r| r.to_a}.flatten
38
+ salt = [1,2].map {|_| pool[rand(pool.size)] }.join
39
+
40
+ password.to_s.crypt(salt)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,72 @@
1
+ require 'erb'
2
+
3
+ module CommonMob
4
+ module Erb
5
+ class Context
6
+ def initialize(hash)
7
+ hash.each do |k,v|
8
+ instance_variable_set("@#{k}", convert_value(v))
9
+ end
10
+ end
11
+
12
+ def convert_value(value)
13
+ case value
14
+ when AngryHash
15
+ value
16
+ when Hash
17
+ AngryHash[value]
18
+ else
19
+ value
20
+ end
21
+ end
22
+
23
+ def get_binding
24
+ binding
25
+ end
26
+ end
27
+
28
+ class ErbError < StandardError
29
+ def initialize(file,ex)
30
+ @file = file
31
+ @src = file.read
32
+ @ex = ex
33
+ end
34
+
35
+ def message
36
+ "error rendering erb: #{@ex.message}\nat line #{line_number+1} of #{@file}\n#{error_with_context * "\n"}"
37
+ end
38
+ def to_s
39
+ message
40
+ end
41
+
42
+ def error_with_context(ctx=2)
43
+ start_on_line = [ line_number - ctx - 1, 0 ].max
44
+ end_on_line = [ line_number + ctx , @src.length].min
45
+
46
+ @src.split("\n")[ start_on_line..end_on_line ]
47
+ end
48
+
49
+ def line_number
50
+ @line_number = line_number!
51
+ end
52
+ def line_number!
53
+ re = /\(erb\):(\d+):in `get_binding'/
54
+ @line_number = $1.to_i if @ex.backtrace.find {|line| line =~ re}
55
+ end
56
+
57
+ def method_missing(meth,*args,&block)
58
+ @ex.send(meth,*args,&block)
59
+ end
60
+ end
61
+
62
+ def render_erb(src,context)
63
+ e = ERB.new(src.read)
64
+ e.result( Context.new(context || {}).get_binding )
65
+ rescue Object
66
+ raise ErbError.new(src, $!)
67
+ #log "error rendering erb: [#{$!.class}] #{$!}"
68
+ #log e.src
69
+ #raise $!
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,55 @@
1
+ require 'pathname'
2
+ require 'etc'
3
+
4
+ module CommonMob
5
+ module FileHelper
6
+ def backup_file(f)
7
+ return if FalseClass === args.backup || args.backup == 0
8
+
9
+ backups = args.backups || 5
10
+ backups = backups.to_i
11
+
12
+ root = f.dirname
13
+ backedup = f.basename.to_s + ".AM-#{Time.now.to_i}"
14
+ backup = root+backedup
15
+
16
+ log "backing #{f} up to #{backup}"
17
+
18
+ FileUtils.cp f, (backup)
19
+
20
+ if backups > 0
21
+ existing_backups = Pathname.glob( root + "#{f.basename}.AM-*" ).sort_by {|f| f.ctime}.reverse
22
+ if existing_backups.size > backups
23
+ log "deleting #{existing_backups.size - backups} old backups (keeping #{backups})"
24
+ existing_backups[backups..-1].each {|to_del| to_del.unlink}
25
+ end
26
+ end
27
+
28
+ backup
29
+ rescue Errno::ENOENT
30
+ # *ulp*
31
+ end
32
+
33
+ def set_file_attrs(file,owner,group,mode)
34
+ file = Pathname(file)
35
+
36
+ unless owner.blank? && group.blank?
37
+ owner ||= file.stat.uid
38
+ group ||= file.stat.gid
39
+
40
+ owner = Etc.getpwnam(owner.to_s).uid unless Integer === owner
41
+ group = Etc.getgrnam(group.to_s).gid unless Integer === group
42
+
43
+ log "setting #{file} to owner=#{owner} group=#{group}"
44
+
45
+ file.chown(owner,group)
46
+ end
47
+
48
+ unless mode.blank?
49
+ log "setting #{file} to mode=0%o" % mode
50
+ file.chmod(mode)
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ module CommonMob
2
+ module PatchHelper
3
+ def patch_marker_re(comment,key,switch)
4
+ %[
5
+ #{Regexp.escape(comment.to_s)}
6
+ \\s+
7
+ angry-mob
8
+ \\s+
9
+ #{Regexp.escape(key.to_s)}
10
+ \\s+
11
+ #{Regexp.escape(switch.to_s)}
12
+ $
13
+ ]
14
+ end
15
+
16
+ def patch_string(to_patch, src, options={})
17
+ comment = options[:comment] || '#'
18
+ key = options[:key]
19
+
20
+ content = "#{comment} angry-mob #{key} start\n#{src}" \
21
+ "\n#{comment} angry-mob #{key} end"
22
+
23
+ pattern = %r[
24
+ #{patch_marker_re(comment,key,'start')}
25
+ .*
26
+ #{patch_marker_re(comment,key,'end')}
27
+ ]mx
28
+
29
+ if to_patch[pattern]
30
+ to_patch.gsub(pattern,content)
31
+ else
32
+ to_patch + "\n" + content
33
+ end
34
+ end
35
+
36
+ def patch_file(to_patch)
37
+ log "what the very hell?"
38
+ if args.src?
39
+ src = args.src.pathname.read
40
+ elsif args.string?
41
+ src = args.string
42
+ end
43
+
44
+ patch_string(to_patch.read, src, {
45
+ :comment => args.comment,
46
+ :key => args.key,
47
+ })
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ module CommonMob
2
+ class ResourceLocator
3
+ def resource(target,name)
4
+ path = target.act.definition_file.to_s.sub(/\.([^\.]+)$/,'')
5
+ (path.pathname + name.to_s)
6
+ end
7
+ alias_method :[], :resource
8
+ end
9
+ end
@@ -0,0 +1,323 @@
1
+ require 'fcntl'
2
+ require 'stringio'
3
+
4
+ module CommonMob
5
+ class ShellError < StandardError
6
+ attr_accessor :result
7
+ end
8
+
9
+ module ShellHelper
10
+ def sh(*args,&blk)
11
+ CommonMob::Shell.new(*args,&blk)
12
+ end
13
+
14
+ # call ruby, or a ruby command with the environment cleaned of bundler spooge
15
+ def bundler_sh(*args,&blk)
16
+ args.options.without_cleaning_bundler = true
17
+ CommonMob::Shell.new(*args,&blk)
18
+ end
19
+ end
20
+
21
+ class Shell
22
+ def debug(*msg)
23
+ AngryMob::Mob.ui.debug "sh: #{msg * ' '}"
24
+ end
25
+
26
+ attr_reader :options
27
+
28
+ def initialize(*args,&block)
29
+ @block = block
30
+ @options = if Hash === args.last then args.pop else {} end
31
+
32
+ unless args.empty?
33
+ @options[:cmd] = args
34
+ end
35
+
36
+ @options[:stream] = false unless @options.key?(:stream)
37
+ end
38
+
39
+ def execute
40
+ error,out = nil,nil
41
+
42
+ rv = popen4(options) {|pid,stdin,stdout,stderr|
43
+ out = stdout.read
44
+ error = stderr.read
45
+ }
46
+
47
+ # TODO print as debug
48
+
49
+ rv.stderr = error
50
+ rv.stdout = out
51
+
52
+ rv
53
+ end
54
+
55
+ def run
56
+ execute.ensure_ok!
57
+ end
58
+
59
+ def ok?
60
+ execute.ok?
61
+ end
62
+
63
+ def to_s
64
+ result = execute
65
+ if result.ok?
66
+ result.stdout.chomp
67
+ else
68
+ ''
69
+ end
70
+ end
71
+
72
+ class ShellResult < Struct.new(:process_result, :options, :stderr, :stdout)
73
+ def ok?
74
+ process_result.success?
75
+ end
76
+
77
+ def ensure_ok!
78
+ unless ok?
79
+ ex = ShellError.new("unable to run options=#{options.inspect}\noutput=#{stdout}\nerror=#{stderr}")
80
+ ex.result = self
81
+ raise(ex)
82
+ end
83
+ end
84
+ end
85
+
86
+ # This is taken directly from Chef and then modified to suit the needs of Igor.
87
+ #
88
+ # This is taken directly from Ara T Howard's Open4 library, and then
89
+ # modified to suit the needs of Chef. Any bugs here are most likely
90
+ # my own, and not Ara's.
91
+ #
92
+ # The original appears in external/open4.rb in its unmodified form.
93
+ #
94
+ # Thanks Ara!
95
+ def popen4(args={}, &b)
96
+
97
+ cmd = args[:cmd]
98
+
99
+
100
+ # Do we wait for the child process to die before we yield
101
+ # to the block, or after?
102
+ #
103
+ # By default, we are waiting before we yield the block.
104
+ args[:stream] ||= false
105
+
106
+
107
+ args[:user] ||= nil
108
+ unless args[:user].kind_of?(Integer)
109
+ args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
110
+ end
111
+
112
+ args[:group] ||= nil
113
+ unless args[:group].kind_of?(Integer)
114
+ args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
115
+ end
116
+
117
+
118
+ args[:environment] ||= {}
119
+
120
+ # Default on C locale so parsing commands output can be done
121
+ # independently of the node's default locale.
122
+ # "LC_ALL" could be set to nil, in which case we also must ignore it.
123
+ unless args[:environment].has_key?("LC_ALL")
124
+ args[:environment]["LC_ALL"] = "C"
125
+ end
126
+
127
+ unless TrueClass === args[:without_cleaning_bundler]
128
+ args[:environment].update('RUBYOPT' => nil, 'BUNDLE_GEMFILE' => nil, 'GEM_HOME' => nil, 'GEM_PATH' => nil)
129
+ end
130
+
131
+ if user = args[:as]
132
+ if (evars = args[:environment].reject {|k,v| v.nil?}.map {|k,v| "#{k}=#{v}"}) && !evars.empty?
133
+ env = "env #{evars.join(' ')}"
134
+ else
135
+ env = ''
136
+ end
137
+
138
+ cmd = "sudo -H -u #{user} #{env} #{cmd}"
139
+ end
140
+
141
+ # debug "running #{cmd} #{massaged_args(args).inspect}"
142
+
143
+ pwrite, pread, perror, pexception = IO.pipe, IO.pipe, IO.pipe, IO.pipe
144
+
145
+ verbose = $VERBOSE
146
+ begin
147
+ $VERBOSE = nil
148
+ pexception.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
149
+
150
+ cid = fork {
151
+ pwrite.last.close
152
+ STDIN.reopen pwrite.first
153
+ pwrite.first.close
154
+
155
+ pread.first.close
156
+ STDOUT.reopen pread.last
157
+ pread.last.close
158
+
159
+ perror.first.close
160
+ STDERR.reopen perror.last
161
+ perror.last.close
162
+
163
+ STDOUT.sync = STDERR.sync = true
164
+
165
+ if args[:group]
166
+ Process.egid = args[:group]
167
+ Process.gid = args[:group]
168
+ end
169
+
170
+ if args[:user]
171
+ Process.euid = args[:user]
172
+ Process.uid = args[:user]
173
+ end
174
+
175
+ # Copy the specified environment across to the child's environment.
176
+ # Keys with `nil` values are deleted from the environment.
177
+ args[:environment].each do |key,value|
178
+ if value.nil?
179
+ ENV.delete(key.to_s)
180
+ else
181
+ ENV[key.to_s] = value
182
+ end
183
+ end
184
+
185
+ if args[:umask]
186
+ umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
187
+ File.umask(umask)
188
+ end
189
+
190
+ if args[:cwd]
191
+ Dir.chdir args[:cwd]
192
+ end
193
+
194
+ begin
195
+ if cmd.kind_of?(Array)
196
+ exec(*cmd)
197
+ else
198
+ exec(cmd)
199
+ end
200
+ raise 'forty-two'
201
+ rescue Exception => e
202
+ Marshal.dump(e, pexception.last)
203
+ pexception.last.flush
204
+ end
205
+ pexception.last.close unless (pexception.last.closed?)
206
+ exit!
207
+ }
208
+ ensure
209
+ $VERBOSE = verbose
210
+ end
211
+
212
+ [pwrite.first, pread.last, perror.last, pexception.last].each{|fd| fd.close}
213
+
214
+ begin
215
+ e = Marshal.load pexception.first
216
+ raise(Exception === e ? e : "unknown failure!")
217
+ rescue EOFError # If we get an EOF error, then the exec was successful
218
+ 42
219
+ ensure
220
+ pexception.first.close
221
+ end
222
+
223
+ pwrite.last.sync = true
224
+
225
+ pi = [pwrite.last, pread.first, perror.first]
226
+
227
+ if b
228
+ begin
229
+ if args[:stream]
230
+ b[cid, *pi]
231
+ ShellResult.new(Process.waitpid2(cid).last, args)
232
+ else
233
+ o = StringIO.new
234
+ e = StringIO.new
235
+
236
+ if args[:input]
237
+ pi[0].puts args[:input]
238
+ end
239
+
240
+ pi[0].close
241
+
242
+ stdout = pi[1]
243
+ stderr = pi[2]
244
+
245
+ stdout.sync = true
246
+ stderr.sync = true
247
+
248
+ stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
249
+ stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
250
+
251
+ stdout_finished = false
252
+ stderr_finished = false
253
+
254
+ results = nil
255
+
256
+ while !stdout_finished || !stderr_finished
257
+ begin
258
+ channels_to_watch = []
259
+ channels_to_watch << stdout if !stdout_finished
260
+ channels_to_watch << stderr if !stderr_finished
261
+ ready = IO.select(channels_to_watch, nil, nil, 1.0)
262
+ rescue Errno::EAGAIN
263
+ results = Process.waitpid2(cid, Process::WNOHANG)
264
+ if results
265
+ stdout_finished = true
266
+ stderr_finished = true
267
+ end
268
+ end
269
+
270
+ if ready && ready.first.include?(stdout)
271
+ line = results ? stdout.gets(nil) : stdout.gets
272
+ if line
273
+ o.write(line)
274
+ else
275
+ stdout_finished = true
276
+ end
277
+ end
278
+ if ready && ready.first.include?(stderr)
279
+ line = results ? stderr.gets(nil) : stderr.gets
280
+ if line
281
+ e.write(line)
282
+ else
283
+ stderr_finished = true
284
+ end
285
+ end
286
+ end
287
+ results = Process.waitpid2(cid).last unless results
288
+ o.rewind
289
+ e.rewind
290
+ b[cid, pi[0], o, e]
291
+
292
+ ShellResult.new(results, args)
293
+ end
294
+ ensure
295
+ pi.each{|fd| fd.close unless fd.closed?}
296
+ end
297
+ else
298
+ [cid, pw.last, pr.first, pe.first]
299
+ end
300
+ end
301
+
302
+ def massaged_args args
303
+ returning(args.dup) do |args_to_print|
304
+ args_to_print[:environment] = e = args[:environment].dup
305
+
306
+ %w{LC_ALL GEM_HOME GEM_PATH RUBYOPT BUNDLE_GEMFILE}.each {|env| e.delete(env)}
307
+
308
+ args_to_print['cwd'] = args_to_print['cwd'].to_s if args_to_print['cwd']
309
+
310
+ args_to_print.delete_if{|k,v| v.blank?}
311
+ end
312
+ end
313
+
314
+ end
315
+ end
316
+
317
+ class String
318
+ def sh(options={})
319
+ CommonMob::Shell.new(self,options)
320
+ end
321
+ end
322
+
323
+