Skip to content

Commit 76d9d63

Browse files
committed
Merge branch 'develop'
* develop: chore: prepare release 0.3.11 fix: fix typo in Strategizable#strategize refactor: rename Strategyable to Strategizable docs: refine English in README.md build: rename build job to test chore: generate .rubocop_todo.yml to silence existing offenses style: apply rubocop -A (unsafe auto-correct) style: apply rubocop -a (safe auto-correct) chore: set TargetRubyVersion to 2.4 in .rubocop.yml build: add lint job to workflow build: add conditional rubocop dependency docs: add project guidelines
2 parents e6cb03d + 46a8684 commit 76d9d63

27 files changed

+298
-47
lines changed

.github/workflows/build.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
name: build
22
on: [push]
33
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:
519
runs-on: ubuntu-latest
620
strategy:
721
matrix:

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
inherit_from: .rubocop_todo.yml
2+
13
AllCops:
24
NewCops: enable
5+
TargetRubyVersion: 2.4
36
Style/HashSyntax:
47
EnforcedShorthandSyntax: never
58
Naming/BlockForwarding:

.rubocop_todo.yml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

AGENTS.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## [Unreleased]
22

3+
## [0.3.11] - 2026-01-03
4+
5+
- Fix a typo in `Strategizable#strategize` where it incorrectly called `strategy!` instead of `strategize!`.`
6+
37
## [0.3.10] - 2022-12-30
48

59
- Change `CallableTree::Node::Internal#broadcastable` to take `matchable` keyword parameter as boolean. It defaults to `true`, which is the same behavior as before.

CONTRIBUTING.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ gemspec
88
gem 'rake', '~> 13.3'
99

1010
gem 'rspec', '~> 3.13'
11+
12+
gem 'rubocop', require: false if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('4.0')

README.md

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ Or install it yourself as:
2121

2222
## Usage
2323

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.
2525

2626
- `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`).
2828
- `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.
3030
- `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.
3232

3333
### Basic
3434

3535
There are two ways to define the nodes: class style and builder style.
3636

3737
#### `CallableTree::Node::Internal#seekable` (default strategy)
3838

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.
4040

4141
##### Class style
4242

@@ -51,18 +51,15 @@ module Node
5151
File.extname(input) == '.json'
5252
end
5353

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.
5655
def call(input, **options)
5756
File.open(input) do |file|
5857
json = ::JSON.load(file)
5958
super(json, **options)
6059
end
6160
end
6261

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`.
6663
def terminate?(_output, *_inputs, **_options)
6764
true
6865
end
@@ -94,17 +91,14 @@ module Node
9491
File.extname(input) == '.xml'
9592
end
9693

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.
9995
def call(input, **options)
10096
File.open(input) do |file|
10197
super(REXML::Document.new(file), **options)
10298
end
10399
end
104100

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`.
108102
def terminate?(_output, *_inputs, **_options)
109103
true
110104
end
@@ -131,7 +125,7 @@ module Node
131125
end
132126
end
133127

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.
135129
tree = CallableTree::Node::Root.new.seekable.append(
136130
Node::JSON::Parser.new.seekable.append(
137131
Node::JSON::Scraper.new(type: :animals),
@@ -265,7 +259,7 @@ Run `examples/builder/internal-seekable.rb`:
265259

266260
#### `CallableTree::Node::Internal#broadcastable`
267261

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.
269263

270264
##### Class style
271265

@@ -411,7 +405,7 @@ Run `examples/builder/internal-broadcastable.rb`:
411405
412406
#### `CallableTree::Node::Internal#composable`
413407
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.
415409
It also ignores their `terminate?` methods by default.
416410
417411
##### Class style
@@ -560,7 +554,7 @@ Run `examples/builder/internal-composable.rb`:
560554
561555
#### `CallableTree::Node::External#verbosify`
562556
563-
If you want verbose output results, call this method.
557+
Use this method to enable verbose output.
564558
565559
`examples/builder/external-verbosify.rb`:
566560
```ruby
@@ -739,7 +733,7 @@ Run `examples/builder/logging.rb`:
739733
740734
#### `CallableTree::Node#identity`
741735
742-
If you want to customize the node identity, specify identifier.
736+
Specify an identifier to customize the node identity.
743737
744738
`examples/builder/identity.rb`:
745739
```ruby

0 commit comments

Comments
 (0)