callable_tree 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 +7 -0
- data/.github/workflows/build.yml +25 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +436 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/callable_tree.gemspec +35 -0
- data/examples/docs/animals.json +6 -0
- data/examples/docs/animals.xml +1 -0
- data/examples/docs/fruits.json +6 -0
- data/examples/docs/fruits.xml +1 -0
- data/examples/example1.rb +101 -0
- data/examples/example2.rb +101 -0
- data/examples/example3.rb +122 -0
- data/examples/example4.rb +158 -0
- data/examples/example5.rb +35 -0
- data/lib/callable_tree.rb +14 -0
- data/lib/callable_tree/node.rb +45 -0
- data/lib/callable_tree/node/branch.rb +35 -0
- data/lib/callable_tree/node/external.rb +49 -0
- data/lib/callable_tree/node/external/verbose.rb +20 -0
- data/lib/callable_tree/node/hooks/call.rb +67 -0
- data/lib/callable_tree/node/internal.rb +91 -0
- data/lib/callable_tree/node/root.rb +14 -0
- data/lib/callable_tree/version.rb +5 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3d3a8425d7d4634140f8ae67d37b00212909bfefb2055fd75d98e8d6f851b5be
|
4
|
+
data.tar.gz: 0103f8cda6b99b7f122b79f95f6b70bfdd8c8b5aecc01791e870941b0d5d2f36
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6722db751a4b784f8983aa9164371bf9f933c3ea7a1e00b3e0595970a3842f93fd86357da48fc6958babdb7add5d0018d5ace986049ae286961bf328ddcc36ab
|
7
|
+
data.tar.gz: 74fd411e58ef8b04d810504f2003809f50488e6fabcc8ff9ff4b596e509b750eaeb4e333698d259dd6a39ce3293f84f2755bcaa3d296a5364d17bbb315128ff9
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: build
|
2
|
+
on: [push]
|
3
|
+
jobs:
|
4
|
+
build:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
matrix:
|
8
|
+
ruby: ['2.4', '2.5', '2.6', '2.7', '3.0']
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: ${{ matrix.ruby }}
|
14
|
+
- run: gem install bundler:2.2.4
|
15
|
+
- uses: actions/cache@v2
|
16
|
+
with:
|
17
|
+
path: vendor/bundle
|
18
|
+
key: ${{ runner.os }}-ruby-${{ matrix.ruby }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
19
|
+
restore-keys: |
|
20
|
+
${{ runner.os }}-gems-
|
21
|
+
- name: Run bundle install
|
22
|
+
run: |
|
23
|
+
bundle config path vendor/bundle
|
24
|
+
bundle install --jobs 4 --retry 3
|
25
|
+
- run: bundle exec rspec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.0
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
callable_tree (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.4.4)
|
10
|
+
rake (13.0.3)
|
11
|
+
rspec (3.10.0)
|
12
|
+
rspec-core (~> 3.10.0)
|
13
|
+
rspec-expectations (~> 3.10.0)
|
14
|
+
rspec-mocks (~> 3.10.0)
|
15
|
+
rspec-core (3.10.1)
|
16
|
+
rspec-support (~> 3.10.0)
|
17
|
+
rspec-expectations (3.10.1)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.10.0)
|
20
|
+
rspec-mocks (3.10.2)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.10.0)
|
23
|
+
rspec-support (3.10.2)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
x86_64-darwin-19
|
27
|
+
x86_64-darwin-20
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
callable_tree!
|
31
|
+
rake (~> 13.0)
|
32
|
+
rspec (~> 3.0)
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
2.2.17
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,436 @@
|
|
1
|
+
# CallableTree
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'callable_tree'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle install
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install callable_tree
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
- `CallableTree::Node::Internal`
|
22
|
+
- This `module` is used to define a node that can have child nodes.
|
23
|
+
- `CallableTree::Node::External`
|
24
|
+
- This `module` is used to define a leaf node that cannot have child nodes.
|
25
|
+
- `CallableTree::Node::Root`
|
26
|
+
- This `class` includes `CallableTree::Node::Internal`. When there is no need to customize the internal node, use this `class`.
|
27
|
+
|
28
|
+
Builds a tree by linking instances of the nodes. The `call` method of the node where the `match?` method returns a truthy value is called in a chain from the root node to the leaf node.
|
29
|
+
If the `call` method returns a value other than `nil`, the next sibling node does not be called. This behavior is changeable by overriding the `terminate?` method.
|
30
|
+
|
31
|
+
### Basic
|
32
|
+
|
33
|
+
`examples/example1.rb`:
|
34
|
+
```ruby
|
35
|
+
module Node
|
36
|
+
module JSON
|
37
|
+
class Parser
|
38
|
+
include CallableTree::Node::Internal
|
39
|
+
|
40
|
+
def match?(input, **options)
|
41
|
+
File.extname(input) == '.json'
|
42
|
+
end
|
43
|
+
|
44
|
+
# If there is need to convert the input value for
|
45
|
+
# child nodes, override the `call` method.
|
46
|
+
def call(input, **options)
|
47
|
+
File.open(input) do |file|
|
48
|
+
json = ::JSON.load(file)
|
49
|
+
super(json, **options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# If a returned value of the `call` method is `nil`,
|
54
|
+
# but there is no need to call the sibling nodes,
|
55
|
+
# override the `terminate?` method to return `true`.
|
56
|
+
def terminate?(output, **options)
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Scraper
|
62
|
+
include CallableTree::Node::External
|
63
|
+
|
64
|
+
def initialize(type:)
|
65
|
+
@type = type
|
66
|
+
end
|
67
|
+
|
68
|
+
def match?(input, **options)
|
69
|
+
!!input[@type.to_s]
|
70
|
+
end
|
71
|
+
|
72
|
+
def call(input, **options)
|
73
|
+
input[@type.to_s]
|
74
|
+
.map { |element| [element['name'], element['emoji']] }
|
75
|
+
.to_h
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module XML
|
81
|
+
class Parser
|
82
|
+
include CallableTree::Node::Internal
|
83
|
+
|
84
|
+
def match?(input, **options)
|
85
|
+
File.extname(input) == '.xml'
|
86
|
+
end
|
87
|
+
|
88
|
+
# If there is need to convert the input value for
|
89
|
+
# child nodes, override the `call` method.
|
90
|
+
def call(input, **options)
|
91
|
+
File.open(input) do |file|
|
92
|
+
super(REXML::Document.new(file), **options)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# If a returned value of the `call` method is `nil`,
|
97
|
+
# but there is no need to call the sibling nodes,
|
98
|
+
# override the `terminate?` method to return `true`.
|
99
|
+
def terminate?(output, **options)
|
100
|
+
true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Scraper
|
105
|
+
include CallableTree::Node::External
|
106
|
+
|
107
|
+
def initialize(type:)
|
108
|
+
@type = type
|
109
|
+
end
|
110
|
+
|
111
|
+
def match?(input, **options)
|
112
|
+
!input.get_elements("//#{@type}").empty?
|
113
|
+
end
|
114
|
+
|
115
|
+
def call(input, **options)
|
116
|
+
input
|
117
|
+
.get_elements("//#{@type}")
|
118
|
+
.first
|
119
|
+
.map { |element| [element['name'], element['emoji']] }
|
120
|
+
.to_h
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
tree = CallableTree::Node::Root.new.append(
|
127
|
+
Node::JSON::Parser.new.append(
|
128
|
+
Node::JSON::Scraper.new(type: :animals),
|
129
|
+
Node::JSON::Scraper.new(type: :fruits)
|
130
|
+
),
|
131
|
+
Node::XML::Parser.new.append(
|
132
|
+
Node::XML::Scraper.new(type: :animals),
|
133
|
+
Node::XML::Scraper.new(type: :fruits)
|
134
|
+
)
|
135
|
+
)
|
136
|
+
|
137
|
+
Dir.glob(__dir__ + '/docs/*') do |file|
|
138
|
+
options = { foo: :bar }
|
139
|
+
puts tree.call(file, **options)
|
140
|
+
puts '---'
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
Run `examples/example1.rb`:
|
145
|
+
```sh
|
146
|
+
% ruby examples/example1.rb
|
147
|
+
{"Dog"=>"🐶", "Cat"=>"🐱"}
|
148
|
+
---
|
149
|
+
{"Dog"=>"🐶", "Cat"=>"🐱"}
|
150
|
+
---
|
151
|
+
{"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
|
152
|
+
---
|
153
|
+
{"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
|
154
|
+
---
|
155
|
+
```
|
156
|
+
|
157
|
+
### Advanced
|
158
|
+
|
159
|
+
#### `CallableTree::Node::External#verbosify`
|
160
|
+
|
161
|
+
If you want verbose result, call it.
|
162
|
+
|
163
|
+
`examples/example2.rb`:
|
164
|
+
```ruby
|
165
|
+
...
|
166
|
+
|
167
|
+
tree = CallableTree::Node::Root.new.append(
|
168
|
+
Node::JSON::Parser.new.append(
|
169
|
+
Node::JSON::Scraper.new(type: :animals).verbosify,
|
170
|
+
Node::JSON::Scraper.new(type: :fruits).verbosify
|
171
|
+
),
|
172
|
+
Node::XML::Parser.new.append(
|
173
|
+
Node::XML::Scraper.new(type: :animals).verbosify,
|
174
|
+
Node::XML::Scraper.new(type: :fruits).verbosify
|
175
|
+
)
|
176
|
+
)
|
177
|
+
|
178
|
+
...
|
179
|
+
```
|
180
|
+
|
181
|
+
Run `examples/example2.rb`:
|
182
|
+
```sh
|
183
|
+
% ruby examples/example2.rb
|
184
|
+
#<struct CallableTree::Node::External::Output value={"Dog"=>"🐶", "Cat"=>"🐱"}, options={:foo=>:bar}, routes=[Node::JSON::Scraper, Node::JSON::Parser, CallableTree::Node::Root]>
|
185
|
+
---
|
186
|
+
#<struct CallableTree::Node::External::Output value={"Dog"=>"🐶", "Cat"=>"🐱"}, options={:foo=>:bar}, routes=[Node::XML::Scraper, Node::XML::Parser, CallableTree::Node::Root]>
|
187
|
+
---
|
188
|
+
#<struct CallableTree::Node::External::Output value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"}, options={:foo=>:bar}, routes=[Node::JSON::Scraper, Node::JSON::Parser, CallableTree::Node::Root]>
|
189
|
+
---
|
190
|
+
#<struct CallableTree::Node::External::Output value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"}, options={:foo=>:bar}, routes=[Node::XML::Scraper, Node::XML::Parser, CallableTree::Node::Root]>
|
191
|
+
---
|
192
|
+
```
|
193
|
+
|
194
|
+
At first glance, this looks good, but the `routes` are ambiguous when there are multiple nodes of the same class.
|
195
|
+
You can work around it by overriding the `identity` method of the node.
|
196
|
+
|
197
|
+
#### `CallableTree::Node#identity`
|
198
|
+
|
199
|
+
If you want to customize the node identity, override it.
|
200
|
+
|
201
|
+
`examples/example3.rb`:
|
202
|
+
```ruby
|
203
|
+
module Node
|
204
|
+
class Identity
|
205
|
+
attr_reader :klass, :type
|
206
|
+
|
207
|
+
def initialize(klass:, type:)
|
208
|
+
@klass = klass
|
209
|
+
@type = type
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_s
|
213
|
+
"#{klass}(#{type})"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
module JSON
|
218
|
+
...
|
219
|
+
|
220
|
+
class Scraper
|
221
|
+
include CallableTree::Node::External
|
222
|
+
|
223
|
+
def initialize(type:)
|
224
|
+
@type = type
|
225
|
+
end
|
226
|
+
|
227
|
+
def identity
|
228
|
+
Identity.new(klass: super, type: @type)
|
229
|
+
end
|
230
|
+
|
231
|
+
...
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
module XML
|
236
|
+
...
|
237
|
+
|
238
|
+
class Scraper
|
239
|
+
include CallableTree::Node::External
|
240
|
+
|
241
|
+
def initialize(type:)
|
242
|
+
@type = type
|
243
|
+
end
|
244
|
+
|
245
|
+
def identity
|
246
|
+
Identity.new(klass: super, type: @type)
|
247
|
+
end
|
248
|
+
|
249
|
+
...
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
...
|
255
|
+
```
|
256
|
+
|
257
|
+
Run `examples/example3.rb`:
|
258
|
+
```sh
|
259
|
+
% ruby examples/example3.rb
|
260
|
+
#<struct CallableTree::Node::External::Output value={"Dog"=>"🐶", "Cat"=>"🐱"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007ff1f78e09e0 @klass=Node::JSON::Scraper, @type=:animals>, Node::JSON::Parser, CallableTree::Node::Root]>
|
261
|
+
---
|
262
|
+
#<struct CallableTree::Node::External::Output value={"Dog"=>"🐶", "Cat"=>"🐱"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007ff1e7885208 @klass=Node::XML::Scraper, @type=:animals>, Node::XML::Parser, CallableTree::Node::Root]>
|
263
|
+
---
|
264
|
+
#<struct CallableTree::Node::External::Output value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007ff1e78771a8 @klass=Node::JSON::Scraper, @type=:fruits>, Node::JSON::Parser, CallableTree::Node::Root]>
|
265
|
+
---
|
266
|
+
#<struct CallableTree::Node::External::Output value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007ff1f20d7f50 @klass=Node::XML::Scraper, @type=:fruits>, Node::XML::Parser, CallableTree::Node::Root]>
|
267
|
+
---
|
268
|
+
```
|
269
|
+
|
270
|
+
#### Logging
|
271
|
+
|
272
|
+
This is an example of logging.
|
273
|
+
|
274
|
+
`examples/example4.rb`:
|
275
|
+
```ruby
|
276
|
+
module Node
|
277
|
+
module Logging
|
278
|
+
INDENT_SIZE = 2
|
279
|
+
BLANK = ' '.freeze
|
280
|
+
|
281
|
+
module Match
|
282
|
+
LIST_STYLE = '*'.freeze
|
283
|
+
|
284
|
+
def match?(_input, **)
|
285
|
+
super.tap do |matched|
|
286
|
+
prefix = LIST_STYLE.rjust(self.depth * INDENT_SIZE - INDENT_SIZE + LIST_STYLE.length, BLANK)
|
287
|
+
puts "#{prefix} #{self.identity}: [matched: #{matched}]"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
module Call
|
293
|
+
INPUT_LABEL = 'Input :'.freeze
|
294
|
+
OUTPUT_LABEL = 'Output:'.freeze
|
295
|
+
|
296
|
+
def call(input, **)
|
297
|
+
super.tap do |output|
|
298
|
+
input_prefix = INPUT_LABEL.rjust(self.depth * INDENT_SIZE + INPUT_LABEL.length, BLANK)
|
299
|
+
puts "#{input_prefix} #{input}"
|
300
|
+
output_prefix = OUTPUT_LABEL.rjust(self.depth * INDENT_SIZE + OUTPUT_LABEL.length, BLANK)
|
301
|
+
puts "#{output_prefix} #{output}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
...
|
308
|
+
|
309
|
+
module JSON
|
310
|
+
class Parser
|
311
|
+
include CallableTree::Node::Internal
|
312
|
+
prepend Logging::Match
|
313
|
+
|
314
|
+
...
|
315
|
+
end
|
316
|
+
|
317
|
+
class Scraper
|
318
|
+
include CallableTree::Node::External
|
319
|
+
prepend Logging::Match
|
320
|
+
prepend Logging::Call
|
321
|
+
|
322
|
+
...
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
module XML
|
327
|
+
class Parser
|
328
|
+
include CallableTree::Node::Internal
|
329
|
+
prepend Logging::Match
|
330
|
+
|
331
|
+
...
|
332
|
+
end
|
333
|
+
|
334
|
+
class Scraper
|
335
|
+
include CallableTree::Node::External
|
336
|
+
prepend Logging::Match
|
337
|
+
prepend Logging::Call
|
338
|
+
|
339
|
+
...
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
...
|
345
|
+
```
|
346
|
+
|
347
|
+
Run `examples/example4.rb`:
|
348
|
+
```sh
|
349
|
+
% ruby examples/example4.rb
|
350
|
+
* Node::JSON::Parser: [matched: true]
|
351
|
+
* Node::JSON::Scraper(animals): [matched: true]
|
352
|
+
Input : {"animals"=>[{"name"=>"Dog", "emoji"=>"🐶"}, {"name"=>"Cat", "emoji"=>"🐱"}]}
|
353
|
+
Output: {"Dog"=>"🐶", "Cat"=>"🐱"}
|
354
|
+
#<struct CallableTree::Node::External::Output value={"Dog"=>"🐶", "Cat"=>"🐱"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007f9737074060 @klass=Node::JSON::Scraper, @type=:animals>, Node::JSON::Parser, CallableTree::Node::Root]>
|
355
|
+
---
|
356
|
+
* Node::JSON::Parser: [matched: false]
|
357
|
+
* Node::XML::Parser: [matched: true]
|
358
|
+
* Node::XML::Scraper(animals): [matched: true]
|
359
|
+
Input : <root><animals><animal emoji='🐶' name='Dog'/><animal emoji='🐱' name='Cat'/></animals></root>
|
360
|
+
Output: {"Dog"=>"🐶", "Cat"=>"🐱"}
|
361
|
+
#<struct CallableTree::Node::External::Output value={"Dog"=>"🐶", "Cat"=>"🐱"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007f973710d0f8 @klass=Node::XML::Scraper, @type=:animals>, Node::XML::Parser, CallableTree::Node::Root]>
|
362
|
+
---
|
363
|
+
* Node::JSON::Parser: [matched: true]
|
364
|
+
* Node::JSON::Scraper(animals): [matched: false]
|
365
|
+
* Node::JSON::Scraper(fruits): [matched: true]
|
366
|
+
Input : {"fruits"=>[{"name"=>"Red Apple", "emoji"=>"🍎"}, {"name"=>"Green Apple", "emoji"=>"🍏"}]}
|
367
|
+
Output: {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
|
368
|
+
#<struct CallableTree::Node::External::Output value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007f97370ffed0 @klass=Node::JSON::Scraper, @type=:fruits>, Node::JSON::Parser, CallableTree::Node::Root]>
|
369
|
+
---
|
370
|
+
* Node::JSON::Parser: [matched: false]
|
371
|
+
* Node::XML::Parser: [matched: true]
|
372
|
+
* Node::XML::Scraper(animals): [matched: false]
|
373
|
+
* Node::XML::Scraper(fruits): [matched: true]
|
374
|
+
Input : <root><fruits><fruit emoji='🍎' name='Red Apple'/><fruit emoji='🍏' name='Green Apple'/></fruits></root>
|
375
|
+
Output: {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
|
376
|
+
#<struct CallableTree::Node::External::Output value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"}, options={:foo=>:bar}, routes=[#<Node::Identity:0x00007f97370cceb8 @klass=Node::XML::Scraper, @type=:fruits>, Node::XML::Parser, CallableTree::Node::Root]>
|
377
|
+
---
|
378
|
+
```
|
379
|
+
|
380
|
+
#### `CallableTree::Node::Hooks::Call` (experimental)
|
381
|
+
|
382
|
+
`examples/example5.rb`:
|
383
|
+
```ruby
|
384
|
+
module Node
|
385
|
+
class HooksSample
|
386
|
+
include CallableTree::Node::Internal
|
387
|
+
prepend CallableTree::Node::Hooks::Call
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
Node::HooksSample.new
|
392
|
+
.before_call do |input, **options|
|
393
|
+
puts "before_call input: #{input}";
|
394
|
+
input + 1
|
395
|
+
end
|
396
|
+
.append(
|
397
|
+
lambda do |input, **options|
|
398
|
+
puts "external input: #{input}"
|
399
|
+
input * 2
|
400
|
+
end
|
401
|
+
)
|
402
|
+
.around_call do |input, **options, &block|
|
403
|
+
puts "around_call input: #{input}"
|
404
|
+
output = block.call
|
405
|
+
puts "around_call output: #{output}"
|
406
|
+
output * input
|
407
|
+
end
|
408
|
+
.after_call do |output, **options|
|
409
|
+
puts "after_call output: #{output}"
|
410
|
+
output * 2
|
411
|
+
end
|
412
|
+
.tap do |tree|
|
413
|
+
options = { foo: :bar }
|
414
|
+
output = tree.call(1, **options)
|
415
|
+
puts "result: #{output}"
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
Run `examples/example5.rb`:
|
420
|
+
```sh
|
421
|
+
% ruby examples/example5.rb
|
422
|
+
before_call input: 1
|
423
|
+
external input: 2
|
424
|
+
around_call input: 2
|
425
|
+
around_call output: 4
|
426
|
+
after_call output: 8
|
427
|
+
result: 16
|
428
|
+
```
|
429
|
+
|
430
|
+
## Contributing
|
431
|
+
|
432
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jsmmr/callable_tree.
|
433
|
+
|
434
|
+
## License
|
435
|
+
|
436
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|