fun_ci 1.1.0 → 1.2.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 +19 -0
- data/exe/fun-ci-trigger +6 -6
- data/exe/fun-ci-tui +6 -6
- data/lib/fun_ci/cli.rb +17 -16
- data/lib/fun_ci/persistence/database.rb +52 -0
- data/lib/fun_ci/persistence/pipeline_recorder.rb +74 -0
- data/lib/fun_ci/persistence/pipeline_run.rb +59 -0
- data/lib/fun_ci/persistence/stage_job.rb +46 -0
- data/lib/fun_ci/{state_machine.rb → persistence/state_machine.rb} +3 -12
- data/lib/fun_ci/pipeline/background_wrapper.rb +27 -0
- data/lib/fun_ci/pipeline/pipeline_forker.rb +38 -0
- data/lib/fun_ci/pipeline/process_runner.rb +28 -0
- data/lib/fun_ci/pipeline/progress_reporter.rb +31 -0
- data/lib/fun_ci/pipeline/stage_runner.rb +85 -0
- data/lib/fun_ci/pipeline/stale_pipeline_canceller.rb +53 -0
- data/lib/fun_ci/pipeline/trigger.rb +153 -0
- data/lib/fun_ci/setup/hook_writer.rb +75 -0
- data/lib/fun_ci/setup/installer.rb +55 -0
- data/lib/fun_ci/setup/maven_linter_detector.rb +26 -0
- data/lib/fun_ci/setup/project_config.rb +42 -0
- data/lib/fun_ci/setup/project_detector.rb +22 -0
- data/lib/fun_ci/setup/setup_checker.rb +30 -0
- data/lib/fun_ci/setup/template_writer.rb +53 -0
- data/lib/fun_ci/tui/admin_tui.rb +90 -0
- data/lib/fun_ci/tui/animation.rb +49 -0
- data/lib/fun_ci/tui/animation_compositor.rb +107 -0
- data/lib/fun_ci/tui/animation_frames.rb +112 -0
- data/lib/fun_ci/tui/animation_library.rb +46 -0
- data/lib/fun_ci/tui/animation_renderer.rb +144 -0
- data/lib/fun_ci/tui/ansi.rb +34 -0
- data/lib/fun_ci/tui/board_data.rb +53 -0
- data/lib/fun_ci/tui/board_renderer.rb +105 -0
- data/lib/fun_ci/tui/duration_formatter.rb +24 -0
- data/lib/fun_ci/tui/header_animation_manager.rb +71 -0
- data/lib/fun_ci/tui/header_animation_player.rb +45 -0
- data/lib/fun_ci/tui/key_handler.rb +86 -0
- data/lib/fun_ci/tui/looping_animation_player.rb +45 -0
- data/lib/fun_ci/tui/relative_time.rb +22 -0
- data/lib/fun_ci/tui/row_formatter.rb +108 -0
- data/lib/fun_ci/tui/screen.rb +103 -0
- data/lib/fun_ci/tui/spinner.rb +24 -0
- data/lib/fun_ci/tui/stage_change_detector.rb +58 -0
- data/lib/fun_ci/tui/streak_counter.rb +29 -0
- data/lib/fun_ci/tui/terminal_input.rb +69 -0
- data/lib/fun_ci.rb +6 -6
- metadata +41 -37
- data/lib/fun_ci/admin_tui.rb +0 -238
- data/lib/fun_ci/animation.rb +0 -47
- data/lib/fun_ci/animation_compositor.rb +0 -105
- data/lib/fun_ci/animation_frames.rb +0 -111
- data/lib/fun_ci/animation_library.rb +0 -44
- data/lib/fun_ci/animation_renderer.rb +0 -142
- data/lib/fun_ci/ansi.rb +0 -32
- data/lib/fun_ci/background_wrapper.rb +0 -27
- data/lib/fun_ci/board_data.rb +0 -51
- data/lib/fun_ci/database.rb +0 -50
- data/lib/fun_ci/duration_formatter.rb +0 -23
- data/lib/fun_ci/header_animation_manager.rb +0 -69
- data/lib/fun_ci/header_animation_player.rb +0 -43
- data/lib/fun_ci/hook_writer.rb +0 -73
- data/lib/fun_ci/installer.rb +0 -53
- data/lib/fun_ci/looping_animation_player.rb +0 -43
- data/lib/fun_ci/maven_linter_detector.rb +0 -24
- data/lib/fun_ci/pipeline_forker.rb +0 -36
- data/lib/fun_ci/pipeline_recorder.rb +0 -72
- data/lib/fun_ci/pipeline_run.rb +0 -57
- data/lib/fun_ci/progress_reporter.rb +0 -29
- data/lib/fun_ci/project_config.rb +0 -40
- data/lib/fun_ci/project_detector.rb +0 -18
- data/lib/fun_ci/relative_time.rb +0 -20
- data/lib/fun_ci/row_formatter.rb +0 -106
- data/lib/fun_ci/screen.rb +0 -95
- data/lib/fun_ci/setup_checker.rb +0 -28
- data/lib/fun_ci/spinner.rb +0 -22
- data/lib/fun_ci/stage_change_detector.rb +0 -56
- data/lib/fun_ci/stage_job.rb +0 -44
- data/lib/fun_ci/stage_runner.rb +0 -108
- data/lib/fun_ci/stale_pipeline_canceller.rb +0 -51
- data/lib/fun_ci/streak_counter.rb +0 -30
- data/lib/fun_ci/template_writer.rb +0 -51
- data/lib/fun_ci/trigger.rb +0 -150
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fun_ci
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Thyge Madsen
|
|
@@ -41,12 +41,6 @@ files:
|
|
|
41
41
|
- exe/fun-ci-trigger
|
|
42
42
|
- exe/fun-ci-tui
|
|
43
43
|
- lib/fun_ci.rb
|
|
44
|
-
- lib/fun_ci/admin_tui.rb
|
|
45
|
-
- lib/fun_ci/animation.rb
|
|
46
|
-
- lib/fun_ci/animation_compositor.rb
|
|
47
|
-
- lib/fun_ci/animation_frames.rb
|
|
48
|
-
- lib/fun_ci/animation_library.rb
|
|
49
|
-
- lib/fun_ci/animation_renderer.rb
|
|
50
44
|
- lib/fun_ci/animations/celebrate.rb
|
|
51
45
|
- lib/fun_ci/animations/explosion.rb
|
|
52
46
|
- lib/fun_ci/animations/flash.rb
|
|
@@ -55,37 +49,47 @@ files:
|
|
|
55
49
|
- lib/fun_ci/animations/running.rb
|
|
56
50
|
- lib/fun_ci/animations/success.rb
|
|
57
51
|
- lib/fun_ci/animations/yay.rb
|
|
58
|
-
- lib/fun_ci/ansi.rb
|
|
59
|
-
- lib/fun_ci/background_wrapper.rb
|
|
60
|
-
- lib/fun_ci/board_data.rb
|
|
61
52
|
- lib/fun_ci/cli.rb
|
|
62
|
-
- lib/fun_ci/database.rb
|
|
63
|
-
- lib/fun_ci/
|
|
64
|
-
- lib/fun_ci/
|
|
65
|
-
- lib/fun_ci/
|
|
66
|
-
- lib/fun_ci/
|
|
67
|
-
- lib/fun_ci/
|
|
68
|
-
- lib/fun_ci/
|
|
69
|
-
- lib/fun_ci/
|
|
70
|
-
- lib/fun_ci/
|
|
71
|
-
- lib/fun_ci/
|
|
72
|
-
- lib/fun_ci/
|
|
73
|
-
- lib/fun_ci/
|
|
74
|
-
- lib/fun_ci/
|
|
75
|
-
- lib/fun_ci/
|
|
76
|
-
- lib/fun_ci/
|
|
77
|
-
- lib/fun_ci/
|
|
78
|
-
- lib/fun_ci/
|
|
79
|
-
- lib/fun_ci/setup_checker.rb
|
|
80
|
-
- lib/fun_ci/
|
|
81
|
-
- lib/fun_ci/
|
|
82
|
-
- lib/fun_ci/
|
|
83
|
-
- lib/fun_ci/
|
|
84
|
-
- lib/fun_ci/
|
|
85
|
-
- lib/fun_ci/
|
|
86
|
-
- lib/fun_ci/
|
|
87
|
-
- lib/fun_ci/
|
|
88
|
-
- lib/fun_ci/
|
|
53
|
+
- lib/fun_ci/persistence/database.rb
|
|
54
|
+
- lib/fun_ci/persistence/pipeline_recorder.rb
|
|
55
|
+
- lib/fun_ci/persistence/pipeline_run.rb
|
|
56
|
+
- lib/fun_ci/persistence/stage_job.rb
|
|
57
|
+
- lib/fun_ci/persistence/state_machine.rb
|
|
58
|
+
- lib/fun_ci/pipeline/background_wrapper.rb
|
|
59
|
+
- lib/fun_ci/pipeline/pipeline_forker.rb
|
|
60
|
+
- lib/fun_ci/pipeline/process_runner.rb
|
|
61
|
+
- lib/fun_ci/pipeline/progress_reporter.rb
|
|
62
|
+
- lib/fun_ci/pipeline/stage_runner.rb
|
|
63
|
+
- lib/fun_ci/pipeline/stale_pipeline_canceller.rb
|
|
64
|
+
- lib/fun_ci/pipeline/trigger.rb
|
|
65
|
+
- lib/fun_ci/setup/hook_writer.rb
|
|
66
|
+
- lib/fun_ci/setup/installer.rb
|
|
67
|
+
- lib/fun_ci/setup/maven_linter_detector.rb
|
|
68
|
+
- lib/fun_ci/setup/project_config.rb
|
|
69
|
+
- lib/fun_ci/setup/project_detector.rb
|
|
70
|
+
- lib/fun_ci/setup/setup_checker.rb
|
|
71
|
+
- lib/fun_ci/setup/template_writer.rb
|
|
72
|
+
- lib/fun_ci/tui/admin_tui.rb
|
|
73
|
+
- lib/fun_ci/tui/animation.rb
|
|
74
|
+
- lib/fun_ci/tui/animation_compositor.rb
|
|
75
|
+
- lib/fun_ci/tui/animation_frames.rb
|
|
76
|
+
- lib/fun_ci/tui/animation_library.rb
|
|
77
|
+
- lib/fun_ci/tui/animation_renderer.rb
|
|
78
|
+
- lib/fun_ci/tui/ansi.rb
|
|
79
|
+
- lib/fun_ci/tui/board_data.rb
|
|
80
|
+
- lib/fun_ci/tui/board_renderer.rb
|
|
81
|
+
- lib/fun_ci/tui/duration_formatter.rb
|
|
82
|
+
- lib/fun_ci/tui/header_animation_manager.rb
|
|
83
|
+
- lib/fun_ci/tui/header_animation_player.rb
|
|
84
|
+
- lib/fun_ci/tui/key_handler.rb
|
|
85
|
+
- lib/fun_ci/tui/looping_animation_player.rb
|
|
86
|
+
- lib/fun_ci/tui/relative_time.rb
|
|
87
|
+
- lib/fun_ci/tui/row_formatter.rb
|
|
88
|
+
- lib/fun_ci/tui/screen.rb
|
|
89
|
+
- lib/fun_ci/tui/spinner.rb
|
|
90
|
+
- lib/fun_ci/tui/stage_change_detector.rb
|
|
91
|
+
- lib/fun_ci/tui/streak_counter.rb
|
|
92
|
+
- lib/fun_ci/tui/terminal_input.rb
|
|
89
93
|
homepage: https://github.com/beatmadsen/fun-ci
|
|
90
94
|
licenses:
|
|
91
95
|
- MIT
|
data/lib/fun_ci/admin_tui.rb
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "board_data"
|
|
4
|
-
require_relative "header_animation_manager"
|
|
5
|
-
require_relative "row_formatter"
|
|
6
|
-
require_relative "screen"
|
|
7
|
-
require_relative "spinner"
|
|
8
|
-
require_relative "streak_counter"
|
|
9
|
-
require "io/console"
|
|
10
|
-
|
|
11
|
-
module FunCi
|
|
12
|
-
class AdminTui
|
|
13
|
-
FAST_REFRESH = 0.1 # seconds (spinner + timer)
|
|
14
|
-
SLOW_REFRESH = 5.0 # seconds (settled board)
|
|
15
|
-
HEADER_HEIGHT = HeaderAnimationManager::HEADER_HEIGHT
|
|
16
|
-
|
|
17
|
-
def initialize(db:, output: $stdout, input: $stdin, width: 80,
|
|
18
|
-
width_provider: nil, page_size: nil, animation_renderer: nil)
|
|
19
|
-
@board_data = BoardData.new(db, page_size: page_size)
|
|
20
|
-
@screen = Screen.new(output: output, width: width)
|
|
21
|
-
@animation_renderer = animation_renderer
|
|
22
|
-
@input = input
|
|
23
|
-
@width_provider = width_provider
|
|
24
|
-
@spinner = Spinner.new
|
|
25
|
-
@cursor_index = nil
|
|
26
|
-
@running = false
|
|
27
|
-
@confirm_cancel = nil
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def render_once
|
|
31
|
-
update_width_from_provider
|
|
32
|
-
runs = @board_data.runs
|
|
33
|
-
streak = @board_data.streak
|
|
34
|
-
streak_text = StreakCounter.format_text(streak)
|
|
35
|
-
|
|
36
|
-
@screen.render_header(streak_text: streak_text)
|
|
37
|
-
(HEADER_HEIGHT - 1).times { @screen.println }
|
|
38
|
-
|
|
39
|
-
if runs.empty?
|
|
40
|
-
@screen.render_empty_state
|
|
41
|
-
@screen.render_footer(empty: true)
|
|
42
|
-
else
|
|
43
|
-
rows = runs.map { |run| format_run(run) }
|
|
44
|
-
@screen.render_board(rows, cursor_index: @cursor_index)
|
|
45
|
-
@screen.println
|
|
46
|
-
@screen.render_footer(empty: false, confirming: confirming?)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
@screen.clear_below
|
|
50
|
-
render_animations(runs)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def run
|
|
54
|
-
@running = true
|
|
55
|
-
@screen.clear
|
|
56
|
-
|
|
57
|
-
begin
|
|
58
|
-
setup_raw_mode
|
|
59
|
-
setup_sigwinch_trap
|
|
60
|
-
loop do
|
|
61
|
-
render_frame
|
|
62
|
-
break unless @running
|
|
63
|
-
key = read_key_with_timeout(refresh_interval)
|
|
64
|
-
handle_key(key) if key
|
|
65
|
-
end
|
|
66
|
-
ensure
|
|
67
|
-
restore_terminal
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def resize(new_width)
|
|
72
|
-
@screen.width = new_width
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def confirming?
|
|
76
|
-
!@confirm_cancel.nil?
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def confirmation_run
|
|
80
|
-
@confirm_cancel
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def handle_key(key)
|
|
84
|
-
if @confirm_cancel
|
|
85
|
-
handle_confirm_key(key)
|
|
86
|
-
return
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
case key
|
|
90
|
-
when "q"
|
|
91
|
-
@running = false
|
|
92
|
-
when "j", :down
|
|
93
|
-
move_cursor_down
|
|
94
|
-
when "k", :up
|
|
95
|
-
move_cursor_up
|
|
96
|
-
when "c"
|
|
97
|
-
initiate_cancel
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
private
|
|
102
|
-
|
|
103
|
-
def render_animations(runs)
|
|
104
|
-
return unless @animation_renderer
|
|
105
|
-
|
|
106
|
-
@animation_renderer.render(@screen, runs)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def format_run(run)
|
|
110
|
-
opts = {}
|
|
111
|
-
if run[:status] == "running"
|
|
112
|
-
active_stage = run[:stages].find { |s| s[:status] == "running" }
|
|
113
|
-
if active_stage && active_stage[:started_at]
|
|
114
|
-
opts[:elapsed_seconds] = Time.now - Time.parse(active_stage[:started_at])
|
|
115
|
-
end
|
|
116
|
-
opts[:spinner_frame] = @spinner.current_frame
|
|
117
|
-
end
|
|
118
|
-
RowFormatter.format(run, **opts)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def render_frame
|
|
122
|
-
@screen.move_cursor_home
|
|
123
|
-
@spinner.advance!
|
|
124
|
-
render_once
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def refresh_interval
|
|
128
|
-
return FAST_REFRESH if @animation_renderer
|
|
129
|
-
|
|
130
|
-
runs = @board_data.runs
|
|
131
|
-
any_running = runs.any? { |r| r[:status] == "running" }
|
|
132
|
-
any_running ? FAST_REFRESH : SLOW_REFRESH
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def move_cursor_down
|
|
136
|
-
runs = @board_data.runs
|
|
137
|
-
return if runs.empty?
|
|
138
|
-
|
|
139
|
-
if @cursor_index.nil?
|
|
140
|
-
@cursor_index = 0
|
|
141
|
-
elsif @cursor_index < runs.length - 1
|
|
142
|
-
@cursor_index += 1
|
|
143
|
-
@board_data.load_more if @cursor_index == runs.length - 1
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def move_cursor_up
|
|
148
|
-
return if @cursor_index.nil?
|
|
149
|
-
|
|
150
|
-
if @cursor_index > 0
|
|
151
|
-
@cursor_index -= 1
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def initiate_cancel
|
|
156
|
-
return if @cursor_index.nil?
|
|
157
|
-
|
|
158
|
-
runs = @board_data.runs
|
|
159
|
-
return if @cursor_index >= runs.length
|
|
160
|
-
|
|
161
|
-
run = runs[@cursor_index]
|
|
162
|
-
case run[:status]
|
|
163
|
-
when "scheduled"
|
|
164
|
-
@board_data.cancel_run(run[:id])
|
|
165
|
-
when "running"
|
|
166
|
-
@confirm_cancel = run
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def handle_confirm_key(key)
|
|
171
|
-
case key
|
|
172
|
-
when "y"
|
|
173
|
-
@board_data.cancel_run(@confirm_cancel[:id])
|
|
174
|
-
@confirm_cancel = nil
|
|
175
|
-
when "n", :escape
|
|
176
|
-
@confirm_cancel = nil
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def update_width_from_provider
|
|
181
|
-
return unless @width_provider
|
|
182
|
-
|
|
183
|
-
new_width = @width_provider.call
|
|
184
|
-
resize(new_width) if new_width
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def setup_sigwinch_trap
|
|
188
|
-
return unless @width_provider
|
|
189
|
-
|
|
190
|
-
@signal_read, @signal_write = IO.pipe
|
|
191
|
-
Signal.trap("WINCH") do
|
|
192
|
-
@signal_write.write_nonblock(".") rescue nil
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def setup_raw_mode
|
|
197
|
-
@input.raw! if @input.respond_to?(:raw!)
|
|
198
|
-
rescue Errno::ENOTTY
|
|
199
|
-
# Not a terminal (testing)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def restore_terminal
|
|
203
|
-
@input.cooked! if @input.respond_to?(:cooked!)
|
|
204
|
-
rescue Errno::ENOTTY
|
|
205
|
-
# Not a terminal
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def read_key_with_timeout(timeout)
|
|
209
|
-
return nil unless @input.respond_to?(:read_nonblock)
|
|
210
|
-
|
|
211
|
-
watched = [@input]
|
|
212
|
-
watched << @signal_read if @signal_read
|
|
213
|
-
ready = IO.select(watched, nil, nil, timeout)
|
|
214
|
-
return nil unless ready
|
|
215
|
-
|
|
216
|
-
# Drain signal pipe if it woke us
|
|
217
|
-
if @signal_read && ready[0].include?(@signal_read)
|
|
218
|
-
@signal_read.read_nonblock(1) rescue nil
|
|
219
|
-
return nil unless ready[0].include?(@input)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
byte = @input.read_nonblock(1)
|
|
223
|
-
if byte == "\e"
|
|
224
|
-
# Escape sequence
|
|
225
|
-
seq = @input.read_nonblock(2) rescue ""
|
|
226
|
-
case seq
|
|
227
|
-
when "[A" then :up
|
|
228
|
-
when "[B" then :down
|
|
229
|
-
else :escape
|
|
230
|
-
end
|
|
231
|
-
else
|
|
232
|
-
byte
|
|
233
|
-
end
|
|
234
|
-
rescue IO::WaitReadable, EOFError
|
|
235
|
-
nil
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
data/lib/fun_ci/animation.rb
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunCi
|
|
4
|
-
class Animation
|
|
5
|
-
TYPES = {
|
|
6
|
-
failure: { total_frames: 40, priority: 3 },
|
|
7
|
-
timeout: { total_frames: 4, priority: 2 },
|
|
8
|
-
success: { total_frames: 40, priority: 1 },
|
|
9
|
-
stage_pass: { total_frames: 3, priority: 0 }
|
|
10
|
-
}.freeze
|
|
11
|
-
|
|
12
|
-
attr_reader :type, :run_id, :stage, :frame
|
|
13
|
-
|
|
14
|
-
def initialize(type:, run_id:, stage: nil)
|
|
15
|
-
raise ArgumentError, "Unknown animation type: #{type}" unless TYPES.key?(type)
|
|
16
|
-
|
|
17
|
-
@type = type
|
|
18
|
-
@run_id = run_id
|
|
19
|
-
@stage = stage
|
|
20
|
-
@frame = 0
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def advance!
|
|
24
|
-
@frame += 1
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def finished?
|
|
28
|
-
frame >= total_frames
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def total_frames
|
|
32
|
-
TYPES[@type][:total_frames]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def priority
|
|
36
|
-
TYPES[@type][:priority]
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def has_header?
|
|
40
|
-
%i[failure timeout success].include?(@type)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def has_footer?
|
|
44
|
-
%i[failure success].include?(@type)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ansi"
|
|
4
|
-
require_relative "animation_frames"
|
|
5
|
-
require_relative "row_formatter"
|
|
6
|
-
require_relative "duration_formatter"
|
|
7
|
-
|
|
8
|
-
module FunCi
|
|
9
|
-
module AnimationCompositor
|
|
10
|
-
RESET = Ansi::RESET
|
|
11
|
-
STAGE_STAGGER = { "lint" => 0, "build" => 2, "fast" => 4, "slow" => 6 }.freeze
|
|
12
|
-
|
|
13
|
-
def self.header_overlay(animation, width)
|
|
14
|
-
frames = case animation.type
|
|
15
|
-
when :failure then AnimationFrames.failure_header(width)
|
|
16
|
-
when :success then AnimationFrames.success_header(width)
|
|
17
|
-
when :timeout then AnimationFrames.timeout_header(width)
|
|
18
|
-
else return nil
|
|
19
|
-
end
|
|
20
|
-
frame_at(frames, animation.frame)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.footer_overlay(animation, width)
|
|
24
|
-
frames = case animation.type
|
|
25
|
-
when :failure
|
|
26
|
-
AnimationFrames.failure_footer(animation.stage || "stage", width)
|
|
27
|
-
when :success then AnimationFrames.success_footer(width)
|
|
28
|
-
else return nil
|
|
29
|
-
end
|
|
30
|
-
frame_at(frames, animation.frame)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def self.stage_column_overlay(animation, stage_text, col)
|
|
34
|
-
case animation.type
|
|
35
|
-
when :stage_pass then color_flash(animation, stage_text, AnimationFrames.stage_pass_colors)
|
|
36
|
-
when :timeout then color_flash(animation, stage_text, AnimationFrames.timeout_colors)
|
|
37
|
-
when :failure then failure_flanks(animation, stage_text)
|
|
38
|
-
when :success then sparkle_sweep(animation, stage_text)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def self.stage_text_for(run, stage_name)
|
|
43
|
-
stage = run[:stages]&.find { |s| s[:stage] == stage_name }
|
|
44
|
-
return nil unless stage
|
|
45
|
-
|
|
46
|
-
name = RowFormatter::STAGE_NAMES[stage[:stage]] || stage[:stage]
|
|
47
|
-
dur = stage[:duration] ? DurationFormatter.format(stage[:duration]) : "--"
|
|
48
|
-
case stage[:status]
|
|
49
|
-
when "completed" then "#{name} #{dur}"
|
|
50
|
-
when "failed" then "#{name} FAIL #{dur}"
|
|
51
|
-
when "timed_out" then "#{name} TIMEOUT #{dur}"
|
|
52
|
-
else "#{name} --"
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def self.stage_col_for(run, stage_name)
|
|
57
|
-
project = run[:project_path] ? " #{File.basename(run[:project_path])}" : ""
|
|
58
|
-
prefix_len = 2 + 7 + 2 + run[:branch].to_s.length + project.length + 2
|
|
59
|
-
(run[:stages] || []).each do |s|
|
|
60
|
-
return prefix_len + 1 if s[:stage] == stage_name
|
|
61
|
-
prefix_len += stage_text_for(run, s[:stage]).to_s.length + 2
|
|
62
|
-
end
|
|
63
|
-
nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def self.color_flash(animation, stage_text, colors)
|
|
67
|
-
idx = [animation.frame, colors.length - 1].min
|
|
68
|
-
"#{colors[idx]}#{Ansi.strip(stage_text)}#{RESET}"
|
|
69
|
-
end
|
|
70
|
-
private_class_method :color_flash
|
|
71
|
-
|
|
72
|
-
def self.failure_flanks(animation, stage_text)
|
|
73
|
-
particles = AnimationFrames.failure_particles
|
|
74
|
-
return nil if animation.frame >= particles.length
|
|
75
|
-
|
|
76
|
-
p = particles[animation.frame]
|
|
77
|
-
plain = Ansi.strip(stage_text)
|
|
78
|
-
return plain if p[:chars].empty?
|
|
79
|
-
|
|
80
|
-
left = p[:chars].reverse
|
|
81
|
-
right = p[:chars]
|
|
82
|
-
"#{p[:color]}#{left}#{RESET} #{Ansi.bold_red(plain)} #{p[:color]}#{right}#{RESET}"
|
|
83
|
-
end
|
|
84
|
-
private_class_method :failure_flanks
|
|
85
|
-
|
|
86
|
-
def self.sparkle_sweep(animation, stage_text)
|
|
87
|
-
plain = Ansi.strip(stage_text)
|
|
88
|
-
stagger = STAGE_STAGGER[animation.stage] || 0
|
|
89
|
-
pos = animation.frame - stagger
|
|
90
|
-
return nil if pos < 0 || pos > plain.length
|
|
91
|
-
|
|
92
|
-
result = plain.chars.map { |c| "\e[32m#{c}" }
|
|
93
|
-
result[pos] = "\e[1;33m#{plain[pos]}" if pos < plain.length
|
|
94
|
-
"#{result.join}#{RESET}"
|
|
95
|
-
end
|
|
96
|
-
private_class_method :sparkle_sweep
|
|
97
|
-
|
|
98
|
-
def self.frame_at(frames, index)
|
|
99
|
-
return nil if index >= frames.length
|
|
100
|
-
|
|
101
|
-
frames[index]
|
|
102
|
-
end
|
|
103
|
-
private_class_method :frame_at
|
|
104
|
-
end
|
|
105
|
-
end
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ansi"
|
|
4
|
-
|
|
5
|
-
module FunCi
|
|
6
|
-
module AnimationFrames
|
|
7
|
-
RESET = Ansi::RESET
|
|
8
|
-
FOOTER_HOLD = 8
|
|
9
|
-
|
|
10
|
-
def self.failure_particles
|
|
11
|
-
# Left/right flanking debris per frame (chars expanding outward)
|
|
12
|
-
[
|
|
13
|
-
{ chars: "*", color: "\e[1;31m" },
|
|
14
|
-
{ chars: ".*", color: "\e[1;31m" },
|
|
15
|
-
{ chars: ".+*.", color: "\e[38;5;208m" },
|
|
16
|
-
{ chars: "*.+'", color: "\e[38;5;208m" },
|
|
17
|
-
{ chars: "' .", color: "\e[38;5;52m" },
|
|
18
|
-
{ chars: ".", color: "\e[38;5;52m" },
|
|
19
|
-
{ chars: "", color: "" }
|
|
20
|
-
]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.failure_header(width)
|
|
24
|
-
boom = "BOOM"
|
|
25
|
-
[
|
|
26
|
-
nil,
|
|
27
|
-
banner(boom, width, "\e[48;5;124m", particles: "* "),
|
|
28
|
-
banner(boom, width, "\e[48;5;166m", particles: "* * "),
|
|
29
|
-
banner(boom, width, "\e[48;5;52m", particles: ". * * "),
|
|
30
|
-
banner(boom, width, "\e[48;5;236m", particles: "' . * "),
|
|
31
|
-
banner(boom, width, "\e[48;5;236m", particles: ". "),
|
|
32
|
-
nil
|
|
33
|
-
]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def self.failure_footer(stage_name, width)
|
|
37
|
-
up = stage_name.upcase
|
|
38
|
-
down = stage_name.downcase
|
|
39
|
-
hold [
|
|
40
|
-
nil,
|
|
41
|
-
center("\e[1;31m>>> #{up} FAILED <<<#{RESET}", ">>> #{up} FAILED <<<", width),
|
|
42
|
-
center("\e[31m>> #{down} failed <<#{RESET}", ">> #{down} failed <<", width),
|
|
43
|
-
center("\e[2;31m> #{down} failed <#{RESET}", "> #{down} failed <", width),
|
|
44
|
-
nil
|
|
45
|
-
]
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def self.success_header(width)
|
|
49
|
-
text = "ALL PASSED"
|
|
50
|
-
[
|
|
51
|
-
nil,
|
|
52
|
-
banner(text, width, "\e[48;5;22m", particles: ""),
|
|
53
|
-
banner(text, width, "\e[48;5;22m", particles: "* * "),
|
|
54
|
-
banner(text, width, "\e[48;5;22m", particles: "* . . "),
|
|
55
|
-
banner(text, width, "\e[48;5;58m", particles: ". ' ' "),
|
|
56
|
-
banner(text, width, "\e[48;5;22m", particles: "' "),
|
|
57
|
-
banner(text, width, "\e[48;5;22m", particles: ""),
|
|
58
|
-
nil
|
|
59
|
-
]
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def self.success_footer(width)
|
|
63
|
-
hold [
|
|
64
|
-
nil,
|
|
65
|
-
center("\e[1;32m* * * NICE! * * *#{RESET}", "* * * NICE! * * *", width),
|
|
66
|
-
center("\e[32m. + . * NICE! * . + .#{RESET}", ". + . * NICE! * . + .", width),
|
|
67
|
-
center("\e[2;32m' . + . nice . + . '#{RESET}", "' . + . nice . + . '", width),
|
|
68
|
-
nil
|
|
69
|
-
]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def self.timeout_header(width)
|
|
73
|
-
[
|
|
74
|
-
nil,
|
|
75
|
-
banner("TIMED OUT", width, "\e[48;5;58m", particles: ""),
|
|
76
|
-
banner("timed out", width, "\e[48;5;58m", particles: ""),
|
|
77
|
-
nil
|
|
78
|
-
]
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def self.stage_pass_colors
|
|
82
|
-
["\e[1;33m", "\e[1;32m", "\e[32m"]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def self.timeout_colors
|
|
86
|
-
["\e[1;33m", "\e[33m", "\e[1;33m", "\e[33m"]
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def self.banner(text, width, bg_code, particles: "")
|
|
90
|
-
padded = if particles.empty?
|
|
91
|
-
text.center(width)
|
|
92
|
-
else
|
|
93
|
-
inner = "#{particles} #{text} #{particles.reverse}"
|
|
94
|
-
inner.center(width)
|
|
95
|
-
end
|
|
96
|
-
"#{bg_code}\e[1;37m#{padded}#{RESET}"
|
|
97
|
-
end
|
|
98
|
-
private_class_method :banner
|
|
99
|
-
|
|
100
|
-
def self.hold(frames)
|
|
101
|
-
frames.flat_map { |f| Array.new(FOOTER_HOLD, f) }
|
|
102
|
-
end
|
|
103
|
-
private_class_method :hold
|
|
104
|
-
|
|
105
|
-
def self.center(colored_text, plain_text, width)
|
|
106
|
-
pad = [(width - plain_text.length) / 2, 0].max
|
|
107
|
-
"#{" " * pad}#{colored_text}"
|
|
108
|
-
end
|
|
109
|
-
private_class_method :center
|
|
110
|
-
end
|
|
111
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "animations/explosion"
|
|
4
|
-
require_relative "animations/success"
|
|
5
|
-
require_relative "animations/celebrate"
|
|
6
|
-
require_relative "animations/flash"
|
|
7
|
-
require_relative "animations/leprechauns"
|
|
8
|
-
require_relative "animations/yay"
|
|
9
|
-
require_relative "animations/idle"
|
|
10
|
-
require_relative "animations/running"
|
|
11
|
-
|
|
12
|
-
module FunCi
|
|
13
|
-
module AnimationLibrary
|
|
14
|
-
FAILURE = [Animations::Explosion::DATA].freeze
|
|
15
|
-
|
|
16
|
-
SUCCESS = [
|
|
17
|
-
Animations::Success::DATA,
|
|
18
|
-
Animations::Celebrate::DATA,
|
|
19
|
-
Animations::Flash::DATA,
|
|
20
|
-
Animations::Leprechauns::DATA,
|
|
21
|
-
Animations::Yay::DATA
|
|
22
|
-
].freeze
|
|
23
|
-
|
|
24
|
-
IDLE = Animations::Idle::DATA
|
|
25
|
-
|
|
26
|
-
RUNNING = Animations::Running::DATA
|
|
27
|
-
|
|
28
|
-
def self.random_failure
|
|
29
|
-
FAILURE.sample
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def self.random_success
|
|
33
|
-
SUCCESS.sample
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def self.idle
|
|
37
|
-
IDLE
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def self.running
|
|
41
|
-
RUNNING
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|