callable_tree 0.3.7 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +7 -0
  3. data/.github/workflows/build.yml +3 -5
  4. data/.github/workflows/codeql-analysis.yml +4 -4
  5. data/.rubocop.yml +6 -0
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +12 -0
  8. data/Gemfile.lock +3 -3
  9. data/README.md +460 -276
  10. data/callable_tree.gemspec +1 -0
  11. data/examples/builder/external-verbosify.rb +87 -0
  12. data/examples/builder/hooks.rb +67 -0
  13. data/examples/builder/identity.rb +91 -0
  14. data/examples/builder/internal-broadcastable.rb +11 -11
  15. data/examples/builder/internal-composable.rb +11 -11
  16. data/examples/builder/internal-seekable.rb +9 -15
  17. data/examples/builder/logging.rb +36 -39
  18. data/examples/{external-verbosify.rb → class/external-verbosify.rb} +3 -5
  19. data/examples/class/hooks.rb +70 -0
  20. data/examples/{identity.rb → class/identity.rb} +3 -5
  21. data/examples/{internal-broadcastable.rb → class/internal-broadcastable.rb} +0 -0
  22. data/examples/{internal-composable.rb → class/internal-composable.rb} +0 -0
  23. data/examples/{internal-seekable.rb → class/internal-seekable.rb} +3 -5
  24. data/examples/{logging.rb → class/logging.rb} +47 -47
  25. data/lib/callable_tree/node/builder.rb +13 -0
  26. data/lib/callable_tree/node/hooks/terminator.rb +99 -0
  27. data/lib/callable_tree/node/internal/strategy/broadcast.rb +19 -2
  28. data/lib/callable_tree/node/internal/strategy/compose.rb +15 -3
  29. data/lib/callable_tree/node/internal/strategy/seek.rb +11 -1
  30. data/lib/callable_tree/node/internal/strategy.rb +10 -2
  31. data/lib/callable_tree/node/internal.rb +22 -28
  32. data/lib/callable_tree/node/root.rb +1 -0
  33. data/lib/callable_tree/version.rb +1 -1
  34. data/lib/callable_tree.rb +1 -0
  35. metadata +17 -11
  36. data/examples/builder/hooks-caller.rb +0 -38
  37. data/examples/hooks-caller.rb +0 -39
data/README.md CHANGED
@@ -21,10 +21,10 @@ Or install it yourself as:
21
21
 
22
22
  ## Usage
23
23
 
24
- 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.
24
+ Builds a tree by linking `CallableTree` node instances. The `call` methods of the nodes where the `match?` method returns a truthy value are called in a chain from the root node to the leaf node.
25
25
 
26
26
  - `CallableTree::Node::Internal`
27
- - This `module` is used to define a node that can have child nodes. An instance of this node has several strategies. The strategy can be changed by calling the method of the instance.
27
+ - This `module` is used to define a node that can have child nodes. This node has several strategies (`seekable`, `broadcastable`, `composable`).
28
28
  - `CallableTree::Node::External`
29
29
  - This `module` is used to define a leaf node that cannot have child nodes.
30
30
  - `CallableTree::Node::Root`
@@ -32,11 +32,15 @@ Builds a tree by linking instances of the nodes. The `call` method of the node w
32
32
 
33
33
  ### Basic
34
34
 
35
- #### `CallableTree::Node::Internal#seekable` (default)
35
+ There are two ways to define the nodes: class style and builder style.
36
+
37
+ #### `CallableTree::Node::Internal#seekable` (default strategy)
36
38
 
37
39
  This strategy does not call the next sibling node if the `call` method of the current node returns a value other than `nil`. This behavior is changeable by overriding the `terminate?` method.
38
40
 
39
- `examples/internal-seekable.rb`:
41
+ ##### Class style
42
+
43
+ `examples/class/internal-seekable.rb`:
40
44
  ```ruby
41
45
  module Node
42
46
  module JSON
@@ -77,8 +81,7 @@ module Node
77
81
 
78
82
  def call(input, **_options)
79
83
  input[@type.to_s]
80
- .map { |element| [element['name'], element['emoji']] }
81
- .to_h
84
+ .to_h { |element| [element['name'], element['emoji']] }
82
85
  end
83
86
  end
84
87
  end
@@ -122,8 +125,7 @@ module Node
122
125
  input
123
126
  .get_elements("//#{@type}")
124
127
  .first
125
- .map { |element| [element['name'], element['emoji']] }
126
- .to_h
128
+ .to_h { |element| [element['name'], element['emoji']] }
127
129
  end
128
130
  end
129
131
  end
@@ -148,9 +150,109 @@ Dir.glob("#{__dir__}/docs/*") do |file|
148
150
  end
149
151
  ```
