magritte 0.5.0 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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