awesome_spawn 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of awesome_spawn might be problematic. Click here for more details.

data/README.md CHANGED
@@ -11,6 +11,7 @@ AwesomeSpawn is a module that provides some useful features over Ruby's Kernel.s
11
11
  Some additional features include...
12
12
 
13
13
  - Parameter passing as a Hash or associative Array sanitizing them to prevent command line injection.
14
+ - Ability to pass the contents of stdin as a String.
14
15
  - Results returned as an object giving access to the output stream, error stream, and exit status.
15
16
  - Optionally raising an exception when exit status is not 0.
16
17
 
data/lib/awesome_spawn.rb CHANGED
@@ -31,26 +31,35 @@ module AwesomeSpawn
31
31
  # result.exit_status # => 1
32
32
  #
33
33
  # @example With parameters sanitized
34
- # result = AwesomeSpawn.run('echo', :params => {"--out" => "; rm /some/file"})
34
+ # result = AwesomeSpawn.run('echo', :params => {:out => "; rm /some/file"})
35
35
  # # => #<AwesomeSpawn::CommandResult:0x007ff64baa6650 @exit_status=0>
36
36
  # result.command_line
37
37
  # # => "echo --out \\;\\ rm\\ /some/file"
38
38
  #
39
+ # @example With data to be passed on stdin
40
+ # result = AwesomeSpawn.run('cat', :in_data => "line1\nline2")
41
+ # => #<AwesomeSpawn::CommandResult:0x007fff05b0ab10 @exit_status=0>
42
+ # result.output
43
+ # => "line1\nline2"
44
+ #
39
45
  # @param [String] command The command to run
40
- # @param [Hash] options The options for running the command
46
+ # @param [Hash] options The options for running the command. Also accepts any
47
+ # option that can be passed to Kernel.spawn, except `:out` and `:err`.
41
48
  # @option options [Hash,Array] :params The command line parameters. See
42
- # {#build_command_line} for how to specify params. Alternate key
43
- # `:parameters`.
44
- # @option options [String] :chdir see the `:chdir` parameter for Kernel.spawn
49
+ # {#build_command_line} for how to specify params.
50
+ # @option options [String] :in_data Data to be passed on stdin. If this option
51
+ # is specified you cannot specify `:in`.
45
52
  #
46
53
  # @raise [NoSuchFileError] if the `command` is not found
47
54
  # @return [CommandResult] the output stream, error stream, and exit status
48
55
  # @see http://ruby-doc.org/core/Kernel.html#method-i-spawn Kernel.spawn
49
56
  def run(command, options = {})
50
- params = options[:params] || options[:parameters]
51
-
52
- launch_params = {}
53
- launch_params[:chdir] = options[:chdir] if options[:chdir]
57
+ raise ArgumentError, "options cannot contain :out" if options.include?(:out)
58
+ raise ArgumentError, "options cannot contain :err" if options.include?(:err)
59
+ raise ArgumentError, "options cannot contain :in if :in_data is specified" if options.include?(:in) && options.include?(:in_data)
60
+ options = options.dup
61
+ params = options.delete(:params)
62
+ in_data = options.delete(:in_data)
54
63
 
55
64
  output = ""
56
65
  error = ""
@@ -58,7 +67,7 @@ module AwesomeSpawn
58
67
  command_line = build_command_line(command, params)
59
68
 
60
69
  begin
61
- output, error = launch(command_line, launch_params)
70
+ output, error = launch(command_line, in_data, options)
62
71
  status = exitstatus
63
72
  ensure
64
73
  output ||= ""
@@ -99,11 +108,15 @@ module AwesomeSpawn
99
108
  # @param [String] command The command to run
100
109
  # @param [Hash,Array] params Optional command line parameters. They can
101
110
  # be passed as a Hash or associative Array. The values are sanitized to
102
- # prevent command line injection.
111
+ # prevent command line injection. Keys as symbols are prefixed with `--`,
112
+ # and `_` is replaced with `-`.
103
113
  #
114
+ # - `{:key => "value"}` generates `--key value`
104
115
  # - `{"--key" => "value"}` generates `--key value`
116
+ # - `{:key= => "value"}` generates `--key=value`
105
117
  # - `{"--key=" => "value"}` generates `--key=value`
