taski 0.8.3 → 0.9.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 +52 -0
- data/README.md +108 -50
- data/docs/GUIDE.md +79 -55
- 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 +167 -359
- data/lib/taski/execution/fiber_protocol.rb +27 -0
- 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 +279 -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/start_dep_analyzer.rb +400 -0
- data/lib/taski/static_analysis/visitor.rb +1 -39
- data/lib/taski/task.rb +49 -58
- data/lib/taski/task_proxy.rb +59 -0
- data/lib/taski/test_helper/errors.rb +1 -1
- data/lib/taski/test_helper.rb +22 -36
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +62 -61
- data/sig/taski.rbs +194 -203
- metadata +34 -8
- data/examples/section_demo.rb +0 -195
- data/lib/taski/execution/base_progress_display.rb +0 -393
- 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 -247
- data/lib/taski/execution/tree_progress_display.rb +0 -643
- data/lib/taski/section.rb +0 -74
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taski
|
|
4
|
+
module Progress
|
|
5
|
+
module Layout
|
|
6
|
+
# Liquid filter module for colorizing text output.
|
|
7
|
+
# Uses ThemeDrop from context to get color codes, falls back to defaults.
|
|
8
|
+
#
|
|
9
|
+
# @example Usage in Liquid template
|
|
10
|
+
# {{ task.name | green }}
|
|
11
|
+
# {{ task.error_message | red }}
|
|
12
|
+
# {{ task.state | dim }}
|
|
13
|
+
module ColorFilter
|
|
14
|
+
DEFAULT_COLORS = {
|
|
15
|
+
red: "\e[31m",
|
|
16
|
+
green: "\e[32m",
|
|
17
|
+
yellow: "\e[33m",
|
|
18
|
+
dim: "\e[2m",
|
|
19
|
+
reset: "\e[0m"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
def red(input) = colorize(input, :red)
|
|
23
|
+
def green(input) = colorize(input, :green)
|
|
24
|
+
def yellow(input) = colorize(input, :yellow)
|
|
25
|
+
def dim(input) = colorize(input, :dim)
|
|
26
|
+
|
|
27
|
+
# Format a count value using Theme's format_count method.
|
|
28
|
+
# Falls back to to_s if no template is provided.
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# {{ execution.done_count | format_count }}
|
|
32
|
+
def format_count(input)
|
|
33
|
+
template = @context["template"]
|
|
34
|
+
template&.format_count(input) || input.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Format a duration value using Theme's format_duration method.
|
|
38
|
+
# Falls back to default formatting if no template is provided.
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# {{ task.duration | format_duration }}
|
|
42
|
+
# {{ execution.total_duration | format_duration }}
|
|
43
|
+
def format_duration(input)
|
|
44
|
+
return "" if input.nil?
|
|
45
|
+
|
|
46
|
+
template = @context["template"]
|
|
47
|
+
template&.format_duration(input) || default_format_duration(input)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Truncate a list to a maximum number of items, joining with separator.
|
|
51
|
+
# Uses Theme's truncate_list_separator and truncate_list_suffix if available.
|
|
52
|
+
#
|
|
53
|
+
# @example
|
|
54
|
+
# {{ execution.task_names | truncate_list: 3 }}
|
|
55
|
+
# # => "TaskA, TaskB, TaskC..."
|
|
56
|
+
def truncate_list(input, limit = 3)
|
|
57
|
+
return "" if input.nil?
|
|
58
|
+
|
|
59
|
+
items = input.is_a?(Array) ? input : [input]
|
|
60
|
+
return "" if items.empty?
|
|
61
|
+
|
|
62
|
+
template = @context["template"]
|
|
63
|
+
separator = template&.truncate_list_separator || ", "
|
|
64
|
+
suffix = template&.truncate_list_suffix || "..."
|
|
65
|
+
|
|
66
|
+
truncated = items.first(limit)
|
|
67
|
+
result = truncated.join(separator)
|
|
68
|
+
result += suffix if items.size > limit
|
|
69
|
+
result
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Extract short name from a fully qualified class name.
|
|
73
|
+
# Returns the last component after "::".
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# {{ task.name | short_name }}
|
|
77
|
+
# # "MyModule::MyTask" => "MyTask"
|
|
78
|
+
def short_name(input)
|
|
79
|
+
return "" if input.nil?
|
|
80
|
+
input.to_s.split("::").last || input.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Truncate text to a maximum length, adding suffix if truncated.
|
|
84
|
+
# Uses Theme's truncate_text_suffix if available.
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# {{ task.stdout | truncate_text: 40 }}
|
|
88
|
+
# # => "Uploading files to server..."
|
|
89
|
+
def truncate_text(input, max_length = 40)
|
|
90
|
+
return "" if input.nil?
|
|
91
|
+
return "" if max_length <= 0
|
|
92
|
+
|
|
93
|
+
text = input.to_s
|
|
94
|
+
return text if text.length <= max_length
|
|
95
|
+
|
|
96
|
+
template = @context["template"]
|
|
97
|
+
suffix = template&.truncate_text_suffix || "..."
|
|
98
|
+
|
|
99
|
+
truncated_length = [max_length - suffix.length, 0].max
|
|
100
|
+
if truncated_length == 0
|
|
101
|
+
suffix[0, max_length]
|
|
102
|
+
else
|
|
103
|
+
text[0, truncated_length] + suffix
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def colorize(input, color_name)
|
|
110
|
+
template = @context["template"]
|
|
111
|
+
color = template&.public_send(:"color_#{color_name}") || DEFAULT_COLORS[color_name]
|
|
112
|
+
reset = template&.color_reset || DEFAULT_COLORS[:reset]
|
|
113
|
+
"#{color}#{input}#{reset}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def default_format_duration(ms)
|
|
117
|
+
if ms >= 1000
|
|
118
|
+
"#{(ms / 1000.0).round(1)}s"
|
|
119
|
+
else
|
|
120
|
+
"#{ms}ms"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../theme/plain"
|
|
5
|
+
|
|
6
|
+
module Taski
|
|
7
|
+
module Progress
|
|
8
|
+
module Layout
|
|
9
|
+
# Log layout for non-TTY environments (CI, log files, piped output).
|
|
10
|
+
# Outputs plain text without terminal escape codes.
|
|
11
|
+
#
|
|
12
|
+
# Output format:
|
|
13
|
+
# [START] TaskName
|
|
14
|
+
# [DONE] TaskName (123.4ms)
|
|
15
|
+
# [FAIL] TaskName: Error message
|
|
16
|
+
#
|
|
17
|
+
# Uses Theme::Plain by default to ensure no ANSI escape codes in output.
|
|
18
|
+
class Log < Base
|
|
19
|
+
def initialize(output: $stderr, theme: nil)
|
|
20
|
+
theme ||= Theme::Plain.new
|
|
21
|
+
super
|
|
22
|
+
@output.sync = true if @output.respond_to?(:sync=)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../theme/compact"
|
|
5
|
+
|
|
6
|
+
module Taski
|
|
7
|
+
module Progress
|
|
8
|
+
module Layout
|
|
9
|
+
# Simple layout providing a minimalist single-line progress display.
|
|
10
|
+
# Shows task execution status in a compact format with spinner animation:
|
|
11
|
+
#
|
|
12
|
+
# ⠹ [3/5] DeployTask | Uploading files...
|
|
13
|
+
#
|
|
14
|
+
# Customization is done through Theme classes:
|
|
15
|
+
#
|
|
16
|
+
# class MyTheme < Taski::Progress::Theme::Base
|
|
17
|
+
# def spinner_frames
|
|
18
|
+
# %w[🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘]
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def icon_success
|
|
22
|
+
# "🎉"
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# def format_count(count)
|
|
26
|
+
# "#{count}件"
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# def execution_complete
|
|
30
|
+
# '{% icon %} Done! {{ execution.completed_count | format_count }} tasks in {{ execution.total_duration | format_duration }}'
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# layout = Taski::Progress::Layout::Simple.new(theme: MyTheme.new)
|
|
35
|
+
class Simple < Base
|
|
36
|
+
def initialize(output: $stdout, theme: nil)
|
|
37
|
+
theme ||= Theme::Compact.new
|
|
38
|
+
super
|
|
39
|
+
@renderer_thread = nil
|
|
40
|
+
@running = false
|
|
41
|
+
@running_mutex = Mutex.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
# === Template method overrides ===
|
|
47
|
+
|
|
48
|
+
def handle_ready
|
|
49
|
+
graph = context&.dependency_graph
|
|
50
|
+
return unless graph
|
|
51
|
+
|
|
52
|
+
graph.all_tasks.each { |tc| register_task(tc) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Simple layout uses periodic status line updates instead of per-event output
|
|
56
|
+
def handle_task_update(_task_class, _current_state, _phase)
|
|
57
|
+
# No per-event output; status line is updated by render_live
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle_group_started(_task_class, _group_name, _phase)
|
|
61
|
+
# No per-event output; status line is updated by render_live
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def handle_group_completed(_task_class, _group_name, _phase, _duration)
|
|
65
|
+
# No per-event output; status line is updated by render_live
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def should_activate?
|
|
69
|
+
tty?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def handle_start
|
|
73
|
+
@running_mutex.synchronize { @running = true }
|
|
74
|
+
start_spinner_timer
|
|
75
|
+
@output.print "\e[?25l" # Hide cursor
|
|
76
|
+
@renderer_thread = Thread.new do
|
|
77
|
+
loop do
|
|
78
|
+
break unless @running_mutex.synchronize { @running }
|
|
79
|
+
render_live
|
|
80
|
+
sleep @theme.render_interval
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def handle_stop
|
|
86
|
+
@running_mutex.synchronize { @running = false }
|
|
87
|
+
@renderer_thread&.join
|
|
88
|
+
stop_spinner_timer
|
|
89
|
+
@output.print "\e[?25h" # Show cursor
|
|
90
|
+
render_final
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def render_live
|
|
96
|
+
@monitor.synchronize do
|
|
97
|
+
line = build_status_line
|
|
98
|
+
# Truncate line to terminal width to prevent line wrap
|
|
99
|
+
max_width = terminal_width - 1 # Leave space for cursor
|
|
100
|
+
line = line[0, max_width] if line.length > max_width
|
|
101
|
+
# Clear line and write new content
|
|
102
|
+
@output.print "\r\e[K#{line}"
|
|
103
|
+
@output.flush
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def terminal_width
|
|
108
|
+
@output.winsize[1]
|
|
109
|
+
rescue
|
|
110
|
+
80 # Default fallback
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_final
|
|
114
|
+
@monitor.synchronize do
|
|
115
|
+
line = if failed_count > 0
|
|
116
|
+
render_execution_failed(failed_count: failed_count, total_count: total_count, total_duration: total_duration)
|
|
117
|
+
else
|
|
118
|
+
render_execution_completed(completed_count: completed_count, total_count: total_count, total_duration: total_duration)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@output.print "\r\e[K#{line}\n"
|
|
122
|
+
@output.flush
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_status_line
|
|
127
|
+
task_names = collect_current_task_names
|
|
128
|
+
|
|
129
|
+
primary_task = running_tasks.keys.first || cleaning_tasks.keys.first
|
|
130
|
+
task_stdout = build_task_stdout(primary_task)
|
|
131
|
+
|
|
132
|
+
render_execution_running(
|
|
133
|
+
done_count: done_count,
|
|
134
|
+
total_count: total_count,
|
|
135
|
+
task_names: task_names.empty? ? nil : task_names,
|
|
136
|
+
task_stdout: task_stdout
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def collect_current_task_names
|
|
141
|
+
# Prioritize: cleaning > running > pending
|
|
142
|
+
current_tasks = if cleaning_tasks.any?
|
|
143
|
+
cleaning_tasks.keys
|
|
144
|
+
elsif running_tasks.any?
|
|
145
|
+
running_tasks.keys
|
|
146
|
+
elsif pending_tasks.any?
|
|
147
|
+
pending_tasks.keys
|
|
148
|
+
else
|
|
149
|
+
[]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
current_tasks.map { |t| task_class_name(t) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def build_task_stdout(task_class)
|
|
156
|
+
return nil unless @output_capture && task_class
|
|
157
|
+
|
|
158
|
+
last_line = @output_capture.last_line_for(task_class)
|
|
159
|
+
return nil unless last_line && !last_line.strip.empty?
|
|
160
|
+
|
|
161
|
+
last_line.strip
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "liquid"
|
|
4
|
+
require_relative "filters"
|
|
5
|
+
|
|
6
|
+
module Taski
|
|
7
|
+
module Progress
|
|
8
|
+
module Layout
|
|
9
|
+
# Liquid tag for rendering animated spinner characters.
|
|
10
|
+
# Uses ThemeDrop from context to get spinner frames, falls back to defaults.
|
|
11
|
+
# Uses spinner_index from context to determine current frame.
|
|
12
|
+
#
|
|
13
|
+
# @example Usage in Liquid template
|
|
14
|
+
# {% spinner %} Loading...
|
|
15
|
+
# {% spinner %} [{{ done }}/{{ total }}]
|
|
16
|
+
class SpinnerTag < Liquid::Tag
|
|
17
|
+
DEFAULT_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
18
|
+
|
|
19
|
+
def render(context)
|
|
20
|
+
template = context["template"]
|
|
21
|
+
frames = template&.spinner_frames || DEFAULT_FRAMES
|
|
22
|
+
index = context["spinner_index"] || 0
|
|
23
|
+
frames[index % frames.size]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Liquid tag for rendering status icons based on current state.
|
|
28
|
+
# Uses ThemeDrop from context to get icons and colors.
|
|
29
|
+
# Uses state from context to determine which icon to show.
|
|
30
|
+
#
|
|
31
|
+
# @example Usage in Liquid template
|
|
32
|
+
# {% icon %} Task completed
|
|
33
|
+
# {% icon %} [{{ done }}/{{ total }}]
|
|
34
|
+
class IconTag < Liquid::Tag
|
|
35
|
+
DEFAULTS = {
|
|
36
|
+
icon_success: "✓",
|
|
37
|
+
icon_failure: "✗",
|
|
38
|
+
icon_pending: "○",
|
|
39
|
+
icon_skip: "⊘",
|
|
40
|
+
color_green: ColorFilter::DEFAULT_COLORS[:green],
|
|
41
|
+
color_red: ColorFilter::DEFAULT_COLORS[:red],
|
|
42
|
+
color_yellow: ColorFilter::DEFAULT_COLORS[:yellow],
|
|
43
|
+
color_dim: ColorFilter::DEFAULT_COLORS[:dim],
|
|
44
|
+
color_reset: ColorFilter::DEFAULT_COLORS[:reset]
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
STATE_CONFIG = {
|
|
48
|
+
"completed" => {icon: :icon_success, color: :color_green},
|
|
49
|
+
"failed" => {icon: :icon_failure, color: :color_red},
|
|
50
|
+
"running" => {icon: :icon_pending, color: :color_yellow},
|
|
51
|
+
"skipped" => {icon: :icon_skip, color: :color_dim}
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
def render(context)
|
|
55
|
+
@template = context["template"]
|
|
56
|
+
state = context["state"]&.to_s
|
|
57
|
+
|
|
58
|
+
config = STATE_CONFIG[state]
|
|
59
|
+
return get_value(:icon_pending) unless config
|
|
60
|
+
|
|
61
|
+
colorize(get_value(config[:icon]), config[:color])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def get_value(key)
|
|
67
|
+
@template&.public_send(key) || DEFAULTS[key]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def colorize(icon, color_key)
|
|
71
|
+
"#{get_value(color_key)}#{icon}#{get_value(:color_reset)}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "liquid"
|
|
4
|
+
|
|
5
|
+
module Taski
|
|
6
|
+
module Progress
|
|
7
|
+
module Layout
|
|
8
|
+
# Liquid Drop for Theme to enable method access from filters/tags.
|
|
9
|
+
# Wraps a Theme instance and delegates color/icon/spinner methods.
|
|
10
|
+
#
|
|
11
|
+
# @example Using in Liquid context
|
|
12
|
+
# drop = ThemeDrop.new(theme)
|
|
13
|
+
# Liquid::Template.parse("{{ theme.color_red }}")
|
|
14
|
+
# .render("theme" => drop)
|
|
15
|
+
class ThemeDrop < Liquid::Drop
|
|
16
|
+
def initialize(theme)
|
|
17
|
+
@theme = theme
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Color methods
|
|
21
|
+
def color_red = @theme.color_red
|
|
22
|
+
def color_green = @theme.color_green
|
|
23
|
+
def color_yellow = @theme.color_yellow
|
|
24
|
+
def color_dim = @theme.color_dim
|
|
25
|
+
def color_reset = @theme.color_reset
|
|
26
|
+
|
|
27
|
+
# Spinner settings
|
|
28
|
+
def spinner_frames = @theme.spinner_frames
|
|
29
|
+
def spinner_interval = @theme.spinner_interval
|
|
30
|
+
def render_interval = @theme.render_interval
|
|
31
|
+
|
|
32
|
+
# Status icons
|
|
33
|
+
def icon_success = @theme.icon_success
|
|
34
|
+
def icon_failure = @theme.icon_failure
|
|
35
|
+
def icon_pending = @theme.icon_pending
|
|
36
|
+
def icon_skip = @theme.icon_skip
|
|
37
|
+
|
|
38
|
+
# Formatting methods (used by filters)
|
|
39
|
+
def format_count(count) = @theme.format_count(count)
|
|
40
|
+
def format_duration(ms) = @theme.format_duration(ms)
|
|
41
|
+
|
|
42
|
+
# List truncation settings
|
|
43
|
+
def truncate_list_separator = @theme.truncate_list_separator
|
|
44
|
+
def truncate_list_suffix = @theme.truncate_list_suffix
|
|
45
|
+
|
|
46
|
+
# Text truncation settings
|
|
47
|
+
def truncate_text_suffix = @theme.truncate_text_suffix
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Base class for Liquid Drops with dynamic property access.
|
|
51
|
+
# Provides common functionality for TaskDrop and ExecutionDrop.
|
|
52
|
+
class DataDrop < Liquid::Drop
|
|
53
|
+
def initialize(**data)
|
|
54
|
+
@data = data
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def liquid_method_missing(method)
|
|
58
|
+
@data[method.to_sym]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Liquid Drop for task-specific variables.
|
|
63
|
+
# Provides access to individual task information in templates.
|
|
64
|
+
#
|
|
65
|
+
# Available properties: name, state, duration, error_message, group_name, stdout
|
|
66
|
+
#
|
|
67
|
+
# @example Using in Liquid template
|
|
68
|
+
# {{ task.name }} ({{ task.state }})
|
|
69
|
+
# {{ task.duration | format_duration }}
|
|
70
|
+
class TaskDrop < DataDrop; end
|
|
71
|
+
|
|
72
|
+
# Liquid Drop for execution-level variables.
|
|
73
|
+
# Provides access to overall execution state in templates.
|
|
74
|
+
#
|
|
75
|
+
# Available properties: state, pending_count, done_count, completed_count,
|
|
76
|
+
# failed_count, total_count, total_duration, root_task_name, task_names
|
|
77
|
+
#
|
|
78
|
+
# @example Using in Liquid template
|
|
79
|
+
# [{{ execution.completed_count }}/{{ execution.total_count }}]
|
|
80
|
+
# {{ execution.total_duration | format_duration }}
|
|
81
|
+
class ExecutionDrop < DataDrop; end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|