rubysh 0.2.4 → 0.3.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.
- data/History.txt +4 -0
- data/README.md +25 -2
- data/examples/on_read_example.sh +7 -0
- data/lib/rubysh.rb +10 -21
- data/lib/rubysh/command.rb +9 -4
- data/lib/rubysh/subprocess.rb +46 -7
- data/lib/rubysh/version.rb +1 -1
- data/test/functional/lib/execute_block.rb +26 -0
- data/test/unit/lib/rubysh/subprocess.rb +27 -3
- metadata +8 -4
data/History.txt
ADDED
data/README.md
CHANGED
@@ -58,6 +58,12 @@ are arbitrary symbols):
|
|
58
58
|
runner.read(:stdout) # "hi\n"
|
59
59
|
runner.read(:stderr) # ""
|
60
60
|
|
61
|
+
You can also have your child run a Ruby block rather than execute a command:
|
62
|
+
|
63
|
+
command = Rubysh(Rubysh.stdout > :stdout) {puts "hi from child"}
|
64
|
+
runner = command.run
|
65
|
+
runner.read(:stdout) # "hi from child\n"
|
66
|
+
|
61
67
|
## Controlled input
|
62
68
|
|
63
69
|
You can easily read and write data interactively:
|
@@ -67,9 +73,26 @@ You can easily read and write data interactively:
|
|
67
73
|
>> runner.read(:how => :partial) # block until some output available
|
68
74
|
=> ".\n.\n.\n.\n.\n.\n.\n.\n.\n"
|
69
75
|
>> runner.read(:how => :nonblock)
|
70
|
-
=>
|
76
|
+
=> nil
|
71
77
|
>> runner.read # block until all output available
|
72
|
-
=> [
|
78
|
+
=> ".\n[...]"
|
79
|
+
|
80
|
+
## Reactive output
|
81
|
+
|
82
|
+
You can also receive real-time notifications as data becomes available:
|
83
|
+
|
84
|
+
>> runner = Rubysh(
|
85
|
+
'examples/on_read_example.sh',
|
86
|
+
Rubysh.stdout > :stdout, Rubysh.stderr > :stderr,
|
87
|
+
on_read: Proc.new {|target, data| puts "[#{target}]: #{data}"}
|
88
|
+
)
|
89
|
+
=> Command: examples/on_read_example.sh >:stdout 2>:stderr {:on_read=>#<Proc:0x007f8ad3bc5790@(irb):4>}
|
90
|
+
>> runner.run
|
91
|
+
[stdout]: [1] Hello from stdout
|
92
|
+
[stderr]: [1] Hello from stderr
|
93
|
+
[stdout]: [2] Hello from stdout
|
94
|
+
[stderr]: [2] Hello from stderr
|
95
|
+
[...]
|
73
96
|
|
74
97
|
## API
|
75
98
|
|
data/lib/rubysh.rb
CHANGED
@@ -62,23 +62,12 @@ require 'rubysh/util'
|
|
62
62
|
# Rubysh('ls', '/tmp', Rubysh.&)
|
63
63
|
# => Command: ls /tmp &
|
64
64
|
|
65
|
-
#
|
65
|
+
# You can then create a new Rubysh command:
|
66
66
|
#
|
67
67
|
# command = Rubysh('ls')
|
68
68
|
# command.run
|
69
|
-
#
|
70
|
-
# Or use the block syntax to create and run one:
|
71
|
-
#
|
72
|
-
# Rubysh {'ls'}
|
73
69
|
def Rubysh(*args, &blk)
|
74
|
-
|
75
|
-
raise Rubysh::Error::BaseError.new("Can't provide arguments and a block") if args.length > 0
|
76
|
-
command = blk.call
|
77
|
-
command = Rubysh::Command.new(command) unless command.kind_of?(Rubysh::Command)
|
78
|
-
command.run
|
79
|
-
else
|
80
|
-
Rubysh::Command.new(args)
|
81
|
-
end
|
70
|
+
Rubysh::Command.new(args, &blk)
|
82
71
|
end
|
83
72
|
|
84
73
|
def AliasRubysh(name)
|
@@ -90,22 +79,22 @@ end
|
|
90
79
|
module Rubysh
|
91
80
|
# Convenience methods
|
92
81
|
def self.run(*args, &blk)
|
93
|
-
command = Rubysh::Command.new(args)
|
94
|
-
command.run
|
82
|
+
command = Rubysh::Command.new(args, &blk)
|
83
|
+
command.run
|
95
84
|
end
|
96
85
|
|
97
86
|
def self.run_async(*args, &blk)
|
98
|
-
command = Rubysh::Command.new(args)
|
99
|
-
command.run_async
|
87
|
+
command = Rubysh::Command.new(args, &blk)
|
88
|
+
command.run_async
|
100
89
|
end
|
101
90
|
|
102
91
|
def self.check_call(*args, &blk)
|
103
|
-
command = Rubysh::Command.new(args)
|
104
|
-
command.check_call
|
92
|
+
command = Rubysh::Command.new(args, &blk)
|
93
|
+
command.check_call
|
105
94
|
end
|
106
95
|
|
107
|
-
def self.Command(*args)
|
108
|
-
Command.new(*args)
|
96
|
+
def self.Command(*args, &blk)
|
97
|
+
Command.new(*args, &blk)
|
109
98
|
end
|
110
99
|
|
111
100
|
def self.Pipeline(*args)
|
data/lib/rubysh/command.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
module Rubysh
|
2
2
|
class Command < BaseCommand
|
3
|
-
attr_accessor :raw_args, :directives, :args
|
3
|
+
attr_accessor :raw_args, :directives, :args, :blk
|
4
4
|
|
5
|
-
def initialize(args)
|
5
|
+
def initialize(args, &blk)
|
6
6
|
if args.length == 1 && args[0].kind_of?(Array)
|
7
|
-
raise "It looks like you created a Rubysh::Command with a singleton nested array: #{args.inspect}. That'll never be runnable, and probably indicates you forgot a splat somewhere."
|
7
|
+
raise Rubysh::Error::BaseError.new("It looks like you created a Rubysh::Command with a singleton nested array: #{args.inspect}. That'll never be runnable, and probably indicates you forgot a splat somewhere.")
|
8
8
|
end
|
9
9
|
|
10
10
|
@raw_args = args
|
11
11
|
@directives = []
|
12
12
|
@args = nil
|
13
13
|
@opts = {}
|
14
|
+
@blk = blk
|
14
15
|
|
15
16
|
process_args
|
16
17
|
end
|
@@ -32,6 +33,10 @@ module Rubysh
|
|
32
33
|
arg.to_s
|
33
34
|
end
|
34
35
|
end.compact
|
36
|
+
|
37
|
+
if @args.length > 0 && @blk
|
38
|
+
raise Rubysh::Error::BaseError.new("You can't provide command-line arguments and a block at the same time.")
|
39
|
+
end
|
35
40
|
end
|
36
41
|
|
37
42
|
def stringify
|
@@ -123,7 +128,7 @@ module Rubysh
|
|
123
128
|
# pipeline, which should not win out over internal redirects.
|
124
129
|
directives = extra_directives(runner) + @directives
|
125
130
|
post_forks = base_post_forks + extra_post_forks(runner)
|
126
|
-
state(runner)[:subprocess] = Subprocess.new(args, directives, post_forks, runner)
|
131
|
+
state(runner)[:subprocess] = Subprocess.new(args, blk, directives, post_forks, runner)
|
127
132
|
end
|
128
133
|
end
|
129
134
|
end
|
data/lib/rubysh/subprocess.rb
CHANGED
@@ -14,11 +14,18 @@ module Rubysh
|
|
14
14
|
|
15
15
|
# TODO: switch directives over to an OrderedHash of some form? Really
|
16
16
|
# want to preserve the semantics here.
|
17
|
-
def initialize(args, directives=[], post_fork=[], runner=nil)
|
17
|
+
def initialize(args, blk=nil, directives=[], post_fork=[], runner=nil)
|
18
18
|
raise ArgumentError.new("Must provide an array (#{args.inspect} provided)") unless args.kind_of?(Array)
|
19
|
-
|
19
|
+
|
20
|
+
if args.length > 0 && blk
|
21
|
+
raise ArgumentError.new("Provided both arguments (#{args.inspect}) and a block (#{blk.inspect}). You can only provide one.")
|
22
|
+
elsif args.length == 0 && !blk
|
23
|
+
raise ArgumentError.new("No command specified (#{args.inspect} provided)")
|
24
|
+
end
|
25
|
+
|
20
26
|
@command = args[0]
|
21
27
|
@args = args[1..-1]
|
28
|
+
@blk = blk
|
22
29
|
@directives = directives
|
23
30
|
@runner = runner
|
24
31
|
|
@@ -35,7 +42,7 @@ module Rubysh
|
|
35
42
|
end
|
36
43
|
|
37
44
|
def to_s
|
38
|
-
"Subprocess: command=#{@command.inspect} args=#{@args.inspect} directives: #{@directives.inspect}"
|
45
|
+
"Subprocess: command=#{@command.inspect} blk=#{@blk.inspect} args=#{@args.inspect} directives: #{@directives.inspect}"
|
39
46
|
end
|
40
47
|
|
41
48
|
def run
|
@@ -93,7 +100,12 @@ module Rubysh
|
|
93
100
|
@exec_status.write_only
|
94
101
|
run_post_fork
|
95
102
|
apply_directives_child
|
96
|
-
|
103
|
+
|
104
|
+
if @blk
|
105
|
+
run_blk
|
106
|
+
else
|
107
|
+
exec_program
|
108
|
+
end
|
97
109
|
end
|
98
110
|
|
99
111
|
def run_post_fork
|
@@ -120,6 +132,21 @@ module Rubysh
|
|
120
132
|
end
|
121
133
|
end
|
122
134
|
|
135
|
+
def run_blk
|
136
|
+
# Close the writing end of the pipe
|
137
|
+
@exec_status.read_only
|
138
|
+
|
139
|
+
begin
|
140
|
+
# Run the actual block
|
141
|
+
@blk.call
|
142
|
+
rescue Exception => e
|
143
|
+
render_exception(e)
|
144
|
+
hard_exit(e)
|
145
|
+
else
|
146
|
+
hard_exit(nil)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
123
150
|
def exec_program
|
124
151
|
begin
|
125
152
|
Kernel.exec([command, command], *args)
|
@@ -131,9 +158,9 @@ module Rubysh
|
|
131
158
|
'caller' => e.send(:caller)
|
132
159
|
}
|
133
160
|
@exec_status.dump_json_and_close(msg)
|
134
|
-
#
|
135
|
-
#
|
136
|
-
|
161
|
+
# Abort without running at_exit handlers or giving the user a
|
162
|
+
# chance to accidentally catch the exit.
|
163
|
+
hard_exit(e)
|
137
164
|
else
|
138
165
|
raise Rubysh::Error::UnreachableError.new("This code should be unreachable. If you are seeing this exception, it means someone overrode Kernel.exec. That's not very nice of them.")
|
139
166
|
end
|
@@ -153,5 +180,17 @@ module Rubysh
|
|
153
180
|
|
154
181
|
raise @exec_error if @exec_error
|
155
182
|
end
|
183
|
+
|
184
|
+
def render_exception(e)
|
185
|
+
$stderr.print("[Rubysh subprocess #{$$}] #{e.class}: #{e.message}\n\t")
|
186
|
+
$stderr.print(e.backtrace.join("\n\t"))
|
187
|
+
$stderr.print("\n")
|
188
|
+
end
|
189
|
+
|
190
|
+
# Broken out for the tests
|
191
|
+
def hard_exit(exception)
|
192
|
+
status = exception ? 1 : 0
|
193
|
+
exit!(status)
|
194
|
+
end
|
156
195
|
end
|
157
196
|
end
|
data/lib/rubysh/version.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path('../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Functional
|
4
|
+
class EnvTest < FunctionalTest
|
5
|
+
describe 'when executing a block' do
|
6
|
+
it 'runs the expected code' do
|
7
|
+
result = Rubysh.run(Rubysh.stdout > :stdout) {puts "hi"}
|
8
|
+
|
9
|
+
assert_equal(0, result.exitstatus)
|
10
|
+
output = result.read(:stdout)
|
11
|
+
assert_equal("hi\n", output)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'hard exits from the subprocess' do
|
15
|
+
Rubysh::Subprocess.any_instance.stubs(:render_exception)
|
16
|
+
|
17
|
+
begin
|
18
|
+
result = Rubysh.run {raise "this should in fact get printed"}
|
19
|
+
rescue Exception
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal(1, result.exitstatus)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,10 +2,19 @@ require File.expand_path('../../_lib', File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
module RubyshTest::Unit
|
4
4
|
class SubprocessTest < UnitTest
|
5
|
+
before do
|
6
|
+
Rubysh::Subprocess.any_instance.stubs(:hard_exit).with do |e|
|
7
|
+
raise e if e
|
8
|
+
true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
5
12
|
describe 'when running a command' do
|
6
13
|
it 'calls exec with the expected arguments' do
|
7
14
|
read_fd, write_fd = stub_pipe
|
8
|
-
Kernel.expects(:exec).with
|
15
|
+
Kernel.expects(:exec).with do |*args|
|
16
|
+
assert_equal([['cmd', 'cmd'], 'arg1', 'arg2'], args)
|
17
|
+
end
|
9
18
|
|
10
19
|
proc = Rubysh::Subprocess.new(['cmd', 'arg1', 'arg2'])
|
11
20
|
proc.send(:open_exec_status)
|
@@ -19,13 +28,15 @@ module RubyshTest::Unit
|
|
19
28
|
runner = mock
|
20
29
|
|
21
30
|
read_fd, write_fd = stub_pipe
|
22
|
-
Kernel.expects(:exec).with
|
31
|
+
Kernel.expects(:exec).with do |*args|
|
32
|
+
assert_equal([['cmd', 'cmd'], 'arg1', 'arg2'], args)
|
33
|
+
end
|
23
34
|
|
24
35
|
redirect = Rubysh::Redirect.new(2, '>', 1)
|
25
36
|
redirect.expects(:apply!).with(runner)
|
26
37
|
|
27
38
|
proc = Rubysh::Subprocess.new(['cmd', 'arg1', 'arg2'],
|
28
|
-
[redirect], [], runner)
|
39
|
+
nil, [redirect], [], runner)
|
29
40
|
proc.send(:open_exec_status)
|
30
41
|
assert_raises(Rubysh::Error::UnreachableError) do
|
31
42
|
proc.send(:do_run_child)
|
@@ -33,5 +44,18 @@ module RubyshTest::Unit
|
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
47
|
+
|
48
|
+
describe 'with a block' do
|
49
|
+
it 'executes the block and then calls exit!' do
|
50
|
+
called = false
|
51
|
+
blk = Proc.new {called = true}
|
52
|
+
|
53
|
+
proc = Rubysh::Subprocess.new([], blk)
|
54
|
+
proc.send(:open_exec_status)
|
55
|
+
proc.send(:do_run_child)
|
56
|
+
|
57
|
+
assert(called, "Did not call the block")
|
58
|
+
end
|
59
|
+
end
|
36
60
|
end
|
37
61
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubysh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-05-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -84,10 +84,12 @@ extra_rdoc_files: []
|
|
84
84
|
files:
|
85
85
|
- .gitignore
|
86
86
|
- Gemfile
|
87
|
+
- History.txt
|
87
88
|
- LICENSE
|
88
89
|
- README.md
|
89
90
|
- Rakefile
|
90
91
|
- examples/dots.sh
|
92
|
+
- examples/on_read_example.sh
|
91
93
|
- examples/self_tee_example.rb
|
92
94
|
- lib/rubysh.rb
|
93
95
|
- lib/rubysh/base_command.rb
|
@@ -111,6 +113,7 @@ files:
|
|
111
113
|
- test/_lib.rb
|
112
114
|
- test/functional/_lib.rb
|
113
115
|
- test/functional/lib/env.rb
|
116
|
+
- test/functional/lib/execute_block.rb
|
114
117
|
- test/functional/lib/fd-lister
|
115
118
|
- test/functional/lib/kill.rb
|
116
119
|
- test/functional/lib/leaked_fds.rb
|
@@ -143,7 +146,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
143
146
|
version: '0'
|
144
147
|
segments:
|
145
148
|
- 0
|
146
|
-
hash:
|
149
|
+
hash: 3769802053247508870
|
147
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
151
|
none: false
|
149
152
|
requirements:
|
@@ -152,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
155
|
version: '0'
|
153
156
|
segments:
|
154
157
|
- 0
|
155
|
-
hash:
|
158
|
+
hash: 3769802053247508870
|
156
159
|
requirements: []
|
157
160
|
rubyforge_project:
|
158
161
|
rubygems_version: 1.8.23
|
@@ -166,6 +169,7 @@ test_files:
|
|
166
169
|
- test/_lib.rb
|
167
170
|
- test/functional/_lib.rb
|
168
171
|
- test/functional/lib/env.rb
|
172
|
+
- test/functional/lib/execute_block.rb
|
169
173
|
- test/functional/lib/fd-lister
|
170
174
|
- test/functional/lib/kill.rb
|
171
175
|
- test/functional/lib/leaked_fds.rb
|