coque 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +135 -0
- data/Rakefile +10 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/coque.gemspec +39 -0
- data/drake.rb +113 -0
- data/io.rb +215 -0
- data/lib/coque/cmd.rb +35 -0
- data/lib/coque/context.rb +35 -0
- data/lib/coque/errors.rb +4 -0
- data/lib/coque/pipeline.rb +62 -0
- data/lib/coque/rb.rb +54 -0
- data/lib/coque/redirectable.rb +96 -0
- data/lib/coque/result.rb +24 -0
- data/lib/coque/sh.rb +45 -0
- data/lib/coque/version.rb +3 -0
- data/lib/coque.rb +27 -0
- data/script.py +129 -0
- metadata +153 -0
data/io.rb
ADDED
@@ -0,0 +1,215 @@
|
|
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
|
data/lib/coque/cmd.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Coque
|
2
|
+
class Cmd
|
3
|
+
include Redirectable
|
4
|
+
attr_reader :context
|
5
|
+
|
6
|
+
def |(other)
|
7
|
+
verify_redirectable(other)
|
8
|
+
case other
|
9
|
+
when Cmd
|
10
|
+
Pipeline.new([self, other])
|
11
|
+
when Pipeline
|
12
|
+
Pipeline.new([self] + other.commands)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def clone
|
17
|
+
raise "Not Implemented - Override"
|
18
|
+
end
|
19
|
+
|
20
|
+
def ensure_default_fds
|
21
|
+
if self.stdin.nil?
|
22
|
+
inr, inw = IO.pipe
|
23
|
+
inw.close
|
24
|
+
self.stdin = inr
|
25
|
+
end
|
26
|
+
|
27
|
+
if self.stdout.nil?
|
28
|
+
outr, outw = IO.pipe
|
29
|
+
self.stdout = outw
|
30
|
+
# only used for Result if this is the last command in a pipe
|
31
|
+
@stdout_read = outr
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Coque
|
2
|
+
class Context
|
3
|
+
attr_reader :dir, :env
|
4
|
+
def initialize(dir = Dir.pwd, env = {}, disinherits_env = false)
|
5
|
+
@dir = dir
|
6
|
+
@env = env
|
7
|
+
@disinherits_env = disinherits_env
|
8
|
+
end
|
9
|
+
|
10
|
+
def disinherits_env?
|
11
|
+
@disinherits_env
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](*args)
|
15
|
+
Sh.new(self, args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rb(&block)
|
19
|
+
Rb.new(self, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def chdir(new_dir)
|
23
|
+
Context.new(new_dir, env, disinherits_env?)
|
24
|
+
end
|
25
|
+
|
26
|
+
def setenv(opts)
|
27
|
+
opts = opts.map { |k,v| [k.to_s, v.to_s] }.to_h
|
28
|
+
Context.new(dir, self.env.merge(opts), disinherits_env?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def disinherit_env
|
32
|
+
Context.new(dir, {}, true)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/coque/errors.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Coque
|
2
|
+
class Pipeline
|
3
|
+
include Redirectable
|
4
|
+
|
5
|
+
attr_reader :commands
|
6
|
+
def initialize(commands = [])
|
7
|
+
@commands = commands
|
8
|
+
end
|
9
|
+
|
10
|
+
def clone
|
11
|
+
self.class.new(commands)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"<Pipeline #{commands.join(" | ")} >"
|
16
|
+
end
|
17
|
+
|
18
|
+
def |(other)
|
19
|
+
verify_redirectable(other)
|
20
|
+
case other
|
21
|
+
when Pipeline
|
22
|
+
Pipeline.new(commands + other.commands)
|
23
|
+
when Cmd
|
24
|
+
Pipeline.new(commands + [other])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def stitch
|
29
|
+
# Set head in
|
30
|
+
if commands.first.stdin.nil?
|
31
|
+
start_r, start_w = IO.pipe
|
32
|
+
start_w.close
|
33
|
+
commands.first.stdin = start_r
|
34
|
+
end
|
35
|
+
|
36
|
+
# Connect intermediate in/outs
|
37
|
+
commands.each_cons(2) do |left, right|
|
38
|
+
read, write = IO.pipe
|
39
|
+
left.stdout = write
|
40
|
+
right.stdin = read
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set tail out
|
44
|
+
if self.stdout
|
45
|
+
commands.last.stdout = stdout
|
46
|
+
stdout
|
47
|
+
elsif commands.last.stdout
|
48
|
+
commands.last.stdout
|
49
|
+
else
|
50
|
+
next_r, next_w = IO.pipe
|
51
|
+
commands.last.stdout = next_w
|
52
|
+
next_r
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def run
|
57
|
+
stdout = stitch
|
58
|
+
results = commands.map(&:run)
|
59
|
+
Result.new(results.last.pid, stdout)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/coque/rb.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Coque
|
2
|
+
class Rb < Cmd
|
3
|
+
NOOP = Proc.new { }
|
4
|
+
attr_reader :block, :pre_block, :post_block
|
5
|
+
def initialize(context = Context.new, &block)
|
6
|
+
if block_given?
|
7
|
+
@block = block
|
8
|
+
else
|
9
|
+
@block = NOOP
|
10
|
+
end
|
11
|
+
@pre_block = nil
|
12
|
+
@post_block = nil
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
def clone
|
17
|
+
self.class.new(context, &block).pre(&pre_block).post(&post_block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def pre(&block)
|
21
|
+
if block_given?
|
22
|
+
@pre_block = block
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def post(&block)
|
28
|
+
if block_given?
|
29
|
+
@post_block = block
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
ensure_default_fds
|
36
|
+
|
37
|
+
pid = fork do
|
38
|
+
STDOUT.reopen(stdout)
|
39
|
+
Dir.chdir(context.dir)
|
40
|
+
if context.disinherits_env?
|
41
|
+
ENV.clear
|
42
|
+
end
|
43
|
+
context.env.each do |k,v|
|
44
|
+
ENV[k] = v
|
45
|
+
end
|
46
|
+
@pre_block.call if @pre_block
|
47
|
+
stdin.each_line(&@block)
|
48
|
+
@post_block.call if @post_block
|
49
|
+
end
|
50
|
+
stdout.close
|
51
|
+
Result.new(pid, stdout_read)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module Coque
|
4
|
+
module Redirectable
|
5
|
+
attr_reader :stdin, :stdout, :stderr
|
6
|
+
|
7
|
+
def >(io)
|
8
|
+
clone.tap do |c|
|
9
|
+
c.stdout = io
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def <(io)
|
14
|
+
clone.tap do |c|
|
15
|
+
c.stdin = io
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def >=(io)
|
20
|
+
clone.tap do |c|
|
21
|
+
c.stderr = io
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def getio(io, mode = "r")
|
26
|
+
case io
|
27
|
+
when String
|
28
|
+
File.open(io, mode)
|
29
|
+
when Pathname
|
30
|
+
File.open(io, mode)
|
31
|
+
when IO
|
32
|
+
io
|
33
|
+
when Tempfile
|
34
|
+
io
|
35
|
+
else
|
36
|
+
raise ArgumentError.new("Can't redirect stream to #{io}, must be String, Pathname, or IO")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def stdin_redirected?
|
41
|
+
defined? @stdin
|
42
|
+
end
|
43
|
+
|
44
|
+
def stdout_redirected?
|
45
|
+
defined? @stdout
|
46
|
+
end
|
47
|
+
|
48
|
+
def stderr_redirected?
|
49
|
+
defined? @stderr
|
50
|
+
end
|
51
|
+
|
52
|
+
def stderr=(s)
|
53
|
+
if stderr_redirected?
|
54
|
+
raise RedirectionError.new("Can't set stderr of #{self} to #{s}, is already set to #{stderr}")
|
55
|
+
else
|
56
|
+
@stderr = getio(s, "w")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def stdout=(s)
|
61
|
+
if stdout_redirected?
|
62
|
+
raise RedirectionError.new("Can't set stdout of #{self} to #{s}, is already set to #{stdout}")
|
63
|
+
else
|
64
|
+
@stdout = getio(s, "w")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def stdin=(s)
|
69
|
+
if stdin_redirected?
|
70
|
+
raise RedirectionError.new("Can't set stdin of #{self} to #{s}, is already set to #{stdin}")
|
71
|
+
else
|
72
|
+
@stdin = getio(s, "r")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def verify_redirectable(other)
|
77
|
+
if self.stdout_redirected?
|
78
|
+
raise RedirectionError.new("Can't pipe #{self} into #{other} -- #{self}'s STDIN is already redirected")
|
79
|
+
end
|
80
|
+
|
81
|
+
if other.stdin_redirected?
|
82
|
+
raise RedirectionError.new("Can't pipe #{self} into #{other} -- #{other}'s STDIN is already redirected")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def stdout_read
|
89
|
+
if defined? @stdout_read
|
90
|
+
@stdout_read
|
91
|
+
else
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/coque/result.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
class Coque::Result
|
2
|
+
attr_reader :pid, :exit_code
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(pid, out)
|
6
|
+
@pid = pid
|
7
|
+
@out = out
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
@out.each_line do |line|
|
12
|
+
block.call(line.chomp)
|
13
|
+
end
|
14
|
+
unless defined? @exit_code
|
15
|
+
wait
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def wait
|
20
|
+
_, status = Process.waitpid2(pid)
|
21
|
+
@exit_code = status.exitstatus
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
data/lib/coque/sh.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Coque
|
2
|
+
class Sh < Cmd
|
3
|
+
attr_reader :args, :context
|
4
|
+
def initialize(context, args)
|
5
|
+
@context = context
|
6
|
+
@args = args
|
7
|
+
end
|
8
|
+
|
9
|
+
def clone
|
10
|
+
self.class.new(context, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"<Coque::Sh #{args.inspect}>"
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](*new_args)
|
22
|
+
self.class.new(self.context, self.args + new_args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
ensure_default_fds
|
27
|
+
opts = {in: stdin, stdin.fileno => stdin.fileno,
|
28
|
+
out: stdout, stdout.fileno => stdout.fileno,
|
29
|
+
chdir: context.dir, unsetenv_others: context.disinherits_env?}
|
30
|
+
|
31
|
+
# Redirect err to out: (e.g. for 2>&1)
|
32
|
+
# {err: [:child, :out]}
|
33
|
+
err_opts = if stderr
|
34
|
+
{err: stderr, stderr.fileno => stderr.fileno}
|
35
|
+
else
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
|
39
|
+
pid = spawn(context.env, args.join(" "), opts.merge(err_opts))
|
40
|
+
|
41
|
+
stdout.close
|
42
|
+
Result.new(pid, stdout_read)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/coque.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "coque/redirectable"
|
2
|
+
require "coque/cmd"
|
3
|
+
require "coque/sh"
|
4
|
+
require "coque/rb"
|
5
|
+
require "coque/context"
|
6
|
+
require "coque/errors"
|
7
|
+
require "coque/pipeline"
|
8
|
+
require "coque/result"
|
9
|
+
require "coque/version"
|
10
|
+
|
11
|
+
module Coque
|
12
|
+
def self.context(dir: Dir.pwd, env: {}, disinherits_env: false)
|
13
|
+
Context.new(dir, env, disinherits_env)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.[](*args)
|
17
|
+
Context.new[*args]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.rb(&block)
|
21
|
+
Rb.new(Context.new, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.pipeline(*commands)
|
25
|
+
commands.reduce(:|)
|
26
|
+
end
|
27
|
+
end
|