taski 0.1.1 → 0.2.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: d04adfab160e07e101e7d6364796d5607a896010396950ee5d479824bb4c422e
4
- data.tar.gz: aeb0ea3476853608c0ef77e80e907c79db7ea62c1aaead8a22d8232d7e4c13aa
3
+ metadata.gz: 8b66b9afd145af8c5a07839b1886212336ffbdcb935897a278afae10315162e8
4
+ data.tar.gz: 7f43e874932671c2a41adc776514022e7d44d9ec0018935e2bd2e7e4d4537543
5
5
  SHA512:
6
- metadata.gz: b3201ee69bc45ae1e58569bf1a98647ff7e38adabf0d04334805fdbac816468d420b1d460113970e1fc9394e6f9e6ca2fe4c700e0c390b9032366a01690c7762
7
- data.tar.gz: 81a9796f7234f8f66216b61b7ea676b40614bb8258b8a6d6a0244e5d016319ea1492f7817324e1669a6ede1eeaf3f0ad4d8b71fd210168022838f66047348bff
6
+ metadata.gz: 8175467340490afdd0e9f1d4ba5c2e3db257e7d4c62850f88a138676056ea140753e2c38042803dd46c6c071731245314c2b597af83eb8c237be28d20e998e60
7
+ data.tar.gz: d1695488ca402cdb18800bd4b8623c64ed3c681a5dd6039c651c117c99793045fa59cca85f82cde0fde311b0ada0d81b8a66ba4d8cbb9b576ebe00fe71e760cf
data/README.md CHANGED
@@ -1,68 +1,333 @@
1
1
  # Taski
2
2
 
3
- **Taski** is a Ruby-based task runner designed for small, composable processing steps.
4
- In Taski, you define tasks as Ruby classes that expose named values through `define`. Dependencies between tasks are established automatically when one task references the result of another—no need for explicit dependency declarations.
3
+ > **🚧 Development Status:** Taski is currently under active development and the API may change. Not yet recommended for production use.
5
4
 
6
- Tasks are executed in a topologically sorted order, ensuring that tasks are built only after their inputs are available. Reverse execution is also supported, making it easy to clean up intermediate files or revert changes after a build.
5
+ **Taski** is a powerful Ruby framework for building task dependency graphs with automatic resolution and execution. It provides two complementary APIs for different use cases: static dependencies through exports and dynamic dependencies through define.
7
6
 
8
- > **🚧 Development Status:** Taski is currently under active development and the API may change.
7
+ ## 🎯 Key Features
9
8
 
10
- > **⚠️ Limitation:** Circular dependencies are **not** supported at this time.
9
+ - **Automatic Dependency Resolution**: Dependencies are detected automatically through static analysis and runtime evaluation
10
+ - **Two Complementary APIs**: Choose the right approach for your use case
11
+ - **Exports API**: For simple, static dependencies
12
+ - **Define API**: For complex, dynamic dependencies based on runtime conditions
13
+ - **Thread-Safe Execution**: Safe for concurrent access with Monitor-based synchronization
14
+ - **Circular Dependency Detection**: Prevents infinite loops with clear error messages
15
+ - **Memory Leak Prevention**: Built-in reset mechanisms for long-running applications
16
+ - **Topological Execution**: Tasks execute in correct dependency order automatically
17
+ - **Reverse Cleanup**: Clean operations run in reverse dependency order
11
18
 
12
- > **ℹ️ Note:** Taski does **not** infer dependencies from file contents or behavior. Instead, dependencies are implicitly established via references between task definitions.
19
+ ## 🚀 Quick Start
13
20
 