150
152
 
151
- Run `examples/internal-seekable.rb`:
153
+ Run `examples/class/internal-seekable.rb`:
154
+ ```sh
155
+ % ruby examples/class/internal-seekable.rb
156
+ {"Dog"=>"🐶", "Cat"=>"🐱"}
157
+ ---
158
+ {"Dog"=>"🐶", "Cat"=>"🐱"}
159
+ ---
160
+ {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
161
+ ---
162
+ {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
163
+ ---
164
+ ```
165
+
166
+ ##### Builder style
167
+
168
+ `examples/builder/internal-seekable.rb`:
169
+ ```ruby
170
+ JSONParser =
171
+ CallableTree::Node::Internal::Builder
172
+ .new
173
+ .matcher do |input, **_options|
174
+ File.extname(input) == '.json'
175
+ end
176
+ .caller do |input, **options, &original|
177
+ File.open(input) do |file|
178
+ json = ::JSON.load(file)
179
+ # The following block call is equivalent to calling `super` in the class style.
180
+ original.call(json, **options)
181
+ end
182
+ end
183
+ .terminator { true }
184
+ .build
185
+
186
+ XMLParser =
187
+ CallableTree::Node::Internal::Builder
188
+ .new
189
+ .matcher do |input, **_options|
190
+ File.extname(input) == '.xml'
191
+ end
192
+ .caller do |input, **options, &original|
193
+ File.open(input) do |file|
194
+ # The following block call is equivalent to calling `super` in the class style.
195
+ original.call(REXML::Document.new(file), **options)
196
+ end
197
+ end
198
+ .terminator { true }
199
+ .build
200
+
201
+ def build_json_scraper(type)
202
+ CallableTree::Node::External::Builder
203
+ .new
204
+ .matcher do |input, **_options|
205
+ !!input[type.to_s]
206
+ end
207
+ .caller do |input, **_options|
208
+ input[type.to_s]
209
+ .to_h { |element| [element['name'], element['emoji']] }
210
+ end
211
+ .build
212
+ end
213
+
214
+ AnimalsJSONScraper = build_json_scraper(:animals)
215
+ FruitsJSONScraper = build_json_scraper(:fruits)
216
+
217
+ def build_xml_scraper(type)
218
+ CallableTree::Node::External::Builder
219
+ .new
220
+ .matcher do |input, **_options|
221
+ !input.get_elements("//#{type}").empty?
222
+ end
223
+ .caller do |input, **_options|
224
+ input
225
+ .get_elements("//#{type}")
226
+ .first
227
+ .to_h { |element| [element['name'], element['emoji']] }
228
+ end
229
+ .build
230
+ end
231
+
232
+ AnimalsXMLScraper = build_xml_scraper(:animals)
233
+ FruitsXMLScraper = build_xml_scraper(:fruits)
234
+
235
+ tree = CallableTree::Node::Root.new.seekable.append(
236
+ JSONParser.new.seekable.append(
237
+ AnimalsJSONScraper.new,
238
+ FruitsJSONScraper.new
239
+ ),
240
+ XMLParser.new.seekable.append(
241
+ AnimalsXMLScraper.new,
242
+ FruitsXMLScraper.new
243
+ )
244
+ )
245
+
246
+ Dir.glob("#{__dir__}/../docs/*") do |file|
247
+ options = { foo: :bar }
248
+ pp tree.call(file, **options)
249
+ puts '---'
250
+ end
251
+ ```
252
+
253
+ Run `examples/builder/internal-seekable.rb`:
152
254
  ```sh
153
- % ruby examples/internal-seekable.rb
255
+ % ruby examples/builder/internal-seekable.rb
154
256
  {"Dog"=>"🐶", "Cat"=>"🐱"}
155
257
  ---
156
258
  {"Dog"=>"🐶", "Cat"=>"🐱"}
@@ -163,9 +265,11 @@ Run `examples/internal-seekable.rb`:
163
265
 
164
266
  #### `CallableTree::Node::Internal#broadcastable`
165
267
 
166
- This strategy calls all child nodes of the internal node and ignores their `terminate?` methods, and then outputs their results as array.
268
+ This strategy broadcasts to output a result of the child nodes as array. It also ignores their `terminate?` methods by default.
167
269
 
