callable_tree 0.3.11 → 0.3.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa3020e36165379b1ad6b642240470a33503150fd3bc945206f0685b2dbb80f4
4
- data.tar.gz: 0b5314f8b192d2908640df885db697bf7a283878f08568e0ce713aa2bd6fd38b
3
+ metadata.gz: 2eaeaea579c042e73c734e49279adb3321ab6008e4f6165ff4a701228217af00
4
+ data.tar.gz: e0c848254ac6802960723abb285423c21a477c228396cc384cd84a22fa31d565
5
5
  SHA512:
6
- metadata.gz: 2476e82ba6ad533bf752d75aec400bc06e9ce7106ca53a736e1b987641cf0f5d3a375126df90a5963feaf3f887ab589d2c87d61790ff08b583469e1fac13b312
7
- data.tar.gz: 7eb64d2ecc3ae2333e60346173675c341b89ae14cc2d28e2f32f6b48d19d08cd580c2392d976ea6c550a2e66fee2b909c2b286e09a56e7c52b473adbbcadb82a
6
+ metadata.gz: a144f0e3bcd69957274c4eb80008a812aa0f980163339f9aa0f501cc4ee0b63c1c1097df6dcfb2633aa449be79e620214b81f962b1c0a3b9cc81acdbc037beeb
7
+ data.tar.gz: 1c3c8fb5f5e4043864506b91ee7dd8b87e616ddecefdf593d9a302de92d056f8c505a483e6b6173a6ce01fe016366ccd56d58aa295f46902a7cf70c3bce039f3
data/.rubocop_todo.yml CHANGED
@@ -27,6 +27,8 @@ Lint/UnderscorePrefixedVariableName:
27
27
  - 'examples/builder/identity.rb'
28
28
  - 'examples/builder/logging.rb'
29
29
  - 'examples/class/logging.rb'
30
+ - 'examples/factory/identity.rb'
31
+ - 'examples/factory/logging.rb'
30
32
 
31
33
  # Offense count: 8
32
34
  # This cop supports safe autocorrection (--autocorrect).
@@ -74,6 +76,10 @@ Naming/FileName:
74
76
  - 'examples/class/internal-broadcastable.rb'
75
77
  - 'examples/class/internal-composable.rb'
76
78
  - 'examples/class/internal-seekable.rb'
79
+ - 'examples/factory/external-verbosify.rb'
80
+ - 'examples/factory/internal-broadcastable.rb'
81
+ - 'examples/factory/internal-composable.rb'
82
+ - 'examples/factory/internal-seekable.rb'
77
83
 
78
84
  # Offense count: 1
79
85
  # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
@@ -103,6 +109,8 @@ Style/MultilineBlockChain:
103
109
  - 'examples/builder/logging.rb'
104
110
  - 'examples/class/hooks.rb'
105
111
  - 'examples/class/logging.rb'
112
+ - 'examples/factory/hooks.rb'
113
+ - 'examples/factory/logging.rb'
106
114
  - 'lib/callable_tree/node/internal/strategy/seek.rb'
107
115
 
108
116
  # Offense count: 1
data/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Agent Guide for callable_tree
2
2
 
3
3
  ## Project Overview
4
- `callable_tree` is a Ruby gem that builds a tree of callable nodes. It allows for complex logic flow (like nested `if`/`case`) to be represented as a tree of objects. Nodes are traversed based on matching conditions (`match?`), and executed (`call`).
4
+ `callable_tree` is a Ruby gem for building tree-structured executable workflows. It provides a framework for organizing complex logic into a tree of callable nodes, offering a structured, modular alternative to complex conditional logic. Nodes are matched against input (`match?`) and executed (`call`) in a chain from root to leaf.
5
5
 
6
6
  ## Core Concepts
7
7
  - **Nodes**:
@@ -15,11 +15,23 @@
15
15
  - `match?(input)`: Determines if a node should process the input.
16
16
  - `call(input)`: Executes the node logic.
17
17
  - `terminate?`: Controls when to stop traversal (mostly for `seekable`).
18
+ - **Hooks** (`hookable`):
19
+ - Enable instrumentation (logging, debugging) by adding callbacks.
20
+ - `before_matcher!`, `after_matcher!`: Hook into matching phase.
21
+ - `before_caller!`, `after_caller!`: Hook into call phase.
22
+ - `before_terminator!`, `after_terminator!`: Hook into termination phase.
23
+ - **Verbosify** (`verbosify`):
24
+ - Wraps External node output in `CallableTree::Node::External::Output` struct.
25
+ - Provides `value`, `options`, and `routes` (call path) for debugging.
18
26
 
19
27
  ## Directory Structure
20
28
  - `lib/`: Source code.
21
29
  - `spec/`: RSpec tests.
22
- - `examples/`: Usage examples (Class-style and Builder-style).
30
+ - `examples/`: Usage examples.
31
+ - `examples/class/`: Class-style node definitions (using `include CallableTree::Node::*`).
32
+ - `examples/builder/`: Builder-style definitions (using `Builder.new.matcher { }.caller { }.build`).
33
+ - `examples/factory/`: Factory-style definitions (using `External.create(caller: ...)` or `External::Pod.new`).
34
+ - `examples/docs/`: Sample data files (JSON, XML) used by examples.
23
35
 
24
36
  ## Development
25
37
  - **Tool Version Manager**: mise
