taski 0.8.2 → 0.9.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 +47 -0
- data/README.md +65 -50
- data/docs/GUIDE.md +41 -56
- data/examples/README.md +10 -29
- data/examples/clean_demo.rb +25 -65
- data/examples/large_tree_demo.rb +356 -0
- data/examples/message_demo.rb +0 -1
- data/examples/progress_demo.rb +13 -24
- data/examples/reexecution_demo.rb +8 -44
- data/lib/taski/execution/execution_facade.rb +150 -0
- data/lib/taski/execution/executor.rb +156 -357
- data/lib/taski/execution/registry.rb +15 -19
- data/lib/taski/execution/scheduler.rb +161 -140
- data/lib/taski/execution/task_observer.rb +41 -0
- data/lib/taski/execution/task_output_router.rb +41 -58
- data/lib/taski/execution/task_wrapper.rb +123 -219
- data/lib/taski/execution/worker_pool.rb +238 -64
- data/lib/taski/logging.rb +105 -0
- data/lib/taski/progress/layout/base.rb +600 -0
- data/lib/taski/progress/layout/filters.rb +126 -0
- data/lib/taski/progress/layout/log.rb +27 -0
- data/lib/taski/progress/layout/simple.rb +166 -0
- data/lib/taski/progress/layout/tags.rb +76 -0
- data/lib/taski/progress/layout/theme_drop.rb +84 -0
- data/lib/taski/progress/layout/tree.rb +300 -0
- data/lib/taski/progress/theme/base.rb +224 -0
- data/lib/taski/progress/theme/compact.rb +58 -0
- data/lib/taski/progress/theme/default.rb +25 -0
- data/lib/taski/progress/theme/detail.rb +48 -0
- data/lib/taski/progress/theme/plain.rb +40 -0
- data/lib/taski/static_analysis/analyzer.rb +5 -17
- data/lib/taski/static_analysis/dependency_graph.rb +19 -1
- data/lib/taski/static_analysis/visitor.rb +1 -39
- data/lib/taski/task.rb +44 -58
- data/lib/taski/test_helper/errors.rb +1 -1
- data/lib/taski/test_helper.rb +21 -35
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +60 -61
- data/sig/taski.rbs +194 -203
- metadata +31 -8
- data/examples/section_demo.rb +0 -195
- data/lib/taski/execution/base_progress_display.rb +0 -364
- data/lib/taski/execution/execution_context.rb +0 -390
- data/lib/taski/execution/plain_progress_display.rb +0 -76
- data/lib/taski/execution/simple_progress_display.rb +0 -206
- data/lib/taski/execution/tree_progress_display.rb +0 -643
- data/lib/taski/section.rb +0 -74
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: taski
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ahogappa
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: liquid
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: prism
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -60,27 +74,36 @@ files:
|
|
|
60
74
|
- examples/args_demo.rb
|
|
61
75
|
- examples/clean_demo.rb
|
|
62
76
|
- examples/group_demo.rb
|
|
77
|
+
- examples/large_tree_demo.rb
|
|
63
78
|
- examples/message_demo.rb
|
|
64
79
|
- examples/progress_demo.rb
|
|
65
80
|
- examples/quick_start.rb
|
|
66
81
|
- examples/reexecution_demo.rb
|
|
67
|
-
- examples/section_demo.rb
|
|
68
82
|
- lib/taski.rb
|
|
69
83
|
- lib/taski/args.rb
|
|
70
84
|
- lib/taski/env.rb
|
|
71
|
-
- lib/taski/execution/
|
|
72
|
-
- lib/taski/execution/execution_context.rb
|
|
85
|
+
- lib/taski/execution/execution_facade.rb
|
|
73
86
|
- lib/taski/execution/executor.rb
|
|
74
|
-
- lib/taski/execution/plain_progress_display.rb
|
|
75
87
|
- lib/taski/execution/registry.rb
|
|
76
88
|
- lib/taski/execution/scheduler.rb
|
|
77
|
-
- lib/taski/execution/
|
|
89
|
+
- lib/taski/execution/task_observer.rb
|
|
78
90
|
- lib/taski/execution/task_output_pipe.rb
|
|
79
91
|
- lib/taski/execution/task_output_router.rb
|
|
80
92
|
- lib/taski/execution/task_wrapper.rb
|
|
81
|
-
- lib/taski/execution/tree_progress_display.rb
|
|
82
93
|
- lib/taski/execution/worker_pool.rb
|
|
83
|
-
- lib/taski/
|
|
94
|
+
- lib/taski/logging.rb
|
|
95
|
+
- lib/taski/progress/layout/base.rb
|
|
96
|
+
- lib/taski/progress/layout/filters.rb
|
|
97
|
+
- lib/taski/progress/layout/log.rb
|
|
98
|
+
- lib/taski/progress/layout/simple.rb
|
|
99
|
+
- lib/taski/progress/layout/tags.rb
|
|
100
|
+
- lib/taski/progress/layout/theme_drop.rb
|
|
101
|
+
- lib/taski/progress/layout/tree.rb
|
|
102
|
+
- lib/taski/progress/theme/base.rb
|
|
103
|
+
- lib/taski/progress/theme/compact.rb
|
|
104
|
+
- lib/taski/progress/theme/default.rb
|
|
105
|
+
- lib/taski/progress/theme/detail.rb
|
|
106
|
+
- lib/taski/progress/theme/plain.rb
|
|
84
107
|
- lib/taski/static_analysis/analyzer.rb
|
|
85
108
|
- lib/taski/static_analysis/dependency_graph.rb
|
|
86
109
|
- lib/taski/static_analysis/visitor.rb
|
data/examples/section_demo.rb
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
# Section API Basics Example
|
|
4
|
-
# This example demonstrates runtime implementation selection with the Section API
|
|
5
|
-
|
|
6
|
-
# Section API is perfect for:
|
|
7
|
-
# - Environment-specific implementations (dev/staging/prod)
|
|
8
|
-
# - Different service adapters (AWS/GCP/local)
|
|
9
|
-
# - Clean abstraction with guaranteed interfaces
|
|
10
|
-
|
|
11
|
-
require_relative "../lib/taski"
|
|
12
|
-
|
|
13
|
-
# Example 1: Database Configuration Section
|
|
14
|
-
# This section provides database configuration with different implementations
|
|
15
|
-
# for development and production environments
|
|
16
|
-
class DatabaseSection < Taski::Section
|
|
17
|
-
# Define the interface that implementations must provide
|
|
18
|
-
interfaces :host, :port, :username, :password, :database_name, :pool_size
|
|
19
|
-
|
|
20
|
-
# Select implementation based on environment
|
|
21
|
-
# Note: Must return a Task class - .run is automatically called
|
|
22
|
-
# No 'self' needed - just define as instance method!
|
|
23
|
-
def impl
|
|
24
|
-
if ENV["RAILS_ENV"] == "production"
|
|
25
|
-
Production
|
|
26
|
-
else
|
|
27
|
-
Development
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Production implementation with secure settings
|
|
32
|
-
# No exports needed - automatically inherited from interfaces
|
|
33
|
-
class Production < Taski::Task
|
|
34
|
-
def run
|
|
35
|
-
@host = "prod-db.example.com"
|
|
36
|
-
@port = 5432
|
|
37
|
-
@username = "app_user"
|
|
38
|
-
@password = ENV["DB_PASSWORD"] || "secure_password"
|
|
39
|
-
@database_name = "myapp_production"
|
|
40
|
-
@pool_size = 25
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Development implementation with local settings
|
|
45
|
-
class Development < Taski::Task
|
|
46
|
-
def run
|
|
47
|
-
@host = "localhost"
|
|
48
|
-
@port = 5432
|
|
49
|
-
@username = "dev_user"
|
|
50
|
-
@password = "dev_password"
|
|
51
|
-
@database_name = "myapp_development"
|
|
52
|
-
@pool_size = 5
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Example 2: API Configuration Section
|
|
58
|
-
# This section provides API endpoints and credentials
|
|
59
|
-
class ApiSection < Taski::Section
|
|
60
|
-
interfaces :base_url, :api_key, :timeout, :retry_count
|
|
61
|
-
|
|
62
|
-
# No 'self' needed - just define as instance method!
|
|
63
|
-
def impl
|
|
64
|
-
# Select based on feature flag
|
|
65
|
-
# Note: Must return a Task class - .run is automatically called
|
|
66
|
-
if ENV["USE_STAGING_API"] == "true"
|
|
67
|
-
Staging
|
|
68
|
-
else
|
|
69
|
-
Production
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
class Production < Taski::Task
|
|
74
|
-
def run
|
|
75
|
-
@base_url = "https://api.example.com/v1"
|
|
76
|
-
@api_key = ENV["PROD_API_KEY"] || "prod-key-123"
|
|
77
|
-
@timeout = 30
|
|
78
|
-
@retry_count = 3
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
class Staging < Taski::Task
|
|
83
|
-
def run
|
|
84
|
-
@base_url = "https://staging-api.example.com/v1"
|
|
85
|
-
@api_key = ENV["STAGING_API_KEY"] || "staging-key-456"
|
|
86
|
-
@timeout = 60
|
|
87
|
-
@retry_count = 1
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Example 3: Task that depends on multiple sections
|
|
93
|
-
class ApplicationSetup < Taski::Task
|
|
94
|
-
exports :config_summary
|
|
95
|
-
|
|
96
|
-
def run
|
|
97
|
-
puts "Setting up application with configuration:"
|
|
98
|
-
puts "Database: #{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
|
|
99
|
-
puts "API: #{ApiSection.base_url}"
|
|
100
|
-
puts "Pool size: #{DatabaseSection.pool_size}"
|
|
101
|
-
puts "API timeout: #{ApiSection.timeout}s"
|
|
102
|
-
|
|
103
|
-
@config_summary = {
|
|
104
|
-
database: {
|
|
105
|
-
host: DatabaseSection.host,
|
|
106
|
-
port: DatabaseSection.port,
|
|
107
|
-
database: DatabaseSection.database_name,
|
|
108
|
-
pool_size: DatabaseSection.pool_size
|
|
109
|
-
},
|
|
110
|
-
api: {
|
|
111
|
-
base_url: ApiSection.base_url,
|
|
112
|
-
timeout: ApiSection.timeout,
|
|
113
|
-
retry_count: ApiSection.retry_count
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Example 4: Complex dependency chain with sections
|
|
120
|
-
class DatabaseConnection < Taski::Task
|
|
121
|
-
exports :connection
|
|
122
|
-
|
|
123
|
-
def run
|
|
124
|
-
puts "Connecting to database..."
|
|
125
|
-
# Use section configuration to create connection
|
|
126
|
-
connection_string = "postgresql://#{DatabaseSection.username}:#{DatabaseSection.password}@#{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
|
|
127
|
-
@connection = "Connected to: #{connection_string} (pool: #{DatabaseSection.pool_size})"
|
|
128
|
-
puts @connection
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
class ApiClient < Taski::Task
|
|
133
|
-
exports :client
|
|
134
|
-
|
|
135
|
-
def run
|
|
136
|
-
puts "Initializing API client..."
|
|
137
|
-
@client = "API Client: #{ApiSection.base_url} (timeout: #{ApiSection.timeout}s, retries: #{ApiSection.retry_count})"
|
|
138
|
-
puts @client
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
class Application < Taski::Task
|
|
143
|
-
def run
|
|
144
|
-
puts "\n=== Starting Application ==="
|
|
145
|
-
|
|
146
|
-
# Dependencies are automatically resolved
|
|
147
|
-
# DatabaseConnection and ApiClient will be executed first
|
|
148
|
-
# which triggers execution of their respective sections
|
|
149
|
-
|
|
150
|
-
puts "\nDatabase ready: #{DatabaseConnection.connection}"
|
|
151
|
-
puts "API ready: #{ApiClient.client}"
|
|
152
|
-
|
|
153
|
-
puts "\nApplication configuration summary:"
|
|
154
|
-
puts ApplicationSetup.config_summary.inspect
|
|
155
|
-
|
|
156
|
-
puts "\n=== Application Started Successfully ==="
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
puts "Taski Section Configuration Example"
|
|
161
|
-
puts "=" * 50
|
|
162
|
-
|
|
163
|
-
puts "\n1. Development Environment (default)"
|
|
164
|
-
ENV["RAILS_ENV"] = "development"
|
|
165
|
-
ENV["USE_STAGING_API"] = "false"
|
|
166
|
-
|
|
167
|
-
# Reset all tasks to ensure fresh build
|
|
168
|
-
[DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
|
|
169
|
-
|
|
170
|
-
Application.run
|
|
171
|
-
|
|
172
|
-
puts "\n" + "=" * 50
|
|
173
|
-
puts "\n2. Production Environment with Staging API"
|
|
174
|
-
ENV["RAILS_ENV"] = "production"
|
|
175
|
-
ENV["USE_STAGING_API"] = "true"
|
|
176
|
-
|
|
177
|
-
# Reset all tasks to see different configuration
|
|
178
|
-
[DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
|
|
179
|
-
|
|
180
|
-
Application.run
|
|
181
|
-
|
|
182
|
-
puts "\n" + "=" * 50
|
|
183
|
-
puts "\n3. Dependency Tree Visualization"
|
|
184
|
-
puts "\nApplication dependency tree:"
|
|
185
|
-
puts Application.tree
|
|
186
|
-
|
|
187
|
-
puts "\nDatabaseConnection dependency tree:"
|
|
188
|
-
puts DatabaseConnection.tree
|
|
189
|
-
|
|
190
|
-
puts "\nApiClient dependency tree:"
|
|
191
|
-
puts ApiClient.tree
|
|
192
|
-
|
|
193
|
-
puts "\n" + "=" * 50
|
|
194
|
-
puts "\nSection dependency resolution successfully demonstrated!"
|
|
195
|
-
puts "Notice how sections appear in the dependency trees and logs."
|
|
@@ -1,364 +0,0 @@
|
|
|
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
|
-
@message_queue = []
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Set the output capture for getting task output
|
|
92
|
-
# @param capture [ThreadOutputCapture] The output capture instance
|
|
93
|
-
def set_output_capture(capture)
|
|
94
|
-
@monitor.synchronize do
|
|
95
|
-
@output_capture = capture
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Set the root task to build tree structure
|
|
100
|
-
# Only sets root task if not already set (prevents nested executor overwrite)
|
|
101
|
-
# @param root_task_class [Class] The root task class
|
|
102
|
-
def set_root_task(root_task_class)
|
|
103
|
-
@monitor.synchronize do
|
|
104
|
-
return if @root_task_class # Don't overwrite existing root task
|
|
105
|
-
@root_task_class = root_task_class
|
|
106
|
-
on_root_task_set
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Register which impl was selected for a section
|
|
111
|
-
# @param section_class [Class] The section class
|
|
112
|
-
# @param impl_class [Class] The selected implementation class
|
|
113
|
-
def register_section_impl(section_class, impl_class)
|
|
114
|
-
@monitor.synchronize do
|
|
115
|
-
on_section_impl_registered(section_class, impl_class)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# @param task_class [Class] The task class to register
|
|
120
|
-
def register_task(task_class)
|
|
121
|
-
@monitor.synchronize do
|
|
122
|
-
return if @tasks.key?(task_class)
|
|
123
|
-
@tasks[task_class] = TaskProgress.new
|
|
124
|
-
on_task_registered(task_class)
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# @param task_class [Class] The task class to check
|
|
129
|
-
# @return [Boolean] true if the task is registered
|
|
130
|
-
def task_registered?(task_class)
|
|
131
|
-
@monitor.synchronize do
|
|
132
|
-
@tasks.key?(task_class)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# @param task_class [Class] The task class to update
|
|
137
|
-
# @param state [Symbol] The new state
|
|
138
|
-
# @param duration [Float] Duration in milliseconds (for completed tasks)
|
|
139
|
-
# @param error [Exception] Error object (for failed tasks)
|
|
140
|
-
def update_task(task_class, state:, duration: nil, error: nil)
|
|
141
|
-
@monitor.synchronize do
|
|
142
|
-
progress = @tasks[task_class]
|
|
143
|
-
# Register task if not already registered (for late-registered tasks)
|
|
144
|
-
progress ||= @tasks[task_class] = TaskProgress.new
|
|
145
|
-
|
|
146
|
-
apply_state_transition(progress, state, duration, error)
|
|
147
|
-
on_task_updated(task_class, state, duration, error)
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# @param task_class [Class] The task class
|
|
152
|
-
# @return [Symbol] The task state
|
|
153
|
-
def task_state(task_class)
|
|
154
|
-
@monitor.synchronize do
|
|
155
|
-
@tasks[task_class]&.state
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Update group state for a task.
|
|
160
|
-
# @param task_class [Class] The task class containing the group
|
|
161
|
-
# @param group_name [String] The name of the group
|
|
162
|
-
# @param state [Symbol] The new state (:running, :completed, :failed)
|
|
163
|
-
# @param duration [Float, nil] Duration in milliseconds (for completed groups)
|
|
164
|
-
# @param error [Exception, nil] Error object (for failed groups)
|
|
165
|
-
def update_group(task_class, group_name, state:, duration: nil, error: nil)
|
|
166
|
-
@monitor.synchronize do
|
|
167
|
-
progress = @tasks[task_class]
|
|
168
|
-
return unless progress
|
|
169
|
-
|
|
170
|
-
apply_group_state_transition(progress, group_name, state, duration, error)
|
|
171
|
-
on_group_updated(task_class, group_name, state, duration, error)
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def start
|
|
176
|
-
should_start = false
|
|
177
|
-
@monitor.synchronize do
|
|
178
|
-
@nest_level += 1
|
|
179
|
-
return if @nest_level > 1 # Already running from outer executor
|
|
180
|
-
return unless should_activate?
|
|
181
|
-
|
|
182
|
-
@start_time = Time.now
|
|
183
|
-
should_start = true
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
return unless should_start
|
|
187
|
-
|
|
188
|
-
on_start
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def stop
|
|
192
|
-
should_stop = false
|
|
193
|
-
@monitor.synchronize do
|
|
194
|
-
@nest_level -= 1 if @nest_level > 0
|
|
195
|
-
return unless @nest_level == 0
|
|
196
|
-
|
|
197
|
-
should_stop = true
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
return unless should_stop
|
|
201
|
-
|
|
202
|
-
on_stop
|
|
203
|
-
flush_queued_messages
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Queue a message to be displayed after progress display stops.
|
|
207
|
-
# Thread-safe for concurrent task execution.
|
|
208
|
-
# @param text [String] The message text to queue
|
|
209
|
-
def queue_message(text)
|
|
210
|
-
@monitor.synchronize { @message_queue << text }
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
protected
|
|
214
|
-
|
|
215
|
-
# Template methods - override in subclasses
|
|
216
|
-
|
|
217
|
-
# Called when root task is set. Override to build tree structure.
|
|
218
|
-
def on_root_task_set
|
|
219
|
-
# Default: no-op
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Called when a section impl is registered.
|
|
223
|
-
def on_section_impl_registered(section_class, impl_class)
|
|
224
|
-
# Default: no-op
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Called when a task is registered.
|
|
228
|
-
def on_task_registered(task_class)
|
|
229
|
-
# Default: no-op
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Called when a task state is updated.
|
|
233
|
-
def on_task_updated(task_class, state, duration, error)
|
|
234
|
-
# Default: no-op
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Called when a group state is updated.
|
|
238
|
-
def on_group_updated(task_class, group_name, state, duration, error)
|
|
239
|
-
# Default: no-op
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# Called to determine if display should activate.
|
|
243
|
-
# @return [Boolean] true if display should start
|
|
244
|
-
def should_activate?
|
|
245
|
-
true
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# Called when display starts.
|
|
249
|
-
def on_start
|
|
250
|
-
# Default: no-op
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Called when display stops.
|
|
254
|
-
def on_stop
|
|
255
|
-
# Default: no-op
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
# Shared tree traversal for subclasses
|
|
259
|
-
|
|
260
|
-
# Register all tasks from a tree structure recursively
|
|
261
|
-
def register_tasks_from_tree(node)
|
|
262
|
-
return unless node
|
|
263
|
-
|
|
264
|
-
task_class = node[:task_class]
|
|
265
|
-
@tasks[task_class] ||= TaskProgress.new
|
|
266
|
-
@tasks[task_class].is_impl_candidate = true if node[:is_impl_candidate]
|
|
267
|
-
|
|
268
|
-
node[:children].each { |child| register_tasks_from_tree(child) }
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# Utility methods for subclasses
|
|
272
|
-
|
|
273
|
-
# Get short name of a task class
|
|
274
|
-
def short_name(task_class)
|
|
275
|
-
return "Unknown" unless task_class
|
|
276
|
-
task_class.name&.split("::")&.last || task_class.to_s
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
# Format duration for display
|
|
280
|
-
def format_duration(ms)
|
|
281
|
-
if ms >= 1000
|
|
282
|
-
"#{(ms / 1000.0).round(1)}s"
|
|
283
|
-
else
|
|
284
|
-
"#{ms.round(1)}ms"
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
# Check if output is a TTY
|
|
289
|
-
def tty?
|
|
290
|
-
@output.tty?
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
private
|
|
294
|
-
|
|
295
|
-
# Flush all queued messages to output.
|
|
296
|
-
# Called when progress display stops.
|
|
297
|
-
def flush_queued_messages
|
|
298
|
-
messages = @monitor.synchronize { @message_queue.dup.tap { @message_queue.clear } }
|
|
299
|
-
messages.each { |msg| @output.puts(msg) }
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# Apply state transition to TaskProgress
|
|
303
|
-
def apply_state_transition(progress, state, duration, error)
|
|
304
|
-
case state
|
|
305
|
-
# Run lifecycle states
|
|
306
|
-
when :pending
|
|
307
|
-
progress.run_state = :pending
|
|
308
|
-
when :running
|
|
309
|
-
progress.run_state = :running
|
|
310
|
-
progress.run_start_time = Time.now
|
|
311
|
-
when :completed
|
|
312
|
-
progress.run_state = :completed
|
|
313
|
-
progress.run_end_time = Time.now
|
|
314
|
-
progress.run_duration = duration if duration
|
|
315
|
-
when :failed
|
|
316
|
-
progress.run_state = :failed
|
|
317
|
-
progress.run_end_time = Time.now
|
|
318
|
-
progress.run_error = error if error
|
|
319
|
-
# Clean lifecycle states
|
|
320
|
-
when :cleaning
|
|
321
|
-
progress.clean_state = :cleaning
|
|
322
|
-
progress.clean_start_time = Time.now
|
|
323
|
-
when :clean_completed
|
|
324
|
-
progress.clean_state = :clean_completed
|
|
325
|
-
progress.clean_end_time = Time.now
|
|
326
|
-
progress.clean_duration = duration if duration
|
|
327
|
-
when :clean_failed
|
|
328
|
-
progress.clean_state = :clean_failed
|
|
329
|
-
progress.clean_end_time = Time.now
|
|
330
|
-
progress.clean_error = error if error
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
# Apply state transition to GroupProgress
|
|
335
|
-
def apply_group_state_transition(progress, group_name, state, duration, error)
|
|
336
|
-
case state
|
|
337
|
-
when :running
|
|
338
|
-
group = GroupProgress.new(group_name)
|
|
339
|
-
group.state = :running
|
|
340
|
-
group.start_time = Time.now
|
|
341
|
-
progress.groups << group
|
|
342
|
-
progress.current_group_index = progress.groups.size - 1
|
|
343
|
-
when :completed
|
|
344
|
-
group = progress.groups.find { |g| g.name == group_name && g.state == :running }
|
|
345
|
-
if group
|
|
346
|
-
group.state = :completed
|
|
347
|
-
group.end_time = Time.now
|
|
348
|
-
group.duration = duration
|
|
349
|
-
end
|
|
350
|
-
progress.current_group_index = nil
|
|
351
|
-
when :failed
|
|
352
|
-
group = progress.groups.find { |g| g.name == group_name && g.state == :running }
|
|
353
|
-
if group
|
|
354
|
-
group.state = :failed
|
|
355
|
-
group.end_time = Time.now
|
|
356
|
-
group.duration = duration
|
|
357
|
-
group.error = error
|
|
358
|
-
end
|
|
359
|
-
progress.current_group_index = nil
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
end
|
|
364
|
-
end
|