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
@@ -0,0 +1,625 @@
1
+ # Advanced Features
2
+
3
+ This guide covers Taski's advanced features including progress display, signal handling, logging, and lifecycle management.
4
+
5
+ ## Progress Display
6
+
7
+ Taski provides rich visual feedback during task execution with animated spinners and real-time output capture.
8
+
9
+ ### Basic Progress Display
10
+
11
+ ```ruby
12
+ class LongRunningTask < Taski::Task
13
+ def run
14
+ puts "Starting process..."
15
+ sleep(1.0)
16
+ puts "Processing data..."
17
+ puts "Almost done..."
18
+ sleep(0.5)
19
+ puts "Completed!"
20
+ end
21
+ end
22
+
23
+ LongRunningTask.run
24
+ # During execution shows:
25
+ # ⠧ LongRunningTask
26
+ # Starting process...
27
+ # Processing data...
28
+ # Almost done...
29
+ # Completed!
30
+ #
31
+ # After completion shows:
32
+ # ✅ LongRunningTask (1500ms)
33
+ ```
34
+
35
+ ### Progress Display Features
36
+
37
+ - **Spinner Animation**: Dots-style spinner during task execution
38
+ - **Output Capture**: Real-time display of task output (last 5 lines)
39
+ - **Status Indicators**: ✅ for success, ❌ for failure
40
+ - **Execution Time**: Shows task duration after completion
41
+ - **TTY Detection**: Clean output when redirected to files
42
+
43
+ ### Complex Build Pipeline Example
44
+
45
+ ```ruby
46
+ class CompileAssets < Taski::Task
47
+ def run
48
+ puts "Compiling SCSS..."
49
+ sleep(0.8)
50
+ puts "Processing JavaScript..."
51
+ sleep(1.2)
52
+ puts "Optimizing images..."
53
+ sleep(0.5)
54
+ puts "Assets compiled successfully"
55
+ end
56
+ end
57
+
58
+ class RunTests < Taski::Task
59
+ def run
60
+ puts "Running unit tests..."
61
+ sleep(2.0)
62
+ puts "Running integration tests..."
63
+ sleep(1.5)
64
+ puts "All tests passed"
65
+ end
66
+ end
67
+
68
+ class DeployApp < Taski::Task
69
+ def run
70
+ puts "Building Docker image..."
71
+ CompileAssets.run
72
+ puts "Running test suite..."
73
+ RunTests.run
74
+ puts "Deploying to production..."
75
+ sleep(1.0)
76
+ puts "Deployment complete"
77
+ end
78
+ end
79
+
80
+ DeployApp.run
81
+ # Shows nested progress with clear hierarchy and timing
82
+ ```
83
+
84
+ ### File Output Mode
85
+
86
+ When output is redirected to a file, Taski automatically disables the interactive spinner:
87
+
88
+ ```bash
89
+ # Interactive mode with spinner
90
+ ruby build.rb
91
+
92
+ # Clean output mode for logging
93
+ ruby build.rb > build.log 2>&1
94
+ cat build.log
95
+ # ✅ CompileAssets (800ms)
96
+ # ✅ RunTests (3500ms)
97
+ # ✅ DeployApp (6200ms)
98
+ ```
99
+
100
+ ## Signal Handling
101
+
102
+ Taski supports graceful task interruption with comprehensive signal handling.
103
+
104
+ ### Basic Signal Handling
105
+
106
+ ```ruby
107
+ class LongRunningTask < Taski::Task
108
+ def run
109
+ puts "Starting long operation..."
110
+ perform_long_operation
111
+ rescue Taski::TaskInterruptedException => e
112
+ puts "Task was interrupted: #{e.message}"
113
+ cleanup_resources
114
+ raise # Re-raise to maintain proper error handling
115
+ end
116
+
117
+ private
118
+
119
+ def perform_long_operation
120
+ # Long running operation that can be interrupted
121
+ 100.times do |i|
122
+ puts "Processing item #{i + 1}/100..."
123
+ sleep(0.1) # Simulated work
124
+ end
125
+ end
126
+
127
+ def cleanup_resources
128
+ puts "Cleaning up temporary files..."
129
+ puts "Closing database connections..."
130
+ puts "Cleanup complete"
131
+ end
132
+ end
133
+
134
+ # Task can be interrupted with Ctrl+C (SIGINT) or SIGTERM
135
+ LongRunningTask.run
136
+ ```
137
+
138
+ ### Signal Handling Features
139
+
140
+ - **Multiple Signals**: Supports INT, TERM, USR1, USR2
141
+ - **Exception Conversion**: Converts signals to TaskInterruptedException
142
+ - **Test Environment**: Automatically disabled in test environments
143
+ - **Graceful Shutdown**: Allows cleanup before termination
144
+
145
+ ### Advanced Signal Handling
146
+
147
+ ```ruby
148
+ class DatabaseMigration < Taski::Task
149
+ def run
150
+ puts "Starting database migration..."
151
+
152
+ begin
153
+ migrate_tables
154
+ rescue Taski::TaskInterruptedException => e
155
+ puts "Migration interrupted: #{e.message}"
156
+ rollback_partial_changes
157
+ raise
158
+ end
159
+
160
+ puts "Migration completed successfully"
161
+ end
162
+
163
+ private
164
+
165
+ def migrate_tables
166
+ %w[users orders products].each do |table|
167
+ puts "Migrating #{table} table..."
168
+ sleep(2) # Simulated migration time
169
+ end
170
+ end
171
+
172
+ def rollback_partial_changes
173
+ puts "Rolling back partial migration..."
174
+ puts "Database restored to consistent state"
175
+ end
176
+ end
177
+ ```
178
+
179
+ ## Error Recovery
180
+
181
+ Taski provides sophisticated error recovery mechanisms for handling dependency failures.
182
+
183
+ ### Basic Error Recovery
184
+
185
+ ```ruby
186
+ class DatabaseTask < Taski::Task
187
+ exports :connection
188
+
189
+ def run
190
+ # This might fail in test environments
191
+ @connection = connect_to_database
192
+ end
193
+
194
+ private
195
+
196
+ def connect_to_database
197
+ raise "Database connection failed" if ENV['FAIL_DB'] == 'true'
198
+ "postgresql://localhost/myapp"
199
+ end
200
+ end
201
+
202
+ class AppTask < Taski::Task
203
+ # Rescue dependency errors and provide fallback
204
+ rescue_deps StandardError, -> { "fallback://localhost/sqlite" }
205
+
206
+ def run
207
+ # Uses DatabaseTask.connection, or fallback if DatabaseTask fails
208
+ connection = DatabaseTask.connection
209
+ puts "Using connection: #{connection}"
210
+ end
211
+ end
212
+
213
+ ENV['FAIL_DB'] = 'true'
214
+ AppTask.run
215
+ # => Using connection: fallback://localhost/sqlite
216
+ ```
217
+
218
+ ### Multiple Rescue Strategies
219
+
220
+ ```ruby
221
+ class ExternalApiTask < Taski::Task
222
+ exports :data
223
+
224
+ def run
225
+ @data = fetch_from_api
226
+ end
227
+
228
+ private
229
+
230
+ def fetch_from_api
231
+ raise "API unavailable" if ENV['API_DOWN'] == 'true'
232
+ { users: 100, orders: 50 }
233
+ end
234
+ end
235
+
236
+ class CacheTask < Taski::Task
237
+ exports :cached_data
238
+
239
+ def run
240
+ @cached_data = load_from_cache
241
+ end
242
+
243
+ private
244
+
245
+ def load_from_cache
246
+ raise "Cache miss" if ENV['CACHE_MISS'] == 'true'
247
+ { users: 95, orders: 48 } # Slightly stale data
248
+ end
249
+ end
250
+
251
+ class DataAggregator < Taski::Task
252
+ # Try API first, then cache, then static fallback
253
+ rescue_deps StandardError, -> { ExternalApiTask.data }
254
+ rescue_deps StandardError, -> { CacheTask.cached_data }
255
+ rescue_deps StandardError, -> { { users: 0, orders: 0 } }
256
+
257
+ def run
258
+ data = ExternalApiTask.data
259
+ puts "Data aggregated: #{data}"
260
+ end
261
+ end
262
+
263
+ # Test different failure scenarios
264
+ ENV['API_DOWN'] = 'true'
265
+ DataAggregator.run
266
+ # => Data aggregated: {users: 95, orders: 48} # Falls back to cache
267
+
268
+ ENV['CACHE_MISS'] = 'true'
269
+ DataAggregator.reset!
270
+ DataAggregator.run
271
+ # => Data aggregated: {users: 0, orders: 0} # Falls back to static data
272
+ ```
273
+
274
+ ### Error Recovery Features
275
+
276
+ - **Dependency Rescue**: Automatic fallback for failed dependencies
277
+ - **Custom Handlers**: Lambda-based error handling
278
+ - **Chain of Responsibility**: Multiple rescue strategies
279
+ - **Transparent Fallback**: Seamless error recovery
280
+
281
+ ## Advanced Logging
282
+
283
+ Taski provides comprehensive logging with multiple formats and detailed performance monitoring.
284
+
285
+ ### Logging Configuration
286
+
287
+ ```ruby
288
+ # Configure logging level
289
+ Taski.logger.level = Logger::DEBUG
290
+
291
+ # Configure logging format
292
+ Taski.logger.formatter = Taski::Logging::StructuredFormatter.new
293
+
294
+ # Available formatters:
295
+ # - SimpleFormatter (default)
296
+ # - StructuredFormatter
297
+ # - JsonFormatter
298
+ ```
299
+
300
+ ### Structured Logging
301
+
302
+ ```ruby
303
+ class MonitoredTask < Taski::Task
304
+ def run
305
+ Taski.logger.info "Task starting",
306
+ task: self.class.name,
307
+ environment: ENV['RAILS_ENV']
308
+
309
+ perform_work
310
+
311
+ Taski.logger.info "Task completed",
312
+ duration: "1.2s",
313
+ success: true
314
+ end
315
+
316
+ private
317
+
318
+ def perform_work
319
+ Taski.logger.debug "Processing data",
320
+ items_count: 100,
321
+ batch_size: 10
322
+
323
+ # Simulated work
324
+ sleep(1.2)
325
+ end
326
+ end
327
+
328
+ MonitoredTask.run
329
+ # Structured output:
330
+ # [INFO] Task starting (task=MonitoredTask, environment=development)
331
+ # [DEBUG] Processing data (items_count=100, batch_size=10)
332
+ # [INFO] Task completed (duration=1.2s, success=true)
333
+ ```
334
+
335
+ ### JSON Logging
336
+
337
+ ```ruby
338
+ # Configure JSON formatter for machine-readable logs
339
+ Taski.logger.formatter = Taski::Logging::JsonFormatter.new
340
+
341
+ class ApiTask < Taski::Task
342
+ exports :response_data
343
+
344
+ def run
345
+ Taski.logger.info "API request starting",
346
+ endpoint: "/api/users",
347
+ method: "GET"
348
+
349
+ @response_data = { users: 42 }
350
+
351
+ Taski.logger.info "API request completed",
352
+ endpoint: "/api/users",
353
+ status: 200,
354
+ response_size: @response_data.to_json.size
355
+ end
356
+ end
357
+
358
+ ApiTask.run
359
+ # JSON output:
360
+ # {"timestamp":"2024-01-01T12:00:00Z","level":"INFO","message":"API request starting","endpoint":"/api/users","method":"GET"}
361
+ # {"timestamp":"2024-01-01T12:00:01Z","level":"INFO","message":"API request completed","endpoint":"/api/users","status":200,"response_size":12}
362
+ ```
363
+
364
+ ### Performance Monitoring
365
+
366
+ ```ruby
367
+ class PerformanceTask < Taski::Task
368
+ def run
369
+ # Automatic performance logging is built-in
370
+ slow_operation
371
+ fast_operation
372
+ end
373
+
374
+ private
375
+
376
+ def slow_operation
377
+ Taski.logger.debug "Starting slow operation"
378
+ sleep(2.0)
379
+ Taski.logger.debug "Slow operation completed"
380
+ end
381
+
382
+ def fast_operation
383
+ Taski.logger.debug "Starting fast operation"
384
+ sleep(0.1)
385
+ Taski.logger.debug "Fast operation completed"
386
+ end
387
+ end
388
+
389
+ PerformanceTask.run
390
+ # Automatic timing logs:
391
+ # [INFO] Task build started (task=PerformanceTask, dependencies=0)
392
+ # [DEBUG] Starting slow operation
393
+ # [DEBUG] Slow operation completed
394
+ # [DEBUG] Starting fast operation
395
+ # [DEBUG] Fast operation completed
396
+ # [INFO] Task build completed (task=PerformanceTask, duration_ms=2100)
397
+ ```
398
+
399
+ ## Lifecycle Management
400
+
401
+ Taski supports comprehensive lifecycle management with run and clean methods.
402
+
403
+ ### Basic Lifecycle
404
+
405
+ ```ruby
406
+ class DatabaseSetup < Taski::Task
407
+ exports :connection
408
+
409
+ def run
410
+ @connection = "postgresql://localhost:5432/myapp"
411
+ puts "Database connected"
412
+ end
413
+
414
+ def clean
415
+ puts "Database disconnected"
416
+ # Cleanup logic here
417
+ end
418
+ end
419
+
420
+ class WebServer < Taski::Task
421
+ def run
422
+ puts "Web server started with #{DatabaseSetup.connection}"
423
+ end
424
+
425
+ def clean
426
+ puts "Web server stopped"
427
+ end
428
+ end
429
+
430
+ # Start everything
431
+ WebServer.run
432
+ # => Database connected
433
+ # => Web server started with postgresql://localhost:5432/myapp
434
+
435
+ # Clean everything in reverse order
436
+ WebServer.clean
437
+ # => Web server stopped
438
+ # => Database disconnected
439
+ ```
440
+
441
+ ### Advanced Lifecycle with Resource Management
442
+
443
+ ```ruby
444
+ class FileProcessor < Taski::Task
445
+ exports :output_file, :temp_dir
446
+
447
+ def run
448
+ @temp_dir = "/tmp/processing_#{Time.now.to_i}"
449
+ Dir.mkdir(@temp_dir)
450
+ puts "Created temp directory: #{@temp_dir}"
451
+
452
+ @output_file = "#{@temp_dir}/result.txt"
453
+ File.write(@output_file, "Processing complete")
454
+ puts "Created output file: #{@output_file}"
455
+ end
456
+
457
+ def clean
458
+ if @output_file && File.exist?(@output_file)
459
+ File.delete(@output_file)
460
+ puts "Deleted output file: #{@output_file}"
461
+ end
462
+
463
+ if @temp_dir && Dir.exist?(@temp_dir)
464
+ Dir.rmdir(@temp_dir)
465
+ puts "Deleted temp directory: #{@temp_dir}"
466
+ end
467
+ end
468
+ end
469
+
470
+ class ReportGenerator < Taski::Task
471
+ def run
472
+ data = File.read(FileProcessor.output_file)
473
+ puts "Generated report from: #{data}"
474
+ end
475
+
476
+ def clean
477
+ puts "Report generation cleaned up"
478
+ end
479
+ end
480
+
481
+ # Usage
482
+ ReportGenerator.run
483
+ # => Created temp directory: /tmp/processing_1234567890
484
+ # => Created output file: /tmp/processing_1234567890/result.txt
485
+ # => Generated report from: Processing complete
486
+
487
+ ReportGenerator.clean
488
+ # => Report generation cleaned up
489
+ # => Deleted output file: /tmp/processing_1234567890/result.txt
490
+ # => Deleted temp directory: /tmp/processing_1234567890
491
+ ```
492
+
493
+ ### Idempotent Clean Methods
494
+
495
+ **Important**: Clean methods must be idempotent - safe to call multiple times:
496
+
497
+ ```ruby
498
+ class SafeFileTask < Taski::Task
499
+ exports :data_file
500
+
501
+ def run
502
+ @data_file = '/tmp/safe_data.txt'
503
+ File.write(@data_file, 'Important data')
504
+ end
505
+
506
+ def clean
507
+ # ✅ Good: Check before delete
508
+ if @data_file && File.exist?(@data_file)
509
+ File.delete(@data_file)
510
+ puts "Deleted #{@data_file}"
511
+ else
512
+ puts "File #{@data_file} already deleted or doesn't exist"
513
+ end
514
+ end
515
+ end
516
+
517
+ # Safe to call multiple times
518
+ task = SafeFileTask.new
519
+ task.run
520
+ task.clean # => Deleted /tmp/safe_data.txt
521
+ task.clean # => File /tmp/safe_data.txt already deleted or doesn't exist
522
+ ```
523
+
524
+ ## Dependency Tree Visualization
525
+
526
+ Visualize complex dependency relationships with the tree method:
527
+
528
+ ```ruby
529
+ class Database < Taski::Task
530
+ exports :connection
531
+ def run; @connection = "db-conn"; end
532
+ end
533
+
534
+ class Cache < Taski::Task
535
+ exports :redis_url
536
+ def run; @redis_url = "redis://localhost"; end
537
+ end
538
+
539
+ class Config < Taski::Task
540
+ exports :settings
541
+ def run
542
+ @settings = {
543
+ database: Database.connection,
544
+ cache: Cache.redis_url
545
+ }
546
+ end
547
+ end
548
+
549
+ class WebServer < Taski::Task
550
+ def run
551
+ puts "Starting with #{Config.settings}"
552
+ end
553
+ end
554
+
555
+ puts WebServer.tree
556
+ # => WebServer
557
+ # => └── Config
558
+ # => ├── Database
559
+ # => └── Cache
560
+
561
+ # Complex hierarchies are clearly visualized
562
+ class ApiGateway < Taski::Task
563
+ def run
564
+ puts "Gateway starting with #{Config.settings}"
565
+ puts "Using cache: #{Cache.redis_url}"
566
+ end
567
+ end
568
+
569
+ puts ApiGateway.tree
570
+ # => ApiGateway
571
+ # => ├── Config
572
+ # => │ ├── Database
573
+ # => │ └── Cache
574
+ # => └── Cache
575
+ ```
576
+
577
+ ## Performance Tips
578
+
579
+ ### 1. Use Appropriate Reset Strategies
580
+
581
+ ```ruby
582
+ # Reset specific tasks when needed
583
+ DatabaseTask.reset! # Only resets this task
584
+ AppTask.reset! # Resets this task and triggers dependency rebuilds
585
+
586
+ # Reset entire dependency tree
587
+ Taski::Task.reset_all! # Nuclear option - resets everything
588
+ ```
589
+
590
+ ### 2. Optimize Long-Running Tasks
591
+
592
+ ```ruby
593
+ class OptimizedTask < Taski::Task
594
+ def run
595
+ # Break long operations into chunks for better progress display
596
+ (1..100).each_slice(10) do |batch|
597
+ puts "Processing batch: #{batch.first}-#{batch.last}"
598
+ process_batch(batch)
599
+ end
600
+ end
601
+
602
+ private
603
+
604
+ def process_batch(items)
605
+ # Process items in batch
606
+ sleep(0.1) # Simulated work
607
+ end
608
+ end
609
+ ```
610
+
611
+ ### 3. Use Structured Logging for Debugging
612
+
613
+ ```ruby
614
+ class DebuggableTask < Taski::Task
615
+ def run
616
+ Taski.logger.debug "Task configuration",
617
+ config: get_config,
618
+ dependencies: self.class.dependencies.map(&:name)
619
+
620
+ perform_work
621
+ end
622
+ end
623
+ ```
624
+
625
+ This comprehensive guide covers all of Taski's advanced features. Each feature is designed to work seamlessly together, providing a robust foundation for complex task orchestration.