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 +4 -4
- data/README.md +52 -4
- data/examples/README.md +13 -1
- data/examples/section_configuration.rb +212 -0
- data/examples/tree_demo.rb +125 -0
- data/lib/taski/dependency_analyzer.rb +65 -38
- data/lib/taski/exceptions.rb +3 -0
- data/lib/taski/logger.rb +7 -62
- data/lib/taski/logging/formatter_factory.rb +34 -0
- data/lib/taski/logging/formatter_interface.rb +19 -0
- data/lib/taski/logging/json_formatter.rb +26 -0
- data/lib/taski/logging/simple_formatter.rb +16 -0
- data/lib/taski/logging/structured_formatter.rb +44 -0
- data/lib/taski/progress/display_colors.rb +17 -0
- data/lib/taski/progress/display_manager.rb +115 -0
- data/lib/taski/progress/output_capture.rb +105 -0
- data/lib/taski/progress/spinner_animation.rb +46 -0
- data/lib/taski/progress/task_formatter.rb +25 -0
- data/lib/taski/progress/task_status.rb +38 -0
- data/lib/taski/progress/terminal_controller.rb +35 -0
- data/lib/taski/progress_display.rb +23 -320
- data/lib/taski/section.rb +268 -0
- data/lib/taski/task/base.rb +11 -32
- data/lib/taski/task/dependency_resolver.rb +4 -64
- data/lib/taski/task/instance_management.rb +28 -15
- data/lib/taski/tree_colors.rb +91 -0
- data/lib/taski/utils/dependency_resolver_helper.rb +85 -0
- data/lib/taski/utils/tree_display_helper.rb +71 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +4 -0
- metadata +18 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c018d3aa27087ea7f87e11e01833ffe9c2e6118cc59af29f7f718b7706d108f
|
4
|
+
data.tar.gz: c18eb90fcfab955b8576b6329ee5df932fcdb8cfed9079c8705656c9e8b90de9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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. **[
|
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
|
data/examples/tree_demo.rb
CHANGED
@@ -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
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
38
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
47
|
-
|
48
|
-
Taski.logger.
|
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
|
-
|
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
|
-
|
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
|
data/lib/taski/exceptions.rb
CHANGED