pipetree 0.0.1 → 0.0.2
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 +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
|