@@ -41,4 +53,4 @@
41
53
  ## Architecture
42
54
  - **Composite Pattern**: Used for `Internal` nodes to treat individual objects and compositions uniformly.
43
55
  - **Builder Pattern**: `CallableTree::Node::Internal::Builder` and `CallableTree::Node::External::Builder` provide a fluent interface for constructing complex trees.
44
-
56
+ - **Pod Pattern**: `CallableTree::Node::Internal::Pod` and `CallableTree::Node::External::Pod` enable inline node creation via `External.create` / `Internal.create` factory methods with proc-based behaviors.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.12] - 2026-01-07
4
+
5
+ - Add Factory style for inline node creation as a third option alongside Class and Builder styles.
6
+ - `CallableTree::Node::External.create` and `CallableTree::Node::Internal.create` factory methods
7
+ - Supports `hookable: true` option for Hooks (before/around/after callbacks)
8
+ - See `examples/factory/*.rb` for details.
9
+
3
10
  ## [0.3.11] - 2026-01-03
4
11
 
5
12
  - Fix a typo in `Strategizable#strategize` where it incorrectly called `strategy!` instead of `strategize!`.`
data/README.md CHANGED
@@ -3,6 +3,10 @@
3
3
  [![build](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/build.yml/badge.svg)](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/build.yml)
4
4
  [![CodeQL](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/codeql-analysis.yml)
5
5
 
6
+ A framework for building tree-structured executable workflows in Ruby.
7
+
8
+ Construct trees of callable nodes to handle complex execution flows. Supports strategies to seek specific handlers, broadcast to multiple listeners, or compose processing pipelines. Nodes are matched against input and executed in a chain, offering a structured, modular alternative to complex conditional logic.
9
+
6
10
  ## Installation
7
11
 
8
12
  Add this line to your application's Gemfile:
@@ -32,7 +36,7 @@ Builds a tree of `CallableTree` nodes. Invokes the `call` method on nodes where
32
36
 
33
37
  ### Basic
34
38
 
35
- There are two ways to define the nodes: class style and builder style.
39
+ There are three ways to define nodes: class style, builder style, and factory style.
36
40
 
37
41
  #### `CallableTree::Node::Internal#seekable` (default strategy)
38
42
 
@@ -257,6 +261,84 @@ Run `examples/builder/internal-seekable.rb`:
257
261
  ---
