callable_tree 0.3.9 → 0.3.11

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: 1f81f9d9f97c638d350599e56656dd0430765f8ab79e572b3906963ffc94d32b
4
- data.tar.gz: 7fb4ad3eb420568d51cb3c3969969fa5c14bf82d99b6f2fb2035ce081e641d64
3
+ metadata.gz: aa3020e36165379b1ad6b642240470a33503150fd3bc945206f0685b2dbb80f4
4
+ data.tar.gz: 0b5314f8b192d2908640df885db697bf7a283878f08568e0ce713aa2bd6fd38b
5
5
  SHA512:
6
- metadata.gz: cdf78b08f6d7b6ea112b510ac564724b7c15842bc44ccf4e603bc17083f0a0999705656e37277729006f8f955340f21e0191af720b474492d97e74b6cd6c2a51
7
- data.tar.gz: c0fd6fed3b3ec188b51375558a202543d72ee70aaffe25489dd1cf99c6f8882377e61868e32dc07eed43947cb85248c419c35ebbc89f2ca326f829a0bdb533cd
6
+ metadata.gz: 2476e82ba6ad533bf752d75aec400bc06e9ce7106ca53a736e1b987641cf0f5d3a375126df90a5963feaf3f887ab589d2c87d61790ff08b583469e1fac13b312
7
+ data.tar.gz: 7eb64d2ecc3ae2333e60346173675c341b89ae14cc2d28e2f32f6b48d19d08cd580c2392d976ea6c550a2e66fee2b909c2b286e09a56e7c52b473adbbcadb82a
@@ -1,18 +1,32 @@
1
1
  name: build
2
2
  on: [push]
3
3
  jobs:
4
- build:
4
+ lint:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v6
8
+ - uses: ruby/setup-ruby@v1
9
+ with:
10
+ ruby-version: 4.0
11
+ bundler-cache: true
12
+ - name: Run bundle install
13
+ run: |
14
+ bundle config path vendor/bundle
15
+ bundle install --jobs 4 --retry 3
16
+ - run: bundle exec rubocop
17
+
18
+ test:
5
19
  runs-on: ubuntu-latest
6
20
  strategy:
7
21
  matrix:
8
- ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2.0-preview1']
22
+ ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
9
23
  steps:
10
- - uses: actions/checkout@v3
24
+ - uses: actions/checkout@v6
11
25
  - uses: ruby/setup-ruby@v1
12
26
  with:
13
27
  ruby-version: ${{ matrix.ruby }}
14
- - run: gem install bundler:2.3.7
15
- - uses: actions/cache@v3
28
+ bundler-cache: true
29
+ - uses: actions/cache@v5
16
30
  with:
17
31
  path: vendor/bundle
18
32
  key: ${{ runner.os }}-ruby-${{ matrix.ruby }}-gems-${{ hashFiles('**/Gemfile.lock') }}
@@ -38,11 +38,11 @@ jobs:
38
38
 
39
39
  steps:
40
40
  - name: Checkout repository
41
- uses: actions/checkout@v3
41
+ uses: actions/checkout@v6
42
42
 
43
43
  # Initializes the CodeQL tools for scanning.
44
44
  - name: Initialize CodeQL
45
- uses: github/codeql-action/init@v2
45
+ uses: github/codeql-action/init@v4
46
46
  with:
47
47
  languages: ${{ matrix.language }}
48
48
  # If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +53,7 @@ jobs:
53
53
  # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
54
  # If this step fails, then you should remove it and run the build manually (see below)
55
55
  - name: Autobuild
56
- uses: github/codeql-action/autobuild@v2
56
+ uses: github/codeql-action/autobuild@v4
57
57
 
58
58
  # ℹ️ Command-line programs to run using the OS shell.
59
59
  # 📚 https://git.io/JvXDl
@@ -67,4 +67,4 @@ jobs:
67
67
  # make release
68
68
 
69
69
  - name: Perform CodeQL Analysis
70
- uses: github/codeql-action/analyze@v2
70
+ uses: github/codeql-action/analyze@v4
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+ /Gemfile.lock
data/.rubocop.yml CHANGED
@@ -1,5 +1,8 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  AllCops:
2
4
  NewCops: enable
5
+ TargetRubyVersion: 2.4
3
6
  Style/HashSyntax:
4
7
  EnforcedShorthandSyntax: never
