pvc 0.0.1 → 0.0.2
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/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile.lock +4 -0
- data/README.md +34 -4
- data/lib/pvc/block_piece.rb +43 -0
- data/lib/pvc/input_piece.rb +37 -0
- data/lib/pvc/null_piece.rb +32 -0
- data/lib/pvc/only_err_piece.rb +44 -0
- data/lib/pvc/pipeline.rb +74 -0
- data/lib/pvc/process_piece.rb +41 -0
- data/lib/pvc/result.rb +32 -0
- data/lib/pvc/result_piece.rb +69 -0
- data/lib/pvc/version.rb +1 -1
- data/lib/pvc/with_err_piece.rb +48 -0
- data/lib/pvc.rb +12 -0
- data/pvc.gemspec +3 -2
- data/spec/pvc/integration_spec.rb +109 -0
- data/spec/ruby/pipe_io_spec.rb +64 -0
- data/spec/spec_helper.rb +18 -0
- metadata +33 -3
data/.rspec
ADDED
data/Gemfile.lock
CHANGED
@@ -2,11 +2,15 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
pvc (0.0.1)
|
5
|
+
childprocess
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: http://rubygems.org/
|
8
9
|
specs:
|
10
|
+
childprocess (0.3.6)
|
11
|
+
ffi (~> 1.0, >= 1.0.6)
|
9
12
|
diff-lcs (1.1.3)
|
13
|
+
ffi (1.3.1)
|
10
14
|
rspec (2.12.0)
|
11
15
|
rspec-core (~> 2.12.0)
|
12
16
|
rspec-expectations (~> 2.12.0)
|
data/README.md
CHANGED
@@ -7,9 +7,6 @@ Pipe between processes as easily as in the shell
|
|
7
7
|
# run the specs
|
8
8
|
bundle exec rspec spec
|
9
9
|
|
10
|
-
# run the simulation interactively (see description below)
|
11
|
-
./bin/gorobo
|
12
|
-
|
13
10
|
## Install it as a RubyGem
|
14
11
|
|
15
12
|
This code is packaged as a Gem. If you like, you can build and install it by running:
|
@@ -17,8 +14,41 @@ This code is packaged as a Gem. If you like, you can build and install it by run
|
|
17
14
|
gem build pvc.gemspec
|
18
15
|
gem install pvc-*.gem
|
19
16
|
|
20
|
-
##
|
17
|
+
## Synopsis - implemented
|
18
|
+
|
19
|
+
# Run a single process
|
20
|
+
PVC.new("echo hello").run
|
21
|
+
|
22
|
+
# Or pipe from one process to another
|
23
|
+
PVC.new("echo hello").to("tr h H").run
|
24
|
+
|
25
|
+
# Get individual or several outputs from the final result
|
26
|
+
PVC.new("echo hello && ls doesnotexist").run.stdout # => "hello\n"
|
27
|
+
PVC.new("echo hello && ls doesnotexist").run.stderr # => "ls: doesnotexist: No such file or directory\n"
|
28
|
+
PVC.new("echo hello && ls doesnotexist").run.stdboth # => "hello\nls: doesnotexist: No such file or directory\n"
|
29
|
+
PVC.new("echo hello && ls doesnotexist").run.code # => 1
|
30
|
+
stderr, code = PVC.new("echo hello && ls doesnotexist").run.get(:stderr, :code) # => ["ls: doesnotexist: No such file or directory\n", 1]
|
31
|
+
|
32
|
+
# Input a string into stdin
|
33
|
+
PVC.new.input("one\ntwo\nthree\n").to("sort -r").run.stdout # => "two\nthree\none\n"
|
34
|
+
|
35
|
+
# Process intermediate results with Ruby
|
36
|
+
PVC.new("cat some.log").to { |i,o| i.each_line { |line| o.puts line if line.match(/ERROR/) } }.to("tail -n10").run
|
37
|
+
|
38
|
+
# Mix stderr and stdin at some point in a pipeline
|
39
|
+
PVC.new("echo hello && ls doesnotexist").with_err.to("wc -l").run.stdout # => " 2\n"
|
40
|
+
|
41
|
+
# Pass on only stderr at some point in a pipeline
|
42
|
+
PVC.new("echo hello && ls doesnotexist").only_err.to("wc -l").run.stdout # => " 1\n"
|
43
|
+
|
44
|
+
# Insert one pipeline into another
|
45
|
+
upcase_unique_pipeline = PVC.new("tr a-z A-Z").to("uniq")
|
46
|
+
PVC.new.input("hello\nHeLlO\nworld\nWoRlD\n").to(upcase_unique_pipeline).to("sort -r").run.stdout # => "WORLD\nHELLO"
|
47
|
+
|
48
|
+
## Synopsis - unimplemented
|
21
49
|
|
50
|
+
# Kill run if it does not finish in time (miliseconds)
|
51
|
+
PVC.new("sleep 2").run(:timeout => 1000)
|
22
52
|
|
23
53
|
## Compatibility
|
24
54
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module PVC
|
2
|
+
class BlockPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
|
6
|
+
def initialize(&block)
|
7
|
+
@block = block
|
8
|
+
@read, @write = IO.pipe
|
9
|
+
@read.close_on_exec = true
|
10
|
+
@write.close_on_exec = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def stdin
|
14
|
+
@write
|
15
|
+
end
|
16
|
+
|
17
|
+
def start(following_piece)
|
18
|
+
@return = nil
|
19
|
+
@thread = Thread.new do
|
20
|
+
@return = @block.call(@read, following_piece.stdin)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def finish
|
25
|
+
@write.close
|
26
|
+
@thread.join
|
27
|
+
@read.close
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :return
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(&block)
|
34
|
+
@block = block
|
35
|
+
end
|
36
|
+
|
37
|
+
def runner
|
38
|
+
Runner.new(&@block)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module PVC
|
2
|
+
class InputPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
def initialize(input)
|
6
|
+
@input = input
|
7
|
+
@read, @write = IO.pipe
|
8
|
+
@read.close_on_exec = true
|
9
|
+
@write.close_on_exec = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def stdin
|
13
|
+
@write
|
14
|
+
end
|
15
|
+
|
16
|
+
def start(following_piece)
|
17
|
+
following_piece.stdin.write(@input)
|
18
|
+
following_piece.stdin.flush
|
19
|
+
end
|
20
|
+
|
21
|
+
def finish
|
22
|
+
@write.close
|
23
|
+
@read.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(input)
|
28
|
+
@input = input
|
29
|
+
end
|
30
|
+
|
31
|
+
def runner
|
32
|
+
Runner.new(@input)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PVC
|
2
|
+
class NullPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
def initialize
|
6
|
+
@read, @write = IO.pipe
|
7
|
+
@read.close_on_exec = true
|
8
|
+
@write.close_on_exec = true
|
9
|
+
end
|
10
|
+
|
11
|
+
def stdin
|
12
|
+
@write
|
13
|
+
end
|
14
|
+
|
15
|
+
def start(following=nil)
|
16
|
+
# do nothing
|
17
|
+
end
|
18
|
+
|
19
|
+
def finish
|
20
|
+
@write.close
|
21
|
+
@read.close
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def runner
|
27
|
+
Runner.new
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PVC
|
2
|
+
class OnlyErrPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
def initialize
|
6
|
+
@stdread, @stdwrite = IO.pipe
|
7
|
+
@errread, @errwrite = IO.pipe
|
8
|
+
@stdread.close_on_exec = true
|
9
|
+
@stdwrite.close_on_exec = true
|
10
|
+
@errread.close_on_exec = true
|
11
|
+
@errwrite.close_on_exec = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def stdin
|
15
|
+
@stdwrite
|
16
|
+
end
|
17
|
+
|
18
|
+
def errin
|
19
|
+
@errwrite
|
20
|
+
end
|
21
|
+
|
22
|
+
def start(following_piece)
|
23
|
+
@errthread = Thread.new do
|
24
|
+
@errread.each_line { |line| following_piece.stdin.puts line }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def finish
|
29
|
+
@stdwrite.close
|
30
|
+
@errwrite.close
|
31
|
+
@errthread.join
|
32
|
+
@stdread.close
|
33
|
+
@errread.close
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def runner
|
39
|
+
Runner.new
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/lib/pvc/pipeline.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "childprocess"
|
2
|
+
|
3
|
+
require "pvc/block_piece"
|
4
|
+
require "pvc/null_piece"
|
5
|
+
require "pvc/process_piece"
|
6
|
+
require "pvc/with_err_piece"
|
7
|
+
require "pvc/only_err_piece"
|
8
|
+
require "pvc/result_piece"
|
9
|
+
require "pvc/input_piece"
|
10
|
+
require "pvc/result"
|
11
|
+
|
12
|
+
module PVC
|
13
|
+
class Pipeline
|
14
|
+
|
15
|
+
def initialize(*args, &block)
|
16
|
+
@pieces = []
|
17
|
+
if args.length > 0 || block_given?
|
18
|
+
self.to(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to(*args, &block)
|
23
|
+
if block_given?
|
24
|
+
@pieces << BlockPiece.new(&block)
|
25
|
+
elsif args.length == 1 && args.first.respond_to?(:pieces)
|
26
|
+
args.first.pieces.each { |piece| @pieces << piece }
|
27
|
+
else
|
28
|
+
@pieces << ProcessPiece.new(*args)
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def input(input)
|
34
|
+
@pieces << InputPiece.new(input)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_err
|
39
|
+
@pieces << WithErrPiece.new
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def only_err
|
44
|
+
@pieces << OnlyErrPiece.new
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
runners = ([NullPiece.new] + @pieces + [ResultPiece.new]).map(&:runner)
|
50
|
+
|
51
|
+
runners.zip(runners[1..-1]).reverse.each do |current, following|
|
52
|
+
current.start(following)
|
53
|
+
end
|
54
|
+
|
55
|
+
runners.each do |current|
|
56
|
+
current.finish
|
57
|
+
end
|
58
|
+
|
59
|
+
Result.new(
|
60
|
+
:stdout => runners.last.stdout,
|
61
|
+
:stderr => runners.last.stderr,
|
62
|
+
:stdboth => runners.last.stdboth,
|
63
|
+
:codes => runners.inject([]) { |codes, runner| codes << runner.code if runner.respond_to?(:code); codes },
|
64
|
+
:returns => runners.inject([]) { |returns, runner| returns << runner.return if runner.respond_to?(:return); returns }
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
attr_reader :pieces
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module PVC
|
2
|
+
class ProcessPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
def initialize(*args)
|
6
|
+
@args = args
|
7
|
+
@process = ChildProcess.build(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def stdin
|
11
|
+
@process.io.stdin
|
12
|
+
end
|
13
|
+
|
14
|
+
def start(following_piece)
|
15
|
+
@process.duplex = true
|
16
|
+
@process.io.stdout = following_piece.stdin
|
17
|
+
@process.io.stderr = following_piece.errin if following_piece.respond_to?(:errin)
|
18
|
+
@process.start
|
19
|
+
end
|
20
|
+
|
21
|
+
def finish
|
22
|
+
@process.io.stdin.close
|
23
|
+
@process.wait
|
24
|
+
end
|
25
|
+
|
26
|
+
def code
|
27
|
+
@process.exit_code
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(args)
|
32
|
+
@args = args
|
33
|
+
end
|
34
|
+
|
35
|
+
def runner
|
36
|
+
Runner.new(@args)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/pvc/result.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module PVC
|
2
|
+
class Result
|
3
|
+
|
4
|
+
attr_reader :stdout
|
5
|
+
attr_reader :stderr
|
6
|
+
attr_reader :stdboth
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
@stdout = args[:stdout]
|
10
|
+
@stderr = args[:stderr]
|
11
|
+
@stdboth = args[:stdboth]
|
12
|
+
@returns = args[:returns]
|
13
|
+
@codes = args[:codes]
|
14
|
+
end
|
15
|
+
|
16
|
+
def return
|
17
|
+
@returns.last
|
18
|
+
end
|
19
|
+
|
20
|
+
def code
|
21
|
+
@codes.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(*requested_outputs)
|
25
|
+
allowed_outputs = [:stdout, :stderr, :stdboth, :return, :code]
|
26
|
+
raise "No such output to get!" unless (requested_outputs-allowed_outputs)==[]
|
27
|
+
requested_outputs.map { |output_kind| self.send(output_kind) }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module PVC
|
2
|
+
class ResultPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
def initialize
|
6
|
+
@stdread, @stdwrite = IO.pipe
|
7
|
+
@errread, @errwrite = IO.pipe
|
8
|
+
@stdread.close_on_exec = true
|
9
|
+
@stdwrite.close_on_exec = true
|
10
|
+
@errread.close_on_exec = true
|
11
|
+
@errwrite.close_on_exec = true
|
12
|
+
@stdout = []
|
13
|
+
@stderr = []
|
14
|
+
@stdboth = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def stdin
|
18
|
+
@stdwrite
|
19
|
+
end
|
20
|
+
|
21
|
+
def errin
|
22
|
+
@errwrite
|
23
|
+
end
|
24
|
+
|
25
|
+
def start(following=nil)
|
26
|
+
@stdthread = Thread.new do
|
27
|
+
@stdread.each_line do |line|
|
28
|
+
@stdout << line
|
29
|
+
@stdboth << line
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@errthread = Thread.new do
|
33
|
+
@errread.each_line do |line|
|
34
|
+
@stderr << line
|
35
|
+
@stdboth << line
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def finish
|
41
|
+
@stdwrite.close
|
42
|
+
@errwrite.close
|
43
|
+
@stdthread.join
|
44
|
+
@errthread.join
|
45
|
+
@stdread.close
|
46
|
+
@errread.close
|
47
|
+
end
|
48
|
+
|
49
|
+
def stdout
|
50
|
+
@stdout.join("")
|
51
|
+
end
|
52
|
+
|
53
|
+
def stderr
|
54
|
+
@stderr.join("")
|
55
|
+
end
|
56
|
+
|
57
|
+
def stdboth
|
58
|
+
@stdboth.join("")
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def runner
|
64
|
+
Runner.new
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
data/lib/pvc/version.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
module PVC
|
2
|
+
class WithErrPiece
|
3
|
+
|
4
|
+
class Runner
|
5
|
+
def initialize
|
6
|
+
@stdread, @stdwrite = IO.pipe
|
7
|
+
@errread, @errwrite = IO.pipe
|
8
|
+
@stdread.close_on_exec = true
|
9
|
+
@stdwrite.close_on_exec = true
|
10
|
+
@errread.close_on_exec = true
|
11
|
+
@errwrite.close_on_exec = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def stdin
|
15
|
+
@stdwrite
|
16
|
+
end
|
17
|
+
|
18
|
+
def errin
|
19
|
+
@errwrite
|
20
|
+
end
|
21
|
+
|
22
|
+
def start(following_piece)
|
23
|
+
@stdthread = Thread.new do
|
24
|
+
@stdread.each_line { |line| following_piece.stdin.puts line }
|
25
|
+
end
|
26
|
+
@errthread = Thread.new do
|
27
|
+
@errread.each_line { |line| following_piece.stdin.puts line }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def finish
|
32
|
+
@stdwrite.close
|
33
|
+
@errwrite.close
|
34
|
+
@stdthread.join
|
35
|
+
@errthread.join
|
36
|
+
@stdread.close
|
37
|
+
@errread.close
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def runner
|
43
|
+
Runner.new
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
data/lib/pvc.rb
CHANGED
data/pvc.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = PVC::VERSION
|
8
8
|
s.authors = ["Chris Berkhout"]
|
9
9
|
s.email = ["chrisberkhout@gmail.com"]
|
10
|
-
s.homepage = "http://
|
10
|
+
s.homepage = "http://github.com/chrisberkhout/pvc"
|
11
11
|
s.summary = %q{Easy piping between processes}
|
12
12
|
|
13
13
|
s.rubyforge_project = "pvc"
|
@@ -17,7 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
-
|
20
|
+
s.add_runtime_dependency "childprocess"
|
21
|
+
|
21
22
|
s.add_development_dependency "rspec"
|
22
23
|
|
23
24
|
s.required_ruby_version = '>= 1.9.0'
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "pvc"
|
2
|
+
|
3
|
+
describe "pvc" do
|
4
|
+
|
5
|
+
describe "(synopsis tests)" do
|
6
|
+
|
7
|
+
it "should run a single process" do
|
8
|
+
PVC.new("echo hello").run.stdout.should == "hello\n"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should pipe from one process to another" do
|
12
|
+
PVC.new("echo hello").to("tr h H").run.stdout.should == "Hello\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should let you get stdout" do
|
16
|
+
PVC.new("echo hello && ls doesnotexist").run.stdout.should == "hello\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should let you get stderr" do
|
20
|
+
PVC.new("echo hello && ls doesnotexist").run.stderr.should == "ls: doesnotexist: No such file or directory\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should let you get stdout and stderr together" do
|
24
|
+
PVC.new("echo hello && ls doesnotexist").run.stdboth.should == "hello\nls: doesnotexist: No such file or directory\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should let you get the exit code of the last process" do
|
28
|
+
PVC.new("echo hello").run.code.should == 0
|
29
|
+
PVC.new("echo hello && ls doesnotexist").run.code.should == 1
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should let you get several outputs from the final result" do
|
33
|
+
PVC.new("echo hello && ls doesnotexist").run.get(:stderr, :code).should == ["ls: doesnotexist: No such file or directory\n", 1]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should let you input into the stdin" do
|
37
|
+
PVC.new.input("one\ntwo\nthree\n").to("sort -r").run.stdout.should == "two\nthree\none\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should let you process intermediate results with Ruby" do
|
41
|
+
PVC.new.input("one\ntwo\nthree\n").to do |i,o|
|
42
|
+
i.each_line { |line| o.puts line if line.match(/^t/) }
|
43
|
+
end.to("cat").run.stdout.should == "two\nthree\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should let you mix stderr and stdin at some point in a pipeline" do
|
47
|
+
PVC.new("echo hello && ls doesnotexist").with_err.to("wc -l").run.stdout.should == " 2\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should let you pass on only stderr at some point in a pipeline" do
|
51
|
+
PVC.new("echo hello && ls doesnotexist").only_err.to("wc -l").run.stdout.should == " 1\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should let you insert one pipeline into another" do
|
55
|
+
upcase_unique_pipeline = PVC.new("tr a-z A-Z").to("uniq")
|
56
|
+
string = "hello\nHeLlO\nworld\nWoRlD\n"
|
57
|
+
PVC.new.input(string).to(upcase_unique_pipeline).to("sort -r").run.stdout.should == "WORLD\nHELLO\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "(original manual tests)" do
|
63
|
+
|
64
|
+
let(:log) { [] }
|
65
|
+
|
66
|
+
it "should work with 2 shell commands and a block" do
|
67
|
+
PVC.new.
|
68
|
+
to("echo BBB && echo AAA").
|
69
|
+
to("sort").
|
70
|
+
to do |input, output|
|
71
|
+
input.each_line { |line| log << line.chomp }
|
72
|
+
end.run
|
73
|
+
|
74
|
+
log.should == %w{AAA BBB}
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should work with sevearal blocks" do
|
78
|
+
PVC.new.
|
79
|
+
to("echo BBB && echo AAA").
|
80
|
+
to("sort").
|
81
|
+
to do |input, output|
|
82
|
+
input.each_line { |line| output.write line }
|
83
|
+
end.
|
84
|
+
to do |input, output|
|
85
|
+
input.each_line { |line| log << line.chomp }
|
86
|
+
end.run
|
87
|
+
|
88
|
+
log.should == %w{AAA BBB}
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should work with sevearal blocks separated by a shell command" do
|
92
|
+
PVC.new.
|
93
|
+
to("echo BBB && echo AAA").
|
94
|
+
to("sort").
|
95
|
+
to do |input, output|
|
96
|
+
input.each_line { |line| output.write line }
|
97
|
+
end.
|
98
|
+
to("cat").
|
99
|
+
to do |input, output|
|
100
|
+
input.each_line { |line| log << line.chomp }
|
101
|
+
end.run
|
102
|
+
|
103
|
+
log.should == %w{AAA BBB}
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "childprocess"
|
2
|
+
|
3
|
+
describe "pipe io" do
|
4
|
+
|
5
|
+
def fork_childprocess
|
6
|
+
ChildProcess.unix?.should == true && ChildProcess.posix_spawn?.should == false # to ensure this does fork+exec
|
7
|
+
process = ChildProcess.build("cat")
|
8
|
+
process.duplex = true # to make stdin available on start
|
9
|
+
process.start
|
10
|
+
yield if block_given?
|
11
|
+
process.io.stdin.close
|
12
|
+
process.wait
|
13
|
+
end
|
14
|
+
|
15
|
+
# see also the example at: http://rubydoc.info/stdlib/core/1.9.3/IO.pipe
|
16
|
+
|
17
|
+
it "will not show EOF on close if a fork has an open copy of the file handle" do
|
18
|
+
read, write = IO.pipe
|
19
|
+
fork_childprocess do
|
20
|
+
write.close
|
21
|
+
expect { read.read_nonblock(1) }.to raise_error(Errno::EAGAIN) # not yet EOF
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should show EOF on close if the fork has closed its copy of the file handle (by exiting)" do
|
26
|
+
read, write = IO.pipe
|
27
|
+
fork_childprocess
|
28
|
+
write.close
|
29
|
+
expect { read.read_nonblock(1) }.to raise_error(EOFError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should show EOF on close if the fork has closed its copy of the file handle (by closing on exec)" do
|
33
|
+
read, write = IO.pipe
|
34
|
+
write.close_on_exec = true
|
35
|
+
fork_childprocess do
|
36
|
+
write.close
|
37
|
+
expect { read.read_nonblock(1) }.to raise_error(EOFError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not close everywhere if closed in a fork" do
|
42
|
+
read, write = IO.pipe
|
43
|
+
read.close_on_exec = true
|
44
|
+
write.close_on_exec = true
|
45
|
+
fork_childprocess do
|
46
|
+
read.closed?.should be_false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should get a final unterminated line via #each_line" do
|
51
|
+
read, write = IO.pipe
|
52
|
+
lines_read = []
|
53
|
+
thread = Thread.new do
|
54
|
+
read.each_line { |line| lines_read << line }
|
55
|
+
end
|
56
|
+
write.puts "terminated line"
|
57
|
+
write.print "unterminated line"
|
58
|
+
write.close
|
59
|
+
thread.join
|
60
|
+
lines_read.should == ["terminated line\n", "unterminated line"]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
18
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pvc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: childprocess
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: rspec
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -35,15 +51,27 @@ extensions: []
|
|
35
51
|
extra_rdoc_files: []
|
36
52
|
files:
|
37
53
|
- .gitignore
|
54
|
+
- .rspec
|
38
55
|
- Gemfile
|
39
56
|
- Gemfile.lock
|
40
57
|
- README.md
|
41
58
|
- Rakefile
|
42
59
|
- lib/pvc.rb
|
60
|
+
- lib/pvc/block_piece.rb
|
61
|
+
- lib/pvc/input_piece.rb
|
62
|
+
- lib/pvc/null_piece.rb
|
63
|
+
- lib/pvc/only_err_piece.rb
|
64
|
+
- lib/pvc/pipeline.rb
|
65
|
+
- lib/pvc/process_piece.rb
|
66
|
+
- lib/pvc/result.rb
|
67
|
+
- lib/pvc/result_piece.rb
|
43
68
|
- lib/pvc/version.rb
|
69
|
+
- lib/pvc/with_err_piece.rb
|
44
70
|
- pvc.gemspec
|
71
|
+
- spec/pvc/integration_spec.rb
|
72
|
+
- spec/ruby/pipe_io_spec.rb
|
45
73
|
- spec/spec_helper.rb
|
46
|
-
homepage: http://
|
74
|
+
homepage: http://github.com/chrisberkhout/pvc
|
47
75
|
licenses: []
|
48
76
|
post_install_message:
|
49
77
|
rdoc_options: []
|
@@ -68,5 +96,7 @@ signing_key:
|
|
68
96
|
specification_version: 3
|
69
97
|
summary: Easy piping between processes
|
70
98
|
test_files:
|
99
|
+
- spec/pvc/integration_spec.rb
|
100
|
+
- spec/ruby/pipe_io_spec.rb
|
71
101
|
- spec/spec_helper.rb
|
72
102
|
has_rdoc:
|