pipetree 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +10 -0
- data/README.md +6 -0
- data/lib/pipetree/insert.rb +8 -22
- data/lib/pipetree/inspect.rb +1 -11
- data/lib/pipetree/{flow → railway}/inspect.rb +7 -11
- data/lib/pipetree/railway/operator.rb +29 -0
- data/lib/pipetree/railway.rb +103 -0
- data/lib/pipetree/version.rb +1 -1
- data/lib/pipetree.rb +1 -54
- data/test/altering_test.rb +14 -41
- data/test/inspect_test.rb +28 -28
- data/test/railway_test.rb +183 -0
- metadata +7 -9
- data/lib/pipetree/flow/step_map.rb +0 -24
- data/lib/pipetree/flow.rb +0 -111
- data/test/flow_test.rb +0 -188
- data/test/pipetree_test.rb +0 -213
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d2f95504872630ac29dd00818a64ab869910211
|
4
|
+
data.tar.gz: 1df1dc4a468ad5e4ab2970f70e6bcae19c0e3a22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0f00369c97fbe68c5b96910166d34cf06b172d23f2ce30a97a19962faa2c5f5dbec91fd07750e9840451d5bc24c18ecff6ed48ac41f4bf7195e0ad079dc2c88
|
7
|
+
data.tar.gz: 43e323582b82f168b93835d0fe4ae4c9b1b5d1989db3e70ee2d4a0f2d0d72f8b2580c74778dd6cb72ba9f6d69b0b121b27168aa385bbba4778c8d3ab148eb130
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# 0.1.0
|
2
|
+
|
3
|
+
* Rename `Flow` to `Railway`.
|
4
|
+
* All "operators" like `Flow#&` are now optional in `Operator` and considered experimental. Single entry point to adding steps is `Flow#add`.
|
5
|
+
* Remove `Flow#>>`.
|
6
|
+
* Add `Flow#add` which allows a low-level *tie*, a step that sits directly on the track with incoming interface `(last, input, options)` and outgoing `[new_track, input]`.
|
7
|
+
* Make `Flow#_insert` private, as we now have `add`.
|
8
|
+
* `And`'s direction can now be configured via `:on_true` and `:on_false`, allowing to deviate to any kind of track.
|
9
|
+
* Remove the `Pipetree` array implementation and save it for future versions.
|
10
|
+
|
1
11
|
# 0.0.5
|
2
12
|
|
3
13
|
* `Flow` doesn't inherit from `Array` anymore. Temporarily use `Uber::Delegates` to achieve the same interface. This will change in 0.1.x when `Insert` works against an array. This makes `Flow.new` the new canonical constructor which allows us to initialize `step@proc` properly.
|
data/README.md
CHANGED
@@ -16,6 +16,12 @@ Instead of implementing the perfect API where users can override methods, call `
|
|
16
16
|
|
17
17
|
This is way less tedious than climbing through `super` calls and callstacks.
|
18
18
|
|
19
|
+
##
|
20
|
+
|
21
|
+
[ tie, tie, tie ]
|
22
|
+
On.new(track, strut)
|
23
|
+
Right.new( step )
|
24
|
+
|
19
25
|
## Installation
|
20
26
|
|
21
27
|
In your `Gemfile`.
|
data/lib/pipetree/insert.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
module Pipetree::Function
|
2
2
|
class Insert
|
3
|
-
|
3
|
+
Operations = [:delete, :replace, :before, :after, :append, :prepend]
|
4
|
+
|
5
|
+
# DISCUSS: all methods write to original array.
|
6
|
+
def call(arr, operation, *args)
|
4
7
|
# arr = arr.dup
|
5
|
-
|
8
|
+
raise "[Pipetree] Unknown Insert operation #{args.inspect}" unless Operations.include?(operation)
|
6
9
|
|
7
|
-
# replace!(arr,
|
8
|
-
options.keys.reverse.each { |k| operations.include?(k) and return send("#{k}!", arr, options[k], func) }
|
10
|
+
send("#{operation}!", arr, *args) # replace!(arr, Old, New)
|
9
11
|
|
10
|
-
raise "[Pipetree] Unknown command #{options.inspect}" # TODO: test.
|
11
12
|
# arr
|
12
13
|
end
|
13
14
|
|
@@ -25,17 +26,10 @@ module Pipetree::Function
|
|
25
26
|
arr
|
26
27
|
end
|
27
28
|
|
28
|
-
def delete!(arr,
|
29
|
-
index = arr.index(
|
29
|
+
def delete!(arr, old_func, *)
|
30
|
+
index = arr.index(old_func)
|
30
31
|
arr.delete_at(index)
|
31
32
|
|
32
|
-
# TODO: make nice.
|
33
|
-
# arr.each_with_index { |func, index|
|
34
|
-
# if func.is_a?(Collect)
|
35
|
-
# arr[index] = Collect[*Pipeline::Insert.(func, removed_func, delete: true)]
|
36
|
-
# end
|
37
|
-
# }
|
38
|
-
|
39
33
|
arr
|
40
34
|
end
|
41
35
|
|
@@ -57,15 +51,7 @@ module Pipetree::Function
|
|
57
51
|
def prepend!(arr, old_func, new_func)
|
58
52
|
arr.unshift(new_func)
|
59
53
|
end
|
60
|
-
|
61
|
-
module Macros
|
62
|
-
def insert!(new_function, options)
|
63
|
-
Pipetree::Insert.(self, new_function, options)
|
64
|
-
end
|
65
|
-
end
|
66
54
|
end
|
67
55
|
end
|
68
56
|
|
69
57
|
Pipetree::Insert = Pipetree::Function::Insert.new
|
70
|
-
|
71
|
-
#FIXME: all methods write to original array.
|
data/lib/pipetree/inspect.rb
CHANGED
@@ -10,18 +10,8 @@ module Pipetree::Inspect
|
|
10
10
|
inspect_rows(names)
|
11
11
|
end
|
12
12
|
|
13
|
-
# open source file to retrieve the constant name.
|
14
13
|
def inspect_func(func)
|
15
|
-
|
16
|
-
inspect_proc(func)
|
17
|
-
end
|
18
|
-
|
19
|
-
def inspect_object(obj)
|
20
|
-
obj.inspect.sub(/0x\w+/, "")
|
21
|
-
end
|
22
|
-
|
23
|
-
def inspect_proc(proc)
|
24
|
-
File.readlines(proc.source_location[0])[proc.source_location[1]-1].match(/^\s+([\w\:]+)/)[1]
|
14
|
+
func
|
25
15
|
end
|
26
16
|
|
27
17
|
def inspect_line(names)
|
@@ -1,26 +1,22 @@
|
|
1
1
|
require "pipetree/inspect"
|
2
2
|
|
3
|
-
module Pipetree::
|
3
|
+
module Pipetree::Railway::Inspect
|
4
4
|
include ::Pipetree::Inspect
|
5
5
|
|
6
|
-
Proc = Struct.new(:name, :proc, :operator)
|
7
|
-
|
8
6
|
def inspect_func(step)
|
9
|
-
|
10
|
-
[cfg.name || super(cfg.proc), cfg.operator]
|
7
|
+
@inspect[step]
|
11
8
|
end
|
12
9
|
|
10
|
+
Operator = { Pipetree::Railway::Left => "<", Pipetree::Railway::Right => ">", }
|
11
|
+
|
13
12
|
def inspect_line(names)
|
14
|
-
string = names.collect { |i, name| "#{
|
13
|
+
string = names.collect { |i, (track, name)| "#{Operator[track]}#{name}" }.join(",")
|
15
14
|
"[#{string}]"
|
16
15
|
end
|
17
16
|
|
18
|
-
def inspect_row(index, name)
|
19
|
-
"#{index} #{name.last}#{name.first}"
|
20
|
-
end
|
21
|
-
|
22
17
|
def inspect_rows(names)
|
23
|
-
string = names.collect do |i, (
|
18
|
+
string = names.collect do |i, (track, name)|
|
19
|
+
operator = Operator[track]
|
24
20
|
|
25
21
|
op = "#{operator}#{name}"
|
26
22
|
padding = 38
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Optimize the most common steps with Stay/And objects that are faster than procs.
|
2
|
+
# This is experimental API and might be removed/changed without prior warning.
|
3
|
+
class Pipetree::Railway
|
4
|
+
module Operator
|
5
|
+
def <(proc, options={})
|
6
|
+
_insert(Pipetree::Railway.<(proc), options, Left, proc)
|
7
|
+
end
|
8
|
+
|
9
|
+
def &(proc, options={})
|
10
|
+
_insert(Pipetree::Railway.&(proc), options, Right, proc)
|
11
|
+
end
|
12
|
+
|
13
|
+
def >(proc, options={})
|
14
|
+
_insert(Pipetree::Railway.>(proc), options, Right, proc)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.<(proc)
|
19
|
+
On.new(Left, Stay.new(proc))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.&(proc)
|
23
|
+
On.new(Right, And.new(proc))
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.>(proc)
|
27
|
+
On.new(Right, Stay.new(proc))
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
class Pipetree
|
2
|
+
class Railway
|
3
|
+
require "pipetree/insert"
|
4
|
+
require "pipetree/railway/operator"
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
@steps = Array.new(*args)
|
8
|
+
@index = Hash.new
|
9
|
+
@inspect = Hash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Actual implementation of Pipetree:Railway. Yes, it's that simple!
|
13
|
+
def call(input, options)
|
14
|
+
input = [Right, input]
|
15
|
+
|
16
|
+
@steps.inject(input) do |(last, memo), step|
|
17
|
+
step.call(last, memo, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Naming:
|
22
|
+
# * Track
|
23
|
+
# * Tie: the callable that's usually an On instance and is sitting directly in the pipe, on a Track.
|
24
|
+
# * Strut: the callable that's wrapped by the Tie and implements the decider logic (e.g. And).
|
25
|
+
# * Step: the user callable with interface `Step.(input, options)`.
|
26
|
+
module Add
|
27
|
+
def add(track, strut, options={})
|
28
|
+
_insert On.new(track, strut), options, track, strut
|
29
|
+
end
|
30
|
+
|
31
|
+
require "uber/delegates"
|
32
|
+
extend Uber::Delegates
|
33
|
+
# TODO: make Insert properly decoupled! it still relies on Array interface on pipe`.
|
34
|
+
delegates :@steps, :<<, :each_with_index, :[]=, :delete_at, :insert, :unshift, :index
|
35
|
+
|
36
|
+
private
|
37
|
+
def _insert(tie, options, track, strut)
|
38
|
+
insert_operation = (options.keys & ::Pipetree::Function::Insert::Operations).last || :append
|
39
|
+
|
40
|
+
old_tie = @index[ options[insert_operation] ] # name --> tie
|
41
|
+
|
42
|
+
# todo: step, old_tie (e.g. for #delete!).
|
43
|
+
Insert.(self, insert_operation, old_tie, tie)
|
44
|
+
|
45
|
+
@index[options[:name]] = tie
|
46
|
+
@inspect[tie] = [ track, options[:name] ]
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
include Add
|
52
|
+
|
53
|
+
# Tracks emitted by steps.
|
54
|
+
Track = Class.new
|
55
|
+
Left = Class.new(Track)
|
56
|
+
Right = Class.new(Track)
|
57
|
+
|
58
|
+
# Incoming direction must be Left/Right.
|
59
|
+
# Tie
|
60
|
+
class On
|
61
|
+
def initialize(track, proc)
|
62
|
+
@track, @proc = track, proc
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(last, input, options)
|
66
|
+
return [last, input] unless last == @track # return unless incoming track is Right (or Left).
|
67
|
+
@proc.(last, input, options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Strut
|
72
|
+
def initialize(proc, config={})
|
73
|
+
@proc = proc
|
74
|
+
@config = config
|
75
|
+
end
|
76
|
+
|
77
|
+
def call(last, input, options)
|
78
|
+
result = @proc.(input, options) # call the actual step.
|
79
|
+
|
80
|
+
[self.class::Decider.(result, @config, last, input, options), input] # decide about the track and return Flow-compliant response.
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Call step proc and return (Right || Left).
|
85
|
+
class And < Strut
|
86
|
+
# Deciders return the new track.
|
87
|
+
Decider = ->(result, config, *) do
|
88
|
+
result ?
|
89
|
+
config[:on_true] || Right :
|
90
|
+
config[:on_false] || Left
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Call step proc and return incoming last step.
|
95
|
+
class Stay < Strut
|
96
|
+
# simply pass through the current direction: e.g. Left or Right.
|
97
|
+
Decider = ->(result, cfg, last, *) { last }
|
98
|
+
end
|
99
|
+
|
100
|
+
require "pipetree/railway/inspect"
|
101
|
+
include Inspect
|
102
|
+
end
|
103
|
+
end
|
data/lib/pipetree/version.rb
CHANGED
data/lib/pipetree.rb
CHANGED
@@ -1,55 +1,2 @@
|
|
1
|
-
class Pipetree
|
2
|
-
# Allows to implement a pipeline of filters where a value gets passed in and the result gets
|
3
|
-
# passed to the next callable object.
|
4
|
-
Stop = Class.new
|
5
|
-
|
6
|
-
# options is mutuable.
|
7
|
-
# we have a fixed set of arguments here, since array splat significantly slows this down, as in
|
8
|
-
# call(input, *options)
|
9
|
-
def call(input, options)
|
10
|
-
inject(input) do |memo, step|
|
11
|
-
res = evaluate(step, memo, options)
|
12
|
-
return(Stop) if Stop == res
|
13
|
-
res
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
def evaluate(step, input, options)
|
19
|
-
step.call(input, options)
|
20
|
-
end
|
21
|
-
|
22
|
-
require "pipetree/inspect"
|
23
|
-
include Inspect
|
24
|
-
|
25
|
-
module Macros
|
26
|
-
def insert!(new_function, options)
|
27
|
-
Pipetree::Insert.(self, new_function, options)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
require "pipetree/insert"
|
31
|
-
include Macros
|
32
|
-
|
33
|
-
# Collect applies a pipeline to each element of input.
|
34
|
-
class Collect < self
|
35
|
-
# when stop, the element is skipped. (should that be Skip then?)
|
36
|
-
def call(input, options)
|
37
|
-
arr = []
|
38
|
-
input.each_with_index do |item_fragment, i|
|
39
|
-
result = super(item_fragment, options.merge(index: i)) # DISCUSS: NO :fragment set.
|
40
|
-
Stop == result ? next : arr << result
|
41
|
-
end
|
42
|
-
arr
|
43
|
-
end
|
44
|
-
|
45
|
-
# DISCUSS: will this make it into the final version?
|
46
|
-
class Hash < self
|
47
|
-
def call(input, options)
|
48
|
-
{}.tap do |hsh|
|
49
|
-
input.each { |key, item_fragment|
|
50
|
-
hsh[key] = super(item_fragment, options) }# DISCUSS: NO :fragment set.
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
1
|
+
class Pipetree
|
55
2
|
end
|
data/test/altering_test.rb
CHANGED
@@ -1,48 +1,21 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
B = ->(*) { }
|
6
|
-
C = ->(*) { "otherwise it'll confuse empty procs" }
|
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
|
-
|
30
|
-
# last option wins
|
31
|
-
it { Pipetree[A,B].insert!(C, after: B, prepend: true).inspect.must_equal %{[C|>A|>B]} }
|
32
|
-
end
|
33
|
-
|
34
|
-
require "pipetree/flow"
|
35
|
-
class FlowInsertTest < Minitest::Spec
|
3
|
+
require "pipetree/railway"
|
4
|
+
class RailwayInsertTest < Minitest::Spec
|
36
5
|
A = ->{ }
|
37
6
|
B = ->{ }
|
38
7
|
C = ->{ }
|
39
8
|
|
40
|
-
|
41
|
-
|
42
|
-
it { pipe
|
43
|
-
it { pipe
|
44
|
-
it { pipe
|
45
|
-
it { pipe
|
46
|
-
it { pipe
|
47
|
-
|
9
|
+
let (:pipe) { Pipetree::Railway.new.extend(Pipetree::Railway::Operator) }
|
10
|
+
|
11
|
+
it { pipe.>(A, name: :A).>(B, name: :B).inspect.must_equal %{[>A,>B]} }
|
12
|
+
it { pipe.>(A, name: :A).>(B, before: :A, name: :B).inspect.must_equal %{[>B,>A]} }
|
13
|
+
it { pipe.>(A, name: :A).>(B, name: :B).>(C, after: :A, name: :C).inspect.must_equal %{[>A,>C,>B]} }
|
14
|
+
it { pipe.>(A, name: "A").>(C, append: true, name: :C).inspect.must_equal %{[>A,>C]} }
|
15
|
+
it { pipe.>(A, name: :A).>(C, prepend: true, name: "C").inspect.must_equal %{[>C,>A]} }
|
16
|
+
it { pipe.>(A, name: :A).>(C, replace: :A, name: :C).inspect.must_equal %{[>C]} }
|
17
|
+
it { pipe.>(A, name: :A).add(nil, :A, delete: :A).inspect.must_equal %{[]} }
|
18
|
+
# last insert operation wins.
|
19
|
+
it { pipe.>(A, name: :A).>(B, name: :B).>(C, name: :C, after: :A, before: :A).inspect.must_equal %{[>C,>A,>B]} }
|
20
|
+
it { pipe.>(A, name: :A).>(B, name: :B).>(C, name: :C, before: :A, after: :A).inspect.must_equal %{[>A,>C,>B]} }
|
48
21
|
end
|
data/test/inspect_test.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
require "test_helper"
|
1
|
+
# require "test_helper"
|
2
2
|
|
3
|
-
class InspectTest < Minitest::Spec
|
4
|
-
|
5
|
-
|
3
|
+
# class InspectTest < Minitest::Spec
|
4
|
+
# module M
|
5
|
+
# end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
# M::AlphaConstant = ->(*) { }
|
8
|
+
# M::Beta = ->(*) { }
|
9
|
+
# Callable = Object.new
|
10
10
|
|
11
|
-
|
11
|
+
# 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, Callable] }
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
# it do
|
14
|
+
# puts pipe.inspect
|
15
|
+
# puts pipe.inspect(style: :rows)
|
16
16
|
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
10|>M::Beta
|
30
|
-
11|>#<Object:>}
|
31
|
-
|
18
|
+
# pipe.inspect(style: :rows).must_equal %{
|
19
|
+
# 0|>M::Beta
|
20
|
+
# 1|>M::AlphaConstant
|
21
|
+
# 2|>M::Beta
|
22
|
+
# 3|>M::AlphaConstant
|
23
|
+
# 4|>M::Beta
|
24
|
+
# 5|>M::AlphaConstant
|
25
|
+
# 6|>M::Beta
|
26
|
+
# 7|>M::AlphaConstant
|
27
|
+
# 8|>M::Beta
|
28
|
+
# 9|>M::AlphaConstant
|
29
|
+
# 10|>M::Beta
|
30
|
+
# 11|>#<Object:>}
|
31
|
+
# end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
33
|
+
# # different separator
|
34
|
+
# it { ::Pipetree[M::AlphaConstant,M::Beta,Callable].inspect.must_equal %{[M::AlphaConstant|>M::Beta|>#<Object:>]} }
|
35
|
+
# end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "pipetree/railway"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
class RailwayTest < Minitest::Spec
|
6
|
+
F = Pipetree::Railway
|
7
|
+
|
8
|
+
describe "#add" do
|
9
|
+
let (:pipe) do
|
10
|
+
pipe = F.new
|
11
|
+
|
12
|
+
# add: (track, proc)
|
13
|
+
# macro returns [signal, input]: (can be added to pipe via #add)
|
14
|
+
step_1 = F::And.new(->(input, options) { options["x"] = true })
|
15
|
+
step_2 = F::And.new(->(input, options) { input })
|
16
|
+
step_3 = F::And.new(->(input, options) { options["step_3"] = true })
|
17
|
+
|
18
|
+
fail_1 = F::Stay.new(->(input, options) { options["fail_1"] = true })
|
19
|
+
fail_2 = F::And.new(->(input, options) { options["fail_2"] = true }, on_true: left_1, on_false: F::Left)
|
20
|
+
fail_3 = F::Stay.new(->(input, options) { options["fail_3"] = true })
|
21
|
+
|
22
|
+
|
23
|
+
pipe.add(F::Right, step_1)
|
24
|
+
pipe.add(F::Right, step_2)
|
25
|
+
pipe.add(F::Left, fail_1)
|
26
|
+
pipe.add(F::Left, fail_2)
|
27
|
+
pipe.add(F::Left, fail_3)
|
28
|
+
pipe.add(F::Right, step_3)
|
29
|
+
end
|
30
|
+
|
31
|
+
let (:left_1) { Class.new(F::Left) }
|
32
|
+
|
33
|
+
# only right
|
34
|
+
it { [pipe.(true, options={}), options].must_equal [[F::Right, true], {"x"=>true, "step_3"=>true}] }
|
35
|
+
# jumps to left at step_2
|
36
|
+
it { [pipe.(false, options={}), options].must_equal [[left_1, false], {"x"=>true, "fail_1"=>true, "fail_2"=>true}] }
|
37
|
+
|
38
|
+
# options for #add
|
39
|
+
# chainability of #add.
|
40
|
+
it do
|
41
|
+
F.new
|
42
|
+
.add(F::Right, Object, name: "operation.new")
|
43
|
+
.add(F::Right, Module, name: "nested.create", before: "operation.new")
|
44
|
+
.inspect.must_equal %{[>nested.create,>operation.new]}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Aaa = ->(*) { "yo" }
|
49
|
+
B = ->(*) { }
|
50
|
+
|
51
|
+
let (:pipe) { pipe = Pipetree::Railway.new.extend(Pipetree::Railway::Operator)
|
52
|
+
pipe.& ->(value, options) { value && options["deserializer.result"] = JSON.parse(value) }
|
53
|
+
pipe.& ->(value, options) { options["deserializer.result"]["key"] == 1 ? true : (options["contract.errors"]=false) }
|
54
|
+
pipe.& ->(value, options) { options["deserializer.result"]["key2"] == 2 ? true : (options["contract.errors.2"]="screwd";false) }
|
55
|
+
pipe.< ->(value, options) { options["after_deserialize.fail"]=true }
|
56
|
+
pipe.> ->(value, options) { options["meantime"] = true }
|
57
|
+
pipe.< ->(value, options) { options["after_meantime.left?"]=true; false } # false is ignored.
|
58
|
+
|
59
|
+
}
|
60
|
+
|
61
|
+
# success?
|
62
|
+
it do
|
63
|
+
options = {}
|
64
|
+
pipe.(%{{"key": 1,"key2":2}}, options)#.must_equal ""
|
65
|
+
|
66
|
+
options.must_equal({"deserializer.result"=>{"key"=>1, "key2"=>2}, "meantime"=>true})
|
67
|
+
end
|
68
|
+
|
69
|
+
# invalid?
|
70
|
+
it do
|
71
|
+
options = {}
|
72
|
+
pipe.(%{{"key": 2}}, options)#.must_equal ""
|
73
|
+
|
74
|
+
options.must_equal({"deserializer.result"=>{"key"=>2}, "contract.errors"=>false, "after_deserialize.fail"=>true, "after_meantime.left?"=>true})
|
75
|
+
end
|
76
|
+
|
77
|
+
it do
|
78
|
+
options = {}
|
79
|
+
pipe.(%{{"key": 1,"key2":null}}, options)#.must_equal ""
|
80
|
+
|
81
|
+
options.must_equal({"deserializer.result"=>{"key"=>1, "key2"=>nil}, "contract.errors.2"=>"screwd", "after_deserialize.fail"=>true, "after_meantime.left?"=>true})
|
82
|
+
end
|
83
|
+
|
84
|
+
#---
|
85
|
+
# return value is new input.
|
86
|
+
it do
|
87
|
+
pipe = Pipetree::Railway.new [
|
88
|
+
Pipetree::Railway::On.new(Pipetree::Railway::Right, ->(last, value, options) { [Pipetree::Railway::Right, value.reverse] } )
|
89
|
+
]
|
90
|
+
pipe.("Hello", {}).must_equal [Pipetree::Railway::Right, "olleH"]
|
91
|
+
end
|
92
|
+
|
93
|
+
#---
|
94
|
+
# #>
|
95
|
+
describe "#>" do
|
96
|
+
let (:pipe) { Pipetree::Railway.new.extend(Pipetree::Railway::Operator) }
|
97
|
+
it {
|
98
|
+
pipe.> ->(input, options) { input.reverse }
|
99
|
+
# pipe.| B
|
100
|
+
# pipe.% A
|
101
|
+
pipe.("Hallo", {}).must_equal [Pipetree::Railway::Right, "Hallo"]
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
#---
|
106
|
+
# #inspect
|
107
|
+
Seventeen = ->(*) { snippet }
|
108
|
+
Long = ->(*) { snippet }
|
109
|
+
Callable = Object.new # random callable object.
|
110
|
+
|
111
|
+
describe "#inspect" do
|
112
|
+
let (:pipe) { Pipetree::Railway.new.extend(Pipetree::Railway::Operator).&(Aaa, name: "Aaa").<(B, name: "B").<(Seventeen, name: "Seventeen").>(Long, name: "Long").>(Callable, name: "Callable") }
|
113
|
+
|
114
|
+
it { pipe.inspect.must_equal %{[>Aaa,<B,<Seventeen,>Long,>Callable]} }
|
115
|
+
|
116
|
+
it { pipe.inspect(style: :rows).must_equal %{
|
117
|
+
0 ==================================>Aaa
|
118
|
+
1 <B====================================
|
119
|
+
2 <Seventeen============================
|
120
|
+
3 =================================>Long
|
121
|
+
4 =============================>Callable} }
|
122
|
+
end
|
123
|
+
|
124
|
+
#---
|
125
|
+
# with aliases
|
126
|
+
it do
|
127
|
+
pipe = Pipetree::Railway.new.extend(Pipetree::Railway::Operator).
|
128
|
+
>(Aaa, name: "pipe.aaa").
|
129
|
+
>(B, name: "pipe.b").
|
130
|
+
>(Aaa, name: "pipe.aaa.aaa")
|
131
|
+
|
132
|
+
pipe.inspect.must_equal %{[>pipe.aaa,>pipe.b,>pipe.aaa.aaa]}
|
133
|
+
pipe.inspect(style: :rows).must_equal %{
|
134
|
+
0 =============================>pipe.aaa
|
135
|
+
1 ===============================>pipe.b
|
136
|
+
2 =========================>pipe.aaa.aaa}
|
137
|
+
|
138
|
+
pipe.>(Long, after: "pipe.b", name: "Long").inspect.must_equal %{[>pipe.aaa,>pipe.b,>Long,>pipe.aaa.aaa]}
|
139
|
+
end
|
140
|
+
|
141
|
+
#---
|
142
|
+
# test decompose array
|
143
|
+
it do
|
144
|
+
pipe = Pipetree::Railway.new.extend(Pipetree::Railway::Operator).
|
145
|
+
&( ->((value, input), options) { input["x"] = value } ) # decomposes input.
|
146
|
+
|
147
|
+
options={key: 1}
|
148
|
+
input = {}
|
149
|
+
|
150
|
+
pipe.([options[:key], input], options).must_equal [Pipetree::Railway::Right, [1, {"x"=>1}]]
|
151
|
+
input.inspect.must_equal %{{"x"=>1}}
|
152
|
+
options.inspect.must_equal %{{:key=>1}}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class NestedPipeTest < Minitest::Spec
|
157
|
+
R = Pipetree::Railway
|
158
|
+
|
159
|
+
it do
|
160
|
+
nested_pipe = Pipetree::Railway.new.extend(Pipetree::Railway::Operator)
|
161
|
+
.&( ->(input, options) { options["extract"] = true } )
|
162
|
+
.&( ->(input, options) { options["validate"] = options[:success] } )
|
163
|
+
|
164
|
+
# This is basically what Nested() does.
|
165
|
+
pipe = R.new.add(R::Right,
|
166
|
+
R::On.new( R::Right, ->(last, input, options) {
|
167
|
+
|
168
|
+
signal, input = nested_pipe.(input, options)
|
169
|
+
|
170
|
+
[signal, input] } )
|
171
|
+
)
|
172
|
+
|
173
|
+
|
174
|
+
options = { success: true }
|
175
|
+
pipe.(Object, options).must_equal [Pipetree::Railway::Right, Object]
|
176
|
+
|
177
|
+
options.inspect.must_equal %{{:success=>true, \"extract\"=>true, \"validate\"=>true}}
|
178
|
+
|
179
|
+
|
180
|
+
pipe.(Object, options = { success: false }).must_equal [Pipetree::Railway::Left, Object]
|
181
|
+
options.inspect.must_equal %{{:success=>false, \"extract\"=>true, \"validate\"=>false}}
|
182
|
+
end
|
183
|
+
end
|
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.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|
@@ -79,19 +79,18 @@ files:
|
|
79
79
|
- README.md
|
80
80
|
- Rakefile
|
81
81
|
- lib/pipetree.rb
|
82
|
-
- lib/pipetree/flow.rb
|
83
|
-
- lib/pipetree/flow/inspect.rb
|
84
|
-
- lib/pipetree/flow/step_map.rb
|
85
82
|
- lib/pipetree/insert.rb
|
86
83
|
- lib/pipetree/inspect.rb
|
84
|
+
- lib/pipetree/railway.rb
|
85
|
+
- lib/pipetree/railway/inspect.rb
|
86
|
+
- lib/pipetree/railway/operator.rb
|
87
87
|
- lib/pipetree/version.rb
|
88
88
|
- pipetree.gemspec
|
89
89
|
- test/altering_test.rb
|
90
90
|
- test/benchmark_monads.rb
|
91
91
|
- test/benchmarking.rb
|
92
|
-
- test/flow_test.rb
|
93
92
|
- test/inspect_test.rb
|
94
|
-
- test/
|
93
|
+
- test/railway_test.rb
|
95
94
|
- test/test_helper.rb
|
96
95
|
homepage: https://github.com/apotonick/pipetree
|
97
96
|
licenses:
|
@@ -122,7 +121,6 @@ test_files:
|
|
122
121
|
- test/altering_test.rb
|
123
122
|
- test/benchmark_monads.rb
|
124
123
|
- test/benchmarking.rb
|
125
|
-
- test/flow_test.rb
|
126
124
|
- test/inspect_test.rb
|
127
|
-
- test/
|
125
|
+
- test/railway_test.rb
|
128
126
|
- test/test_helper.rb
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# Map original proc or its name to the wrapped On.
|
2
|
-
# This class is solely dedicated for inspect and insert operations, and not
|
3
|
-
# involved at run-time at all.
|
4
|
-
class Pipetree::Flow::StepMap
|
5
|
-
def initialize
|
6
|
-
@hash = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def []=(step, (name, original_proc, operator))
|
10
|
-
@hash[step] = Pipetree::Flow::Inspect::Proc.new(name, original_proc, operator)
|
11
|
-
end
|
12
|
-
|
13
|
-
def [](key)
|
14
|
-
@hash[key]
|
15
|
-
end
|
16
|
-
|
17
|
-
def find_proc(original_proc)
|
18
|
-
method = original_proc.is_a?(String) ? :name : :proc
|
19
|
-
|
20
|
-
@hash.find do |step, inspect_proc|
|
21
|
-
inspect_proc.send(method) == original_proc and return step
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
data/lib/pipetree/flow.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
class Pipetree < Array
|
2
|
-
class Flow
|
3
|
-
require "pipetree/flow/inspect"
|
4
|
-
include Inspect
|
5
|
-
require "pipetree/flow/step_map"
|
6
|
-
require "pipetree/insert"
|
7
|
-
|
8
|
-
def initialize(*args)
|
9
|
-
@steps = Array.new(*args)
|
10
|
-
@step2proc = StepMap.new
|
11
|
-
end
|
12
|
-
|
13
|
-
# TODO: don't inherit from Array, because we don't want Array[].
|
14
|
-
|
15
|
-
module Operators
|
16
|
-
# Optimize the most common steps with Stay/And objects that are faster than procs.
|
17
|
-
def <(proc, options={})
|
18
|
-
_insert On.new(Left, Stay.new(proc)), options, proc, "<"
|
19
|
-
end
|
20
|
-
|
21
|
-
# OnRight-> ? Right, input : Left, input
|
22
|
-
def &(proc, options={})
|
23
|
-
_insert On.new(Right, And.new(proc)), options, proc, "&"
|
24
|
-
end
|
25
|
-
|
26
|
-
# TODO: test me.
|
27
|
-
def >(proc, options={})
|
28
|
-
_insert On.new(Right, Stay.new(proc)), options, proc, ">"
|
29
|
-
end
|
30
|
-
|
31
|
-
def >>(proc, options={})
|
32
|
-
_insert On.new(Right,
|
33
|
-
->(last, input, options) { [Right, proc.(input, options)] } ), options, proc, ">>"
|
34
|
-
end
|
35
|
-
|
36
|
-
def %(proc, options={})
|
37
|
-
# no condition is needed, and we want to stay on the same track, too.
|
38
|
-
_insert Stay.new(proc), options, proc, "%"
|
39
|
-
end
|
40
|
-
|
41
|
-
# :private:
|
42
|
-
# proc is the original step proc, e.g. Validate.
|
43
|
-
def _insert(step, options, original_proc, operator)
|
44
|
-
options = { append: true }.merge(options)
|
45
|
-
|
46
|
-
insert!(step, options).tap do
|
47
|
-
@step2proc[step] = options[:name], original_proc, operator
|
48
|
-
end
|
49
|
-
|
50
|
-
self
|
51
|
-
end
|
52
|
-
|
53
|
-
# :private:
|
54
|
-
def index(proc) # @step2proc: { <On @proc> => {proc: @proc, name: "trb.validate", operator: "&"} }
|
55
|
-
on = @step2proc.find_proc(proc) and return @steps.index(on)
|
56
|
-
end
|
57
|
-
|
58
|
-
require "uber/delegates"
|
59
|
-
extend Uber::Delegates
|
60
|
-
delegates :@steps, :<<, :each_with_index, :[]=, :delete_at, :insert, :unshift # FIXME: make Insert properly decoupled!
|
61
|
-
end
|
62
|
-
include Operators
|
63
|
-
|
64
|
-
# Actual implementation of Pipetree:Flow. Yes, it's that simple!
|
65
|
-
def call(input, options)
|
66
|
-
input = [Right, input]
|
67
|
-
|
68
|
-
@steps.inject(input) do |memooo, step|
|
69
|
-
last, memo = memooo
|
70
|
-
step.call(last, memo, options)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Directions emitted by steps.
|
75
|
-
Left = Class.new
|
76
|
-
Right = Class.new
|
77
|
-
|
78
|
-
# Incoming direction must be Left/Right.
|
79
|
-
class On
|
80
|
-
def initialize(direction, proc)
|
81
|
-
@direction, @proc = direction, proc
|
82
|
-
end
|
83
|
-
|
84
|
-
def call(last, input, options)
|
85
|
-
return [last, input] unless last == @direction # return unless incoming direction is Right (or Left).
|
86
|
-
@proc.(last, input, options)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# Call step proc and return (Right || Left).
|
91
|
-
class And
|
92
|
-
def initialize(proc)
|
93
|
-
@proc = proc
|
94
|
-
end
|
95
|
-
|
96
|
-
def call(last, input, options)
|
97
|
-
@proc.(input, options) ? [Right, input] : [Left, input]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Call step proc and return incoming last step.
|
102
|
-
class Stay < And
|
103
|
-
def call(last, input, options)
|
104
|
-
@proc.(input, options)
|
105
|
-
[last, input] # simply pass through the current direction: either [Left, input] or [Right, input].
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
include Function::Insert::Macros # #insert!
|
110
|
-
end
|
111
|
-
end
|
data/test/flow_test.rb
DELETED
@@ -1,188 +0,0 @@
|
|
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.new }
|
32
|
-
# it do
|
33
|
-
# pipe.>> ->(*) { puts "snippet" }
|
34
|
-
# pipe.({},{})
|
35
|
-
# end
|
36
|
-
# end
|
37
|
-
|
38
|
-
Aaa = ->(*) { "yo" }
|
39
|
-
B = ->(*) { }
|
40
|
-
|
41
|
-
let (:pipe) { pipe = Pipetree::Flow.new
|
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 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.new [
|
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.new }
|
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.new }
|
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
|
-
Seventeen = ->(*) { snippet }
|
107
|
-
Long = ->(*) { snippet }
|
108
|
-
Callable = Object.new # random callable object.
|
109
|
-
|
110
|
-
describe "#inspect" do
|
111
|
-
let (:pipe) { Pipetree::Flow.new.&(Aaa).>>(Long).<(B).%(Aaa).<(Seventeen).>(Long).>(Callable) }
|
112
|
-
|
113
|
-
it { pipe.inspect.must_equal %{[&Aaa,>>Long,<B,%Aaa,<Seventeen,>Long,>#<Object:>]} }
|
114
|
-
|
115
|
-
it { pipe.inspect(style: :rows).must_equal %{
|
116
|
-
0 ==================================&Aaa
|
117
|
-
1 ================================>>Long
|
118
|
-
2 <B====================================
|
119
|
-
3 =================%Aaa=================
|
120
|
-
4 <Seventeen============================
|
121
|
-
5 =================================>Long
|
122
|
-
6 ===========================>#<Object:>} }
|
123
|
-
end
|
124
|
-
|
125
|
-
describe "#index" do
|
126
|
-
let (:pipe) { Pipetree::Flow.new.&(Aaa).<(B).%(Aaa, name: "a.triple") }
|
127
|
-
|
128
|
-
it { pipe.index(B).must_equal 1 }
|
129
|
-
it { pipe.index(Aaa).must_equal 0 }
|
130
|
-
# with alias
|
131
|
-
it { pipe.index("a.triple").must_equal 2 }
|
132
|
-
|
133
|
-
# without steps
|
134
|
-
it { Pipetree::Flow.new.index(B).must_equal nil }
|
135
|
-
end
|
136
|
-
|
137
|
-
#---
|
138
|
-
# with aliases
|
139
|
-
it do
|
140
|
-
pipe = Pipetree::Flow.new.
|
141
|
-
>(Aaa, name: "pipe.aaa").
|
142
|
-
>(B, name: "pipe.b").
|
143
|
-
>(Aaa, name: "pipe.aaa.aaa")
|
144
|
-
|
145
|
-
pipe.inspect.must_equal %{[>pipe.aaa,>pipe.b,>pipe.aaa.aaa]}
|
146
|
-
pipe.inspect(style: :rows).must_equal %{
|
147
|
-
0 =============================>pipe.aaa
|
148
|
-
1 ===============================>pipe.b
|
149
|
-
2 =========================>pipe.aaa.aaa}
|
150
|
-
|
151
|
-
pipe.>(Long, after: "pipe.b").inspect.must_equal %{[>pipe.aaa,>pipe.b,>Long,>pipe.aaa.aaa]}
|
152
|
-
end
|
153
|
-
|
154
|
-
#---
|
155
|
-
# test decompose array
|
156
|
-
it do
|
157
|
-
pipe = Pipetree::Flow.new.
|
158
|
-
>>( ->(input, options) { [options[:key], input] } ). # passes [bla, input] as input.
|
159
|
-
&( ->((value, input), options) { input["x"] = value } ) # decomposes input.
|
160
|
-
|
161
|
-
pipe.(input={}, options={key: 1}).must_equal [Pipetree::Flow::Right, [1, {"x"=>1}]]
|
162
|
-
input.inspect.must_equal %{{"x"=>1}}
|
163
|
-
options.inspect.must_equal %{{:key=>1}}
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
#- StepMap
|
168
|
-
class StepMapTest < Minitest::Spec
|
169
|
-
it do
|
170
|
-
map = Pipetree::Flow::StepMap.new
|
171
|
-
|
172
|
-
original_proc = ->(*) { snippet }
|
173
|
-
step = ->(*) { original_proc }
|
174
|
-
|
175
|
-
original_proc2 = ->(*) { snippet }
|
176
|
-
step2 = ->(*) { original_proc2 }
|
177
|
-
|
178
|
-
map[step] = "my.step", original_proc, ">"
|
179
|
-
map[step2] = "my.step2", original_proc2, ">"
|
180
|
-
|
181
|
-
map.find_proc(original_proc).must_equal step
|
182
|
-
map.find_proc("my.step").must_equal step
|
183
|
-
map.find_proc(original_proc2).must_equal step2
|
184
|
-
map.find_proc("my.step2").must_equal step2
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
# TODO: instead of testing #index, test all options like :before, etc.
|
data/test/pipetree_test.rb
DELETED
@@ -1,213 +0,0 @@
|
|
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
|
-
#---
|
75
|
-
#- #index
|
76
|
-
it do
|
77
|
-
A = ->(*) { snippet }
|
78
|
-
B = ->(*) { snippet }
|
79
|
-
|
80
|
-
pipe = Pipetree[A, B]
|
81
|
-
pipe.index(A).must_equal 0
|
82
|
-
pipe.index(B).must_equal 1
|
83
|
-
end
|
84
|
-
|
85
|
-
# ######### collection :ratings
|
86
|
-
|
87
|
-
# let (:ratings) {
|
88
|
-
# dfn = R::Definition.new(:ratings, collection: true, skip_render: ->(*) { false })
|
89
|
-
|
90
|
-
# R::Hash::Binding::Collection.new(dfn)
|
91
|
-
# }
|
92
|
-
# it "render scalar collection" do
|
93
|
-
# doc = {}
|
94
|
-
# P[
|
95
|
-
# R::GetValue,
|
96
|
-
# R::StopOnSkipable,
|
97
|
-
# R::Collect[
|
98
|
-
# R::SkipRender,
|
99
|
-
# ],
|
100
|
-
# R::AssignName,
|
101
|
-
# R::WriteFragment
|
102
|
-
# ].extend(P::Debug).(nil, {represented: Album.new([1,2,3]), binding: ratings, doc: doc, options: {}}).must_equal([1,2,3])
|
103
|
-
|
104
|
-
# doc.must_equal({"ratings"=>[1,2,3]})
|
105
|
-
# end
|
106
|
-
|
107
|
-
# ######### collection :songs, extend: SongRepresenter
|
108
|
-
# let (:artists) {
|
109
|
-
# dfn = R::Definition.new(:artists, collection: true, extend: ArtistRepresenter, class: Artist)
|
110
|
-
|
111
|
-
# R::Hash::Binding::Collection.new(dfn)
|
112
|
-
# }
|
113
|
-
# it "render typed collection" do
|
114
|
-
# doc = {}
|
115
|
-
# P[
|
116
|
-
# R::GetValue,
|
117
|
-
# R::StopOnSkipable,
|
118
|
-
# R::Collect[
|
119
|
-
# R::Decorate,
|
120
|
-
# R::Serialize,
|
121
|
-
# ],
|
122
|
-
# R::AssignName,
|
123
|
-
# R::WriteFragment
|
124
|
-
# ].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"}])
|
125
|
-
|
126
|
-
# doc.must_equal({"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]})
|
127
|
-
# end
|
128
|
-
|
129
|
-
# let (:album_model) { Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]) }
|
130
|
-
|
131
|
-
# it "parse typed collection" do
|
132
|
-
# doc = {"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]}
|
133
|
-
# P[
|
134
|
-
# R::AssignName,
|
135
|
-
# R::ReadFragment,
|
136
|
-
# R::StopOnNotFound,
|
137
|
-
# R::OverwriteOnNil,
|
138
|
-
# # R::SkipParse,
|
139
|
-
# R::Collect[
|
140
|
-
# R::AssignFragment,
|
141
|
-
# R::CreateObject::Class,
|
142
|
-
# R::Decorate,
|
143
|
-
# R::Deserialize,
|
144
|
-
# ],
|
145
|
-
# R::SetValue,
|
146
|
-
# ].extend(P::Debug).(doc, {represented: album_model, binding: artists, doc: doc, options: {}}).must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")])
|
147
|
-
|
148
|
-
# album_model.artists.must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")])
|
149
|
-
# end
|
150
|
-
|
151
|
-
# # TODO: test with arrays, too, not "only" Pipeline instances.
|
152
|
-
# describe "#Insert Pipeline[], Function, replace: OldFunction" do
|
153
|
-
# let (:pipeline) { P[R::GetValue, R::StopOnSkipable, R::StopOnNil] }
|
154
|
-
|
155
|
-
# it "returns Pipeline instance when passing in Pipeline instance" do
|
156
|
-
# P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_be_instance_of(R::Pipeline)
|
157
|
-
# end
|
158
|
-
|
159
|
-
# it "replaces if exists" do
|
160
|
-
# # pipeline.insert!(R::Default, replace: R::StopOnSkipable)
|
161
|
-
|
162
|
-
|
163
|
-
# P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_equal P[R::GetValue, R::Default, R::StopOnNil]
|
164
|
-
# pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil]
|
165
|
-
# end
|
166
|
-
|
167
|
-
# it "replaces Function instance" do
|
168
|
-
# pipeline = P[R::Prepare, R::StopOnSkipable, R::StopOnNil]
|
169
|
-
# P::Insert.(pipeline, R::Default, replace: R::Prepare).must_equal P[R::Default, R::StopOnSkipable, R::StopOnNil]
|
170
|
-
# pipeline.must_equal P[R::Prepare, R::StopOnSkipable, R::StopOnNil]
|
171
|
-
# end
|
172
|
-
|
173
|
-
# it "does not replace when not existing" do
|
174
|
-
# P::Insert.(pipeline, R::Default, replace: R::Prepare)
|
175
|
-
# pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil]
|
176
|
-
# end
|
177
|
-
|
178
|
-
# it "applies on nested Collect" do
|
179
|
-
# pipeline = P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil]
|
180
|
-
|
181
|
-
# P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]"
|
182
|
-
# pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil]
|
183
|
-
|
184
|
-
|
185
|
-
# P::Insert.(pipeline, R::Default, replace: R::StopOnNil).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], Default]"
|
186
|
-
# end
|
187
|
-
|
188
|
-
# it "applies on nested Collect with Function::CreateObject" do
|
189
|
-
# pipeline = P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil]
|
190
|
-
|
191
|
-
# P::Insert.(pipeline, R::Default, replace: R::CreateObject).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]"
|
192
|
-
# pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil]
|
193
|
-
# end
|
194
|
-
# end
|
195
|
-
|
196
|
-
# describe "Insert.(delete: true)" do
|
197
|
-
# let(:pipeline) { P[R::GetValue, R::StopOnNil] }
|
198
|
-
|
199
|
-
# it do
|
200
|
-
# P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[StopOnNil]"
|
201
|
-
# pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, StopOnNil]"
|
202
|
-
# end
|
203
|
-
# end
|
204
|
-
|
205
|
-
# describe "Insert.(delete: true) with Collect" do
|
206
|
-
# let(:pipeline) { P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] }
|
207
|
-
|
208
|
-
# it do
|
209
|
-
# P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[Collect[StopOnSkipable], StopOnNil]"
|
210
|
-
# pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], StopOnNil]"
|
211
|
-
# end
|
212
|
-
# end
|
213
|
-
end
|