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