taski 0.4.2 → 0.7.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +51 -33
  4. data/Steepfile +1 -0
  5. data/docs/GUIDE.md +340 -0
  6. data/examples/README.md +68 -20
  7. data/examples/{context_demo.rb → args_demo.rb} +27 -27
  8. data/examples/clean_demo.rb +204 -0
  9. data/examples/data_pipeline_demo.rb +3 -3
  10. data/examples/group_demo.rb +113 -0
  11. data/examples/nested_section_demo.rb +161 -0
  12. data/examples/parallel_progress_demo.rb +1 -1
  13. data/examples/reexecution_demo.rb +93 -80
  14. data/examples/system_call_demo.rb +56 -0
  15. data/examples/tree_progress_demo.rb +164 -0
  16. data/lib/taski/{context.rb → args.rb} +3 -3
  17. data/lib/taski/execution/execution_context.rb +379 -0
  18. data/lib/taski/execution/executor.rb +538 -0
  19. data/lib/taski/execution/registry.rb +26 -2
  20. data/lib/taski/execution/scheduler.rb +308 -0
  21. data/lib/taski/execution/task_output_pipe.rb +42 -0
  22. data/lib/taski/execution/task_output_router.rb +216 -0
  23. data/lib/taski/execution/task_wrapper.rb +295 -146
  24. data/lib/taski/execution/tree_progress_display.rb +793 -0
  25. data/lib/taski/execution/worker_pool.rb +104 -0
  26. data/lib/taski/section.rb +23 -0
  27. data/lib/taski/static_analysis/analyzer.rb +4 -2
  28. data/lib/taski/static_analysis/visitor.rb +86 -5
  29. data/lib/taski/task.rb +223 -120
  30. data/lib/taski/version.rb +1 -1
  31. data/lib/taski.rb +147 -28
  32. data/sig/taski.rbs +310 -67
  33. metadata +17 -8
  34. data/docs/advanced-features.md +0 -625
  35. data/docs/api-guide.md +0 -509
  36. data/docs/error-handling.md +0 -684
  37. data/lib/taski/execution/coordinator.rb +0 -63
  38. data/lib/taski/execution/parallel_progress_display.rb +0 -201
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Taski Re-execution Demo
4
+ # Taski Scope-Based Execution Demo
5
5
  #
6
- # This example demonstrates cache control and re-execution:
7
- # - Default caching behavior
8
- # - Task.new for fresh instances
9
- # - Task.reset! for clearing all caches
6
+ # This example demonstrates the execution model:
7
+ # - Task.run / Task.value: Fresh execution every time
8
+ # - Task.new.run: Instance-level caching
9
+ # - Dependencies within same execution scope share results
10
10
  #
11
- # Run: ruby examples/reexecution_demo.rb
11
+ # Run: TASKI_PROGRESS_DISABLE=1 ruby examples/reexecution_demo.rb
12
12
 
13
13
  require_relative "../lib/taski"
14
14
 
15
- puts "Taski Re-execution Demo"
16
- puts "=" * 40
15
+ puts "Taski Scope-Based Execution Demo"
16
+ puts "=" * 50
17
17
 
18
- # Task that generates random values (to demonstrate caching)
18
+ # Task that generates random values (to demonstrate execution behavior)
19
19
  class RandomGenerator < Taski::Task
20
20
  exports :value, :timestamp
21
21
 
@@ -23,6 +23,7 @@ class RandomGenerator < Taski::Task
23
23
  @value = rand(1000)
24
24
  @timestamp = Time.now.strftime("%H:%M:%S.%L")
25
25
  puts " RandomGenerator.run called: value=#{@value}, time=#{@timestamp}"
26
+ @value
26
27
  end
27
28
  end
28
29
 
@@ -32,96 +33,108 @@ class Consumer < Taski::Task
32
33
 
33
34
  def run
34
35
  random_value = RandomGenerator.value
35
- @result = "Consumed value: #{random_value}"
36
- puts " Consumer.run called: #{@result}"
36
+ @result = "Consumed: #{random_value}"
37
+ puts " Consumer.run called with RandomGenerator.value=#{random_value}"
38
+ @result
37
39
  end
38
40
  end
39
41
 
