taski 0.8.0 → 0.8.2
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 +18 -0
- data/README.md +42 -1
- data/docs/GUIDE.md +18 -4
- data/examples/README.md +33 -45
- data/examples/message_demo.rb +57 -0
- data/examples/progress_demo.rb +154 -0
- data/lib/taski/execution/base_progress_display.rb +16 -0
- data/lib/taski/execution/execution_context.rb +10 -3
- data/lib/taski/execution/simple_progress_display.rb +36 -3
- data/lib/taski/execution/task_output_router.rb +2 -1
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +24 -1
- metadata +4 -9
- data/examples/data_pipeline_demo.rb +0 -231
- data/examples/large_tree_demo.rb +0 -519
- data/examples/nested_section_demo.rb +0 -161
- data/examples/parallel_progress_demo.rb +0 -72
- data/examples/simple_progress_demo.rb +0 -80
- data/examples/system_call_demo.rb +0 -56
- data/examples/tree_progress_demo.rb +0 -164
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ecbd73a50d1f39207625cda94e9f90a90ce212d270a70ed49cd94705bb30a5c
|
|
4
|
+
data.tar.gz: d5efd7fcf8c5c0d5bdfacf147e302375e63d41acd00f428924e87ba3a4e54ac0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e263e9c974f17ff46f545adc3cf90cddf66cbfe273c783d07f036a2d3fc4c7f8ec4759c1a94ff72ab0b302d8c4fae50a4654cda5b4b17d9f218ae897ba67572a
|
|
7
|
+
data.tar.gz: 9bf8d6b053ee781ef9ba2535926ef663be9ed45d88249d59a5325d7cbd600468ae5bacc69e506ae5bc7f2819707b9e4451a0faf0aa377dbbfb33fe04a7d9199b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.2] - 2026-01-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Queue `Taski.message` output until progress display stops to prevent interleaved output ([#133](https://github.com/ahogappa/taski/pull/133))
|
|
14
|
+
- Correct task count display in SimpleProgressDisplay ([#132](https://github.com/ahogappa/taski/pull/132))
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Consolidate examples from 15 to 8 files for better maintainability ([#131](https://github.com/ahogappa/taski/pull/131))
|
|
18
|
+
|
|
19
|
+
## [0.8.1] - 2026-01-26
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- `Taski.message` API for user-facing output during task execution ([#129](https://github.com/ahogappa/taski/pull/129))
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Count unselected section candidates as completed in SimpleProgressDisplay ([#128](https://github.com/ahogappa/taski/pull/128))
|
|
26
|
+
- Prioritize environment variable over code settings for progress_mode ([#127](https://github.com/ahogappa/taski/pull/127))
|
|
27
|
+
|
|
10
28
|
## [0.8.0] - 2026-01-23
|
|
11
29
|
|
|
12
30
|
### Added
|
data/README.md
CHANGED
|
@@ -262,6 +262,34 @@ end
|
|
|
262
262
|
|
|
263
263
|
When `TaskAbortException` is raised, no new tasks will start. Already running tasks will complete, then execution stops.
|
|
264
264
|
|
|
265
|
+
### Lifecycle Management
|
|
266
|
+
|
|
267
|
+
Define `clean` methods for resource cleanup. Clean runs in reverse dependency order:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
class DatabaseSetup < Taski::Task
|
|
271
|
+
exports :connection
|
|
272
|
+
|
|
273
|
+
def run
|
|
274
|
+
@connection = connect_to_database
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def clean
|
|
278
|
+
@connection&.close
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Run then clean in one call
|
|
283
|
+
DatabaseSetup.run_and_clean
|
|
284
|
+
|
|
285
|
+
# Or separately
|
|
286
|
+
DatabaseSetup.run
|
|
287
|
+
# ... do work ...
|
|
288
|
+
DatabaseSetup.clean
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
See [docs/guide.md](docs/guide.md#lifecycle-management) for details.
|
|
292
|
+
|
|
265
293
|
### Progress Display
|
|
266
294
|
|
|
267
295
|
Tree-based progress visualization is enabled by default:
|
|
@@ -281,14 +309,27 @@ WebServer (Task)
|
|
|
281
309
|
✓ [5/5] All tasks completed (1234ms)
|
|
282
310
|
```
|
|
283
311
|
|
|
312
|
+
**Plain mode** provides text output without escape codes (for CI/logs):
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
[START] DatabaseSetup
|
|
316
|
+
[DONE] DatabaseSetup (45.2ms)
|
|
317
|
+
[START] WebServer
|
|
318
|
+
[DONE] WebServer (120.5ms)
|
|
319
|
+
[TASKI] Completed: 2/2 tasks (165ms)
|
|
320
|
+
```
|
|
321
|
+
|
|
284
322
|
**Configuration:**
|
|
285
323
|
|
|
286
324
|
```ruby
|
|
287
325
|
# Via API
|
|
288
|
-
Taski.progress_mode = :
|
|
326
|
+
Taski.progress_mode = :tree # Tree display (default)
|
|
327
|
+
Taski.progress_mode = :simple # Single-line display
|
|
328
|
+
Taski.progress_mode = :plain # Plain text (CI/logs)
|
|
289
329
|
|
|
290
330
|
# Via environment variable
|
|
291
331
|
TASKI_PROGRESS_MODE=simple ruby your_script.rb
|
|
332
|
+
TASKI_PROGRESS_MODE=plain ruby your_script.rb
|
|
292
333
|
```
|
|
293
334
|
|
|
294
335
|
To disable: `TASKI_PROGRESS_DISABLE=1 ruby your_script.rb`
|
data/docs/GUIDE.md
CHANGED
|
@@ -324,20 +324,34 @@ On failure:
|
|
|
324
324
|
✗ [3/5] DeployTask failed: Connection refused
|
|
325
325
|
```
|
|
326
326
|
|
|
327
|
+
#### Plain Mode
|
|
328
|
+
|
|
329
|
+
Plain text output without escape codes, designed for CI/logs:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
[START] DatabaseSetup
|
|
333
|
+
[DONE] DatabaseSetup (45.2ms)
|
|
334
|
+
[START] WebServer
|
|
335
|
+
[DONE] WebServer (120.5ms)
|
|
336
|
+
[TASKI] Completed: 2/2 tasks (165ms)
|
|
337
|
+
```
|
|
338
|
+
|
|
327
339
|
### Configuring Progress Mode
|
|
328
340
|
|
|
329
341
|
**Via API:**
|
|
330
342
|
|
|
331
343
|
```ruby
|
|
332
|
-
Taski.progress_mode = :
|
|
333
|
-
Taski.progress_mode = :
|
|
344
|
+
Taski.progress_mode = :tree # Tree display (default)
|
|
345
|
+
Taski.progress_mode = :simple # Single-line display
|
|
346
|
+
Taski.progress_mode = :plain # Plain text (CI/logs)
|
|
334
347
|
```
|
|
335
348
|
|
|
336
349
|
**Via environment variable:**
|
|
337
350
|
|
|
338
351
|
```bash
|
|
339
|
-
TASKI_PROGRESS_MODE=simple ruby your_script.rb
|
|
340
352
|
TASKI_PROGRESS_MODE=tree ruby your_script.rb
|
|
353
|
+
TASKI_PROGRESS_MODE=simple ruby your_script.rb
|
|
354
|
+
TASKI_PROGRESS_MODE=plain ruby your_script.rb
|
|
341
355
|
```
|
|
342
356
|
|
|
343
357
|
### Disabling Progress Display
|
|
@@ -363,7 +377,7 @@ ruby build.rb > build.log 2>&1
|
|
|
363
377
|
| Variable | Purpose |
|
|
364
378
|
|----------|---------|
|
|
365
379
|
| `TASKI_PROGRESS_DISABLE=1` | Disable progress display |
|
|
366
|
-
| `TASKI_PROGRESS_MODE=simple\|
|
|
380
|
+
| `TASKI_PROGRESS_MODE=tree\|simple\|plain` | Set progress display mode (default: tree) |
|
|
367
381
|
| `TASKI_DEBUG=1` | Enable debug output |
|
|
368
382
|
|
|
369
383
|
### Dependency Tree Visualization
|
data/examples/README.md
CHANGED
|
@@ -67,78 +67,67 @@ TASKI_PROGRESS_DISABLE=1 ruby examples/reexecution_demo.rb
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
### 5.
|
|
70
|
+
### 5. clean_demo.rb - Lifecycle Management
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
ruby examples/data_pipeline_demo.rb
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Covers:**
|
|
79
|
-
- Multiple data sources in parallel
|
|
80
|
-
- Data transformation stages
|
|
81
|
-
- Aggregation and reporting
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
### 6. parallel_progress_demo.rb - Progress Display
|
|
86
|
-
|
|
87
|
-
Real-time progress visualization during parallel execution.
|
|
72
|
+
Demonstrates resource cleanup with clean methods.
|
|
88
73
|
|
|
89
74
|
```bash
|
|
90
|
-
ruby examples/
|
|
75
|
+
ruby examples/clean_demo.rb
|
|
91
76
|
```
|
|
92
77
|
|
|
93
78
|
**Covers:**
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
79
|
+
- Defining `clean` methods for resource cleanup
|
|
80
|
+
- Reverse dependency order execution
|
|
81
|
+
- `run_and_clean` combined operation
|
|
97
82
|
|
|
98
83
|
---
|
|
99
84
|
|
|
100
|
-
###
|
|
85
|
+
### 6. group_demo.rb - Task Output Grouping
|
|
101
86
|
|
|
102
|
-
|
|
87
|
+
Organize task output into logical phases with groups.
|
|
103
88
|
|
|
104
89
|
```bash
|
|
105
|
-
ruby examples/
|
|
90
|
+
ruby examples/group_demo.rb
|
|
106
91
|
```
|
|
107
92
|
|
|
108
93
|
**Covers:**
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
94
|
+
- `group("label") { ... }` for organizing output
|
|
95
|
+
- Groups displayed as children in progress tree
|
|
96
|
+
- Multiple groups within a single task
|
|
112
97
|
|
|
113
98
|
---
|
|
114
99
|
|
|
115
|
-
###
|
|
100
|
+
### 7. message_demo.rb - User-Facing Messages
|
|
116
101
|
|
|
117
|
-
|
|
102
|
+
Output messages that bypass the progress display capture.
|
|
118
103
|
|
|
119
104
|
```bash
|
|
120
|
-
ruby examples/
|
|
105
|
+
ruby examples/message_demo.rb
|
|
121
106
|
```
|
|
122
107
|
|
|
123
108
|
**Covers:**
|
|
124
|
-
- `
|
|
125
|
-
-
|
|
126
|
-
-
|
|
109
|
+
- `Taski.message(text)` for user-facing output
|
|
110
|
+
- Messages queued during progress and shown after completion
|
|
111
|
+
- Difference between `puts` (captured) and `Taski.message` (bypassed)
|
|
127
112
|
|
|
128
113
|
---
|
|
129
114
|
|
|
130
|
-
###
|
|
115
|
+
### 8. progress_demo.rb - Progress Display Modes
|
|
131
116
|
|
|
132
|
-
|
|
117
|
+
Real-time progress visualization during parallel execution.
|
|
133
118
|
|
|
134
119
|
```bash
|
|
135
|
-
|
|
120
|
+
ruby examples/progress_demo.rb # Tree mode
|
|
121
|
+
TASKI_PROGRESS_MODE=simple ruby examples/progress_demo.rb # Simple mode
|
|
122
|
+
TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb # Disabled
|
|
136
123
|
```
|
|
137
124
|
|
|
138
125
|
**Covers:**
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
126
|
+
- Tree progress display (default)
|
|
127
|
+
- Simple one-line progress display
|
|
128
|
+
- Parallel task execution
|
|
129
|
+
- Task output capture and streaming
|
|
130
|
+
- system() output integration
|
|
142
131
|
|
|
143
132
|
---
|
|
144
133
|
|
|
@@ -148,13 +137,12 @@ TASKI_PROGRESS_DISABLE=1 ruby examples/nested_section_demo.rb
|
|
|
148
137
|
|---------|---------|------------|
|
|
149
138
|
| quick_start | Exports API | Basic |
|
|
150
139
|
| section_demo | Section API | Intermediate |
|
|
151
|
-
| args_demo | Args API | Intermediate |
|
|
140
|
+
| args_demo | Args/Env API | Intermediate |
|
|
152
141
|
| reexecution_demo | Scope-Based Execution | Intermediate |
|
|
153
|
-
| data_pipeline_demo | ETL Pipeline | Advanced |
|
|
154
|
-
| parallel_progress_demo | Progress Display | Advanced |
|
|
155
142
|
| clean_demo | Lifecycle Management | Intermediate |
|
|
156
|
-
|
|
|
157
|
-
|
|
|
143
|
+
| group_demo | Output Grouping | Intermediate |
|
|
144
|
+
| message_demo | User Messages | Basic |
|
|
145
|
+
| progress_demo | Progress Display | Advanced |
|
|
158
146
|
|
|
159
147
|
## Running All Examples
|
|
160
148
|
|
|
@@ -163,7 +151,7 @@ TASKI_PROGRESS_DISABLE=1 ruby examples/nested_section_demo.rb
|
|
|
163
151
|
for f in examples/*.rb; do echo "=== $f ===" && ruby "$f" && echo; done
|
|
164
152
|
|
|
165
153
|
# Disable progress display if needed
|
|
166
|
-
TASKI_PROGRESS_DISABLE=1 ruby examples/
|
|
154
|
+
TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb
|
|
167
155
|
```
|
|
168
156
|
|
|
169
157
|
## Next Steps
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Demonstrates Taski.message API
|
|
5
|
+
#
|
|
6
|
+
# Taski.message outputs text to the user without being captured by TaskOutputRouter.
|
|
7
|
+
# Messages are queued during progress display and shown after task completion.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ruby examples/message_demo.rb
|
|
11
|
+
# TASKI_FORCE_PROGRESS=1 ruby examples/message_demo.rb # Force progress display
|
|
12
|
+
|
|
13
|
+
require_relative "../lib/taski"
|
|
14
|
+
|
|
15
|
+
class ProcessDataTask < Taski::Task
|
|
16
|
+
exports :processed_count
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
puts "Starting data processing..." # Captured by TaskOutputRouter
|
|
20
|
+
|
|
21
|
+
# Simulate processing
|
|
22
|
+
5.times do |i|
|
|
23
|
+
puts "Processing batch #{i + 1}/5..." # Captured
|
|
24
|
+
sleep 0.3
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@processed_count = 42
|
|
28
|
+
|
|
29
|
+
# These messages bypass TaskOutputRouter and appear after execution
|
|
30
|
+
Taski.message("Created: /tmp/output.txt")
|
|
31
|
+
Taski.message("Summary: #{@processed_count} items processed successfully")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class GenerateReportTask < Taski::Task
|
|
36
|
+
exports :report_path
|
|
37
|
+
|
|
38
|
+
def run
|
|
39
|
+
# Dependency: ProcessDataTask will be executed first
|
|
40
|
+
count = ProcessDataTask.processed_count
|
|
41
|
+
puts "Generating report for #{count} items..." # Captured
|
|
42
|
+
|
|
43
|
+
sleep 0.5
|
|
44
|
+
|
|
45
|
+
@report_path = "/tmp/report.pdf"
|
|
46
|
+
|
|
47
|
+
Taski.message("Report available at: #{@report_path}")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
puts "=== Taski.message Demo ==="
|
|
52
|
+
puts
|
|
53
|
+
|
|
54
|
+
GenerateReportTask.run
|
|
55
|
+
|
|
56
|
+
puts
|
|
57
|
+
puts "=== Done ==="
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Taski Progress Display Demo
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates the progress display modes:
|
|
7
|
+
# - Tree mode (default): Shows task hierarchy with real-time updates
|
|
8
|
+
# - Simple mode: One-line spinner with current task name
|
|
9
|
+
#
|
|
10
|
+
# Run:
|
|
11
|
+
# ruby examples/progress_demo.rb # Tree mode (default)
|
|
12
|
+
# TASKI_PROGRESS_MODE=simple ruby examples/progress_demo.rb # Simple mode
|
|
13
|
+
# TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb # No progress
|
|
14
|
+
#
|
|
15
|
+
# Covers:
|
|
16
|
+
# - Parallel task execution with progress display
|
|
17
|
+
# - Tree vs simple progress modes
|
|
18
|
+
# - Task output capture and display
|
|
19
|
+
# - system() output streaming
|
|
20
|
+
|
|
21
|
+
require_relative "../lib/taski"
|
|
22
|
+
|
|
23
|
+
# Configuration section with multiple implementations
|
|
24
|
+
class DatabaseSection < Taski::Section
|
|
25
|
+
interfaces :connection_string
|
|
26
|
+
|
|
27
|
+
def impl
|
|
28
|
+
(ENV["USE_PROD_DB"] == "1") ? ProductionDB : DevelopmentDB
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ProductionDB < Taski::Task
|
|
32
|
+
def run
|
|
33
|
+
puts "Connecting to production database..."
|
|
34
|
+
sleep(0.4)
|
|
35
|
+
@connection_string = "postgresql://prod-server:5432/myapp"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class DevelopmentDB < Taski::Task
|
|
40
|
+
def run
|
|
41
|
+
puts "Connecting to development database..."
|
|
42
|
+
sleep(0.3)
|
|
43
|
+
@connection_string = "postgresql://localhost:5432/myapp_dev"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Parallel download tasks (executed concurrently)
|
|
49
|
+
class DownloadLayer1 < Taski::Task
|
|
50
|
+
exports :layer1_data
|
|
51
|
+
|
|
52
|
+
def run
|
|
53
|
+
puts "Downloading base image..."
|
|
54
|
+
sleep(0.8)
|
|
55
|
+
puts "Base image complete"
|
|
56
|
+
@layer1_data = "Layer 1 (base)"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class DownloadLayer2 < Taski::Task
|
|
61
|
+
exports :layer2_data
|
|
62
|
+
|
|
63
|
+
def run
|
|
64
|
+
puts "Downloading dependencies..."
|
|
65
|
+
sleep(1.2)
|
|
66
|
+
puts "Dependencies complete"
|
|
67
|
+
@layer2_data = "Layer 2 (deps)"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class DownloadLayer3 < Taski::Task
|
|
72
|
+
exports :layer3_data
|
|
73
|
+
|
|
74
|
+
def run
|
|
75
|
+
puts "Downloading application..."
|
|
76
|
+
sleep(0.4)
|
|
77
|
+
puts "Application complete"
|
|
78
|
+
@layer3_data = "Layer 3 (app)"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Task that depends on all downloads (waits for parallel completion)
|
|
83
|
+
class ExtractLayers < Taski::Task
|
|
84
|
+
exports :extracted_data
|
|
85
|
+
|
|
86
|
+
def run
|
|
87
|
+
layer1 = DownloadLayer1.layer1_data
|
|
88
|
+
layer2 = DownloadLayer2.layer2_data
|
|
89
|
+
layer3 = DownloadLayer3.layer3_data
|
|
90
|
+
|
|
91
|
+
puts "Extracting layers..."
|
|
92
|
+
sleep(0.3)
|
|
93
|
+
@extracted_data = [layer1, layer2, layer3]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Task demonstrating system() output capture
|
|
98
|
+
class RunSystemCommand < Taski::Task
|
|
99
|
+
exports :command_result
|
|
100
|
+
|
|
101
|
+
def run
|
|
102
|
+
puts "Running system command..."
|
|
103
|
+
@command_result = system("echo 'Step 1: Preparing...' && sleep 0.2 && echo 'Step 2: Processing...' && sleep 0.2 && echo 'Step 3: Done'")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Final task combining all dependencies
|
|
108
|
+
class BuildApplication < Taski::Task
|
|
109
|
+
exports :result
|
|
110
|
+
|
|
111
|
+
def run
|
|
112
|
+
db = DatabaseSection.connection_string
|
|
113
|
+
layers = ExtractLayers.extracted_data
|
|
114
|
+
RunSystemCommand.command_result
|
|
115
|
+
|
|
116
|
+
puts "Building application..."
|
|
117
|
+
sleep(0.3)
|
|
118
|
+
puts "Finalizing build..."
|
|
119
|
+
sleep(0.2)
|
|
120
|
+
|
|
121
|
+
@result = {
|
|
122
|
+
database: db,
|
|
123
|
+
layers: layers,
|
|
124
|
+
status: "success"
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Main execution
|
|
130
|
+
puts "Taski Progress Display Demo"
|
|
131
|
+
puts "=" * 50
|
|
132
|
+
puts "Progress mode: #{Taski.progress_mode || "tree (default)"}"
|
|
133
|
+
puts
|
|
134
|
+
|
|
135
|
+
puts "Task Tree Structure:"
|
|
136
|
+
puts "-" * 50
|
|
137
|
+
puts BuildApplication.tree
|
|
138
|
+
puts "-" * 50
|
|
139
|
+
puts
|
|
140
|
+
|
|
141
|
+
# Reset for fresh execution
|
|
142
|
+
BuildApplication.reset!
|
|
143
|
+
|
|
144
|
+
# Execute (progress display is automatic)
|
|
145
|
+
result = BuildApplication.result
|
|
146
|
+
|
|
147
|
+
puts
|
|
148
|
+
puts "=" * 50
|
|
149
|
+
puts "Execution completed!"
|
|
150
|
+
puts "Result: #{result.inspect}"
|
|
151
|
+
puts
|
|
152
|
+
puts "Try different modes:"
|
|
153
|
+
puts " TASKI_PROGRESS_MODE=simple ruby examples/progress_demo.rb"
|
|
154
|
+
puts " TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb"
|
|
@@ -85,6 +85,7 @@ module Taski
|
|
|
85
85
|
@root_task_class = nil
|
|
86
86
|
@output_capture = nil
|
|
87
87
|
@start_time = nil
|
|
88
|
+
@message_queue = []
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
# Set the output capture for getting task output
|
|
@@ -199,6 +200,14 @@ module Taski
|
|
|
199
200
|
return unless should_stop
|
|
200
201
|
|
|
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 }
|
|
202
211
|
end
|
|
203
212
|
|
|
204
213
|
protected
|
|
@@ -283,6 +292,13 @@ module Taski
|
|
|
283
292
|
|
|
284
293
|
private
|
|
285
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
|
+
|
|
286
302
|
# Apply state transition to TaskProgress
|
|
287
303
|
def apply_state_transition(progress, state, duration, error)
|
|
288
304
|
case state
|
|
@@ -89,6 +89,14 @@ module Taski
|
|
|
89
89
|
@monitor.synchronize { !@output_capture.nil? }
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
# Get the original stdout before output capture was set up.
|
|
93
|
+
# Thread-safe accessor.
|
|
94
|
+
#
|
|
95
|
+
# @return [IO, nil] The original stdout or nil if not captured
|
|
96
|
+
def original_stdout
|
|
97
|
+
@monitor.synchronize { @original_stdout }
|
|
98
|
+
end
|
|
99
|
+
|
|
92
100
|
# Set up output capture for inline progress display.
|
|
93
101
|
# Creates TaskOutputRouter and replaces $stdout.
|
|
94
102
|
# Should only be called when progress display is active and not already set up.
|
|
@@ -97,7 +105,7 @@ module Taski
|
|
|
97
105
|
def setup_output_capture(output_io)
|
|
98
106
|
@monitor.synchronize do
|
|
99
107
|
@original_stdout = output_io
|
|
100
|
-
@output_capture = TaskOutputRouter.new(@original_stdout)
|
|
108
|
+
@output_capture = TaskOutputRouter.new(@original_stdout, self)
|
|
101
109
|
@output_capture.start_polling
|
|
102
110
|
$stdout = @output_capture
|
|
103
111
|
end
|
|
@@ -295,8 +303,7 @@ module Taski
|
|
|
295
303
|
dispatch(:start)
|
|
296
304
|
end
|
|
297
305
|
|
|
298
|
-
|
|
299
|
-
# Notify registered observers that execution has stopped.
|
|
306
|
+
# Notify observers to stop.
|
|
300
307
|
def notify_stop
|
|
301
308
|
dispatch(:stop)
|
|
302
309
|
end
|
|
@@ -34,6 +34,7 @@ module Taski
|
|
|
34
34
|
@spinner_index = 0
|
|
35
35
|
@renderer_thread = nil
|
|
36
36
|
@running = false
|
|
37
|
+
@section_candidates = {} # section_class => [candidate_classes]
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
protected
|
|
@@ -44,9 +45,23 @@ module Taski
|
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
# Template method: Called when a section impl is registered
|
|
47
|
-
def on_section_impl_registered(
|
|
48
|
+
def on_section_impl_registered(section_class, impl_class)
|
|
48
49
|
@tasks[impl_class] ||= TaskProgress.new
|
|
49
50
|
@tasks[impl_class].is_impl_candidate = false
|
|
51
|
+
|
|
52
|
+
# Mark the section itself as completed (it's represented by its impl)
|
|
53
|
+
if @tasks[section_class]
|
|
54
|
+
@tasks[section_class].run_state = :completed
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Mark unselected candidates as completed (skipped)
|
|
58
|
+
candidates = @section_candidates[section_class] || []
|
|
59
|
+
candidates.each do |candidate|
|
|
60
|
+
next if candidate == impl_class
|
|
61
|
+
progress = @tasks[candidate]
|
|
62
|
+
next unless progress
|
|
63
|
+
progress.run_state = :completed
|
|
64
|
+
end
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
# Template method: Determine if display should activate
|
|
@@ -83,6 +98,23 @@ module Taski
|
|
|
83
98
|
# Use TreeProgressDisplay's static method for tree building
|
|
84
99
|
tree = TreeProgressDisplay.build_tree_node(@root_task_class)
|
|
85
100
|
register_tasks_from_tree(tree)
|
|
101
|
+
collect_section_candidates(tree)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def collect_section_candidates(node)
|
|
105
|
+
return unless node
|
|
106
|
+
|
|
107
|
+
task_class = node[:task_class]
|
|
108
|
+
|
|
109
|
+
# If this is a section, collect its implementation candidates
|
|
110
|
+
if node[:is_section]
|
|
111
|
+
candidates = node[:children]
|
|
112
|
+
.select { |c| c[:is_impl_candidate] }
|
|
113
|
+
.map { |c| c[:task_class] }
|
|
114
|
+
@section_candidates[task_class] = candidates unless candidates.empty?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
node[:children].each { |child| collect_section_candidates(child) }
|
|
86
118
|
end
|
|
87
119
|
|
|
88
120
|
def render_live
|
|
@@ -121,7 +153,8 @@ module Taski
|
|
|
121
153
|
def build_status_line
|
|
122
154
|
running_tasks = @tasks.select { |_, p| p.run_state == :running }
|
|
123
155
|
cleaning_tasks = @tasks.select { |_, p| p.clean_state == :cleaning }
|
|
124
|
-
|
|
156
|
+
# Count both completed and failed tasks as "done"
|
|
157
|
+
done = @tasks.values.count { |p| p.run_state == :completed || p.run_state == :failed }
|
|
125
158
|
failed = @tasks.values.count { |p| p.run_state == :failed }
|
|
126
159
|
total = @tasks.size
|
|
127
160
|
|
|
@@ -147,7 +180,7 @@ module Taski
|
|
|
147
180
|
# Get last output message if available
|
|
148
181
|
output_suffix = build_output_suffix(running_tasks.keys.first || cleaning_tasks.keys.first)
|
|
149
182
|
|
|
150
|
-
parts = ["#{status_icon} [#{
|
|
183
|
+
parts = ["#{status_icon} [#{done}/#{total}]"]
|
|
151
184
|
parts << task_names if task_names && !task_names.empty?
|
|
152
185
|
parts << "|" << output_suffix if output_suffix
|
|
153
186
|
|
|
@@ -22,9 +22,10 @@ module Taski
|
|
|
22
22
|
READ_BUFFER_SIZE = 4096
|
|
23
23
|
MAX_RECENT_LINES = 30 # Maximum number of recent lines to keep per task
|
|
24
24
|
|
|
25
|
-
def initialize(original_stdout)
|
|
25
|
+
def initialize(original_stdout, execution_context = nil)
|
|
26
26
|
super()
|
|
27
27
|
@original = original_stdout
|
|
28
|
+
@execution_context = execution_context
|
|
28
29
|
@pipes = {} # task_class => TaskOutputPipe
|
|
29
30
|
@thread_map = {} # Thread => task_class
|
|
30
31
|
@recent_lines = {} # task_class => Array<String>
|
data/lib/taski/version.rb
CHANGED