14
- ### Features
21
+ ```ruby
22
+ require 'taski'
23
+
24
+ # Simple static dependency using Exports API
25
+ class DatabaseSetup < Taski::Task
26
+ exports :connection_string
27
+
28
+ def build
29
+ @connection_string = "postgresql://localhost/myapp"
30
+ puts "Database configured"
31
+ end
32
+ end
33
+
34
+ class APIServer < Taski::Task
35
+ exports :port
36
+
37
+ def build
38
+ # Automatic dependency: DatabaseSetup will be built first
39
+ puts "Starting API with #{DatabaseSetup.connection_string}"
40
+ @port = 3000
41
+ end
42
+ end
43
+
44
+ # Execute - dependencies are resolved automatically
45
+ APIServer.build
46
+ # => Database configured
47
+ # => Starting API with postgresql://localhost/myapp
48
+ ```
49
+
50
+ ## 📚 API Guide
51
+
52
+ ### Exports API - Static Dependencies
53
+
54
+ Use the **Exports API** when you have simple, predictable dependencies:
55
+
56
+ ```ruby
57
+ class ConfigLoader < Taski::Task
58
+ exports :app_name, :version
59
+
60
+ def build
61
+ @app_name = "MyApp"
62
+ @version = "1.0.0"
63
+ end
64
+ end
65
+
66
+ class Deployment < Taski::Task
67
+ exports :deploy_url
68
+
69
+ def build
70
+ # Static dependency - always uses ConfigLoader
71
+ @deploy_url = "https://#{ConfigLoader.app_name}.example.com"
72
+ end
73
+ end
74
+ ```
75
+
76
+ ### Define API - Dynamic Dependencies
77
+
78
+ Use the **Define API** when dependencies change based on runtime conditions:
79
+
80
+ ```ruby
81
+ class EnvironmentConfig < Taski::Task
82
+ define :database_service, -> {
83
+ # Dynamic dependency based on environment
84
+ case ENV['RAILS_ENV']
85
+ when 'production'
86
+ ProductionDatabase.setup
87
+ when 'staging'
88
+ StagingDatabase.setup
89
+ else
90
+ DevelopmentDatabase.setup
91
+ end
92
+ }
93
+
94
+ define :cache_strategy, -> {
95
+ # Dynamic dependency based on feature flags
96
+ if FeatureFlag.enabled?(:redis_cache)
97
+ RedisCache.configure
98
+ else
99
+ MemoryCache.configure
100
+ end
101
+ }
102
+
103
+ def build
104
+ puts "Using #{database_service}"
105
+ puts "Cache: #{cache_strategy}"
106
+ end
107
+ end
108
+ ```
109
+
110
+ > **⚠️ Note:** The `define` API uses dynamic method definition, which may generate Ruby warnings about method redefinition. This is expected behavior due to the dependency resolution mechanism and does not affect functionality.
15
111
 
16
- - Define tasks using Ruby classes
17
- - Implicit dependencies via reference to other task outputs
18
- - Topological execution order
19
- - Reverse execution for cleanup
20
- - Built entirely in Ruby
112
+ ### When to Use Each API
21
113
 
22
- ### Example
114
+ | Use Case | Recommended API | Example |
115
+ |----------|----------------|---------|
116
+ | Simple value exports | Exports API | Configuration values, file paths |
117
+ | Environment-specific logic | Define API | Different services per environment |
118
+ | Feature flag dependencies | Define API | Optional components based on flags |
119
+ | Conditional processing | Define API | Different algorithms based on input |
120
+ | Static file dependencies | Exports API | Build artifacts, compiled assets |
121
+
122
+ ## 🔧 Advanced Features
123
+
124
+ ### Thread Safety
125
+
126
+ Taski is thread-safe and handles concurrent access gracefully:
23
127
 
24
128
  ```ruby
25
- class TaskA < Taski::Task
26
- define :task_a_result, -> { "Task A" }
129
+ # Multiple threads can safely access the same task
130
+ threads = 5.times.map do
131
+ Thread.new { MyTask.some_value }
132
+ end
133
+
134
+ # All threads get the same instance - built only once
135
+ results = threads.map(&:value)
136
+ ```
137
+
138
+ ### Error Handling
139
+
140
+ Comprehensive error handling with custom exception types:
141
+
142
+ ```ruby
143
+ begin
144
+ TaskWithCircularDep.build
145
+ rescue Taski::CircularDependencyError => e
146
+ puts "Circular dependency detected: #{e.message}"
147
+ rescue Taski::TaskBuildError => e
148
+ puts "Build failed: #{e.message}"
149
+ end
150
+ ```
151
+
152
+ ### Lifecycle Management
27
153
 
