taski 0.5.0 → 0.7.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/CHANGELOG.md +50 -0
- data/README.md +168 -21
- data/docs/GUIDE.md +394 -0
- data/examples/README.md +65 -17
- data/examples/{context_demo.rb → args_demo.rb} +27 -27
- data/examples/clean_demo.rb +204 -0
- data/examples/data_pipeline_demo.rb +1 -1
- data/examples/group_demo.rb +113 -0
- data/examples/large_tree_demo.rb +519 -0
- data/examples/reexecution_demo.rb +93 -80
- data/examples/simple_progress_demo.rb +80 -0
- data/examples/system_call_demo.rb +56 -0
- data/lib/taski/{context.rb → args.rb} +3 -3
- data/lib/taski/execution/base_progress_display.rb +348 -0
- data/lib/taski/execution/execution_context.rb +383 -0
- data/lib/taski/execution/executor.rb +405 -134
- data/lib/taski/execution/plain_progress_display.rb +76 -0
- data/lib/taski/execution/registry.rb +17 -1
- data/lib/taski/execution/scheduler.rb +308 -0
- data/lib/taski/execution/simple_progress_display.rb +173 -0
- data/lib/taski/execution/task_output_pipe.rb +42 -0
- data/lib/taski/execution/task_output_router.rb +287 -0
- data/lib/taski/execution/task_wrapper.rb +215 -52
- data/lib/taski/execution/tree_progress_display.rb +349 -212
- data/lib/taski/execution/worker_pool.rb +104 -0
- data/lib/taski/section.rb +16 -3
- data/lib/taski/static_analysis/visitor.rb +3 -0
- data/lib/taski/task.rb +218 -37
- data/lib/taski/test_helper/errors.rb +13 -0
- data/lib/taski/test_helper/minitest.rb +38 -0
- data/lib/taski/test_helper/mock_registry.rb +51 -0
- data/lib/taski/test_helper/mock_wrapper.rb +46 -0
- data/lib/taski/test_helper/rspec.rb +38 -0
- data/lib/taski/test_helper.rb +214 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +211 -23
- data/sig/taski.rbs +207 -27
- metadata +25 -8
- data/docs/advanced-features.md +0 -625
- data/docs/api-guide.md +0 -509
- data/docs/error-handling.md +0 -684
- data/examples/section_progress_demo.rb +0 -78
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module Taski
|
|
6
|
+
module Execution
|
|
7
|
+
# Base class for progress display implementations.
|
|
8
|
+
# Provides common task tracking and lifecycle management.
|
|
9
|
+
# Subclasses override template methods for custom rendering.
|
|
10
|
+
class BaseProgressDisplay
|
|
11
|
+
# Shared task progress tracking
|
|
12
|
+
class TaskProgress
|
|
13
|
+
# Run lifecycle tracking
|
|
14
|
+
attr_accessor :run_state, :run_start_time, :run_end_time, :run_error, :run_duration
|
|
15
|
+
# Clean lifecycle tracking
|
|
16
|
+
attr_accessor :clean_state, :clean_start_time, :clean_end_time, :clean_error, :clean_duration
|
|
17
|
+
# Display properties
|
|
18
|
+
attr_accessor :is_impl_candidate
|
|
19
|
+
# Group tracking
|
|
20
|
+
attr_accessor :groups, :current_group_index
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
# Run lifecycle
|
|
24
|
+
@run_state = :pending
|
|
25
|
+
@run_start_time = nil
|
|
26
|
+
@run_end_time = nil
|
|
27
|
+
@run_error = nil
|
|
28
|
+
@run_duration = nil
|
|
29
|
+
# Clean lifecycle
|
|
30
|
+
@clean_state = nil # nil means clean hasn't started
|
|
31
|
+
@clean_start_time = nil
|
|
32
|
+
@clean_end_time = nil
|
|
33
|
+
@clean_error = nil
|
|
34
|
+
@clean_duration = nil
|
|
35
|
+
# Display
|
|
36
|
+
@is_impl_candidate = false
|
|
37
|
+
# Groups
|
|
38
|
+
@groups = []
|
|
39
|
+
@current_group_index = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the most relevant state for display
|
|
43
|
+
def state
|
|
44
|
+
@clean_state || @run_state
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Legacy accessors for backward compatibility
|
|
48
|
+
def start_time
|
|
49
|
+
@clean_start_time || @run_start_time
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def end_time
|
|
53
|
+
@clean_end_time || @run_end_time
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def error
|
|
57
|
+
@clean_error || @run_error
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def duration
|
|
61
|
+
@clean_duration || @run_duration
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Tracks the progress of a group within a task
|
|
66
|
+
class GroupProgress
|
|
67
|
+
attr_accessor :name, :state, :start_time, :end_time, :duration, :error, :last_message
|
|
68
|
+
|
|
69
|
+
def initialize(name)
|
|
70
|
+
@name = name
|
|
71
|
+
@state = :pending
|
|
72
|
+
@start_time = nil
|
|
73
|
+
@end_time = nil
|
|
74
|
+
@duration = nil
|
|
75
|
+
@error = nil
|
|
76
|
+
@last_message = nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def initialize(output: $stdout)
|
|
81
|
+
@output = output
|
|
82
|
+
@tasks = {}
|
|
83
|
+
@monitor = Monitor.new
|
|
84
|
+
@nest_level = 0
|
|
85
|
+
@root_task_class = nil
|
|
86
|
+
@output_capture = nil
|
|
87
|
+
@start_time = nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Set the output capture for getting task output
|
|
91
|
+
# @param capture [ThreadOutputCapture] The output capture instance
|
|
92
|
+
def set_output_capture(capture)
|
|
93
|
+
@monitor.synchronize do
|
|
94
|
+
@output_capture = capture
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Set the root task to build tree structure
|
|
99
|
+
# Only sets root task if not already set (prevents nested executor overwrite)
|
|
100
|
+
# @param root_task_class [Class] The root task class
|
|
101
|
+
def set_root_task(root_task_class)
|
|
102
|
+
@monitor.synchronize do
|
|
103
|
+
return if @root_task_class # Don't overwrite existing root task
|
|
104
|
+
@root_task_class = root_task_class
|
|
105
|
+
on_root_task_set
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Register which impl was selected for a section
|
|
110
|
+
# @param section_class [Class] The section class
|
|
111
|
+
# @param impl_class [Class] The selected implementation class
|
|
112
|
+
def register_section_impl(section_class, impl_class)
|
|
113
|
+
@monitor.synchronize do
|
|
114
|
+
on_section_impl_registered(section_class, impl_class)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @param task_class [Class] The task class to register
|
|
119
|
+
def register_task(task_class)
|
|
120
|
+
@monitor.synchronize do
|
|
121
|
+
return if @tasks.key?(task_class)
|
|
122
|
+
@tasks[task_class] = TaskProgress.new
|
|
123
|
+
on_task_registered(task_class)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @param task_class [Class] The task class to check
|
|
128
|
+
# @return [Boolean] true if the task is registered
|
|
129
|
+
def task_registered?(task_class)
|
|
130
|
+
@monitor.synchronize do
|
|
131
|
+
@tasks.key?(task_class)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @param task_class [Class] The task class to update
|
|
136
|
+
# @param state [Symbol] The new state
|
|
137
|
+
# @param duration [Float] Duration in milliseconds (for completed tasks)
|
|
138
|
+
# @param error [Exception] Error object (for failed tasks)
|
|
139
|
+
def update_task(task_class, state:, duration: nil, error: nil)
|
|
140
|
+
@monitor.synchronize do
|
|
141
|
+
progress = @tasks[task_class]
|
|
142
|
+
# Register task if not already registered (for late-registered tasks)
|
|
143
|
+
progress ||= @tasks[task_class] = TaskProgress.new
|
|
144
|
+
|
|
145
|
+
apply_state_transition(progress, state, duration, error)
|
|
146
|
+
on_task_updated(task_class, state, duration, error)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @param task_class [Class] The task class
|
|
151
|
+
# @return [Symbol] The task state
|
|
152
|
+
def task_state(task_class)
|
|
153
|
+
@monitor.synchronize do
|
|
154
|
+
@tasks[task_class]&.state
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Update group state for a task.
|
|
159
|
+
# @param task_class [Class] The task class containing the group
|
|
160
|
+
# @param group_name [String] The name of the group
|
|
161
|
+
# @param state [Symbol] The new state (:running, :completed, :failed)
|
|
162
|
+
# @param duration [Float, nil] Duration in milliseconds (for completed groups)
|
|
163
|
+
# @param error [Exception, nil] Error object (for failed groups)
|
|
164
|
+
def update_group(task_class, group_name, state:, duration: nil, error: nil)
|
|
165
|
+
@monitor.synchronize do
|
|
166
|
+
progress = @tasks[task_class]
|
|
167
|
+
return unless progress
|
|
168
|
+
|
|
169
|
+
apply_group_state_transition(progress, group_name, state, duration, error)
|
|
170
|
+
on_group_updated(task_class, group_name, state, duration, error)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def start
|
|
175
|
+
should_start = false
|
|
176
|
+
@monitor.synchronize do
|
|
177
|
+
@nest_level += 1
|
|
178
|
+
return if @nest_level > 1 # Already running from outer executor
|
|
179
|
+
return unless should_activate?
|
|
180
|
+
|
|
181
|
+
@start_time = Time.now
|
|
182
|
+
should_start = true
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
return unless should_start
|
|
186
|
+
|
|
187
|
+
on_start
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def stop
|
|
191
|
+
should_stop = false
|
|
192
|
+
@monitor.synchronize do
|
|
193
|
+
@nest_level -= 1 if @nest_level > 0
|
|
194
|
+
return unless @nest_level == 0
|
|
195
|
+
|
|
196
|
+
should_stop = true
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
return unless should_stop
|
|
200
|
+
|
|
201
|
+
on_stop
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
protected
|
|
205
|
+
|
|
206
|
+
# Template methods - override in subclasses
|
|
207
|
+
|
|
208
|
+
# Called when root task is set. Override to build tree structure.
|
|
209
|
+
def on_root_task_set
|
|
210
|
+
# Default: no-op
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Called when a section impl is registered.
|
|
214
|
+
def on_section_impl_registered(section_class, impl_class)
|
|
215
|
+
# Default: no-op
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Called when a task is registered.
|
|
219
|
+
def on_task_registered(task_class)
|
|
220
|
+
# Default: no-op
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Called when a task state is updated.
|
|
224
|
+
def on_task_updated(task_class, state, duration, error)
|
|
225
|
+
# Default: no-op
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Called when a group state is updated.
|
|
229
|
+
def on_group_updated(task_class, group_name, state, duration, error)
|
|
230
|
+
# Default: no-op
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Called to determine if display should activate.
|
|
234
|
+
# @return [Boolean] true if display should start
|
|
235
|
+
def should_activate?
|
|
236
|
+
true
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Called when display starts.
|
|
240
|
+
def on_start
|
|
241
|
+
# Default: no-op
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Called when display stops.
|
|
245
|
+
def on_stop
|
|
246
|
+
# Default: no-op
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Shared tree traversal for subclasses
|
|
250
|
+
|
|
251
|
+
# Register all tasks from a tree structure recursively
|
|
252
|
+
def register_tasks_from_tree(node)
|
|
253
|
+
return unless node
|
|
254
|
+
|
|
255
|
+
task_class = node[:task_class]
|
|
256
|
+
@tasks[task_class] ||= TaskProgress.new
|
|
257
|
+
@tasks[task_class].is_impl_candidate = true if node[:is_impl_candidate]
|
|
258
|
+
|
|
259
|
+
node[:children].each { |child| register_tasks_from_tree(child) }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Utility methods for subclasses
|
|
263
|
+
|
|
264
|
+
# Get short name of a task class
|
|
265
|
+
def short_name(task_class)
|
|
266
|
+
return "Unknown" unless task_class
|
|
267
|
+
task_class.name&.split("::")&.last || task_class.to_s
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Format duration for display
|
|
271
|
+
def format_duration(ms)
|
|
272
|
+
if ms >= 1000
|
|
273
|
+
"#{(ms / 1000.0).round(1)}s"
|
|
274
|
+
else
|
|
275
|
+
"#{ms.round(1)}ms"
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Check if output is a TTY
|
|
280
|
+
def tty?
|
|
281
|
+
@output.tty?
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
private
|
|
285
|
+
|
|
286
|
+
# Apply state transition to TaskProgress
|
|
287
|
+
def apply_state_transition(progress, state, duration, error)
|
|
288
|
+
case state
|
|
289
|
+
# Run lifecycle states
|
|
290
|
+
when :pending
|
|
291
|
+
progress.run_state = :pending
|
|
292
|
+
when :running
|
|
293
|
+
progress.run_state = :running
|
|
294
|
+
progress.run_start_time = Time.now
|
|
295
|
+
when :completed
|
|
296
|
+
progress.run_state = :completed
|
|
297
|
+
progress.run_end_time = Time.now
|
|
298
|
+
progress.run_duration = duration if duration
|
|
299
|
+
when :failed
|
|
300
|
+
progress.run_state = :failed
|
|
301
|
+
progress.run_end_time = Time.now
|
|
302
|
+
progress.run_error = error if error
|
|
303
|
+
# Clean lifecycle states
|
|
304
|
+
when :cleaning
|
|
305
|
+
progress.clean_state = :cleaning
|
|
306
|
+
progress.clean_start_time = Time.now
|
|
307
|
+
when :clean_completed
|
|
308
|
+
progress.clean_state = :clean_completed
|
|
309
|
+
progress.clean_end_time = Time.now
|
|
310
|
+
progress.clean_duration = duration if duration
|
|
311
|
+
when :clean_failed
|
|
312
|
+
progress.clean_state = :clean_failed
|
|
313
|
+
progress.clean_end_time = Time.now
|
|
314
|
+
progress.clean_error = error if error
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Apply state transition to GroupProgress
|
|
319
|
+
def apply_group_state_transition(progress, group_name, state, duration, error)
|
|
320
|
+
case state
|
|
321
|
+
when :running
|
|
322
|
+
group = GroupProgress.new(group_name)
|
|
323
|
+
group.state = :running
|
|
324
|
+
group.start_time = Time.now
|
|
325
|
+
progress.groups << group
|
|
326
|
+
progress.current_group_index = progress.groups.size - 1
|
|
327
|
+
when :completed
|
|
328
|
+
group = progress.groups.find { |g| g.name == group_name && g.state == :running }
|
|
329
|
+
if group
|
|
330
|
+
group.state = :completed
|
|
331
|
+
group.end_time = Time.now
|
|
332
|
+
group.duration = duration
|
|
333
|
+
end
|
|
334
|
+
progress.current_group_index = nil
|
|
335
|
+
when :failed
|
|
336
|
+
group = progress.groups.find { |g| g.name == group_name && g.state == :running }
|
|
337
|
+
if group
|
|
338
|
+
group.state = :failed
|
|
339
|
+
group.end_time = Time.now
|
|
340
|
+
group.duration = duration
|
|
341
|
+
group.error = error
|
|
342
|
+
end
|
|
343
|
+
progress.current_group_index = nil
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|