12b43534274020c43f59c4630fce22cce9155e1d
[shellsnippets/shellsnippets.git] / mksh / progress-bar
1 # -*- mode: sh -*-
2 # $MirOS: contrib/hosted/tg/progress-bar,v 1.4 2018/12/02 08:12:31 tg Exp $
3 #-
4 # Copyright © 2018
5 #       mirabilos <m@mirbsd.org>
6 # Copyright © 2017
7 #       mirabilos <t.glaser@tarent.de>
8 # Copyright © 2015, 2017
9 #       mirabilos <thorsten.glaser@teckids.org>
10 #
11 # Provided that these terms and disclaimer and all copyright notices
12 # are retained or reproduced in an accompanying document, permission
13 # is granted to deal in this work without restriction, including un‐
14 # limited rights to use, publicly perform, distribute, sell, modify,
15 # merge, give away, or sublicence.
16 #
17 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
18 # the utmost extent permitted by applicable law, neither express nor
19 # implied; without malicious intent or gross negligence. In no event
20 # may a licensor, author or contributor be held liable for indirect,
21 # direct, other damage, loss, or other issues arising in any way out
22 # of dealing in the work, even if advised of the possibility of such
23 # damage or existence of a defect, except proven that it results out
24 # of said person’s immediate fault when using the work as intended.
25 #-
26 # Shell library for easy display of a progress bar
27 #
28 # Usage:
29 # – before:   init_progress_bar $n
30 # – $n times: draw_progress_bar
31 # – after:    done_progress_bar
32 #
33 # init_progress_bar trashes the EXIT and SIGWINCH traps, which later
34 # are cleared, again, by done_progress_bar; note this forces using a
35 # “while [[ -n $(jobs) ]]; do wait; done” loop instead of just wait.
36 # Use “redo_progress_bar [±]$n” to rerender updating the estimate.
37
38 # global variables used by this library
39 _cnt_progress_bar=0
40 _cur_progress_bar=0
41 isin_progress_bar=0
42 nlin_progress_bar=0
43 _cch_progress_bar=
44
45 if [[ $KSH_VERSION = @(\@\(#\)MIRBSD KSH R)@(5[5-9]|[6-9][0-9]|[1-9][0-9][0-9])\ * ]]; then
46         alias global='typeset -g'
47 else
48         alias global=global
49 fi
50
51 # args: $1 = number of draw_progress_bar calls to make up 100%
52 function init_progress_bar {
53         global -i _cnt_progress_bar=$1 _cur_progress_bar=0
54         global -i nlin_progress_bar=$LINES isin_progress_bar=1
55         _cch_progress_bar=
56
57         trap 'done_progress_bar $?' EXIT
58         trap 'sigwinch_progress_bar' WINCH
59         # set up scrolling region, draw initial empty bar
60         sigwinch_progress_bar
61 }
62
63 unalias global
64
65 function sigwinch_progress_bar {
66         (( isin_progress_bar )) || return 0
67
68         # get new terminal size
69         nlin_progress_bar=$LINES
70
71         # save position; clear scrolling region; restore position; newline;
72         # up one line (to ensure we are not in the last line); save position;
73         # clear rest of screen; set new scrolling region; restore position
74         print -nu2 "\\e7\\e[0;0r\\e8\\n\\e[A\\e7\\e[J\\e[1;$((# nlin_progress_bar - 1))r\\e8"
75
76         # redraw progress bar
77         draw_progress_bar_internal
78 }
79
80 function done_progress_bar {
81         (( isin_progress_bar )) || return 0
82         isin_progress_bar=0
83         _cch_progress_bar=
84         # save position; clear scrolling region; restore position;
85         # save position; clear rest of screen; restore position
86         print -nu2 "\\e7\\e[0;0r\\e8\\e7\\e[J\\e8"
87         trap - WINCH
88         trap - EXIT
89         [[ -z $1 ]] || return $1
90         (( _cur_progress_bar == _cnt_progress_bar )) || \
91             print -ru2 W: expected $_cnt_progress_bar draw_progress_bar calls, \
92             got only $_cur_progress_bar
93 }
94
95 function draw_progress_bar {
96         # increment current progress
97         if (( ++_cur_progress_bar > _cnt_progress_bar )); then
98                 print -ru2 "W: too many draw_progress_bar calls"
99                 _cur_progress_bar=$_cnt_progress_bar
100         fi
101         # remaining drawing code
102         draw_progress_bar_internal
103 }
104
105 function redo_progress_bar {
106         if [[ $1 = +* ]]; then
107                 (( _cnt_progress_bar += ${1#+} ))
108         elif [[ $1 = -* ]]; then
109                 (( _cnt_progress_bar -= ${1#-} ))
110         else
111                 _cnt_progress_bar=$1
112         fi
113         if (( _cur_progress_bar > _cnt_progress_bar )); then
114                 print -ru2 W: new estimate $_cnt_progress_bar too low \
115                     after $_cur_progress_bar calls
116                 _cur_progress_bar=$_cnt_progress_bar
117         fi
118         _cch_progress_bar=
119         draw_progress_bar_internal
120 }
121
122 function draw_progress_bar_internal {
123         local bar num w=$COLUMNS pct
124
125         ((# num = (_cur_progress_bar * w * 8) / _cnt_progress_bar ))
126         ((# pct = _cur_progress_bar * 100 / _cnt_progress_bar ))
127         [[ $_cch_progress_bar != $num.$pct ]] || return 0
128         while ((# num >= 8 )); do
129                 bar+=█
130                 ((# num -= 8 ))
131         done
132         case $num {
133         (7) bar+=▉ ;;
134         (6) bar+=▊ ;;
135         (5) bar+=▋ ;;
136         (4) bar+=▌ ;;
137         (3) bar+=▍ ;;
138         (2) bar+=▎ ;;
139         (1) bar+=▏ ;;
140         }
141         # fill complete line, right-align completion percentage display
142         local -R$w spc="$pct%"
143         # elide percentage when it stops fitting
144         ((# (_cur_progress_bar * w / _cnt_progress_bar) > (w - 4) )) && spc=
145         # save position; go to last line; set colours;
146         # output a line full of spaces (and completion percentage);
147         # jump to first column; output bar (line præfix); restore position
148         print -nu2 -- "\\e7\\e[$nlin_progress_bar;1H\\e[0;1;33;44m$spc\\r$bar\\e8"
149         _cch_progress_bar=$num.$pct
150 }