coque 0.5.0 → 0.8.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.
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