5
8
  Naming/BlockForwarding:
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,120 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-01-02 20:06:51 UTC using RuboCop version 1.82.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: AllowedMethods.
11
+ # AllowedMethods: enums
12
+ Lint/ConstantDefinitionInBlock:
13
+ Exclude:
14
+ - 'spec/callable_tree/node/external_spec.rb'
15
+ - 'spec/callable_tree/node/internal/strategizable_spec.rb'
16
+
17
+ # Offense count: 1
18
+ # Configuration parameters: AllowedParentClasses.
19
+ Lint/MissingSuper:
20
+ Exclude:
21
+ - 'lib/callable_tree/node/root.rb'
22
+
23
+ # Offense count: 10
24
+ # Configuration parameters: AllowKeywordBlockArguments.
25
+ Lint/UnderscorePrefixedVariableName:
26
+ Exclude:
27
+ - 'examples/builder/identity.rb'
28
+ - 'examples/builder/logging.rb'
29
+ - 'examples/class/logging.rb'
30
+
31
+ # Offense count: 8
32
+ # This cop supports safe autocorrection (--autocorrect).
33
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
34
+ # NotImplementedExceptions: NotImplementedError
35
+ Lint/UnusedMethodArgument:
36
+ Exclude:
37
+ - 'lib/callable_tree/node/builder.rb'
38
+ - 'lib/callable_tree/node/external/builder.rb'
39
+ - 'spec/callable_tree/node/builder_spec.rb'
40
+
41
+ # Offense count: 7
42
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
43
+ Metrics/AbcSize:
44
+ Max: 25
45
+
46
+ # Offense count: 38
47
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
48
+ # AllowedMethods: refine
49
+ Metrics/BlockLength:
50
+ Max: 798
51
+
52
+ # Offense count: 1
53
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
54
+ Metrics/CyclomaticComplexity:
55
+ Max: 8
56
+
57
+ # Offense count: 14
58
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
59
+ Metrics/MethodLength:
60
+ Max: 45
61
+
62
+ # Offense count: 8
63
+ # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
64
+ # CheckDefinitionPathHierarchyRoots: lib, spec, test, src
65
+ # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
66
+ Naming/FileName:
67
+ Exclude:
68
+ - 'Rakefile.rb'
69
+ - 'examples/builder/external-verbosify.rb'
70
+ - 'examples/builder/internal-broadcastable.rb'
71
+ - 'examples/builder/internal-composable.rb'
72
+ - 'examples/builder/internal-seekable.rb'
73
+ - 'examples/class/external-verbosify.rb'
74
+ - 'examples/class/internal-broadcastable.rb'
75
+ - 'examples/class/internal-composable.rb'
76
+ - 'examples/class/internal-seekable.rb'
77
+
78
+ # Offense count: 1
79
+ # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
80
+ # AllowedMethods: call
81
+ # WaywardPredicates: nonzero?
82
+ Naming/PredicateMethod:
83
+ Exclude:
84
+ - 'spec/callable_tree/node/builder_spec.rb'
85
+
86
+ # Offense count: 1
87
+ Style/ClassVars:
88
+ Exclude:
89
+ - 'lib/callable_tree/node/internal/strategizable.rb'
90
+
91
+ # Offense count: 40
92
+ # Configuration parameters: AllowedConstants.
93
+ Style/Documentation:
94
+ Enabled: false
95
+
96
+ # Offense count: 45
97
+ Style/MultilineBlockChain:
98
+ Exclude:
99
+ - 'examples/builder/external-verbosify.rb'
100
+ - 'examples/builder/hooks.rb'
101
+ - 'examples/builder/identity.rb'
102
+ - 'examples/builder/internal-seekable.rb'
103
+ - 'examples/builder/logging.rb'
104
+ - 'examples/class/hooks.rb'
105
+ - 'examples/class/logging.rb'
106
+ - 'lib/callable_tree/node/internal/strategy/seek.rb'
107
+
108
+ # Offense count: 1
109
+ # Configuration parameters: AllowedMethods.
110
+ # AllowedMethods: respond_to_missing?
111
+ Style/OptionalBooleanParameter:
112
+ Exclude:
113
+ - 'lib/callable_tree/node/builder.rb'
114
+
115
+ # Offense count: 5
116
+ # This cop supports safe autocorrection (--autocorrect).
117
+ # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
118
+ # URISchemes: http, https
119
+ Layout/LineLength:
120
+ Max: 210
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.1.2
1
+ 4.0.0
data/AGENTS.md ADDED
@@ -0,0 +1,44 @@
1
+ # Agent Guide for callable_tree
2
+
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`).
5
+
6
+ ## Core Concepts
7
+ - **Nodes**:
8
+ - `Root`: The entry point of the tree.
9
+ - `Internal`: Branch nodes that contain child nodes. Strategies:
10
+ - `seekable`: Calls the first matching child (like `case`).
11
+ - `broadcastable`: Calls all matching children.
12
+ - `composable`: Pipes output from one child to the next.
13
+ - `External`: Leaf nodes that perform actual work.
14
+ - **Traversal**:
15
+ - `match?(input)`: Determines if a node should process the input.
16
+ - `call(input)`: Executes the node logic.
17
+ - `terminate?`: Controls when to stop traversal (mostly for `seekable`).
18
+
19
+ ## Directory Structure
20
+ - `lib/`: Source code.
21
+ - `spec/`: RSpec tests.
22
+ - `examples/`: Usage examples (Class-style and Builder-style).
23
+
24
+ ## Development
25
+ - **Tool Version Manager**: mise
26
+ - **Language**: Ruby (>= 2.4.0)
27
+ - **Dependency Management**: Bundler
28
+ - Execute `bundle` commands via `mise` (e.g., `mise x -- bundle exec ...`)
29
+ - **Testing**: RSpec
30
+ - Run all tests: `mise x -- bundle exec rake` or `mise x -- bundle exec rake spec`
31
+ - **Commit Messages**: Follow the convention in [CONTRIBUTING.md](CONTRIBUTING.md).
32
+ - **Linter/Formatter**:
33
+ - Uses `rubocop`.
34
+ - Run checks: `mise x -- bundle exec rubocop`
35
+ - **CI/CD**:
36
+ - GitHub Actions: `.github/workflows/build.yml` runs tests and linter on push/PR.
37
+ - **Release Process**:
38
+ - Version: `lib/callable_tree/version.rb`
39
+ - Tagging: Create a git tag (e.g., `v0.4.0`) and push.
40
+
41
+ ## Architecture
42
+ - **Composite Pattern**: Used for `Internal` nodes to treat individual objects and compositions uniformly.
43
+ - **Builder Pattern**: `CallableTree::Node::Internal::Builder` and `CallableTree::Node::External::Builder` provide a fluent interface for constructing complex trees.
44
+
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.11] - 2026-01-03
4
+
5
+ - Fix a typo in `Strategizable#strategize` where it incorrectly called `strategy!` instead of `strategize!`.`
6
+
7
+ ## [0.3.10] - 2022-12-30
8
+
9
+ - Change `CallableTree::Node::Internal#broadcastable` to take `matchable` keyword parameter as boolean. It defaults to `true`, which is the same behavior as before.
10
+ - Change `CallableTree::Node::Internal#composable` to take `matchable` keyword parameter as boolean. It defaults to `true`, which is the same behavior as before.
11
+ - Change `CallableTree::Node::Internal#seekable` to take `matchable` keyword parameter as boolean. It defaults to `true`, which is the same behavior as before.
12
+
3
13
  ## [0.3.9] - 2022-11-06
