taski 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da68d3dc067cdf14dd29a438cbf88cc7b00769000682417dc3395eb63f1a1659
4
- data.tar.gz: a5ec661eeea77a73f77b07cbc09dfc4aef88fce2e856bac0366a5e32782356cc
3
+ metadata.gz: 0c018d3aa27087ea7f87e11e01833ffe9c2e6118cc59af29f7f718b7706d108f
4
+ data.tar.gz: c18eb90fcfab955b8576b6329ee5df932fcdb8cfed9079c8705656c9e8b90de9
5
5
  SHA512:
6
- metadata.gz: 68b3c5ddaf63a71067fea5f3c6fdc47c80e6a020f0e7bd417f8cc03e8f595aa4a3f0b16826bf3d8a65935959ee7a243074c51db4bb766c296d25c73817fe90a8
7
- data.tar.gz: ed2a3f80b1a5987c9a2ce8d410c6594c4edd35483a1be9507de0565582facba12e238f2862ae5964af27180431c21b3b80d00887aab8525ee89af2bfb91a507a
6
+ metadata.gz: ea18fc4b07667fd17f2a37356c6a92c54daeadd4f7ad986d5e6131a4a717baf985548845789a17f8b5f5508de70354359a7413b957510d080b041c2f9cde6067
7
+ data.tar.gz: '08d4ce07b0bcde870805a2d5b0daf38d6a3cb850bc0e4da8b197c71b906d9f854fc8c97963362bd7b404b1709ded3d2d53975314bc31b19aab3967bdba903a19'
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **🚧 Development Status:** Taski is currently under active development. Not yet recommended for production use.
8
8
 
9
- **Taski** is a Ruby framework for building task dependency graphs with automatic resolution and execution. It provides two APIs: static dependencies through **Exports** and dynamic dependencies through **Define**.
9
+ **Taski** is a Ruby framework for building task dependency graphs with automatic resolution and execution. It provides three APIs: static dependencies through **Exports**, dynamic dependencies through **Define**, and abstraction layers through **Section**.
10
10
 
11
11
  > **Name Origin**: "Taski" comes from the Japanese word "č„·" (tasuki), a sash used in relay races. Just like how runners pass the sash to the next teammate, tasks in Taski pass dependencies to one another in a continuous chain.
12
12
 
@@ -97,19 +97,61 @@ EnvironmentConfig.build
97
97
  # => Environment: production
98
98
  ```
99
99
 
100
+ ### Section API - Abstraction Layers
101
+
102
+ For environment-specific implementations with clean interfaces:
103
+
104
+ ```ruby
105
+ class DatabaseSection < Taski::Section
106
+ interface :host, :port
107
+
108
+ def impl # No 'self' needed!
109
+ ENV['RAILS_ENV'] == 'production' ? Production : Development
110
+ end
111
+
112
+ class Production < Taski::Task
113
+ def build
114
+ @host = "prod-db.example.com"
115
+ @port = 5432
116
+ end
117
+ end
118
+
119
+ class Development < Taski::Task
120
+ def build
121
+ @host = "localhost"
122
+ @port = 5432
123
+ end
124
+ end
125
+
126
+ apply_auto_exports # DRY - auto-adds exports to nested tasks
127
+ end
128
+
129
+ # Usage is simple - Section works like any Task
130
+ class App < Taski::Task
131
+ def build
132
+ puts "DB: #{DatabaseSection.host}:#{DatabaseSection.port}"
133
+ end
134
+ end
135
+
136
+ App.build # => DB: localhost:5432
137
+ ```
138
+
100
139
  ### When to Use Each API
101
140
 
102
141
  - **Define API**: Best for dynamic runtime dependencies. Cannot contain side effects in definition blocks. Dependencies are analyzed at class definition time, not runtime.
103
142
  - **Exports API**: Ideal for static dependencies. Supports side effects in build methods.
143
+ - **Section API**: Perfect for abstraction layers where you need different implementations based on runtime conditions while maintaining static analysis capabilities.
104
144
 
105
145
  | Use Case | API | Example |
106
146
  |----------|-----|---------|
107
147
  | Configuration values | Exports | File paths, settings |
108
- | Environment-specific logic | Define | Different services per env |
148
+ | Environment-specific logic | Define/Section | Different services per env |
109
149
  | Side effects | Exports | Database connections, I/O |
110
150
  | Conditional processing | Define | Algorithm selection |
151
+ | Implementation abstraction | Section | Database/API adapters |
152
+ | Multi-environment configs | Section | Dev/Test/Prod settings |
111
153
 
112
- **Note**: Define API analyzes dependencies when the class is defined. Conditional dependencies like `ENV['USE_NEW'] ? TaskA : TaskB` will only include the task selected at class definition time, not runtime.
154
+ **Note**: Define API analyzes dependencies when the class is defined. Conditional dependencies like `ENV['USE_NEW'] ? TaskA : TaskB` will only include the task selected at class definition time, not runtime. Use Section API when you need true runtime selection.
113
155
 
114
156
  ## ✨ Key Features
115
157
 
@@ -157,6 +199,11 @@ puts WebServer.tree
157
199
  # => └── Config
158
200
  # => ā”œā”€ā”€ Database
159
201
  # => └── Cache
202
+
203
+ # Sections also appear in dependency trees
204
+ puts AppServer.tree
205
+ # => AppServer
206
+ # => └── DatabaseSection
160
207
  ```