168
- `examples/internal-broadcastable.rb`:
270
+ ##### Class style
271
+
272
+ `examples/class/internal-broadcastable.rb`:
169
273
  ```ruby
170
274
  module Node
171
275
  class LessThan
@@ -199,9 +303,99 @@ end
199
303
 
200
304
  ```
201
305
 
202
- Run `examples/internal-broadcastable.rb`:
306
+ Run `examples/class/internal-broadcastable.rb`:
307
+ ```sh
308
+ % ruby examples/class/internal-broadcastable.rb
309
+ 0 -> [[0, 1], [0, -1]]
310
+ 1 -> [[2, 2], [3, 0]]
311
+ 2 -> [[4, 3], [6, 1]]
312
+ 3 -> [[6, 4], [9, 2]]
313
+ 4 -> [[8, 5], [12, 3]]
314
+ 5 -> [nil, [15, 4]]
315
+ 6 -> [nil, [18, 5]]
316
+ 7 -> [nil, [21, 6]]
317
+ 8 -> [nil, [24, 7]]
318
+ 9 -> [nil, [27, 8]]
319
+ 10 -> [nil, nil]
320
+ ```
321
+
322
+ ##### Builder style
323
+
324
+ `examples/builder/internal-broadcastable.rb`:
325
+ ```ruby
326
+ def less_than(num)
327
+ # The following block call is equivalent to calling `super` in the class style.
328
+ proc { |input, &original| original.call(input) && input < num }
329
+ end
330
+
331
+ LessThan5 =
332
+ CallableTree::Node::Internal::Builder
333
+ .new
334
+ .matcher(&method(:less_than).call(5))
335
+ .build
336
+
337
+ LessThan10 =
338
+ CallableTree::Node::Internal::Builder
339
+ .new
340
+ .matcher(&method(:less_than).call(10))
341
+ .build
342
+
343
+ def add(num)
344
+ proc { |input| input + num }
345
+ end
346
+
347
+ Add1 =
348
+ CallableTree::Node::External::Builder
349
+ .new
350
+ .caller(&method(:add).call(1))
351
+ .build
352
+
353
+ def subtract(num)
354
+ proc { |input| input - num }
355
+ end
356
+
357
+ Subtract1 =
358
+ CallableTree::Node::External::Builder
359
+ .new
360
+ .caller(&method(:subtract).call(1))
361
+ .build
362
+
363
+ def multiply(num)
364
+ proc { |input| input * num }
365
+ end
366
+
367
+ Multiply2 =
368
+ CallableTree::Node::External::Builder
369
+ .new
370
+ .caller(&method(:multiply).call(2))
371
+ .build
372
+
373
+ Multiply3 =
374
+ CallableTree::Node::External::Builder
375
+ .new
376
+ .caller(&method(:multiply).call(3))
377
+ .build
378
+
379
+ tree = CallableTree::Node::Root.new.broadcastable.append(
380
+ LessThan5.new.broadcastable.append(
381
+ Multiply2.new,
382
+ Add1.new
383
+ ),
384
+ LessThan10.new.broadcastable.append(
385
+ Multiply3.new,
386
+ Subtract1.new
387
+ )
388
+ )
389
+
390
+ (0..10).each do |input|
391
+ output = tree.call(input)
392
+ puts "#{input} -> #{output}"
393
+ end
394
+ ```
395
+
396
+ Run `examples/builder/internal-broadcastable.rb`:
203
397
  ```sh
204
- % ruby examples/internal-broadcastable.rb
398
+ % ruby examples/builder/internal-broadcastable.rb
205
399
  0 -> [[0, 1], [0, -1]]
206
400
  1 -> [[2, 2], [3, 0]]
207
401
  2 -> [[4, 3], [6, 1]]
@@ -217,9 +411,12 @@ Run `examples/internal-broadcastable.rb`:
217
411
 
218
412
  #### `CallableTree::Node::Internal#composable`
219
413
 
220
- This strategy calls all child nodes of the internal node in order to input the output of the previous node to the next node and ignores their `terminate?` methods, and then outputs a single result.
414
+ This strategy composes the child nodes to input the output of the previous node into the next node and to output a result.
415
+ It also ignores their `terminate?` methods by default.
416
+
417
+ ##### Class style
221
418
 
222
- `examples/internal-composable.rb`:
419
+ `examples/class/internal-composable.rb`:
223
420
  ```ruby
224
421
  module Node
225
422
  class LessThan
@@ -253,9 +450,99 @@ end
253
450
 
254
451
  ```
255
452
 