154
+ Full control over task lifecycle:
155
+
156
+ ```ruby
157
+ class ProcessingTask < Taski::Task
28
158
  def build
29
- puts 'Processing...'
159
+ # Setup and processing logic
160
+ puts "Processing data..."
161
+ end
162
+
163
+ def clean
164
+ # Cleanup logic (runs in reverse dependency order)
165
+ puts "Cleaning up temporary files..."
30
166
  end
31
167
  end
32
168
 
33
- class TaskB < Taski::Task
34
- define :simple_task, -> { "Task result is #{TaskA.task_a_result}" }
169
+ # Build dependencies in correct order
170
+ ProcessingTask.build
171
+
172
+ # Clean in reverse order
173
+ ProcessingTask.clean
174
+ ```
175
+
176
+ ## 🏗️ Complex Example
177
+
178
+ Here's a realistic example showing both APIs working together:
179
+
180
+ ```ruby
181
+ # Environment configuration using Define API
182
+ class Environment < Taski::Task
183
+ define :database_url, -> {
184
+ case ENV['RAILS_ENV']
185
+ when 'production'
186
+ ProductionDB.connection_string
187
+ when 'test'
188
+ TestDB.connection_string
189
+ else
190
+ "sqlite3://development.db"
191
+ end
192
+ }
193
+
194
+ define :redis_config, -> {
195
+ if FeatureFlag.enabled?(:redis_cache)
196
+ RedisService.configuration
197
+ else
198
+ nil
199
+ end
200
+ }
201
+ end
202
+
203
+ # Static configuration using Exports API
204
+ class AppConfig < Taski::Task
205
+ exports :app_name, :version, :port
35
206
 
36
207
  def build
37
- puts simple_task
208
+ @app_name = "MyWebApp"
209
+ @version = "2.1.0"
210
+ @port = ENV.fetch('PORT', 3000).to_i
38
211
  end
39
212
  end
40
213
 
41
- TaskB.build
42
- # => Processing...
43
- # => Task result is Task A
214
+ # Application startup combining both APIs
215
+ class Application < Taski::Task
216
+ def build
217
+ puts "Starting #{AppConfig.app_name} v#{AppConfig.version}"
218
+ puts "Database: #{Environment.database_url}"
219
+ puts "Redis: #{Environment.redis_config || 'disabled'}"
220
+ puts "Port: #{AppConfig.port}"
221
+
222
+ # Start the application...
223
+ end
224
+
225
+ def clean
226
+ puts "Shutting down #{AppConfig.app_name}..."
227
+ # Cleanup logic...
228
+ end
229
+ end
230
+
231
+ # Everything runs in the correct order automatically
232
+ Application.build
44
233
  ```
45
234
 
46
- ## Installation
235
+ ## 📦 Installation
47
236
 
48
- Install the gem and add to the application's Gemfile by executing:
237
+ > **⚠️ Warning:** Taski is currently in development. API changes may occur. Use at your own risk in production environments.
238
+
239
+ Add this line to your application's Gemfile:
240
+
241
+ ```ruby
242
+ gem 'taski'
243
+ ```
244
+
245
+ And then execute:
49
246
 
50
247
  ```bash
51
- bundle add taski
248
+ bundle install
52
249
  ```
53
250
 
54
- If bundler is not being used to manage dependencies, install the gem by executing:
251
+ Or install it yourself as:
55
252
 
56
253
  ```bash
57
254
  gem install taski
58
255
  ```
59
256
 
60
- ## Development
257
+ For development and testing purposes, you can also install directly from the repository:
258
+
259
+ ```ruby
260
+ # In your Gemfile
261
+ gem 'taski', git: 'https://github.com/[USERNAME]/taski.git'
262
+ ```
263
+
264
+ ## 🧪 Testing
265
+
266
+ Taski includes comprehensive test coverage. Run the test suite:
267
+
268
+ ```bash
269
+ bundle exec rake test
270
+ ```
271
+
272
+ > **ℹ️ Note:** Test output may include warnings about method redefinition from the `define` API. These warnings are expected and can be safely ignored.
273
+
274
+
275
+ ## 🏛️ Architecture
276
+
277
+ Taski is built with a modular architecture:
278
+
279
+ - **Task Base**: Core framework and constants
280
+ - **Exports API**: Static dependency resolution
281
+ - **Define API**: Dynamic dependency resolution
282
+ - **Instance Management**: Thread-safe lifecycle management
283
+ - **Dependency Resolver**: Topological sorting and analysis
284
+ - **Static Analyzer**: AST-based dependency detection
285
+
286
+ ## 🚧 Development Status
61
287
 