4
14
 
5
15
  - Change `CallableTree::Node::Internal#broadcastable` to take `terminable` keyword parameter as boolean. It defaults to `false`, which is the same behavior as before.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,68 @@
1
+ # Commit Message Convention
2
+
3
+ We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification.
4
+
5
+ ## Format
6
+
7
+ ```
8
+ <type>(<scope>): <subject>
9
+
10
+ <body>
11
+
12
+ <footer>
13
+ ```
14
+
15
+ ## Header
16
+
17
+ The header is mandatory and must not exceed 72 characters.
18
+
19
+ ### Type
20
+
21
+ Must be one of the following:
22
+
23
+ - **feat**: A new feature
24
+ - **fix**: A bug fix
25
+ - **docs**: Documentation only changes
26
+ - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
27
+ - **refactor**: A code change that neither fixes a bug nor adds a feature
28
+ - **perf**: A code change that improves performance
29
+ - **test**: Adding missing tests or correcting existing tests
30
+ - **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
31
+ - **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
32
+ - **chore**: Other changes that don't modify src or test files
33
+ - **revert**: Reverts a previous commit
34
+
35
+ ### Scope
36
+
37
+ The scope is optional and should be a phrase describing the section of the codebase affected.
38
+
39
+ ### Subject
40
+
41
+ The subject contains a succinct description of the change:
42
+
43
+ - Use the imperative, present tense: "change" not "changed" nor "changes"
44
+ - Don't capitalize the first letter
45
+ - No dot (.) at the end
46
+
47
+ ## Body
48
+
49
+ The body is optional and should include the motivation for the change and contrast this with previous behavior.
50
+
51
+ ## Footer
52
+
53
+ The footer is optional and should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit closes.
54
+
55
+ ## Branch Naming
56
+
57
+ We generally use the following prefixes for branch names:
58
+
59
+ - `feature/`: New features (e.g., `feature/login-screen`)
60
+ - `fix/`: Bug fixes (e.g., `fix/memory-leak`)
61
+ - `docs/`: Documentation only changes (e.g., `docs/update-readme`)
62
+ - `style/`: Changes that do not affect the meaning of the code (e.g., `style/rubocop-fixes`)
63
+ - `refactor/`: Code changes that neither fix a bug nor add a feature (e.g., `refactor/extract-method`)
64
+ - `test/`: Adding or correcting tests (e.g., `test/add-rspec-cases`)
65
+ - `chore/`: Changes to the build process or auxiliary tools and libraries (e.g., `chore/update-gems`)
66
+
67
+ Use kebab-case for the branch name (e.g., `feature/my-new-feature`).
68
+
data/Gemfile CHANGED
@@ -5,6 +5,8 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in callable_tree.gemspec
6
6
  gemspec
7
7
 
8
- gem 'rake', '~> 13.0'
8
+ gem 'rake', '~> 13.3'
9
9
 
10
- gem 'rspec', '~> 3.0'
10
+ gem 'rspec', '~> 3.13'
11
+
12
+ gem 'rubocop', require: false if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('4.0')
data/README.md CHANGED
@@ -21,14 +21,14 @@ Or install it yourself as:
21
21
 
22
22
  ## Usage
23
23
 
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.
24
+ Builds a tree of `CallableTree` nodes. Invokes the `call` method on nodes where `match?` returns a truthy value, chaining execution from root to leaf.
25
25
 
26
26
  - `CallableTree::Node::Internal`
27
- - This `module` is used to define a node that can have child nodes. This node has several strategies (`seekable`, `broadcastable`, `composable`).
27
+ - Defines a node that can have child nodes. Supports several strategies (`seekable`, `broadcastable`, `composable`).
28
28
  - `CallableTree::Node::External`
29
- - This `module` is used to define a leaf node that cannot have child nodes.
29
+ - Defines a leaf node, which cannot have child nodes.
30
30
  - `CallableTree::Node::Root`
31
- - This `class` includes `CallableTree::Node::Internal`. When there is no need to customize the internal node, use this `class`.
31
+ - Includes `CallableTree::Node::Internal`. Use this class when customization of the internal node is not required.
32
32
 
33
33
  ### Basic
34
34
 
@@ -36,7 +36,7 @@ There are two ways to define the nodes: class style and builder style.
36
36
 
37
37
  #### `CallableTree::Node::Internal#seekable` (default strategy)
38
38
 
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.
39
+ This strategy stops processing subsequent sibling nodes if the current node's `call` method returns a non-nil value. This behavior is changeable by overriding the `terminate?` method.
40
40
 
41
41
  ##### Class style
42
42
 