40
- puts "\n1. Default Caching Behavior"
41
- puts "-" * 40
42
- puts "First call to RandomGenerator.value:"
42
+ # Task that accesses RandomGenerator twice
43
+ class DoubleConsumer < Taski::Task
44
+ exports :first_value, :second_value
45
+
46
+ def run
47
+ @first_value = RandomGenerator.value
48
+ puts " DoubleConsumer: first access = #{@first_value}"
49
+ @second_value = RandomGenerator.value
50
+ puts " DoubleConsumer: second access = #{@second_value}"
51
+ end
52
+ end
53
+
54
+ puts "\n1. Class Method Calls: Fresh Execution Every Time"
55
+ puts "-" * 50
56
+ puts "Each Task.value call creates a NEW execution:"
57
+ puts "\nFirst call:"
43
58
  value1 = RandomGenerator.value
44
59
  puts " => #{value1}"
45
60
 
46
- puts "\nSecond call to RandomGenerator.value (cached, no run):"
61
+ puts "\nSecond call (NEW execution, different value):"
47
62
  value2 = RandomGenerator.value
48
63
  puts " => #{value2}"
49
64
 
50
- puts "\nValues are identical: #{value1 == value2}"
65
+ puts "\nValues are different: #{value1 != value2}"
66
+
67
+ puts "\n" + "=" * 50
68
+ puts "\n2. Task.new: Instance-Level Caching"
69
+ puts "-" * 50
70
+ puts "Same instance caches the result:"
71
+
72
+ instance = RandomGenerator.new
73
+ puts "\nFirst run on instance:"
74
+ result1 = instance.run
75
+ puts " instance.value = #{instance.value}"
76
+
77
+ puts "\nSecond run on same instance (returns cached):"
78
+ result2 = instance.run
79
+ puts " instance.value = #{instance.value}"
80
+
81
+ puts "\nSame result: #{result1 == result2}"
51
82
 
52
- puts "\n" + "=" * 40
53
- puts "\n2. Using Task.new for Fresh Instance"
54
- puts "-" * 40
55
- puts "Creating new instance with RandomGenerator.new:"
83
+ puts "\n" + "=" * 50
84
+ puts "\n3. Different Instances: Independent Executions"
85
+ puts "-" * 50
56
86
 
57
87
  instance1 = RandomGenerator.new
58
88
  instance1.run
59
- puts " instance1.value = #{instance1.value}"
89
+ puts "instance1.value = #{instance1.value}"
60
90
 
61
91
  instance2 = RandomGenerator.new
62
92
  instance2.run
63
- puts " instance2.value = #{instance2.value}"
64
-
65
- puts "\nNote: Each .new creates independent instance"
66
- puts "Class-level cache unchanged: RandomGenerator.value = #{RandomGenerator.value}"
67
-
68
- puts "\n" + "=" * 40
69
- puts "\n3. Using reset! to Clear Cache"
70
- puts "-" * 40
71
- puts "Before reset!:"
72
- puts " RandomGenerator.value = #{RandomGenerator.value}"
73
-
74
- puts "\nCalling RandomGenerator.reset!..."
75
- RandomGenerator.reset!
76
-
77
- puts "\nAfter reset! (fresh execution):"
78
- new_value = RandomGenerator.value
79
- puts " RandomGenerator.value = #{new_value}"
80
-
81
- puts "\n" + "=" * 40
82
- puts "\n4. Dependency Chain with Re-execution"
83
- puts "-" * 40
84
-
85
- # Reset both tasks
86
- RandomGenerator.reset!
87
- Consumer.reset!
88
-
89
- puts "First Consumer execution:"
90
- result1 = Consumer.result
91
- puts " => #{result1}"
92
-
93
- puts "\nSecond Consumer execution (cached):"
94
- result2 = Consumer.result
95
- puts " => #{result2}"
96
-
97
- puts "\nReset Consumer and re-execute:"
98
- Consumer.reset!
99
- result3 = Consumer.result
100
- puts " => #{result3}"
101
- puts " (Dependencies are re-resolved when task is reset)"
102
-
103
- puts "\nReset both tasks:"
104
- RandomGenerator.reset!
105
- Consumer.reset!
106
- result4 = Consumer.result
107
- puts " => #{result4}"
108
- puts " (New random value because both were reset)"
109
-
110
- puts "\n" + "=" * 40
111
- puts "\n5. Use Cases Summary"
112
- puts "-" * 40
93
+ puts "instance2.value = #{instance2.value}"
94
+
95
+ puts "\nDifferent values: #{instance1.value != instance2.value}"
96
+
97
+ puts "\n" + "=" * 50
98
+ puts "\n4. Scope-Based Caching for Dependencies"
99
+ puts "-" * 50
100
+ puts "Within ONE execution, dependencies are cached:"
101
+
102
+ puts "\nDoubleConsumer accesses RandomGenerator.value twice:"
103
+ DoubleConsumer.run
104
+
105
+ puts "\nNote: Both accesses return the SAME value!"
106
+ puts "(Because they're in the same execution scope)"
107
+
108
+ puts "\n" + "=" * 50
109
+ puts "\n5. Dependency Chain with Fresh Execution"
110
+ puts "-" * 50
111
+
112
+ puts "Each Consumer.run creates fresh RandomGenerator:"
113
+ puts "\nFirst Consumer.run:"
114
+ Consumer.run
115
+
116
+ puts "\nSecond Consumer.run (different RandomGenerator value):"
117
+ Consumer.run
118
+
119
+ puts "\n" + "=" * 50
120
+ puts "\n6. Use Cases Summary"
121
+ puts "-" * 50
113
122
  puts <<~SUMMARY
