terrapin 0.6.0.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +6 -0
  4. data/GOALS +8 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE +26 -0
  7. data/NEWS.md +85 -0
  8. data/README.md +215 -0
  9. data/Rakefile +13 -0
  10. data/lib/terrapin.rb +12 -0
  11. data/lib/terrapin/command_line.rb +197 -0
  12. data/lib/terrapin/command_line/multi_pipe.rb +50 -0
  13. data/lib/terrapin/command_line/output.rb +12 -0
  14. data/lib/terrapin/command_line/runners.rb +7 -0
  15. data/lib/terrapin/command_line/runners/backticks_runner.rb +30 -0
  16. data/lib/terrapin/command_line/runners/fake_runner.rb +30 -0
  17. data/lib/terrapin/command_line/runners/popen_runner.rb +29 -0
  18. data/lib/terrapin/command_line/runners/posix_runner.rb +49 -0
  19. data/lib/terrapin/command_line/runners/process_runner.rb +41 -0
  20. data/lib/terrapin/exceptions.rb +8 -0
  21. data/lib/terrapin/os_detector.rb +27 -0
  22. data/lib/terrapin/version.rb +4 -0
  23. data/spec/spec_helper.rb +31 -0
  24. data/spec/support/fake_logger.rb +18 -0
  25. data/spec/support/have_output.rb +11 -0
  26. data/spec/support/nonblocking_examples.rb +14 -0
  27. data/spec/support/stub_os.rb +25 -0
  28. data/spec/support/unsetting_exitstatus.rb +7 -0
  29. data/spec/support/with_exitstatus.rb +12 -0
  30. data/spec/terrapin/command_line/output_spec.rb +14 -0
  31. data/spec/terrapin/command_line/runners/backticks_runner_spec.rb +24 -0
  32. data/spec/terrapin/command_line/runners/fake_runner_spec.rb +22 -0
  33. data/spec/terrapin/command_line/runners/popen_runner_spec.rb +24 -0
  34. data/spec/terrapin/command_line/runners/posix_runner_spec.rb +40 -0
  35. data/spec/terrapin/command_line/runners/process_runner_spec.rb +40 -0
  36. data/spec/terrapin/command_line_spec.rb +195 -0
  37. data/spec/terrapin/errors_spec.rb +62 -0
  38. data/spec/terrapin/os_detector_spec.rb +23 -0
  39. data/spec/terrapin/runners_spec.rb +97 -0
  40. data/terrapin.gemspec +28 -0
  41. metadata +209 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2f5aede86af5c29288c8f251af1dbf9bd1a179b90a416dd638ea02c3e29930a3