@@ -51,8 +51,7 @@ module Node
51
51
  File.extname(input) == '.json'
52
52
  end
53
53
 
54
- # If there is need to convert the input values for
55
- # child nodes, override the `call` method.
54
+ # Override `call` if you need to transform input values for child nodes.
56
55
  def call(input, **options)
57
56
  File.open(input) do |file|
58
57
  json = ::JSON.load(file)
@@ -60,9 +59,7 @@ module Node
60
59
  end
61
60
  end
62
61
 
63
- # If a returned value of the `call` method is `nil`,
64
- # but there is no need to call the sibling nodes,
65
- # override the `terminate?` method to return `true`.
62
+ # Override `terminate?` to return `true` to stop processing sibling nodes even if `call` returns `nil`.
66
63
  def terminate?(_output, *_inputs, **_options)
67
64
  true
68
65
  end
@@ -94,17 +91,14 @@ module Node
94
91
  File.extname(input) == '.xml'
95
92
  end
96
93
 
97
- # If there is need to convert the input values for
98
- # child nodes, override the `call` method.
94
+ # Override `call` if you need to transform input values for child nodes.
99
95
  def call(input, **options)
100
96
  File.open(input) do |file|
101
97
  super(REXML::Document.new(file), **options)
102
98
  end
103
99
  end
104
100
 
105
- # If a returned value of the `call` method is `nil`,
106
- # but there is no need to call the sibling nodes,
107
- # override the `terminate?` method to return `true`.
101
+ # Override `terminate?` to return `true` to stop processing sibling nodes even if `call` returns `nil`.
108
102
  def terminate?(_output, *_inputs, **_options)
109
103
  true
110
104
  end
@@ -131,7 +125,7 @@ module Node
131
125
  end
132
126
  end
133
127
 
