pipetree 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +3 -0
- data/Gemfile +8 -0
- data/README.md +23 -0
- data/Rakefile +10 -0
- data/lib/pipetree/flow/inspect.rb +21 -0
- data/lib/pipetree/flow.rb +104 -0
- data/lib/pipetree/insert.rb +61 -0
- data/lib/pipetree/inspect.rb +33 -0
- data/lib/pipetree/version.rb +1 -1
- data/lib/pipetree.rb +55 -1
- data/test/altering_test.rb +44 -0
- data/test/benchmark_monads.rb +180 -0
- data/test/benchmarking.rb +59 -0
- data/test/flow_test.rb +125 -0
- data/test/inspect_test.rb +33 -0
- data/test/pipetree_test.rb +200 -0
- data/test/test_helper.rb +2 -0
- metadata +26 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 372090919caec356b0dccdfb2c3dc80ff18e565c
|
4
|
+
data.tar.gz: 4c34b1b76303b7df53db05605b2037f2f559b7f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b29ec2b9ea9f2503ebf53336ae433fdf54c57eb1d9f0ffb40750ec4d093608978fec321453e357e88fd939359921535eb76ea4fa4f9b1aedc030364cab215723
|
7
|
+
data.tar.gz: 9cc798a6dd1d4d055d627e21533e4212060afb55583dd5766ce76449d1aa54714c18800610281d7de00015184aa5bb8f91d01e564cc9c5916eac018a588f1ce9
|
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Example use: trailblazer.to/gems/operation/2.0/pipetree.html
|
2
|
+
|
3
|
+
|
4
|
+
Executing a pipetree generated from declarative configuration.
|
5
|
+
|
6
|
+
This reduces conditionals at run-time,
|
7
|
+
Replaces clumsy module includes to inject or override features (in the correct order) with explicit, (visual!) pipelines
|
8
|
+
Speeds up as only the pipelines need to be run without too much decider code.
|
9
|
+
|
10
|
+
|
11
|
+
First used in the Representable gem.
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
Instead of implementing the perfect API where users can override methods, call `super` or inject their logic, let them construct their own workflow.
|
16
|
+
|
17
|
+
This is way less tedious than climbing through `super` calls and callstacks.
|
18
|
+
|
19
|
+
|
20
|
+
## TODO
|
21
|
+
|
22
|
+
* Catch exceptions and show where in the pipe they were raised.
|
23
|
+
* Statically compile pipetrees into Ruby methods to make it lightning fast (paid gem?)
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "pipetree/inspect"
|
2
|
+
|
3
|
+
module Pipetree::Flow::Inspect
|
4
|
+
include ::Pipetree::Inspect
|
5
|
+
|
6
|
+
Proc = Struct.new(:proc, :operator)
|
7
|
+
|
8
|
+
def inspect_for(step)
|
9
|
+
debug = @debug[step]
|
10
|
+
[super(debug.proc), debug.operator]
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect_line(names)
|
14
|
+
string = names.collect { |i, name| "#{name.last}#{name.first}" }.join(",")
|
15
|
+
"[#{string}]"
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect_row(index, name)
|
19
|
+
"#{index} #{name.last}#{name.first}"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
class Pipetree::Flow < Array # yes, we could inherit, and so on.
|
2
|
+
require "pipetree/flow/inspect"
|
3
|
+
include Inspect
|
4
|
+
|
5
|
+
module Operators
|
6
|
+
# Optimize the most common steps with Stay/And objects that are faster than procs.
|
7
|
+
def <(proc, options=nil)
|
8
|
+
_insert On.new(Left, Stay.new(proc)), options, proc, "<"
|
9
|
+
end
|
10
|
+
|
11
|
+
# OnRight-> ? Right, input : Left, input
|
12
|
+
def &(proc, options=nil)
|
13
|
+
_insert On.new(Right, And.new(proc)), options, proc, "&"
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: test me.
|
17
|
+
def >(proc, options=nil)
|
18
|
+
_insert On.new(Right, Stay.new(proc)), options, proc, ">"
|
19
|
+
end
|
20
|
+
|
21
|
+
def >>(proc, options=nil)
|
22
|
+
_insert On.new(Right,
|
23
|
+
->(last, input, options) { [Right, proc.(input, options)] } ), options, proc, ">>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def %(proc, options=nil)
|
27
|
+
# no condition is needed, and we want to stay on the same track, too.
|
28
|
+
_insert Stay.new(proc), options, proc, "%"
|
29
|
+
end
|
30
|
+
|
31
|
+
# :private:
|
32
|
+
# proc is the original step proc, e.g. Validate.
|
33
|
+
def _insert(step, options, proc, operator)
|
34
|
+
options ||= { append: true } # DISCUSS: needed?
|
35
|
+
|
36
|
+
insert!(step, options).tap do
|
37
|
+
@debug ||= {}
|
38
|
+
@debug[step] = Inspect::Proc.new(proc, operator)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
include Operators
|
43
|
+
|
44
|
+
# Actual implementation of Pipetree:Flow. Yes, it's that simple!
|
45
|
+
def call(input, options)
|
46
|
+
input = [Right, input]
|
47
|
+
|
48
|
+
inject(input) do |memooo, step|
|
49
|
+
last, memo = memooo
|
50
|
+
step.call(last, memo, options)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def index(step) # @debug maps the original user's step proc to the On instance (or any kind of wrapper proc).
|
55
|
+
@debug.find { |on, inspect_proc| inspect_proc.proc == step and return super(on) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Directions emitted by steps.
|
59
|
+
Left = Class.new
|
60
|
+
Right = Class.new
|
61
|
+
|
62
|
+
# Incoming direction must be Left/Right.
|
63
|
+
class On
|
64
|
+
def initialize(direction, proc)
|
65
|
+
@direction, @proc = direction, proc
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(last, input, options)
|
69
|
+
return [last, input] unless last == @direction # return unless incoming direction is Right (or Left).
|
70
|
+
@proc.(last, input, options)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Call step proc and return (Right || Left).
|
75
|
+
class And
|
76
|
+
def initialize(proc)
|
77
|
+
@proc = proc
|
78
|
+
end
|
79
|
+
|
80
|
+
def call(last, input, options)
|
81
|
+
@proc.(input, options) ? [Right, input] : [Left, input]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Call step proc and return incoming last step.
|
86
|
+
class Stay < And
|
87
|
+
def call(last, input, options)
|
88
|
+
@proc.(input, options)
|
89
|
+
[last, input] # simply pass through the current direction: either [Left, input] or [Right, input].
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
require "pipetree/insert"
|
98
|
+
module Macros
|
99
|
+
def insert!(new_function, options)
|
100
|
+
Pipetree::Insert.(self, new_function, options)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
include Macros # FIXME: we shouldn't expose #insert!
|
104
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Pipetree::Function
|
2
|
+
class Insert
|
3
|
+
def call(arr, func, options)
|
4
|
+
# arr = arr.dup
|
5
|
+
return delete!(arr, func) if options[:delete]
|
6
|
+
return replace!(arr, options[:replace], func) if options[:replace]
|
7
|
+
return before!(arr, options[:before], func) if options[:before]
|
8
|
+
return after!(arr, options[:after], func) if options[:after]
|
9
|
+
return append!(arr, options[:append], func) if options[:append]
|
10
|
+
return prepend!(arr, options[:prepend], func) if options[:prepend]
|
11
|
+
|
12
|
+
raise "[Pipetree] Unknown command #{options.inspect}" # TODO: test.
|
13
|
+
# arr
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def replace!(arr, old_func, new_func)
|
18
|
+
arr.each_with_index { |func, index|
|
19
|
+
if func.is_a?(Collect)
|
20
|
+
arr[index] = Collect[*Pipeline::Insert.(func, new_func, replace: old_func)]
|
21
|
+
end
|
22
|
+
|
23
|
+
arr[index] = new_func if func==old_func
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete!(arr, removed_func)
|
28
|
+
arr.delete(removed_func)
|
29
|
+
|
30
|
+
# TODO: make nice.
|
31
|
+
arr.each_with_index { |func, index|
|
32
|
+
if func.is_a?(Collect)
|
33
|
+
arr[index] = Collect[*Pipeline::Insert.(func, removed_func, delete: true)]
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: not nested.
|
39
|
+
def before!(arr, old_func, new_func)
|
40
|
+
index = arr.index(old_func)
|
41
|
+
arr.insert(index, new_func)
|
42
|
+
end
|
43
|
+
|
44
|
+
def after!(arr, old_func, new_func)
|
45
|
+
index = arr.index(old_func)+1
|
46
|
+
arr.insert(index, new_func)
|
47
|
+
end
|
48
|
+
|
49
|
+
def append!(arr, old_func, new_func)
|
50
|
+
arr << (new_func)
|
51
|
+
end
|
52
|
+
|
53
|
+
def prepend!(arr, old_func, new_func)
|
54
|
+
arr.unshift(new_func)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Pipetree::Insert = Pipetree::Function::Insert.new
|
60
|
+
|
61
|
+
#FIXME: all methods write to original array.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Pipetree::Inspect
|
2
|
+
# TODO: implement for nested
|
3
|
+
# TODO: remove in Representable::Debug.
|
4
|
+
def inspect(options={ style: :line })
|
5
|
+
names = each_with_index.collect do |func, i|
|
6
|
+
[i, inspect_for(func)]
|
7
|
+
end
|
8
|
+
|
9
|
+
return inspect_line(names) if options[:style] == :line
|
10
|
+
|
11
|
+
string = names.collect do |i, name|
|
12
|
+
index = sprintf("%2d", i)
|
13
|
+
inspect_row(index, name)
|
14
|
+
end.join("\n")
|
15
|
+
# name = sprintf("%-60.300s", name) # no idea what i'm doing here.
|
16
|
+
# "#{index}) #{name} #{func.source_location.join(":")}"
|
17
|
+
"\n#{string}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# open source file to retrieve the constant name.
|
21
|
+
def inspect_for(func)
|
22
|
+
File.readlines(func.source_location[0])[func.source_location[1]-1].match(/^\s+([\w\:]+)/)[1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect_line(names)
|
26
|
+
string = names.collect { |i, name| "#{name}" }.join("|>")
|
27
|
+
"[#{string}]"
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect_row(index, name)
|
31
|
+
"#{index}|>#{name}"
|
32
|
+
end
|
33
|
+
end
|
data/lib/pipetree/version.rb
CHANGED
data/lib/pipetree.rb
CHANGED
@@ -1,3 +1,57 @@
|
|
1
|
-
|
1
|
+
class Pipetree < Array
|
2
|
+
VERSION = "0.0.1"
|
2
3
|
|
4
|
+
# Allows to implement a pipeline of filters where a value gets passed in and the result gets
|
5
|
+
# passed to the next callable object.
|
6
|
+
Stop = Class.new
|
7
|
+
|
8
|
+
# options is mutuable.
|
9
|
+
# we have a fixed set of arguments here, since array splat significantly slows this down, as in
|
10
|
+
# call(input, *options)
|
11
|
+
def call(input, options)
|
12
|
+
inject(input) do |memo, step|
|
13
|
+
res = evaluate(step, memo, options)
|
14
|
+
return(Stop) if Stop == res
|
15
|
+
res
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def evaluate(step, input, options)
|
21
|
+
step.call(input, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
require "pipetree/inspect"
|
25
|
+
include Inspect
|
26
|
+
|
27
|
+
module Macros
|
28
|
+
def insert!(new_function, options)
|
29
|
+
Pipetree::Insert.(self, new_function, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
require "pipetree/insert"
|
33
|
+
include Macros
|
34
|
+
|
35
|
+
# Collect applies a pipeline to each element of input.
|
36
|
+
class Collect < self
|
37
|
+
# when stop, the element is skipped. (should that be Skip then?)
|
38
|
+
def call(input, options)
|
39
|
+
arr = []
|
40
|
+
input.each_with_index do |item_fragment, i|
|
41
|
+
result = super(item_fragment, options.merge(index: i)) # DISCUSS: NO :fragment set.
|
42
|
+
Stop == result ? next : arr << result
|
43
|
+
end
|
44
|
+
arr
|
45
|
+
end
|
46
|
+
|
47
|
+
# DISCUSS: will this make it into the final version?
|
48
|
+
class Hash < self
|
49
|
+
def call(input, options)
|
50
|
+
{}.tap do |hsh|
|
51
|
+
input.each { |key, item_fragment|
|
52
|
+
hsh[key] = super(item_fragment, options) }# DISCUSS: NO :fragment set.
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
3
57
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class AlteringTest < Minitest::Spec
|
4
|
+
A = ->(*) { }
|
5
|
+
B = ->(*) { }
|
6
|
+
C = ->(*) { }
|
7
|
+
|
8
|
+
# constructor.
|
9
|
+
it do
|
10
|
+
pipe = ::Pipetree[A, B]
|
11
|
+
pipe.inspect.must_equal %{[A|>B]}
|
12
|
+
end
|
13
|
+
|
14
|
+
it { Pipetree[].insert(0, B).inspect.must_equal %{[B]} }
|
15
|
+
it { Pipetree[].unshift(B).inspect.must_equal %{[B]} }
|
16
|
+
it { Pipetree[].unshift(B, A).inspect.must_equal %{[B|>A]} }
|
17
|
+
|
18
|
+
it { Pipetree[A,B].insert!(C, before: A).inspect.must_equal %{[C|>A|>B]} }
|
19
|
+
it { Pipetree[A,B].insert!(C, before: B).inspect.must_equal %{[A|>C|>B]} }
|
20
|
+
|
21
|
+
it { Pipetree[A,B].insert!(C, after: A).inspect.must_equal %{[A|>C|>B]} }
|
22
|
+
it { Pipetree[A,B].insert!(C, after: B).inspect.must_equal %{[A|>B|>C]} }
|
23
|
+
|
24
|
+
it { Pipetree[A,B].insert!(C, append: true).inspect.must_equal %{[A|>B|>C]} }
|
25
|
+
it { Pipetree[].insert!(C, append: true).inspect.must_equal %{[C]} }
|
26
|
+
|
27
|
+
it { Pipetree[A,B].insert!(C, prepend: true).inspect.must_equal %{[C|>A|>B]} }
|
28
|
+
it { Pipetree[].insert!(C, prepend: true).inspect.must_equal %{[C]} }
|
29
|
+
end
|
30
|
+
|
31
|
+
class FlowInsertTest < Minitest::Spec
|
32
|
+
A = ->{ }
|
33
|
+
B = ->{ }
|
34
|
+
C = ->{ }
|
35
|
+
|
36
|
+
it { pipe = Pipetree::Flow[].>(A).>(B).inspect.must_equal %{[>A,>B]} }
|
37
|
+
it { pipe = Pipetree::Flow[].>(A).>(B, before: A).inspect.must_equal %{[>B,>A]} }
|
38
|
+
it { pipe = Pipetree::Flow[].>(A).>(B).>(C, after: A).inspect.must_equal %{[>A,>C,>B]} }
|
39
|
+
it { pipe = Pipetree::Flow[].>(A).>(C, append: true).inspect.must_equal %{[>A,>C]} }
|
40
|
+
it { pipe = Pipetree::Flow[].>(A).>(C, prepend: true).inspect.must_equal %{[>C,>A]} }
|
41
|
+
# it { pipe = Pipetree::Flow[].>(A).>(C, replace: A).inspect.must_equal %{[>C]} }
|
42
|
+
|
43
|
+
# FIXME: add :delete and :replace.
|
44
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
|
4
|
+
require "dry-monads"
|
5
|
+
|
6
|
+
class EitherCalculator
|
7
|
+
include Dry::Monads::Either::Mixin
|
8
|
+
|
9
|
+
attr_accessor :input
|
10
|
+
|
11
|
+
def calculate(input)
|
12
|
+
Right(input).bind do |value|
|
13
|
+
# puts "|>DESERIALIZATION"
|
14
|
+
value.nil? ? Left(value) : Right(value)
|
15
|
+
end.bind do |value|
|
16
|
+
# puts "|>VALIDATION"
|
17
|
+
value[:ok?] ? Right(value) : Left(value)
|
18
|
+
end.bind do |value|
|
19
|
+
# puts "|>PERSISTENCE"
|
20
|
+
value[:persist?] ? Right(value) : Left(value)
|
21
|
+
end.or do |value|
|
22
|
+
# puts "|| INVALID=CALLBACK"
|
23
|
+
Left(value)
|
24
|
+
end.bind do |value|
|
25
|
+
# puts "|> OK=CALLBACK"
|
26
|
+
Right(value)
|
27
|
+
end.or do |value|
|
28
|
+
# puts "|| SECOND-INVALID=CALLBACK"
|
29
|
+
Left(value)
|
30
|
+
end.or do |value|
|
31
|
+
# puts "|| FIXING IT=CALLBACK"
|
32
|
+
Right(value)
|
33
|
+
end
|
34
|
+
# .bind do |value|
|
35
|
+
# puts "|> WHATEVER HAPPENED=CALLBACK"
|
36
|
+
# Right(value)
|
37
|
+
# end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# EitherCalculator instance
|
42
|
+
c = EitherCalculator.new
|
43
|
+
|
44
|
+
# If everything went right
|
45
|
+
# c.calculate(nil)
|
46
|
+
# c.calculate({})
|
47
|
+
c.calculate({ok?: true})
|
48
|
+
# c.calculate({ok?: true, persist?: true})
|
49
|
+
|
50
|
+
require "pipetree/flow"
|
51
|
+
|
52
|
+
# 254.186k (± 1.2%) i/s - 1.289M in 5.071256s # pure dry-monads
|
53
|
+
# 268.407k (± 1.6%) i/s - 1.353M in 5.043612s # pipetree-flow
|
54
|
+
|
55
|
+
pipe = Pipetree::Flow[
|
56
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Right, ->(last, value, options) { #puts "|>DESERIALIZATION"
|
57
|
+
value.nil? ? [Pipetree::Flow::Left, value] : [Pipetree::Flow::Right, value] } ),
|
58
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Right, ->(last, value, options) { #puts "|>VALIDATION"
|
59
|
+
value[:ok?] ? [Pipetree::Flow::Right, value] : [Pipetree::Flow::Left, value] } ),
|
60
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Right, ->(last, value, options) { #puts "|>PERSISTENCE";
|
61
|
+
value[:persist?] ? [Pipetree::Flow::Right, value] : [Pipetree::Flow::Left, value] } ),
|
62
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Left, ->(last, value, options) { #puts "|| INVALID=CALLBACK";
|
63
|
+
[Pipetree::Flow::Left, value] } ),
|
64
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Right, ->(last, value, options) { #puts "|>OK=CALLBACK";
|
65
|
+
[Pipetree::Flow::Right, value] } ),
|
66
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Left, ->(last, value, options) { #puts "|| SECOND-INVALID=CALLBACK";
|
67
|
+
[Pipetree::Flow::Left, value] } ),
|
68
|
+
|
69
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Left, ->(last, value, options) { #puts "|| FIXING IT=CALLBACK";
|
70
|
+
[Pipetree::Flow::Right, value] } ),
|
71
|
+
]
|
72
|
+
|
73
|
+
puts "Pipetree"
|
74
|
+
pipe.({ok?: true}, {})
|
75
|
+
|
76
|
+
# exit
|
77
|
+
|
78
|
+
|
79
|
+
flow = Pipetree::Flow[]
|
80
|
+
flow.& ->(value, options) {
|
81
|
+
#puts "|>DESERIALIZATION"
|
82
|
+
!value.nil? }
|
83
|
+
flow.& ->(value, options) {
|
84
|
+
#puts "|>VALIDATION"
|
85
|
+
value[:ok?] }
|
86
|
+
flow.& ->(value, options) {
|
87
|
+
#puts "|>PERSISTENCE";
|
88
|
+
value[:persist?] }
|
89
|
+
flow.< ->(value, options) {
|
90
|
+
#puts "< INVALID=CALLBACK";
|
91
|
+
value }
|
92
|
+
flow.& ->(value, options) {
|
93
|
+
#puts "<>OK=CALLBACK";
|
94
|
+
value }
|
95
|
+
flow.< ->(value, options) {
|
96
|
+
#puts "< SECOND-INVALID=CALLBACK";
|
97
|
+
value }
|
98
|
+
flow.< ->(value, options) {
|
99
|
+
#puts "|| FIXING IT=CALLBACK"
|
100
|
+
value }
|
101
|
+
|
102
|
+
puts "FLOW"
|
103
|
+
flow.({ok?: true}, {})
|
104
|
+
|
105
|
+
require "benchmark/ips"
|
106
|
+
Benchmark.ips do |x|
|
107
|
+
x.report { c.calculate({ok?: true}) }
|
108
|
+
x.report { pipe.({ok?: true}, {}) }
|
109
|
+
x.report { flow.({ok?: true}, {}) }
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
# puts result = c.calculate
|
116
|
+
|
117
|
+
|
118
|
+
M = Dry::Monads
|
119
|
+
|
120
|
+
maybe_user = M.Maybe(true).bind do |u|
|
121
|
+
M.Maybe(nil).bind do |a|
|
122
|
+
M.Maybe(raise)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class A
|
127
|
+
def call
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Benchmark.ips do |x|
|
132
|
+
# x.report { A.new(1) } # 3.059M (± 4.4%) i/s - 15.302M in 5.012694s
|
133
|
+
# x.report { [A, 1] } # 6.606M (± 2.9%) i/s - 33.042M in 5.007420s
|
134
|
+
# end
|
135
|
+
|
136
|
+
# proc = ->(*) { }
|
137
|
+
# a = A.new
|
138
|
+
# Benchmark.ips do |x|
|
139
|
+
# x.report { proc.is_a?(Proc) }
|
140
|
+
# x.report { a.() }
|
141
|
+
# end
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
# 258.340k (± 1.0%) i/s - 1.297M in 5.020288s
|
147
|
+
# 266.743k (± 0.9%) i/s - 1.345M in 5.043017s
|
148
|
+
# 215.273k (± 1.0%) i/s - 1.089M in 5.060944s
|
149
|
+
|
150
|
+
|
151
|
+
class A
|
152
|
+
def initialize
|
153
|
+
@b = B.new
|
154
|
+
end
|
155
|
+
|
156
|
+
class B
|
157
|
+
def b
|
158
|
+
"B"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def b
|
163
|
+
@b.b
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class One
|
168
|
+
def b
|
169
|
+
b_internal
|
170
|
+
end
|
171
|
+
|
172
|
+
def b_internal
|
173
|
+
"B"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
Benchmark.ips do |x|
|
178
|
+
x.report { A.new.b }
|
179
|
+
x.report { One.new.b }
|
180
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
require "benchmark/ips"
|
4
|
+
# 0.020000 0.000000 0.020000 ( 0.023401)
|
5
|
+
# 0.050000 0.000000 0.050000 ( 0.057883)
|
6
|
+
Return = ->(params, payload, read, write) {
|
7
|
+
read["bla"]
|
8
|
+
write["bla."] = Object
|
9
|
+
params
|
10
|
+
}
|
11
|
+
# Return = ->(params, payload) {
|
12
|
+
# payload[:read]["bla"]
|
13
|
+
# payload[:write]["bla."] = Object
|
14
|
+
# params
|
15
|
+
# }
|
16
|
+
|
17
|
+
pipe = Pipetree[
|
18
|
+
*(99999.times.collect{ Return })
|
19
|
+
]
|
20
|
+
|
21
|
+
# puts Benchmark.measure { pipe.("bla", {}, {}, {}) }
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
#---
|
26
|
+
# replacing result AND options.
|
27
|
+
Return1 = ->(input, options) {
|
28
|
+
result = input + "*"
|
29
|
+
|
30
|
+
result
|
31
|
+
}
|
32
|
+
|
33
|
+
pipe1 = Pipetree[
|
34
|
+
*(99999.times.collect{ Return1 })
|
35
|
+
]
|
36
|
+
|
37
|
+
class Pipetree2 < Pipetree
|
38
|
+
def call(input, options)
|
39
|
+
inject(input) do |memo, step|
|
40
|
+
res, options = evaluate(step, memo, options)
|
41
|
+
return(Stop) if Stop == res
|
42
|
+
res
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Return2 = ->(input, options) {
|
49
|
+
[(result = (input + "*")), options]
|
50
|
+
}
|
51
|
+
|
52
|
+
pipe2 = Pipetree2[
|
53
|
+
*(99999.times.collect{ Return2 })
|
54
|
+
]
|
55
|
+
|
56
|
+
Benchmark.ips do |x|
|
57
|
+
x.report { pipe1.("", {}) }
|
58
|
+
x.report { pipe2.("", {}) }
|
59
|
+
end
|
data/test/flow_test.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
require "pipetree/flow"
|
4
|
+
|
5
|
+
# pipe = Pipetree[
|
6
|
+
# Pipetree::OnRight.new( ->(value, options) { #puts "|>DESERIALIZATION"
|
7
|
+
# value.nil? ? [Pipetree::Left, value] : [Pipetree::Right, value] } ),
|
8
|
+
# Pipetree::OnRight.new( ->(value, options) { #puts "|>VALIDATION"
|
9
|
+
# value[:ok?] ? [Pipetree::Right, value] : [Pipetree::Left, value] } ),
|
10
|
+
# Pipetree::OnRight.new( ->(value, options) { #puts "|>PERSISTENCE";
|
11
|
+
# value[:persist?] ? [Pipetree::Right, value] : [Pipetree::Left, value] } ),
|
12
|
+
# Pipetree::OnLeft.new( ->(value, options) { #puts "|| INVALID=CALLBACK";
|
13
|
+
# [Pipetree::Left, value] } ),
|
14
|
+
# Pipetree::OnRight.new( ->(value, options) { #puts "|>OK=CALLBACK";
|
15
|
+
# [Pipetree::Right, value] } ),
|
16
|
+
# Pipetree::OnLeft.new( ->(value, options) { #puts "|| SECOND-INVALID=CALLBACK";
|
17
|
+
# [Pipetree::Left, value] } ),
|
18
|
+
|
19
|
+
# Pipetree::OnLeft.new( ->(value, options) { #puts "|| FIXING IT=CALLBACK";
|
20
|
+
# [Pipetree::Right, value] } ),
|
21
|
+
# ]
|
22
|
+
|
23
|
+
# puts "Pipetree"
|
24
|
+
# pipe.({ok?: true}, {})
|
25
|
+
|
26
|
+
require "json"
|
27
|
+
|
28
|
+
class FlowTest < Minitest::Spec
|
29
|
+
# TODO: test each function from & to > is only called once!
|
30
|
+
# describe "#call" do
|
31
|
+
# let (:pipe) { Pipetree::Flow[] }
|
32
|
+
# it do
|
33
|
+
# pipe.>> ->(*) { puts "snippet" }
|
34
|
+
# pipe.({},{})
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
|
38
|
+
A = ->(*) { }
|
39
|
+
B = ->(*) { }
|
40
|
+
|
41
|
+
let (:pipe) { pipe = Pipetree::Flow[]
|
42
|
+
pipe.& ->(value, options) { value && options["deserializer.result"] = JSON.parse(value) }
|
43
|
+
pipe.& ->(value, options) { options["deserializer.result"]["key"] == 1 ? true : (options["contract.errors"]=false) }
|
44
|
+
pipe.& ->(value, options) { options["deserializer.result"]["key2"] == 2 ? true : (options["contract.errors.2"]="screwd";false) }
|
45
|
+
pipe.< ->(value, options) { options["after_deserialize.fail"]=true }
|
46
|
+
pipe.% ->(value, options) { options["meantime"] = true }
|
47
|
+
pipe.< ->(value, options) { options["after_meantime.left?"]=true; false } # false is ignored.
|
48
|
+
|
49
|
+
}
|
50
|
+
|
51
|
+
# success?
|
52
|
+
it do
|
53
|
+
options = {}
|
54
|
+
pipe.(%{{"key": 1,"key2":2}}, options)#.must_equal ""
|
55
|
+
|
56
|
+
options.must_equal({"deserializer.result"=>{"key"=>1, "key2"=>2}, "meantime"=>true})
|
57
|
+
end
|
58
|
+
|
59
|
+
# invalid?
|
60
|
+
it do
|
61
|
+
options = {}
|
62
|
+
pipe.(%{{"key": 2}}, options)#.must_equal ""
|
63
|
+
|
64
|
+
options.must_equal({"deserializer.result"=>{"key"=>2}, "contract.errors"=>false, "after_deserialize.fail"=>true, "meantime"=>true, "after_meantime.left?"=>true})
|
65
|
+
end
|
66
|
+
|
67
|
+
it "what" do
|
68
|
+
options = {}
|
69
|
+
pipe.(%{{"key": 1,"key2":null}}, options)#.must_equal ""
|
70
|
+
|
71
|
+
options.must_equal({"deserializer.result"=>{"key"=>1, "key2"=>nil}, "contract.errors.2"=>"screwd", "after_deserialize.fail"=>true, "meantime"=>true, "after_meantime.left?"=>true})
|
72
|
+
end
|
73
|
+
|
74
|
+
#---
|
75
|
+
# return value is new input.
|
76
|
+
it do
|
77
|
+
pipe = Pipetree::Flow[
|
78
|
+
Pipetree::Flow::On.new(Pipetree::Flow::Right, ->(last, value, options) { [Pipetree::Flow::Right, value.reverse] } )
|
79
|
+
]
|
80
|
+
pipe.("Hello", {}).must_equal [Pipetree::Flow::Right, "olleH"]
|
81
|
+
end
|
82
|
+
|
83
|
+
#---
|
84
|
+
# #>
|
85
|
+
describe "#>" do
|
86
|
+
let (:pipe) { Pipetree::Flow[] }
|
87
|
+
it {
|
88
|
+
pipe.> ->(input, options) { input.reverse }
|
89
|
+
# pipe.| B
|
90
|
+
# pipe.% A
|
91
|
+
pipe.("Hallo", {}).must_equal [Pipetree::Flow::Right, "Hallo"]
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
# #>>
|
96
|
+
describe "#>>" do
|
97
|
+
let (:pipe) { Pipetree::Flow[] }
|
98
|
+
it {
|
99
|
+
pipe.>> ->(input, options) { input.reverse }
|
100
|
+
pipe.("Hallo", {}).must_equal [Pipetree::Flow::Right, "ollaH"]
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
#---
|
105
|
+
# #inspect
|
106
|
+
describe "#inspect" do
|
107
|
+
let (:pipe) { Pipetree::Flow[].&(A).<(B).%(A) }
|
108
|
+
|
109
|
+
it { pipe.inspect.must_equal %{[&A,<B,%A]} }
|
110
|
+
|
111
|
+
it { pipe.inspect(style: :rows).must_equal %{
|
112
|
+
0 &A
|
113
|
+
1 <B
|
114
|
+
2 %A} }
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#index" do
|
118
|
+
let (:pipe) { Pipetree::Flow[].&(A).<(B).%(A) }
|
119
|
+
|
120
|
+
it { pipe.index(B).must_equal 1 }
|
121
|
+
it { pipe.index(A).must_equal 0 }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# TODO: instead of testing #index, test all options like :before, etc.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class InspectTest < Minitest::Spec
|
4
|
+
module M
|
5
|
+
end
|
6
|
+
|
7
|
+
M::AlphaConstant = ->(*) { }
|
8
|
+
M::Beta = ->(*) { }
|
9
|
+
|
10
|
+
let (:pipe) { ::Pipetree[M::Beta, M::AlphaConstant, M::Beta, M::AlphaConstant, M::Beta, M::AlphaConstant, M::Beta, M::AlphaConstant, M::Beta, M::AlphaConstant, M::Beta] }
|
11
|
+
|
12
|
+
it do
|
13
|
+
puts pipe.inspect
|
14
|
+
puts pipe.inspect(style: :rows)
|
15
|
+
|
16
|
+
|
17
|
+
pipe.inspect(style: :rows).must_equal %{
|
18
|
+
0|>M::Beta
|
19
|
+
1|>M::AlphaConstant
|
20
|
+
2|>M::Beta
|
21
|
+
3|>M::AlphaConstant
|
22
|
+
4|>M::Beta
|
23
|
+
5|>M::AlphaConstant
|
24
|
+
6|>M::Beta
|
25
|
+
7|>M::AlphaConstant
|
26
|
+
8|>M::Beta
|
27
|
+
9|>M::AlphaConstant
|
28
|
+
10|>M::Beta}
|
29
|
+
end
|
30
|
+
|
31
|
+
# different separator
|
32
|
+
it { ::Pipetree[M::AlphaConstant,M::Beta].inspect.must_equal %{[M::AlphaConstant|>M::Beta]} }
|
33
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PipelineTest < MiniTest::Spec
|
4
|
+
Song = Struct.new(:title, :artist)
|
5
|
+
Artist = Struct.new(:name)
|
6
|
+
Album = Struct.new(:ratings, :artists)
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
Getter = ->(input, options) { "Yo" }
|
11
|
+
StopOnNil = ->(input, options) { input }
|
12
|
+
SkipRender = ->(input, *) { input == "Yo" ? input : P::Stop }
|
13
|
+
|
14
|
+
Prepare = ->(input, options) { "Prepare(#{input})" }
|
15
|
+
Deserialize = ->(input, options) { "Deserialize(#{input}, #{options[:fragment]})" }
|
16
|
+
|
17
|
+
SkipParse = ->(input, options) { input }
|
18
|
+
CreateObject = ->(input, options) { OpenStruct.new }
|
19
|
+
|
20
|
+
|
21
|
+
Setter = ->(input, options) { "Setter(#{input})" }
|
22
|
+
|
23
|
+
AssignFragment = ->(input, options) { options[:fragment] = input }
|
24
|
+
|
25
|
+
it "linear" do
|
26
|
+
Pipetree[SkipParse, Setter].("doc", {fragment: 1}).must_equal "Setter(doc)"
|
27
|
+
|
28
|
+
|
29
|
+
# parse style.
|
30
|
+
Pipetree[AssignFragment, SkipParse, CreateObject, Prepare].("Bla", {}).must_equal "Prepare(#<OpenStruct>)"
|
31
|
+
|
32
|
+
|
33
|
+
# render style.
|
34
|
+
Pipetree[Getter, StopOnNil, SkipRender, Prepare, Setter].(nil, {}).
|
35
|
+
must_equal "Setter(Prepare(Yo))"
|
36
|
+
|
37
|
+
# pipeline = Representable::Pipeline[SkipParse , SetResult, ModifyResult]
|
38
|
+
# pipeline.(fragment: "yo!").must_equal "modified object from yo!"
|
39
|
+
end
|
40
|
+
|
41
|
+
Stopping = ->(input, options) { return Pipetree::Stop if options[:fragment] == "stop!"; input }
|
42
|
+
|
43
|
+
|
44
|
+
it "stopping" do
|
45
|
+
pipeline = Pipetree[SkipParse, Stopping, Prepare]
|
46
|
+
pipeline.(nil, fragment: "oy!").must_equal "Prepare()"
|
47
|
+
pipeline.(nil, fragment: "stop!").must_equal Pipetree::Stop
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "Collect" do
|
51
|
+
Reverse = ->(input, options) { input.reverse }
|
52
|
+
Add = ->(input, options) { "#{input}+" }
|
53
|
+
let(:pipeline) { Pipetree::Collect[Reverse, Add] }
|
54
|
+
|
55
|
+
it { pipeline.(["yo!", "oy!"], {}).must_equal ["!oy+", "!yo+"] }
|
56
|
+
|
57
|
+
describe "Pipeline with Collect" do
|
58
|
+
let(:pipeline) { Pipetree[Reverse, Pipetree::Collect[Reverse, Add]] }
|
59
|
+
it { pipeline.(["yo!", "oy!"], {}).must_equal ["!yo+", "!oy+"] }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#---
|
64
|
+
# >1 arguments
|
65
|
+
|
66
|
+
First = ->(input, options) { input }
|
67
|
+
Second = ->(input, options, memory) { input }
|
68
|
+
|
69
|
+
it do
|
70
|
+
skip
|
71
|
+
Pipetree[First, Second].("", options={}, memory={})
|
72
|
+
end
|
73
|
+
|
74
|
+
# ######### collection :ratings
|
75
|
+
|
76
|
+
# let (:ratings) {
|
77
|
+
# dfn = R::Definition.new(:ratings, collection: true, skip_render: ->(*) { false })
|
78
|
+
|
79
|
+
# R::Hash::Binding::Collection.new(dfn)
|
80
|
+
# }
|
81
|
+
# it "render scalar collection" do
|
82
|
+
# doc = {}
|
83
|
+
# P[
|
84
|
+
# R::GetValue,
|
85
|
+
# R::StopOnSkipable,
|
86
|
+
# R::Collect[
|
87
|
+
# R::SkipRender,
|
88
|
+
# ],
|
89
|
+
# R::AssignName,
|
90
|
+
# R::WriteFragment
|
91
|
+
# ].extend(P::Debug).(nil, {represented: Album.new([1,2,3]), binding: ratings, doc: doc, options: {}}).must_equal([1,2,3])
|
92
|
+
|
93
|
+
# doc.must_equal({"ratings"=>[1,2,3]})
|
94
|
+
# end
|
95
|
+
|
96
|
+
# ######### collection :songs, extend: SongRepresenter
|
97
|
+
# let (:artists) {
|
98
|
+
# dfn = R::Definition.new(:artists, collection: true, extend: ArtistRepresenter, class: Artist)
|
99
|
+
|
100
|
+
# R::Hash::Binding::Collection.new(dfn)
|
101
|
+
# }
|
102
|
+
# it "render typed collection" do
|
103
|
+
# doc = {}
|
104
|
+
# P[
|
105
|
+
# R::GetValue,
|
106
|
+
# R::StopOnSkipable,
|
107
|
+
# R::Collect[
|
108
|
+
# R::Decorate,
|
109
|
+
# R::Serialize,
|
110
|
+
# ],
|
111
|
+
# R::AssignName,
|
112
|
+
# R::WriteFragment
|
113
|
+
# ].extend(P::Debug).(nil, {represented: Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]), binding: artists, doc: doc, options: {}}).must_equal([{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}])
|
114
|
+
|
115
|
+
# doc.must_equal({"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]})
|
116
|
+
# end
|
117
|
+
|
118
|
+
# let (:album_model) { Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]) }
|
119
|
+
|
120
|
+
# it "parse typed collection" do
|
121
|
+
# doc = {"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]}
|
122
|
+
# P[
|
123
|
+
# R::AssignName,
|
124
|
+
# R::ReadFragment,
|
125
|
+
# R::StopOnNotFound,
|
126
|
+
# R::OverwriteOnNil,
|
127
|
+
# # R::SkipParse,
|
128
|
+
# R::Collect[
|
129
|
+
# R::AssignFragment,
|
130
|
+
# R::CreateObject::Class,
|
131
|
+
# R::Decorate,
|
132
|
+
# R::Deserialize,
|
133
|
+
# ],
|
134
|
+
# R::SetValue,
|
135
|
+
# ].extend(P::Debug).(doc, {represented: album_model, binding: artists, doc: doc, options: {}}).must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")])
|
136
|
+
|
137
|
+
# album_model.artists.must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")])
|
138
|
+
# end
|
139
|
+
|
140
|
+
# # TODO: test with arrays, too, not "only" Pipeline instances.
|
141
|
+
# describe "#Insert Pipeline[], Function, replace: OldFunction" do
|
142
|
+
# let (:pipeline) { P[R::GetValue, R::StopOnSkipable, R::StopOnNil] }
|
143
|
+
|
144
|
+
# it "returns Pipeline instance when passing in Pipeline instance" do
|
145
|
+
# P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_be_instance_of(R::Pipeline)
|
146
|
+
# end
|
147
|
+
|
148
|
+
# it "replaces if exists" do
|
149
|
+
# # pipeline.insert!(R::Default, replace: R::StopOnSkipable)
|
150
|
+
# P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_equal P[R::GetValue, R::Default, R::StopOnNil]
|
151
|
+
# pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil]
|
152
|
+
# end
|
153
|
+
|
154
|
+
# it "replaces Function instance" do
|
155
|
+
# pipeline = P[R::Prepare, R::StopOnSkipable, R::StopOnNil]
|
156
|
+
# P::Insert.(pipeline, R::Default, replace: R::Prepare).must_equal P[R::Default, R::StopOnSkipable, R::StopOnNil]
|
157
|
+
# pipeline.must_equal P[R::Prepare, R::StopOnSkipable, R::StopOnNil]
|
158
|
+
# end
|
159
|
+
|
160
|
+
# it "does not replace when not existing" do
|
161
|
+
# P::Insert.(pipeline, R::Default, replace: R::Prepare)
|
162
|
+
# pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil]
|
163
|
+
# end
|
164
|
+
|
165
|
+
# it "applies on nested Collect" do
|
166
|
+
# pipeline = P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil]
|
167
|
+
|
168
|
+
# P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]"
|
169
|
+
# pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil]
|
170
|
+
|
171
|
+
|
172
|
+
# P::Insert.(pipeline, R::Default, replace: R::StopOnNil).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], Default]"
|
173
|
+
# end
|
174
|
+
|
175
|
+
# it "applies on nested Collect with Function::CreateObject" do
|
176
|
+
# pipeline = P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil]
|
177
|
+
|
178
|
+
# P::Insert.(pipeline, R::Default, replace: R::CreateObject).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]"
|
179
|
+
# pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil]
|
180
|
+
# end
|
181
|
+
# end
|
182
|
+
|
183
|
+
# describe "Insert.(delete: true)" do
|
184
|
+
# let(:pipeline) { P[R::GetValue, R::StopOnNil] }
|
185
|
+
|
186
|
+
# it do
|
187
|
+
# P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[StopOnNil]"
|
188
|
+
# pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, StopOnNil]"
|
189
|
+
# end
|
190
|
+
# end
|
191
|
+
|
192
|
+
# describe "Insert.(delete: true) with Collect" do
|
193
|
+
# let(:pipeline) { P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] }
|
194
|
+
|
195
|
+
# it do
|
196
|
+
# P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[Collect[StopOnSkipable], StopOnNil]"
|
197
|
+
# pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], StopOnNil]"
|
198
|
+
# end
|
199
|
+
# end
|
200
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pipetree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -59,9 +59,24 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
+
- CHANGES.md
|
63
|
+
- Gemfile
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
62
66
|
- lib/pipetree.rb
|
67
|
+
- lib/pipetree/flow.rb
|
68
|
+
- lib/pipetree/flow/inspect.rb
|
69
|
+
- lib/pipetree/insert.rb
|
70
|
+
- lib/pipetree/inspect.rb
|
63
71
|
- lib/pipetree/version.rb
|
64
72
|
- pipetree.gemspec
|
73
|
+
- test/altering_test.rb
|
74
|
+
- test/benchmark_monads.rb
|
75
|
+
- test/benchmarking.rb
|
76
|
+
- test/flow_test.rb
|
77
|
+
- test/inspect_test.rb
|
78
|
+
- test/pipetree_test.rb
|
79
|
+
- test/test_helper.rb
|
65
80
|
homepage: https://github.com/apotonick/pipetree
|
66
81
|
licenses:
|
67
82
|
- MIT
|
@@ -82,9 +97,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
97
|
version: '0'
|
83
98
|
requirements: []
|
84
99
|
rubyforge_project:
|
85
|
-
rubygems_version: 2.
|
100
|
+
rubygems_version: 2.6.3
|
86
101
|
signing_key:
|
87
102
|
specification_version: 4
|
88
103
|
summary: Functional nested pipeline dialect that reduces runtime logic, overriding
|
89
104
|
and awkward module inclusion.
|
90
|
-
test_files:
|
105
|
+
test_files:
|
106
|
+
- test/altering_test.rb
|
107
|
+
- test/benchmark_monads.rb
|
108
|
+
- test/benchmarking.rb
|
109
|
+
- test/flow_test.rb
|
110
|
+
- test/inspect_test.rb
|
111
|
+
- test/pipetree_test.rb
|
112
|
+
- test/test_helper.rb
|