datapipes 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a09f50e26a2e9da8b66f7bd6904a78e451a8a78a
4
- data.tar.gz: 047393dea516f11e7de3ccac83b9a936cd84ed50
3
+ metadata.gz: 8abb0451500188b58038dbb2731ff2fcb8977f4b
4
+ data.tar.gz: 7f6348febf36db66e7939bca687b820f5a029fe4
5
5
  SHA512:
6
- metadata.gz: b2e0b92e8f32a6b45e711d9f129255046b02089e27fbd43370b4e4272e85e50da209b5b4596ad9750bb034dfb424680b0fc6c12098724c3ca991489789646c0e
7
- data.tar.gz: aac12fc41369a05128f0bfbfb3e07ad3b4cc95dd450f89d396e5aeaa303da53283ccd4aa65e0d4b42b0b0cc5e8a315e4e2f5a4062bd60746881612f50a8f2010
6
+ metadata.gz: 43ba18b6df1af0b1118ac7c39c719d0f7d63160cd90247e1e2a0b4dd8baf3992a888fede632491d7f276b949b07910f327f761758f719e4ff36604626e3db385
7
+ data.tar.gz: 413545e4c611a78e48f98ce0cfb791003b7ae9512b16adee0535159e49cd30368c81b6a2671e7bc62179cd1ae52984de6ebd1846a31a2b9f1973aa90faeb4a6f
@@ -1,3 +1,6 @@
1
+ ## 0.1.4
2
+ - Add documents.
3
+
1
4
  ## 0.1.3
2
5
  - Fix bug with composing sources.
3
6
 