114
123
  TaskClass.run / TaskClass.value
115
- => Normal execution with caching (recommended for dependency graphs)
124
+ => Fresh execution every time
125
+ => Dependencies within same execution are cached
126
+ => Use for: Independent executions, scripts
116
127
 
117
128
  TaskClass.new.run
118
- => Re-execute only this task (dependencies still use cache)
119
- => Useful for: testing, one-off executions
120
-
121
- TaskClass.reset!
122
- => Clear this task's cache, next call will re-execute
123
- => Useful for: environment changes, refreshing data
129
+ => Instance caches results
130
+ => Multiple .run calls return cached value
131
+ => Use for: Re-execution control, testing
132
+
133
+ instance.reset!
134
+ => Clears instance cache
135
+ => Next .run will execute fresh
136
+ => Use for: Re-running same instance
124
137
  SUMMARY
125
138
 
126
- puts "\n" + "=" * 40
127
- puts "Re-execution demonstration complete!"
139
+ puts "\n" + "=" * 50
140
+ puts "Scope-Based Execution demonstration complete!"
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/taski"
5
+
6
+ # Demo: Subprocess output capture with system()
7
+ #
8
+ # This example demonstrates how Taski captures output from system() calls
9
+ # and displays them in the progress spinner.
10
+ # Run with: ruby examples/system_call_demo.rb
11
+
12
+ class SlowOutputTask < Taski::Task
13
+ exports :success
14
+
15
+ def run
16
+ puts "Running command with streaming output..."
17
+ # Use a command that produces output over time
18
+ @success = system("for i in 1 2 3 4 5; do echo \"Processing step $i...\"; sleep 0.3; done")
19
+ end
20
+ end
21
+
22
+ class AnotherSlowTask < Taski::Task
23
+ exports :result
24
+
25
+ def run
26
+ puts "Running another slow command..."
27
+ @result = system("for i in A B C; do echo \"Stage $i complete\"; sleep 0.4; done")
28
+ end
29
+ end
30
+
31
+ class MainTask < Taski::Task
32
+ exports :summary
33
+
34
+ def run
35
+ puts "Starting main task..."
36
+ slow1 = SlowOutputTask.success
37
+ slow2 = AnotherSlowTask.result
38
+ @summary = {slow1: slow1, slow2: slow2}
39
+ puts "All done!"
40
+ end
41
+ end
42
+
43
+ puts "=" * 60
44
+ puts "Subprocess Output Capture Demo"
45
+ puts "Watch the spinner show system() output in real-time!"
46
+ puts "=" * 60
47
+ puts
48
+
49
+ Taski.progress_display&.start
50
+ result = MainTask.summary
51
+ Taski.progress_display&.stop
52
+
53
+ puts
54
+ puts "=" * 60
55
+ puts "Result: #{result.inspect}"
56
+ puts "=" * 60
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/taski"
5
+
6
+ # Demo for tree-based progress display
7
+ # Run with: ruby examples/tree_progress_demo.rb
8
+
9
+ # Database configuration section with multiple impl candidates
10
+ class DatabaseSection < Taski::Section
11
+ interfaces :connection_string
12
+
13
+ def impl
14
+ if ENV["USE_PROD_DB"] == "1"
15
+ ProductionDB
16
+ else
17
+ DevelopmentDB
18
+ end
19
+ end
20
+
21
+ class ProductionDB < Taski::Task
22
+ def run
23
+ puts "Connecting to production database..."
24
+ sleep(0.5)
25
+ puts "Production DB connected"
26
+ @connection_string = "postgresql://prod-server:5432/myapp"
27
+ end
28
+ end
29
+
30
+ class DevelopmentDB < Taski::Task
31
+ def run
32
+ puts "Connecting to development database..."
33
+ sleep(0.3)
34
+ puts "Development DB connected"
35
+ @connection_string = "postgresql://localhost:5432/myapp_dev"
36
+ end
37
+ end
38
+ end
39
+
40
+ # API section with multiple impl candidates
41
+ class ApiSection < Taski::Section
42
+ interfaces :base_url
43
+
44
+ def impl
45
+ if ENV["USE_STAGING_API"] == "1"
46
+ StagingApi
47
+ else
48
+ ProductionApi
49
+ end
50
+ end
51
+
52
+ class ProductionApi < Taski::Task
53
+ def run
54
+ puts "Initializing production API..."
55
+ sleep(0.4)
56
+ @base_url = "https://api.example.com"
57
+ end
58
+ end
59
+
60
+ class StagingApi < Taski::Task
61
+ def run
62
+ puts "Initializing staging API..."
63
+ sleep(0.2)
64
+ @base_url = "https://staging.api.example.com"
65
+ end
66
+ end
67
+ end
68
+
69
+ # Task with stdout output
70
+ class FetchUserData < Taski::Task
71
+ exports :users
72
+
73
+ def run
74
+ puts "Fetching users from database..."
75
+ sleep(0.3)
76
+ puts "Found 100 users"
77
+ sleep(0.2)
78
+ puts "Processing user records..."
79
+ sleep(0.3)
80
+ puts "User data ready"
81
+ @users = ["Alice", "Bob", "Charlie"]
82
+ end
83
+ end
84
+
85
+ class FetchProductData < Taski::Task
86
+ exports :products
87
+
88
+ def run
89
+ puts "Loading product catalog..."
90
+ sleep(0.4)
91
+ puts "Fetched 50 products"
92
+ sleep(0.2)
93
+ puts "Indexing products..."
94
+ sleep(0.3)
95
+ @products = ["Widget", "Gadget", "Thing"]
96
+ end
97
+ end
98
+
99
+ class BuildReport < Taski::Task
100
+ exports :report
101
+
102
+ def run
103
+ db = DatabaseSection.connection_string
104
+ api = ApiSection.base_url
105
+ users = FetchUserData.users
106
+ products = FetchProductData.products
107
+
108
+ puts "Building report..."
109
+ sleep(0.2)
110
+ puts "Aggregating data from #{users.size} users..."
111
+ sleep(0.3)
112
+ puts "Processing #{products.size} products..."
113
+ sleep(0.2)
114
+ puts "Report generated successfully"
115
+
116
+ @report = {
117
+ database: db,
118
+ api: api,
119
+ user_count: users.size,
120
+ product_count: products.size
121
+ }
122
+ end
123
+ end
124
+
125
+ class SendNotification < Taski::Task
126
+ exports :notification_sent
127
+
128
+ def run
129
+ BuildReport.report
130
+ puts "Sending notification..."
131
+ sleep(0.2)
132
+ puts "Email sent to admin@example.com"
133
+ @notification_sent = true
134
+ end
135
+ end
136
+
137
+ class Application < Taski::Task
138
+ exports :status
139
+
140
+ def run
141
+ notification = SendNotification.notification_sent
142
+ puts "Application startup complete"
143
+ @status = notification ? "success" : "failed"
144
+ end
145
+ end
146
+
147
+ # Show tree structure before execution
148
+ puts "Task Tree Structure:"
149
+ puts "=" * 60
150
+ puts Application.tree
151
+ puts "=" * 60
152
+ puts
153
+
154
+ # Reset for execution
155
+ Application.reset!
156
+
157
+ # Execute with tree progress display (start/stop handled automatically by Executor)
158
+ result = Application.status
159
+
160
+ puts "\n"
161
+ puts "=" * 60
162
+ puts "Execution completed!"
163
+ puts "Status: #{result}"
164
+ puts "Report: #{BuildReport.report.inspect}"
@@ -3,10 +3,10 @@
3
3
  require "monitor"
4
4
 
5
5
  module Taski
6
- # Runtime context accessible from any task.
6
+ # Runtime arguments accessible from any task.
7
7
  # Holds user-defined options and execution metadata.
8
- # Context is immutable after creation - options cannot be modified during task execution.
9
- class Context
8
+ # Args is immutable after creation - options cannot be modified during task execution.
9
+ class Args
10
10
  attr_reader :started_at, :working_directory, :root_task
11
11
 
12
12
  # @param options [Hash] User-defined options (immutable after creation)