106
- # - `{"--key" => nil}` generates `--key`
118
+ # - `{:key_name => "value"}` generates `--key-name value`
119
+ # - `{:key => nil}` generates `--key`
107
120
  # - `{"-f" => ["file1", "file2"]}` generates `-f file1 file2`
108
121
  # - `{nil => ["file1", "file2"]}` generates `file1 file2`
109
122
  #
@@ -118,12 +131,22 @@ module AwesomeSpawn
118
131
  def sanitize(params)
119
132
  return [] if params.nil? || params.empty?
120
133
  params.collect do |k, v|
121
- v = case v
122
- when Array; v.collect {|i| i.to_s.shellescape}
123
- when NilClass; v
124
- else v.to_s.shellescape
125
- end
126
- [k, v]
134
+ [sanitize_key(k), sanitize_value(v)]
135
+ end
136
+ end
137
+
138
+ def sanitize_key(key)
139
+ case key
140
+ when Symbol then "--#{key.to_s.tr("_", "-")}"
141
+ else key
142
+ end
143
+ end
144
+
145
+ def sanitize_value(value)
146
+ case value
147
+ when Array then value.collect { |i| i.to_s.shellescape }
148
+ when NilClass then value
149
+ else value.to_s.shellescape
127
150
  end
128
151
  end
129
152
 
@@ -139,20 +162,34 @@ module AwesomeSpawn
139
162
  # http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146
140
163
  THREAD_SYNC_KEY = "#{self.name}-exitstatus"
141
164
 
142
- def launch(command, spawn_options = {})
165
+ def launch(command, in_data, spawn_options = {})
143
166
  out_r, out_w = IO.pipe
144
167
  err_r, err_w = IO.pipe
145
- pid = Kernel.spawn(command, {:err => err_w, :out => out_w}.merge(spawn_options))
146
- wait_for_process(pid, out_w, err_w)
168
+ in_r, in_w = IO.pipe if in_data
169
+
170
+ spawn_options[:out] = out_w
171
+ spawn_options[:err] = err_w
172
+ spawn_options[:in] = in_r if in_data
173
+
174
+ pid = Kernel.spawn(command, spawn_options)
175
+
176
+ write_to_input(in_w, in_data) if in_data
177
+ wait_for_process(pid, out_w, err_w, in_r)
147
178
  wait_for_pipes(out_r, err_r)
148
179
  end
149
180
 
150
- def wait_for_process(pid, out_w, err_w)
181
+ def write_to_input(in_w, in_data)
182
+ in_w.write(in_data)
183
+ in_w.close
184
+ end
185
+
186
+ def wait_for_process(pid, out_w, err_w, in_r)
151
187
  self.exitstatus = :not_done
152
188
  Thread.new(Thread.current) do |parent_thread|
153
189
  _, status = Process.wait2(pid)
154
190
  out_w.close
155
191
  err_w.close
192
+ in_r.close if in_r
156
193
  parent_thread[THREAD_SYNC_KEY] = status.exitstatus
157
194
  end
158
195
  end
@@ -1,3 +1,3 @@
1
1
  module AwesomeSpawn
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -6,11 +6,13 @@ describe AwesomeSpawn do
6
6
 
7
7
  let(:params) do
8
8
  {
9
- "--user" => "bob",
10
- "--pass" => "P@$sw0^& |<>/-+*d%",
11
- "--db" => nil,
12
- "--desc=" => "Some Description",
13
- nil => ["pkg1", "some pkg"]
9
+ "--user" => "bob",
10
+ "--pass" => "P@$sw0^& |<>/-+*d%",
11
+ "--db" => nil,
12
+ "--desc=" => "Some Description",
13
+ :symkey => nil,
14
+ :symkey_dash => nil,
15
+ nil => ["pkg1", "some pkg"]
14
16
  }
15
17
  end
16
18
 
@@ -19,21 +21,28 @@ describe AwesomeSpawn do
19
21
  end
20
22
 
21
23
  shared_examples_for "run" do
22
- context "parameters" do
24
+ context "options" do
23
25
  before do
24
26
  subject.stub(:exitstatus => 0)
25
27
  end
26
28
 
27
- it "won't modify caller params" do
29
+ it ":params won't be modified" do
28
30
  orig_params = params.dup
29
31
  subject.stub(:launch)
