taski 0.3.1 → 0.4.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -271
  3. data/Steepfile +19 -0
  4. data/docs/advanced-features.md +625 -0
  5. data/docs/api-guide.md +509 -0
  6. data/docs/error-handling.md +684 -0
  7. data/examples/README.md +98 -42
  8. data/examples/context_demo.rb +118 -0
  9. data/examples/data_pipeline_demo.rb +231 -0
  10. data/examples/parallel_progress_demo.rb +72 -0
  11. data/examples/quick_start.rb +4 -4
  12. data/examples/reexecution_demo.rb +127 -0
  13. data/examples/{section_configuration.rb → section_demo.rb} +49 -60
  14. data/lib/taski/context.rb +50 -0
  15. data/lib/taski/execution/coordinator.rb +63 -0
  16. data/lib/taski/execution/parallel_progress_display.rb +201 -0
  17. data/lib/taski/execution/registry.rb +72 -0
  18. data/lib/taski/execution/task_wrapper.rb +255 -0
  19. data/lib/taski/section.rb +26 -254
  20. data/lib/taski/static_analysis/analyzer.rb +46 -0
  21. data/lib/taski/static_analysis/dependency_graph.rb +90 -0
  22. data/lib/taski/static_analysis/visitor.rb +130 -0
  23. data/lib/taski/task.rb +199 -0
  24. data/lib/taski/version.rb +1 -1
  25. data/lib/taski.rb +68 -39
  26. data/rbs_collection.lock.yaml +116 -0
  27. data/rbs_collection.yaml +19 -0
  28. data/sig/taski.rbs +269 -62
  29. metadata +36 -32
  30. data/examples/advanced_patterns.rb +0 -119
  31. data/examples/progress_demo.rb +0 -166
  32. data/examples/tree_demo.rb +0 -205
  33. data/lib/taski/dependency_analyzer.rb +0 -232
  34. data/lib/taski/exceptions.rb +0 -17
  35. data/lib/taski/logger.rb +0 -158
  36. data/lib/taski/logging/formatter_factory.rb +0 -34
  37. data/lib/taski/logging/formatter_interface.rb +0 -19
  38. data/lib/taski/logging/json_formatter.rb +0 -26
  39. data/lib/taski/logging/simple_formatter.rb +0 -16
  40. data/lib/taski/logging/structured_formatter.rb +0 -44
  41. data/lib/taski/progress/display_colors.rb +0 -17
  42. data/lib/taski/progress/display_manager.rb +0 -117
  43. data/lib/taski/progress/output_capture.rb +0 -105
  44. data/lib/taski/progress/spinner_animation.rb +0 -49
  45. data/lib/taski/progress/task_formatter.rb +0 -25
  46. data/lib/taski/progress/task_status.rb +0 -38
  47. data/lib/taski/progress/terminal_controller.rb +0 -35
  48. data/lib/taski/progress_display.rb +0 -57
  49. data/lib/taski/reference.rb +0 -40
  50. data/lib/taski/task/base.rb +0 -91
  51. data/lib/taski/task/define_api.rb +0 -156
  52. data/lib/taski/task/dependency_resolver.rb +0 -73
  53. data/lib/taski/task/exports_api.rb +0 -29
  54. data/lib/taski/task/instance_management.rb +0 -201
  55. data/lib/taski/tree_colors.rb +0 -91
  56. data/lib/taski/utils/dependency_resolver_helper.rb +0 -85
  57. data/lib/taski/utils/tree_display_helper.rb +0 -68
  58. data/lib/taski/utils.rb +0 -107