134
- # The `seekable` method call can be omitted since it is the default strategy.
128
+ # The `seekable` call can be omitted as it is the default strategy.
135
129
  tree = CallableTree::Node::Root.new.seekable.append(
136
130
  Node::JSON::Parser.new.seekable.append(
137
131
  Node::JSON::Scraper.new(type: :animals),
@@ -265,7 +259,7 @@ Run `examples/builder/internal-seekable.rb`:
265
259
 
266
260
  #### `CallableTree::Node::Internal#broadcastable`
267
261
 
268
- This strategy broadcasts to output a result of the child nodes as array. It also ignores their `terminate?` methods by default.
262
+ This strategy broadcasts input to all child nodes and returns their results as an array. It ignores child `terminate?` methods by default.
269
263
 
270
264
  ##### Class style
271
265
 
@@ -411,7 +405,7 @@ Run `examples/builder/internal-broadcastable.rb`:
411
405
 
412
406
  #### `CallableTree::Node::Internal#composable`
413
407
 
414
- This strategy composes the child nodes to input the output of the previous node into the next node and to output a result.
408
+ This strategy chains child nodes, passing the output of the previous node as input to the next.
415
409
  It also ignores their `terminate?` methods by default.
416
410
 
417
411
  ##### Class style
@@ -560,7 +554,7 @@ Run `examples/builder/internal-composable.rb`:
560
554
 
561
555
  #### `CallableTree::Node::External#verbosify`
562
556
 
563
- If you want verbose output results, call this method.
557
+ Use this method to enable verbose output.
564
558
 
565
559
  `examples/builder/external-verbosify.rb`:
566
560
  ```ruby
@@ -659,19 +653,19 @@ module Logging
659
653
  matched
660
654
  end
661
655
 
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
674
- end
656
+ return unless node.external?
657
+
658
+ node
659
+ .before_caller! do |input, *, _node_:, **|
660
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
661
+ puts "#{input_prefix} #{input}"
662
+ input
663
+ end
664
+ .after_caller! do |output, _node_:, **|
665
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
666
+ puts "#{output_prefix} #{output}"
667
+ output
668
+ end
675
669
  end
676
670
  end
677
671
 
@@ -739,7 +733,7 @@ Run `examples/builder/logging.rb`:
739
733
 
740
734
  #### `CallableTree::Node#identity`
741
735
 
742
- If you want to customize the node identity, specify identifier.
736
+ Specify an identifier to customize the node identity.
743
737
 
744
738
  `examples/builder/identity.rb`:
745
739
  ```ruby
@@ -778,7 +772,7 @@ end
778
772
  ...
779
773
  ```
780
774
 
781
- Run `examples/class/identity.rb`:
775
+ Run `examples/builder/identity.rb`:
782
776
  ```sh
783
777
  % ruby examples/builder/identity.rb
784
778
  #<struct CallableTree::Node::External::Output
@@ -12,7 +12,7 @@ JSONParser =
12
12
  end
13
13
  .caller do |input, **options, &block|
14
14
  File.open(input) do |file|
15
- json = ::JSON.load(file)
15
+ json = JSON.parse(file.read)
16
16
  # The following block call is equivalent to calling `super` in the class style.
17
17
  block.call(json, **options)
18
18
  end
@@ -12,7 +12,7 @@ JSONParser =
12
12
  end
13
13
  .caller do |input, **options, &block|
14
14
  File.open(input) do |file|
15
- json = ::JSON.load(file)
15
+ json = JSON.parse(file.read)
16
16
  # The following block call is equivalent to calling `super` in the class style.
17
17
  block.call(json, **options)
18
18
  end
@@ -12,7 +12,7 @@ JSONParser =
12
12
  end
13
13
  .caller do |input, **options, &original|
14
14
  File.open(input) do |file|
15
- json = ::JSON.load(file)
15
+ json = JSON.parse(file.read)
16
16
  # The following block call is equivalent to calling `super` in the class style.
17
17
  original.call(json, **options)
18
18
  end
@@ -12,7 +12,7 @@ JSONParser =
12
12
  end
13
13
  .caller do |input, **options, &original|
14
14
  File.open(input) do |file|
15
- json = ::JSON.load(file)
15
+ json = JSON.parse(file.read)
16
16
  # The following block call is equivalent to calling `super` in the class style.
17
17
  original.call(json, **options)
18
18
  end
@@ -87,19 +87,19 @@ module Logging
87
87
  matched
88
88
  end
89
89
 
90
- if node.external?
91
- node
92
- .before_caller! do |input, *, _node_:, **|
93
- input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
94
- puts "#{input_prefix} #{input}"
95
- input
96
- end
97
- .after_caller! do |output, _node_:, **|
98
- output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
99
- puts "#{output_prefix} #{output}"
100
- output
101
- end
102
- end
90
+ return unless node.external?
91
+
92
+ node
93
+ .before_caller! do |input, *, _node_:, **|
94
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
95
+ puts "#{input_prefix} #{input}"
96
+ input
97
+ end
98
+ .after_caller! do |output, _node_:, **|
99
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
100
+ puts "#{output_prefix} #{output}"
101
+ output
102
+ end
103
103
  end
104
104
  end
105
105
 
@@ -15,7 +15,7 @@ module Node
15
15
 
16
16
  def call(input, **options)
17
17
  File.open(input) do |file|
18
- json = ::JSON.load(file)
18
+ json = ::JSON.parse(file.read)
19
19
  super(json, **options)
20
20
  end
21
21
  end
@@ -28,7 +28,7 @@ module Node
28
28
 
29
29
  def call(input, **options)
30
30
  File.open(input) do |file|
31
- json = ::JSON.load(file)
31
+ json = ::JSON.parse(file.read)
32
32
  super(json, **options)
33
33
  end
34
34
  end
@@ -15,7 +15,7 @@ module Node
15
15
 
16
16
  def call(input, **options)
17
17
  File.open(input) do |file|
18
- json = ::JSON.load(file)
18
+ json = ::JSON.parse(file.read)
19
19
  super(json, **options)
20
20
  end
21
21
  end
@@ -29,7 +29,7 @@ module Node
29
29
 
30
30
  def call(input, **options)
31
31
  File.open(input) do |file|
32
- json = ::JSON.load(file)
32
+ json = ::JSON.parse(file.read)
33
33
  super(json, **options)
34
34
  end
35
35
  end
@@ -124,19 +124,19 @@ module Logging
124
124
  matched
125
125
  end
126
126
 
127
- if node.external?
128
- node
129
- .before_caller! do |input, *, _node_:, **|
130
- input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
131
- puts "#{input_prefix} #{input}"
132
- input
133
- end
134
- .after_caller! do |output, _node_:, **|
135
- output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
136
- puts "#{output_prefix} #{output}"
137
- output
138
- end
139
- end
127
+ return unless node.external?
128
+
129
+ node
130
+ .before_caller! do |input, *, _node_:, **|
131
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
132
+ puts "#{input_prefix} #{input}"
133
+ input
134
+ end
135
+ .after_caller! do |output, _node_:, **|
136
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
137
+ puts "#{output_prefix} #{output}"
138
+ output
139
+ end
140
140
  end
141
141
  end
142
142
 
@@ -50,6 +50,7 @@ module CallableTree
50
50
  ::Class
51
51
  .new do
52
52
  include node_type
53
+
53
54
  if hookable
54
55
  prepend Hooks::Matcher
55
56
  prepend Hooks::Caller
@@ -11,7 +11,7 @@ module CallableTree
11
11
  end
12
12
 
13
13
  def call(*inputs, **options)
14
- output = super(*inputs, **options)
14
+ output = super
15
15
  options.delete(:_node_)
16
16
  routes = self.routes
17
17
 
@@ -6,10 +6,10 @@ module CallableTree
6
6
  include Node
7
7
 
8
8
  def self.included(mod)
9
- if mod.include?(Internal)
10
- raise ::CallableTree::Error,
11
- "#{mod} cannot include #{self} together with #{Internal}"
12
- end
9
+ return unless mod.include?(Internal)
10
+
11
+ raise ::CallableTree::Error,
12
+ "#{mod} cannot include #{self} together with #{Internal}"
13
13
  end
14
14
 
15
15
  def self.proxify(callable)
@@ -32,6 +32,7 @@ module CallableTree
32
32
 
33
33
  def verbosify!
34
34
  extend Verbose
35
+
35
36
  self
36
37
  end
37
38
 
@@ -42,7 +42,7 @@ module CallableTree
42
42
 
43
43
  terminated =
44
44
  if around_terminator_callbacks.empty?
45
- super(output, *inputs, **options)
45
+ super
46
46
  else
47
47
  around_terminator_callbacks_head, *around_terminator_callbacks_tail = around_terminator_callbacks
48
48
  terminator = proc { super(output, *inputs, **options) }
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ module Strategizable
7
+ def self.included(mod)
8
+ mod.extend ClassMethods
9
+ end
10
+
11
+ DEFAUTL_FACTORY = proc do |klass, *_args, matchable:, terminable:, **_kwargs|
12
+ klass.new(matchable: matchable, terminable: terminable)
13
+ end
14
+
15
+ @@strategy_configs = {
16
+ seek: {
17
+ klass: Strategy::Seek,
18
+ alias: :seekable,
19
+ matchable: true,
20
+ terminable: true,
21
+ factory: DEFAUTL_FACTORY
22
+ },
23
+ broadcast: {
24
+ klass: Strategy::Broadcast,
25
+ alias: :broadcastable,
26
+ matchable: true,
27
+ terminable: false,
28
+ factory: DEFAUTL_FACTORY
29
+ },
30
+ compose: {
31
+ klass: Strategy::Compose,
32
+ alias: :composable,
33
+ matchable: true,
34
+ terminable: false,
35
+ factory: DEFAUTL_FACTORY
36
+ }
37
+ }
38
+
39
+ class << self
40
+ private
41
+
42
+ def strategy_configs
43
+ @@strategy_configs
44
+ end
45
+
46
+ def define_strategy_methods(key, config)
47
+ define_method(:"#{config[:alias]}?") { strategy.is_a?(config[:klass]) }
48
+
49
+ define_method(config[:alias]) do |*args, matchable: config[:matchable], terminable: config[:terminable], **kwargs|
50
+ # TODO: Modify to use strategize method
51
+ if strategy == config[:factory].call(
52
+ config[:klass], *args, matchable: matchable, terminable: terminable, **kwargs
53
+ )
54
+ self
55
+ else
56
+ clone.__send__(:"#{config[:alias]}!", *args, matchable: matchable, terminable: terminable, **kwargs)
57
+ end
58
+ end
59
+
60
+ define_method(:"#{config[:alias]}!") do |*args, matchable: config[:matchable], terminable: config[:terminable], **kwargs|
61
+ strategize!(key, *args, matchable: matchable, terminable: terminable, **kwargs)
62
+ end
63
+ end
64
+ end
65
+
66
+ module ClassMethods
67
+ def store_strategy(key, config)
68
+ raise ::CallableTree::Error, 'Strategy class is required. [:klass]' unless config[:klass]
69
+
70
+ key = key.to_sym
71
+ config[:alias] = key unless config[:alias]
72
+ config[:factory] = DEFAUTL_FACTORY unless config[:factory]
73
+ Strategizable.__send__(:strategy_configs)[key] = config
74
+ Strategizable.__send__(:define_strategy_methods, key, config)
75
+ end
76
+ end
77
+
78
+ @@strategy_configs.each { |key, config| define_strategy_methods(key, config) }
79
+
80
+ # Backward compatibility
81
+ alias seek? seekable?
82
+ alias seek seekable
83
+ alias seek! seekable!
84
+ alias broadcast? broadcastable?
85
+ alias broadcast broadcastable
86
+ alias broadcast! broadcastable!
87
+ alias compose? composable?
88
+ alias compose composable
89
+ alias compose! composable!
90
+
91
+ protected
92
+
93
+ attr_writer :strategy
94
+
95
+ private
96
+
97
+ def strategize(name, *args, matchable:, terminable:, **kwargs)
98
+ clone.strategize!(name, *args, matchable: matchable, terminable: terminable, **kwargs)
99
+ end
100
+
101
+ def strategize!(name, *args, matchable:, terminable:, **kwargs)
102
+ config = @@strategy_configs[name.to_sym]
103
+ raise ::CallableTree::Error, "Strategy is not found. [#{name}]" unless config
104
+
105
+ self.strategy = config[:factory].call(
106
+ config[:klass], *args, matchable: matchable, terminable: terminable, **kwargs
107
+ )
108
+ self
109
+ end
110
+
111
+ def strategy
112
+ @strategy ||= @@strategy_configs[:seek][:klass].new
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -7,26 +7,19 @@ module CallableTree
7
7
  class Broadcast
8
8
  include Strategy
9
9
 
10
- def initialize(terminable: false)
10
+ def initialize(matchable: true, terminable: false)
11
+ self.matchable = matchable
11
12
  self.terminable = terminable
12
- @terminator =
13
- if terminable
14
- proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
15
- else
16
- proc { false }
17
- end
18
13
  end
19
14
 
20
15
  def call(nodes, *inputs, **options)
21
- nodes.reduce([]) do |outputs, node|
22
- output = (node.call(*inputs, **options) if node.match?(*inputs, **options))
16
+ nodes.each_with_object([]) do |node, outputs|
17
+ output = (node.call(*inputs, **options) if matcher.call(node, *inputs, **options))
23
18
  outputs << output
24
19
 
25
- if @terminator.call(node, output, *inputs, **options)
26
- break outputs
27
- else
28
- outputs
29
- end
20
+ break outputs if terminator.call(node, output, *inputs, **options)
21
+
22
+ outputs
30
23
  end
31
24
  end
32
25
  end
@@ -7,22 +7,17 @@ module CallableTree
7
7
  class Compose
8
8
  include Strategy
9
9
 
10
- def initialize(terminable: false)
10
+ def initialize(matchable: true, terminable: false)
11
+ self.matchable = matchable
11
12
  self.terminable = terminable
12
- @terminator =
13
- if terminable
14
- proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
15
- else
16
- proc { false }
17
- end
18
13
  end
19
14
 
20
15
  def call(nodes, *inputs, **options)
21
16
  head, *tail = inputs
22
17
  nodes.reduce(head) do |input, node|
23
- if node.match?(input, *tail, **options)
18
+ if matcher.call(node, input, *tail, **options)
24
19
  output = node.call(input, *tail, **options)
25
- break output if @terminator.call(node, output, input, *tail, **options)
20
+ break output if terminator.call(node, output, input, *tail, **options)
26
21
 
27
22
  output
28
23
  else
@@ -7,23 +7,18 @@ module CallableTree
7
7
  class Seek
8
8
  include Strategy
9
9
 
10
- def initialize(terminable: true)
10
+ def initialize(matchable: true, terminable: true)
11
+ self.matchable = matchable
11
12
  self.terminable = terminable
12
- @terminator =
13
- if terminable
14
- proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
15
- else
16
- proc { false }
17
- end
18
13
  end
19
14
 
20
15
  def call(nodes, *inputs, **options)
21
16
  nodes
22
17
  .lazy
23
- .select { |node| node.match?(*inputs, **options) }
18
+ .select { |node| matcher.call(node, *inputs, **options) }
24
19
  .map do |node|
25
20
  output = node.call(*inputs, **options)
26
- terminated = @terminator.call(node, output, *inputs, **options)
21
+ terminated = terminator.call(node, output, *inputs, **options)
27
22
  [output, terminated]
28
23
  end
29
24
  .select { |_output, terminated| terminated }
@@ -13,7 +13,7 @@ module CallableTree
13
13
  end
14
14
 
15
15
  def ==(other)
16
- name == other.name && terminable? == other.terminable?
16
+ name == other.name && matchable? == other.matchable? && terminable? == other.terminable?
17
17
  end
18
18
 
19
19
  def eql?(other)
@@ -21,7 +21,11 @@ module CallableTree
21
21
  end
22
22
 
23
23
  def hash
24
- [self.class.name, terminable].join('-').hash
24
+ [self.class.name, matchable, terminable].join('-').hash
25
+ end
26
+
27
+ def matchable?
28
+ matchable
25
29
  end
26
30
 
27
31
  def terminable?
@@ -30,7 +34,25 @@ module CallableTree
30
34
 
31
35
  private
32
36
 
33
- attr_accessor :terminable
37
+ attr_accessor :matchable, :terminable
38
+
39
+ def matcher
40
+ @matcher ||=
41
+ if matchable
42
+ proc { |node, *inputs, **options| node.match?(*inputs, **options) }
43
+ else
44
+ proc { false }
45
+ end
46
+ end
47
+
48
+ def terminator
49
+ @terminator ||=
50
+ if terminable
51
+ proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
52
+ else
53
+ proc { false }
54
+ end
55
+ end
34
56
  end
35
57
  end
36
58
  end
@@ -5,12 +5,13 @@ module CallableTree
5
5
  module Internal
6
6
  extend ::Forwardable
7
7
  include Node
8
+ include Strategizable
8
9
 
9
10
  def self.included(mod)
10
- if mod.include?(External)
11
- raise ::CallableTree::Error,
12
- "#{mod} cannot include #{self} together with #{External}"
13
- end
11
+ return unless mod.include?(External)
12
+
13
+ raise ::CallableTree::Error,
14
+ "#{mod} cannot include #{self} together with #{External}"
14
15
  end
15
16
 
16
17
  def_delegators :child_nodes, :[], :at
@@ -39,14 +40,14 @@ module CallableTree
39
40
  node = child_nodes.find(&block)
40
41
  return node if node
41
42
 
42
- if recursive
43
- child_nodes
44
- .lazy
45
- .select(&:internal?)
46
- .map { |node| node.find(recursive: true, &block) }
47
- .reject(&:nil?)
48
- .first
49
- end
43
+ return unless recursive
44
+
45
+ child_nodes
46
+ .lazy
47
+ .select(&:internal?)
48
+ .map { |node| node.find(recursive: true, &block) }
49
+ .reject(&:nil?) # rubocop:disable Style/CollectionCompact
50
+ .first
50
51
  end
51
52
 
52
53
  def reject(recursive: false, &block)
@@ -85,72 +86,6 @@ module CallableTree
85
86
  strategy.call(child_nodes, *inputs, **options)
86
87
  end
87
88
 
88
- def seek?
89
- strategy.is_a?(Strategy::Seek)
90
- end
91
-
92
- def seek(terminable: true)
93
- if strategy == Strategy::Seek.new(terminable: terminable)
94
- self
95
- else
96
- clone.seek!(terminable: terminable)
97
- end
98
- end
99
-
100
- def seek!(terminable: true)
101
- self.strategy = Strategy::Seek.new(terminable: terminable)
102
-
103
- self
104
- end
105
-
106
- alias seekable? seek?
107
- alias seekable seek
108
- alias seekable! seek!
109
-
110
- def broadcast?
111
- strategy.is_a?(Strategy::Broadcast)
112
- end
113
-
114
- def broadcast(terminable: false)
115
- if strategy == Strategy::Broadcast.new(terminable: terminable)
116
- self
117
- else
118
- clone.broadcast!(terminable: terminable)
119
- end
120
- end
121
-
122
- def broadcast!(terminable: false)
123
- self.strategy = Strategy::Broadcast.new(terminable: terminable)
124
-
125
- self
126
- end
127
-
128
- alias broadcastable? broadcast?
129
- alias broadcastable broadcast
130
- alias broadcastable! broadcast!
131
-
132
- def compose?
133
- strategy.is_a?(Strategy::Compose)
134
- end
135
-
136
- def compose(terminable: false)
137
- if strategy == Strategy::Compose.new(terminable: terminable)
138
- self
139
- else
140
- clone.compose!(terminable: terminable)
141
- end
142
- end
143
-
144
- def compose!(terminable: false)
145
- self.strategy = Strategy::Compose.new(terminable: terminable)
146
-
147
- self
148
- end
149
-
150
- alias composable? compose?
151
- alias composable compose
152
- alias composable! compose!
153
-
154
89
  def outline(&block)
155
90
  key = block ? block.call(self) : identity
156
91
  value = child_nodes.reduce({}) { |memo, node| memo.merge!(node.outline(&block)) }
@@ -165,10 +100,6 @@ module CallableTree
165
100
  false
166
101
  end
167
102
 
168
- protected
169
-
170
- attr_writer :strategy
171
-
172
103
  def child_nodes
173
104
  @child_nodes ||= []
174
105
  end
@@ -186,10 +117,6 @@ module CallableTree
186
117
  .tap { |node| node.parent = self }
187
118
  end
188
119
 
189
- def strategy
190
- @strategy ||= Strategy::Seek.new
191
- end
192
-
193
120
  def initialize_copy(_node)
194
121
  super
195
122
  self.parent = nil
@@ -13,7 +13,7 @@ module CallableTree
13
13
  node = self
14
14
  loop do
15
15
  y << node
16
- break unless node = node.parent
16
+ break unless (node = node.parent)
17
17
  end
18
18
  end
19
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CallableTree
4
- VERSION = '0.3.9'
4
+ VERSION = '0.3.11'
5
5
  end
data/lib/callable_tree.rb CHANGED
@@ -14,6 +14,7 @@ require_relative 'callable_tree/node/internal/strategy'
14
14
  require_relative 'callable_tree/node/internal/strategy/broadcast'
15
15
  require_relative 'callable_tree/node/internal/strategy/seek'
16
16
  require_relative 'callable_tree/node/internal/strategy/compose'
17
+ require_relative 'callable_tree/node/internal/strategizable'
17
18
  require_relative 'callable_tree/node/external/verbose'
18
19
  require_relative 'callable_tree/node/external'
19
20
  require_relative 'callable_tree/node/internal'
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.0"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: callable_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.3.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - jsmmr
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-11-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Builds a tree by linking callable nodes. The nodes that match the conditions
14
13
  are called in a chain from the root node to the leaf node. These are like nested
@@ -25,10 +24,12 @@ files:
25
24
  - ".gitignore"
26
25
  - ".rspec"
27
26
  - ".rubocop.yml"
27
+ - ".rubocop_todo.yml"
28
28
  - ".ruby-version"
29
+ - AGENTS.md
29
30
  - CHANGELOG.md
31
+ - CONTRIBUTING.md
30
32
  - Gemfile
31
- - Gemfile.lock
32
33
  - LICENSE.txt
33
34
  - README.md
34
35
  - Rakefile
@@ -64,21 +65,22 @@ files:
64
65
  - lib/callable_tree/node/hooks/terminator.rb
65
66
  - lib/callable_tree/node/internal.rb
66
67
  - lib/callable_tree/node/internal/builder.rb
68
+ - lib/callable_tree/node/internal/strategizable.rb
67
69
  - lib/callable_tree/node/internal/strategy.rb
68
70
  - lib/callable_tree/node/internal/strategy/broadcast.rb
69
71
  - lib/callable_tree/node/internal/strategy/compose.rb
70
72
  - lib/callable_tree/node/internal/strategy/seek.rb
71
73
  - lib/callable_tree/node/root.rb
72
74
  - lib/callable_tree/version.rb
75
+ - mise.toml
73
76
  homepage: https://github.com/jsmmr/ruby_callable_tree
74
77
  licenses:
75
78
  - MIT
76
79
  metadata:
77
80
  homepage_uri: https://github.com/jsmmr/ruby_callable_tree
78
81
  source_code_uri: https://github.com/jsmmr/ruby_callable_tree
79
- changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.3.9/CHANGELOG.md
82
+ changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.3.11/CHANGELOG.md
80
83
  rubygems_mfa_required: 'true'
81
- post_install_message:
82
84
  rdoc_options: []
83
85
  require_paths:
84
86
  - lib
@@ -93,8 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
95
  - !ruby/object:Gem::Version
94
96
  version: '0'
95
97
  requirements: []
96
- rubygems_version: 3.3.7
97
- signing_key:
98
+ rubygems_version: 4.0.3
98
99
  specification_version: 4
99
100
  summary: Builds a tree by linking callable nodes. The nodes that match the conditions
100
101
  are called in a chain from the root node to the leaf node. These are like nested
data/Gemfile.lock DELETED
@@ -1,34 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- callable_tree (0.3.9)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- diff-lcs (1.5.0)
10
- rake (13.0.6)
11
- rspec (3.11.0)
12
- rspec-core (~> 3.11.0)
13
- rspec-expectations (~> 3.11.0)
14
- rspec-mocks (~> 3.11.0)
15
- rspec-core (3.11.0)
16
- rspec-support (~> 3.11.0)
17
- rspec-expectations (3.11.1)
18
- diff-lcs (>= 1.2.0, < 2.0)
19
- rspec-support (~> 3.11.0)
20
- rspec-mocks (3.11.1)
21
- diff-lcs (>= 1.2.0, < 2.0)
22
- rspec-support (~> 3.11.0)
23
- rspec-support (3.11.1)
24
-
25
- PLATFORMS
26
- x86_64-darwin-21
27
-
28
- DEPENDENCIES
29
- callable_tree!
30
- rake (~> 13.0)
31
- rspec (~> 3.0)
32
-
33
- BUNDLED WITH
34
- 2.3.7