62
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
288
+ **Taski is currently in active development and should be considered experimental.** While the core functionality is working and well-tested, the API may undergo changes as we refine the framework based on feedback and real-world usage.
63
289
 
64
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
290
+ ### Current Development Phase
291
+
292
+ - ✅ **Core Framework**: Dependency resolution, both APIs, thread safety
293
+ - ✅ **Testing**: Comprehensive test suite with 38+ tests
294
+ - ✅ **Type Safety**: RBS definitions and Steep integration
295
+ - 🚧 **API Stability**: Some breaking changes may occur
296
+ - 🚧 **Performance**: Optimizations for large dependency graphs
297
+ - 🚧 **Documentation**: Examples and best practices
298
+
299
+ ### Known Limitations
300
+
301
+ - **API Changes**: Breaking changes may occur in minor version updates
302
+ - **Production Readiness**: Not yet recommended for production environments
303
+ - **Static Analysis**: Works best with straightforward Ruby code patterns
304
+ - **Metaprogramming**: Complex metaprogramming may require manual dependency specification
305
+ - **Performance**: Not yet optimized for very large dependency graphs (1000+ tasks)
306
+ - **Method Redefinition Warnings**: Using `define` API may generate Ruby warnings about method redefinition (this is expected behavior)
307
+
308
+ ### Future Development
309
+
310
+ The future direction of Taski will be determined based on community feedback, real-world usage, and identified needs. Development priorities may include areas such as performance optimization, enhanced static analysis, and improved documentation, but specific roadmap items have not yet been finalized.
311
+
312
+ ### Contributing to Development
313
+
314
+ We welcome contributions during this development phase! Areas where help is especially appreciated:
315
+
316
+ - **Real-world Testing**: Try Taski in your projects and report issues
317
+ - **Performance Testing**: Test with large dependency graphs
318
+ - **API Feedback**: Suggest improvements to the developer experience
319
+ - **Documentation**: Help improve examples and guides
65
320
 
66
321
  ## Contributing
67
322
 
