-
Notifications
You must be signed in to change notification settings - Fork 5
/
cli-shell-utils.bash-testing
3950 lines (3371 loc) · 140 KB
/
cli-shell-utils.bash-testing
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#==============================================================================
# cli-shell-utils.bash
# An integrated collection of utilites for shell scripting.
# The .bash version uses $"..." for translation and another bashism in cmd().
#
# (C) 2016 -- 2019 Paul Banham <antiX@operamail.com>
# License: GPLv3 or later
#
# Note regarding reading command-line arguments and options:
#
# This is the oldest part of the code base. Thie idea is to make it easy for
# programs that use this library to provide an easy, intuitive, and clear
# command line user interface.
#
# SHORT_STACK variable, list of single chars that stack
# fatal(msg) routine, fatal([errnum] [errlabel] "error message")
# takes_param(arg) routine, true if arg takes a value
# eval_argument(arg, [val]) routine, do whatever you want with $arg and $val
#==============================================================================
LIB_NAME="cli-shell-utils"
LIB_VERSION="T-2.41.07"
LIB_DATE="Wed 23 Oct 2019 09:37:49 PM MDT"
: ${ME:=${0##*/}}
: ${MY_DIR:=$(dirname "$(readlink -f $0)")}
: ${MY_LIB_DIR:=$(readlink -f "$MY_DIR/../cli-shell-utils")}
: ${LIB_DIR:=/usr/local/lib/cli-shell-utils}
: ${LOCK_FILE:=/run/lock/$ME}
: ${LOG_FILE:=/dev/null}
: ${DATE_FMT:=%Y-%m-%d %H:%M}
: ${DEFAULT_USER:=1000}
: ${K_IFS:=|}
: ${P_IFS:=&}
: ${MAJOR_SD_DEV_LIST:=3,8,22,179,259}
: ${MAJOR_SR_DEV_LIST:=11}
: ${LIVE_MP:=/live/boot-dev}
: ${TORAM_MP:=/live/to-ram}
: ${MIN_ISO_SIZE:=180M}
: ${MENU_PATH:=$MY_LIB_DIR/text-menus/:$LIB_DIR/text-menus}
: ${MIN_LINUXFS_SIZE:=120M}
: ${CONFIG_FILE:=/etc/$ME/$ME.conf}
: ${PROG_FILE:=/dev/null}
: ${LOG_FILE:=/dev/null}
: ${SCREEN_WIDTH:=$(stty size 2>/dev/null | cut -d" " -f2)}
: ${SCREEN_WIDTH:=80}
: ${USB_DIRTY_BYTES:=20000000} # Need this small size for the progress bar to work
: ${PROG_BAR_WIDTH:=100} # Width of progress bar in percent of screen width
: ${VM_VERSION_PROG:=vmlinuz-version}
: ${PROGRESS_SCALE:=100}
: ${INITRD_CONFIG:=/live/config/initrd.out}
: ${CP_ARGS:=--no-dereference --preserve=mode,links --recursive}
: ${WORK_DIR:=/run/$ME}
FUSE_ISO_PROGS="fuseiso"
FOUR_GIG=$((1024 * 1024 * 1024 * 4))
# For testing!!
# FOUR_GIG=$((1024 * 1024 * 10))
# Make sure these start out empty. See lib_clean_up()
unset ORIG_DIRTY_BYTES ORIG_DIRTY_RATIO COPY_PPID COPY_PID SUSPENDED_AUTOMOUNT
unset ULTRA_FIT_DETECTED ADD_DMESG_TO_FATAL DID_WARN_DEFRAG
unset XORRISO_LARGE_FILES XORRISO_SIZE XORRISO_FILE
SANDISK_ULTRA_FIT="SanDisk Ultra Fit"
FORCE_UMOUNT=true
export TEXTDOMAIN="cli-shell-utils"
domain_dir=$(readlink -f "$MY_DIR/../cli-shell-utils/locale")
test -d "$domain_dir" && export TEXTDOMAINDIR=$domain_dir
BAR_80="==============================================================================="
SBAR_80="-------------------------------------------------------------------------------"
RBAR_80=">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
LBAR_80="<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
# Make sure /usr/sbin and /sbin are on the PATH
for p in /usr/sbin /sbin; do
test -d "$p" || continue
[ -z "${PATH##$p:*}" -o -z "${PATH##*:$p:*}" -o -z "${PATH##*:$p}" ] && continue
PATH="$PATH:$p"
done
#------------------------------------------------------------------------------
# Sometimes it's useful to process some arguments (-h --help, for example)
# before others. This can let normal users get simple usage.
# This relies on $SHORT_STACK, takes_param(), and eval_early_arguments()
# Only works on flags, not parameters that take options.
#------------------------------------------------------------------------------
read_early_params() {
local arg
while [ $# -gt 0 ]; do
arg=$1 ; shift
[ ${#arg} -gt 0 -a -z "${arg##-*}" ] || continue
arg=${arg#-}
# Expand stacked single-char arguments
case $arg in
[$SHORT_STACK][$SHORT_STACK]*)
if echo "$arg" | grep -q "^[$SHORT_STACK]\+$"; then
local old_cnt=$#
set -- $(echo $arg | sed -r 's/([a-zA-Z])/ -\1 /g') "$@"
continue
fi;;
esac
takes_param "$arg" && shift
eval_early_argument "$arg"
done
}
#------------------------------------------------------------------------------
# This will read all command line parameters. Ones that start with "-" are
# evaluated one at a time by eval_arguments(). All others are evaluated by
# assign_parameter() which is given a count and a value.
# The amount you should shift to get remaining parameters is in SHIFT_2.
#------------------------------------------------------------------------------
read_all_cmdline_mingled() {
: ${PARAM_CNT:=0}
SHIFT_2=0
while [ $# -gt 0 ]; do
read_params "$@"
shift $SHIFT
SHIFT_2=$((SHIFT_2 + SHIFT))
[ -n "$END_CMDLINE" ] && return
while [ $# -gt 0 -a ${#1} -gt 0 -a -n "${1##-*}" ]; do
PARAM_CNT=$((PARAM_CNT + 1))
assign_parameter $PARAM_CNT "$1"
shift
SHIFT_2=$((SHIFT_2 + 1))
done
done
}
#-------------------------------------------------------------------------------
# Sets "global" variable SHIFT to the number of arguments that have been read.
# Reads a series of "$@" arguments stacking short parameters and dealing with
# options that take arguments. Use global SHORT_STACK for stacking and calls
# eval_argument() and takes_param() which should be provided by the calling
# program. The SHIFT variable tells how many parameters we grabbed.
#-------------------------------------------------------------------------------
read_params() {
# Most of this code is boiler-plate for parsing cmdline args
SHIFT=0
# These are the single-char options that can stack
local arg val
# Loop through the cmdline args
while [ $# -gt 0 -a ${#1} -gt 0 -a -z "${1##-*}" ]; do
arg=${1#-} ; shift
SHIFT=$((SHIFT + 1))
# Expand stacked single-char arguments
case $arg in
[$SHORT_STACK][$SHORT_STACK]*)
if echo "$arg" | grep -q "^[$SHORT_STACK]\+$"; then
local old_cnt=$#
set -- $(echo $arg | sed -r 's/([a-zA-Z])/ -\1 /g') "$@"
SHIFT=$((SHIFT - $# + old_cnt))
continue
fi;;
esac
# Deal with all options that take a parameter
if takes_param "$arg"; then
[ $# -lt 1 ] && fatal $"Expected a parameter after: %s" "-$arg"
val=$1
[ -n "$val" -a -z "${val##-*}" ] \
&& fatal $"Suspicious argument after %s: %s" "-$arg" "$val"
SHIFT=$((SHIFT + 1))
shift
else
case $arg in
*=*) val=${arg#*=} ;;
*) val="???" ;;
esac
fi
eval_argument "$arg" "$val"
[ "$END_CMDLINE" ] && return
done
}
#==============================================================================
# Flow-Control Utilities
#
# These are used for flow-control, not system calls
#==============================================================================
#------------------------------------------------------------------------------
# return true if "$cmd" or "all" are in "$CMDS"
# If true print a small "==> $cmd" message
#------------------------------------------------------------------------------
need() {
need_q "$1" || return 1
local cmd=$1 xlat=${2:-$1}
log_it echo &>/dev/null
set_window_title "$ME $VERSION: $1"
Msg "$(bq ">>") $xlat"
#echo -e "@ $(date +"%Y-%m-%d %H:%M:%S")\n" >> $LOG_FILE
return 0
}
#------------------------------------------------------------------------------
# Same as need() but silent _q = quiet
#------------------------------------------------------------------------------
need_q() {
local cmd=$1 cmd2=${1%%-*}
# The command "all" should not work for update-initrd or format-usb
case $cmd in
update-initrd|format-usb)
echo "$CMDS" | egrep -q "$cmd" && return 0
return 1 ;;
esac
echo "$CMDS" | egrep -q "(^| )($cmd|$cmd2|all)( |$)" || return 1
return 0
}
#------------------------------------------------------------------------------
# Return true if $cmd is in $CMD. Unlike need(), ignore "all" and don't
# print anything extra.
#------------------------------------------------------------------------------
given_cmd() {
local cmd=$1
echo "$CMDS" | egrep -q "(^| )$cmd( |$)" || return 1
return 0
}
#------------------------------------------------------------------------------
# Returns true if $here or "all" are in the comma delimited list $FORCE
#------------------------------------------------------------------------------
force() {
local here=$1 option_list=${2:-$FORCE}
case ,$option_list, in
*,$here,*|*,all,*) return 0 ;;
esac
return 1
}
#------------------------------------------------------------------------------
# See if QUESTION_MODE matches any of the arguments
#------------------------------------------------------------------------------
q_mode() {
local mode
for mode; do
[ "$QUESTION_MODE" = "$mode" ] && return 0
done
return 1
}
#------------------------------------------------------------------------------
# Pause execution if $here or "all" are in comma delimited $PAUSE
#------------------------------------------------------------------------------
pause() {
local here=$1 xlated_here=${2:-$1}
case ,$PAUSE, in
*,$here,*) ;;
*,all,*) ;;
*) return ;;
esac
msg $"Paused at %s" $xlated_here
press_enter
}
#------------------------------------------------------------------------------
# Wait until user presses <Enter>
#------------------------------------------------------------------------------
press_enter() {
local ans enter=$"Enter"
quest $"Press <%s> to continue" "$(pqq "$enter")"
read ans
}
#------------------------------------------------------------------------------
# Make sure all force or pause options are valid
# First param is the name of the list variable so we can transform all spaces
# to commas. See force() and pause()
#------------------------------------------------------------------------------
check_force() { _check_any force "$@" ;}
check_pause() { _check_any pause "$@" ;}
#------------------------------------------------------------------------------
# Shared functionality of two commands above
#------------------------------------------------------------------------------
_check_any() {
local type=$1 name=$2 all=$3 opt
eval "local opts=\$$name"
# Convert spaces to commas
opts=${opts// /,}
eval $name=\$opts
for opt in ${opts//,/ }; do
case ,$all, in
*,$opt,*) continue ;;
*) fatal "Unknown %s option: %s" "$type" "$opt" ;;
esac
done
}
#------------------------------------------------------------------------------
# Test for valid commands and all process cmd+ commands if $ordered is given.
# If $ordered is not given then cmd+ is not allowed. If it is given then
# "cmd+" will add cmd and everything after it in $ordered to the variable
# named as the first argument. See need() NOT cmd() which is different (sorry)
#------------------------------------------------------------------------------
check_cmds() {
local cmds_nam=$1 all=" $2 " ordered=$3 cmds_in cmds_out
eval "local cmds_in=\$$cmds_nam"
local cmd plus_cnt=0 plus
[ "$ordered" ] && plus="+"
for cmd in $cmds_in; do
case $all in
*" ${cmd%$plus} "*) ;;
*) fatal $"Unknown command: %s" $cmd ;;
esac
[ -z "${cmd%%*+}" ] || continue
cmd=${cmd%+}
cmds_out="$cmds_out $(echo "$ORDERED_CMDS" | sed -rn "s/.*($cmd )/\1/p")"
plus_cnt=$((plus_cnt + 1))
[ $plus_cnt -gt 1 ] && fatal "Only one + command allowed"
done
[ ${#cmds_out} -gt 0 ] && eval "$cmds_nam=\"$cmds_in \$cmds_out\""
}
#------------------------------------------------------------------------------
# Works like cmd() below but ignores the $PRETEND_MODE variable. This can be
# useful if you want to always run a command but also want to record the call.
#------------------------------------------------------------------------------
always_cmd() { local PRETEND_MODE= ; cmd "$@" ;}
#------------------------------------------------------------------------------
# Always send the command line and all output to the log file. Set the log
# file to /dev/null to disable this feature.
# If BE_VERBOSE then send output to the screen as well as the log file.
# If VERY_VERBOSE then send commands to screen was well as the long file.
# If PRETEND_MODE then don't actually run the command.
#------------------------------------------------------------------------------
cmd() {
pcmd "$@"
[ "$PRETEND_MODE" ] && return 0
if [ "$BE_VERBOSE" ]; then
"$@" 2>&1 | tee -a $LOG_FILE
else
"$@" 2>&1 | strip_ansi | tee -a $LOG_FILE &>/dev/null
fi
# Warning: Bashism
local ret=${PIPESTATUS[0]}
test -e "$ERR_FILE" && exit 3
return $ret
}
#------------------------------------------------------------------------------
# Just do the printing out part because (cmd cd xxx && yyyy) fails to work
#------------------------------------------------------------------------------
pcmd() {
local pre=" >"
[ "$PRETEND_MODE" ] && pre="p>"
echo "$pre $*" >> $LOG_FILE
[ "$VERY_VERBOSE" ] && printf "%s\n" "$pre $*" | sed "s|$WORK_DIR|.|g"
}
#------------------------------------------------------------------------------
# cd to the first argument and then execute the remaing args.
# I do it in this funny way because (cmd cd xxx && yyy) does not work.
#------------------------------------------------------------------------------
cd_cmd() {
local dir=$1 ; shift
pcmd cd "$dir"
(cd "$dir" && cmd "$@")
}
#------------------------------------------------------------------------------
# What it says on the tin
#------------------------------------------------------------------------------
verbose_cmd() { local BE_VERBOSE=true ; cmd "$@" ;}
#------------------------------------------------------------------------------
# Throw away stderr *sigh*
#------------------------------------------------------------------------------
cmd_ne() {
local pre=" >"
[ "$PRETEND_MODE" ] && pre="p>"
echo "$pre $*" >> $LOG_FILE
[ "$VERY_VERBOSE" ] && printf "%s\n" "$pre $*" | sed "s|$WORK_DIR|.|g"
[ "$PRETEND_MODE" ] && return 0
if [ "$BE_VERBOSE" ]; then
"$@" 2>/dev/null | tee -a $LOG_FILE
else
"$@" 2>/dev/null | strip_ansi | tee -a $LOG_FILE &>/dev/null
fi
# Warning: Bashism
local ret=${PIPESTATUS[0]}
test -e "$ERR_FILE" && exit 3
return $ret
}
#------------------------------------------------------------------------------
# A little convenience routine to run "dd" with no normal output and a fatal
# error if there is an error. Try to capture the error message in the log
# file.
#------------------------------------------------------------------------------
cmd_dd() {
cmd dd status=none "$@" 2>&1 | tee -a $LOG_FILE || fatal "Command 'dd $*' failed"
}
#==============================================================================
# BASIC TEXT UI ELEMENTS
#
# These are meant to provide easy and consistent text UI elements. In addition
# the plan is to automatically switch over to letting a GUI control the UI,
# perhaps by sending menus and questions and so on to the GUI. Ideally one
# set of calls in the script will suffice for both purposes.
#==============================================================================
#------------------------------------------------------------------------------
# The order is weird but it allows the *error* message to work like printf
# The purpose is to make it easy to put questions into the error log.
#------------------------------------------------------------------------------
yes_NO_fatal() {
local code=$1 question=$2 continuation=$3 fmt=$4
shift 4
local msg=$(printf "$fmt" "$@")
q_mode gui && fatal "$msg"
[ -n "$continuation" -a -z "${continuation##*%s*}" ] \
&& continuation=$(printf "$continuation" "$(pq "--force=$code")")
if [ "$AUTO_MODE" ]; then
FATAL_QUESTION=$question
fatal "$code" "$fmt" "$@"
fi
warn "$fmt" "$@"
[ ${#continuation} -gt 0 ] && question="$question\n($m_co$continuation$quest_co)"
yes_NO "$question" && return 0
fatal "$code" "$fmt" "$@"
}
#------------------------------------------------------------------------------
# Default to yes if QUESTION_MODE is "expert" otherwise default to no.
#------------------------------------------------------------------------------
expert_YES_no() {
case $QUESTION_MODE in
gui|simple) return 1 ;;
expert) YES_no "$@" ; return $? ;;
*) yes_NO "$@" ; return $? ;;
esac
}
#------------------------------------------------------------------------------
# Always default no
#------------------------------------------------------------------------------
expert_yes_NO() {
case $QUESTION_MODE in
gui|simple) return 1 ;;
expert) yes_NO "$@" ; return $? ;;
*) yes_NO "$@" ; return $? ;;
esac
}
#------------------------------------------------------------------------------
# Simple "yes" "no" questions. Ask a question, wait for a valid response. The
# responses are all numbers which might be better for internationalizations.
# I'm not sure if we should include the "quit" option or not. The difference
# between the two routines is the default:
# yes_NO() default is "no"
# YES_no() default is "yes"
#------------------------------------------------------------------------------
yes_NO() { _yes_no 1 "$1" ;}
YES_no() { _yes_no 0 "$1" ;}
_yes_no() {
local answer ret=$1 question=$2 def_entry=$(($1 + 1))
[ "$AUTO_MODE" ] && return $ret
local yes=$"yes" no=$"no" quit=$"quit" default=$"default"
quit="$quit_co$quit"
local menu=$(
menu_printf yes "$yes"
menu_printf no "$no"
)
my_select answer "$question" "$menu" "" "$def_entry"
case $answer in
yes) return 0 ;;
no) return 1 ;;
quit) return 1 ;;
*) internal_error _yes_no "$anwer"
esac
}
#------------------------------------------------------------------------------
# Create a simple yes/no/pretend-mode menu
#------------------------------------------------------------------------------
YES_no_pretend() {
local question=$1 answer orig_pretend=$PRETEND_MODE
[ ${#question} -gt 0 ] || question=$"Shall we begin?"
local yes=$"yes" no=$"no" pretend=$"pretend mode"
local menu
if [ "$PRETEND_MODE" ]; then
menu="pretend$P_IFS$pretend\nno$P_IFS$no\n"
else
menu="yes$P_IFS$yes\nno$P_IFS$no\npretend$P_IFS$pretend\n"
fi
my_select answer "$question" "$menu"
case $answer in
yes) return 0 ;;
no) return 1 ;;
pretend) PRETEND_MODE=true ; shout_pretend ; return 0 ;;
*) fatal "Internal error in YES_no_pretend()" ;;
esac
}
#------------------------------------------------------------------------------
# Announce to the world we are in pretend mode
#------------------------------------------------------------------------------
:
shout_pretend() { [ "$PRETEND_MODE" ] && Shout $"PRETEND MODE ENABLED" ;}
#------------------------------------------------------------------------------
# Like printf but prepend with the first argument and the payload separator.
# This is very handy for creating menu entries.
#------------------------------------------------------------------------------
menu_printf() {
local payload=$1 fmt=$2 ; shift 2
printf "%s$P_IFS$m_co$fmt$nc_co\n" "$payload" "$@"
}
#------------------------------------------------------------------------------
# Same as above but allow for singular and plural forms
#------------------------------------------------------------------------------
menu_printf_plural() {
local payload=$1 cnt=$2 lab1=$3 lab2=$4
case $cnt in
1) printf "%s$P_IFS$lab1\n" "$payload" "$(nq $cnt)" ;;
*) printf "%s$P_IFS$lab2\n" "$payload" "$(nq $cnt)" ;;
esac
}
#------------------------------------------------------------------------------
# Print either the first label or the 2nd depending on if $cnt is 1 or not.
# Assume there is exactly one "%s" in the labels
#------------------------------------------------------------------------------
printf_plural() {
local cnt=$1 lab1=$2 lab2=$3
case $cnt in
1) printf "$lab1\n" "$(nq $cnt)" ;;
*) printf "$lab2\n" "$(nq $cnt)" ;;
esac
}
questn_plural() { questn "$(printf_plural "$@")" ; }
warn_plural() { warn "$(printf_plural "$@")" ; }
msg_plural() { msg "$(printf_plural "$@")" ; }
#------------------------------------------------------------------------------
# Generate a simple selection menu based on a data:label data structure.
# The "1)" and so on get added automatically.
#------------------------------------------------------------------------------
my_select() {
if [ -n "$GRAPHICAL_MENUS" ]; then
graphical_select "$@"
else
my_select_num "$@"
fi
}
#------------------------------------------------------------------------------
# This is the original my_select() routine, a front-end to my_select_2()
# This has been superseded by graphical_select().
#------------------------------------------------------------------------------
my_select_num() {
local var=$1 title=$2 list=$3 def_str=$4 default=${5:-1} orig_ifs=$IFS
local IFS=$P_IFS
local cnt=1 dcnt datum label data menu
while read datum label; do
if [ ${#datum} -eq 0 ]; then
[ ${#label} -gt 0 ] && menu="$menu $label\n"
continue
fi
dcnt=$cnt
if [ "$datum" = 'quit' ]; then
dcnt=0
label="$quit_co$label$nc_co"
fi
[ $dcnt = "$default" ] && label=$(printf "%s (%s)" "$label" "$m_co$(cq 'default')")
data="${data}$dcnt:$datum\n"
menu="${menu}$(printf "$quest_co%3d$hi_co)$m_co %${width}s" $dcnt "$label")\n"
cnt=$((cnt+1))
done<<My_Select
$(echo -e "$list")
My_Select
[ "$VERBOSE_SELECT" ] && printf "\nMENU: $title\n$menu" | strip_color >> $LOG_FILE
IFS=$orig_ifs
my_select_2 $var "$title" "$default" "$data" "$menu" "$def_str"
}
#------------------------------------------------------------------------------
# This is the workhorse for several of my menu systems (in other codes).
#
# $var: the name of the variable the answer goes in
# $title: the question asked
# $default: the default selection (a number)
# $data: A string of lines of $NUM:$VALUE
# The number select by the user gets converted to the value
# The third field is used to mimic the value in the menu
# for the initrd text menus but that may not be used here.
# $menu A multi-line string that is the menu to be displayed. It
# The callers job to make sure it is properly aligned with
# the contents of $data.
# $def_str A string to indicate the default answer
#------------------------------------------------------------------------------
my_select_2() {
local var=$1 title=$2 default=$3 data=$4 menu=$5 def_str=$6
if [ -n "$def_str" ]; then
def_str="($(pqq $def_str))"
else
def_str=$"entry"
fi
# Press <Enter> for the default entry
local enter=$"Enter"
# Press <Enter> for the default entry
local press_enter=$"Press <%s> for the default %s"
local p2 def_prompt=$(printf "$press_enter" "$(pqq "$enter")" "$def_str")
local quit=$"quit"
[ -n "$BACK_TO_MAIN" ] && quit=$BACK_TO_MAIN
if [ "$HAVE_MAN" ]; then
# Use <h> for help, <q> to <go back to main menu>
local for_help=$"Use %s for help, %s to %s"
p2=$(printf "$for_help" "'$(pqq h)'" "'$(pqq q)'" "$quit")
else
# Use <q> to <go back to main menu>
local use_x=$"Use %s to %s"
p2=$(printf "$use_x" "'$(pqq q)'" "$quit")
fi
echo
local val input_1 input err_msg
while [ -z "$val" ]; do
echo -e "$quest_co$title$nc_co"
echo -en "$menu" | colorize_menu
[ "$err_msg" ] && printf "$err_co%s$nc_co\n" "$err_msg"
[ "$default" ] && printf "$m_co%s$nc_co\n" "$quest_co$def_prompt$nc_co"
[ "$p2" ] && quest "$p2\n"
local input= input_1=
while true; do
err_msg=
local orig_IFS=$IFS
local IFS=
read -n1 input_1
IFS=$orig_IFS
case $input_1 in
"") input= ; break ;;
[qQ]) input=$input_1 ; break ;;
[hH]) input=$input_1 ; break ;;
[0-9]) echo -ne "\b"
read -ei "$input_1" input
break ;;
*) quest " %s\n" $"Opps. Please try again" ;;
esac
done
# Evaluate again in case of backspacing
case $input in
[qQ]*) if [ -n "$BACK_TO_MAIN" ]; then
eval $var=quit
echo
return
else
final_quit ; continue
fi ;;
[hH]*) if [ "$HAVE_MAN" ]; then
man "$MAN_PAGE" ; echo ; continue
fi;;
esac
[ -z "$input" -a -n "$default" ] && input=$default
if ! echo "$input" | grep -q "^[0-9]\+$"; then
err_msg=$"You must enter a number"
[ "$default" ] && err_msg=$"You must enter a number or press <Enter>"
continue
fi
# Note the initrd text menus assume no : in the payload hence the cut
#val=$(echo -e "$data" | sed -n "s/^$input://p" | cut -d: -f1)
val=$(echo -e "$data" | sed -n "s/^$input://p")
if [ -z "$val" ]; then
local out_of_range=$"The number %s is out of range"
err_msg=$(printf "$out_of_range" "$(pqe $input)")
continue
fi
# FIXME! is this always right?
[ "$val" = 'default' ] && val=
eval $var=\$val
[ "$VERBOSE_SELECT" ] && printf "ANS: $input: $val\n" >> $LOG_FILE
break
done
}
#------------------------------------------------------------------------------
# See if a man page exists. Search locally first then try the man commmand.
#------------------------------------------------------------------------------
find_man_page() {
local man_page dir me me2=$(basename $0 .sh)
[ "$me2" = "$ME" ] && me2=""
HAVE_MAN=
for me in $ME $me2; do
for dir in "$MY_DIR/" "$MY_DIR/man/" ""; do
man_page=$dir$me$ext.1
test -r "$man_page" || continue
HAVE_MAN=true
break
done
[ "$HAVE_MAN" ] && break
man -w $me &>/dev/null || continue
HAVE_MAN=true
break
done
if [ "$HAVE_MAN" ]; then
MAN_PAGE=$man_page
echo "Found man page: $man_page" >> $LOG_FILE
else
echo "No man page found" >> $LOG_FILE
fi
}
#------------------------------------------------------------------------------
# The final quit menu after a 'q' has been detected
#------------------------------------------------------------------------------
final_quit() {
local input
echo
quest $"Press %s again to quit" "$(pqq q)"
echo -n " "
read -n1 input
echo
[ "$input" = 'q' ] || return
_final_quit
}
#------------------------------------------------------------------------------
# Returns true if the input only contains: numbers, commas, spaces. and dashes.
#------------------------------------------------------------------------------
is_num_range() {
[ -n "${1##*[^0-9, -]*}" ]
return $?
}
#------------------------------------------------------------------------------
# Convert a series of numbers, commas, spaces, and dashes into a series of
# numbers. Commas are treated like spaces. A dash surrounded by two numbers
# is considered to be a range of numbers. If the first number is missing and
# <min> is given the <min> is used as the first number. If the 2nd number is
# missing and <max> is given then <max> is used as the 2nd number. Interior
# dashes and numbers are ignored so: 1---20---3 is the same as 1-3.
#------------------------------------------------------------------------------
num_range() {
is_num_range "$1" || return
local list=$1 min=$2 max=$3
local list=$(echo ${list//,/ } | sed -r -e "s/\s+-/-/g" -e "s/-\s+/-/g")
local num found
for num in $list; do
if [ -n "${num##*-*}" ]; then
case ,$found, in
*,$num,*) continue ;;
esac
found=$found,$num
echo $num
continue
fi
local start=${num%%-*}
: ${start:=$min}
local end=${num##*-}
: ${end:=$max}
[ -z "$start" -o -z "$end" ] && continue
for num in $(seq $start $end); do
case ,$found, in
*,$num,*) continue ;;
esac
found=$found,$num
echo $num
done
done
echo "$found" | tr ',' ' ' >&2
}
#------------------------------------------------------------------------------
# An interface to the text menus developed for live-init. Each menu has .menu
# and .data files. The format of the .data files is slightly different here.
#------------------------------------------------------------------------------
cli_text_menu() {
local d dir path=$MENU_PATH
local orig_IFS=$IFS IFS=:
for d in $MENU_PATH; do
test -d "$d" || continue
dir=$d
break
done
IFS=$orig_IFS
if [ -z "$dir" ]; then
warn "Could not find text menus"
return 2
fi
local var=$1 name=$2 title=$3 blurb=$4 text_menu_val
local dfile="$dir/$name.data" mfile="$dir/$name.menu"
local file
for file in "$dfile" "$mfile"; do
[ -r "$file" ] && continue
warn "Missing file %s" "$file"
return 2
done
[ "$blurb" ] && title="$title\n$blurb"
local data=$(cat $dfile)
local menu=$(cat $mfile)
my_select_2 text_menu_val "$title" 1 "$data" "$menu\n"
# FIXME: maybe a char other than : would be better for 2nd delimiter
local val=${text_menu_val%%:*}
local lab=${text_menu_val##*:}
msg $"You chose %s" "$(pq $lab)"
eval $var=\$val
return 0
}
#------------------------------------------------------------------------------
# Return false if no boot dir is found so caller can handle error message
#------------------------------------------------------------------------------
find_live_boot_dir() {
local var=$1 mp=$2 fname=$3 title=$4 min_size=${5:-$MIN_LINUXFS_SIZE}
[ ${#title} -eq 0 ] && title=$"Please select the live boot directory"
local find_opts="-maxdepth 2 -mindepth 2 -type f -name $fname -size +$MIN_LINUXFS_SIZE"
local list=$(find $mp $find_opts | sed -e "s|^$mp||" -e "s|/$fname$||")
case $(count_lines "$list") in
0) return 1 ;;
1) eval $var=\$list
return 0 ;;
esac
local dir menu
while read dir; do
menu="$menu$dir$P_IFS$dir\n"
done<<Live_Boot_Dir
$(echo "$list")
Live_Boot_Dir
if ! q_mode gui; then
my_select $var "$title" "$menu"
return 0
fi
# Try to find the directory that has our running kernel
need_prog "$VM_VERSION_PROG"
local cnt=0 the_dir
while read dir; do
$VM_VERSION_PROG -c "$mp$dir" &>/dev/null || continue
cnt=$((cnt + 1))
the_dir=$dir
done<<Live_Boot_Dir
$(echo "$list")
Live_Boot_Dir
case $cnt in
0) return 2 ;;
1) eval $var=\$the_dir ;;
*) return 3 ;;
esac
}
#==============================================================================
# USE FIND COMMAND TO MAKE A MENU OF .iso files
# Experiemental and currently not used
#==============================================================================
#------------------------------------------------------------------------------
# Expand ~/ to the actual user's home directory (we run as root).
#------------------------------------------------------------------------------
expand_directories() {
local dir
for dir; do
# Fudge ~/ so it becomes the default users' home, not root's
case $dir in
~/*) dir=$(get_user_home)/${dir#~/} ;;
esac
eval "dir=$dir"
if test -d $dir; then
echo $dir
else
warn "Not a directory %s" "$dir"
fi
done
}
#------------------------------------------------------------------------------
# Use "find" command to provide a list of .iso files. WORK IN PROGRESS.
#------------------------------------------------------------------------------
cli_search_file() {
local var=$1 spec=$2 dir_list=$3 max_depth=$4 max_found=${5:-20} min_size=${6:-$MIN_ISO_SIZE}
local _sf_input title dir_cnt invalid
while true; do
dir_list=$(expand_directories $dir_list)
dir_cnt=$(echo "$dir_list" | wc -w)
title=$(
if [ $dir_cnt -eq 1 ]; then
quest "Will search %s directory: %s\n" "$(pnq $dir_cnt)" "$(pqq $dir_list)"
else
quest "Will search %s directories: %s\n" "$(pnq $dir_cnt)" "$(pqq $dir_list)"
fi
quest "for files matching %s with a size of %s or greater\n" "$(pqq $spec)" "$(pq $min_size)"
quest "Will search down %s directories" "$(pqq $max_depth)"
)
if [ $dir_cnt -le 0 ]; then
while [ $dir_cnt -le 0 ]; do
warn "No directories were found in the list. Please try again"
cli_get_text dir_list "Enter directories"
dir_list=$(expand_directories $dir_list)
dir_cnt=$(echo "$dir_list" | wc -w)
done
continue
fi
my_select _sf_input "$title" "$(select_file_menu $invalid)"
invalid=
# FIXME: need to make some of these entries more specfic
case $_sf_input in
search) ;;
dirs) cli_get_text dir_list "Enter directories" ; continue ;;
depth) cli_get_text max_depth "Enter maximum depth (1-9)" ; continue ;;
spec) cli_get_text spec "Enter file specfication" ; continue ;;
esac
local depth=1 dir f found found_cnt
echo -n 'depth:'
while [ $depth -le $max_depth ]; do
echo -n " $depth"
for dir in $dir_list; do
test -d "$dir" || continue
local args="-maxdepth $depth -mindepth $depth -type f -size +$MIN_ISO_SIZE"
f=$(find "$dir" $args -iname "$spec" -print0 | tr '\000' '\t')
[ ${#f} -gt 0 ] && found="$found$f"
found_cnt=$(count_tabs "$found")
echo -n "($found_cnt)"