161
208
 
162
209
  ### Progress Display
@@ -309,8 +356,9 @@ bundle exec rake test
309
356
  - **Task Base**: Core framework
310
357
  - **Exports API**: Static dependency resolution
311
358
  - **Define API**: Dynamic dependency resolution
359
+ - **Section API**: Abstraction layer with runtime implementation selection
312
360
  - **Instance Management**: Thread-safe lifecycle
313
- - **Dependency Resolver**: Topological sorting
361
+ - **Dependency Resolver**: Topological sorting with Section support
314
362
 
315
363
  ## Contributing
316
364
 
data/examples/README.md CHANGED
@@ -30,7 +30,17 @@ ruby examples/progress_demo.rb > build.log 2>&1
30
30
  cat build.log
31
31
  ```
32
32
 
33
- ### 3. **[advanced_patterns.rb](advanced_patterns.rb)** - Complex Dependency Patterns
33
+ ### 3. **[section_configuration.rb](section_configuration.rb)** - Section-based Configuration Management
34
+ - Dynamic implementation selection with Taski::Section
35
+ - Environment-specific configuration
36
+ - Section dependency resolution
37
+ - Complex configuration hierarchies
38
+
39
+ ```bash
40
+ ruby examples/section_configuration.rb
41
+ ```
42
+
43
+ ### 4. **[advanced_patterns.rb](advanced_patterns.rb)** - Complex Dependency Patterns
34
44
  - Mixed Exports API and Define API usage
35
45
  - Environment-specific dependencies
36
46
  - Feature flags and conditional logic
@@ -44,6 +54,8 @@ ruby examples/advanced_patterns.rb
44
54
 
45
55
  - **Exports API**: Static dependencies with `exports :property`
46
56
  - **Define API**: Dynamic dependencies with `define :property, -> { ... }`
57
+ - **Section API**: Dynamic implementation selection with `Taski::Section`
58
+ - **Dependency Resolution**: Automatic dependency detection for sections
47
59
  - **Progress Display**: Rich terminal output with spinners and colors
48
60
  - **Output Capture**: Tail-style display of task output
49
61
  - **Environment Configuration**: Different behavior based on runtime settings
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Section Configuration Example
5
+ # This example demonstrates how to use Taski::Section for dynamic implementation
6
+ # selection and dependency resolution with configuration management.
7
+ #
8
+ # Key Features Demonstrated:
9
+ # 1. DRY Principle: No need to duplicate 'exports' declarations in nested Task classes
10
+ # - interface declaration automatically adds exports to nested Task classes
11
+ # 2. Consistent API: impl must return Task classes - .build is called automatically
12
+ # 3. Dynamic Implementation Selection: Different implementations based on environment
13
+ # 4. Dependency Resolution: Sections are properly detected in dependency analysis
14
+ # 5. Tree Visualization: Sections appear in dependency trees
15
+
16
+ require_relative "../lib/taski"
17
+
18
+ # Example 1: Database Configuration Section
19
+ # This section provides database configuration with different implementations
20
+ # for development and production environments
21
+ class DatabaseSection < Taski::Section
22
+ # Define the interface that implementations must provide
23
+ interface :host, :port, :username, :password, :database_name, :pool_size
24
+
25
+ # Select implementation based on environment
26
+ # Note: Must return a Task class - .build is automatically called
27
+ # No 'self' needed - just define as instance method!
28
+ def impl
29
+ if ENV["RAILS_ENV"] == "production"
30
+ Production
31
+ else
32
+ Development
33
+ end
34
+ end
35
+
36
+ # Production implementation with secure settings
37
+ # Note: exports are automatically inherited from interface declaration
38
+ class Production < Taski::Task
39
+ def build
40
+ @host = "prod-db.example.com"
41
+ @port = 5432
42
+ @username = "app_user"
43
+ @password = ENV["DB_PASSWORD"] || "secure_password"
44
+ @database_name = "myapp_production"
45
+ @pool_size = 25
46
+ end
47
+ end
48
+
49
+ # Development implementation with local settings
50
+ # Note: exports are automatically inherited from interface declaration
51
+ class Development < Taski::Task
52
+ def build
53
+ @host = "localhost"
54
+ @port = 5432
55
+ @username = "dev_user"
56
+ @password = "dev_password"
57
+ @database_name = "myapp_development"
58
+ @pool_size = 5
59
+ end
60
+ end
61
+
62
+ # Apply auto-exports after all nested Task classes are defined
63
+ apply_auto_exports
64
+ end
65
+
66
+ # Example 2: API Configuration Section
67
+ # This section provides API endpoints and credentials
68
+ class ApiSection < Taski::Section
69
+ interface :base_url, :api_key, :timeout, :retry_count
70
+
71
+ # No 'self' needed - just define as instance method!
72
+ def impl
73
+ # Select based on feature flag
74
+ # Note: Must return a Task class - .build is automatically called
75
+ if ENV["USE_STAGING_API"] == "true"
76
+ Staging
77
+ else
78
+ Production
79
+ end
80
+ end
81
+
82
+ # Note: exports are automatically inherited from interface declaration - DRY principle!
83
+ class Production < Taski::Task
84
+ def build
85
+ @base_url = "https://api.example.com/v1"
86
+ @api_key = ENV["PROD_API_KEY"] || "prod-key-123"
87
+ @timeout = 30
88
+ @retry_count = 3
89
+ end
90
+ end
91
+
92
+ # Note: exports are automatically inherited from interface declaration - DRY principle!
93
+ class Staging < Taski::Task
94
+ def build
95
+ @base_url = "https://staging-api.example.com/v1"
96
+ @api_key = ENV["STAGING_API_KEY"] || "staging-key-456"
97
+ @timeout = 60
98
+ @retry_count = 1
99
+ end
100
+ end
101
+
102
+ # Apply auto-exports after all nested Task classes are defined
103
+ apply_auto_exports
104
+ end
105
+
106
+ # Example 3: Task that depends on multiple sections
107
+ class ApplicationSetup < Taski::Task
108
+ exports :config_summary
109
+
110
+ def build
111
+ puts "Setting up application with configuration:"
112
+ puts "Database: #{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
113
+ puts "API: #{ApiSection.base_url}"
114
+ puts "Pool size: #{DatabaseSection.pool_size}"
115
+ puts "API timeout: #{ApiSection.timeout}s"
116
+
117
+ @config_summary = {
118
+ database: {
119
+ host: DatabaseSection.host,
120
+ port: DatabaseSection.port,
121
+ database: DatabaseSection.database_name,
122
+ pool_size: DatabaseSection.pool_size
123
+ },
124
+ api: {
125
+ base_url: ApiSection.base_url,
126
+ timeout: ApiSection.timeout,
127
+ retry_count: ApiSection.retry_count
128
+ }
129
+ }
130
+ end
131
+ end
132
+
133
+ # Example 4: Complex dependency chain with sections
134
+ class DatabaseConnection < Taski::Task
135
+ exports :connection
136
+
137
+ def build
138
+ puts "Connecting to database..."
139
+ # Use section configuration to create connection
140
+ connection_string = "postgresql://#{DatabaseSection.username}:#{DatabaseSection.password}@#{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
141
+ @connection = "Connected to: #{connection_string} (pool: #{DatabaseSection.pool_size})"
142
+ puts @connection
143
+ end
144
+ end
145
+
146
+ class ApiClient < Taski::Task
147
+ exports :client
148
+
149
+ def build
150
+ puts "Initializing API client..."
151
+ @client = "API Client: #{ApiSection.base_url} (timeout: #{ApiSection.timeout}s, retries: #{ApiSection.retry_count})"
152
+ puts @client
153
+ end
154
+ end
155
+
156
+ class Application < Taski::Task
157
+ def build
158
+ puts "\n=== Starting Application ==="
159
+
160
+ # Dependencies are automatically resolved
161
+ # DatabaseConnection and ApiClient will be built first
162
+ # which triggers building of their respective sections
163
+
164
+ puts "\nDatabase ready: #{DatabaseConnection.connection}"
165
+ puts "API ready: #{ApiClient.client}"
166
+
167
+ puts "\nApplication configuration summary:"
168
+ puts ApplicationSetup.config_summary.inspect
169
+
170
+ puts "\n=== Application Started Successfully ==="
171
+ end
172
+ end
173
+
174
+ # Demo script
175
+ if __FILE__ == $0
176
+ puts "Taski Section Configuration Example"
177
+ puts "=" * 50
178
+
179
+ puts "\n1. Development Environment (default)"
180
+ ENV["RAILS_ENV"] = "development"
181
+ ENV["USE_STAGING_API"] = "false"
182
+
183
+ # Reset all tasks to ensure fresh build
184
+ [DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
185
+
186
+ Application.build
187
+
188
+ puts "\n" + "=" * 50
189
+ puts "\n2. Production Environment with Staging API"
190
+ ENV["RAILS_ENV"] = "production"
191
+ ENV["USE_STAGING_API"] = "true"
192
+
193
+ # Reset all tasks to see different configuration
194
+ [DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
195
+
196
+ Application.build
197
+
198
+ puts "\n" + "=" * 50
199
+ puts "\n3. Dependency Tree Visualization"
200
+ puts "\nApplication dependency tree:"
201
+ puts Application.tree
202
+
203
+ puts "\nDatabaseConnection dependency tree:"
204
+ puts DatabaseConnection.tree
205
+
206
+ puts "\nApiClient dependency tree:"
207
+ puts ApiClient.tree
208
+
209
+ puts "\n" + "=" * 50
210
+ puts "\nSection dependency resolution successfully demonstrated!"
211
+ puts "Notice how sections appear in the dependency trees and logs."
212
+ end
@@ -76,5 +76,130 @@ puts WebServer.tree
76
76
  puts "\nConfig dependencies:"
77
77
  puts Config.tree
78
78
 
79
+ puts "\nšŸ”§ Section-based Architecture (Dynamic Implementation Selection):"
80
+
81
+ # Create database section with multiple implementation options
82
+ class DatabaseSection < Taski::Section
83
+ interface :connection_string, :pool_size
84
+
85
+ def self.impl
86
+ if ENV["DATABASE"] == "postgres"
87
+ PostgresImplementation
88
+ elsif ENV["DATABASE"] == "mysql"
89
+ MysqlImplementation
90
+ else
91
+ SQLiteImplementation
92
+ end
93
+ end
94
+
95
+ class PostgresImplementation < Taski::Task
96
+ exports :connection_string, :pool_size
97
+
98
+ def build
99
+ Logger.log_level
100
+ @connection_string = "postgresql://localhost/production_app"
101
+ @pool_size = 20
102
+ end
103
+ end
104
+
105
+ class MysqlImplementation < Taski::Task
106
+ exports :connection_string, :pool_size
107
+
108
+ def build
109
+ Logger.log_level
110
+ @connection_string = "mysql://localhost/production_app"
111
+ @pool_size = 15
112
+ end
113
+ end
114
+
115
+ class SQLiteImplementation < Taski::Task
116
+ exports :connection_string, :pool_size
117
+
118
+ def build
119
+ @connection_string = "sqlite:///tmp/development.db"
120
+ @pool_size = 1
121
+ end
122
+ end
123
+ end
124
+
125
+ # Cache section with Redis/Memory options
126
+ class CacheSection < Taski::Section
127
+ interface :cache_url
128
+
129
+ def self.impl
130
+ if ENV["CACHE"] == "redis"
131
+ RedisCache
132
+ else
133
+ MemoryCache
134
+ end
135
+ end
136
+
137
+ class RedisCache < Taski::Task
138
+ exports :cache_url
139
+ def build
140
+ DatabaseSection.connection_string
141
+ @cache_url = "redis://localhost:6379"
142
+ end
143
+ end
144
+
145
+ class MemoryCache < Taski::Task
146
+ exports :cache_url
147
+ def build
148
+ @cache_url = "memory://local"
149
+ end
150
+ end
151
+ end
152
+
153
+ puts "\nšŸ“‹ Section Trees (Show Available Implementations):"
154
+ puts "\nDatabaseSection.tree:"
155
+ puts DatabaseSection.tree
156
+
157
+ puts "\nCacheSection.tree:"
158
+ puts CacheSection.tree
159
+
160
+ puts "\nšŸ” Individual Implementation Trees (Show Actual Dependencies):"
161
+ puts "\nDatabaseSection::PostgresImplementation.tree:"
162
+ puts DatabaseSection::PostgresImplementation.tree
163
+
164
+ puts "\nDatabaseSection::SQLiteImplementation.tree:"
165
+ puts DatabaseSection::SQLiteImplementation.tree
166
+
167
+ puts "\nCacheSection::RedisCache.tree:"
168
+ puts CacheSection::RedisCache.tree
169
+
170
+ puts "\nšŸ”„ Section vs Implementation Comparison:"
171
+ puts "Section shows POSSIBLE implementations:"
172
+ puts DatabaseSection.tree
173
+ puts "\nBut implementation shows ACTUAL dependencies:"
174
+ puts DatabaseSection::PostgresImplementation.tree
175
+
176
+ puts "\nšŸ’” Workflow:"
177
+ puts "1. Use DatabaseSection.tree to see what implementations are available"
178
+ puts "2. Use DatabaseSection::PostgresImplementation.tree to see specific dependencies"
179
+ puts "3. Runtime selects implementation based on ENV variables"
180
+
181
+ puts "\nšŸŽØ Colored Tree Display (if TTY supports colors):"
182
+
183
+ # Enable colors for demonstration
184
+ Taski::TreeColors.enabled = true
185
+
186
+ puts "\nDatabaseSection.tree (with colors):"
187
+ puts DatabaseSection.tree(color: true)
188
+
189
+ puts "\nCacheSection.tree (with colors):"
190
+ puts CacheSection.tree(color: true)
191
+
192
+ puts "\nDatabaseSection::PostgresImplementation.tree (with colors):"
193
+ puts DatabaseSection::PostgresImplementation.tree(color: true)
194
+
195
+ puts "\nšŸŽÆ Color Legend:"
196
+ puts "#{Taski::TreeColors.section("Blue")} = Section names (dynamic selection layer)"
197
+ puts "#{Taski::TreeColors.task("Green")} = Task names (concrete implementations)"
198
+ puts "#{Taski::TreeColors.implementations("Yellow")} = Implementation candidates"
199
+ puts "#{Taski::TreeColors.connector("Gray")} = Tree connectors"
200
+
201
+ # Reset colors to auto-detection
202
+ Taski::TreeColors.enabled = nil
203
+
79
204
  puts "\nā–¶ļø Building Application (to verify dependencies work):"
80
205
  Application.build
@@ -15,52 +15,79 @@ module Taski
15
15
  file_path, line_number = source_location
16
16
  return [] unless File.exist?(file_path)
17
17
 
18
- begin
19
- result = Prism.parse_file(file_path)
20
-
21
- unless result.success?
22
- Taski.logger.error("Parse errors in source file",
23
- file: file_path,
24
- errors: result.errors.map(&:message),
25
- method: "#{klass}##{method_name}")
26
- return []
27
- end
18
+ parse_source_file(file_path, line_number, klass, method_name)
19
+ end
28
20
 
29
- # Handle warnings if present
30
- if result.warnings.any?
31
- Taski.logger.warn("Parse warnings in source file",
32
- file: file_path,
33
- warnings: result.warnings.map(&:message),
34
- method: "#{klass}##{method_name}")
35
- end
21
+ private
36
22
 
37
- dependencies = []
38
- method_node = find_method_node(result.value, method_name, line_number)
23
+ # Parse source file and extract dependencies with proper error handling
24
+ # @param file_path [String] Path to source file
25
+ # @param line_number [Integer] Line number of method definition
26
+ # @param klass [Class] Class containing the method
27
+ # @param method_name [Symbol] Method name being analyzed
28
+ # @return [Array<Class>] Array of dependency classes
29
+ def parse_source_file(file_path, line_number, klass, method_name)
30
+ result = Prism.parse_file(file_path)
31
+ handle_parse_errors(result, file_path, klass, method_name)
32
+ extract_dependencies_from_node(result.value, line_number, klass, method_name)
33
+ rescue IOError, SystemCallError => e
34
+ Taski.logger.error("Failed to read source file",
35
+ file: file_path,
36
+ error: e.message,
37
+ method: "#{klass}##{method_name}")
38
+ []
39
+ rescue => e
40
+ Taski.logger.error("Failed to analyze method dependencies",
41
+ class: klass.name,
42
+ method: method_name,
43
+ error: e.message,
44
+ error_class: e.class.name)
45
+ []
46
+ end
39
47
 
40
- if method_node
41
- visitor = TaskDependencyVisitor.new(klass)
42
- visitor.visit(method_node)
43
- dependencies = visitor.dependencies
44
- end
48
+ # Handle parse errors and warnings from Prism parsing
49
+ # @param result [Prism::ParseResult] Parse result from Prism
50
+ # @param file_path [String] Path to source file
51
+ # @param klass [Class] Class containing the method
52
+ # @param method_name [Symbol] Method name being analyzed
53
+ # @return [Array] Empty array if errors found
54
+ # @raise [RuntimeError] If parse fails
55
+ def handle_parse_errors(result, file_path, klass, method_name)
56
+ unless result.success?
57
+ Taski.logger.error("Parse errors in source file",
58
+ file: file_path,
59
+ errors: result.errors.map(&:message),
60
+ method: "#{klass}##{method_name}")
61
+ return []
62
+ end
45
63
 
46
- dependencies.uniq
47
- rescue IOError, SystemCallError => e
48
- Taski.logger.error("Failed to read source file",
64
+ # Handle warnings if present
65
+ if result.warnings.any?
66
+ Taski.logger.warn("Parse warnings in source file",
49
67
  file: file_path,
50
- error: e.message,
68
+ warnings: result.warnings.map(&:message),
51
69
  method: "#{klass}##{method_name}")
52
- []
53
- rescue => e
54
- Taski.logger.error("Failed to analyze method dependencies",
55
- class: klass.name,
56
- method: method_name,
57
- error: e.message,
58
- error_class: e.class.name)
59
- []
60
70
  end
61
71
  end
62
72
 
63
- private
73
+ # Extract dependencies from parsed AST node
74
+ # @param root_node [Prism::Node] Root AST node
75
+ # @param line_number [Integer] Line number of method definition
76
+ # @param klass [Class] Class containing the method
77
+ # @param method_name [Symbol] Method name being analyzed
78
+ # @return [Array<Class>] Array of unique dependency classes
79
+ def extract_dependencies_from_node(root_node, line_number, klass, method_name)
80
+ dependencies = []
81
+ method_node = find_method_node(root_node, method_name, line_number)
82
+
83
+ if method_node
84
+ visitor = TaskDependencyVisitor.new(klass)
85
+ visitor.visit(method_node)
86
+ dependencies = visitor.dependencies
87
+ end
88
+
89
+ dependencies.uniq
90
+ end
64
91
 
65
92
  def find_method_node(node, method_name, target_line)
66
93
  return nil unless node
@@ -146,7 +173,7 @@ module Taski
146
173
  resolved_class = resolve_relative_constant(const_name)
147
174
  end
148
175
 
149
- if resolved_class&.is_a?(Class) && resolved_class < Taski::Task
176
+ if resolved_class&.is_a?(Class) && (resolved_class < Taski::Task || resolved_class < Taski::Section)
150
177
  @constant_cache[const_name] = resolved_class
151
178
  @dependencies << resolved_class
152
179
  else
@@ -11,4 +11,7 @@ module Taski
11
11
 
12
12
  # Raised when task building fails during execution
13
13
  class TaskBuildError < StandardError; end
14
+
15
+ # Raised when section implementation method is missing
16
+ class SectionImplementationError < StandardError; end
14
17
  end