Fix BS handling
[smenu.git] / examples / simple_menu / simple_menu.sh
blob29d7740f7fed0bf6c29708f3cf5d1516ba325ec4
1 #!/usr/bin/env bash
3 # Variables
4 # """""""""
5 PROG=${0#*/}
7 typeset -a MENU_STACK # A stack of MENUS to store the previously visited menus
9 # Array of menu characteristics for caching purpose
10 # '''''''''''''''''''''''''''''''''''''''''''''''''
11 typeset -A MENU_ARRAY
12 typeset -A COL_ARRAY
13 typeset -A CENTERING_ARRAY
14 typeset -A TITLE_ARRAY
15 typeset -A ERASE_ARRAY
17 SEL= # The selection
18 SMENU= # The smenu path will be stored here
20 # ============================ #
21 # Usage function, always fails #
22 # ============================ #
23 function usage
25 echo "Usage: $PROG menu_file[.mnu] user_program," >&2
26 echo " read the README for an example" >&2
27 exit 1
30 # ==================== #
31 # Fatal error function #
32 # ==================== #
33 function error
35 echo $* >&2
36 exit 1
39 # The script expects exactly one argument (the filename of the root menu).
40 # """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
41 (( $# != 2 )) && usage
43 USER_PROGRAM=$2
45 # ======================================================================= #
46 # Parse a level in the menu hierarchy and call process with the selection #
47 # (possibly empty). #
48 # ======================================================================= #
49 function process_menu
51 TITLE=$'\n'"[ENTER: select, q: abort]" # Untitled by default
52 CENTERING="-M" # Is the menu centered in the screen ?, defaults to yes
53 ERASE="-d" # Destroy the selection window after use. Defaults to yes
55 MENU_FILE=$1
56 MENU= # Make sure the working area is empty
58 # If the menu has already been seen, read its characteristics from the cache
59 # else construct them.
60 # """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
61 if [[ -z ${MENU_ARRAY[$MENU_FILE]} ]]; then
62 COL=1
64 # Parse the directives embedded in the menu file
65 # """"""""""""""""""""""""""""""""""""""""""""""
66 MENU_DIRECIVES=$(grep '^\.' $MENU_FILE)
68 while read DIRECTIVE VALUE; do
69 case $DIRECTIVE in
70 .columns) # Number of columns of the menu
71 COL=$VALUE
72 (( COL < 1 || COL > 15 )) && error "$DIRECTIVE, bad value"
75 .centered) # Is the menu centered?
76 [[ $VALUE == no ]] && CENTERING=""
79 .eraseafter) # Will the space used by the menu be reclaimed?
80 [[ $VALUE == no ]] && ERASE=""
83 .title) # The menu title
84 TITLE="$VALUE"$'\n'$TITLE
88 error "bad directive $DIRECTIVE"
90 esac
91 done <<< "$MENU_DIRECIVES"
93 # Build the menu entries in the working area
94 # """"""""""""""""""""""""""""""""""""""""""
95 MENU_LINES=$(grep -v -e '^\.' -e '^#' -e '^[ \t]*$' $MENU_FILE)
97 # The special tag "---" creates an empty entry (a hole in a column)
98 # The special tag "===" create an empty line
99 # The special tag "EXIT" permit to exit the menu
100 # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
102 while read TAG VALUE; do
103 ITEMS_TO_ADD=1 # By default, only one iteration of the tag is taken
104 # into account
106 (( ${#TAG} > 30 )) \
107 && error "Menu tag \"${TAG}\" too long (max 30 characters)."
109 [[ $TAG == --- ]] && VALUE=@@@
110 [[ $TAG == === ]] && VALUE=@@@ && ITEMS_TO_ADD=COL
112 [[ -z $VALUE ]] && error "Empty menu entry for $TAG"
113 [[ $TAG == EXIT ]] && TAG="@EXIT@xxxxxxxxxxxxxxxxxxxxxxxx00"
114 [[ $TAG == "<"* ]] && TAG="<xxxxxxxxxxxxxxxxxxxxxxxxxxxxx00"
116 # Protect quotes in VALUE
117 # """""""""""""""""""""""
118 FINAL_VALUE=$(echo "$VALUE" | sed -e 's/"/\\"/' -e "s/'/\\\\'/")
120 while (( ITEMS_TO_ADD-- > 0 )); do
121 MENU+="'$TAG $FINAL_VALUE'"$'\n'
122 done
123 done <<< "$MENU_LINES"
125 # Feed the cache
126 # """"""""""""""
127 MENU_ARRAY[$MENU_FILE]="$MENU"
128 COL_ARRAY[$MENU_FILE]=$COL
129 CENTERING_ARRAY[$MENU_FILE]=$CENTERING
130 TITLE_ARRAY[$MENU_FILE]=$TITLE
131 ERASE_ARRAY[$MENU_FILE]=$ERASE
132 else
133 # Read from the cache
134 # """""""""""""""""""
135 MENU="${MENU_ARRAY[$MENU_FILE]}"
136 COL=${COL_ARRAY[$MENU_FILE]}
137 CENTERING=${CENTERING_ARRAY[$MENU_FILE]}
138 TITLE=${TITLE_ARRAY[$MENU_FILE]}
139 ERASE=${ERASE_ARRAY[$MENU_FILE]}
142 # Display the menu and get the selection
143 # """"""""""""""""""""""""""""""""""""""
144 SEL=$(echo "$MENU" | $SMENU \
145 $CENTERING \
146 $ERASE \
147 -N \
148 -F -D o:30 i:0 n:2 \
149 -n \
150 -a m:0/6 \
151 -m "$TITLE" \
152 -t $COL \
153 -S'/@@@/ /' \
154 -S'/^[^ ]+ //v' \
155 -e @@@)
156 SEL=${SEL%% *}
159 # Check for the presence of smenu
160 # """""""""""""""""""""""""""""""
161 SMENU=$(which smenu 2>/dev/null)
162 [[ -x "$SMENU" ]] || error "smenu is not in the PATH or not executable."
164 # Initialize the menu stack with the argument
165 # """""""""""""""""""""""""""""""""""""""""""
166 MENU_STACK+=(${1%.mnu}.mnu)
168 # And process the main menu
169 # """""""""""""""""""""""""
170 process_menu ${1%.mnu}.mnu
172 # According to the selection, navigate in the submenus or return
173 # the tag associated with the selected menu entry.
174 # """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
175 while true; do
176 if [[ $SEL == "<"* ]]; then
177 # Back to the previous menu
178 # '''''''''''''''''''''''''
179 if (( ${#MENU_STACK[*]} == 1 )); then
180 process_menu ${MENU_STACK[${#MENU_STACK[*]}-1]}
181 else
182 # Unstack the newly found submenu
183 # '''''''''''''''''''''''''''''''
184 unset MENU_STACK[${#MENU_STACK[*]}-1]
186 # And generate the previous menu
187 # ''''''''''''''''''''''''''''''
188 process_menu ${MENU_STACK[${#MENU_STACK[*]}-1]}
191 elif [[ $SEL == ">"* ]]; then
192 # Enter the selected submenu
193 # ''''''''''''''''''''''''''
194 SMENU_FILE=${SEL#>}
195 SMENU_FILE=${SMENU_FILE%.mnu}.mnu
196 [[ -f $SMENU_FILE ]] || error "The file $SMENU_FILE was not found/readable."
198 # Stack the newly found submenu
199 # '''''''''''''''''''''''''''''
200 MENU_STACK+=($SMENU_FILE)
202 # And generate the submenu
203 # ''''''''''''''''''''''''
204 process_menu $SMENU_FILE
205 else
206 # An empty selection means than q or ^C has been hit
207 # ''''''''''''''''''''''''''''''''''''''''''''''''''
208 [[ -z $SEL ]] && exit 0
210 # Output the selected menu tag or exit the menu without outputting
211 # anything.
212 # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
213 [[ $SEL == @EXIT@* ]] && exit 0
215 # Launch the user action which has the responsibility to act according
216 # to the tag passed as argument.
217 # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
218 $USER_PROGRAM $SEL
220 # And re-generate the current menu
221 # ''''''''''''''''''''''''''''''''
222 process_menu ${MENU_STACK[-1]}
224 done