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 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
+ ![Ceci n'est pas une pipe](https://raw.github.com/relistan/magritte/master/assets/ceci-nest-pas-une-pipe.jpg)
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
@@ -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
- new(StringIO.new(str || ""))
26
+ self.from_input_stream((StringIO.new(str)))
28
27
  end
29
28
 
30
29
  def out_to(io=nil, &block)
31
- if block_given?
32
- @output = Proc.new(&block)
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 = io if io
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
- send_output
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 = write_to(@subproc_input, @write_data)
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 += read_from(@subproc_output)
115
+ @read_data += @subproc_output.read
119
116
  end
120
117
 
121
118
  def read_from_input
122
- @write_data += read_from(@input)
119
+ @write_data += @input.read
123
120
  end
124
121
 
125
- def send_output
126
- bytes_written = if @line_by_line && @output.is_a?(Proc)
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.0
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-11 00:00:00.000000000 Z
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: &70302135231300 !ruby/object:Gem::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: *70302135231300
24
+ version_requirements: *70322250772220
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70302135230200 !ruby/object:Gem::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: *70302135230200
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