data/docs/api-guide.md ADDED
@@ -0,0 +1,509 @@
1
+ # API Guide
2
+
3
+ This guide provides detailed documentation for Taski's three APIs: Exports, Define, and Section.
4
+
5
+ ## Overview
6
+
7
+ Taski provides three complementary APIs for different dependency scenarios:
8
+
9
+ - **Exports API**: Static dependencies with side effects
10
+ - **Define API**: Dynamic dependencies without side effects
11
+ - **Section API**: Runtime implementation selection
12
+
13
+ ## Exports API - Static Dependencies
14
+
15
+ The Exports API is ideal for static values and operations with side effects.
16
+
17
+ ### Basic Usage
18
+
19
+ ```ruby
20
+ class ConfigLoader < Taski::Task
21
+ exports :app_name, :version, :database_url
22
+
23
+ def run
24
+ @app_name = "MyApp"
25
+ @version = "1.0.0"
26
+ @database_url = "postgresql://localhost/myapp"
27
+ puts "Configuration loaded"
28
+ end
29
+ end
30
+
31
+ class Server < Taski::Task
32
+ def run
33
+ puts "Starting #{ConfigLoader.app_name} v#{ConfigLoader.version}"
34
+ puts "Database: #{ConfigLoader.database_url}"
35
+ end
36
+ end
37
+
38
+ Server.run
39
+ # => Configuration loaded
40
+ # => Starting MyApp v1.0.0
41
+ # => Database: postgresql://localhost/myapp
42
+ ```
43
+
44
+ ### Multiple Dependencies
45
+
46
+ ```ruby
47
+ class Database < Taski::Task
48
+ exports :connection
49
+
50
+ def run
51
+ @connection = "db-connection-#{Time.now.to_i}"
52
+ puts "Database connected"
53
+ end
54
+ end
55
+
56
+ class Cache < Taski::Task
57
+ exports :redis_client
58
+
59
+ def run
60
+ @redis_client = "redis-client-#{Time.now.to_i}"
61
+ puts "Cache connected"
62
+ end
63
+ end
64
+
65
+ class Application < Taski::Task
66
+ def run
67
+ puts "App starting with DB: #{Database.connection}"
68
+ puts "App starting with Cache: #{Cache.redis_client}"
69
+ end
70
+ end
71
+
72
+ Application.run
73
+ # => Database connected
74
+ # => Cache connected
75
+ # => App starting with DB: db-connection-1234567890
76
+ # => App starting with Cache: redis-client-1234567890
77
+ ```
78
+
79
+ ### When to Use Exports API
80
+
81
+ - **Static configuration values**: File paths, settings, constants
82
+ - **Side effects**: Database connections, file I/O, network calls
83
+ - **Predictable dependencies**: When the dependency chain is known at class definition time
84
+
85
+ ## Define API - Dynamic Dependencies
86
+
87
+ The Define API enables runtime-dependent values without side effects.
88
+
89
+ ### Basic Usage
90
+
91
+ ```ruby
92
+ class EnvironmentConfig < Taski::Task
93
+ define :database_host, -> {
94
+ case ENV['RAILS_ENV']
95
+ when 'production'
96
+ 'prod-db.example.com'
97
+ when 'staging'
98
+ 'staging-db.example.com'
99
+ else
100
+ 'localhost'
101
+ end
102
+ }
103
+
104
+ define :redis_url, -> {
105
+ case ENV['RAILS_ENV']
106
+ when 'production'
107
+ 'redis://prod-redis.example.com:6379'
108
+ else
109
+ 'redis://localhost:6379'
110
+ end
111
+ }
112
+
113
+ def run
114
+ puts "Database: #{database_host}"
115
+ puts "Redis: #{redis_url}"
116
+ end
117
+ end
118
+
119
+ ENV['RAILS_ENV'] = 'development'
120
+ EnvironmentConfig.run
121
+ # => Database: localhost
122
+ # => Redis: redis://localhost:6379
123
+
124
+ ENV['RAILS_ENV'] = 'production'
125
+ EnvironmentConfig.reset!
126
+ EnvironmentConfig.run
127
+ # => Database: prod-db.example.com
128
+ # => Redis: redis://prod-redis.example.com:6379
129
+ ```
130
+
131
+ ### Dynamic Dependencies Between Tasks
132
+
133
+ ```ruby
134
+ class FeatureFlags < Taski::Task
135
+ define :use_new_algorithm, -> {
136
+ ENV['USE_NEW_ALGORITHM'] == 'true'
137
+ }
138
+ end
139
+
140
+ class DataProcessor < Taski::Task
141
+ define :algorithm, -> {
142
+ FeatureFlags.use_new_algorithm ? 'v2' : 'v1'
143
+ }
144
+
145
+ def run
146
+ puts "Using algorithm: #{algorithm}"
147
+ end
148
+ end
149
+
150
+ ENV['USE_NEW_ALGORITHM'] = 'false'
151
+ DataProcessor.run
152
+ # => Using algorithm: v1
153
+
154
+ ENV['USE_NEW_ALGORITHM'] = 'true'
155
+ DataProcessor.reset!
156
+ DataProcessor.run
157
+ # => Using algorithm: v2
158
+ ```
159
+
160
+ ### Forward References with ref()
161
+
162
+ Use `ref()` when you need to reference a class that's defined later:
163
+
164
+ ```ruby
165
+ class EarlyTask < Taski::Task
166
+ define :config, -> {
167
+ ref("LaterTask").settings
168
+ }
169
+
170
+ def run
171
+ puts "Early task using: #{config}"
172
+ end
173
+ end
174
+
175
+ class LaterTask < Taski::Task
176
+ exports :settings
177
+
178
+ def run
179
+ @settings = "late-configuration"
180
+ end
181
+ end
182
+
183
+ EarlyTask.run
184
+ # => Early task using: late-configuration
185
+ ```
186
+
187
+ **Important**: Use `ref()` sparingly. Prefer direct class references when possible:
188
+
189
+ ```ruby
190
+ # ✅ Preferred
191
+ define :config, -> { LaterTask.settings }
192
+
193
+ # ⚠️ Only when forward declaration needed
194
+ define :config, -> { ref("LaterTask").settings }
195
+ ```
196
+
197
+ ### When to Use Define API
198
+
199
+ - **Environment-specific logic**: Different behavior per environment
200
+ - **Feature flags**: Conditional functionality
201
+ - **Runtime configuration**: Values that change based on current state
202
+ - **No side effects**: Pure computation only
203
+
204
+ ## Section API - Abstraction Layers
205
+
206
+ The Section API provides runtime implementation selection with static analysis support.
207
+
208
+ ### Basic Usage
209
+
210
+ ```ruby
211
+ class DatabaseSection < Taski::Section
212
+ interface :host, :port, :connection_string
213
+
214
+ def impl
215
+ case ENV['RAILS_ENV']
216
+ when 'production'
217
+ ProductionDB
218
+ when 'test'
219
+ TestDB
220
+ else
221
+ DevelopmentDB
222
+ end
223
+ end
224
+
225
+ class ProductionDB < Taski::Task
226
+ def run
227
+ @host = "prod-db.example.com"
228
+ @port = 5432
229
+ @connection_string = "postgresql://#{@host}:#{@port}/myapp_production"
230
+ end
231
+ end
232
+
233
+ class TestDB < Taski::Task
234
+ def run
235
+ @host = ":memory:"
236
+ @port = nil
237
+ @connection_string = "sqlite3::memory:"
238
+ end
239
+ end
240
+
241
+ class DevelopmentDB < Taski::Task
242
+ def run
243
+ @host = "localhost"
244
+ @port = 5432
245
+ @connection_string = "postgresql://#{@host}:#{@port}/myapp_development"
246
+ end
247
+ end
248
+ end
249
+
250
+ class Application < Taski::Task
251
+ def run
252
+ puts "Connecting to: #{DatabaseSection.connection_string}"
253
+ puts "Host: #{DatabaseSection.host}, Port: #{DatabaseSection.port}"
254
+ end
255
+ end
256
+
257
+ ENV['RAILS_ENV'] = 'development'
258
+ Application.run
259
+ # => Connecting to: postgresql://localhost:5432/myapp_development
260
+ # => Host: localhost, Port: 5432
261
+
262
+ ENV['RAILS_ENV'] = 'production'
263
+ DatabaseSection.reset!
264
+ Application.run
265
+ # => Connecting to: postgresql://prod-db.example.com:5432/myapp_production
266
+ # => Host: prod-db.example.com, Port: 5432
267
+ ```
268
+
269
+ ### Complex Section Hierarchies
270
+
271
+ ```ruby
272
+ class LoggingSection < Taski::Section
273
+ interface :logger, :level
274
+
275
+ def impl
276
+ ENV['LOG_FORMAT'] == 'json' ? JsonLogging : SimpleLogging
277
+ end
278
+
279
+ class JsonLogging < Taski::Task
280
+ def run
281
+ @logger = "JsonLogger"
282
+ @level = "INFO"
283
+ end
284
+ end
285
+
286
+ class SimpleLogging < Taski::Task
287
+ def run
288
+ @logger = "SimpleLogger"
289
+ @level = "DEBUG"
290
+ end
291
+ end
292
+ end
293
+
294
+ class ServiceSection < Taski::Section
295
+ interface :api_endpoint, :timeout
296
+
297
+ def impl
298
+ ENV['SERVICE_MODE'] == 'mock' ? MockService : RealService
299
+ end
300
+
301
+ class MockService < Taski::Task
302
+ def run
303
+ @api_endpoint = "http://localhost:3000/mock"
304
+ @timeout = 1
305
+ end
306
+ end
307
+
308
+ class RealService < Taski::Task
309
+ def run
310
+ @api_endpoint = LoggingSection.level == 'DEBUG' ?
311
+ "https://api-debug.example.com" :
312
+ "https://api.example.com"
313
+ @timeout = 30
314
+ end
315
+ end
316
+ end
317
+
318
+ class MainApp < Taski::Task
319
+ def run
320
+ puts "Logger: #{LoggingSection.logger} (#{LoggingSection.level})"
321
+ puts "API: #{ServiceSection.api_endpoint} (timeout: #{ServiceSection.timeout}s)"
322
+ end
323
+ end
324
+
325
+ ENV['LOG_FORMAT'] = 'simple'
326
+ ENV['SERVICE_MODE'] = 'real'
327
+ MainApp.run
328
+ # => Logger: SimpleLogger (DEBUG)
329
+ # => API: https://api-debug.example.com (timeout: 30s)
330
+ ```
331
+
332
+ ### When to Use Section API
333
+
334
+ - **Implementation abstraction**: Different adapters or strategies
335
+ - **Environment-specific implementations**: Dev/test/prod variations
336
+ - **Conditional implementations**: Based on runtime state
337
+ - **Clean interfaces**: When you want to hide implementation details
338
+
339
+ ## API Comparison
340
+
341
+ | Feature | Exports API | Define API | Section API |
342
+ |---------|-------------|------------|-------------|
343
+ | **Side Effects** | ✅ Allowed | ❌ Not allowed | ✅ Allowed |
344
+ | **Static Analysis** | ✅ Full support | ✅ Full support | ✅ Full support |
345
+ | **Runtime Selection** | ❌ No | ✅ Limited | ✅ Full support |
346
+ | **Interface Definition** | ❌ No | ❌ No | ✅ Yes |
347
+ | **Caching** | ✅ Yes | ✅ Yes | ✅ Yes |
348
+ | **Reset Support** | ✅ Yes | ✅ Yes | ✅ Yes |
349
+
350
+ ## Best Practices
351
+
352
+ ### 1. Choose the Right API
353
+
354
+ ```ruby
355
+ # ✅ Good: Static configuration with Exports
356
+ class Config < Taski::Task
357
+ exports :app_name, :version
358
+ def run
359
+ @app_name = "MyApp"
360
+ @version = "1.0.0"
361
+ end
362
+ end
363
+
364
+ # ✅ Good: Environment logic with Define
365
+ class EnvConfig < Taski::Task
366
+ define :debug_mode, -> { ENV['DEBUG'] == 'true' }
367
+ end
368
+
369
+ # ✅ Good: Implementation selection with Section
370
+ class DatabaseSection < Taski::Section
371
+ interface :connection
372
+ def impl
373
+ production? ? PostgreSQL : SQLite
374
+ end
375
+ end
376
+ ```
377
+
378
+ ### 2. Minimize ref() Usage
379
+
380
+ ```ruby
381
+ # ✅ Preferred: Direct reference
382
+ class TaskB < Taski::Task
383
+ exports :value
384
+ def run; @value = "hello"; end
385
+ end
386
+
387
+ class TaskA < Taski::Task
388
+ define :result, -> { TaskB.value.upcase }
389
+ end
390
+
391
+ # ⚠️ Only when necessary: Forward reference
392
+ class TaskA < Taski::Task
393
+ define :result, -> { ref("TaskB").value.upcase }
394
+ end
395
+
396
+ class TaskB < Taski::Task
397
+ exports :value
398
+ def run; @value = "hello"; end
399
+ end
400
+ ```
401
+
402
+ ### 3. Keep Define Blocks Pure
403
+
404
+ ```ruby
405
+ # ✅ Good: Pure computation
406
+ define :config_path, -> {
407
+ ENV['CONFIG_PATH'] || '/etc/myapp/config.yml'
408
+ }
409
+
410
+ # ❌ Bad: Side effects
411
+ define :config, -> {
412
+ File.read('/etc/config.yml') # File I/O is a side effect
413
+ }
414
+ ```
415
+
416
+ ### 4. Use Clear Interface Definitions
417
+
418
+ ```ruby
419
+ # ✅ Good: Clear interface
420
+ class DatabaseSection < Taski::Section
421
+ interface :host, :port, :username, :password, :database
422
+
423
+ # Implementation classes follow interface
424
+ end
425
+
426
+ # ❌ Bad: Unclear interface
427
+ class DatabaseSection < Taski::Section
428
+ # No interface definition - unclear what's available
429
+ end
430
+ ```
431
+
432
+ ## Advanced Patterns
433
+
434
+ ### Conditional Dependencies
435
+
436
+ ```ruby
437
+ class OptionalService < Taski::Task
438
+ define :enabled, -> { ENV['OPTIONAL_SERVICE'] == 'true' }
439
+
440
+ def run
441
+ if enabled
442
+ puts "Optional service is enabled"
443
+ else
444
+ puts "Optional service is disabled"
445
+ end
446
+ end
447
+ end
448
+ ```
449
+
450
+ ### Dependency Injection
451
+
452
+ ```ruby
453
+ class ServiceSection < Taski::Section
454
+ interface :client
455
+
456
+ def impl
457
+ case ENV['SERVICE_PROVIDER']
458
+ when 'aws' then AWSService
459
+ when 'gcp' then GCPService
460
+ else LocalService
461
+ end
462
+ end
463
+
464
+ class AWSService < Taski::Task
465
+ def run; @client = "AWS Client"; end
466
+ end
467
+
468
+ class GCPService < Taski::Task
469
+ def run; @client = "GCP Client"; end
470
+ end
471
+
472
+ class LocalService < Taski::Task
473
+ def run; @client = "Local Client"; end
474
+ end
475
+ end
476
+ ```
477
+
478
+ ### Mixed API Usage
479
+
480
+ ```ruby
481
+ class Config < Taski::Task
482
+ exports :base_url
483
+ def run; @base_url = "https://api.example.com"; end
484
+ end
485
+
486
+ class ApiSection < Taski::Section
487
+ interface :endpoint
488
+
489
+ def impl
490
+ ENV['API_VERSION'] == 'v2' ? ApiV2 : ApiV1
491
+ end
492
+
493
+ class ApiV1 < Taski::Task
494
+ def run
495
+ @endpoint = "#{Config.base_url}/v1"
496
+ end
497
+ end
498
+
499
+ class ApiV2 < Taski::Task
500
+ define :version_suffix, -> { ENV['BETA'] == 'true' ? '-beta' : '' }
501
+
502
+ def run
503
+ @endpoint = "#{Config.base_url}/v2#{version_suffix}"
504
+ end
505
+ end
506
+ end
507
+ ```
508
+
509
+ This combination shows how all three APIs can work together in a single application.