data/README.md CHANGED
@@ -46,6 +46,7 @@ are composable individually. So the diagram above will be:
46
46
  ```
47
47
 
48
48
  ## Installation
49
+ __datapipes requires Ruby >= 2.0.__
49
50
 
50
51
  Add this line to your application's Gemfile:
51
52
 
@@ -63,7 +64,7 @@ Or install it yourself as:
63
64
  You have to define your own Source, Tube and Sink. See more in `examples`.
64
65
 
65
66
  ### Composing objects
66
- TODO...
67
+ See more in `examples/composing.rb`.
67
68
 
68
69
  ## Contributing
69
70
 
@@ -1,17 +1,19 @@
1
1
  require 'datapipes'
2
2
 
3
- $: << File.expand_path('../lib', __FILE__)
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
4
  require 'list'
5
5
  require 'long_task'
6
6
  require 'mul'
7
7
  require 'triple'
8
8
  require 'acc'
9
+ require 'reverse_acc'
9
10
 
10
11
  acc = Acc.new
12
+ rev_acc = ReverseAcc.new
11
13
 
12
14
  source = List.new + LongTask.new(21..30)
13
15
  tube = Mul.new >> Triple.new
14
- sink = acc
16
+ sink = acc + rev_acc
15
17
 
16
18
  datapipe = Datapipes.new(
17
19
  source,
@@ -22,3 +24,4 @@ datapipe = Datapipes.new(
22
24
  datapipe.run_resource
23
25
 
24
26
  p acc.stock
27
+ p rev_acc.stock
@@ -0,0 +1,11 @@
1
+ class ReverseAcc < Datapipes::Sink
2
+ attr_reader :stock
3
+
4
+ def initialize
5
+ @stock = []
6
+ end
7
+
8
+ def run(data)
9
+ @stock.unshift data
10
+ end
11
+ end
@@ -9,6 +9,11 @@ require 'datapipes/version'
9
9
  Thread.abort_on_exception = true
10
10
 
11
11
  class Datapipes
12
+ # Pass datapipes components instances.
13
+ # Each component can be composed. See detail in examples.
14
+ #
15
+ # tube and pipe are optional.
16
+ # If not given tube, a default tube which takes no effect is used.
12
17
  def initialize(source, sink, tube: Tube.new, pipe: Pipe.new)
13
18
  @source = source
14
19
  @tube = tube
@@ -16,6 +21,11 @@ class Datapipes
16
21
  @pipe = pipe
17
22
  end
18
23
 
24
+ # Run sources, data flow via pipe, tubes and sinks work.
25
+ # Everything work with just call this method.
26
+ #
27
+ # When all sources finished producing, and all sinks did their jobs,
28
+ # this method returns.
19
29
  def run_resource
20
30
  @source.pipe = @pipe
21
31
  runners = @source.run_all
@@ -2,6 +2,8 @@ class Datapipes
2
2
  module Composable
3
3
  attr_accessor :accumulated
4
4
 
5
+ # Source and Sink can't composed as function composition.
6
+ # So accumulates composed objects internaly, then use them later.
5
7
  def +(op2)
6
8
  op1 = self
7
9
  op1_acc = (op1.accumulated || [op1])
@@ -1,13 +1,26 @@
1
1
  class Datapipes
2
+ # Pipe is not only data pipeline but handling asynchronous.
3
+ #
4
+ # You can make your own pipe with database or something, but
5
+ # becareful this object used in multi thread.
6
+ #
7
+ # If you make your own, you can override _initialize_, because
8
+ # this is not used in internal.
9
+ #
10
+ # Then supply _recieve_ and _pull_. _pull_ must cause thread blocking
11
+ # when it is empty.
2
12
  class Pipe
13
+ # You can override and don't need to call super in sub class.
3
14
  def initialize
4
15
  @queue ||= Queue.new
5
16
  end
6
17
 
18
+ # Emit data to pipe.
7
19
  def recieve(data)
8
20
  @queue.enq data
9
21
  end
10
22
 
23
+ # _pull_ must cause thread blocking when it is empty.
11
24
  def pull
12
25
  @queue.deq
13
26
  end
@@ -1,9 +1,49 @@
1
1
  class Datapipes
2
2
  # Build your own sink logic in `run` method.
3
+ #
4
+ # Be careful each sinks are executed concurrently.
5
+ # Avoid race condition in multi sinks.
6
+ #
7
+ # This is bad:
8
+ #
9
+ # $shared = []
10
+ #
11
+ # class A < Datapipes::Sink
12
+ # def run(data)
13
+ # $shared << data
14
+ # end
15
+ # end
16
+ #
17
+ # class B < Datapipes::Sink
18
+ # def run(data)
19
+ # $shared << data
20
+ # end
21
+ # end
22
+ #
23
+ # On the other hand, a sink is called serially. So you can
24
+ # touch shared object in one sink logic.
25
+ #
26
+ # This is good:
27
+ #
28
+ # class A < Datapipes::Source
29
+ # def initialize
30
+ # @shared = []
31
+ # end
32
+ #
33
+ # def run(data)
34
+ # @shared << data
35
+ # end
36
+ # end
37
+ #
3
38
  class Sink
4
39
  include Composable
5
40
 
6
- # Run all sinks concurrently.
41
+ # Override this in sub class
42
+ def run(data)
43
+ data
44
+ end
45
+
46
+ # For internal uses.
7
47
  def run_all(data)
8
48
  @accumulated ||= [self]
9
49
  count = Parallel.processor_count
@@ -1,5 +1,4 @@
1
1
  class Datapipes
2
- #
3
2
  # Build your own source logic in `run` method.
4
3
  # Use `produce` method to emitt data to pipe.
5
4
  #
@@ -7,17 +6,34 @@ class Datapipes
7
6
  # 10.times {|i| produce(i) }
8
7
  # end
9
8
  #
9
+ # You can use infinitie stream like:
10
+ #
11
+ # def run
12
+ # twitter_client.userstream do |event|
13
+ # produce(event)
14
+ # end
15
+ # end
16
+ #
10
17
  class Source
11
18
  include Composable
12
19
 
13
- attr_accessor :pipe
20
+ # Override in sub class.
21
+ def run
22
+ end
14
23
 
24
+ # For internal used.
25
+ #
26
+ # Run accumulated sources which are set by composition.
27
+ # Each source works in new thread.
15
28
  def run_all
16
29
  @accumulated ||= [self]
17
30
  set_pipe
18
31
  @accumulated.map {|s| Thread.new { s.run } }
19
32
  end
20
33
 
34
+ # For internal uses. Do not touch.
35
+ attr_accessor :pipe
36
+
21
37
  private
22
38
 
23
39
  def produce(data)
@@ -3,6 +3,9 @@ class Datapipes
3
3
  #
4
4
  # Build your own tube logic in `run` method.
5
5
  class Tube
6
+ # _>>_ is used to compose tubes. See usage in examples.
7
+ #
8
+ # Tube composition satisfies associative law. See more in spec.
6
9
  def >>(op2)
7
10
  op1 = self
8
11
  Tube.new.tap do |o|
@@ -12,6 +15,24 @@ class Datapipes
12
15
  end
13
16
  end
14
17
 
18
+ # Override this in sub class.
19
+ #
20
+ # _run_ recieves any data, so you have to ignore
21
+ # unexpected data.
22
+ #
23
+ # def run(data)
24
+ # if accept? data
25
+ # [data, data, data]
26
+ # else
27
+ # data
28
+ # end
29
+ # end
30
+ #
31
+ # def accept?(data)
32
+ # data.is_a? Integer and data > 3
33
+ # end
34
+ #
35
+ # Don't forget to return data in else clause.
15
36
  def run(data)
16
37
  data
17
38
  end
@@ -1,3 +1,3 @@
1
1
  class Datapipes
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- $: << File.expand_path('../../examples/lib', __FILE__)
3
+ $:.unshift File.expand_path('../../examples/lib', __FILE__)
4
4
  require 'list'
5
5
  require 'triple'
6
6
  require 'print'
@@ -1,27 +1,33 @@
1
1
  require 'spec_helper'
2
2
 
3
- $: << File.expand_path('../../examples/lib', __FILE__)
3
+ $:.unshift File.expand_path('../../examples/lib', __FILE__)
4
4
  require 'list'
5
5
  require 'long_task'
6
6
  require 'mul'
7
7
  require 'triple'
8
8
  require 'acc'
9
+ require 'reverse_acc'
9
10
 
10
11
  describe 'composability' do
11
12
  let(:acc) { Acc.new }
13
+ let(:rev_acc) { ReverseAcc.new }
14
+
12
15
  let(:source) { List.new + LongTask.new(21..30) }
13
16
  let(:tube) { Mul.new >> Triple.new }
17
+ let(:sink) { acc + rev_acc }
14
18
 
15
19
  let(:datapipe) do
16
20
  Datapipes.new(
17
21
  source,
18
- acc,
22
+ sink,
19
23
  tube: tube
20
24
  )
21
25
  end
22
26
 
23
27
  it 'runs without errors' do
24
28
  datapipe.run_resource
29
+
25
30
  expect(acc.stock).to have(20).items
31
+ expect(rev_acc.stock).to have(20).items
26
32
  end
27
33
  end
@@ -7,7 +7,7 @@ describe Datapipes::Sink do
7
7
 
8
8
  it 'composes' do
9
9
  subject.run_all(5)
10
- expect(list).to eq [8, 20, 6]
10
+ expect(list).to have(3).items
11
11
  end
12
12
  end
13
13
 
@@ -16,7 +16,7 @@ describe Datapipes::Sink do
16
16
 
17
17
  it 'composes' do
18
18
  subject.run_all(5)
19
- expect(list).to eq [8, 20, 6, 50]
19
+ expect(list).to have(4).items
20
20
  end
21
21
  end
22
22
 
@@ -25,11 +25,18 @@ describe Datapipes::Sink do
25
25
  let(:b) { sink_a + (sink_b + sink_c) }
26
26
 
27
27
  it 'keeps' do
28
- expect(a.run_all(3)).to eq b.run_all(3)
28
+ a.run_all(3)
29
+ size_a = list.size
30
+ list.clear
31
+
32
+ b.run_all(3)
33
+ size_b = list.size
34
+
35
+ expect(size_a).to eq size_b
29
36
  end
30
37
  end
31
38
 
32
- let(:list) { [] }
39
+ let(:list) { Queue.new }
33
40
 
34
41
  let(:sink_a) do
35
42
  list_ = list
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datapipes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taiki ONO
@@ -132,6 +132,7 @@ files:
132
132
  - examples/lib/long_task.rb
133
133
  - examples/lib/mul.rb
134
134
  - examples/lib/print.rb
135
+ - examples/lib/reverse_acc.rb
135
136
  - examples/lib/triple.rb
136
137
  - lib/datapipes.rb
137
138
  - lib/datapipes/composable.rb