-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathprogress.sh
320 lines (240 loc) · 7.52 KB
/
progress.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#!/usr/bin/env bash
# An asynchronous progress bar inspired by APT PackageManagerFancy Progress
# Copyright (C) 2018 Kristoffer Minya
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>
set -a
# The following terms are used for maintainment
# FIXME : Code that needs to improved or does not work
# DEBUG : Code that needs to be debugged
# TEST : Code that needs to be tested for alternatives
# TODO : Reminder for code that needs to be added or changed
# FUTURE : For future changes, this must be considered
# IDEA : Ideas for future improvement or added features
percentage="0.0"
last_reported_progress=-1
#-- In which rate reporting should be done
reporting_steps=${reporting_steps:-1} # reporting_step can be set by the caller, defaults to 1
foreground="${foreground:-$(tput setaf 0)}" # Foreground can be set by the caller, defaults to black
background="${background:-$(tput setab 2)}" # Background can be set by the caller, defaults to green
reset_color="$(tput sgr0)"
#-- Options to change progressbar look
LEFT_BRACKET="${LEFT_BRACKET:-[}"
RIGHT_BRACKET="${RIGHT_BRACKET:-]}"
FILL="${FILL:-#}"
REMAIN="${REMAIN:-.}"
#-- Command aliases for readability
save_cursor='tput sc'
restore_cursor='tput rc'
disable_cursor='tput civis'
enable_cursor='tput cnorm'
scroll_area='tput csr'
move_to='tput cup'
move_up='tput cuu'
flush='tput ed'
# Bash does not handle floats
# This section defines some math functions using awk
# ==================================================
export LC_ALL=C
math::floor() {
#-- This function takes a pseudo-floating point as argument
#-- and rounds down to nearest integer
awk -v f="$1" 'BEGIN{f=int(f); print f}'
}
math::round() {
#-- This function takes a pseudo-floating point as argument
#-- and rounds to nearest integer
awk -v f="$1" 'BEGIN {printf "%.0f\n", f}'
}
math::min() {
#-- Takes two values as arguments and compare them
awk -v f1="$1" -v f2="$2" 'BEGIN{if (f1<=f2) min=f1; else min=f2; print min "\n"}'
}
math::max() {
#-- Takes two values as arguments and compare them
awk -v f1="$1" -v f2="$2" 'BEGIN{if (f1>f2) max=f1; else max=f2; print max "\n"}'
}
math::calc() {
#-- Normal calculator
awk "BEGIN{print $*}"
}
####################################################
# The main function stack
# ==================================================
__tty_size(){
set -- $(stty size)
HEIGHT=$1
WIDTH=$2
}
__change_scroll_area() {
local -i n_rows=$1
#-- Return if number of lines is 1
if (( n_rows <= 1)); then
return 1
fi
((n_rows-=2))
#-- Go down one line to avoid visual glitch
#-- when terminal scroll region shrinks by 1
echo
#-- Save cursor position
eval "${save_cursor}"
#-- Set scroll region
eval "${scroll_area} 0 $n_rows"
#-- Restore cursor
eval "${restore_cursor}"
#-- Move up 1 line in case cursor was saved outside scroll region
eval "${move_up} 2"
echo
#-- Set tty size to reflect changes to scroll region
#-- this is to avoid i.e pagers to override the progress bar
((++n_rows))
#-- Temporarily disabling SIGWINCH to avoid a loop caused by stty sending SIGWINCH whenever theres a change in size
trap '' WINCH
stty rows $n_rows
trap handle_sigwinch WINCH
}
__status_changed() {
local -i StepsDone TotalSteps __int_percentage
((StepsDone=$1))
((TotalSteps=$2))
#-- FIXME
#-- Sanity check reporting_steps, if this value is too big no progress will be written
#-- Should that really be checked here?
percentage=$(math::calc "$(math::calc "$StepsDone/$TotalSteps")*100.00")
((__int_percentage=$(math::round "$percentage")))
printf -v progress_str "Progress: [%3li%%]" $__int_percentage
if (( __int_percentage < (last_reported_progress + reporting_steps) )); then
return 1
else
return 0
fi
}
__progress_string() {
local output Percent
local -i OutputSize BarSize BarDone it
output=""
Percent="$1"
((OutputSize=$2))
#-- Return an empty string if OutputSize is less than 3
if ((OutputSize < 3)); then
echo "$output"
return 1
fi
((BarSize=OutputSize-2))
BarDone=$(math::max 0 "$(math::min $BarSize "$(math::floor "$(math::calc "$Percent*$BarSize")")")")
output="${LEFT_BRACKET}"
for (( it = 0; it < BarDone; it++ )); do
output+="${FILL}"
done
for (( it = 0; it < BarSize - BarDone; it++ )); do
output+="${REMAIN}"
done
output+="${RIGHT_BRACKET}"
echo "$output"
return 0
}
__draw_status_line(){
__tty_size
if (( HEIGHT < 1 || WIDTH < 1 )); then
return 1
fi
local current_percent progress_bar
local -i padding progressbar_size
((padding=4))
progress_bar=""
#-- Save the cursor
eval "${save_cursor}"
#-- Make cursor invisible
eval "${disable_cursor}"
#-- Move to last row
eval "${move_to} $((HEIGHT)) 0"
printf '%s' "${background}${foreground}${progress_str}${reset_color}"
((progressbar_size=WIDTH-padding-${#progress_str}))
current_percent=$(math::calc "$percentage/100.00")
progress_bar="$(__progress_string "${current_percent}" ${progressbar_size})"
printf '%s' " ${progress_bar} "
#-- Restore the cursor
eval "${restore_cursor}"
eval "${enable_cursor}"
((last_reported_progress=$(math::round "$percentage")))
return 0
}
bar::start() {
#-- TODO: Track process that called this function
# proc...
E_START_INVOKED=-1
__tty_size
__change_scroll_area $HEIGHT
}
bar::stop() {
E_STOP_INVOKED=-1
if (( ! ${E_START_INVOKED:-0} )); then
echo "Warn: bar::stop called but bar::start was not invoked" >&2
echo "Returning.." # Exit or return?
return 1
fi
#-- Reset bar::start check
E_START_INVOKED=0
__tty_size
if ((HEIGHT > 0)); then
#-- Passing +2 here because we changed tty size to 1 less than it actually is
__change_scroll_area $((HEIGHT+2))
#-- tput ed might fail (OS X) in which case we force clear
trap 'printf "\033[J"' ERR
#-- Flush progress bar
eval "${flush}"
trap - ERR
#-- Go up one row after flush
eval "${move_up} 1"
echo
fi
#-- Restore original (if any) handler
trap - WINCH
return 0
}
#-- FIXME: Pass worker pid?
bar::status_changed() {
if (( ! ${E_START_INVOKED:-0} )); then
echo "ERR: bar::start not called" >&2
echo "Exiting.."
exit 1
fi
local -i StepsDone TotalSteps
((StepsDone=$1))
((TotalSteps=$2))
if ! __status_changed $StepsDone $TotalSteps; then
return 1
fi
__draw_status_line
return $?
}
####################################################
# This section defines some functions that should be
# triggered for traps
# ==================================================
handle_sigwinch(){
__tty_size
n_rows=$HEIGHT
__change_scroll_area $n_rows
__draw_status_line
}
handle_exit(){
#-- if stop_exit doesn't have value it means it wasn't invoked
(( ! ${E_STOP_INVOKED:-0} )) && bar::stop
trap - EXIT
}
####################################################
set +a
trap handle_sigwinch WINCH
trap handle_exit EXIT HUP INT QUIT PIPE TERM