256
- Run `examples/internal-composable.rb`:
453
+ Run `examples/class/internal-composable.rb`:
454
+ ```sh
455
+ % ruby examples/class/internal-composable.rb
456
+ 0 -> 2
457
+ 1 -> 8
458
+ 2 -> 14
459
+ 3 -> 20
460
+ 4 -> 26
461
+ 5 -> 14
462
+ 6 -> 17
463
+ 7 -> 20
464
+ 8 -> 23
465
+ 9 -> 26
466
+ 10 -> 10
467
+ ```
468
+
469
+ ##### Builder style
470
+
471
+ `examples/builder/internal-composable.rb`:
472
+ ```ruby
473
+ def less_than(num)
474
+ # The following block call is equivalent to calling `super` in the class style.
475
+ proc { |input, &original| original.call(input) && input < num }
476
+ end
477
+
478
+ LessThan5 =
479
+ CallableTree::Node::Internal::Builder
480
+ .new
481
+ .matcher(&method(:less_than).call(5))
482
+ .build
483
+
484
+ LessThan10 =
485
+ CallableTree::Node::Internal::Builder
486
+ .new
487
+ .matcher(&method(:less_than).call(10))
488
+ .build
489
+
490
+ def add(num)
491
+ proc { |input| input + num }
492
+ end
493
+
494
+ Add1 =
495
+ CallableTree::Node::External::Builder
496
+ .new
497
+ .caller(&method(:add).call(1))
498
+ .build
499
+
500
+ def subtract(num)
501
+ proc { |input| input - num }
502
+ end
503
+
504
+ Subtract1 =
505
+ CallableTree::Node::External::Builder
506
+ .new
507
+ .caller(&method(:subtract).call(1))
508
+ .build
509
+
510
+ def multiply(num)
511
+ proc { |input| input * num }
512
+ end
513
+
514
+ Multiply2 =
515
+ CallableTree::Node::External::Builder
516
+ .new
517
+ .caller(&method(:multiply).call(2))
518
+ .build
519
+
520
+ Multiply3 =
521
+ CallableTree::Node::External::Builder
522
+ .new
523
+ .caller(&method(:multiply).call(3))
524
+ .build
525
+
526
+ tree = CallableTree::Node::Root.new.composable.append(
527
+ LessThan5.new.composable.append(
528
+ Multiply2.new,
529
+ Add1.new
530
+ ),
531
+ LessThan10.new.composable.append(
532
+ Multiply3.new,
533
+ Subtract1.new
534
+ )
535
+ )
536
+
537
+ (0..10).each do |input|
538
+ output = tree.call(input)
539
+ puts "#{input} -> #{output}"
540
+ end
541
+ ```
542
+
543
+ Run `examples/builder/internal-composable.rb`:
257
544
  ```sh
258
- % ruby examples/internal-composable.rb
545
+ % ruby examples/builder/internal-composable.rb
259
546
  0 -> 2
260
547
  1 -> 8
261
548
  2 -> 14
@@ -275,350 +562,247 @@ Run `examples/internal-composable.rb`:
275
562
 
276
563
  If you want verbose output results, call this method.
277
564
 
278
- `examples/external-verbosify.rb`:
565
+ `examples/builder/external-verbosify.rb`:
279
566
  ```ruby
280
567
  ...
281
568
 
