datapipes 0.1.3 → 0.1.4

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.
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