taski 0.9.1 → 0.9.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 +12 -0
- data/lib/taski/progress/config.rb +90 -0
- data/lib/taski/progress/layout/base.rb +25 -3
- data/lib/taski/progress/layout/simple.rb +17 -31
- data/lib/taski/progress/layout/theme_drop.rb +1 -1
- data/lib/taski/progress/layout/tree/event.rb +49 -0
- data/lib/taski/progress/layout/tree/live.rb +85 -0
- data/lib/taski/progress/layout/tree/structure.rb +142 -0
- data/lib/taski/progress/layout/tree.rb +25 -283
- data/lib/taski/progress/theme/base.rb +1 -1
- data/lib/taski/progress/theme/compact.rb +1 -1
- data/lib/taski/task.rb +1 -1
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +23 -7
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6cf783b65df1fdbbb8c8f19d78467243ea1d054c1b47097cae24b11d22d30c56
|
|
4
|
+
data.tar.gz: f5a88d4f88a6babb6e121b93cd22066691563a539c722cd8a3e336258ebfe495
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff7ccc6b57e2ed321a84406e2e8e913aac4ec5d9cf2e6f3d00f28cc9b22bbb6609147d500a3fca1dde7fe37bb921cd8ba00380c9b52962f71ea5e2331bef8e16
|
|
7
|
+
data.tar.gz: 5978f76dab28ef8cc3f57338a89996e9ba45fce7c897cf7739e7ada9076e5fc0006bb5663a1723c5492140fec8e14f5811100a8325e96dc56ebdf35684aeca0b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.2] - 2026-02-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Progress::Config API for declarative layout/theme configuration ([#180](https://github.com/ahogappa/taski/pull/180))
|
|
14
|
+
- Split Layout::Tree into Tree::Live (TTY) and Tree::Event (non-TTY) with `Tree.for` factory ([#181](https://github.com/ahogappa/taski/pull/181))
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Add base64 as runtime dependency ([#182](https://github.com/ahogappa/taski/pull/182))
|
|
18
|
+
- Use done_count instead of completed_count in completion display ([#179](https://github.com/ahogappa/taski/pull/179))
|
|
19
|
+
- Pass skipped_count in Simple layout's render_final ([#179](https://github.com/ahogappa/taski/pull/179))
|
|
20
|
+
- Show most recently started tasks first in simple progress display ([#178](https://github.com/ahogappa/taski/pull/178))
|
|
21
|
+
|
|
10
22
|
## [0.9.1] - 2026-02-16
|
|
11
23
|
|
|
12
24
|
### Changed
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taski
|
|
4
|
+
module Progress
|
|
5
|
+
# Configuration for progress display.
|
|
6
|
+
# Holds class references for Layout and Theme, and builds display instances lazily.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# Taski.progress.layout = Taski::Progress::Layout::Tree
|
|
10
|
+
# Taski.progress.theme = Taski::Progress::Theme::Detail
|
|
11
|
+
class Config
|
|
12
|
+
attr_reader :layout, :theme, :output
|
|
13
|
+
|
|
14
|
+
# @param on_invalidate [Proc, nil] Called when config changes (to clear external caches)
|
|
15
|
+
def initialize(&on_invalidate)
|
|
16
|
+
@layout = nil
|
|
17
|
+
@theme = nil
|
|
18
|
+
@output = nil
|
|
19
|
+
@cached_display = nil
|
|
20
|
+
@on_invalidate = on_invalidate
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def layout=(klass)
|
|
24
|
+
validate_layout!(klass) if klass
|
|
25
|
+
@layout = klass
|
|
26
|
+
invalidate!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def theme=(klass)
|
|
30
|
+
validate_theme!(klass) if klass
|
|
31
|
+
@theme = klass
|
|
32
|
+
invalidate!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def output=(io)
|
|
36
|
+
@output = io
|
|
37
|
+
invalidate!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Build a Layout instance from the current config.
|
|
41
|
+
# Returns a cached instance if config hasn't changed.
|
|
42
|
+
def build
|
|
43
|
+
@cached_display ||= build_display
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Reset all settings to defaults.
|
|
47
|
+
def reset
|
|
48
|
+
@layout = nil
|
|
49
|
+
@theme = nil
|
|
50
|
+
@output = nil
|
|
51
|
+
invalidate!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def invalidate!
|
|
57
|
+
@cached_display = nil
|
|
58
|
+
@on_invalidate&.call
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def build_display
|
|
62
|
+
layout_ref = @layout || Layout::Simple
|
|
63
|
+
args = {}
|
|
64
|
+
args[:theme] = @theme.new if @theme
|
|
65
|
+
args[:output] = @output if @output
|
|
66
|
+
|
|
67
|
+
if layout_ref.respond_to?(:for)
|
|
68
|
+
layout_ref.for(**args)
|
|
69
|
+
else
|
|
70
|
+
layout_ref.new(**args)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_layout!(klass)
|
|
75
|
+
# Accept a Class that inherits from Base, or a Module with .for factory
|
|
76
|
+
valid = (klass.is_a?(Class) && klass <= Layout::Base) ||
|
|
77
|
+
(klass.is_a?(Module) && klass.respond_to?(:for))
|
|
78
|
+
unless valid
|
|
79
|
+
raise ArgumentError, "layout must be a Layout::Base subclass or a module with .for, got #{klass.inspect}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validate_theme!(klass)
|
|
84
|
+
unless klass.is_a?(Class) && klass <= Theme::Base
|
|
85
|
+
raise ArgumentError, "theme must be a subclass of Taski::Progress::Theme::Base, got #{klass.inspect}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -267,7 +267,7 @@ module Taski
|
|
|
267
267
|
if failed_count > 0
|
|
268
268
|
render_execution_failed(failed_count: failed_count, total_count: total_count, total_duration: total_duration, skipped_count: skipped_count)
|
|
269
269
|
else
|
|
270
|
-
render_execution_completed(
|
|
270
|
+
render_execution_completed(done_count: done_count, total_count: total_count, total_duration: total_duration, skipped_count: skipped_count)
|
|
271
271
|
end
|
|
272
272
|
end
|
|
273
273
|
|
|
@@ -372,8 +372,8 @@ module Taski
|
|
|
372
372
|
end
|
|
373
373
|
|
|
374
374
|
# Render execution complete event
|
|
375
|
-
def render_execution_completed(
|
|
376
|
-
execution = ExecutionDrop.new(state: :completed,
|
|
375
|
+
def render_execution_completed(done_count:, total_count:, total_duration:, skipped_count: 0)
|
|
376
|
+
execution = ExecutionDrop.new(state: :completed, done_count:, total_count:, total_duration:, skipped_count:)
|
|
377
377
|
render_execution_template(:execution_complete, execution:)
|
|
378
378
|
end
|
|
379
379
|
|
|
@@ -515,6 +515,28 @@ module Taski
|
|
|
515
515
|
task_class.name || task_class.to_s
|
|
516
516
|
end
|
|
517
517
|
|
|
518
|
+
# Start a periodic render loop in a background thread.
|
|
519
|
+
# Starts spinner timer and calls the given block at @theme.render_interval.
|
|
520
|
+
# @yield Block to execute each render cycle (called under @monitor.synchronize)
|
|
521
|
+
def render_loop(&block)
|
|
522
|
+
@render_thread_running = true
|
|
523
|
+
start_spinner_timer
|
|
524
|
+
@render_thread = Thread.new do
|
|
525
|
+
while @render_thread_running
|
|
526
|
+
@monitor.synchronize(&block)
|
|
527
|
+
sleep @theme.render_interval
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Stop the periodic render loop and spinner timer.
|
|
533
|
+
def stop_render_loop
|
|
534
|
+
@render_thread_running = false
|
|
535
|
+
@render_thread&.join
|
|
536
|
+
@render_thread = nil
|
|
537
|
+
stop_spinner_timer
|
|
538
|
+
end
|
|
539
|
+
|
|
518
540
|
# Check if output is a TTY
|
|
519
541
|
def tty?
|
|
520
542
|
@output.tty?
|
|
@@ -36,9 +36,6 @@ module Taski
|
|
|
36
36
|
def initialize(output: $stdout, theme: nil)
|
|
37
37
|
theme ||= Theme::Compact.new
|
|
38
38
|
super
|
|
39
|
-
@renderer_thread = nil
|
|
40
|
-
@running = false
|
|
41
|
-
@running_mutex = Mutex.new
|
|
42
39
|
end
|
|
43
40
|
|
|
44
41
|
protected
|
|
@@ -70,38 +67,26 @@ module Taski
|
|
|
70
67
|
end
|
|
71
68
|
|
|
72
69
|
def handle_start
|
|
73
|
-
@running_mutex.synchronize { @running = true }
|
|
74
|
-
start_spinner_timer
|
|
75
70
|
@output.print "\e[?25l" # Hide cursor
|
|
76
|
-
|
|
77
|
-
loop do
|
|
78
|
-
break unless @running_mutex.synchronize { @running }
|
|
79
|
-
render_live
|
|
80
|
-
sleep @theme.render_interval
|
|
81
|
-
end
|
|
82
|
-
end
|
|
71
|
+
render_loop { render_status_line }
|
|
83
72
|
end
|
|
84
73
|
|
|
85
74
|
def handle_stop
|
|
86
|
-
|
|
87
|
-
@renderer_thread&.join
|
|
88
|
-
stop_spinner_timer
|
|
75
|
+
stop_render_loop
|
|
89
76
|
@output.print "\e[?25h" # Show cursor
|
|
90
77
|
render_final
|
|
91
78
|
end
|
|
92
79
|
|
|
93
80
|
private
|
|
94
81
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@output.flush
|
|
104
|
-
end
|
|
82
|
+
def render_status_line
|
|
83
|
+
line = build_status_line
|
|
84
|
+
# Truncate line to terminal width to prevent line wrap
|
|
85
|
+
max_width = terminal_width - 1 # Leave space for cursor
|
|
86
|
+
line = line[0, max_width] if line.length > max_width
|
|
87
|
+
# Clear line and write new content
|
|
88
|
+
@output.print "\r\e[K#{line}"
|
|
89
|
+
@output.flush
|
|
105
90
|
end
|
|
106
91
|
|
|
107
92
|
def terminal_width
|
|
@@ -113,9 +98,9 @@ module Taski
|
|
|
113
98
|
def render_final
|
|
114
99
|
@monitor.synchronize do
|
|
115
100
|
line = if failed_count > 0
|
|
116
|
-
render_execution_failed(failed_count: failed_count, total_count: total_count, total_duration: total_duration)
|
|
101
|
+
render_execution_failed(failed_count: failed_count, total_count: total_count, total_duration: total_duration, skipped_count: skipped_count)
|
|
117
102
|
else
|
|
118
|
-
render_execution_completed(
|
|
103
|
+
render_execution_completed(done_count: done_count, total_count: total_count, total_duration: total_duration, skipped_count: skipped_count)
|
|
119
104
|
end
|
|
120
105
|
|
|
121
106
|
@output.print "\r\e[K#{line}\n"
|
|
@@ -126,7 +111,7 @@ module Taski
|
|
|
126
111
|
def build_status_line
|
|
127
112
|
task_names = collect_current_task_names
|
|
128
113
|
|
|
129
|
-
primary_task = running_tasks.keys.
|
|
114
|
+
primary_task = running_tasks.keys.last || cleaning_tasks.keys.last
|
|
130
115
|
task_stdout = build_task_stdout(primary_task)
|
|
131
116
|
|
|
132
117
|
render_execution_running(
|
|
@@ -139,12 +124,13 @@ module Taski
|
|
|
139
124
|
|
|
140
125
|
def collect_current_task_names
|
|
141
126
|
# Prioritize: cleaning > running > pending
|
|
127
|
+
# Reverse so most recently started tasks appear first
|
|
142
128
|
current_tasks = if cleaning_tasks.any?
|
|
143
|
-
cleaning_tasks.keys
|
|
129
|
+
cleaning_tasks.keys.reverse
|
|
144
130
|
elsif running_tasks.any?
|
|
145
|
-
running_tasks.keys
|
|
131
|
+
running_tasks.keys.reverse
|
|
146
132
|
elsif pending_tasks.any?
|
|
147
|
-
pending_tasks.keys
|
|
133
|
+
pending_tasks.keys.reverse
|
|
148
134
|
else
|
|
149
135
|
[]
|
|
150
136
|
end
|
|
@@ -76,7 +76,7 @@ module Taski
|
|
|
76
76
|
# failed_count, total_count, total_duration, root_task_name, task_names
|
|
77
77
|
#
|
|
78
78
|
# @example Using in Liquid template
|
|
79
|
-
# [{{ execution.
|
|
79
|
+
# [{{ execution.done_count }}/{{ execution.total_count }}]
|
|
80
80
|
# {{ execution.total_duration | format_duration }}
|
|
81
81
|
class ExecutionDrop < DataDrop; end
|
|
82
82
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../theme/detail"
|
|
5
|
+
require_relative "structure"
|
|
6
|
+
|
|
7
|
+
module Taski
|
|
8
|
+
module Progress
|
|
9
|
+
module Layout
|
|
10
|
+
module Tree
|
|
11
|
+
# Non-TTY event-driven tree layout.
|
|
12
|
+
# Outputs lines immediately with tree prefixes as events arrive.
|
|
13
|
+
# Used for logs, CI, piped output, and static tree display.
|
|
14
|
+
class Event < Base
|
|
15
|
+
include Structure
|
|
16
|
+
|
|
17
|
+
def initialize(output: $stderr, theme: nil)
|
|
18
|
+
theme ||= Theme::Detail.new
|
|
19
|
+
super
|
|
20
|
+
init_tree_structure
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def handle_ready
|
|
26
|
+
build_ready_tree
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def handle_task_update(task_class, current_state, phase)
|
|
30
|
+
progress = @tasks[task_class]
|
|
31
|
+
duration = compute_duration(progress, phase)
|
|
32
|
+
text = render_for_task_event(task_class, current_state, duration, nil, phase)
|
|
33
|
+
output_with_prefix(task_class, text) if text
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def handle_group_started(task_class, group_name, phase)
|
|
37
|
+
text = render_group_started(task_class, group_name: group_name)
|
|
38
|
+
output_with_prefix(task_class, text) if text
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_group_completed(task_class, group_name, phase, duration)
|
|
42
|
+
text = render_group_succeeded(task_class, group_name: group_name, task_duration: duration)
|
|
43
|
+
output_with_prefix(task_class, text) if text
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
require_relative "../../theme/detail"
|
|
5
|
+
require_relative "structure"
|
|
6
|
+
|
|
7
|
+
module Taski
|
|
8
|
+
module Progress
|
|
9
|
+
module Layout
|
|
10
|
+
module Tree
|
|
11
|
+
# TTY periodic-update tree layout.
|
|
12
|
+
# Refreshes the entire tree display at regular intervals with spinner animation.
|
|
13
|
+
# Used for interactive terminal output.
|
|
14
|
+
class Live < Base
|
|
15
|
+
include Structure
|
|
16
|
+
|
|
17
|
+
def initialize(output: $stderr, theme: nil)
|
|
18
|
+
theme ||= Theme::Detail.new
|
|
19
|
+
super
|
|
20
|
+
init_tree_structure
|
|
21
|
+
@last_line_count = 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def handle_ready
|
|
27
|
+
build_ready_tree
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# TTY mode: skip per-event output, tree is updated by render_loop
|
|
31
|
+
def handle_task_update(_task_class, _current_state, _phase)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle_group_started(_task_class, _group_name, _phase)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def handle_group_completed(_task_class, _group_name, _phase, _duration)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def should_activate?
|
|
41
|
+
tty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def handle_start
|
|
45
|
+
@output.print "\e[?25l" # Hide cursor
|
|
46
|
+
render_loop { render_tree_live }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def handle_stop
|
|
50
|
+
stop_render_loop
|
|
51
|
+
@output.print "\e[?25h" # Show cursor
|
|
52
|
+
render_final
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def render_tree_live
|
|
58
|
+
lines = build_tree_lines
|
|
59
|
+
clear_previous_output
|
|
60
|
+
lines.each { |line| @output.puts line }
|
|
61
|
+
@output.flush
|
|
62
|
+
@last_line_count = lines.size
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def render_final
|
|
66
|
+
@monitor.synchronize do
|
|
67
|
+
lines = build_tree_lines
|
|
68
|
+
clear_previous_output
|
|
69
|
+
|
|
70
|
+
lines.each { |line| @output.puts line }
|
|
71
|
+
@output.puts render_execution_summary
|
|
72
|
+
@output.flush
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clear_previous_output
|
|
77
|
+
return if @last_line_count == 0
|
|
78
|
+
# Move cursor up and clear lines
|
|
79
|
+
@output.print "\e[#{@last_line_count}A\e[J"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taski
|
|
4
|
+
module Progress
|
|
5
|
+
module Layout
|
|
6
|
+
module Tree
|
|
7
|
+
# Shared tree structure logic for Tree::Live and Tree::Event.
|
|
8
|
+
# Provides tree building, node registration, prefix generation,
|
|
9
|
+
# and tree rendering methods.
|
|
10
|
+
module Structure
|
|
11
|
+
# Tree connector characters
|
|
12
|
+
BRANCH = "├── "
|
|
13
|
+
LAST_BRANCH = "└── "
|
|
14
|
+
VERTICAL = "│ "
|
|
15
|
+
SPACE = " "
|
|
16
|
+
|
|
17
|
+
# Returns the tree structure as a string.
|
|
18
|
+
# Uses the current theme to render task content for each node.
|
|
19
|
+
def render_tree
|
|
20
|
+
build_tree_lines.join("\n") + "\n"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def init_tree_structure
|
|
26
|
+
@tree_nodes = {}
|
|
27
|
+
@node_depths = {}
|
|
28
|
+
@node_is_last = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build_ready_tree
|
|
32
|
+
graph = context&.dependency_graph
|
|
33
|
+
root = context&.root_task_class
|
|
34
|
+
return unless graph && root
|
|
35
|
+
|
|
36
|
+
tree = build_tree_from_graph(root, graph)
|
|
37
|
+
register_tree_nodes(tree, depth: 0, is_last: true, ancestors_last: [])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Output text with tree prefix for the given task
|
|
41
|
+
def output_with_prefix(task_class, text)
|
|
42
|
+
prefix = build_tree_prefix(task_class)
|
|
43
|
+
output_line("#{prefix}#{text}")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def build_tree_from_graph(task_class, graph, ancestors = Set.new)
|
|
49
|
+
is_circular = ancestors.include?(task_class)
|
|
50
|
+
node = {task_class: task_class, is_circular: is_circular, children: []}
|
|
51
|
+
return node if is_circular
|
|
52
|
+
|
|
53
|
+
new_ancestors = ancestors + [task_class]
|
|
54
|
+
deps = graph.dependencies_for(task_class)
|
|
55
|
+
deps.each do |dep|
|
|
56
|
+
child_node = build_tree_from_graph(dep, graph, new_ancestors)
|
|
57
|
+
node[:children] << child_node
|
|
58
|
+
end
|
|
59
|
+
node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def register_tree_nodes(node, depth:, is_last:, ancestors_last:)
|
|
63
|
+
return unless node
|
|
64
|
+
|
|
65
|
+
task_class = node[:task_class]
|
|
66
|
+
@tasks[task_class] ||= new_task_progress
|
|
67
|
+
@tree_nodes[task_class] = node
|
|
68
|
+
@node_depths[task_class] = depth
|
|
69
|
+
@node_is_last[task_class] = {is_last: is_last, ancestors_last: ancestors_last.dup}
|
|
70
|
+
|
|
71
|
+
children = node[:children]
|
|
72
|
+
children.each_with_index do |child, index|
|
|
73
|
+
child_is_last = (index == children.size - 1)
|
|
74
|
+
new_ancestors_last = ancestors_last + [is_last]
|
|
75
|
+
register_tree_nodes(child, depth: depth + 1, is_last: child_is_last, ancestors_last: new_ancestors_last)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_tree_lines
|
|
80
|
+
return [] unless @root_task_class
|
|
81
|
+
|
|
82
|
+
lines = []
|
|
83
|
+
root_node = @tree_nodes[@root_task_class]
|
|
84
|
+
build_node_lines(root_node, lines)
|
|
85
|
+
lines
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def build_node_lines(node, lines)
|
|
89
|
+
return unless node
|
|
90
|
+
|
|
91
|
+
task_class = node[:task_class]
|
|
92
|
+
prefix = build_tree_prefix(task_class)
|
|
93
|
+
content = build_task_content(task_class)
|
|
94
|
+
lines << "#{prefix}#{content}"
|
|
95
|
+
|
|
96
|
+
node[:children].each do |child|
|
|
97
|
+
build_node_lines(child, lines)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def build_task_content(task_class)
|
|
102
|
+
progress = @tasks[task_class]
|
|
103
|
+
|
|
104
|
+
case progress&.dig(:run_state)
|
|
105
|
+
when :running
|
|
106
|
+
render_task_started(task_class)
|
|
107
|
+
when :completed
|
|
108
|
+
render_task_succeeded(task_class, task_duration: compute_duration(progress, :run))
|
|
109
|
+
when :failed
|
|
110
|
+
render_task_failed(task_class, error: nil)
|
|
111
|
+
when :skipped
|
|
112
|
+
render_task_skipped(task_class)
|
|
113
|
+
else
|
|
114
|
+
task = TaskDrop.new(name: task_class_name(task_class), state: :pending)
|
|
115
|
+
render_task_template(:task_pending, task:, execution: execution_drop)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_tree_prefix(task_class)
|
|
120
|
+
depth = @node_depths[task_class]
|
|
121
|
+
return "" if depth.nil? || depth == 0
|
|
122
|
+
|
|
123
|
+
last_info = @node_is_last[task_class]
|
|
124
|
+
return "" unless last_info
|
|
125
|
+
|
|
126
|
+
ancestors_last = last_info[:ancestors_last]
|
|
127
|
+
is_last = last_info[:is_last]
|
|
128
|
+
|
|
129
|
+
prefix = ""
|
|
130
|
+
# Skip the first ancestor (root) since root has no visual prefix
|
|
131
|
+
ancestors_last[1..].each do |ancestor_is_last|
|
|
132
|
+
prefix += ancestor_is_last ? SPACE : VERTICAL
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
prefix += is_last ? LAST_BRANCH : BRANCH
|
|
136
|
+
prefix
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -1,299 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
require_relative "
|
|
3
|
+
require_relative "tree/structure"
|
|
4
|
+
require_relative "tree/live"
|
|
5
|
+
require_relative "tree/event"
|
|
5
6
|
|
|
6
7
|
module Taski
|
|
7
8
|
module Progress
|
|
8
9
|
module Layout
|
|
9
|
-
# Tree layout for hierarchical task display.
|
|
10
|
-
# Renders tasks in a tree structure with visual connectors
|
|
10
|
+
# Tree layout module for hierarchical task display.
|
|
11
|
+
# Renders tasks in a tree structure with visual connectors.
|
|
11
12
|
#
|
|
12
|
-
#
|
|
13
|
-
# - TTY
|
|
14
|
-
# - Non-TTY
|
|
13
|
+
# Contains two implementations:
|
|
14
|
+
# - Tree::Live — TTY periodic-update with spinner animation
|
|
15
|
+
# - Tree::Event — Non-TTY event-driven incremental output
|
|
15
16
|
#
|
|
16
|
-
#
|
|
17
|
-
# BuildApplication
|
|
18
|
-
# ├── ⠹ SetupDatabase
|
|
19
|
-
# │ ├── ✓ CreateSchema (50ms)
|
|
20
|
-
# │ └── ⠹ SeedData
|
|
21
|
-
# ├── ○ ExtractLayers
|
|
22
|
-
# │ ├── ✓ DownloadLayer1 (100ms)
|
|
23
|
-
# │ └── ○ DownloadLayer2
|
|
24
|
-
# └── ✓ RunSystemCommand (200ms)
|
|
17
|
+
# Use Tree.for to automatically select the appropriate implementation.
|
|
25
18
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
19
|
+
# @example Auto-select based on output TTY
|
|
20
|
+
# layout = Taski::Progress::Layout::Tree.for
|
|
28
21
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def initialize(output: $stderr, theme: nil)
|
|
43
|
-
theme ||= Theme::Detail.new
|
|
44
|
-
super
|
|
45
|
-
@tree_nodes = {}
|
|
46
|
-
@node_depths = {}
|
|
47
|
-
@node_is_last = {}
|
|
48
|
-
@renderer_thread = nil
|
|
49
|
-
@running = false
|
|
50
|
-
@running_mutex = Mutex.new
|
|
51
|
-
@last_line_count = 0
|
|
52
|
-
@non_tty_started = false
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Returns the tree structure as a string.
|
|
56
|
-
# Uses the current theme to render task content for each node.
|
|
57
|
-
def render_tree
|
|
58
|
-
build_tree_lines.join("\n") + "\n"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Override on_start to handle non-TTY mode
|
|
62
|
-
def on_start
|
|
63
|
-
@monitor.synchronize do
|
|
64
|
-
@nest_level += 1
|
|
65
|
-
return if @nest_level > 1
|
|
66
|
-
|
|
67
|
-
@start_time = Time.now
|
|
68
|
-
|
|
69
|
-
if should_activate?
|
|
70
|
-
@active = true
|
|
71
|
-
else
|
|
72
|
-
# Non-TTY mode: output execution start message
|
|
73
|
-
@non_tty_started = true
|
|
74
|
-
output_line(render_execution_started(@root_task_class)) if @root_task_class
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
handle_start if @active
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Override on_stop to handle non-TTY mode
|
|
82
|
-
def on_stop
|
|
83
|
-
was_active = false
|
|
84
|
-
non_tty_was_started = false
|
|
85
|
-
@monitor.synchronize do
|
|
86
|
-
@nest_level -= 1 if @nest_level > 0
|
|
87
|
-
return unless @nest_level == 0
|
|
88
|
-
was_active = @active
|
|
89
|
-
non_tty_was_started = @non_tty_started
|
|
90
|
-
@active = false
|
|
91
|
-
@non_tty_started = false
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
if was_active
|
|
95
|
-
handle_stop
|
|
96
|
-
elsif non_tty_was_started
|
|
97
|
-
# Non-TTY mode: output execution summary
|
|
98
|
-
output_execution_summary
|
|
99
|
-
end
|
|
100
|
-
flush_queued_messages
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
protected
|
|
104
|
-
|
|
105
|
-
def handle_ready
|
|
106
|
-
graph = context&.dependency_graph
|
|
107
|
-
root = context&.root_task_class
|
|
108
|
-
return unless graph && root
|
|
109
|
-
|
|
110
|
-
tree = build_tree_from_graph(root, graph)
|
|
111
|
-
register_tree_nodes(tree, depth: 0, is_last: true, ancestors_last: [])
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# In TTY mode, tree is updated by render_live periodically.
|
|
115
|
-
# In non-TTY mode, output lines immediately with tree prefix.
|
|
116
|
-
def handle_task_update(task_class, current_state, phase)
|
|
117
|
-
return if @active # TTY mode: skip per-event output
|
|
118
|
-
|
|
119
|
-
# Non-TTY mode: output with tree prefix
|
|
120
|
-
progress = @tasks[task_class]
|
|
121
|
-
duration = compute_duration(progress, phase)
|
|
122
|
-
text = render_for_task_event(task_class, current_state, duration, nil, phase)
|
|
123
|
-
output_with_prefix(task_class, text) if text
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def handle_group_started(task_class, group_name, phase)
|
|
127
|
-
return if @active # TTY mode: skip per-event output
|
|
128
|
-
|
|
129
|
-
# Non-TTY mode: output with tree prefix
|
|
130
|
-
text = render_group_started(task_class, group_name: group_name)
|
|
131
|
-
output_with_prefix(task_class, text) if text
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def handle_group_completed(task_class, group_name, phase, duration)
|
|
135
|
-
return if @active # TTY mode: skip per-event output
|
|
136
|
-
|
|
137
|
-
# Non-TTY mode: output with tree prefix
|
|
138
|
-
text = render_group_succeeded(task_class, group_name: group_name, task_duration: duration)
|
|
139
|
-
output_with_prefix(task_class, text) if text
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def should_activate?
|
|
143
|
-
tty?
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def handle_start
|
|
147
|
-
@running_mutex.synchronize { @running = true }
|
|
148
|
-
start_spinner_timer
|
|
149
|
-
@output.print "\e[?25l" # Hide cursor
|
|
150
|
-
@renderer_thread = Thread.new do
|
|
151
|
-
loop do
|
|
152
|
-
break unless @running_mutex.synchronize { @running }
|
|
153
|
-
render_live
|
|
154
|
-
sleep @theme.render_interval
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def handle_stop
|
|
160
|
-
@running_mutex.synchronize { @running = false }
|
|
161
|
-
@renderer_thread&.join
|
|
162
|
-
stop_spinner_timer
|
|
163
|
-
@output.print "\e[?25h" # Show cursor
|
|
164
|
-
render_final
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
private
|
|
168
|
-
|
|
169
|
-
# Output text with tree prefix for the given task
|
|
170
|
-
def output_with_prefix(task_class, text)
|
|
171
|
-
prefix = build_tree_prefix(task_class)
|
|
172
|
-
output_line("#{prefix}#{text}")
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Output execution summary for non-TTY mode
|
|
176
|
-
def output_execution_summary
|
|
177
|
-
output_line(render_execution_summary)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def build_tree_from_graph(task_class, graph, ancestors = Set.new)
|
|
181
|
-
is_circular = ancestors.include?(task_class)
|
|
182
|
-
node = {task_class: task_class, is_circular: is_circular, children: []}
|
|
183
|
-
return node if is_circular
|
|
184
|
-
|
|
185
|
-
new_ancestors = ancestors + [task_class]
|
|
186
|
-
deps = graph.dependencies_for(task_class)
|
|
187
|
-
deps.each do |dep|
|
|
188
|
-
child_node = build_tree_from_graph(dep, graph, new_ancestors)
|
|
189
|
-
node[:children] << child_node
|
|
190
|
-
end
|
|
191
|
-
node
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def register_tree_nodes(node, depth:, is_last:, ancestors_last:)
|
|
195
|
-
return unless node
|
|
196
|
-
|
|
197
|
-
task_class = node[:task_class]
|
|
198
|
-
@tasks[task_class] ||= new_task_progress
|
|
199
|
-
@tree_nodes[task_class] = node
|
|
200
|
-
@node_depths[task_class] = depth
|
|
201
|
-
@node_is_last[task_class] = {is_last: is_last, ancestors_last: ancestors_last.dup}
|
|
202
|
-
|
|
203
|
-
children = node[:children]
|
|
204
|
-
children.each_with_index do |child, index|
|
|
205
|
-
child_is_last = (index == children.size - 1)
|
|
206
|
-
new_ancestors_last = ancestors_last + [is_last]
|
|
207
|
-
register_tree_nodes(child, depth: depth + 1, is_last: child_is_last, ancestors_last: new_ancestors_last)
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def render_live
|
|
212
|
-
@monitor.synchronize do
|
|
213
|
-
lines = build_tree_lines
|
|
214
|
-
clear_previous_output
|
|
215
|
-
lines.each { |line| @output.puts line }
|
|
216
|
-
@output.flush
|
|
217
|
-
@last_line_count = lines.size
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def render_final
|
|
222
|
-
@monitor.synchronize do
|
|
223
|
-
lines = build_tree_lines
|
|
224
|
-
clear_previous_output
|
|
225
|
-
|
|
226
|
-
lines.each { |line| @output.puts line }
|
|
227
|
-
@output.puts render_execution_summary
|
|
228
|
-
@output.flush
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def clear_previous_output
|
|
233
|
-
return if @last_line_count == 0
|
|
234
|
-
# Move cursor up and clear lines
|
|
235
|
-
@output.print "\e[#{@last_line_count}A\e[J"
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def build_tree_lines
|
|
239
|
-
return [] unless @root_task_class
|
|
240
|
-
|
|
241
|
-
lines = []
|
|
242
|
-
root_node = @tree_nodes[@root_task_class]
|
|
243
|
-
build_node_lines(root_node, lines)
|
|
244
|
-
lines
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def build_node_lines(node, lines)
|
|
248
|
-
return unless node
|
|
249
|
-
|
|
250
|
-
task_class = node[:task_class]
|
|
251
|
-
prefix = build_tree_prefix(task_class)
|
|
252
|
-
content = build_task_content(task_class)
|
|
253
|
-
lines << "#{prefix}#{content}"
|
|
254
|
-
|
|
255
|
-
node[:children].each do |child|
|
|
256
|
-
build_node_lines(child, lines)
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
def build_task_content(task_class)
|
|
261
|
-
progress = @tasks[task_class]
|
|
262
|
-
|
|
263
|
-
case progress&.dig(:run_state)
|
|
264
|
-
when :running
|
|
265
|
-
render_task_started(task_class)
|
|
266
|
-
when :completed
|
|
267
|
-
render_task_succeeded(task_class, task_duration: compute_duration(progress, :run))
|
|
268
|
-
when :failed
|
|
269
|
-
render_task_failed(task_class, error: nil)
|
|
270
|
-
when :skipped
|
|
271
|
-
render_task_skipped(task_class)
|
|
22
|
+
# @example Explicit selection
|
|
23
|
+
# layout = Taski::Progress::Layout::Tree::Live.new # TTY
|
|
24
|
+
# layout = Taski::Progress::Layout::Tree::Event.new # non-TTY
|
|
25
|
+
module Tree
|
|
26
|
+
# Factory method to create the appropriate tree layout.
|
|
27
|
+
# Returns Tree::Live for TTY outputs, Tree::Event otherwise.
|
|
28
|
+
#
|
|
29
|
+
# @param output [IO] Output stream (default: $stderr)
|
|
30
|
+
# @param theme [Theme::Base, nil] Theme instance
|
|
31
|
+
# @return [Tree::Live, Tree::Event]
|
|
32
|
+
def self.for(output: $stderr, theme: nil)
|
|
33
|
+
if output.respond_to?(:tty?) && output.tty?
|
|
34
|
+
Live.new(output: output, theme: theme)
|
|
272
35
|
else
|
|
273
|
-
|
|
274
|
-
render_task_template(:task_pending, task:, execution: execution_drop)
|
|
36
|
+
Event.new(output: output, theme: theme)
|
|
275
37
|
end
|
|
276
38
|
end
|
|
277
|
-
|
|
278
|
-
def build_tree_prefix(task_class)
|
|
279
|
-
depth = @node_depths[task_class]
|
|
280
|
-
return "" if depth.nil? || depth == 0
|
|
281
|
-
|
|
282
|
-
last_info = @node_is_last[task_class]
|
|
283
|
-
return "" unless last_info
|
|
284
|
-
|
|
285
|
-
ancestors_last = last_info[:ancestors_last]
|
|
286
|
-
is_last = last_info[:is_last]
|
|
287
|
-
|
|
288
|
-
prefix = ""
|
|
289
|
-
# Skip the first ancestor (root) since root has no visual prefix
|
|
290
|
-
ancestors_last[1..].each do |ancestor_is_last|
|
|
291
|
-
prefix += ancestor_is_last ? SPACE : VERTICAL
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
prefix += is_last ? LAST_BRANCH : BRANCH
|
|
295
|
-
prefix
|
|
296
|
-
end
|
|
297
39
|
end
|
|
298
40
|
end
|
|
299
41
|
end
|
|
@@ -84,7 +84,7 @@ module Taski
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def execution_complete
|
|
87
|
-
"[TASKI] Completed: {{ execution.
|
|
87
|
+
"[TASKI] Completed: {{ execution.done_count }}/{{ execution.total_count }} tasks ({{ execution.total_duration | format_duration }})"
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def execution_fail
|
|
@@ -45,7 +45,7 @@ module Taski
|
|
|
45
45
|
|
|
46
46
|
# Execution complete with icon
|
|
47
47
|
def execution_complete
|
|
48
|
-
"{% icon %} [TASKI] Completed: {{ execution.
|
|
48
|
+
"{% icon %} [TASKI] Completed: {{ execution.done_count }}/{{ execution.total_count }} tasks ({{ execution.total_duration | format_duration }})"
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Execution fail with icon
|
data/lib/taski/task.rb
CHANGED
|
@@ -115,7 +115,7 @@ module Taski
|
|
|
115
115
|
def tree
|
|
116
116
|
output = StringIO.new
|
|
117
117
|
theme = Progress::Theme::Plain.new
|
|
118
|
-
layout = Progress::Layout::Tree.
|
|
118
|
+
layout = Progress::Layout::Tree.for(output: output, theme: theme)
|
|
119
119
|
context = Execution::ExecutionFacade.new(root_task_class: self)
|
|
120
120
|
layout.context = context
|
|
121
121
|
layout.on_ready
|
data/lib/taski/version.rb
CHANGED
data/lib/taski.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative "taski/execution/executor"
|
|
|
16
16
|
require_relative "taski/progress/layout/log"
|
|
17
17
|
require_relative "taski/progress/layout/simple"
|
|
18
18
|
require_relative "taski/progress/layout/tree"
|
|
19
|
+
require_relative "taski/progress/config"
|
|
19
20
|
require_relative "taski/args"
|
|
20
21
|
require_relative "taski/env"
|
|
21
22
|
require_relative "taski/logging"
|
|
@@ -267,14 +268,28 @@ module Taski
|
|
|
267
268
|
reset_args! if created_args
|
|
268
269
|
end
|
|
269
270
|
|
|
270
|
-
NOT_CONFIGURED = Object.new.freeze
|
|
271
271
|
PROGRESS_MONITOR = Monitor.new
|
|
272
|
-
|
|
272
|
+
PROGRESS_NOT_SET = Object.new.freeze
|
|
273
|
+
@progress_display = PROGRESS_NOT_SET
|
|
274
|
+
@progress_config = Progress::Config.new {
|
|
275
|
+
PROGRESS_MONITOR.synchronize do
|
|
276
|
+
unless @progress_display.equal?(PROGRESS_NOT_SET)
|
|
277
|
+
@progress_display.stop if @progress_display.respond_to?(:stop)
|
|
278
|
+
end
|
|
279
|
+
@progress_display = PROGRESS_NOT_SET
|
|
280
|
+
end
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# Get the progress configuration singleton.
|
|
284
|
+
# @return [Progress::Config]
|
|
285
|
+
def self.progress
|
|
286
|
+
PROGRESS_MONITOR.synchronize { @progress_config }
|
|
287
|
+
end
|
|
273
288
|
|
|
274
289
|
def self.progress_display
|
|
275
290
|
PROGRESS_MONITOR.synchronize do
|
|
276
|
-
if @progress_display.equal?(
|
|
277
|
-
@progress_display =
|
|
291
|
+
if @progress_display.equal?(PROGRESS_NOT_SET)
|
|
292
|
+
@progress_display = @progress_config.build
|
|
278
293
|
end
|
|
279
294
|
@progress_display
|
|
280
295
|
end
|
|
@@ -282,7 +297,7 @@ module Taski
|
|
|
282
297
|
|
|
283
298
|
def self.progress_display=(display)
|
|
284
299
|
PROGRESS_MONITOR.synchronize do
|
|
285
|
-
unless @progress_display.equal?(
|
|
300
|
+
unless @progress_display.equal?(PROGRESS_NOT_SET)
|
|
286
301
|
@progress_display.stop if @progress_display.respond_to?(:stop)
|
|
287
302
|
end
|
|
288
303
|
@progress_display = display
|
|
@@ -291,10 +306,11 @@ module Taski
|
|
|
291
306
|
|
|
292
307
|
def self.reset_progress_display!
|
|
293
308
|
PROGRESS_MONITOR.synchronize do
|
|
294
|
-
unless @progress_display.equal?(
|
|
309
|
+
unless @progress_display.equal?(PROGRESS_NOT_SET)
|
|
295
310
|
@progress_display.stop if @progress_display.respond_to?(:stop)
|
|
296
311
|
end
|
|
297
|
-
@progress_display =
|
|
312
|
+
@progress_display = PROGRESS_NOT_SET
|
|
313
|
+
@progress_config.reset
|
|
298
314
|
end
|
|
299
315
|
end
|
|
300
316
|
|
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.9.
|
|
4
|
+
version: 0.9.2
|
|
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: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: liquid
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,6 +107,7 @@ files:
|
|
|
93
107
|
- lib/taski/execution/task_wrapper.rb
|
|
94
108
|
- lib/taski/execution/worker_pool.rb
|
|
95
109
|
- lib/taski/logging.rb
|
|
110
|
+
- lib/taski/progress/config.rb
|
|
96
111
|
- lib/taski/progress/layout/base.rb
|
|
97
112
|
- lib/taski/progress/layout/filters.rb
|
|
98
113
|
- lib/taski/progress/layout/log.rb
|
|
@@ -100,6 +115,9 @@ files:
|
|
|
100
115
|
- lib/taski/progress/layout/tags.rb
|
|
101
116
|
- lib/taski/progress/layout/theme_drop.rb
|
|
102
117
|
- lib/taski/progress/layout/tree.rb
|
|
118
|
+
- lib/taski/progress/layout/tree/event.rb
|
|
119
|
+
- lib/taski/progress/layout/tree/live.rb
|
|
120
|
+
- lib/taski/progress/layout/tree/structure.rb
|
|
103
121
|
- lib/taski/progress/theme/base.rb
|
|
104
122
|
- lib/taski/progress/theme/compact.rb
|
|
105
123
|
- lib/taski/progress/theme/default.rb
|