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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +51 -33
- data/Steepfile +1 -0
- data/docs/GUIDE.md +340 -0
- data/examples/README.md +68 -20
- data/examples/{context_demo.rb → args_demo.rb} +27 -27
- data/examples/clean_demo.rb +204 -0
- data/examples/data_pipeline_demo.rb +3 -3
- data/examples/group_demo.rb +113 -0
- data/examples/nested_section_demo.rb +161 -0
- data/examples/parallel_progress_demo.rb +1 -1
- data/examples/reexecution_demo.rb +93 -80
- data/examples/system_call_demo.rb +56 -0
- data/examples/tree_progress_demo.rb +164 -0
- data/lib/taski/{context.rb → args.rb} +3 -3
- data/lib/taski/execution/execution_context.rb +379 -0
- data/lib/taski/execution/executor.rb +538 -0
- data/lib/taski/execution/registry.rb +26 -2
- data/lib/taski/execution/scheduler.rb +308 -0
- data/lib/taski/execution/task_output_pipe.rb +42 -0
- data/lib/taski/execution/task_output_router.rb +216 -0
- data/lib/taski/execution/task_wrapper.rb +295 -146
- data/lib/taski/execution/tree_progress_display.rb +793 -0
- data/lib/taski/execution/worker_pool.rb +104 -0
- data/lib/taski/section.rb +23 -0
- data/lib/taski/static_analysis/analyzer.rb +4 -2
- data/lib/taski/static_analysis/visitor.rb +86 -5
- data/lib/taski/task.rb +223 -120
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +147 -28
- data/sig/taski.rbs +310 -67
- metadata +17 -8
- data/docs/advanced-features.md +0 -625
- data/docs/api-guide.md +0 -509
- data/docs/error-handling.md +0 -684
- data/lib/taski/execution/coordinator.rb +0 -63
- 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
|
|
4
|
+
# Taski Scope-Based Execution Demo
|
|
5
5
|
#
|
|
6
|
-
# This example demonstrates
|
|
7
|
-
# -
|
|
8
|
-
# - Task.new
|
|
9
|
-
# -
|
|
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
|
|
16
|
-
puts "=" *
|
|
15
|
+
puts "Taski Scope-Based Execution Demo"
|
|
16
|
+
puts "=" * 50
|
|
17
17
|
|
|
18
|
-
# Task that generates random values (to demonstrate
|
|
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
|
|
36
|
-
puts " Consumer.run called
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
61
|
+
puts "\nSecond call (NEW execution, different value):"
|
|
47
62
|
value2 = RandomGenerator.value
|
|
48
63
|
puts " => #{value2}"
|
|
49
64
|
|
|
50
|
-
puts "\nValues are
|
|
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" + "=" *
|
|
53
|
-
puts "\
|
|
54
|
-
puts "-" *
|
|
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 "
|
|
89
|
+
puts "instance1.value = #{instance1.value}"
|
|
60
90
|
|
|
61
91
|
instance2 = RandomGenerator.new
|
|
62
92
|
instance2.run
|
|
63
|
-
puts "
|
|
64
|
-
|
|
65
|
-
puts "\
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
puts "\
|
|
69
|
-
puts "
|
|
70
|
-
puts "
|
|
71
|
-
|
|
72
|
-
puts "
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
puts "
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
puts "
|
|
83
|
-
puts "
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
RandomGenerator
|
|
87
|
-
Consumer.
|
|
88
|
-
|
|
89
|
-
puts "
|
|
90
|
-
|
|
91
|
-
puts "
|
|
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
|
-
=>
|
|
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
|
-
=>
|
|
119
|
-
=>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
=>
|
|
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" + "=" *
|
|
127
|
-
puts "
|
|
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
|
|
6
|
+
# Runtime arguments accessible from any task.
|
|
7
7
|
# Holds user-defined options and execution metadata.
|
|
8
|
-
#
|
|
9
|
-
class
|
|
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)
|