pipetree 0.0.5 → 0.1.0
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 +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
|