right_popen 1.0.7-x86-mswin32-60 → 1.0.9-x86-mswin32-60

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/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2010 RightScale, Inc.
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 PURPOSE AND NONINFRINGEMENT.
17
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2010 RightScale, Inc.
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 PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc CHANGED
@@ -1,124 +1,124 @@
1
- = RightPopen
2
-
3
- == DESCRIPTION
4
-
5
- === Synopsis
6
-
7
- RightPopen allows running external processes aynchronously while still
8
- capturing their standard and error outputs. It relies on EventMachine for most
9
- of its internal mechanisms. The Linux implementation is valid for any Linux
10
- platform but there is also a native implementation for Windows platforms.
11
-
12
- Refer to the wiki (https://github.com/rightscale/right_popen/wikis) for up-to-date
13
- documentation.
14
-
15
- Also use the built-in issues tracker (https://github.com/rightscale/right_popen/issues)
16
- to report issues.
17
-
18
-
19
- == USAGE
20
-
21
- === Simple Example
22
-
23
- require 'rubygems'
24
- require 'right_popen'
25
-
26
- @stdout_text = ""
27
- @stderr_text = ""
28
- @exit_status = nil
29
-
30
- def on_read_stdout(data)
31
- @stdout_text << data
32
- end
33
-
34
- def on_read_stderr(data)
35
- @stderr_text << data
36
- end
37
-
38
- def on_exit(status)
39
- @exit_status = status
40
- end
41
-
42
- EM.run do
43
- EM.next_tick do
44
- command = "ruby -e \"puts 'some stdout text'; $stderr.puts 'some stderr text'\; exit 99\""
45
- RightScale.popen3(:command => command,
46
- :target => self,
47
- :environment => nil,
48
- :stdout_handler => :on_read_stdout,
49
- :stderr_handler => :on_read_stderr,
50
- :exit_handler => :on_exit)
51
- end
52
- timer = EM::PeriodicTimer.new(0.1) do
53
- if @exit_status
54
- timer.cancel
55
- EM.stop
56
- end
57
- end
58
- end
59
-
60
- puts "@stdout_text = #{@stdout_text}"
61
- puts "@stderr_text = #{@stderr_text}"
62
- puts "@exit_status.exitstatus = #{@exit_status.exitstatus}"
63
-
64
-
65
- == INSTALLATION
66
-
67
- RightPopen can be installed by entering the following at the command prompt:
68
-
69
- gem install right_popen
70
-
71
-
72
- == BUILDING
73
-
74
- Install the following RubyGems required for building:
75
- * rake
76
-
77
- The Windows implementation relies on a native C module which must currently be
78
- built using the MSVC 6.0 compiler due to a dependency on the standard libraries
79
- FILE struct provided by the "msvcrt.dll".
80
-
81
- The gem can be built on Linux or Windows platforms and will produce separate gem
82
- files depending on current platform. Run the following command from the
83
- directory containing the "Rakefile":
84
-
85
- rake build_binary_gem
86
-
87
-
88
- == TESTING
89
-
90
- Install the following RubyGems required for testing:
91
- * rspec
92
-
93
- The build can be tested using the RSpec gem. Create a link to the installed
94
- "spec" in your Ruby/bin directory (or ensure the bin directory is on the PATH
95
- under Windows) and run the following command from the gem directory to execute
96
- the RightPopen tests:
97
-
98
- rake spec
99
-
100
-
101
- == LICENSE
102
-
103
- <b>RightPopen</b>
104
-
105
- Copyright:: Copyright (c) 2010 RightScale, Inc.
106
-
107
- Permission is hereby granted, free of charge, to any person obtaining
108
- a copy of this software and associated documentation files (the
109
- 'Software'), to deal in the Software without restriction, including
110
- without limitation the rights to use, copy, modify, merge, publish,
111
- distribute, sublicense, and/or sell copies of the Software, and to
112
- permit persons to whom the Software is furnished to do so, subject to
113
- the following conditions:
114
-
115
- The above copyright notice and this permission notice shall be
116
- included in all copies or substantial portions of the Software.
117
-
118
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
119
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
120
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
121
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
122
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
123
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
124
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ = RightPopen
2
+
3
+ == DESCRIPTION
4
+
5
+ === Synopsis
6
+
7
+ RightPopen allows running external processes aynchronously while still
8
+ capturing their standard and error outputs. It relies on EventMachine for most
9
+ of its internal mechanisms. The Linux implementation is valid for any Linux
10
+ platform but there is also a native implementation for Windows platforms.
11
+
12
+ Refer to the wiki (https://github.com/rightscale/right_popen/wikis) for up-to-date
13
+ documentation.
14
+
15
+ Also use the built-in issues tracker (https://github.com/rightscale/right_popen/issues)
16
+ to report issues.
17
+
18
+
19
+ == USAGE
20
+
21
+ === Simple Example
22
+
23
+ require 'rubygems'
24
+ require 'right_popen'
25
+
26
+ @stdout_text = ""
27
+ @stderr_text = ""
28
+ @exit_status = nil
29
+
30
+ def on_read_stdout(data)
31
+ @stdout_text << data
32
+ end
33
+
34
+ def on_read_stderr(data)
35
+ @stderr_text << data
36
+ end
37
+
38
+ def on_exit(status)
39
+ @exit_status = status
40
+ end
41
+
42
+ EM.run do
43
+ EM.next_tick do
44
+ command = "ruby -e \"puts 'some stdout text'; $stderr.puts 'some stderr text'\; exit 99\""
45
+ RightScale.popen3(:command => command,
46
+ :target => self,
47
+ :environment => nil,
48
+ :stdout_handler => :on_read_stdout,
49
+ :stderr_handler => :on_read_stderr,
50
+ :exit_handler => :on_exit)
51
+ end
52
+ timer = EM::PeriodicTimer.new(0.1) do
53
+ if @exit_status
54
+ timer.cancel
55
+ EM.stop
56
+ end
57
+ end
58
+ end
59
+
60
+ puts "@stdout_text = #{@stdout_text}"
61
+ puts "@stderr_text = #{@stderr_text}"
62
+ puts "@exit_status.exitstatus = #{@exit_status.exitstatus}"
63
+
64
+
65
+ == INSTALLATION
66
+
67
+ RightPopen can be installed by entering the following at the command prompt:
68
+
69
+ gem install right_popen
70
+
71
+
72
+ == BUILDING
73
+
74
+ Install the following RubyGems required for building:
75
+ * rake
76
+
77
+ The Windows implementation relies on a native C module which must currently be
78
+ built using the MSVC 6.0 compiler due to a dependency on the standard libraries
79
+ FILE struct provided by the "msvcrt.dll".
80
+
81
+ The gem can be built on Linux or Windows platforms and will produce separate gem
82
+ files depending on current platform. Run the following command from the
83
+ directory containing the "Rakefile":
84
+
85
+ rake build_binary_gem
86
+
87
+
88
+ == TESTING
89
+
90
+ Install the following RubyGems required for testing:
91
+ * rspec
92
+
93
+ The build can be tested using the RSpec gem. Create a link to the installed
94
+ "spec" in your Ruby/bin directory (or ensure the bin directory is on the PATH
95
+ under Windows) and run the following command from the gem directory to execute
96
+ the RightPopen tests:
97
+
98
+ rake spec
99
+
100
+
101
+ == LICENSE
102
+
103
+ <b>RightPopen</b>
104
+
105
+ Copyright:: Copyright (c) 2010 RightScale, Inc.
106
+
107
+ Permission is hereby granted, free of charge, to any person obtaining
108
+ a copy of this software and associated documentation files (the
109
+ 'Software'), to deal in the Software without restriction, including
110
+ without limitation the rights to use, copy, modify, merge, publish,
111
+ distribute, sublicense, and/or sell copies of the Software, and to
112
+ permit persons to whom the Software is furnished to do so, subject to
113
+ the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be
116
+ included in all copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
119
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
120
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
121
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
122
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
123
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
124
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/right_popen.rb CHANGED
@@ -1,65 +1,66 @@
1
- #--
2
- # Copyright (c) 2009 RightScale Inc
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- # RightScale.popen3 allows running external processes aynchronously
25
- # while still capturing their standard and error outputs.
26
- # It relies on EventMachine for most of its internal mechanisms.
27
-
28
- if RUBY_PLATFORM =~ /mswin/
29
- require File.expand_path(File.join(File.dirname(__FILE__), 'win32', 'right_popen'))
30
- else
31
- require File.expand_path(File.join(File.dirname(__FILE__), 'linux', 'right_popen'))
32
- end
33
-
34
- module RightScale
35
-
36
- # Spawn process to run given command asynchronously, hooking all three
37
- # standard streams of the child process.
38
- #
39
- # Streams the command's stdout and stderr to the given handlers. Time-
40
- # ordering of bytes sent to stdout and stderr is not preserved.
41
- #
42
- # Calls given exit handler upon command process termination, passing in the
43
- # resulting Process::Status.
44
- #
45
- # All handlers must be methods exposed by the given target.
46
- #
47
- # === Parameters
48
- # options[:command](String):: Command to execute, including any arguments
49
- # options[:environment](Hash):: Hash of environment variables values keyed by name
50
- # options[:target](Object):: object defining handler methods to be called, optional (no handlers can be defined if not specified)
51
- # options[:stdout_handler](String):: Stdout handler method name, optional
52
- # options[:stderr_handler](String):: Stderr handler method name, optional
53
- # options[:exit_handler](String):: Exit handler method name, optional
54
- #
55
- # === Returns
56
- # true:: Always returns true
57
- def self.popen3(options)
58
- raise "EventMachine reactor must be started" unless EM.reactor_running?
59
- raise "Missing command" unless options[:command]
60
- raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler]
61
- RightScale.popen3_imp(options)
62
- true
63
- end
64
-
65
- end
1
+ #--
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # RightScale.popen3 allows running external processes aynchronously
25
+ # while still capturing their standard and error outputs.
26
+ # It relies on EventMachine for most of its internal mechanisms.
27
+
28
+ if RUBY_PLATFORM =~ /mswin/
29
+ require File.expand_path(File.join(File.dirname(__FILE__), 'win32', 'right_popen'))
30
+ else
31
+ require File.expand_path(File.join(File.dirname(__FILE__), 'linux', 'right_popen'))
32
+ end
33
+
34
+ module RightScale
35
+
36
+ # Spawn process to run given command asynchronously, hooking all three
37
+ # standard streams of the child process.
38
+ #
39
+ # Streams the command's stdout and stderr to the given handlers. Time-
40
+ # ordering of bytes sent to stdout and stderr is not preserved.
41
+ #
42
+ # Calls given exit handler upon command process termination, passing in the
43
+ # resulting Process::Status.
44
+ #
45
+ # All handlers must be methods exposed by the given target.
46
+ #
47
+ # === Parameters
48
+ # options[:command](String):: Command to execute, including any arguments
49
+ # options[:environment](Hash):: Hash of environment variables values keyed by name
50
+ # options[:input](String):: Input string that will get streamed into child's process stdin
51
+ # options[:target](Object):: object defining handler methods to be called, optional (no handlers can be defined if not specified)
52
+ # options[:stdout_handler](String):: Stdout handler method name, optional
53
+ # options[:stderr_handler](String):: Stderr handler method name, optional
54
+ # options[:exit_handler](String):: Exit handler method name, optional
55
+ #
56
+ # === Returns
57
+ # true:: Always returns true
58
+ def self.popen3(options)
59
+ raise "EventMachine reactor must be started" unless EM.reactor_running?
60
+ raise "Missing command" unless options[:command]
61
+ raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler]
62
+ RightScale.popen3_imp(options)
63
+ true
64
+ end
65
+
66
+ end
@@ -1,442 +1,461 @@
1
- #--
2
- # Copyright (c) 2009 RightScale Inc
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- require 'rubygems'
25
- begin
26
- gem 'eventmachine', '=0.12.10.1' # patched version for Windows-only socket close fix
27
- rescue Gem::LoadError
28
- gem 'eventmachine', '=0.12.10'
29
- end
30
- require 'eventmachine'
31
- require 'win32/process'
32
-
33
- require File.join(File.dirname(__FILE__), 'right_popen.so') # win32 native code
34
-
35
- module RightScale
36
-
37
- # Provides an eventmachine callback handler for the stdout stream.
38
- module StdOutHandler
39
-
40
- # Quacks like Process::Status, which we cannot instantiate ourselves because
41
- # has no public new method. RightScale::popen3 needs this because the
42
- # 'win32/process' gem currently won't return Process::Status objects but
43
- # only returns a [pid, exitstatus] value.
44
- class Status
45
- # Process ID
46
- attr_reader :pid
47
-
48
- # Process exit code
49
- attr_reader :exitstatus
50
-
51
- # === Parameters
52
- # pid(Integer):: Process ID.
53
- #
54
- # exitstatus(Integer):: Process exit code
55
- def initialize(pid, exitstatus)
56
- @pid = pid
57
- @exitstatus = exitstatus
58
- end
59
-
60
- # Simulates Process::Status.exited?
61
- #
62
- # === Returns
63
- # true in all cases because this object cannot be returned until the
64
- # process exits
65
- def exited?
66
- return true
67
- end
68
-
69
- # Simulates Process::Status.success?
70
- #
71
- # === Returns
72
- # true if the process returned zero as its exit code
73
- def success?
74
- return @exitstatus ? (0 == @exitstatus) : true;
75
- end
76
- end
77
-
78
- # === Parameters
79
- # target(Object):: Object defining handler methods to be called.
80
- # stdout_handler(String):: Token for stdout handler method name.
81
- # exit_handler(String):: Token for exit handler method name.
82
- # stderr_eventable(Connector):: EM object representing stderr handler.
83
- # stream_out(IO):: Standard output stream.
84
- # pid(Integer):: Child process ID.
85
- def initialize(target, stdout_handler, exit_handler, stderr_eventable, stream_out, pid)
86
- @target = target
87
- @stdout_handler = stdout_handler
88
- @exit_handler = exit_handler
89
- @stderr_eventable = stderr_eventable
90
- @stream_out = stream_out
91
- @pid = pid
92
- @status = nil
93
- end
94
-
95
- # Callback from EM to asynchronously read the stdout stream. Note that this
96
- # callback mechanism is deprecated after EM v0.12.8
97
- def notify_readable
98
- data = RightPopen.async_read(@stream_out)
99
- receive_data(data) if (data && data.length > 0)
100
- detach unless data
101
- end
102
-
103
- # Callback from EM to receive data, which we also use to handle the
104
- # asynchronous data we read ourselves.
105
- def receive_data(data)
106
- @target.method(@stdout_handler).call(data) if @stdout_handler
107
- end
108
-
109
- # Override of Connection.get_status() for Windows implementation.
110
- def get_status
111
- unless @status
112
- begin
113
- @status = Status.new(@pid, Process.waitpid2(@pid)[1])
114
- rescue Process::Error
115
- # process is gone, which means we have no recourse to retrieve the
116
- # actual exit code; let's be optimistic.
117
- @status = Status.new(@pid, 0)
118
- end
119
- end
120
- return @status
121
- end
122
-
123
- # Callback from EM to unbind.
124
- def unbind
125
- # We force the stderr watched handler to go away so that
126
- # we don't end up with a broken pipe
127
- @stderr_eventable.force_detach if @stderr_eventable
128
- @target.method(@exit_handler).call(get_status) if @exit_handler
129
- @stream_out.close
130
- end
131
- end
132
-
133
- # Provides an eventmachine callback handler for the stderr stream.
134
- module StdErrHandler
135
-
136
- # === Parameters
137
- # target(Object):: Object defining handler methods to be called.
138
- #
139
- # stderr_handler(String):: Token for stderr handler method name.
140
- #
141
- # stream_err(IO):: Standard error stream.
142
- def initialize(target, stderr_handler, stream_err)
143
- @target = target
144
- @stderr_handler = stderr_handler
145
- @stream_err = stream_err
146
- @unbound = false
147
- end
148
-
149
- # Callback from EM to asynchronously read the stderr stream. Note that this
150
- # callback mechanism is deprecated after EM v0.12.8
151
- def notify_readable
152
- data = RightPopen.async_read(@stream_err)
153
- receive_data(data) if (data && data.length > 0)
154
- detach unless data
155
- end
156
-
157
- # Callback from EM to receive data, which we also use to handle the
158
- # asynchronous data we read ourselves.
159
- def receive_data(data)
160
- @target.method(@stderr_handler).call(data)
161
- end
162
-
163
- # Callback from EM to unbind.
164
- def unbind
165
- @unbound = true
166
- @stream_err.close
167
- end
168
-
169
- # Forces detachment of the stderr handler on EM's next tick.
170
- def force_detach
171
- # Use next tick to prevent issue in EM where descriptors list
172
- # gets out-of-sync when calling detach in an unbind callback
173
- EM.next_tick { detach unless @unbound }
174
- end
175
- end
176
-
177
- # Creates a child process and connects event handlers to the standard output
178
- # and error streams used by the created process. Connectors use named pipes
179
- # and asynchronous I/O in the native Windows implementation.
180
- #
181
- # See RightScale.popen3
182
- def self.popen3_imp(options)
183
- raise "EventMachine reactor must be started" unless EM.reactor_running?
184
-
185
- # merge and format environment strings, if necessary.
186
- environment_hash = options[:environment] || {}
187
- environment_strings = RightPopenEx.merge_environment(environment_hash)
188
-
189
- # launch cmd and request asynchronous output.
190
- mode = "t"
191
- show_window = false
192
- asynchronous_output = true
193
- stream_in, stream_out, stream_err, pid = RightPopen.popen4(options[:command], mode, show_window, asynchronous_output, environment_strings)
194
-
195
- # close input immediately.
196
- stream_in.close
197
-
198
- # attach handlers to event machine and let it monitor incoming data. the
199
- # streams aren't used directly by the connectors except that they are closed
200
- # on unbind.
201
- stderr_eventable = EM.watch(stream_err, StdErrHandler, options[:target], options[:stderr_handler], stream_err) { |c| c.notify_readable = true } if options[:stderr_handler]
202
- EM.watch(stream_out, StdOutHandler, options[:target], options[:stdout_handler], options[:exit_handler], stderr_eventable, stream_out, pid) { |c| c.notify_readable = true }
203
-
204
- # note that control returns to the caller, but the launched cmd continues
205
- # running and sends output to the handlers. the caller is not responsible
206
- # for waiting for the process to terminate or closing streams as the
207
- # watched eventables will handle this automagically. notification will be
208
- # sent to the exit_handler on process termination.
209
- end
210
-
211
- protected
212
-
213
- module RightPopenEx
214
- # Key class for case-insensitive hash insertion/lookup.
215
- class NoCaseKey
216
- # Internal key
217
- attr_reader :key
218
-
219
- # Stringizes object to be used as key
220
- def initialize key
221
- @key = key.to_s
222
- end
223
-
224
- # Hash code
225
- def hash
226
- @key.downcase.hash
227
- end
228
-
229
- # Equality for hash
230
- def eql? other
231
- @key.downcase.hash == other.key.downcase.hash
232
- end
233
-
234
- # Sort operator
235
- def <=> other
236
- @key.downcase <=> other.key.downcase
237
- end
238
-
239
- # Stringizer
240
- def to_s
241
- @key
242
- end
243
-
244
- # Inspector
245
- def inspect
246
- "\"#{@key}\""
247
- end
248
- end
249
-
250
- # Hash of known environment variable keys to special merge method proc.
251
- SPECIAL_MERGE_ENV_KEY_HASH = {
252
- NoCaseKey.new('PATH') => lambda { |from_value, to_value| merge_environment_path_value(from_value, to_value) }
253
- }
254
-
255
- # Merges the given environment hash with the current environment for this
256
- # process and the current environment for the current thread user from the
257
- # registry. The result is a nul-terminated block of nul-terminated strings
258
- # suitable for use in creating the child process.
259
- #
260
- # === Parameters
261
- # environment_hash(Hash):: Hash of environment key/value pairs or empty to
262
- # only merge the current process and currend thread user environment.
263
- #
264
- # === Returns
265
- # merged string block
266
- def self.merge_environment(environment_hash)
267
- current_user_environment_hash = get_current_user_environment
268
- machine_environment_hash = get_machine_environment
269
- result_environment_hash = get_process_environment
270
-
271
- # machine from registry supercedes process.
272
- merge_environment2(machine_environment_hash, result_environment_hash)
273
-
274
- # user environment from registry supercedes machine and process.
275
- merge_environment2(current_user_environment_hash, result_environment_hash)
276
-
277
- # caller's environment supercedes all.
278
- merge_environment2(environment_hash, result_environment_hash)
279
-
280
- return environment_hash_to_string_block(result_environment_hash)
281
- end
282
-
283
- # Merges from hash to another with special handling for known env vars.
284
- #
285
- # === Parameters
286
- # from_hash(Hash):: hash of string or environment keys to environment values
287
- # to_hash(Hash):: resulting hash or environment keys to environment values
288
- #
289
- # === Returns
290
- # to_hash(Hash):: merged 'to' hash
291
- def self.merge_environment2(from_hash, to_hash)
292
- from_hash.each do |from_key, from_value|
293
- to_key = from_key.kind_of?(NoCaseKey) ?
294
- from_key :
295
- NoCaseKey.new(from_key)
296
- to_value = to_hash[to_key]
297
- if to_value
298
- special_merge_proc = SPECIAL_MERGE_ENV_KEY_HASH[to_key]
299
- if special_merge_proc
300
- # special merge
301
- to_hash[to_key] = special_merge_proc.call(from_value, to_value)
302
- else
303
- # 'from' value supercedes existing 'to' value
304
- to_hash[to_key] = from_value
305
- end
306
- else
307
- # 'from' value replaces missing 'to' value
308
- to_hash[to_key] = from_value
309
- end
310
- end
311
- end
312
-
313
- # Merges a PATH-style variable by appending any missing subpaths on the
314
- # 'to' side to the value on the 'from' side in order of appearance. note
315
- # that the ordering of paths on the 'to' side is not preserved when some of
316
- # the paths also appear on the 'from' side. This is because paths on the
317
- # 'from' side always take precedence. This is an issue if two paths
318
- # reference similarly named executables and swapping the order of paths
319
- # would cause the wrong executable to be invoked. To resolve this, the
320
- # higher precedence path can be changed to ensure that the conflicting paths
321
- # are both specified in the proper order. There is no trivial algorithm
322
- # which can predict the proper ordering of such paths.
323
- #
324
- # === Parameters
325
- # from_value(String):: value to merge from
326
- # to_value(String):: value to merge to
327
- #
328
- # === Returns
329
- # merged_value(String):: merged value
330
- def self.merge_environment_path_value(from_value, to_value)
331
- # normalize to backslashes for Windows-style PATH variable.
332
- from_value = from_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
333
- to_value = to_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
334
-
335
- # quick outs.
336
- return from_value if to_value.empty?
337
- return to_value if from_value.empty?
338
-
339
- # Windows paths are case-insensitive, so we want to match paths efficiently
340
- # while being case-insensitive. we will make use of NoCaseKey again.
341
- from_value_hash = {}
342
- from_value.split(File::PATH_SEPARATOR).each { |path| from_value_hash[NoCaseKey.new(path)] = true }
343
- appender = ""
344
- to_value.split(File::PATH_SEPARATOR).each do |path|
345
- if not from_value_hash[NoCaseKey.new(path)]
346
- appender += File::PATH_SEPARATOR + path
347
- end
348
- end
349
-
350
- return from_value + appender
351
- end
352
-
353
- # Queries the environment strings from the current thread/process user's
354
- # environment. The resulting hash represents any variables set for the
355
- # persisted user context but any set dynamically in the current process
356
- # context.
357
- #
358
- # === Returns
359
- # environment_hash(Hash):: hash of environment key (String) to value (String).
360
- def self.get_current_user_environment
361
- environment_strings = RightPopen.get_current_user_environment
362
-
363
- return string_block_to_environment_hash(environment_strings)
364
- end
365
-
366
- # Queries the environment strings from the machine's environment.
367
- #
368
- # === Returns
369
- # environment_hash(Hash):: hash of environment key (String) to value (String).
370
- def self.get_machine_environment
371
- environment_strings = RightPopen.get_machine_environment
372
-
373
- return string_block_to_environment_hash(environment_strings)
374
- end
375
-
376
- # Queries the environment strings from the process environment (which is kept
377
- # in memory for each process and generally begins life as a copy of the
378
- # process user's environment context plus any changes made by ancestral
379
- # processes).
380
- #
381
- # === Returns
382
- # environment_hash(Hash):: hash of environment key (String) to value (String).
383
- def self.get_process_environment
384
- environment_strings = RightPopen.get_process_environment
385
-
386
- return string_block_to_environment_hash(environment_strings)
387
- end
388
-
389
- # Converts a nul-terminated block of nul-terminated strings to a hash by
390
- # splitting the block on nul characters until the empty string is found.
391
- # splits substrings on the '=' character which is used to delimit key from
392
- # value in Windows environment blocks.
393
- #
394
- # === Paramters
395
- # string_block(String):: string containing nul-terminated substrings followed
396
- # by a nul-terminator.
397
- #
398
- # === Returns
399
- # string_hash(Hash):: hash of string to string
400
- def self.string_block_to_environment_hash(string_block)
401
- result_hash = {}
402
- last_offset = 0
403
- string_block_length = string_block.length
404
- while last_offset < string_block_length
405
- offset = string_block.index(0.chr, last_offset)
406
- if offset.nil?
407
- offset = string_block.length
408
- end
409
- env_string = string_block[last_offset, offset - last_offset]
410
- break if env_string.empty?
411
- last_offset = offset + 1
412
-
413
- # note that Windows uses "=C:=C:\" notation for working directory info, so
414
- # ignore equals if it is the first character.
415
- equals_offset = env_string.index('=', 1)
416
- if equals_offset
417
- env_key = env_string[0, equals_offset]
418
- env_value = env_string[equals_offset + 1..-1]
419
- result_hash[NoCaseKey.new(env_key)] = env_value
420
- end
421
- end
422
-
423
- return result_hash
424
- end
425
-
426
- # Converts a hash of string to string to a string block by combining pairs
427
- # into a single string delimited by the '=' character and then placing nul-
428
- # terminators after each pair, followed by a final nul-terminator.
429
- #
430
- # === Parameters
431
- # environment_hash(Hash):: hash of
432
- def self.environment_hash_to_string_block(environment_hash)
433
- result_block = ""
434
- environment_hash.keys.sort.each do |key|
435
- result_block += "#{key}=#{environment_hash[key]}\0"
436
- end
437
-
438
- return result_block + "\0"
439
- end
440
-
441
- end
442
- end
1
+ #--
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'rubygems'
25
+ require 'eventmachine'
26
+ require 'win32/process'
27
+
28
+ require File.join(File.dirname(__FILE__), 'right_popen.so') # win32 native code
29
+
30
+ module RightScale
31
+
32
+ # Eventmachine callback handler for stdin stream
33
+ module StdInHandler
34
+
35
+ # === Parameters
36
+ # options[:input](String):: Input to be streamed into child process stdin
37
+ # stream_in(IO):: Standard input stream.
38
+ def initialize(options, stream_in)
39
+ @stream_in = stream_in
40
+ @input = options[:input]
41
+ end
42
+
43
+ # Eventmachine callback asking for more to write
44
+ # Send input and close stream in
45
+ def post_init
46
+ if @input
47
+ send_data(@input)
48
+ close_connection_after_writing
49
+ @input = nil
50
+ else
51
+ close_connection
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ # Provides an eventmachine callback handler for the stdout stream.
58
+ module StdOutHandler
59
+
60
+ # Quacks like Process::Status, which we cannot instantiate ourselves because
61
+ # has no public new method. RightScale::popen3 needs this because the
62
+ # 'win32/process' gem currently won't return Process::Status objects but
63
+ # only returns a [pid, exitstatus] value.
64
+ class Status
65
+ # Process ID
66
+ attr_reader :pid
67
+
68
+ # Process exit code
69
+ attr_reader :exitstatus
70
+
71
+ # === Parameters
72
+ # pid(Integer):: Process ID.
73
+ #
74
+ # exitstatus(Integer):: Process exit code
75
+ def initialize(pid, exitstatus)
76
+ @pid = pid
77
+ @exitstatus = exitstatus
78
+ end
79
+
80
+ # Simulates Process::Status.exited?
81
+ #
82
+ # === Returns
83
+ # true in all cases because this object cannot be returned until the
84
+ # process exits
85
+ def exited?
86
+ return true
87
+ end
88
+
89
+ # Simulates Process::Status.success?
90
+ #
91
+ # === Returns
92
+ # true if the process returned zero as its exit code
93
+ def success?
94
+ return @exitstatus ? (0 == @exitstatus) : true;
95
+ end
96
+ end
97
+
98
+ # === Parameters
99
+ # options[:target](Object):: Object defining handler methods to be called.
100
+ # options[:stdout_handler](String):: Token for stdout handler method name.
101
+ # options[:exit_handler](String):: Token for exit handler method name.
102
+ # stderr_eventable(Connector):: EM object representing stderr handler.
103
+ # stream_out(IO):: Standard output stream.
104
+ # pid(Integer):: Child process ID.
105
+ def initialize(options, stderr_eventable, stream_out, pid)
106
+ @target = options[:target]
107
+ @stdout_handler = options[:stdout_handler]
108
+ @exit_handler = options[:exit_handler]
109
+ @stderr_eventable = stderr_eventable
110
+ @stream_out = stream_out
111
+ @pid = pid
112
+ @status = nil
113
+ end
114
+
115
+ # Callback from EM to asynchronously read the stdout stream. Note that this
116
+ # callback mechanism is deprecated after EM v0.12.8
117
+ def notify_readable
118
+ data = RightPopen.async_read(@stream_out)
119
+ receive_data(data) if (data && data.length > 0)
120
+ detach unless data
121
+ end
122
+
123
+ # Callback from EM to receive data, which we also use to handle the
124
+ # asynchronous data we read ourselves.
125
+ def receive_data(data)
126
+ @target.method(@stdout_handler).call(data) if @stdout_handler
127
+ end
128
+
129
+ # Override of Connection.get_status() for Windows implementation.
130
+ def get_status
131
+ unless @status
132
+ begin
133
+ @status = Status.new(@pid, Process.waitpid2(@pid)[1])
134
+ rescue Process::Error
135
+ # process is gone, which means we have no recourse to retrieve the
136
+ # actual exit code; let's be optimistic.
137
+ @status = Status.new(@pid, 0)
138
+ end
139
+ end
140
+ return @status
141
+ end
142
+
143
+ # Callback from EM to unbind.
144
+ def unbind
145
+ # We force the stderr watched handler to go away so that
146
+ # we don't end up with a broken pipe
147
+ @stderr_eventable.force_detach if @stderr_eventable
148
+ @target.method(@exit_handler).call(get_status) if @exit_handler
149
+ @stream_out.close
150
+ end
151
+ end
152
+
153
+ # Provides an eventmachine callback handler for the stderr stream.
154
+ module StdErrHandler
155
+
156
+ # === Parameters
157
+ # options[:target](Object):: Object defining handler methods to be called.
158
+ # options[:stderr_handler](Symbol):: Token for stderr handler method name.
159
+ # stream_err(IO):: Standard error stream.
160
+ def initialize(options, stream_err)
161
+ @target = options[:target]
162
+ @stderr_handler = options[:stderr_handler]
163
+ @stream_err = stream_err
164
+ @unbound = false
165
+ end
166
+
167
+ # Callback from EM to asynchronously read the stderr stream. Note that this
168
+ # callback mechanism is deprecated after EM v0.12.8
169
+ def notify_readable
170
+ data = RightPopen.async_read(@stream_err)
171
+ receive_data(data) if (data && data.length > 0)
172
+ detach unless data
173
+ end
174
+
175
+ # Callback from EM to receive data, which we also use to handle the
176
+ # asynchronous data we read ourselves.
177
+ def receive_data(data)
178
+ @target.method(@stderr_handler).call(data)
179
+ end
180
+
181
+ # Callback from EM to unbind.
182
+ def unbind
183
+ @unbound = true
184
+ @stream_err.close
185
+ end
186
+
187
+ # Forces detachment of the stderr handler on EM's next tick.
188
+ def force_detach
189
+ # Use next tick to prevent issue in EM where descriptors list
190
+ # gets out-of-sync when calling detach in an unbind callback
191
+ EM.next_tick { detach unless @unbound }
192
+ end
193
+ end
194
+
195
+ # Creates a child process and connects event handlers to the standard output
196
+ # and error streams used by the created process. Connectors use named pipes
197
+ # and asynchronous I/O in the native Windows implementation.
198
+ #
199
+ # See RightScale.popen3
200
+ def self.popen3_imp(options)
201
+ raise "EventMachine reactor must be started" unless EM.reactor_running?
202
+
203
+ # merge and format environment strings, if necessary.
204
+ environment_hash = options[:environment] || {}
205
+ environment_strings = RightPopenEx.merge_environment(environment_hash)
206
+
207
+ # launch cmd and request asynchronous output.
208
+ mode = "t"
209
+ show_window = false
210
+ asynchronous_output = true
211
+ stream_in, stream_out, stream_err, pid = RightPopen.popen4(options[:command], mode, show_window, asynchronous_output, environment_strings)
212
+
213
+ # close input immediately.
214
+ stream_in.close if options[:input].nil?
215
+
216
+ # attach handlers to event machine and let it monitor incoming data. the
217
+ # streams aren't used directly by the connectors except that they are closed
218
+ # on unbind.
219
+ stderr_eventable = EM.watch(stream_err, StdErrHandler, options, stream_err) { |c| c.notify_readable = true } if options[:stderr_handler]
220
+ EM.watch(stream_out, StdOutHandler, options, stderr_eventable, stream_out, pid) { |c| c.notify_readable = true }
221
+ EM.attach(stream_in, StdInHandler, options, stream_in) if options[:input]
222
+
223
+ # note that control returns to the caller, but the launched cmd continues
224
+ # running and sends output to the handlers. the caller is not responsible
225
+ # for waiting for the process to terminate or closing streams as the
226
+ # watched eventables will handle this automagically. notification will be
227
+ # sent to the exit_handler on process termination.
228
+ end
229
+
230
+ protected
231
+
232
+ module RightPopenEx
233
+ # Key class for case-insensitive hash insertion/lookup.
234
+ class NoCaseKey
235
+ # Internal key
236
+ attr_reader :key
237
+
238
+ # Stringizes object to be used as key
239
+ def initialize key
240
+ @key = key.to_s
241
+ end
242
+
243
+ # Hash code
244
+ def hash
245
+ @key.downcase.hash
246
+ end
247
+
248
+ # Equality for hash
249
+ def eql? other
250
+ @key.downcase.hash == other.key.downcase.hash
251
+ end
252
+
253
+ # Sort operator
254
+ def <=> other
255
+ @key.downcase <=> other.key.downcase
256
+ end
257
+
258
+ # Stringizer
259
+ def to_s
260
+ @key
261
+ end
262
+
263
+ # Inspector
264
+ def inspect
265
+ "\"#{@key}\""
266
+ end
267
+ end
268
+
269
+ # Hash of known environment variable keys to special merge method proc.
270
+ SPECIAL_MERGE_ENV_KEY_HASH = {
271
+ NoCaseKey.new('PATH') => lambda { |from_value, to_value| merge_environment_path_value(from_value, to_value) }
272
+ }
273
+
274
+ # Merges the given environment hash with the current environment for this
275
+ # process and the current environment for the current thread user from the
276
+ # registry. The result is a nul-terminated block of nul-terminated strings
277
+ # suitable for use in creating the child process.
278
+ #
279
+ # === Parameters
280
+ # environment_hash(Hash):: Hash of environment key/value pairs or empty to
281
+ # only merge the current process and currend thread user environment.
282
+ #
283
+ # === Returns
284
+ # merged string block
285
+ def self.merge_environment(environment_hash)
286
+ current_user_environment_hash = get_current_user_environment
287
+ machine_environment_hash = get_machine_environment
288
+ result_environment_hash = get_process_environment
289
+
290
+ # machine from registry supercedes process.
291
+ merge_environment2(machine_environment_hash, result_environment_hash)
292
+
293
+ # user environment from registry supercedes machine and process.
294
+ merge_environment2(current_user_environment_hash, result_environment_hash)
295
+
296
+ # caller's environment supercedes all.
297
+ merge_environment2(environment_hash, result_environment_hash)
298
+
299
+ return environment_hash_to_string_block(result_environment_hash)
300
+ end
301
+
302
+ # Merges from hash to another with special handling for known env vars.
303
+ #
304
+ # === Parameters
305
+ # from_hash(Hash):: hash of string or environment keys to environment values
306
+ # to_hash(Hash):: resulting hash or environment keys to environment values
307
+ #
308
+ # === Returns
309
+ # to_hash(Hash):: merged 'to' hash
310
+ def self.merge_environment2(from_hash, to_hash)
311
+ from_hash.each do |from_key, from_value|
312
+ to_key = from_key.kind_of?(NoCaseKey) ?
313
+ from_key :
314
+ NoCaseKey.new(from_key)
315
+ to_value = to_hash[to_key]
316
+ if to_value
317
+ special_merge_proc = SPECIAL_MERGE_ENV_KEY_HASH[to_key]
318
+ if special_merge_proc
319
+ # special merge
320
+ to_hash[to_key] = special_merge_proc.call(from_value, to_value)
321
+ else
322
+ # 'from' value supercedes existing 'to' value
323
+ to_hash[to_key] = from_value
324
+ end
325
+ else
326
+ # 'from' value replaces missing 'to' value
327
+ to_hash[to_key] = from_value
328
+ end
329
+ end
330
+ end
331
+
332
+ # Merges a PATH-style variable by appending any missing subpaths on the
333
+ # 'to' side to the value on the 'from' side in order of appearance. note
334
+ # that the ordering of paths on the 'to' side is not preserved when some of
335
+ # the paths also appear on the 'from' side. This is because paths on the
336
+ # 'from' side always take precedence. This is an issue if two paths
337
+ # reference similarly named executables and swapping the order of paths
338
+ # would cause the wrong executable to be invoked. To resolve this, the
339
+ # higher precedence path can be changed to ensure that the conflicting paths
340
+ # are both specified in the proper order. There is no trivial algorithm
341
+ # which can predict the proper ordering of such paths.
342
+ #
343
+ # === Parameters
344
+ # from_value(String):: value to merge from
345
+ # to_value(String):: value to merge to
346
+ #
347
+ # === Returns
348
+ # merged_value(String):: merged value
349
+ def self.merge_environment_path_value(from_value, to_value)
350
+ # normalize to backslashes for Windows-style PATH variable.
351
+ from_value = from_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
352
+ to_value = to_value.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
353
+
354
+ # quick outs.
355
+ return from_value if to_value.empty?
356
+ return to_value if from_value.empty?
357
+
358
+ # Windows paths are case-insensitive, so we want to match paths efficiently
359
+ # while being case-insensitive. we will make use of NoCaseKey again.
360
+ from_value_hash = {}
361
+ from_value.split(File::PATH_SEPARATOR).each { |path| from_value_hash[NoCaseKey.new(path)] = true }
362
+ appender = ""
363
+ to_value.split(File::PATH_SEPARATOR).each do |path|
364
+ if not from_value_hash[NoCaseKey.new(path)]
365
+ appender += File::PATH_SEPARATOR + path
366
+ end
367
+ end
368
+
369
+ return from_value + appender
370
+ end
371
+
372
+ # Queries the environment strings from the current thread/process user's
373
+ # environment. The resulting hash represents any variables set for the
374
+ # persisted user context but any set dynamically in the current process
375
+ # context.
376
+ #
377
+ # === Returns
378
+ # environment_hash(Hash):: hash of environment key (String) to value (String).
379
+ def self.get_current_user_environment
380
+ environment_strings = RightPopen.get_current_user_environment
381
+
382
+ return string_block_to_environment_hash(environment_strings)
383
+ end
384
+
385
+ # Queries the environment strings from the machine's environment.
386
+ #
387
+ # === Returns
388
+ # environment_hash(Hash):: hash of environment key (String) to value (String).
389
+ def self.get_machine_environment
390
+ environment_strings = RightPopen.get_machine_environment
391
+
392
+ return string_block_to_environment_hash(environment_strings)
393
+ end
394
+
395
+ # Queries the environment strings from the process environment (which is kept
396
+ # in memory for each process and generally begins life as a copy of the
397
+ # process user's environment context plus any changes made by ancestral
398
+ # processes).
399
+ #
400
+ # === Returns
401
+ # environment_hash(Hash):: hash of environment key (String) to value (String).
402
+ def self.get_process_environment
403
+ environment_strings = RightPopen.get_process_environment
404
+
405
+ return string_block_to_environment_hash(environment_strings)
406
+ end
407
+
408
+ # Converts a nul-terminated block of nul-terminated strings to a hash by
409
+ # splitting the block on nul characters until the empty string is found.
410
+ # splits substrings on the '=' character which is used to delimit key from
411
+ # value in Windows environment blocks.
412
+ #
413
+ # === Paramters
414
+ # string_block(String):: string containing nul-terminated substrings followed
415
+ # by a nul-terminator.
416
+ #
417
+ # === Returns
418
+ # string_hash(Hash):: hash of string to string
419
+ def self.string_block_to_environment_hash(string_block)
420
+ result_hash = {}
421
+ last_offset = 0
422
+ string_block_length = string_block.length
423
+ while last_offset < string_block_length
424
+ offset = string_block.index(0.chr, last_offset)
425
+ if offset.nil?
426
+ offset = string_block.length
427
+ end
428
+ env_string = string_block[last_offset, offset - last_offset]
429
+ break if env_string.empty?
430
+ last_offset = offset + 1
431
+
432
+ # note that Windows uses "=C:=C:\" notation for working directory info, so
433
+ # ignore equals if it is the first character.
434
+ equals_offset = env_string.index('=', 1)
435
+ if equals_offset
436
+ env_key = env_string[0, equals_offset]
437
+ env_value = env_string[equals_offset + 1..-1]
438
+ result_hash[NoCaseKey.new(env_key)] = env_value
439
+ end
440
+ end
441
+
442
+ return result_hash
443
+ end
444
+
445
+ # Converts a hash of string to string to a string block by combining pairs
446
+ # into a single string delimited by the '=' character and then placing nul-
447
+ # terminators after each pair, followed by a final nul-terminator.
448
+ #
449
+ # === Parameters
450
+ # environment_hash(Hash):: hash of
451
+ def self.environment_hash_to_string_block(environment_hash)
452
+ result_block = ""
453
+ environment_hash.keys.sort.each do |key|
454
+ result_block += "#{key}=#{environment_hash[key]}\0"
455
+ end
456
+
457
+ return result_block + "\0"
458
+ end
459
+
460
+ end
461
+ end