coque 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b7ea60eb553722ddd8d3a150306e31dad188e477
4
- data.tar.gz: bbbd233b0459043ccf194fbbf54618058eb3aae2
2
+ SHA256:
3
+ metadata.gz: 6f9a270751ea085a375d71bc3b7a7ccd34237f4094dce63ffbffb66005b123ff
4
+ data.tar.gz: 89e0e4065a6eb61c27c1b41e2ecd1dd84470b83ba934e3421ea8ee119840d5cd
5
5
  SHA512:
6
- metadata.gz: f23b1f42df3acef474f60ef0e0f71507b7cc99bf4d0eacf47e0cd22def9d99e4ea41210d5574056d4e9eed00d7f8f87a336096beba2fad551d7c3dc5690bfd44
7
- data.tar.gz: 477b28f76c596fbbf679990fb8d6f62c42a8d09bd88ba8de993779017bd382dff6044c3ef2dff7aa21ded24c26ba3277a43594a9ef5f0789614354ae97ef3d4d
6
+ metadata.gz: 64b833a5a39cc7ed3a65339af2ac13c0a3f0e0461247cd899c69deab5a9a7d30ffccb7bae4da16a7fed175638a59a19aaa43c2335aba109a5105b7b27e0f5682
7
+ data.tar.gz: 853b5a31c91d099dc88e0881672c4fa8b465bca04ae70d553dcac9daed4c21f29501a4818fb4b04a42d8bc6e1ab021c32ae5e4a411dc3d5e3a1f1a7e44dc5aa8
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ *.gem
data/.travis.yml CHANGED
@@ -1,5 +1,4 @@
1
- sudo: false
2
1
  language: ruby
