angry_mob_common_targets 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+