plist4r 0.1.0 → 0.1.1
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/README.rdoc +30 -4
- data/VERSION +1 -1
- data/lib/plist4r.rb +7 -6
- data/lib/plist4r/backend.rb +47 -36
- data/lib/plist4r/backend/example.rb +1 -1
- data/lib/plist4r/backend/haml.rb +3 -3
- data/lib/plist4r/backend/libxml4r.rb +4 -4
- data/lib/plist4r/backend/plutil.rb +2 -0
- data/lib/plist4r/backend/ruby_cocoa.rb +3 -3
- data/lib/plist4r/backend_base.rb +3 -0
- data/lib/plist4r/config.rb +15 -12
- data/lib/plist4r/mixin/data_methods.rb +49 -47
- data/lib/plist4r/mixin/popen4.rb +167 -161
- data/lib/plist4r/mixin/ruby_stdlib.rb +2 -0
- data/lib/plist4r/plist.rb +151 -144
- data/lib/plist4r/plist_cache.rb +54 -50
- data/lib/plist4r/plist_type.rb +44 -43
- data/lib/plist4r/plist_type/info.rb +7 -0
- data/lib/plist4r/plist_type/launchd.rb +0 -2
- data/lib/plist4r/plist_type/plist.rb +7 -0
- data/plist4r.gemspec +12 -3
- data/plists/Picture of Today.plist +75 -0
- data/plists/com.adobe.PDFAdminSettings.plist +0 -0
- data/plists/com.apple.SoftwareUpdate.plist +12 -0
- data/plists/foofoo.xml +53 -0
- data/plists/test.plist +0 -0
- data/spec/plist4r/plist_spec.rb +42 -0
- data/spec/plist4r_spec.rb +56 -3
- data/spec/spec.opts +2 -0
- data/test.rb +14 -0
- metadata +11 -2
data/README.rdoc
CHANGED
@@ -10,6 +10,8 @@ Current project status: in alpha
|
|
10
10
|
|
11
11
|
== Quick Start
|
12
12
|
|
13
|
+
require 'plist4r'
|
14
|
+
|
13
15
|
Plist4r::Config.default_dir "/Library/LaunchDaemons"
|
14
16
|
filename = "com.github.myservice.plist"
|
15
17
|
p = Plist4r.open(filename)
|
@@ -65,20 +67,20 @@ We believe thats allright for most uses, and decided to include `next_step` for
|
|
65
67
|
module ::Plist4r::Backend::MyPlistReaderWriter
|
66
68
|
# implement some plist4r api calls here
|
67
69
|
end
|
68
|
-
|
70
|
+
|
69
71
|
# append my backend to the end of the list
|
70
72
|
Plist4r::Config[:backends] << :my_plist_reader_writer
|
71
73
|
|
72
74
|
# or to the front of the list (executes first)
|
73
75
|
Plist4r::Config[:backends].insert 0 :my_plist_reader_writer
|
74
|
-
|
76
|
+
|
75
77
|
# The default directory to load / save files from
|
76
78
|
Plist4r::Config.default_path "/Library/Cars"
|
77
79
|
|
78
80
|
car = Plist4r.new("car.plist")
|
79
81
|
|
80
82
|
car.load
|
81
|
-
|
83
|
+
|
82
84
|
car.file_format :binary
|
83
85
|
# car.plist_type :car # not implemented *yet*
|
84
86
|
|
@@ -121,8 +123,32 @@ Plist4r is currently alpha - quality software. Yet to be completed...
|
|
121
123
|
* Fork the project, and create a topic branch as per {these instructions}[http://wiki.opscode.com/display/opscode/Working+with+Git].
|
122
124
|
* Make your feature addition or bug fix.
|
123
125
|
* Include documentation for it.
|
124
|
-
* Include a regression test for it. So I
|
126
|
+
* Include a regression test for it. So I dont break it in a future version unintentionally.
|
127
|
+
|
128
|
+
== Contributors
|
129
|
+
|
130
|
+
Popen4
|
131
|
+
* Ara T Howard
|
132
|
+
|
133
|
+
ActiveSupport::OrderedHash
|
134
|
+
* Copyright (c) 2005 David Hansson,
|
135
|
+
* Copyright (c) 2007 Mauricio Fernandez, Sam Stephenson
|
136
|
+
* Copyright (c) 2008 Steve Purcell, Josh Peek
|
137
|
+
* Copyright (c) 2009 Christoffer Sawicki
|
138
|
+
|
139
|
+
Mixlib::Config
|
140
|
+
* Author:: Adam Jacob
|
141
|
+
* Author:: Nuo Yan
|
142
|
+
* Author:: Christopher Brown
|
143
|
+
* Copyright:: Copyright (c) 2008 Opscode, Inc.
|
144
|
+
|
145
|
+
Backends...
|
146
|
+
|
147
|
+
Haml, Libxml4r, RubyCocoa
|
148
|
+
* Dreamcat4
|
125
149
|
|
126
150
|
== Copyright
|
127
151
|
|
128
152
|
Copyright (c) 2010 Dreamcat4. See LICENSE for details.
|
153
|
+
|
154
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/plist4r.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
2
|
+
dir = File.dirname(__FILE__)
|
3
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
4
4
|
|
5
5
|
require 'plist4r/plist'
|
6
6
|
|
7
7
|
module Plist4r
|
8
8
|
class << self
|
9
9
|
def new *args, &blk
|
10
|
-
puts args.inspect
|
10
|
+
# puts args.inspect
|
11
11
|
return Plist.new *args, &blk
|
12
12
|
end
|
13
13
|
|
14
14
|
def open filename, *args, &blk
|
15
|
-
puts args.inspect
|
16
|
-
|
15
|
+
# puts args.inspect
|
16
|
+
p = Plist.new filename, *args, &blk
|
17
|
+
p.open
|
17
18
|
end
|
18
19
|
|
19
20
|
def string_detect_format string
|
20
|
-
s.strip!
|
21
|
+
s = string.strip!
|
21
22
|
case s[0,1]
|
22
23
|
when "{","("
|
23
24
|
:next_step
|
data/lib/plist4r/backend.rb
CHANGED
@@ -1,52 +1,63 @@
|
|
1
1
|
|
2
2
|
require 'plist4r/config'
|
3
|
+
require 'plist4r/backend_base'
|
3
4
|
require 'plist4r/mixin/ordered_hash'
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
module Plist4r
|
7
|
+
class Backend
|
8
|
+
ApiMethods = %w[from_string to_xml to_binary to_next_step open save]
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
def initialize plist, *args, &blk
|
11
|
+
@plist = plist
|
12
|
+
@backends = plist.backends.collect do |b|
|
13
|
+
case b
|
14
|
+
when Module
|
15
|
+
b
|
16
|
+
when Symbol, String
|
17
|
+
eval "::Plist4r::Backend::#{b.to_s.camelcase}"
|
18
|
+
else
|
19
|
+
raise "Backend #{b.inspect} is of unsupported type: #{b.class}"
|
20
|
+
end
|
18
21
|
end
|
19
22
|
end
|
20
|
-
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
# vv We also need a version of :call for matrix test harness vv
|
25
|
+
|
26
|
+
def call method_sym, *args, &blk
|
27
|
+
puts "in call"
|
28
|
+
puts "#{method_sym.inspect} #{args.inspect}"
|
29
|
+
raise "Unsupported api call #{method_sym.inspect}" unless ApiMethods.include? method_sym.to_s
|
30
|
+
exceptions = []
|
31
|
+
@backends.each do |backend|
|
32
|
+
if backend.respond_to? method_sym
|
33
|
+
begin
|
34
|
+
result = backend.send(method_sym, @plist, *args, &blk)
|
35
|
+
# puts "result = #{result.inspect}"
|
36
|
+
return result
|
37
|
+
# return backend.send(method_sym, @plist, *args, &blk)
|
38
|
+
rescue LoadError
|
39
|
+
exceptions << $!
|
40
|
+
rescue
|
41
|
+
exceptions << $!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if Config[:raise_any_failure] && exceptions.first
|
45
|
+
raise exceptions.first
|
33
46
|
end
|
34
47
|
end
|
35
|
-
if
|
36
|
-
raise
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
48
|
+
if exceptions.empty?
|
49
|
+
raise "Plist4r: No backend found to handle method #{method_sym.inspect}. Could not execute method #{method_sym.inspect} on plist #{@plist.inspect}"
|
50
|
+
else
|
51
|
+
# $stderr.puts "Failure(s) while executing method #{method_sym.inspect} on plist #{@plist}."
|
52
|
+
exceptions.each do |e|
|
53
|
+
$stderr.puts e.inspect
|
54
|
+
$stderr.puts e.backtrace.collect { |l| "\tfrom #{l}"}.join "\n"
|
55
|
+
end
|
56
|
+
# raise exceptions.first
|
57
|
+
raise "Failure(s) while executing method #{method_sym.inspect} on plist #{@plist}."
|
45
58
|
end
|
46
|
-
raise exceptions.first
|
47
59
|
end
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
51
63
|
|
52
|
-
|
data/lib/plist4r/backend/haml.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
|
-
require 'plist4r/
|
2
|
+
require 'plist4r/backend_base'
|
3
3
|
|
4
|
-
module Plist4r::Backend::
|
4
|
+
module Plist4r::Backend::Haml
|
5
5
|
class << self
|
6
6
|
def to_xml_haml
|
7
7
|
@to_xml_haml ||= <<-'EOC'
|
@@ -66,7 +66,7 @@ EOC
|
|
66
66
|
hash = plist.to_hash
|
67
67
|
filename = plist.filename_path
|
68
68
|
File.open(filename,'w') do |out|
|
69
|
-
out << to_xml
|
69
|
+
out << to_xml(plist)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
|
-
require 'plist4r/
|
2
|
+
require 'plist4r/backend_base'
|
3
3
|
|
4
|
-
module Plist4r::Backend::
|
4
|
+
module Plist4r::Backend::Libxml4r
|
5
5
|
class << self
|
6
6
|
def tree_hash n
|
7
7
|
hash = ::ActiveSupport::OrderedHash.new
|
@@ -49,6 +49,7 @@ module Plist4r::Backend::Libxml4rXmlReader
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def parse_plist_xml string
|
52
|
+
require 'rubygems'
|
52
53
|
require 'libxml4r'
|
53
54
|
::LibXML::XML.default_keep_blanks = false
|
54
55
|
doc = string.to_xmldoc
|
@@ -60,10 +61,9 @@ module Plist4r::Backend::Libxml4rXmlReader
|
|
60
61
|
def from_string plist, string
|
61
62
|
plist_format = Plist4r.string_detect_format string
|
62
63
|
raise "#{self} - cant convert string of format #{plist_format}" unless plist_format == :xml
|
63
|
-
|
64
64
|
hash = parse_plist_xml string
|
65
65
|
plist.import_hash hash
|
66
|
-
plist.file_format
|
66
|
+
plist.file_format plist_format
|
67
67
|
return plist
|
68
68
|
end
|
69
69
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
require 'plist4r/
|
2
|
+
require 'plist4r/backend_base'
|
3
3
|
|
4
4
|
module Plist4r::Backend::RubyCocoa
|
5
5
|
class << self
|
@@ -103,7 +103,7 @@ EOC
|
|
103
103
|
require 'tempfile'
|
104
104
|
require 'plist4r/mixin/popen4'
|
105
105
|
|
106
|
-
|
106
|
+
unless @rb_script && File.exists?(@rb_script.path)
|
107
107
|
@rb_script ||= Tempfile.new("ruby_cocoa_wrapper.rb") do |o|
|
108
108
|
o << ruby_cocoa_rb
|
109
109
|
end
|
@@ -113,7 +113,7 @@ EOC
|
|
113
113
|
cmd = @rb_script.path
|
114
114
|
ordered_hash_rb = File.join(File.dirname(__FILE__), "..", "mixin", "ordered_hash.rb")
|
115
115
|
|
116
|
-
pid, stdin, stdout, stderr = Popen4::popen4
|
116
|
+
pid, stdin, stdout, stderr = ::Plist4r::Popen4::popen4 cmd, ordered_hash_rb
|
117
117
|
|
118
118
|
stdin.puts stdin_str
|
119
119
|
|
data/lib/plist4r/config.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
1
|
|
2
2
|
require 'plist4r/mixin/mixlib_config'
|
3
|
+
require 'plist4r/backend'
|
4
|
+
Dir.glob(File.dirname(__FILE__) + "/backend/**/*.rb").each {|b| require File.expand_path b}
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
module Plist4r
|
7
|
+
class Config
|
8
|
+
extend Mixlib::Config
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
backends [
|
11
|
+
::Plist4r::Backend::RubyCocoa,
|
12
|
+
::Plist4r::Backend::Haml,
|
13
|
+
::Plist4r::Backend::Libxml4r
|
14
|
+
]
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
unsupported_keys true
|
17
|
+
raise_any_failure false
|
18
|
+
deafult_format :xml
|
19
|
+
default_path nil
|
20
|
+
end
|
17
21
|
end
|
18
|
-
|
@@ -1,63 +1,65 @@
|
|
1
1
|
|
2
|
-
require 'plist4r/
|
2
|
+
require 'plist4r/mixin/ordered_hash'
|
3
3
|
|
4
|
-
module
|
4
|
+
module Plist4r
|
5
|
+
module DataMethods
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
def classes_for_key_type
|
8
|
+
{
|
9
|
+
:string => [String],
|
10
|
+
:bool => [TrueClass,FalseClass],
|
11
|
+
:integer => [Fixnum],
|
12
|
+
:array_of_strings => [Array],
|
13
|
+
:hash_of_bools => [Hash],
|
14
|
+
:hash => [Hash],
|
15
|
+
:bool_or_string_or_array_of_strings => [TrueClass,FalseClass,String,Array]
|
16
|
+
}
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
def valid_keys
|
20
|
+
{}
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
def method_missing method_symbol, *args, &blk
|
24
|
+
puts "method_missing: #{method_symbol.inspect}, args: #{args.inspect}"
|
25
|
+
valid_keys.each do |key_type, valid_keys_of_those_type|
|
26
|
+
if valid_keys_of_those_type.include?(method_symbol.to_s.camelcase)
|
27
|
+
puts "key_type = #{key_type}, method_symbol.to_s.camelcase = #{method_symbol.to_s.camelcase}, args = #{args.inspect}"
|
28
|
+
return eval("set_or_return key_type, method_symbol.to_s.camelcase, *args, &blk")
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
def validate_value key_type, key, value
|
34
|
+
unless classes_for_key_type[key_type].include? value.class
|
35
|
+
raise "Key: #{key}, value: #{value.inspect} is of type #{value.class}. Should be: #{classes_for_key_type[key_type].join ", "}"
|
36
|
+
end
|
37
|
+
case key_type
|
38
|
+
when :array_of_strings, :bool_or_string_or_array_of_strings
|
39
|
+
if value.class == Array
|
40
|
+
value.each_index do |i|
|
41
|
+
unless value[i].class == String
|
42
|
+
raise "Element: #{key}[#{i}], value: #{value[i].inspect} is of type #{value[i].class}. Should be: #{classes_for_key_type[:string].join ", "}"
|
43
|
+
end
|
42
44
|
end
|
43
45
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
when :hash_of_bools
|
47
|
+
value.each do |k,v|
|
48
|
+
unless [TrueClass,FalseClass].include? v.class
|
49
|
+
raise "Key: #{key}[#{k}], value: #{v.inspect} is of type #{v.class}. Should be: #{classes_for_key_type[:bool].join ", "}"
|
50
|
+
end
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
def set_or_return key_type, key, value=nil
|
56
|
+
puts "#{method_name}, key_type: #{key_type.inspect}, value: #{value.inspect}"
|
57
|
+
if value
|
58
|
+
validate_value key_type, key, value unless key_type == nil
|
59
|
+
@hash[key] = value
|
60
|
+
else
|
61
|
+
@orig[key]
|
62
|
+
end
|
61
63
|
end
|
62
64
|
end
|
63
|
-
end
|
65
|
+
end
|
data/lib/plist4r/mixin/popen4.rb
CHANGED
@@ -1,193 +1,199 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
2
|
+
require 'fcntl'
|
3
|
+
require 'etc'
|
4
|
+
require 'io/wait'
|
5
|
+
|
6
|
+
module Plist4r
|
7
|
+
module Popen4
|
8
|
+
class << self
|
9
|
+
# This is taken directly from Ara T Howard's Open4 library, and then
|
10
|
+
# modified to suit the needs of Chef. Any bugs here are most likely
|
11
|
+
# my own, and not Ara's.
|
12
|
+
#
|
13
|
+
# The original appears in external/open4.rb in its unmodified form.
|
14
|
+
#
|
15
|
+
# Thanks Ara!
|
16
|
+
def popen4(cmd, args={}, &b)
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
# Waitlast - this is magic.
|
19
|
+
#
|
20
|
+
# Do we wait for the child process to die before we yield
|
21
|
+
# to the block, or after? That is the magic of waitlast.
|
22
|
+
#
|
23
|
+
# By default, we are waiting before we yield the block.
|
24
|
+
args[:waitlast] ||= false
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
args[:user] ||= nil
|
27
|
+
unless args[:user].kind_of?(Integer)
|
28
|
+
args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
|
29
|
+
end
|
30
|
+
args[:group] ||= nil
|
31
|
+
unless args[:group].kind_of?(Integer)
|
32
|
+
args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
|
33
|
+
end
|
34
|
+
args[:environment] ||= {}
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
# Default on C locale so parsing commands output can be done
|
37
|
+
# independently of the node's default locale.
|
38
|
+
# "LC_ALL" could be set to nil, in which case we also must ignore it.
|
39
|
+
unless args[:environment].has_key?("LC_ALL")
|
40
|
+
args[:environment]["LC_ALL"] = "C"
|
41
|
+
end
|
38
42
|
|
39
|
-
|
43
|
+
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
40
44
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
verbose = $VERBOSE
|
46
|
+
begin
|
47
|
+
$VERBOSE = nil
|
48
|
+
ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
+
cid = fork {
|
51
|
+
pw.last.close
|
52
|
+
STDIN.reopen pw.first
|
53
|
+
pw.first.close
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
pr.first.close
|
56
|
+
STDOUT.reopen pr.last
|
57
|
+
pr.last.close
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
59
|
+
pe.first.close
|
60
|
+
STDERR.reopen pe.last
|
61
|
+
pe.last.close
|
58
62
|
|
59
|
-
|
63
|
+
STDOUT.sync = STDERR.sync = true
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
+
if args[:group]
|
66
|
+
Process.egid = args[:group]
|
67
|
+
Process.gid = args[:group]
|
68
|
+
end
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
if args[:user]
|
71
|
+
Process.euid = args[:user]
|
72
|
+
Process.uid = args[:user]
|
73
|
+
end
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
|
75
|
+
args[:environment].each do |key,value|
|
76
|
+
ENV[key] = value
|
77
|
+
end
|
74
78
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
+
if args[:umask]
|
80
|
+
umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
|
81
|
+
File.umask(umask)
|
82
|
+
end
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
begin
|
85
|
+
if cmd.kind_of?(Array)
|
86
|
+
exec(*cmd)
|
87
|
+
else
|
88
|
+
exec(cmd)
|
89
|
+
end
|
90
|
+
raise 'forty-two'
|
91
|
+
rescue Exception => e
|
92
|
+
Marshal.dump(e, ps.last)
|
93
|
+
ps.last.flush
|
94
|
+
end
|
95
|
+
ps.last.close unless (ps.last.closed?)
|
96
|
+
exit!
|
97
|
+
}
|
98
|
+
ensure
|
99
|
+
$VERBOSE = verbose
|
90
100
|
end
|
91
|
-
ps.last.close unless (ps.last.closed?)
|
92
|
-
exit!
|
93
|
-
}
|
94
|
-
ensure
|
95
|
-
$VERBOSE = verbose
|
96
|
-
end
|
97
|
-
|
98
|
-
[pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
|
99
101
|
|
100
|
-
|
101
|
-
e = Marshal.load ps.first
|
102
|
-
raise(Exception === e ? e : "unknown failure!")
|
103
|
-
rescue EOFError # If we get an EOF error, then the exec was successful
|
104
|
-
42
|
105
|
-
ensure
|
106
|
-
ps.first.close
|
107
|
-
end
|
108
|
-
|
109
|
-
pw.last.sync = true
|
102
|
+
[pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
|
110
103
|
|
111
|
-
|
104
|
+
begin
|
105
|
+
e = Marshal.load ps.first
|
106
|
+
raise(Exception === e ? e : "unknown failure!")
|
107
|
+
rescue EOFError # If we get an EOF error, then the exec was successful
|
108
|
+
42
|
109
|
+
ensure
|
110
|
+
ps.first.close
|
111
|
+
end
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
113
|
+
pw.last.sync = true
|
114
|
+
|
115
|
+
pi = [pw.last, pr.first, pe.first]
|
116
|
+
|
117
|
+
if b
|
118
|
+
begin
|
119
|
+
if args[:waitlast]
|
120
|
+
b[cid, *pi]
|
121
|
+
# send EOF so that if the child process is reading from STDIN
|
122
|
+
# it will actually finish up and exit
|
123
|
+
pi[0].close_write
|
124
|
+
Process.waitpid2(cid).last
|
125
|
+
else
|
126
|
+
# This took some doing.
|
127
|
+
# The trick here is to close STDIN
|
128
|
+
# Then set our end of the childs pipes to be O_NONBLOCK
|
129
|
+
# Then wait for the child to die, which means any IO it
|
130
|
+
# wants to do must be done - it's dead. If it isn't,
|
131
|
+
# it's because something totally skanky is happening,
|
132
|
+
# and we don't care.
|
133
|
+
o = StringIO.new
|
134
|
+
e = StringIO.new
|
135
|
+
|
136
|
+
pi[0].close
|
133
137
|
|
134
|
-
|
135
|
-
|
138
|
+
stdout = pi[1]
|
139
|
+
stderr = pi[2]
|
136
140
|
|
137
|
-
|
138
|
-
|
141
|
+
stdout.sync = true
|
142
|
+
stderr.sync = true
|
139
143
|
|
140
|
-
|
141
|
-
|
144
|
+
stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
|
145
|
+
stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
|
142
146
|
|
143
|
-
|
144
|
-
|
147
|
+
stdout_finished = false
|
148
|
+
stderr_finished = false
|
145
149
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
150
|
+
results = nil
|
151
|
+
|
152
|
+
while !stdout_finished || !stderr_finished
|
153
|
+
begin
|
154
|
+
channels_to_watch = []
|
155
|
+
channels_to_watch << stdout if !stdout_finished
|
156
|
+
channels_to_watch << stderr if !stderr_finished
|
157
|
+
ready = IO.select(channels_to_watch, nil, nil, 1.0)
|
158
|
+
rescue Errno::EAGAIN
|
159
|
+
ensure
|
160
|
+
results = Process.waitpid2(cid, Process::WNOHANG)
|
161
|
+
if results
|
162
|
+
stdout_finished = true
|
163
|
+
stderr_finished = true
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
if ready && ready.first.include?(stdout)
|
168
|
+
line = results ? stdout.gets(nil) : stdout.gets
|
169
|
+
if line
|
170
|
+
o.write(line)
|
171
|
+
else
|
172
|
+
stdout_finished = true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
if ready && ready.first.include?(stderr)
|
176
|
+
line = results ? stderr.gets(nil) : stderr.gets
|
177
|
+
if line
|
178
|
+
e.write(line)
|
179
|
+
else
|
180
|
+
stderr_finished = true
|
181
|
+
end
|
182
|
+
end
|
177
183
|
end
|
184
|
+
results = Process.waitpid2(cid) unless results
|
185
|
+
o.rewind
|
186
|
+
e.rewind
|
187
|
+
b[cid, pi[0], o, e]
|
188
|
+
results.last
|
178
189
|
end
|
190
|
+
ensure
|
191
|
+
pi.each{|fd| fd.close unless fd.closed?}
|
179
192
|
end
|
180
|
-
|
181
|
-
|
182
|
-
e.rewind
|
183
|
-
b[cid, pi[0], o, e]
|
184
|
-
results.last
|
193
|
+
else
|
194
|
+
[cid, pw.last, pr.first, pe.first]
|
185
195
|
end
|
186
|
-
ensure
|
187
|
-
pi.each{|fd| fd.close unless fd.closed?}
|
188
196
|
end
|
189
|
-
else
|
190
|
-
[cid, pw.last, pr.first, pe.first]
|
191
197
|
end
|
192
198
|
end
|
193
199
|
end
|