3
- rvm:
4
- - 2.3.0
5
- before_install: gem install bundler -v 1.16.1
2
+ before_install:
3
+ - gem update --system
4
+ - gem install bundler
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- coque (0.5.0)
4
+ coque (0.8.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,7 +10,7 @@ GEM
10
10
  builder (3.2.3)
11
11
  coderay (1.1.2)
12
12
  docile (1.3.1)
13
- json (2.1.0)
13
+ json (2.3.1)
14
14
  method_source (0.9.0)
15
15
  minitest (5.11.3)
16
16
  minitest-reporters (1.2.0)
@@ -21,7 +21,7 @@ GEM
21
21
  pry (0.11.2)
22
22
  coderay (~> 1.1.0)
23
23
  method_source (~> 0.9.0)
24
- rake (12.3.1)
24
+ rake (13.0.1)
25
25
  ruby-progressbar (1.9.0)
26
26
  simplecov (0.16.1)
27
27
  docile (~> 1.1)
@@ -33,13 +33,13 @@ PLATFORMS
33
33
  ruby
34
34
 
35
35
  DEPENDENCIES
36
- bundler (~> 1.16)
36
+ bundler (~> 2.0)
37
37
  coque!
38
38
  minitest (~> 5.11)
39
39
  minitest-reporters
40
40
  pry
41
- rake (~> 12.3)
41
+ rake (~> 13.0)
42
42
  simplecov
43
43
 
44
44
  BUNDLED WITH
45
- 1.16.2
45
+ 2.2.24
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Coque
2
2
 
3
+ [![Build Status](https://travis-ci.org/worace/coque.svg?branch=master)](https://travis-ci.org/worace/coque)
4
+ [![Gem Version](https://badge.fury.io/rb/coque.svg)](https://badge.fury.io/rb/coque)
5
+
3
6
  Create, manage, and interop with shell pipelines from Ruby. Like [Plumbum](https://plumbum.readthedocs.io/en/latest/), for Ruby, with native (Ruby) code streaming integration.
4
7
 
5
8
  ## Installation
@@ -99,6 +102,88 @@ Coque also includes a `Coque.source` helper for feeding Ruby enumerables into sh
99
102
  # => ["500"]
100
103
  ```
101
104
 
105
+ #### Asynchrony and Waiting on Processes
106
+
107
+ Running a Coque command forks a new process, and by default these processes run asynchronously. Calling `.run` on a Coque command or pipeline returns a `Coque::Result` object which can be used to get the output (`.to_a`) or exit code (`.exit_code`) of the process:
108
+
109
+ ```rb
110
+ result = Coque['echo', 'hi'].run
111
+ # => #<Coque::Result:0x000055da63437838 @out=#<IO:fd 15>, @pid=29236>
112
+ puts "its running in the background..."
113
+ its running in the background...
114
+ result.to_a
115
+ # => ["hi"]
116
+ result.exit_code
117
+ # => 0
118
+ ```
119
+
120
+ However you can also just use `.wait` to block on a process while it runs:
121
+
122
+ ```rb
123
+ result = Coque['echo', 'hi'].run.wait
124
+ # => #<Coque::Result:0x000055da633c98b0 @exit_code=0, @out=#<IO:fd 17>, @pid=29536>
125
+ ```
126
+
127
+ Or, use `.run!` to block on the process _and_ raise an exception if it exits with a non-zero response:
128
+
129
+ ```
130
+ Coque["head", "/usr/share/dict/words"].run!
131
+ # => nil
132
+ Coque["head", "/usr/share/dict/pizza"].run!
133
+ # head: cannot open '/usr/share/dict/pizza' for reading: No such file or directory
134
+ # RuntimeError: Coque Command Failed: <Coque::Sh head /usr/share/dict/pizza>
135
+ from /home/horace/.gem/ruby/2.4.4/gems/coque-0.7.1/lib/coque/runnable.rb:13:in `run!'
136
+ ```
137
+
138
+ There's also a `to_a!` variant on commands which combines the error handling of `run!` with the array-slurping of stdout:
139
+
140
+ ```
141
+ Coque['head', '-n 1', '/usr/share/dict/words'].to_a!
142
+ => ["A"]
143
+
144
+ Coque['head', '-n 1', '/usr/share/dict/asdf'].to_a!
145
+ head: cannot open '/usr/share/dict/asdf' for reading: No such file or directory
146
+ RuntimeError: Coque Command Failed: <Coque::Sh head -n 1 /usr/share/dict/asdf>
147
+ from /code/coque/lib/coque/runnable.rb:11:in `to_a!'
148
+ ```
149
+
150
+ #### Named (Non-Operator) Method Alternatives
151
+
152
+ The main piping and redirection methods also include named alternatives:
153
+
154
+ * `|` is aliased to `pipe`
155
+ * `>` is aliased to `out`
156
+ * `>=` is aliased to `err`
157
+ * `<` is aliased to `in`
158
+
159
+ So these 2 invocations are equivalent:
160
+
161
+ ```rb
162
+ (Coque["echo", "hi"] | Coque["wc", "-c"] > STDERR).run!
163
+ # is the same as...
164
+ Coque["echo", "hi"].pipe(Coque["wc", "-c"]).out(STDERR).run!
165
+ ```
166
+
167
+ #### Logging
168
+
169
+ You can set a logger for Coque, which will be used to output messages when commands are executed:
170
+
171
+ ```rb
172
+ Coque.logger = Logger.new(STDOUT)
173
+ (Coque["echo", "hi"] | Coque["wc", "-c"]).run!
174
+ ```
175
+
176
+ Will log:
177
+
178
+ ```
179
+ I, [2019-02-20T20:31:00.325777 #16749] INFO -- : Executing Coque Command: <Pipeline <Coque::Sh echo hi> | <Coque::Sh wc -c> >
180
+ I, [2019-02-20T20:31:00.325971 #16749] INFO -- : Executing Coque Command: <Coque::Sh echo hi>
181
+ I, [2019-02-20T20:31:00.327719 #16749] INFO -- : Coque Command: <Coque::Sh echo hi> finished in 0.001683 seconds.
182
+ I, [2019-02-20T20:31:00.327771 #16749] INFO -- : Executing Coque Command: <Coque::Sh wc -c>
183
+ I, [2019-02-20T20:31:00.329586 #16749] INFO -- : Coque Command: <Coque::Sh wc -c> finished in 0.001739 seconds.
184
+ I, [2019-02-20T20:31:00.329725 #16749] INFO -- : Coque Command: <Pipeline <Coque::Sh echo hi> | <Coque::Sh wc -c> > finished in 0.003796 seconds.
185
+ ```
186
+
102
187
  ### Streaming Performance
103
188
 
104
189
  Should be little overhead compared with the equivalent pipeline from a standard shell.
data/coque.gemspec CHANGED
@@ -30,8 +30,8 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_development_dependency "bundler", "~> 1.16"
34
- spec.add_development_dependency "rake", "~> 12.3"
33
+ spec.add_development_dependency "bundler", "~> 2.0"
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
35
  spec.add_development_dependency "minitest", "~> 5.11"
36
36
  spec.add_development_dependency "minitest-reporters"
37
37
  spec.add_development_dependency "pry"
data/lib/coque.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "coque/redirectable"
2
+ require "coque/runnable"
2
3
  require "coque/cmd"
3
4
  require "coque/sh"
4
5
  require "coque/rb"
@@ -9,6 +10,15 @@ require "coque/result"
9
10
  require "coque/version"
10
11
 
11
12
  module Coque
13
+ @@logger = nil
14
+ def self.logger=(logger)
15
+ @@logger = logger
16
+ end
17
+
18
+ def self.logger
19
+ @@logger
20
+ end
21
+
12
22
  def self.context(dir: Dir.pwd, env: {}, disinherits_env: false)
13
23
  Context.new(dir, env, disinherits_env)
14
24
  end
data/lib/coque/cmd.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  module Coque
2
2
  class Cmd
3
3
  include Redirectable
4
+ include Runnable
4
5
  attr_reader :context
5
6
 
6
- def |(other)
7
+ def pipe(other)
7
8
  case other
8
9
  when Cmd
9
10
  Pipeline.new([self.clone, other.clone])
@@ -12,6 +13,10 @@ module Coque
12
13
  end
13
14
  end
14
15
 
16
+ def |(other)
17
+ pipe(other)
18
+ end
19
+
15
20
  def clone
16
21
  raise "Not Implemented - Override"
17
22
  end
@@ -1,6 +1,7 @@
1
1
  module Coque
2
2
  class Pipeline
3
3
  include Redirectable
4
+ include Runnable
4
5
 
5
6
  attr_reader :commands
6
7
  def initialize(commands = [])
@@ -15,7 +16,7 @@ module Coque
15
16
  "<Pipeline #{commands.join(" | ")} >"
16
17
  end
17
18
 
18
- def |(other)
19
+ def pipe(other)
19
20
  case other
20
21
  when Pipeline
21
22
  Pipeline.new(commands + other.commands)
@@ -24,6 +25,10 @@ module Coque
24
25
  end
25
26
  end
26
27
 
28
+ def |(other)
29
+ pipe(other)
30
+ end
31
+
27
32
  def stitch
28
33
  # Set head in
29
34
  if commands.first.stdin.nil?
@@ -52,7 +57,7 @@ module Coque
52
57
  end
53
58
  end
54
59
 
55
- def run
60
+ def get_result
56
61
  stdout = stitch
57
62
  results = commands.map(&:run)
58
63
  Result.new(results.last.pid, stdout)
data/lib/coque/rb.rb CHANGED
@@ -31,7 +31,7 @@ module Coque
31
31
  self
32
32
  end
33
33
 
34
- def run
34
+ def get_result
35
35
  stdin, stdoutr, stdoutw = get_default_fds
36
36
 
37
37
  pid = fork do
@@ -4,24 +4,36 @@ module Coque
4
4
  module Redirectable
5
5
  attr_reader :stdin, :stdout, :stderr
6
6
 
7
- def >(io)
7
+ def out(io)
8
8
  clone.tap do |c|
9
9
  c.stdout = io
10
10
  end
11
11
  end
12
12
 
13
- def <(io)
13
+ def >(io)
14
+ out(io)
15
+ end
16
+
17
+ def in(io)
14
18
  clone.tap do |c|
15
19
  c.stdin = io
16
20
  end
17
21
  end
18
22
 
19
- def >=(io)
23
+ def <(io)
24
+ self.in(io)
25
+ end
26
+
27
+ def err(io)
20
28
  clone.tap do |c|
21
29
  c.stderr = io
22
30
  end
23
31
  end
24
32
 
33
+ def >=(io)
34
+ err(io)
35
+ end
36
+
25
37
  def getio(io, mode = "r")
26
38
  case io
27
39
  when STDERR
@@ -53,20 +65,6 @@ module Coque
53
65
  @stdin = getio(s, "r")
54
66
  end
55
67
 
56
- def to_a
57
- run.to_a
58
- end
59
-
60
- def success?
61
- run.success?
62
- end
63
-
64
- def run!
65
- if !success?
66
- raise "Coque Command Failed: #{self}"
67
- end
68
- end
69
-
70
68
  private
71
69
 
72
70
  def stdout_read
data/lib/coque/result.rb CHANGED
@@ -24,7 +24,7 @@ class Coque::Result
24
24
  end
25
25
 
26
26
  def wait
27
- _, status = Process.waitpid2(pid)
27
+ _pid, status = Process.waitpid2(pid)
28
28
  @exit_code = status.exitstatus
29
29
  self
30
30
  end
@@ -0,0 +1,39 @@
1
+ module Coque
2
+ module Runnable
3
+ def to_a
4
+ run.to_a
5
+ end
6
+
7
+ def to_a!
8
+ res = run
9
+ rows = res.to_a
10
+ unless res.exit_code == 0
11
+ raise "Coque Command Failed: #{self}"
12
+ end
13
+ rows
14
+ end
15
+
16
+ def success?
17
+ run.success?
18
+ end
19
+
20
+ def run!
21
+ if !success?
22
+ raise "Coque Command Failed: #{self}"
23
+ else
24
+ self
25
+ end
26
+ end
27
+
28
+ def run
29
+ log_start
30
+ get_result
31
+ end
32
+
33
+ def log_start
34
+ if Coque.logger
35
+ Coque.logger.info("Executing Coque Command: #{self.to_s}")
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/coque/sh.rb CHANGED
@@ -14,7 +14,7 @@ module Coque
14
14
  end
15
15
 
16
16
  def to_s
17
- "<Coque::Sh #{args.inspect}>"
17
+ "<Coque::Sh #{args.join(" ")}>"
18
18
  end
19
19
 
20
20
  def inspect
@@ -25,7 +25,7 @@ module Coque
25
25
  self.class.new(self.context, self.args + new_args)
26
26
  end
27
27
 
28
- def run
28
+ def get_result
29
29
  stdin, stdoutr, stdoutw = get_default_fds
30
30
  opts = {in: stdin, stdin.fileno => stdin.fileno,
31
31
  out: stdoutw, stdoutw.fileno => stdoutw.fileno,
data/lib/coque/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Coque
2
- VERSION = "0.5.0"
2
+ VERSION = "0.8.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coque
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Horace Williams
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-24 00:00:00.000000000 Z
11
+ date: 2021-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.16'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.16'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '12.3'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '12.3'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -112,8 +112,6 @@ files:
112
112
  - bin/console
113
113
  - bin/setup
114
114
  - coque.gemspec
115
- - drake.rb
116
- - io.rb
117
115
  - lib/coque.rb
118
116
  - lib/coque/cmd.rb
119
117
  - lib/coque/context.rb
@@ -122,6 +120,7 @@ files:
122
120
  - lib/coque/rb.rb
123
121
  - lib/coque/redirectable.rb
124
122
  - lib/coque/result.rb
123
+ - lib/coque/runnable.rb
125
124
  - lib/coque/sh.rb
126
125
  - lib/coque/version.rb
127
126
  - script.py
@@ -145,8 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
144
  - !ruby/object:Gem::Version
146
145
  version: '0'
147
146
  requirements: []
148
- rubyforge_project:
149
- rubygems_version: 2.6.14.1
147
+ rubygems_version: 3.2.21
150
148
  signing_key:
151
149
  specification_version: 4
152
150
  summary: Shell command utilities with easy integration with Ruby code.
data/drake.rb DELETED
@@ -1,113 +0,0 @@
1
- require "open3"
2
- require "pry"
3
-
4
- class Pipeline
5
- attr_reader :commands
6
- def initialize(commands = [])
7
- @commands = commands
8
- end
9
-
10
- def to_s
11
- "Pipeline of #{commands.join("|")}"
12
- end
13
-
14
- def |(other)
15
- case other
16
- when Pipeline
17
- Pipeline.new(commands + other.commands)
18
- when Cmd
19
- Pipeline.new(commands + [other])
20
- when Crb
21
- Pipeline.new(commands + [other])
22
- end
23
- end
24
-
25
- def run
26
- commands = self.commands
27
- pids = []
28
-
29
- in_read, in_write = IO.pipe
30
- out_read = nil
31
- out_write = nil
32
-
33
- while commands.any? do
34
- in_write.close
35
- out_read, out_write = IO.pipe
36
-
37
- cmd = commands.shift
38
- puts "Spawn command: #{cmd}"
39
- pids << cmd.run(in_read, out_write)
40
-
41
- in_read = out_read
42
- in_write = out_write
43
- end
44
- out_write.close
45
- [pids, out_read]
46
- end
47
- end
48
-
49
- class Cmd
50
- attr_reader :args
51
- def initialize(args)
52
- @args = args
53
- end
54
-
55
- def to_s
56
- "Cmd to run: `#{args.inspect}`"
57
- end
58
-
59
- def self.[](*args)
60
- puts args.inspect
61
- Cmd.new(args)
62
- end
63
-
64
- def command
65
- args.join(" ")
66
- end
67
-
68
- def run(stdin, stdout)
69
- spawn(args.join(" "), in: stdin, stdin => stdin, out: stdout, stdout => stdout)
70
- end
71
-
72
- def |(other)
73
- case other
74
- when Cmd
75
- Pipeline.new([self, other])
76
- when Crb
77
- Pipeline.new([self, other])
78
- when Pipeline
79
- Pipeline.new([self] + other.commands)
80
- end
81
- end
82
- end
83
-
84
- class Crb
85
- def initialize(&block)
86
- @block = block
87
- end
88
-
89
- def run(stdin, stdout)
90
- fork do
91
- STDOUT.reopen(stdout)
92
- stdin.each_line(&@block)
93
- end
94
- end
95
-
96
- def |(other)
97
- case other
98
- when Cmd
99
- Pipeline.new([self, other])
100
- when Crb
101
- Pipeline.new([self, other])
102
- when Pipeline
103
- Pipeline.new([self] + other.commands)
104
- end
105
- end
106
- end
107
-
108
-
109
- c = Cmd['cat', '/usr/share/dict/words'] | Cmd['head'] | Crb.new { |line| puts "crb - #{line}" }
110
- puts "pipeline: #{c}"
111
- pids, out = c.run
112
- puts "Spawned pids: #{pids}"
113
- out.each_line { |l| puts l }
data/io.rb DELETED
@@ -1,215 +0,0 @@
1
- require 'open3'
2
-
3
- def banner(msg)
4
- puts "******* #{msg} *******"
5
- end
6
-
7
- def read_from_spawned_pipe
8
- banner("read_from_spawned_pipe")
9
- pipe_me_in, pipe_peer_out = IO.pipe
10
- pipe_peer_in, pipe_me_out = IO.pipe
11
-
12
- pid = spawn('ls',
13
- out: pipe_peer_out,
14
- pipe_peer_out => pipe_peer_out,
15
- in: pipe_peer_in,
16
- pipe_peer_in => pipe_peer_in)
17
- puts "Spawned #{pid}"
18
-
19
- pipe_peer_out.close
20
- puts pipe_me_in.read
21
- end
22
-
23
- def read_write_to_spawned
24
- banner("read_write_to_spawned")
25
- pipe_me_in, pipe_peer_out = IO.pipe
26
- pipe_peer_in, pipe_me_out = IO.pipe
27
-
28
- ['a', 'b', 'c', 'ab'].each { |l| pipe_me_out.puts(l) }
29
-
30
- pid = spawn('grep a',
31
- out: pipe_peer_out,
32
- pipe_peer_out => pipe_peer_out,
33
- in: pipe_peer_in,
34
- pipe_peer_in => pipe_peer_in)
35
- puts "Spawned #{pid}"
36
- pipe_me_out.close
37
-
38
- pipe_peer_out.close
39
- puts pipe_me_in.read
40
- end
41
-
42
- def chain_two_native_processes
43
- banner("chain_two_native_processes")
44
- a_in_read, a_in_write = IO.pipe
45
- a_out_read, a_out_write = IO.pipe
46
-
47
- p1 = spawn('echo "a\nb\nc\nab\n"',
48
- out: a_out_write,
49
- a_out_write => a_out_write,
50
- in: a_in_read,
51
- a_in_read => a_in_read)
52
-
53
- b_out_read, b_out_write = IO.pipe
54
-
55
- p2 = spawn('grep a',
56
- out: b_out_write,
57
- b_out_write => b_out_write,
58
- in: a_out_read,
59
- a_out_read => a_out_read)
60
-
61
- puts "Spawned a: #{p1}, b: #{p2}"
62
- # Q: Why do we have to close these? do the spawned processes not close them?
63
- b_out_write.close
64
- a_out_write.close
65
- puts b_out_read.read
66
- end
67
-
68
- def reading_large_output
69
- banner("reading_large_output")
70
- a_in_read, a_in_write = IO.pipe
71
- a_out_read, a_out_write = IO.pipe
72
-
73
- p1 = spawn('cat /usr/share/dict/words',
74
- out: a_out_write,
75
- a_out_write => a_out_write)
76
-
77
- a_out_write.close
78
- puts a_out_read.read
79
- end
80
-
81
- def pipe_to_forked_ruby
82
- banner("pipe_to_forked_ruby")
83
- a_in_read, a_in_write = IO.pipe
84
- b_out_read, b_out_write = IO.pipe
85
-
86
- ['a', 'b', 'c', 'ab'].each { |l| a_in_write.puts(l) }
87
- a_in_write.flush
88
- a_in_write.close
89
-
90
- child = fork do
91
- # change our stdin to be the read end of the pipe
92
- STDOUT.puts "forked process to stdout (#{STDOUT.fileno})"
93
- b_out_write.puts "******* forked process to pipe (#{b_out_write.fileno}) **********"
94
-
95
- a_in_read.each_line { |l| b_out_write.puts "child - #{l}" }
96
- end
97
- puts "forked #{child}"
98
-
99
- a_in_write.close
100
- b_out_write.close
101
-
102
- b_out_read.each_line { |l| puts "read from parent - #{l}" }
103
- end
104
-
105
-
106
- def native_process_to_ruby_block
107
- banner("native_process_to_ruby_block")
108
- # a_in_read, a_in_write = IO.pipe
109
- a_out_read, a_out_write = IO.pipe
110
- b_out_read, b_out_write = IO.pipe
111
- puts "parent file descriptors"
112
- puts "a read: #{a_out_read.fileno}"
113
- puts "a write: #{a_out_write.fileno}"
114
- puts "b read: #{b_out_read.fileno}"
115
- puts "b write: #{b_out_write.fileno}"
116
-
117
- # Receives copy of a_out_write; closes when done
118
- p1 = spawn('echo "a\nb\nc\nab"', out: a_out_write)
119
-
120
- # Receives copy of
121
- # a_out_read - needs to read; closed automatically?
122
- # a_out_write - doesn't need, close immediately
123
- # b_out_read - doesn't need, close immediately
124
- # b_out_write - needs to write; close when done
125
- child = fork do
126
- puts "child file descriptors:"
127
- puts "a read: #{a_out_read.fileno}"
128
- puts "a write: #{a_out_write.fileno}"
129
- puts "b read: #{b_out_read.fileno}"
130
- puts "b write: #{b_out_write.fileno}"
131
- a_out_write.close
132
- b_out_read.close
133
-
134
- puts "******* IN FORK **********"
135
-
136
- # while l = a_out_read.gets
137
- # puts "working #{l}"
138
- # b_out_write.puts "child - #{l}"
139
- # end
140
-
141
- a_out_read.each_line { |l| puts "working #{l}"; b_out_write.puts "child - #{l}" }
142
- puts "child done - close writer"
143
- a_out_read.close
144
- b_out_write.close
145
- puts "Fork work done"
146
- end
147
-
148
- puts "done forked"
149
-
150
- a_out_write.close
151
- b_out_write.close
152
- a_out_read.close
153
-
154
- puts "** Display child output:"
155
- b_out_read.each_line { |l| puts "read from parent - #{l}" }
156
- b_out_read.close
157
- puts "done reading"
158
- end
159
-
160
- # read_from_spawned_pipe
161
- # read_write_to_spawned
162
- # pipe_to_forked_ruby
163
- # reading_large_output
164
- # chain_two_native_processes
165
- # native_process_to_ruby_block
166
-
167
- def run_fork(stdin, stdout, &block)
168
- fork do
169
- STDOUT.reopen(stdout)
170
- stdin.each_line(&block)
171
- end
172
- end
173
-
174
- def three_step
175
- writers = []
176
- banner("three step")
177
- a_in_read, a_in_write = IO.pipe
178
- a_out_read, a_out_write = IO.pipe
179
-
180
- a_unused = [a_out_write]
181
-
182
- p1 = spawn('echo "a\nb\nc\nab\n"',
183
- out: a_out_write,
184
- a_out_write => a_out_write,
185
- in: a_in_read,
186
- a_in_read => a_in_read)
187
-
188
- b_out_read, b_out_write = IO.pipe
189
- b_unused = [b_out_write]
190
-
191
- a_unused.each(&:close)
192
- run_fork(a_out_read, b_out_write) { |l| puts "~~ - #{l}" }
193
-
194
- c_out_read, c_out_write = IO.pipe
195
- c_unused = [c_out_write]
196
-
197
- p2 = spawn('grep a',
198
- out: c_out_write,
199
- c_out_write => c_out_write,
200
- in: b_out_read,
201
- b_out_read => b_out_read)
202
- b_unused.each(&:close)
203
-
204
- # Q: Why do we have to close these? do the spawned processes not close them?
205
- c_unused.each(&:close)
206
- puts c_out_read.read
207
- end
208
-
209
- # three_step
210
-
211
- def stdin_redirect
212
- stdin = File.open("/usr/share/dict/words", "r")
213
- spawn("head", in: stdin, stdin => stdin)
214
- end
215
- stdin_redirect