282
- tree = CallableTree::Node::Root.new.append(
283
- Node::JSON::Parser.new.append(
284
- Node::JSON::Scraper.new(type: :animals).verbosify,
285
- Node::JSON::Scraper.new(type: :fruits).verbosify
569
+ tree = CallableTree::Node::Root.new.seekable.append(
570
+ JSONParser.new.seekable.append(
571
+ AnimalsJSONScraper.new.verbosify,
572
+ FruitsJSONScraper.new.verbosify
286
573
  ),
287
- Node::XML::Parser.new.append(
288
- Node::XML::Scraper.new(type: :animals).verbosify,
289
- Node::XML::Scraper.new(type: :fruits).verbosify
574
+ XMLParser.new.seekable.append(
575
+ AnimalsXMLScraper.new.verbosify,
576
+ FruitsXMLScraper.new.verbosify
290
577
  )
291
578
  )
292
579
 
293
580
  ...
294
581
  ```
295
582
 
296
- Run `examples/external-verbosify.rb`:
583
+ Run `examples/builder/external-verbosify.rb`:
297
584
  ```sh
298
- % ruby examples/external-verbosify.rb
585
+ % ruby examples/class/external-verbosify.rb
299
586
  #<struct CallableTree::Node::External::Output
300
- value={"Dog"=>"🐶", "Cat"=>"🐱"},
301
- options={:foo=>:bar},
302
- routes=[Node::JSON::Scraper, Node::JSON::Parser, CallableTree::Node::Root]>
587
+ value={"Dog"=>"🐶", "Cat"=>"🐱"},
588
+ options={:foo=>:bar},
589
+ routes=[AnimalsJSONScraper, JSONParser, CallableTree::Node::Root]>
303
590
  ---
304
591
  #<struct CallableTree::Node::External::Output
305
- value={"Dog"=>"🐶", "Cat"=>"🐱"},
306
- options={:foo=>:bar},
307
- routes=[Node::XML::Scraper, Node::XML::Parser, CallableTree::Node::Root]>
592
+ value={"Dog"=>"🐶", "Cat"=>"🐱"},
593
+ options={:foo=>:bar},
594
+ routes=[AnimalsXMLScraper, XMLParser, CallableTree::Node::Root]>
308
595
  ---
309
596
  #<struct CallableTree::Node::External::Output
310
- value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
311
- options={:foo=>:bar},
312
- routes=[Node::JSON::Scraper, Node::JSON::Parser, CallableTree::Node::Root]>
597
+ value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
598
+ options={:foo=>:bar},
599
+ routes=[FruitsJSONScraper, JSONParser, CallableTree::Node::Root]>
313
600
  ---
314
601
  #<struct CallableTree::Node::External::Output
315
- value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
316
- options={:foo=>:bar},
317
- routes=[Node::XML::Scraper, Node::XML::Parser, CallableTree::Node::Root]>
602
+ value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
603
+ options={:foo=>:bar},
604
+ routes=[FruitsXMLScraper, XMLParser, CallableTree::Node::Root]>
318
605
  ---
319
606
  ```
320
607
 
321
- At first glance, this looks good, but the `routes` are ambiguous when there are multiple nodes of the same class.
322
- You can work around it by overriding the `identity` method of the node.
323
-
324
- #### `CallableTree::Node#identity`
608
+ #### Logging
325
609
 
326
- If you want to customize the node identity, override this method.
610
+ This is an example of logging.
327
611
 
328
- `examples/identity.rb`:
612
+ `examples/builder/logging.rb`:
329
613
  ```ruby
330
- module Node
331
- class Identity
332
- attr_reader :klass, :type
333
-
334
- def initialize(klass:, type:)
335
- @klass = klass
336
- @type = type
337
- end
614
+ JSONParser =
615
+ CallableTree::Node::Internal::Builder
616
+ .new
617
+ ...
618
+ .hookable
619
+ .build
338
620
 
339
- def to_s
340
- "#{klass}(#{type})"
341
- end
342
- end
621
+ XMLParser =
622
+ CallableTree::Node::Internal::Builder
623
+ .new
624
+ ...
625
+ .hookable
626
+ .build
343
627
 
344
- module JSON
628
+ def build_json_scraper(type)
629
+ CallableTree::Node::External::Builder
630
+ .new
345
631
  ...
632
+ .hookable
633
+ .build
634
+ end
346
635
 
347
- class Scraper
348
- include CallableTree::Node::External
636
+ ...
349
637
 
350
- def initialize(type:)
351
- @type = type
352
- end
638
+ def build_xml_scraper(type)
639
+ CallableTree::Node::External::Builder
640
+ .new
641
+ ...
642
+ .hookable
643
+ .build
644
+ end
353
645
 
354
- def identity
355
- Identity.new(klass: super, type: @type)
356
- end
646
+ ...
357
647
 
358
- ...
648
+ module Logging
649
+ INDENT_SIZE = 2
650
+ BLANK = ' '
651
+ LIST_STYLE = '*'
652
+ INPUT_LABEL = 'Input :'
653
+ OUTPUT_LABEL = 'Output:'
654
+
655
+ def self.loggable(node)
656
+ node.after_matcher! do |matched, _node_:, **|
657
+ prefix = LIST_STYLE.rjust((_node_.depth * INDENT_SIZE) - INDENT_SIZE + LIST_STYLE.length, BLANK)
658
+ puts "#{prefix} #{_node_.identity}: [matched: #{matched}]"
659
+ matched
359
660
  end
360
- end
361
-
362
- module XML
363
- ...
364
-
365
- class Scraper
366
- include CallableTree::Node::External
367
-
368
- def initialize(type:)
369
- @type = type
370
- end
371
-
372
- def identity
373
- Identity.new(klass: super, type: @type)
374
- end
375
661
 
376
- ...
662
+ if node.external?
663
+ node
664
+ .before_caller! do |input, *, _node_:, **|
665
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
666
+ puts "#{input_prefix} #{input}"
667
+ input
668
+ end
669
+ .after_caller! do |output, _node_:, **|
670
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
671
+ puts "#{output_prefix} #{output}"
672
+ output
673
+ end
377
674
  end
378
675
  end
379
676
  end
380
677
 
678
+ loggable = Logging.method(:loggable)
679
+
680
+ tree = CallableTree::Node::Root.new.seekable.append(
681
+ JSONParser.new.tap(&loggable).seekable.append(
682
+ AnimalsJSONScraper.new.tap(&loggable).verbosify,
683
+ FruitsJSONScraper.new.tap(&loggable).verbosify
684
+ ),
685
+ XMLParser.new.tap(&loggable).seekable.append(
686
+ AnimalsXMLScraper.new.tap(&loggable).verbosify,
687
+ FruitsXMLScraper.new.tap(&loggable).verbosify
688
+ )
689
+ )
690
+
381
691
  ...
382
692
  ```
383
693
 
384
- Run `examples/identity.rb`:
694
+ Also, see `examples/builder/hooks.rb` for detail about `CallableTree::Node::Hooks::*`.
695
+
696
+ Run `examples/builder/logging.rb`:
385
697
  ```sh
386
- % ruby examples/identity.rb
698
+ % ruby examples/builder/logging.rb
699
+ * JSONParser: [matched: true]
700
+ * AnimalsJSONScraper: [matched: true]
701
+ Input : {"animals"=>[{"name"=>"Dog", "emoji"=>"🐶"}, {"name"=>"Cat", "emoji"=>"🐱"}]}
702
+ Output: {"Dog"=>"🐶", "Cat"=>"🐱"}
387
703
  #<struct CallableTree::Node::External::Output
388
704
  value={"Dog"=>"🐶", "Cat"=>"🐱"},
389
705
  options={:foo=>:bar},
390
- routes=
391
- [#<Node::Identity:0x00007fb4378a9718
392
- @klass=Node::JSON::Scraper,
393
- @type=:animals>,
394
- Node::JSON::Parser,
395
- CallableTree::Node::Root]>
706
+ routes=[AnimalsJSONScraper, JSONParser, CallableTree::Node::Root]>
396
707
  ---
708
+ * JSONParser: [matched: false]
709
+ * XMLParser: [matched: true]
710
+ * AnimalsXMLScraper: [matched: true]
711
+ Input : <root><animals><animal emoji='🐶' name='Dog'/><animal emoji='🐱' name='Cat'/></animals></root>
712
+ Output: {"Dog"=>"🐶", "Cat"=>"🐱"}
397
713
  #<struct CallableTree::Node::External::Output
398
714
  value={"Dog"=>"🐶", "Cat"=>"🐱"},
399
715
  options={:foo=>:bar},
400
- routes=
401
- [#<Node::Identity:0x00007fb41002b6d0
402
- @klass=Node::XML::Scraper,
403
- @type=:animals>,
404
- Node::XML::Parser,
405
- CallableTree::Node::Root]>
716
+ routes=[AnimalsXMLScraper, XMLParser, CallableTree::Node::Root]>
406
717
  ---
718
+ * JSONParser: [matched: true]
719
+ * AnimalsJSONScraper: [matched: false]
720
+ * FruitsJSONScraper: [matched: true]
721
+ Input : {"fruits"=>[{"name"=>"Red Apple", "emoji"=>"🍎"}, {"name"=>"Green Apple", "emoji"=>"🍏"}]}
722
+ Output: {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
407
723
  #<struct CallableTree::Node::External::Output
408
724
  value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
409
725
  options={:foo=>:bar},
410
- routes=
411
- [#<Node::Identity:0x00007fb41001b3e8
412
- @klass=Node::JSON::Scraper,
413
- @type=:fruits>,
414
- Node::JSON::Parser,
415
- CallableTree::Node::Root]>
726
+ routes=[FruitsJSONScraper, JSONParser, CallableTree::Node::Root]>
416
727
  ---
728
+ * JSONParser: [matched: false]
729
+ * XMLParser: [matched: true]
730
+ * AnimalsXMLScraper: [matched: false]
731
+ * FruitsXMLScraper: [matched: true]
732
+ Input : <root><fruits><fruit emoji='🍎' name='Red Apple'/><fruit emoji='🍏' name='Green Apple'/></fruits></root>
733
+ Output: {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
417
734
  #<struct CallableTree::Node::External::Output
418
735
  value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
419
736
  options={:foo=>:bar},
420
- routes=
421
- [#<Node::Identity:0x00007fb410049d38
422
- @klass=Node::XML::Scraper,
423
- @type=:fruits>,
424
- Node::XML::Parser,
425
- CallableTree::Node::Root]>
426
- ---
737
+ routes=[FruitsXMLScraper, XMLParser, CallableTree::Node::Root]>
427
738
  ```
428
739
 
429
- #### Logging
740
+ #### `CallableTree::Node#identity`
430
741
 
431
- This is an example of logging.
742
+ If you want to customize the node identity, specify identifier.
432
743
 
433
- `examples/logging.rb`:
744
+ `examples/builder/identity.rb`:
434
745
  ```ruby
435
- module Node
436
- module Logging
437
- INDENT_SIZE = 2
438
- BLANK = ' '
439
-
440
- module Match
441
- LIST_STYLE = '*'
442
-
443
- def match?(_input, **_options)
444
- super.tap do |matched|
445
- prefix = LIST_STYLE.rjust(depth * INDENT_SIZE - INDENT_SIZE + LIST_STYLE.length, BLANK)
446
- puts "#{prefix} #{identity}: [matched: #{matched}]"
447
- end
448
- end
449
- end
450
-
451
- module Call
452
- INPUT_LABEL = 'Input :'
453
- OUTPUT_LABEL = 'Output:'
454
-
455
- def call(input, **_options)
456
- super.tap do |output|
457
- input_prefix = INPUT_LABEL.rjust(depth * INDENT_SIZE + INPUT_LABEL.length, BLANK)
458
- puts "#{input_prefix} #{input}"
459
- output_prefix = OUTPUT_LABEL.rjust(depth * INDENT_SIZE + OUTPUT_LABEL.length, BLANK)
460
- puts "#{output_prefix} #{output}"
461
- end
462
- end
463
- end
464
- end
465
-
746
+ JSONParser =
747
+ CallableTree::Node::Internal::Builder
748
+ .new
466
749
  ...
750
+ .identifier { |_node_:| _node_.object_id }
751
+ .build
467
752
 
468
- module JSON
469
- class Parser
470
- include CallableTree::Node::Internal
471
- prepend Logging::Match
472
-
473
- ...
474
- end
475
-
476
- class Scraper
477
- include CallableTree::Node::External
478
- prepend Logging::Match
479
- prepend Logging::Call
480
-
481
- ...
482
- end
483
- end
484
-
485
- module XML
486
- class Parser
487
- include CallableTree::Node::Internal
488
- prepend Logging::Match
753
+ XMLParser =
754
+ CallableTree::Node::Internal::Builder
755
+ .new
756
+ ...
757
+ .identifier { |_node_:| _node_.object_id }
758
+ .build
489
759
 
490
- ...
491
- end
760
+ def build_json_scraper(type)
761
+ CallableTree::Node::External::Builder
762
+ .new
763
+ ...
764
+ .identifier { |_node_:| _node_.object_id }
765
+ .build
766
+ end
492
767
 
493
- class Scraper
494
- include CallableTree::Node::External
495
- prepend Logging::Match
496
- prepend Logging::Call
768
+ ...
497
769
 
498
- ...
499
- end
500
- end
770
+ def build_xml_scraper(type)
771
+ CallableTree::Node::External::Builder
772
+ .new
773
+ ...
774
+ .identifier { |_node_:| _node_.object_id }
775
+ .build
501
776
  end
502
777
 
503
778
  ...
504
779
  ```
505
780
 
506
- Run `examples/logging.rb`:
781
+ Run `examples/class/identity.rb`:
507
782
  ```sh
508
- % ruby examples/logging.rb
509
- * Node::JSON::Parser: [matched: true]
510
- * Node::JSON::Scraper(animals): [matched: true]
511
- Input : {"animals"=>[{"name"=>"Dog", "emoji"=>"🐶"}, {"name"=>"Cat", "emoji"=>"🐱"}]}
512
- Output: {"Dog"=>"🐶", "Cat"=>"🐱"}
783
+ % ruby examples/builder/identity.rb
513
784
  #<struct CallableTree::Node::External::Output
514
- value={"Dog"=>"🐶", "Cat"=>"🐱"},
515
- options={:foo=>:bar},
516
- routes=
517
- [#<Node::Identity:0x00007ffd840347b8
518
- @klass=Node::JSON::Scraper,
519
- @type=:animals>,
520
- Node::JSON::Parser,
521
- CallableTree::Node::Root]>
785
+ value={"Dog"=>"🐶", "Cat"=>"🐱"},
786
+ options={:foo=>:bar},
787
+ routes=[60, 80, CallableTree::Node::Root]>
522
788
  ---
523
- * Node::JSON::Parser: [matched: false]
524
- * Node::XML::Parser: [matched: true]
525
- * Node::XML::Scraper(animals): [matched: true]
526
- Input : <root><animals><animal emoji='🐶' name='Dog'/><animal emoji='🐱' name='Cat'/></animals></root>
527
- Output: {"Dog"=>"🐶", "Cat"=>"🐱"}
528
789
  #<struct CallableTree::Node::External::Output
529
- value={"Dog"=>"🐶", "Cat"=>"🐱"},
530
- options={:foo=>:bar},
531
- routes=
532
- [#<Node::Identity:0x00007ffd7403f1f0
533
- @klass=Node::XML::Scraper,
534
- @type=:animals>,
535
- Node::XML::Parser,
536
- CallableTree::Node::Root]>
790
+ value={"Dog"=>"🐶", "Cat"=>"🐱"},
791
+ options={:foo=>:bar},
792
+ routes=[220, 240, CallableTree::Node::Root]>
537
793
  ---
538
- * Node::JSON::Parser: [matched: true]
539
- * Node::JSON::Scraper(animals): [matched: false]
540
- * Node::JSON::Scraper(fruits): [matched: true]
541
- Input : {"fruits"=>[{"name"=>"Red Apple", "emoji"=>"🍎"}, {"name"=>"Green Apple", "emoji"=>"🍏"}]}
542
- Output: {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
543
794
  #<struct CallableTree::Node::External::Output
544
795
  value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
545
796
  options={:foo=>:bar},
546
- routes=
547
- [#<Node::Identity:0x00007ffd8512bdf0
548
- @klass=Node::JSON::Scraper,
549
- @type=:fruits>,
550
- Node::JSON::Parser,
551
- CallableTree::Node::Root]>
797
+ routes=[260, 80, CallableTree::Node::Root]>
552
798
  ---
553
- * Node::JSON::Parser: [matched: false]
554
- * Node::XML::Parser: [matched: true]
555
- * Node::XML::Scraper(animals): [matched: false]
556
- * Node::XML::Scraper(fruits): [matched: true]
557
- Input : <root><fruits><fruit emoji='🍎' name='Red Apple'/><fruit emoji='🍏' name='Green Apple'/></fruits></root>
558
- Output: {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
559
799
  #<struct CallableTree::Node::External::Output
560
800
  value={"Red Apple"=>"🍎", "Green Apple"=>"🍏"},
561
801
  options={:foo=>:bar},
562
- routes=
563
- [#<Node::Identity:0x00007ffd8407a740
564
- @klass=Node::XML::Scraper,
565
- @type=:fruits>,
566
- Node::XML::Parser,
567
- CallableTree::Node::Root]>
802
+ routes=[400, 240, CallableTree::Node::Root]>
568
803
  ---
569
804
  ```
570
805
 
571
- #### `CallableTree::Node::Hooks::Caller` (experimental)
572
-
573
- `examples/hooks-call.rb`:
574
- ```ruby
575
- module Node
576
- class HooksSample
577
- include CallableTree::Node::Internal
578
- prepend CallableTree::Node::Hooks::Caller
579
- end
580
- end
581
-
582
- Node::HooksSample.new
583
- .before_caller do |input, **_options|
584
- puts "before_caller input: #{input}";
585
- input + 1
586
- end
587
- .append(
588
- # anonymous external node
589
- lambda do |input, **_options|
590
- puts "external input: #{input}"
591
- input * 2
592
- end
593
- )
594
- .around_caller do |input, **_options, &block|
595
- puts "around_caller input: #{input}"
596
- output = block.call
597
- puts "around_caller output: #{output}"
598
- output * input
599
- end
600
- .after_caller do |output, **_options|
601
- puts "after_caller output: #{output}"
602
- output * 2
603
- end
604
- .tap do |tree|
605
- options = { foo: :bar }
606
- output = tree.call(1, **options)
607
- puts "result: #{output}"
608
- end
609
- ```
610
-
611
- Run `examples/hooks-caller.rb`:
612
- ```sh
613
- % ruby examples/hooks-caller.rb
614
- before_caller input: 1
615
- external input: 2
616
- around_caller input: 2
617
- around_caller output: 4
618
- after_caller output: 8
619
- result: 16
620
- ```
621
-
622
806
  ## Contributing
623
807
 
624
808
  Bug reports and pull requests are welcome on GitHub at https://github.com/jsmmr/ruby_callable_tree.