taski 0.2.3 ā 0.3.1
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 +50 -4
- data/examples/README.md +13 -1
- data/examples/section_configuration.rb +206 -0
- data/examples/tree_demo.rb +125 -0
- data/lib/taski/dependency_analyzer.rb +68 -40
- 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 +117 -0
- data/lib/taski/progress/output_capture.rb +105 -0
- data/lib/taski/progress/spinner_animation.rb +49 -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 +21 -320
- data/lib/taski/section.rb +272 -0
- data/lib/taski/task/base.rb +15 -35
- data/lib/taski/task/define_api.rb +5 -3
- data/lib/taski/task/dependency_resolver.rb +4 -64
- data/lib/taski/task/exports_api.rb +0 -2
- data/lib/taski/task/instance_management.rb +31 -20
- 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 +68 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +11 -7
- 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: 80c881415617aa09fb7ae24441c38c677af816c20f356cc06ea82bba50639820
|
4
|
+
data.tar.gz: cc0c6e07e3f53c312844dc237ebebe344a0201bb772f1c6c20ee8c6476b9db30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e40462d8b2b359c17f28f16ec03524ee649324eb4e21eaf365fd147ccedad6bb3d683ece9021975683b1b121c93ad1da2bdaa0fed82ec37797090fadc79129c
|
7
|
+
data.tar.gz: 441c9553cded529843af032347f4590f9b6a289bd00f3a8ccbbe996b6be43d2154bc6e2fc4f84a94c1f18c35eb4cfcf5408ff07f6b8878b4f50e41c32a407756
|
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,59 @@ 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
|
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
|
+
end
|
126
|
+
|
127
|
+
# Usage is simple - Section works like any Task
|
128
|
+
class App < Taski::Task
|
129
|
+
def build
|
130
|
+
puts "DB: #{DatabaseSection.host}:#{DatabaseSection.port}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
App.build # => DB: localhost:5432
|
135
|
+
```
|
136
|
+
|
100
137
|
### When to Use Each API
|
101
138
|
|
102
139
|
- **Define API**: Best for dynamic runtime dependencies. Cannot contain side effects in definition blocks. Dependencies are analyzed at class definition time, not runtime.
|
103
140
|
- **Exports API**: Ideal for static dependencies. Supports side effects in build methods.
|
141
|
+
- **Section API**: Perfect for abstraction layers where you need different implementations based on runtime conditions while maintaining static analysis capabilities.
|
104
142
|
|
105
143
|
| Use Case | API | Example |
|
106
144
|
|----------|-----|---------|
|
107
145
|
| Configuration values | Exports | File paths, settings |
|
108
|
-
| Environment-specific logic | Define | Different services per env |
|
146
|
+
| Environment-specific logic | Define/Section | Different services per env |
|
109
147
|
| Side effects | Exports | Database connections, I/O |
|
110
148
|
| Conditional processing | Define | Algorithm selection |
|
149
|
+
| Implementation abstraction | Section | Database/API adapters |
|
150
|
+
| Multi-environment configs | Section | Dev/Test/Prod settings |
|
111
151
|
|
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.
|
152
|
+
**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
153
|
|
114
154
|
## ⨠Key Features
|
115
155
|
|
@@ -157,6 +197,11 @@ puts WebServer.tree
|
|
157
197
|
# => āāā Config
|
158
198
|
# => āāā Database
|
159
199
|
# => āāā Cache
|
200
|
+
|
201
|
+
# Sections also appear in dependency trees
|
202
|
+
puts AppServer.tree
|
203
|
+
# => AppServer
|
204
|
+
# => āāā DatabaseSection
|
160
205
|
```
|
161
206
|
|
162
207
|
### Progress Display
|
@@ -309,8 +354,9 @@ bundle exec rake test
|
|
309
354
|
- **Task Base**: Core framework
|
310
355
|
- **Exports API**: Static dependency resolution
|
311
356
|
- **Define API**: Dynamic dependency resolution
|
357
|
+
- **Section API**: Abstraction layer with runtime implementation selection
|
312
358
|
- **Instance Management**: Thread-safe lifecycle
|
313
|
-
- **Dependency Resolver**: Topological sorting
|
359
|
+
- **Dependency Resolver**: Topological sorting with Section support
|
314
360
|
|
315
361
|
## Contributing
|
316
362
|
|
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,206 @@
|
|
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
|
+
end
|
62
|
+
|
63
|
+
# Example 2: API Configuration Section
|
64
|
+
# This section provides API endpoints and credentials
|
65
|
+
class ApiSection < Taski::Section
|
66
|
+
interface :base_url, :api_key, :timeout, :retry_count
|
67
|
+
|
68
|
+
# No 'self' needed - just define as instance method!
|
69
|
+
def impl
|
70
|
+
# Select based on feature flag
|
71
|
+
# Note: Must return a Task class - .build is automatically called
|
72
|
+
if ENV["USE_STAGING_API"] == "true"
|
73
|
+
Staging
|
74
|
+
else
|
75
|
+
Production
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Note: exports are automatically inherited from interface declaration - DRY principle!
|
80
|
+
class Production < Taski::Task
|
81
|
+
def build
|
82
|
+
@base_url = "https://api.example.com/v1"
|
83
|
+
@api_key = ENV["PROD_API_KEY"] || "prod-key-123"
|
84
|
+
@timeout = 30
|
85
|
+
@retry_count = 3
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Note: exports are automatically inherited from interface declaration - DRY principle!
|
90
|
+
class Staging < Taski::Task
|
91
|
+
def build
|
92
|
+
@base_url = "https://staging-api.example.com/v1"
|
93
|
+
@api_key = ENV["STAGING_API_KEY"] || "staging-key-456"
|
94
|
+
@timeout = 60
|
95
|
+
@retry_count = 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Example 3: Task that depends on multiple sections
|
101
|
+
class ApplicationSetup < Taski::Task
|
102
|
+
exports :config_summary
|
103
|
+
|
104
|
+
def build
|
105
|
+
puts "Setting up application with configuration:"
|
106
|
+
puts "Database: #{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
|
107
|
+
puts "API: #{ApiSection.base_url}"
|
108
|
+
puts "Pool size: #{DatabaseSection.pool_size}"
|
109
|
+
puts "API timeout: #{ApiSection.timeout}s"
|
110
|
+
|
111
|
+
@config_summary = {
|
112
|
+
database: {
|
113
|
+
host: DatabaseSection.host,
|
114
|
+
port: DatabaseSection.port,
|
115
|
+
database: DatabaseSection.database_name,
|
116
|
+
pool_size: DatabaseSection.pool_size
|
117
|
+
},
|
118
|
+
api: {
|
119
|
+
base_url: ApiSection.base_url,
|
120
|
+
timeout: ApiSection.timeout,
|
121
|
+
retry_count: ApiSection.retry_count
|
122
|
+
}
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Example 4: Complex dependency chain with sections
|
128
|
+
class DatabaseConnection < Taski::Task
|
129
|
+
exports :connection
|
130
|
+
|
131
|
+
def build
|
132
|
+
puts "Connecting to database..."
|
133
|
+
# Use section configuration to create connection
|
134
|
+
connection_string = "postgresql://#{DatabaseSection.username}:#{DatabaseSection.password}@#{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
|
135
|
+
@connection = "Connected to: #{connection_string} (pool: #{DatabaseSection.pool_size})"
|
136
|
+
puts @connection
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class ApiClient < Taski::Task
|
141
|
+
exports :client
|
142
|
+
|
143
|
+
def build
|
144
|
+
puts "Initializing API client..."
|
145
|
+
@client = "API Client: #{ApiSection.base_url} (timeout: #{ApiSection.timeout}s, retries: #{ApiSection.retry_count})"
|
146
|
+
puts @client
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Application < Taski::Task
|
151
|
+
def build
|
152
|
+
puts "\n=== Starting Application ==="
|
153
|
+
|
154
|
+
# Dependencies are automatically resolved
|
155
|
+
# DatabaseConnection and ApiClient will be built first
|
156
|
+
# which triggers building of their respective sections
|
157
|
+
|
158
|
+
puts "\nDatabase ready: #{DatabaseConnection.connection}"
|
159
|
+
puts "API ready: #{ApiClient.client}"
|
160
|
+
|
161
|
+
puts "\nApplication configuration summary:"
|
162
|
+
puts ApplicationSetup.config_summary.inspect
|
163
|
+
|
164
|
+
puts "\n=== Application Started Successfully ==="
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Demo script
|
169
|
+
if __FILE__ == $0
|
170
|
+
puts "Taski Section Configuration Example"
|
171
|
+
puts "=" * 50
|
172
|
+
|
173
|
+
puts "\n1. Development Environment (default)"
|
174
|
+
ENV["RAILS_ENV"] = "development"
|
175
|
+
ENV["USE_STAGING_API"] = "false"
|
176
|
+
|
177
|
+
# Reset all tasks to ensure fresh build
|
178
|
+
[DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
|
179
|
+
|
180
|
+
Application.build
|
181
|
+
|
182
|
+
puts "\n" + "=" * 50
|
183
|
+
puts "\n2. Production Environment with Staging API"
|
184
|
+
ENV["RAILS_ENV"] = "production"
|
185
|
+
ENV["USE_STAGING_API"] = "true"
|
186
|
+
|
187
|
+
# Reset all tasks to see different configuration
|
188
|
+
[DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
|
189
|
+
|
190
|
+
Application.build
|
191
|
+
|
192
|
+
puts "\n" + "=" * 50
|
193
|
+
puts "\n3. Dependency Tree Visualization"
|
194
|
+
puts "\nApplication dependency tree:"
|
195
|
+
puts Application.tree
|
196
|
+
|
197
|
+
puts "\nDatabaseConnection dependency tree:"
|
198
|
+
puts DatabaseConnection.tree
|
199
|
+
|
200
|
+
puts "\nApiClient dependency tree:"
|
201
|
+
puts ApiClient.tree
|
202
|
+
|
203
|
+
puts "\n" + "=" * 50
|
204
|
+
puts "\nSection dependency resolution successfully demonstrated!"
|
205
|
+
puts "Notice how sections appear in the dependency trees and logs."
|
206
|
+
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
|
@@ -138,15 +165,16 @@ module Taski
|
|
138
165
|
begin
|
139
166
|
resolved_class = nil
|
140
167
|
|
141
|
-
#
|
168
|
+
# Try absolute reference first for performance and clarity
|
142
169
|
if Object.const_defined?(const_name)
|
143
170
|
resolved_class = Object.const_get(const_name)
|
144
|
-
#
|
171
|
+
# Fall back to relative reference for nested module support
|
172
|
+
# This enables tasks defined inside modules to reference siblings
|
145
173
|
elsif @context_class
|
146
174
|
resolved_class = resolve_relative_constant(const_name)
|
147
175
|
end
|
148
176
|
|
149
|
-
if resolved_class&.is_a?(Class) && resolved_class < Taski::Task
|
177
|
+
if resolved_class&.is_a?(Class) && (resolved_class < Taski::Task || resolved_class < Taski::Section)
|
150
178
|
@constant_cache[const_name] = resolved_class
|
151
179
|
@dependencies << resolved_class
|
152
180
|
else
|
data/lib/taski/exceptions.rb
CHANGED