4
+ data.tar.gz: 4dd46fea24d0a8a78c0c39959d5c08dcfeec5a4429a1f18acfc87323811c18bb
5
+ SHA512:
6
+ metadata.gz: 28e367c3deb7c6b6064a6dd2cfd1600646261302d135805ba47e3fbba18a2ee2256dbb2b9a379378c20bc1a770bcfdfc5b95fe668e5d499d3f2eda2bf1c49677
7
+ data.tar.gz: 873cb7a8046310942c2533003ae09a865fde8288fe64e97484c69d588d042b818e24ffd3b2e06e38308cffee1de99dbc86490231daf24b1cd8bade289e64f1ea
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.rbc
2
+ .rbx
3
+ *.gem
4
+ Gemfile.lock
5
+ *.swp
6
+ *.swo
7
+ .bundle
8
+ bin
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1.5
5
+ - jruby-19mode
6
+ - rbx-2
data/GOALS ADDED
@@ -0,0 +1,8 @@
1
+ Terrapin hits 1.0.0 when:
2
+
3
+ [*] It can run command lines across all major unix platforms.
4
+ [*] It can run command lines on Windows.
5
+ [*] It handles quoting.
6
+ [*] It takes advantage of OS-specific functionality to be thread-safe,
7
+ when possible.
8
+ [ ] It has a consistent and functioning API that pleases us.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ platforms :ruby do
6
+ gem "posix-spawn"
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+
2
+ LICENSE
3
+
4
+ The MIT License
5
+
6
+ Copyright (c) 2011-2014 Jon Yurek and thoughtbot, inc.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+
data/NEWS.md ADDED
@@ -0,0 +1,85 @@
1
+ New for 0.5.7:
2
+
3
+ * Feature: Allow collection of both STDOUT and STDERR.
4
+ * Improvement: Convert arguments to strings when possible
5
+
6
+ New for 0.5.6:
7
+
8
+ * Bug Fix: Java does not need to run commands with `env`
9
+ * Bug Fix: Found out we were rescuing the wrong error
10
+
11
+ New for 0.5.5:
12
+
13
+ * Bug Fix: Posix- and ProcessRunner respect paths *and* are thread safe!
14
+ * Bug Fix: `exitstatus` should always be set, even if command doesn't run.
15
+ * Test Fix: Do not try to test Runners if they don't run on this system.
16
+ * Improvement: Pass the Errno::ENOENT message through to the exception.
17
+ * Improvement: Improve documentation
18
+
19
+ New for 0.5.4:
20
+
21
+ * Bug Fix: PosixRunner and ProcessRunner respect supplemental paths now.
22
+
23
+ New for 0.5.3:
24
+
25
+ * SECURITY: Fix exploitable bug that could allow arbitrary command execution.
26
+ See CVE-2013-4457 for more details. Thanks to Holger Just for report and fix!
27
+ * Bug fix: Sub-word interpolations can be confused for the longer version
28
+
29
+ New for 0.5.2:
30
+
31
+ * Improvement: Close all the IO objects!
32
+ * Feature: Add an Runner that uses IO.popen, so JRuby can play
33
+ * Improvement: Officially drop Ruby 1.8 support, add Ruby 2.0 support
34
+ * Bug fix: Prevent a crash if no command was actually run
35
+ * Improvement: Add security cautions to the README
36
+
37
+ New for 0.5.1:
38
+
39
+ * Fixed a bug preventing running on 1.8.7 for no good reason.
40
+
41
+ New for 0.5.0:
42
+
43
+ * Updated the copyrights to 2013
44
+ * Added UTF encoding markers on code files to ensure they're interpreted as
45
+ UTF-8 instead of ASCII.
46
+ * Swapped the ordering of the PATH and supplemental path. A binary in the
47
+ supplemental path will take precedence, now.
48
+ * Errors contain the output of the erroring command, for inspection.
49
+ * Use climate_control instead for environment management.
50
+
51
+ New for 0.4.2:
52
+
53
+ * Loggers that don't understand `tty?`, like `ActiveSupport::BufferedLogger`
54
+ will still work.
55
+
56
+ New for 0.4.1:
57
+
58
+ * Introduce FakeRunner for testing, so you don't really run commands.
59
+ * Fix logging: output the actual command, not the un-interpolated pattern.
60
+ * Prevent color codes from being output if log destination isn't a TTY.
61
+
62
+ New for 0.4.0:
63
+
64
+ * Moved interpolation to the `run` method, instead of interpolating on `new`.
65
+ * Remove official support for REE.
66
+
67
+ New for 0.3.2:
68
+
69
+ * Fix a hang when processes wait for IO.
70
+
71
+ New for 0.3.1:
72
+
73
+ * Made the `Runner` manually swappable, in case `ProcessRunner` doesn't work
74
+ for some reason.
75
+ * Fixed copyright years.
76
+
77
+ New for 0.3.0:
78
+
79
+ * Support blank arguments.
80
+ * Add `CommandLine#unix?`.
81
+ * Add `CommandLine#exit_status`.
82
+ * Automatically use `POSIX::Spawn` if available.
83
+ * Add `CommandLine#environment` as a hash of extra `ENV` data..
84
+ * Add `CommandLine#runner` which produces an object that responds to `#call`.
85
+ * Fix a race condition but only on Ruby 1.9.
data/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # Terrapin [![Build Status](https://secure.travis-ci.org/thoughtbot/terrapin.png?branch=master)](http://travis-ci.org/thoughtbot/terrapin)
2
+
3
+ A small library for doing (command) lines.
4
+
5
+ [API reference](http://rubydoc.info/gems/terrapin/)
6
+
7
+ ## Usage
8
+
9
+ The basic, normal stuff:
10
+
11
+ ```ruby
12
+ line = Terrapin::CommandLine.new("echo", "hello 'world'")
13
+ line.command # => "echo hello 'world'"
14
+ line.run # => "hello world\n"
15
+ ```
16
+
17
+ Interpolated arguments:
18
+
19
+ ```ruby
20
+ line = Terrapin::CommandLine.new("convert", ":in -scale :resolution :out")
21
+ line.command(in: "omg.jpg",
22
+ resolution: "32x32",
23
+ out: "omg_thumb.jpg")
24
+ # => "convert 'omg.jpg' -scale '32x32' 'omg_thumb.jpg'"
25
+ ```
26
+
27
+ It prevents attempts at being bad:
28
+
29
+ ```ruby
30
+ line = Terrapin::CommandLine.new("cat", ":file")
31
+ line.command(file: "haha`rm -rf /`.txt") # => "cat 'haha`rm -rf /`.txt'"
32
+
33
+ line = Terrapin::CommandLine.new("cat", ":file")
34
+ line.command(file: "ohyeah?'`rm -rf /`.ha!") # => "cat 'ohyeah?'\\''`rm -rf /`.ha!'"
35
+ ```
36
+
37
+ NOTE: It only does that for arguments interpolated via `run`, NOT arguments
38
+ passed into `new` (see 'Security' below):
39
+
40
+ ```ruby
41
+ line = Terrapin::CommandLine.new("echo", "haha`whoami`")
42
+ line.command # => "echo haha`whoami`"
43
+ line.run # => "hahawebserver"
44
+ ```
45
+
46
+ You can ignore the result:
47
+
48
+ ```ruby
49
+ line = Terrapin::CommandLine.new("noisy", "--extra-verbose", swallow_stderr: true)
50
+ line.command # => "noisy --extra-verbose 2>/dev/null"
51
+
52
+ # ... and on Windows...
53
+ line.command # => "noisy --extra-verbose 2>NUL"
54
+ ```
55
+
56
+ If your command errors, you get an exception:
57
+
58
+ ```ruby
59
+ line = Terrapin::CommandLine.new("git", "commit")
60
+ begin
61
+ line.run
62
+ rescue Terrapin::ExitStatusError => e
63
+ e.message # => "Command 'git commit' returned 1. Expected 0"
64
+ end
65
+ ```
66
+
67
+ If your command might return something non-zero, and you expect that, it's cool:
68
+
69
+ ```ruby
70
+ line = Terrapin::CommandLine.new("/usr/bin/false", "", expected_outcodes: [0, 1])
71
+ begin
72
+ line.run
73
+ rescue Terrapin::ExitStatusError => e
74
+ # => You never get here!
75
+ end
76
+ ```
77
+
78
+ You don't have the command? You get an exception:
79
+
80
+ ```ruby
81
+ line = Terrapin::CommandLine.new("lolwut")
82
+ begin
83
+ line.run
84
+ rescue Terrapin::CommandNotFoundError => e
85
+ e # => the command isn't in the $PATH for this process.
86
+ end
87
+ ```
88
+
89
+ But don't fear, you can specify where to look for the command:
90
+
91
+ ```ruby
92
+ Terrapin::CommandLine.path = "/opt/bin"
93
+ line = Terrapin::CommandLine.new("lolwut")
94
+ line.command # => "lolwut", but it looks in /opt/bin for it.
95
+ ```
96
+
97
+ You can even give it a bunch of places to look:
98
+
99
+ ```ruby
100
+ FileUtils.rm("/opt/bin/lolwut")
101
+ File.open('/usr/local/bin/lolwut') {|f| f.write('echo Hello') }
102
+ Terrapin::CommandLine.path = ["/opt/bin", "/usr/local/bin"]
103
+ line = Terrapin::CommandLine.new("lolwut")
104
+ line.run # => prints 'Hello', because it searches the path
105
+ ```
106
+
107
+ Or just put it in the command:
108
+
109
+ ```ruby
110
+ line = Terrapin::CommandLine.new("/opt/bin/lolwut")
111
+ line.command # => "/opt/bin/lolwut"
112
+ ```
113
+
114
+ You can see what's getting run. The 'Command' part it logs is in green for visibility!
115
+
116
+ ```ruby
117
+ line = Terrapin::CommandLine.new("echo", ":var", logger: Logger.new(STDOUT))
118
+ line.run(var: "LOL!") # => Logs this with #info -> Command :: echo 'LOL!'
119
+ ```
120
+
121
+ Or log every command:
122
+
123
+ ```ruby
124
+ Terrapin::CommandLine.logger = Logger.new(STDOUT)
125
+ Terrapin::CommandLine.new("date").run # => Logs this -> Command :: date
126
+ ```
127
+
128
+ ## Security
129
+
130
+ Short version: Only pass user-generated data into the `run` method and NOT
131
+ `new`.
132
+
133
+ As shown in examples above, Terrapin will only shell-escape what is passed in as
134
+ interpolations to the `run` method. It WILL NOT escape what is passed in to the
135
+ second argument of `new`. Terrapin assumes that you will not be manually
136
+ passing user-generated data to that argument and will be using it as a template
137
+ for your command line's structure.
138
+
139
+ ## POSIX Spawn
140
+
141
+ You can potentially increase performance by installing [the posix-spawn
142
+ gem](https://rubygems.org/gems/posix-spawn). This gem can keep your
143
+ application's heap from being copied when forking command line
144
+ processes. For applications with large heaps the gain can be
145
+ significant. To include `posix-spawn`, simply add it to your `Gemfile` or,
146
+ if you don't use bundler, install the gem.
147
+
148
+ ## Runners
149
+
150
+ Terrapin will attempt to choose from among 3 different ways of running commands.
151
+ The simplest is using backticks, and is the default in 1.8. In Ruby 1.9, it
152
+ will attempt to use `Process.spawn`. And, as mentioned above, if the
153
+ `posix-spawn` gem is installed, it will attempt to use that. If for some reason
154
+ one of the `.spawn` runners don't work for you, you can override them manually
155
+ by setting a new runner, like so:
156
+
157
+ ```ruby
158
+ Terrapin::CommandLine.runner = Terrapin::CommandLine::BackticksRunner.new
159
+ ```
160
+
161
+ And if you really want to, you can define your own Runner, though I can't
162
+ imagine why you would.
163
+
164
+ ### JRuby issues
165
+
166
+ #### Caveat
167
+
168
+ If you get `Error::ECHILD` errors and are using JRuby, there is a very good
169
+ chance that the error is actually in JRuby. This was brought to our attention
170
+ in https://github.com/thoughtbot/terrapin/issues/24 and probably fixed in
171
+ http://jira.codehaus.org/browse/JRUBY-6162. You *will* want to use the
172
+ `BackticksRunner` if you are unable to update JRuby.
173
+
174
+ #### Spawn warning
175
+
176
+ If you get `unsupported spawn option: out` warning (like in [issue 38](https://github.com/thoughtbot/terrapin/issues/38)),
177
+ try to use `PopenRunner`:
178
+
179
+ ```ruby
180
+ Terrapin::CommandLine.runner = Terrapin::CommandLine::PopenRunner.new
181
+ ```
182
+
183
+ ## Thread Safety
184
+
185
+ Terrapin should be thread safe. As discussed [here, in this climate_control
186
+ thread](https://github.com/thoughtbot/climate_control/pull/11), climate_control,
187
+ which modifies the environment under which commands are run for the
188
+ BackticksRunner and PopenRunner, is thread-safe but not reentrant. Please let us
189
+ know if you find this is ever not the case.
190
+
191
+ ## Feedback
192
+
193
+ *Security* concerns must be privately emailed to
194
+ [security@thoughtbot.com](security@thoughtbot.com).
195
+
196
+ Question? Idea? Problem? Bug? Comment? Concern? Like using question marks?
197
+
198
+ [GitHub Issues For All!](https://github.com/thoughtbot/terrapin/issues)
199
+
200
+ ## Credits
201
+
202
+ Thank you to all [the contributors](https://github.com/thoughtbot/terrapin/graphs/contributors)!
203
+
204
+ ![thoughtbot](http://thoughtbot.com/logo.png)
205
+
206
+ Terrapin is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community)
207
+
208
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
209
+
210
+ ## License
211
+
212
+ Copyright 2011-2014 Jon Yurek and thoughtbot, inc. This is free software, and
213
+ may be redistributed under the terms specified in the
214
+ [LICENSE](https://github.com/thoughtbot/terrapin/blob/master/LICENSE)
215
+ file.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc 'Default: Run specs.'
6
+ task :default => 'spec:unit'
7
+
8
+ namespace :spec do
9
+ desc "Run unit specs"
10
+ RSpec::Core::RakeTask.new('unit') do |t|
11
+ t.pattern = 'spec/terrapin/**/*_spec.rb'
12
+ end
13
+ end
data/lib/terrapin.rb ADDED
@@ -0,0 +1,12 @@
1
+ # coding: UTF-8
2
+
3
+ require 'rbconfig'
4
+ require 'terrapin/os_detector'
5
+ require 'terrapin/command_line'
6
+ require 'terrapin/command_line/output'
7
+ require 'terrapin/command_line/multi_pipe'
8
+ require 'terrapin/command_line/runners'
9
+ require 'terrapin/exceptions'
10
+
11
+ module Terrapin
12
+ end
@@ -0,0 +1,197 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class CommandLine
5
+ class << self
6
+ attr_accessor :logger, :runner
7
+
8
+ def path
9
+ @supplemental_path
10
+ end
11
+
12
+ def path=(supplemental_path)
13
+ @supplemental_path = Array(supplemental_path).
14
+ flatten.
15
+ join(OS.path_separator)
16
+ end
17
+
18
+ def environment
19
+ @supplemental_environment ||= {}
20
+ end
21
+
22
+ def runner
23
+ @runner || best_runner
24
+ end
25
+
26
+ def runner_options
27
+ @default_runner_options ||= {}
28
+ end
29
+
30
+ def fake!
31
+ @runner = FakeRunner.new
32
+ end
33
+
34
+ def unfake!
35
+ @runner = nil
36
+ end
37
+
38
+ private
39
+
40
+ def best_runner
41
+ [PosixRunner, ProcessRunner, BackticksRunner].detect do |runner|
42
+ runner.supported?
43
+ end.new
44
+ end
45
+ end
46
+
47
+ @environment = {}
48
+
49
+ attr_reader :exit_status, :runner
50
+
51
+ def initialize(binary, params = "", options = {})
52
+ @binary = binary.dup
53
+ @params = params.dup
54
+ @options = options.dup
55
+ @runner = @options.delete(:runner) || self.class.runner
56
+ @logger = @options.delete(:logger) || self.class.logger
57
+ @swallow_stderr = @options.delete(:swallow_stderr)
58
+ @expected_outcodes = @options.delete(:expected_outcodes) || [0]
59
+ @environment = @options.delete(:environment) || {}
60
+ @runner_options = @options.delete(:runner_options) || {}
61
+ end
62
+
63
+ def command(interpolations = {})
64
+ cmd = [path_prefix, @binary, interpolate(@params, interpolations)]
65
+ cmd << bit_bucket if @swallow_stderr
66
+ cmd.join(" ").strip
67
+ end
68
+
69
+ def run(interpolations = {})
70
+ @exit_status = nil
71
+ begin
72
+ full_command = command(interpolations)
73
+ log("#{colored("Command")} :: #{full_command}")
74
+ @output = execute(full_command)
75
+ rescue Errno::ENOENT => e
76
+ raise Terrapin::CommandNotFoundError, e.message
77
+ ensure
78
+ @exit_status = $?.respond_to?(:exitstatus) ? $?.exitstatus : 0
79
+ end
80
+
81
+ if @exit_status == 127
82
+ raise Terrapin::CommandNotFoundError
83
+ end
84
+
85
+ unless @expected_outcodes.include?(@exit_status)
86
+ message = [
87
+ "Command '#{full_command}' returned #{@exit_status}. Expected #{@expected_outcodes.join(", ")}",
88
+ "Here is the command output: STDOUT:\n", command_output,
89
+ "\nSTDERR:\n", command_error_output
90
+ ].join("\n")
91
+ raise Terrapin::ExitStatusError, message
92
+ end
93
+ command_output
94
+ end
95
+
96
+ def command_output
97
+ output.output
98
+ end
99
+
100
+ def command_error_output
101
+ output.error_output
102
+ end
103
+
104
+ def output
105
+ @output || Output.new
106
+ end
107
+
108
+ private
109
+
110
+ def colored(text, ansi_color = "\e[32m")
111
+ if @logger && @logger.respond_to?(:tty?) && @logger.tty?
112
+ "#{ansi_color}#{text}\e[0m"
113
+ else
114
+ text
115
+ end
116
+ end
117
+
118
+ def log(text)
119
+ if @logger
120
+ @logger.info(text)
121
+ end
122
+ end
123
+
124
+ def path_prefix
125
+ if !self.class.path.nil? && !self.class.path.empty?
126
+ os_path_prefix
127
+ end
128
+ end
129
+
130
+ def os_path_prefix
131
+ if OS.unix?
132
+ unix_path_prefix
133
+ else
134
+ windows_path_prefix
135
+ end
136
+ end
137
+
138
+ def unix_path_prefix
139
+ "PATH=#{self.class.path}#{OS.path_separator}$PATH;"
140
+ end
141
+
142
+ def windows_path_prefix
143
+ "SET PATH=#{self.class.path}#{OS.path_separator}%PATH% &"
144
+ end
145
+
146
+ def execute(command)
147
+ runner.call(command, environment, runner_options)
148
+ end
149
+
150
+ def environment
151
+ self.class.environment.merge(@environment)
152
+ end
153
+
154
+ def runner_options
155
+ self.class.runner_options.merge(@runner_options)
156
+ end
157
+
158
+ def interpolate(pattern, interpolations)
159
+ interpolations = stringify_keys(interpolations)
160
+ pattern.gsub(/:\{?(\w+)\b\}?/) do |match|
161
+ key = match.tr(":{}", "")
162
+ if interpolations.key?(key)
163
+ shell_quote_all_values(interpolations[key])
164
+ else
165
+ match
166
+ end
167
+ end
168
+ end
169
+
170
+ def stringify_keys(hash)
171
+ Hash[hash.map{ |k, v| [k.to_s, v] }]
172
+ end
173
+
174
+ def shell_quote_all_values(values)
175
+ Array(values).map(&method(:shell_quote)).join(" ")
176
+ end
177
+
178
+ def shell_quote(string)
179
+ return "" if string.nil?
180
+ string = string.to_s if string.respond_to? :to_s
181
+
182
+ if OS.unix?
183
+ if string.empty?
184
+ "''"
185
+ else
186
+ string.split("'", -1).map{|m| "'#{m}'" }.join("\\'")
187
+ end
188
+ else
189
+ %{"#{string}"}
190
+ end
191
+ end
192
+
193
+ def bit_bucket
194
+ OS.unix? ? "2>/dev/null" : "2>NUL"
195
+ end
196
+ end
197
+ end