30
32
  subject.send(run_method, "true", :params => params)
31
33
  expect(orig_params).to eq(params)
32
34
  end
33
35
 
34
- it "supports spawn's chdir option" do
35
- subject.should_receive(:launch).once.with("true", {:chdir => ".."})
36
- subject.send(run_method, "true", :chdir => "..")
36
+ it ":in_data cannot be passed with :in" do
37
+ expect { subject.send(run_method, "true", :in_data => "XXXXX", :in => "/dev/null") } .to raise_error(ArgumentError)
38
+ end
39
+
40
+ it ":out is not supported" do
41
+ expect { subject.send(run_method, "true", :out => "/dev/null") }.to raise_error(ArgumentError)
42
+ end
43
+
44
+ it ":err is not supported" do
45
+ expect { subject.send(run_method, "true", :err => "/dev/null") }.to raise_error(ArgumentError)
37
46
  end
38
47
  end
39
48
 
@@ -66,6 +75,20 @@ describe AwesomeSpawn do
66
75
  expect { subject.send(run_method, "XXXXX --user=bob") }.to raise_error(Errno::ENOENT, "No such file or directory - XXXXX")
67
76
  end
68
77
 
78
+ context "with option" do
79
+ it ":chdir" do
80
+ result = subject.send(run_method, "pwd", :chdir => "..")
81
+ expect(result.exit_status).to eq(0)
82
+ expect(result.output.chomp).to eq(File.expand_path("..", Dir.pwd))
83
+ end
84
+
85
+ it ":in_data" do
86
+ result = subject.send(run_method, "cat", :in_data => "line1\nline2")
87
+ expect(result.exit_status).to eq(0)
88
+ expect(result.output).to eq("line1\nline2")
89
+ end
90
+ end
91
+
69
92
  context "#exit_status" do
70
93
  it "command ok exit ok" do
71
94
  expect(subject.send(run_method, "true").exit_status).to eq(0)
@@ -113,7 +136,27 @@ describe AwesomeSpawn do
113
136
  context ".build_command_line" do
114
137
  it "sanitizes crazy params" do
115
138
  cl = subject.build_command_line("true", modified_params)
116
- expect(cl).to eq "true --user bob --pass P@\\$sw0\\^\\&\\ \\|\\<\\>/-\\+\\*d\\% --db --desc=Some\\ Description pkg1 some\\ pkg --pool 123 --pool 456"
139
+ expect(cl).to eq "true --user bob --pass P@\\$sw0\\^\\&\\ \\|\\<\\>/-\\+\\*d\\% --db --desc=Some\\ Description --symkey --symkey-dash pkg1 some\\ pkg --pool 123 --pool 456"
140
+ end
141
+
142
+ it "handles Symbol keys" do
143
+ cl = subject.build_command_line("true", :abc => "def")
144
+ expect(cl).to eq "true --abc def"
145
+ end
146
+
147
+ it "handles Symbol keys with tailing '='" do
148
+ cl = subject.build_command_line("true", :abc= => "def")
149
+ expect(cl).to eq "true --abc=def"
150
+ end
151
+
152
+ it "handles Symbol keys with underscore" do
153
+ cl = subject.build_command_line("true", :abc_def => "ghi")
154
+ expect(cl).to eq "true --abc-def ghi"
155
+ end
156
+
157
+ it "handles Symbol keys with underscore and tailing '='" do
158
+ cl = subject.build_command_line("true", :abc_def= => "ghi")
159
+ expect(cl).to eq "true --abc-def=ghi"
117
160
  end
118
161
 
119
162
  it "sanitizes Fixnum array param value" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awesome_spawn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2014-01-04 00:00:00.000000000 Z
15
+ date: 2014-02-03 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bundler
@@ -151,18 +151,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
151
  - - ! '>='
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
- segments:
155
- - 0
156
- hash: -1223152174146401530
157
154
  required_rubygems_version: !ruby/object:Gem::Requirement
158
155
  none: false
159
156
  requirements:
160
157
  - - ! '>='
161
158
  - !ruby/object:Gem::Version
162
159
  version: '0'
163
- segments:
164
- - 0
165
- hash: -1223152174146401530
166
160
  requirements: []
167
161
  rubyforge_project:
168
162
  rubygems_version: 1.8.23