magritte 0.5.0 → 0.5.5
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/README.md +26 -0
- data/lib/magritte/iostreams.rb +63 -0
- data/lib/magritte/pipe.rb +25 -61
- metadata +7 -6
data/README.md
CHANGED
@@ -7,6 +7,8 @@ to an IO or to a block. A simple line buffer class is also provided,
|
|
7
7
|
to turn block writes to the output block into line-by-line output
|
8
8
|
to make interacting with the sub-process easier.
|
9
9
|
|
10
|
+

|
11
|
+
|
10
12
|
What it Does
|
11
13
|
------------
|
12
14
|
You have a sub-command that you want to put data into and from which
|
@@ -50,6 +52,9 @@ Magritte::Pipe.from_input_string(data)
|
|
50
52
|
.filtering_with('grep "relistan"')
|
51
53
|
```
|
52
54
|
|
55
|
+
This works as above, however the input has been taken from the `data`
|
56
|
+
string rather than a file.
|
57
|
+
|
53
58
|
####IO Stream as Input
|
54
59
|
|
55
60
|
```ruby
|
@@ -79,6 +84,10 @@ Magritte::Pipe.from_input_file('some.txt')
|
|
79
84
|
.filtering_with('grep "relistan"')
|
80
85
|
```
|
81
86
|
|
87
|
+
Each block of data that was read from the `stdout` of the sub-process
|
88
|
+
`grep` is passed to the `out_to` block. Note that this is a block of
|
89
|
+
data of uncertain size, and will not end on nice line boundaries.
|
90
|
+
|
82
91
|
####Line Buffering
|
83
92
|
|
84
93
|
When passing data into your block, it's often much easier to work on
|
@@ -96,6 +105,23 @@ Magritte::Pipe.from_input_file('some.txt')
|
|
96
105
|
.filtering_with('grep "relistan"')
|
97
106
|
```
|
98
107
|
|
108
|
+
Note that line buffering does not apply to stream outputs, only to
|
109
|
+
output blocks as there is generally no reason to do this with a stream.
|
110
|
+
|
111
|
+
####Line Buffer with Arbitrary Record Separators
|
112
|
+
|
113
|
+
The default line ending character for the `LineBuffer` is the Unix
|
114
|
+
linefeed '\n' character. You can, however, use any record separator
|
115
|
+
you like. It is done like this (e.g. for Windows line endings):
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
Magritte::Pipe.from_input_file('some.txt')
|
119
|
+
.separated_by("\r\n")
|
120
|
+
.line_by_line
|
121
|
+
.out_to { |data| puts data }
|
122
|
+
.filtering_with('grep "relistan"')
|
123
|
+
```
|
124
|
+
|
99
125
|
Exit Status
|
100
126
|
-----------
|
101
127
|
Magritte will raise an `Errno::EPIPE` in the event of a non-zero
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Magritte
|
4
|
+
class BlockStream
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
READ_BLOCK_SIZE = 512
|
8
|
+
|
9
|
+
attr_reader :io
|
10
|
+
def_delegators :@io, :flush, :closed?, :close
|
11
|
+
|
12
|
+
def initialize(io)
|
13
|
+
@io = io
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(data)
|
17
|
+
return 0 if data.empty?
|
18
|
+
return 0 if @io.closed?
|
19
|
+
|
20
|
+
begin
|
21
|
+
@io.write_nonblock(data)
|
22
|
+
rescue Errno::EAGAIN
|
23
|
+
return 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def read
|
28
|
+
begin
|
29
|
+
data = @io.read_nonblock(READ_BLOCK_SIZE) unless @io.closed?
|
30
|
+
rescue EOFError
|
31
|
+
@io.close
|
32
|
+
rescue Errno::EAGAIN
|
33
|
+
end
|
34
|
+
|
35
|
+
data || ""
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ProcOutputStream
|
40
|
+
def initialize(output_proc)
|
41
|
+
@output = output_proc
|
42
|
+
end
|
43
|
+
|
44
|
+
def write(data)
|
45
|
+
bytes_written = @output.call(data)
|
46
|
+
raise 'output block must return number of bytes written!' unless bytes_written.is_a?(Fixnum)
|
47
|
+
bytes_written
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class LineBufferOutputStream
|
52
|
+
def initialize(output_proc, record_separator="\n")
|
53
|
+
@output_proc = output_proc;
|
54
|
+
@buffer = LineBuffer.new(record_separator)
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(data)
|
58
|
+
bytes_written = @buffer.write(data)
|
59
|
+
@buffer.each_line { |line| @output_proc.call(line) }
|
60
|
+
bytes_written
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/magritte/pipe.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'open3'
|
2
2
|
require 'stringio'
|
3
3
|
require_relative 'line_buffer'
|
4
|
+
require_relative 'iostreams'
|
4
5
|
|
5
6
|
# Acts as a two way pipe like the shell command line. We put
|
6
7
|
# data into a sub-process and capture the output.
|
7
8
|
|
8
9
|
module Magritte
|
9
10
|
class Pipe
|
10
|
-
READ_BLOCK_SIZE = 512
|
11
|
-
|
12
11
|
def initialize(input, output=nil)
|
13
12
|
@input = input
|
14
13
|
@output = output
|
@@ -20,26 +19,35 @@ module Magritte
|
|
20
19
|
end
|
21
20
|
|
22
21
|
def self.from_input_stream(io)
|
23
|
-
new(io)
|
22
|
+
new(BlockStream.new(io))
|
24
23
|
end
|
25
24
|
|
26
25
|
def self.from_input_string(str)
|
27
|
-
|
26
|
+
self.from_input_stream((StringIO.new(str)))
|
28
27
|
end
|
29
28
|
|
30
29
|
def out_to(io=nil, &block)
|
31
|
-
|
32
|
-
@output =
|
30
|
+
unless block_given?
|
31
|
+
@output = BlockStream.new(io)
|
32
|
+
return self
|
33
|
+
end
|
34
|
+
|
35
|
+
proc = Proc.new(&block)
|
36
|
+
if @line_by_line
|
37
|
+
@output = LineBufferOutputStream.new(proc, @record_separator || "\n")
|
33
38
|
else
|
34
|
-
@output =
|
39
|
+
@output = ProcOutputStream.new(proc)
|
35
40
|
end
|
36
41
|
|
37
42
|
self
|
38
43
|
end
|
39
44
|
|
45
|
+
def separated_by(record_separator)
|
46
|
+
@record_separator = record_separator
|
47
|
+
end
|
48
|
+
|
40
49
|
def line_by_line
|
41
50
|
@line_by_line = true
|
42
|
-
@buffer = LineBuffer.new
|
43
51
|
self
|
44
52
|
end
|
45
53
|
|
@@ -47,8 +55,8 @@ module Magritte
|
|
47
55
|
raise "No output IO is set! Invoke out_to first!" unless @output
|
48
56
|
|
49
57
|
Open3.popen2e(command) do |subproc_input, subproc_output, wait_thr|
|
50
|
-
@subproc_output = subproc_output
|
51
|
-
@subproc_input = subproc_input
|
58
|
+
@subproc_output = BlockStream.new(subproc_output)
|
59
|
+
@subproc_input = BlockStream.new(subproc_input)
|
52
60
|
|
53
61
|
clear_buffers
|
54
62
|
|
@@ -63,7 +71,7 @@ module Magritte
|
|
63
71
|
|
64
72
|
if read_ready
|
65
73
|
read_from_subproc
|
66
|
-
|
74
|
+
write_output
|
67
75
|
end
|
68
76
|
|
69
77
|
# We close the input to signal to the sub-process that we are
|
@@ -95,71 +103,27 @@ module Magritte
|
|
95
103
|
end
|
96
104
|
|
97
105
|
def descriptor_array_for(stream)
|
98
|
-
stream.closed? ? nil : [stream]
|
99
|
-
end
|
100
|
-
|
101
|
-
def write_to(io, data)
|
102
|
-
return 0 if data.empty?
|
103
|
-
return 0 if io.closed?
|
104
|
-
|
105
|
-
begin
|
106
|
-
io.write_nonblock(data)
|
107
|
-
rescue Errno::EAGAIN
|
108
|
-
return 0
|
109
|
-
end
|
106
|
+
stream.closed? ? nil : [stream.io]
|
110
107
|
end
|
111
108
|
|
112
109
|
def write_to_subproc
|
113
|
-
bytes_written =
|
110
|
+
bytes_written = @subproc_input.write(@write_data)
|
114
111
|
@write_data = @write_data[bytes_written..-1]
|
115
112
|
end
|
116
113
|
|
117
114
|
def read_from_subproc
|
118
|
-
@read_data +=
|
115
|
+
@read_data += @subproc_output.read
|
119
116
|
end
|
120
117
|
|
121
118
|
def read_from_input
|
122
|
-
@write_data +=
|
119
|
+
@write_data += @input.read
|
123
120
|
end
|
124
121
|
|
125
|
-
def
|
126
|
-
bytes_written =
|
127
|
-
send_output_line_by_line
|
128
|
-
else
|
129
|
-
send_output_block
|
130
|
-
end
|
131
|
-
|
122
|
+
def write_output
|
123
|
+
bytes_written = @output.write(@read_data)
|
132
124
|
@read_data = @read_data[bytes_written..-1]
|
133
125
|
end
|
134
126
|
|
135
|
-
def send_output_line_by_line
|
136
|
-
bytes_written = @buffer.write(@read_data)
|
137
|
-
@buffer.each_line { |line| @output.call(line) }
|
138
|
-
bytes_written
|
139
|
-
end
|
140
|
-
|
141
|
-
def send_output_block
|
142
|
-
if @output.is_a?(Proc)
|
143
|
-
bytes_written = @output.call(@read_data)
|
144
|
-
raise 'output block must return number of bytes written!' unless bytes_written.is_a?(Fixnum)
|
145
|
-
bytes_written
|
146
|
-
else
|
147
|
-
write_to(@output, @read_data)
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
def read_from(io)
|
153
|
-
begin
|
154
|
-
data = io.read_nonblock(READ_BLOCK_SIZE) unless io.closed?
|
155
|
-
rescue EOFError
|
156
|
-
io.close
|
157
|
-
rescue Errno::EAGAIN
|
158
|
-
end
|
159
|
-
|
160
|
-
data || ""
|
161
|
-
end
|
162
|
-
|
163
127
|
def ready_to_close?
|
164
128
|
@input.closed? && @write_data.empty? && @read_data.empty? && !@subproc_input.closed?
|
165
129
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: magritte
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70322250772220 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70322250772220
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70322250771120 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 2.0.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70322250771120
|
36
36
|
description: ! ' Magritte is a simple but powerful wrapper to Open3 pipes that makes
|
37
37
|
it easy to handle two-way piping of data into and out of a sub-process. Various
|
38
38
|
input IO wrappers are supported and output can either be to an IO or to a block.
|
@@ -45,6 +45,7 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- lib/magritte/iostreams.rb
|
48
49
|
- lib/magritte/line_buffer.rb
|
49
50
|
- lib/magritte/pipe.rb
|
50
51
|
- lib/magritte.rb
|