68
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/taski.
323
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ahogappa/taski.
324
+
325
+ ## License
326
+
327
+ The gem is available as open source under the [MIT License](LICENSE).
328
+
329
+ ---
330
+
331
+ **Taski** - Build complex dependency graphs with simple, elegant Ruby code. 🚀
332
+
333
+ > **Experimental Software**: Please use responsibly and provide feedback to help us reach v1.0!
data/Steepfile ADDED
@@ -0,0 +1,20 @@
1
+ # Steepfile
2
+
3
+ D = Steep::Diagnostic
4
+
5
+ target :lib do
6
+ signature "sig"
7
+
8
+ check "lib"
9
+
10
+ library "monitor", "prism"
11
+
12
+ # Configure diagnostics with lenient settings for metaprogramming-heavy code
13
+ configure_code_diagnostics do |hash|
14
+ hash[D::Ruby::UnannotatedEmptyCollection] = :information
15
+ hash[D::Ruby::UnknownInstanceVariable] = :information
16
+ hash[D::Ruby::FallbackAny] = :information
17
+ hash[D::Ruby::NoMethod] = :warning
18
+ hash[D::Ruby::UndeclaredMethodDefinition] = :information
19
+ end
20
+ end
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+ # Complex example from README showing both APIs
3
+
4
+ require_relative '../lib/taski'
5
+
6
+ # Mock classes for the example
7
+ class ProductionDB < Taski::Task
8
+ exports :connection_string
9
+ def build
10
+ @connection_string = "postgres://prod-server/app"
11
+ end
12
+ end
13
+
14
+ class TestDB < Taski::Task
15
+ exports :connection_string
16
+ def build
17
+ @connection_string = "postgres://test-server/app_test"
18
+ end
19
+ end
20
+
21
+ module FeatureFlag
22
+ def self.enabled?(flag)
23
+ ENV["FEATURE_#{flag.to_s.upcase}"] == 'true'
24
+ end
25
+ end
26
+
27
+ class RedisService < Taski::Task
28
+ exports :configuration
29
+ def build
30
+ @configuration = "redis://localhost:6379"
31
+ end
32
+ end
33
+
34
+ # Environment configuration using Define API
35
+ class Environment < Taski::Task
36
+ define :database_url, -> {
37
+ case ENV['RAILS_ENV']
38
+ when 'production'
39
+ ProductionDB.connection_string
40
+ when 'test'
41
+ TestDB.connection_string
42
+ else
43
+ "sqlite3://development.db"
44
+ end
45
+ }
46
+
47
+ define :redis_config, -> {
48
+ if FeatureFlag.enabled?(:redis_cache)
49
+ RedisService.configuration
50
+ else
51
+ nil
52
+ end
53
+ }
54
+
55
+ def build
56
+ # Environment configuration is handled by define blocks
57
+ end
58
+ end
59
+
60
+ # Static configuration using Exports API
61
+ class AppConfig < Taski::Task
62
+ exports :app_name, :version, :port
63
+
64
+ def build
65
+ @app_name = "MyWebApp"
66
+ @version = "2.1.0"
67
+ @port = ENV.fetch('PORT', 3000).to_i
68
+ end
69
+ end
70
+
71
+ # Application startup combining both APIs
72
+ class Application < Taski::Task
73
+ def build
74
+ puts "Starting #{AppConfig.app_name} v#{AppConfig.version}"
75
+ puts "Database: #{Environment.database_url}"
76
+ puts "Redis: #{Environment.redis_config || 'disabled'}"
77
+ puts "Port: #{AppConfig.port}"
78
+ end
79
+
80
+ def clean
81
+ puts "Shutting down #{AppConfig.app_name}..."
82
+ end
83
+ end
84
+
85
+ # Test different environments
86
+ puts "=== Complex Example ==="
87
+
88
+ puts "\n1. Development Environment (default):"
89
+ ENV.delete('RAILS_ENV')
90
+ ENV.delete('FEATURE_REDIS_CACHE')
91
+ Application.build
92
+ Application.reset!
93
+
94
+ puts "\n2. Test Environment:"
95
+ ENV['RAILS_ENV'] = 'test'
96
+ # Reset Environment to re-evaluate define blocks
97
+ Environment.reset!
98
+ Application.build
99
+ Application.reset!
100
+
101
+ puts "\n3. Production with Redis:"
102
+ ENV['RAILS_ENV'] = 'production'
103
+ ENV['FEATURE_REDIS_CACHE'] = 'true'
104
+ # Reset Environment to re-evaluate define blocks
105
+ Environment.reset!
106
+ Application.build
107
+
108
+ puts "\n4. Cleanup:"
109
+ Application.clean
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # Quick Start example from README
3
+
4
+ require_relative '../lib/taski'
5
+
6
+ # Simple static dependency using Exports API
7
+ class DatabaseSetup < Taski::Task
8
+ exports :connection_string
9
+
10
+ def build
11
+ @connection_string = "postgresql://localhost/myapp"
12
+ puts "Database configured"
13
+ end
14
+ end
15
+
16
+ class APIServer < Taski::Task
17
+ exports :port
18
+
19
+ def build
20
+ # Automatic dependency: DatabaseSetup will be built first
21
+ puts "Starting API with #{DatabaseSetup.connection_string}"
22
+ @port = 3000
23
+ end
24
+ end
25
+
26
+ # Execute - dependencies are resolved automatically
27
+ puts "=== Quick Start Example ==="
28
+ APIServer.build
29
+
30
+ puts "\nResult: APIServer running on port #{APIServer.port}"
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
5
+ module Taski
6
+ module DependencyAnalyzer
7
+ class << self
8
+ def analyze_method(klass, method_name)
9
+ return [] unless klass.instance_methods(false).include?(method_name)
10
+
11
+ method = klass.instance_method(method_name)
12
+ source_location = method.source_location
13
+ return [] unless source_location
14
+
15
+ file_path, line_number = source_location
16
+ return [] unless File.exist?(file_path)
17
+
18
+ begin
19
+ result = Prism.parse_file(file_path)
20
+
21
+ unless result.success?
22
+ warn "Taski: Parse errors in #{file_path}: #{result.errors.map(&:message).join(', ')}"
23
+ return []
24
+ end
25
+
26
+ # Handle warnings if present
27
+ if result.warnings.any?
28
+ warn "Taski: Parse warnings in #{file_path}: #{result.warnings.map(&:message).join(', ')}"
29
+ end
30
+
31
+ dependencies = []
32
+ method_node = find_method_node(result.value, method_name, line_number)
33
+
34
+ if method_node
35
+ visitor = TaskDependencyVisitor.new
36
+ visitor.visit(method_node)
37
+ dependencies = visitor.dependencies
38
+ end
39
+
40
+ dependencies.uniq
41
+ rescue IOError, SystemCallError => e
42
+ warn "Taski: Failed to read file #{file_path}: #{e.message}"
43
+ []
44
+ rescue => e
45
+ warn "Taski: Failed to analyze method #{klass}##{method_name}: #{e.message}"
46
+ []
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def find_method_node(node, method_name, target_line)
53
+ return nil unless node
54
+
55
+ case node
56
+ when Prism::DefNode
57
+ if node.name == method_name && node.location.start_line <= target_line && node.location.end_line >= target_line
58
+ return node
59
+ end
60
+ when Prism::ClassNode, Prism::ModuleNode
61
+ if node.respond_to?(:body)
62
+ return find_method_node(node.body, method_name, target_line)
63
+ end
64
+ when Prism::StatementsNode
65
+ node.body.each do |child|
66
+ result = find_method_node(child, method_name, target_line)
67
+ return result if result
68
+ end
69
+ end
70
+
71
+ # Recursively search child nodes
72
+ if node.respond_to?(:child_nodes)
73
+ node.child_nodes.each do |child|
74
+ result = find_method_node(child, method_name, target_line)
75
+ return result if result
76
+ end
77
+ end
78
+
79
+ nil
80
+ end
81
+
82
+ # Task dependency visitor using Prism's visitor pattern
83
+ class TaskDependencyVisitor < Prism::Visitor
84
+ attr_reader :dependencies
85
+
86
+ def initialize
87
+ @dependencies = []
88
+ @constant_cache = {}
89
+ end
90
+
91
+ def visit_constant_read_node(node)
92
+ check_task_constant(node.name.to_s)
93
+ super
94
+ end
95
+
96
+ def visit_constant_path_node(node)
97
+ const_path = extract_constant_path(node)
98
+ check_task_constant(const_path) if const_path
99
+ super
100
+ end
101
+
102
+ def visit_call_node(node)
103
+ # Check for method calls on constants (e.g., TaskA.result)
104
+ case node.receiver
105
+ when Prism::ConstantReadNode
106
+ check_task_constant(node.receiver.name.to_s)
107
+ when Prism::ConstantPathNode
108
+ const_path = extract_constant_path(node.receiver)
109
+ check_task_constant(const_path) if const_path
110
+ end
111
+ super
112
+ end
113
+
114
+ private
115
+
116
+ def check_task_constant(const_name)
117
+ return unless const_name
118
+
119
+ # Use caching to avoid repeated constant resolution
120
+ cached_result = @constant_cache[const_name]
121
+ return cached_result if cached_result == false # Cached negative result
122
+ return @dependencies << cached_result if cached_result # Cached positive result
123
+
124
+ begin
125
+ if Object.const_defined?(const_name)
126
+ klass = Object.const_get(const_name)
127
+ if klass.is_a?(Class) && klass < Taski::Task
128
+ @constant_cache[const_name] = klass
129
+ @dependencies << klass
130
+ else
131
+ @constant_cache[const_name] = false
132
+ end
133
+ else
134
+ @constant_cache[const_name] = false
135
+ end
136
+ rescue NameError, ArgumentError
137
+ @constant_cache[const_name] = false
138
+ end
139
+ end
140
+
141
+ def extract_constant_path(node)
142
+ case node
143
+ when Prism::ConstantReadNode
144
+ node.name.to_s
145
+ when Prism::ConstantPathNode
146
+ parent_path = extract_constant_path(node.parent) if node.parent
147
+ child_name = node.name.to_s
148
+
149
+ if parent_path && child_name
150
+ "#{parent_path}::#{child_name}"
151
+ else
152
+ child_name
153
+ end
154
+ else
155
+ nil
156
+ end
157
+ end
158
+ end
159
+
160
+ end
161
+ end
162
+ end