258
262
  ```
259
263
 
264
+ ##### Factory style
265
+
266
+ Factory style defines behaviors as procs first, then assembles the tree structure separately. This makes the tree structure clearly visible.
267
+
268
+ `examples/factory/internal-seekable.rb`:
269
+ ```ruby
270
+ # === Behavior Definitions ===
271
+
272
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
273
+ json_caller = lambda do |input, **options, &original|
274
+ File.open(input) do |file|
275
+ json = JSON.parse(file.read)
276
+ original.call(json, **options)
277
+ end
278
+ end
279
+
280
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
281
+ xml_caller = lambda do |input, **options, &original|
282
+ File.open(input) do |file|
283
+ original.call(REXML::Document.new(file), **options)
284
+ end
285
+ end
286
+
287
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
288
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
289
+
290
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
291
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
292
+
293
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
294
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
295
+
296
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
297
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
298
+
299
+ terminator_true = ->(*) { true }
300
+
301
+ # === Tree Structure (clearly visible!) ===
302
+
303
+ tree = CallableTree::Node::Root.new.seekable.append(
304
+ CallableTree::Node::Internal.create(
305
+ matcher: json_matcher,
306
+ caller: json_caller,
307
+ terminator: terminator_true
308
+ ).seekable.append(
309
+ CallableTree::Node::External.create(matcher: animals_json_matcher, caller: animals_json_caller),
310
+ CallableTree::Node::External.create(matcher: fruits_json_matcher, caller: fruits_json_caller)
311
+ ),
312
+ CallableTree::Node::Internal.create(
313
+ matcher: xml_matcher,
314
+ caller: xml_caller,
315
+ terminator: terminator_true
316
+ ).seekable.append(
317
+ CallableTree::Node::External.create(matcher: animals_xml_matcher, caller: animals_xml_caller),
318
+ CallableTree::Node::External.create(matcher: fruits_xml_matcher, caller: fruits_xml_caller)
319
+ )
320
+ )
321
+
322
+ Dir.glob("#{__dir__}/../docs/*") do |file|
323
+ options = { foo: :bar }
324
+ pp tree.call(file, **options)
325
+ puts '---'
326
+ end
327
+ ```
328
+
329
+ Run `examples/factory/internal-seekable.rb`:
330
+ ```sh
331
+ % ruby examples/factory/internal-seekable.rb
332
+ {"Dog"=>"🐶", "Cat"=>"🐱"}
333
+ ---
334
+ {"Dog"=>"🐶", "Cat"=>"🐱"}
335
+ ---
336
+ {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
337
+ ---
338
+ {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
339
+ ---
340
+ ```
341
+
260
342
  #### `CallableTree::Node::Internal#broadcastable`
261
343
 
262
344
  This strategy broadcasts input to all child nodes and returns their results as an array. It ignores child `terminate?` methods by default.
@@ -403,6 +485,55 @@ Run `examples/builder/internal-broadcastable.rb`:
403
485
  10 -> [nil, nil]
404
486
  ```
405
487
 
488
+ ##### Factory style
489
+
490
+ `examples/factory/internal-broadcastable.rb`:
491
+ ```ruby
492
+ # === Behavior Definitions ===
493
+
494
+ less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
495
+ less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
496
+
497
+ multiply_2_caller = ->(input, **) { input * 2 }
498
+ add_1_caller = ->(input, **) { input + 1 }
499
+ multiply_3_caller = ->(input, **) { input * 3 }
500
+ subtract_1_caller = ->(input, **) { input - 1 }
501
+
502
+ # === Tree Structure ===
503
+
504
+ tree = CallableTree::Node::Root.new.broadcastable.append(
505
+ CallableTree::Node::Internal.create(matcher: less_than_5_matcher).broadcastable.append(
506
+ CallableTree::Node::External.create(caller: multiply_2_caller),
507
+ CallableTree::Node::External.create(caller: add_1_caller)
508
+ ),
509
+ CallableTree::Node::Internal.create(matcher: less_than_10_matcher).broadcastable.append(
510
+ CallableTree::Node::External.create(caller: multiply_3_caller),
511
+ CallableTree::Node::External.create(caller: subtract_1_caller)
512
+ )
513
+ )
514
+
515
+ (0..10).each do |input|
516
+ output = tree.call(input)
517
+ puts "#{input} -> #{output}"
518
+ end
519
+ ```
520
+
521
+ Run `examples/factory/internal-broadcastable.rb`:
522
+ ```sh
523
+ % ruby examples/factory/internal-broadcastable.rb
524
+ 0 -> [[0, 1], [0, -1]]
525
+ 1 -> [[2, 2], [3, 0]]
526
+ 2 -> [[4, 3], [6, 1]]
527
+ 3 -> [[6, 4], [9, 2]]
528
+ 4 -> [[8, 5], [12, 3]]
529
+ 5 -> [nil, [15, 4]]
530
+ 6 -> [nil, [18, 5]]
531
+ 7 -> [nil, [21, 6]]
532
+ 8 -> [nil, [24, 7]]
533
+ 9 -> [nil, [27, 8]]
534
+ 10 -> [nil, nil]
535
+ ```
536
+
406
537
  #### `CallableTree::Node::Internal#composable`
407
538
 
408
539
  This strategy chains child nodes, passing the output of the previous node as input to the next.
@@ -550,6 +681,55 @@ Run `examples/builder/internal-composable.rb`:
550
681
  10 -> 10
551
682
  ```
552
683
 
684
+ ##### Factory style
685
+
686
+ `examples/factory/internal-composable.rb`:
687
+ ```ruby
688
+ # === Behavior Definitions ===
689
+
690
+ less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
691
+ less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
692
+
693
+ multiply_2_caller = ->(input, **) { input * 2 }
694
+ add_1_caller = ->(input, **) { input + 1 }
695
+ multiply_3_caller = ->(input, **) { input * 3 }
696
+ subtract_1_caller = ->(input, **) { input - 1 }
697
+
698
+ # === Tree Structure ===
699
+
700
+ tree = CallableTree::Node::Root.new.composable.append(
701
+ CallableTree::Node::Internal.create(matcher: less_than_5_matcher).composable.append(
702
+ CallableTree::Node::External.create(caller: multiply_2_caller),
703
+ CallableTree::Node::External.create(caller: add_1_caller)
704
+ ),
705
+ CallableTree::Node::Internal.create(matcher: less_than_10_matcher).composable.append(
706
+ CallableTree::Node::External.create(caller: multiply_3_caller),
707
+ CallableTree::Node::External.create(caller: subtract_1_caller)
708
+ )
709
+ )
710
+
711
+ (0..10).each do |input|
712
+ output = tree.call(input)
713
+ puts "#{input} -> #{output}"
714
+ end
715
+ ```
716
+
717
+ Run `examples/factory/internal-composable.rb`:
718
+ ```sh
719
+ % ruby examples/factory/internal-composable.rb
720
+ 0 -> 2
721
+ 1 -> 8
722
+ 2 -> 14
723
+ 3 -> 20
724
+ 4 -> 26
725
+ 5 -> 14
726
+ 6 -> 17
727
+ 7 -> 20
728
+ 8 -> 23
729
+ 9 -> 26
730
+ 10 -> 10
731
+ ```
732
+
553
733
  ### Advanced
554
734
 
555
735
  #### `CallableTree::Node::External#verbosify`
@@ -8,8 +8,14 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ['jsmmr']
9
9
  spec.email = ['jsmmr@icloud.com']
10
10
 
11
- spec.summary = 'Builds a tree by linking callable nodes. The nodes that match the conditions are called in a chain from the root node to the leaf node. These are like nested `if` or `case` expressions.'
12
- spec.description = 'Builds a tree by linking callable nodes. The nodes that match the conditions are called in a chain from the root node to the leaf node. These are like nested `if` or `case` expressions.'
11
+ spec.summary = 'Builds executable trees of callable nodes with flexible strategies like seek, broadcast, and compose.'
12
+ spec.description = <<~DESC
13
+ CallableTree provides a framework for organizing complex logic into a tree of callable nodes.
14
+ It allows you to chain execution from a root node to leaf nodes based on matching conditions.
15
+ Key features include multiple traversal strategies: `seekable` (like nested `if`/`case`),
16
+ `broadcastable` (one-to-many execution), and `composable` (pipelined processing).
17
+ Supports class-based, builder-style and factory-style definitions.
18
+ DESC
13
19
  spec.homepage = 'https://github.com/jsmmr/ruby_callable_tree'
14
20
  spec.license = 'MIT'
15
21
  spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
 
5
6
  HooksSample =
6
7
  CallableTree::Node::Internal::Builder
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
 
5
6
  def less_than(num)
6
7
  # The following block call is equivalent to calling `super` in the class style.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
 
5
6
  def less_than(num)
6
7
  # The following block call is equivalent to calling `super` in the class style.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
 
5
6
  module Node
6
7
  class HooksSample
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
 
5
6
  module Node
6
7
  class LessThan
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
 
5
6
  module Node
6
7
  class LessThan
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'callable_tree'
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
4
5
  require 'json'
5
6
  require 'rexml/document'
6
7
 
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+ require 'json'
6
+ require 'rexml/document'
7
+
8
+ # Verbosify example using Factory style with pre-defined procs
9
+ # Shows verbose output including route information
10
+
11
+ # === Behavior Definitions ===
12
+
13
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
14
+ json_caller = lambda do |input, **options, &block|
15
+ File.open(input) do |file|
16
+ json = JSON.parse(file.read)
17
+ block.call(json, **options)
18
+ end
19
+ end
20
+
21
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
22
+ xml_caller = lambda do |input, **options, &block|
23
+ File.open(input) do |file|
24
+ block.call(REXML::Document.new(file), **options)
25
+ end
26
+ end
27
+
28
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
29
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
30
+
31
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
32
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
33
+
34
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
35
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
36
+
37
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
38
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
39
+
40
+ terminator_true = ->(*) { true }
41
+
42
+ # === Tree Structure ===
43
+
44
+ tree = CallableTree::Node::Root.new.seekable.append(
45
+ CallableTree::Node::Internal.create(
46
+ matcher: json_matcher,
47
+ caller: json_caller,
48
+ terminator: terminator_true
49
+ ).seekable.append(
50
+ CallableTree::Node::External.create(matcher: animals_json_matcher, caller: animals_json_caller).verbosify,
51
+ CallableTree::Node::External.create(matcher: fruits_json_matcher, caller: fruits_json_caller).verbosify
52
+ ),
53
+ CallableTree::Node::Internal.create(
54
+ matcher: xml_matcher,
55
+ caller: xml_caller,
56
+ terminator: terminator_true
57
+ ).seekable.append(
58
+ CallableTree::Node::External.create(matcher: animals_xml_matcher, caller: animals_xml_caller).verbosify,
59
+ CallableTree::Node::External.create(matcher: fruits_xml_matcher, caller: fruits_xml_caller).verbosify
60
+ )
61
+ )
62
+
63
+ # === Execution ===
64
+
65
+ Dir.glob("#{__dir__}/../docs/*") do |file|
66
+ options = { foo: :bar }
67
+ pp tree.call(file, **options)
68
+ puts '---'
69
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+
6
+ # Hooks example using Factory style with pre-defined procs
7
+ # Demonstrates before/around/after callbacks on matcher, caller, and terminator
8
+
9
+ # === Behavior Definitions ===
10
+
11
+ external_caller = lambda do |input, **_options|
12
+ puts "external input: #{input}"
13
+ input * 2
14
+ end
15
+
16
+ # === Tree Structure with Hooks ===
17
+
18
+ CallableTree::Node::Root.new.append(
19
+ CallableTree::Node::Internal.create(hookable: true)
20
+ .append(external_caller)
21
+ .before_matcher do |input, **_options|
22
+ puts "before_matcher input: #{input}"
23
+ input + 1
24
+ end
25
+ .around_matcher do |input, **_options, &block|
26
+ puts "around_matcher input: #{input}"
27
+ matched = block.call
28
+ puts "around_matcher matched: #{matched}"
29
+ !matched
30
+ end
31
+ .after_matcher do |matched, **_options|
32
+ puts "after_matcher matched: #{matched}"
33
+ !matched
34
+ end
35
+ .before_caller do |input, **_options|
36
+ puts "before_caller input: #{input}"
37
+ input + 1
38
+ end
39
+ .around_caller do |input, **_options, &block|
40
+ puts "around_caller input: #{input}"
41
+ output = block.call
42
+ puts "around_caller output: #{output}"
43
+ output * input
44
+ end
45
+ .after_caller do |output, **_options|
46
+ puts "after_caller output: #{output}"
47
+ output * 2
48
+ end
49
+ .before_terminator do |output, *_inputs, **_options|
50
+ puts "before_terminator output: #{output}"
51
+ output + 1
52
+ end
53
+ .around_terminator do |output, *_inputs, **_options, &block|
54
+ puts "around_terminator output: #{output}"
55
+ terminated = block.call
56
+ puts "around_terminator terminated: #{terminated}"
57
+ !terminated
58
+ end
59
+ .after_terminator do |terminated, **_options|
60
+ puts "after_terminator terminated: #{terminated}"
61
+ !terminated
62
+ end
63
+ ).tap do |tree|
64
+ options = { foo: :bar }
65
+ output = tree.call(1, **options)
66
+ puts "result: #{output}"
67
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+ require 'json'
6
+ require 'rexml/document'
7
+
8
+ # Identity example using Factory style with pre-defined procs
9
+ # Custom identity for each node - _node_: is used here
10
+
11
+ # === Behavior Definitions ===
12
+
13
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
14
+ json_caller = lambda do |input, **options, &block|
15
+ File.open(input) do |file|
16
+ json = JSON.parse(file.read)
17
+ block.call(json, **options)
18
+ end
19
+ end
20
+ json_identifier = ->(_node_:) { _node_.object_id }
21
+
22
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
23
+ xml_caller = lambda do |input, **options, &block|
24
+ File.open(input) do |file|
25
+ block.call(REXML::Document.new(file), **options)
26
+ end
27
+ end
28
+ xml_identifier = ->(_node_:) { _node_.object_id }
29
+
30
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
31
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
32
+ animals_json_identifier = ->(_node_:) { _node_.object_id }
33
+
34
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
35
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
36
+ fruits_json_identifier = ->(_node_:) { _node_.object_id }
37
+
38
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
39
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
40
+ animals_xml_identifier = ->(_node_:) { _node_.object_id }
41
+
42
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
43
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
44
+ fruits_xml_identifier = ->(_node_:) { _node_.object_id }
45
+
46
+ terminator_true = ->(*) { true }
47
+
48
+ # === Tree Structure ===
49
+
50
+ tree = CallableTree::Node::Root.new.seekable.append(
51
+ CallableTree::Node::Internal.create(
52
+ matcher: json_matcher,
53
+ caller: json_caller,
54
+ terminator: terminator_true,
55
+ identifier: json_identifier
56
+ ).seekable.append(
57
+ CallableTree::Node::External.create(
58
+ matcher: animals_json_matcher,
59
+ caller: animals_json_caller,
60
+ identifier: animals_json_identifier
61
+ ).verbosify,
62
+ CallableTree::Node::External.create(
63
+ matcher: fruits_json_matcher,
64
+ caller: fruits_json_caller,
65
+ identifier: fruits_json_identifier
66
+ ).verbosify
67
+ ),
68
+ CallableTree::Node::Internal.create(
69
+ matcher: xml_matcher,
70
+ caller: xml_caller,
71
+ terminator: terminator_true,
72
+ identifier: xml_identifier
73
+ ).seekable.append(
74
+ CallableTree::Node::External.create(
75
+ matcher: animals_xml_matcher,
76
+ caller: animals_xml_caller,
77
+ identifier: animals_xml_identifier
78
+ ).verbosify,
79
+ CallableTree::Node::External.create(
80
+ matcher: fruits_xml_matcher,
81
+ caller: fruits_xml_caller,
82
+ identifier: fruits_xml_identifier
83
+ ).verbosify
84
+ )
85
+ )
86
+
87
+ # === Execution ===
88
+
89
+ Dir.glob("#{__dir__}/../docs/*") do |file|
90
+ options = { foo: :bar }
91
+ pp tree.call(file, **options)
92
+ puts '---'
93
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+
6
+ # Broadcastable example using Factory style with pre-defined procs
7
+ # All matching child nodes are called and results are collected
8
+
9
+ # === Behavior Definitions ===
10
+
11
+ less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
12
+ less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
13
+
14
+ multiply_2_caller = ->(input, **) { input * 2 }
15
+ add_1_caller = ->(input, **) { input + 1 }
16
+ multiply_3_caller = ->(input, **) { input * 3 }
17
+ subtract_1_caller = ->(input, **) { input - 1 }
18
+
19
+ # === Tree Structure ===
20
+
21
+ tree = CallableTree::Node::Root.new.broadcastable.append(
22
+ CallableTree::Node::Internal.create(matcher: less_than_5_matcher).broadcastable.append(
23
+ CallableTree::Node::External.create(caller: multiply_2_caller),
24
+ CallableTree::Node::External.create(caller: add_1_caller)
25
+ ),
26
+ CallableTree::Node::Internal.create(matcher: less_than_10_matcher).broadcastable.append(
27
+ CallableTree::Node::External.create(caller: multiply_3_caller),
28
+ CallableTree::Node::External.create(caller: subtract_1_caller)
29
+ )
30
+ )
31
+
32
+ # === Execution ===
33
+
34
+ (0..10).each do |input|
35
+ output = tree.call(input)
36
+ puts "#{input} -> #{output}"
37
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+
6
+ # Composable example using Factory style with pre-defined procs
7
+ # Output of one node becomes input to the next (pipeline)
8
+
9
+ # === Behavior Definitions ===
10
+
11
+ less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
12
+ less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
13
+
14
+ multiply_2_caller = ->(input, **) { input * 2 }
15
+ add_1_caller = ->(input, **) { input + 1 }
16
+ multiply_3_caller = ->(input, **) { input * 3 }
17
+ subtract_1_caller = ->(input, **) { input - 1 }
18
+
19
+ # === Tree Structure ===
20
+
21
+ tree = CallableTree::Node::Root.new.composable.append(
22
+ CallableTree::Node::Internal.create(matcher: less_than_5_matcher).composable.append(
23
+ CallableTree::Node::External.create(caller: multiply_2_caller),
24
+ CallableTree::Node::External.create(caller: add_1_caller)
25
+ ),
26
+ CallableTree::Node::Internal.create(matcher: less_than_10_matcher).composable.append(
27
+ CallableTree::Node::External.create(caller: multiply_3_caller),
28
+ CallableTree::Node::External.create(caller: subtract_1_caller)
29
+ )
30
+ )
31
+
32
+ # === Execution ===
33
+
34
+ (0..10).each do |input|
35
+ output = tree.call(input)
36
+ puts "#{input} -> #{output}"
37
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+ require 'json'
6
+ require 'rexml/document'
7
+
8
+ # Seekable example using Factory style with pre-defined procs
9
+ # This demonstrates the key difference from Builder style:
10
+ # - Behaviors are defined as procs first
11
+ # - Tree structure is assembled separately, making it clearly visible
12
+
13
+ # === Behavior Definitions ===
14
+
15
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
16
+ json_caller = lambda do |input, **options, &original|
17
+ File.open(input) do |file|
18
+ json = JSON.parse(file.read)
19
+ original.call(json, **options)
20
+ end
21
+ end
22
+
23
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
24
+ xml_caller = lambda do |input, **options, &original|
25
+ File.open(input) do |file|
26
+ original.call(REXML::Document.new(file), **options)
27
+ end
28
+ end
29
+
30
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
31
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
32
+
33
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
34
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
35
+
36
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
37
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
38
+
39
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
40
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
41
+
42
+ terminator_true = ->(*) { true }
43
+
44
+ # === Tree Structure (clearly visible!) ===
45
+
46
+ tree = CallableTree::Node::Root.new.seekable.append(
47
+ CallableTree::Node::Internal.create(
48
+ matcher: json_matcher,
49
+ caller: json_caller,
50
+ terminator: terminator_true
51
+ ).seekable.append(
52
+ CallableTree::Node::External.create(matcher: animals_json_matcher, caller: animals_json_caller),
53
+ CallableTree::Node::External.create(matcher: fruits_json_matcher, caller: fruits_json_caller)
54
+ ),
55
+ CallableTree::Node::Internal.create(
56
+ matcher: xml_matcher,
57
+ caller: xml_caller,
58
+ terminator: terminator_true
59
+ ).seekable.append(
60
+ CallableTree::Node::External.create(matcher: animals_xml_matcher, caller: animals_xml_caller),
61
+ CallableTree::Node::External.create(matcher: fruits_xml_matcher, caller: fruits_xml_caller)
62
+ )
63
+ )
64
+
65
+ # === Execution ===
66
+
67
+ Dir.glob("#{__dir__}/../docs/*") do |file|
68
+ options = { foo: :bar }
69
+ pp tree.call(file, **options)
70
+ puts '---'
71
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+ require 'json'
6
+ require 'rexml/document'
7
+
8
+ # Logging example using Factory style with pre-defined procs
9
+ # Uses hooks to add logging to the tree - _node_: is used in hooks
10
+
11
+ # === Behavior Definitions ===
12
+
13
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
14
+ json_caller = lambda do |input, **options, &original|
15
+ File.open(input) do |file|
16
+ json = JSON.parse(file.read)
17
+ original.call(json, **options)
18
+ end
19
+ end
20
+
21
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
22
+ xml_caller = lambda do |input, **options, &original|
23
+ File.open(input) do |file|
24
+ original.call(REXML::Document.new(file), **options)
25
+ end
26
+ end
27
+
28
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
29
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
30
+
31
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
32
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
33
+
34
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
35
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
36
+
37
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
38
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
39
+
40
+ terminator_true = ->(*) { true }
41
+
42
+ # === Logging Module (uses _node_:) ===
43
+
44
+ module Logging
45
+ INDENT_SIZE = 2
46
+ BLANK = ' '
47
+ LIST_STYLE = '*'
48
+ INPUT_LABEL = 'Input :'
49
+ OUTPUT_LABEL = 'Output:'
50
+
51
+ def self.loggable(node)
52
+ node.after_matcher! do |matched, _node_:, **|
53
+ prefix = LIST_STYLE.rjust((_node_.depth * INDENT_SIZE) - INDENT_SIZE + LIST_STYLE.length, BLANK)
54
+ puts "#{prefix} #{_node_.identity}: [matched: #{matched}]"
55
+ matched
56
+ end
57
+
58
+ return unless node.external?
59
+
60
+ node
61
+ .before_caller! do |input, *, _node_:, **|
62
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
63
+ puts "#{input_prefix} #{input}"
64
+ input
65
+ end
66
+ .after_caller! do |output, _node_:, **|
67
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
68
+ puts "#{output_prefix} #{output}"
69
+ output
70
+ end
71
+ end
72
+ end
73
+
74
+ loggable = Logging.method(:loggable)
75
+
76
+ # === Tree Structure ===
77
+
78
+ tree = CallableTree::Node::Root.new.seekable.append(
79
+ CallableTree::Node::Internal.create(
80
+ matcher: json_matcher,
81
+ caller: json_caller,
82
+ terminator: terminator_true,
83
+ hookable: true
84
+ ).tap(&loggable).seekable.append(
85
+ CallableTree::Node::External.create(
86
+ matcher: animals_json_matcher,
87
+ caller: animals_json_caller,
88
+ hookable: true
89
+ ).tap(&loggable).verbosify,
90
+ CallableTree::Node::External.create(
91
+ matcher: fruits_json_matcher,
92
+ caller: fruits_json_caller,
93
+ hookable: true
94
+ ).tap(&loggable).verbosify
95
+ ),
96
+ CallableTree::Node::Internal.create(
97
+ matcher: xml_matcher,
98
+ caller: xml_caller,
99
+ terminator: terminator_true,
100
+ hookable: true
101
+ ).tap(&loggable).seekable.append(
102
+ CallableTree::Node::External.create(
103
+ matcher: animals_xml_matcher,
104
+ caller: animals_xml_caller,
105
+ hookable: true
106
+ ).tap(&loggable).verbosify,
107
+ CallableTree::Node::External.create(
108
+ matcher: fruits_xml_matcher,
109
+ caller: fruits_xml_caller,
110
+ hookable: true
111
+ ).tap(&loggable).verbosify
112
+ )
113
+ )
114
+
115
+ # === Execution ===
116
+
117
+ Dir.glob("#{__dir__}/../docs/*") do |file|
118
+ options = { foo: :bar }
119
+ pp tree.call(file, **options)
120
+ puts '---'
121
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module External
6
+ # Pod: A node that can be instantiated directly with proc-based behavior.
7
+ # Provides an alternative to Builder style for inline node creation.
8
+ #
9
+ # Usage patterns:
10
+ # Constructor style: Pod.new(caller: ->(input, **) { input * 2 })
11
+ # Factory style: External.create(caller: ->(input, **) { input * 2 })
12
+ # Block style: External.create { |node| node.caller { |input, **| input * 2 } }
13
+ class Pod
14
+ include External
15
+
16
+ def initialize(matcher: nil, caller: nil, terminator: nil, identifier: nil)
17
+ @_matcher = matcher
18
+ @_caller = caller
19
+ @_terminator = terminator
20
+ @_identifier = identifier
21
+
22
+ yield self if block_given?
23
+ end
24
+
25
+ # DSL setters for block syntax
26
+ def matcher(proc = nil, &block)
27
+ @_matcher = proc || block
28
+ self
29
+ end
30
+
31
+ def caller(proc = nil, &block)
32
+ @_caller = proc || block
33
+ self
34
+ end
35
+
36
+ def terminator(proc = nil, &block)
37
+ @_terminator = proc || block
38
+ self
39
+ end
40
+
41
+ def identifier(proc = nil, &block)
42
+ @_identifier = proc || block
43
+ self
44
+ end
45
+
46
+ def match?(*inputs, **options)
47
+ return super unless @_matcher
48
+
49
+ @_matcher.call(*inputs, **options, _node_: self) do |*a, **o|
50
+ super(*a, **o)
51
+ end
52
+ end
53
+
54
+ def call(*inputs, **options)
55
+ raise ::CallableTree::Error, 'caller is not set' unless @_caller
56
+
57
+ @_caller.call(*inputs, **options, _node_: self) do |*a, **o|
58
+ super(*a, **o)
59
+ end
60
+ end
61
+
62
+ def terminate?(output, *inputs, **options)
63
+ return super unless @_terminator
64
+
65
+ @_terminator.call(output, *inputs, **options, _node_: self) do |o, *a, **opts|
66
+ super(o, *a, **opts)
67
+ end
68
+ end
69
+
70
+ def identity
71
+ return super unless @_identifier
72
+
73
+ @_identifier.call(_node_: self) { super }
74
+ end
75
+ end
76
+
77
+ # HookablePod: Pod with Hooks support (before/around/after callbacks).
78
+ class HookablePod < Pod
79
+ prepend Hooks::Matcher
80
+ prepend Hooks::Caller
81
+ prepend Hooks::Terminator
82
+ end
83
+
84
+ # Factory method
85
+ def self.create(matcher: nil, caller: nil, terminator: nil, identifier: nil, hookable: false, &block)
86
+ klass = hookable ? HookablePod : Pod
87
+ klass.new(matcher: matcher, caller: caller, terminator: terminator, identifier: identifier, &block)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ # Pod: A node that can be instantiated directly with proc-based behavior.
7
+ # Provides an alternative to Builder style for inline node creation.
8
+ #
9
+ # Usage patterns:
10
+ # Constructor style: Pod.new(caller: ->(input, **) { input * 2 })
11
+ # Factory style: Internal.create(caller: ->(input, **) { input * 2 })
12
+ # Block style: Internal.create { |node| node.caller { |input, **| input * 2 } }
13
+ class Pod
14
+ include Internal
15
+
16
+ def initialize(matcher: nil, caller: nil, terminator: nil, identifier: nil)
17
+ @_matcher = matcher
18
+ @_caller = caller
19
+ @_terminator = terminator
20
+ @_identifier = identifier
21
+
22
+ yield self if block_given?
23
+ end
24
+
25
+ # DSL setters for block syntax
26
+ def matcher(proc = nil, &block)
27
+ @_matcher = proc || block
28
+ self
29
+ end
30
+
31
+ def caller(proc = nil, &block)
32
+ @_caller = proc || block
33
+ self
34
+ end
35
+
36
+ def terminator(proc = nil, &block)
37
+ @_terminator = proc || block
38
+ self
39
+ end
40
+
41
+ def identifier(proc = nil, &block)
42
+ @_identifier = proc || block
43
+ self
44
+ end
45
+
46
+ def match?(*inputs, **options)
47
+ return super unless @_matcher
48
+
49
+ @_matcher.call(*inputs, **options, _node_: self) do |*a, **o|
50
+ super(*a, **o)
51
+ end
52
+ end
53
+
54
+ def call(*inputs, **options)
55
+ return super unless @_caller
56
+
57
+ @_caller.call(*inputs, **options, _node_: self) do |*a, **o|
58
+ super(*a, **o)
59
+ end
60
+ end
61
+
62
+ def terminate?(output, *inputs, **options)
63
+ return super unless @_terminator
64
+
65
+ @_terminator.call(output, *inputs, **options, _node_: self) do |o, *a, **opts|
66
+ super(o, *a, **opts)
67
+ end
68
+ end
69
+
70
+ def identity
71
+ return super unless @_identifier
72
+
73
+ @_identifier.call(_node_: self) { super }
74
+ end
75
+ end
76
+
77
+ # HookablePod: Pod with Hooks support (before/around/after callbacks).
78
+ class HookablePod < Pod
79
+ prepend Hooks::Matcher
80
+ prepend Hooks::Caller
81
+ prepend Hooks::Terminator
82
+ end
83
+
84
+ # Factory method
85
+ def self.create(matcher: nil, caller: nil, terminator: nil, identifier: nil, hookable: false, &block)
86
+ klass = hookable ? HookablePod : Pod
87
+ klass.new(matcher: matcher, caller: caller, terminator: terminator, identifier: identifier, &block)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CallableTree
4
- VERSION = '0.3.11'
4
+ VERSION = '0.3.12'
5
5
  end
data/lib/callable_tree.rb CHANGED
@@ -18,6 +18,8 @@ require_relative 'callable_tree/node/internal/strategizable'
18
18
  require_relative 'callable_tree/node/external/verbose'
19
19
  require_relative 'callable_tree/node/external'
20
20
  require_relative 'callable_tree/node/internal'
21
+ require_relative 'callable_tree/node/external/pod'
22
+ require_relative 'callable_tree/node/internal/pod'
21
23
  require_relative 'callable_tree/node/builder'
22
24
  require_relative 'callable_tree/node/internal/builder'
23
25
  require_relative 'callable_tree/node/external/builder'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: callable_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.11
4
+ version: 0.3.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - jsmmr
@@ -9,9 +9,12 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: Builds a tree by linking callable nodes. The nodes that match the conditions
13
- are called in a chain from the root node to the leaf node. These are like nested
14
- `if` or `case` expressions.
12
+ description: |
13
+ CallableTree provides a framework for organizing complex logic into a tree of callable nodes.
14
+ It allows you to chain execution from a root node to leaf nodes based on matching conditions.
15
+ Key features include multiple traversal strategies: `seekable` (like nested `if`/`case`),
16
+ `broadcastable` (one-to-many execution), and `composable` (pipelined processing).
17
+ Supports class-based, builder-style and factory-style definitions.
15
18
  email:
16
19
  - jsmmr@icloud.com
17
20
  executables: []
@@ -54,17 +57,26 @@ files:
54
57
  - examples/docs/animals.xml
55
58
  - examples/docs/fruits.json
56
59
  - examples/docs/fruits.xml
60
+ - examples/factory/external-verbosify.rb
61
+ - examples/factory/hooks.rb
62
+ - examples/factory/identity.rb
63
+ - examples/factory/internal-broadcastable.rb
64
+ - examples/factory/internal-composable.rb
65
+ - examples/factory/internal-seekable.rb
66
+ - examples/factory/logging.rb
57
67
  - lib/callable_tree.rb
58
68
  - lib/callable_tree/node.rb
59
69
  - lib/callable_tree/node/builder.rb
60
70
  - lib/callable_tree/node/external.rb
61
71
  - lib/callable_tree/node/external/builder.rb
72
+ - lib/callable_tree/node/external/pod.rb
62
73
  - lib/callable_tree/node/external/verbose.rb
63
74
  - lib/callable_tree/node/hooks/caller.rb
64
75
  - lib/callable_tree/node/hooks/matcher.rb
65
76
  - lib/callable_tree/node/hooks/terminator.rb
66
77
  - lib/callable_tree/node/internal.rb
67
78
  - lib/callable_tree/node/internal/builder.rb
79
+ - lib/callable_tree/node/internal/pod.rb
68
80
  - lib/callable_tree/node/internal/strategizable.rb
69
81
  - lib/callable_tree/node/internal/strategy.rb
70
82
  - lib/callable_tree/node/internal/strategy/broadcast.rb
@@ -79,7 +91,7 @@ licenses:
79
91
  metadata:
80
92
  homepage_uri: https://github.com/jsmmr/ruby_callable_tree
81
93
  source_code_uri: https://github.com/jsmmr/ruby_callable_tree
82
- changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.3.11/CHANGELOG.md
94
+ changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.3.12/CHANGELOG.md
83
95
  rubygems_mfa_required: 'true'
84
96
  rdoc_options: []
85
97
  require_paths:
@@ -97,7 +109,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
109
  requirements: []
98
110
  rubygems_version: 4.0.3
99
111
  specification_version: 4
100
- summary: Builds a tree by linking callable nodes. The nodes that match the conditions
101
- are called in a chain from the root node to the leaf node. These are like nested
102
- `if` or `case` expressions.
112
+ summary: Builds executable trees of callable nodes with flexible strategies like seek,
113
+ broadcast, and compose.
103
114
  test_files: []