diff options
author | pgen <p.gen.progs@gmail.com> | 2019-03-11 00:33:05 +0100 |
---|---|---|
committer | pgen <p.gen.progs@gmail.com> | 2019-03-13 19:59:54 +0100 |
commit | b30b7afbbb527392a1f1654fa9e0d52f3757392a (patch) | |
tree | d3b933086023068e364a322f3741aa2156fc93dd /examples | |
parent | 2df7ebbd411946da92f8f4d1a7065bb8b318aec4 (diff) |
Add an example of a hierarchical menu interpreter
Diffstat (limited to 'examples')
-rw-r--r-- | examples/simple_menu/README | 39 | ||||
-rwxr-xr-x | examples/simple_menu/actions.sh | 13 | ||||
-rw-r--r-- | examples/simple_menu/main.mnu | 24 | ||||
-rwxr-xr-x | examples/simple_menu/simple_menu.sh | 220 | ||||
-rw-r--r-- | examples/simple_menu/sub1.mnu | 7 | ||||
-rw-r--r-- | examples/simple_menu/sub2.mnu | 6 |
6 files changed, 309 insertions, 0 deletions
diff --git a/examples/simple_menu/README b/examples/simple_menu/README new file mode 100644 index 0000000..f2f7b4d --- /dev/null +++ b/examples/simple_menu/README @@ -0,0 +1,39 @@ +This example presents a simple hierarchical menu interpreter. + +Each menu and submenu are in a file suffixed by .mnu and is constituted +by directives and menu entries/preudo-entries. Each of them are in its +proper line. + +Comment lines are allowed and are introduced by a '#' in the first column + +The directives are: + +.columns: Set the number of columns in the menu +.centered: Tell if the menu must be centered +.eraseafter: Tell if the menu window must be destroyed after the selection + and the old cursor location restored. +.title: Set the menu tittle. + +The item lines has at least two fields: a tag and a menu item which will +be displayed. + +The tag is normally the returned value when an item is selected but may +also be part of a pseudo-entry. + +These special tags are: + +>xxx : Loads the submenu file xxx.mnu and interprets it. +< : Reload the previous menu file and interprets it +--- : Inserts an empty item in the menu +=== : Inserts an empty line in the menu (useful when there is more than + 1 column) +EXIT : Exits the menu without outputting anything. + +As usual, 'ENTER' triggers the selection and 'q' quits the menu without +outputting anything. + +To launch the demo, just enter: ./simple_menu.sh main.mnu ./actions.sh + +The first argument is the main menu file and the second one is the path +of the program which will be called each time a selection is made. This +program will be given the selected tag as argument. diff --git a/examples/simple_menu/actions.sh b/examples/simple_menu/actions.sh new file mode 100755 index 0000000..34fb788 --- /dev/null +++ b/examples/simple_menu/actions.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +(( $# != 1 )) && exit 0 + +ACTION=$1 + +case $ACTION in + *) echo "Tag $ACTION selected." + sleep 1 + ;; +esac + +exit 0 diff --git a/examples/simple_menu/main.mnu b/examples/simple_menu/main.mnu new file mode 100644 index 0000000..fe3e363 --- /dev/null +++ b/examples/simple_menu/main.mnu @@ -0,0 +1,24 @@ +# Directives +# .columns: Number of columns in the menu +# .centered: The menu will be centered +# .eraseafter: The menu window will be destroyed after the selection +# .title: The menu tittle + +.columns 2 +.centered yes +.eraseafter yes +.title Simple menu + +ITEM1 First item +>sub1 First submenu +ITEM3 Third item +>sub2 Second submenu +=== +ITEM5 Fifth item +ITEM6 Sixth item +ITEM7 Seventh item +ITEM8 Eighth item +ITEM9 Ninth item +ITEM10 Tenth item +=== +EXIT Exit without selection diff --git a/examples/simple_menu/simple_menu.sh b/examples/simple_menu/simple_menu.sh new file mode 100755 index 0000000..2092478 --- /dev/null +++ b/examples/simple_menu/simple_menu.sh @@ -0,0 +1,220 @@ +#/bin/bash + +# Variables +# """"""""" +PROG=${0#*/} + +typeset -a MENU_STACK # A stack of MENUS to store the previously visited menus + +# Array of menu characteristics for caching purpose +# ''''''''''''''''''''''''''''''''''''''''''''''''' +typeset -A MENU_ARRAY +typeset -A COL_ARRAY +typeset -A CENTERING_ARRAY +typeset -A TITLE_ARRAY +typeset -A ERASE_ARRAY + +SEL= # The selection + +# ============================ # +# Usage function, always fails # +# ============================ # +function usage +{ + echo "Usage: $PROG menu_file[.mnu] user_program," >&2 + echo " read the README for an example" >&2 + exit 1 +} + +# ==================== # +# Fatal error function # +# ==================== # +function error +{ + echo $* >&2 + exit 1 +} + +# The script expects exactly one argument (the filename of the root menu). +# """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +(( $# != 2 )) && usage + +USER_PROGRAM=$2 + +# ======================================================================= # +# Parse a level in the menu hierarchy and call process with the selection # +# (possibly empty). # +# ======================================================================= # +function process_menu +{ + TITLE="[ENTER: select, q: abort]"$'\n' # Untitled by default + CENTERING= # Is the menu centered in the screen ? + ERASE= # Destroy the selection window after use + + MENU_FILE=$1 + MENU= # Make sure the working area is empty + + # If the menu has already been seen, read its characteristics from the cache + # else construct them. + # """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + if [[ -z ${MENU_ARRAY[$MENU_FILE]} ]]; then + COL=1 + + # Parse the directives embedded in the menu file + # """""""""""""""""""""""""""""""""""""""""""""" + MENU_DIRECIVES=$(grep '^\.' $MENU_FILE) + + while read DIRECTIVE VALUE; do + case $DIRECTIVE in + .columns) # Number of columns of the menu + COL=$VALUE + (( COL < 1 || COL > 15 )) && error "$DIRECTIVE, bad value" + ;; + + .centered) # Is the menu centered? + [[ $VALUE == yes ]] && CENTERING="-M" + ;; + + .eraseafter) # Will the space used by the menu be reclaimed? + [[ $VALUE == yes ]] && ERASE="-d" + ;; + + .title) # The menu title + TITLE="$VALUE"$'\n'$TITLE + ;; + + *) + error "bad directive $DIRECTIVE" + ;; + esac + done <<< "$MENU_DIRECIVES" + + # Build the menu entries in the working area + # """""""""""""""""""""""""""""""""""""""""" + MENU_LINES=$(grep -v -e '^\.' -e '^#' -e '^[ \t]*$' $MENU_FILE) + + # The special tag "---" creates an empty entry (a hole in a column) + # The special tag "===" create an empty line + # The special tag "EXIT" permit to exit the menu + # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + + while read TAG VALUE; do + ITEMS_TO_ADD=1 # By default, only one iteration of the tag is taken + # into account + + (( ${#TAG} > 10 )) && error "Menu tag too long (max 10 characters)." + + [[ $TAG == --- ]] && VALUE=@@@ + [[ $TAG == === ]] && VALUE=@@@ && ITEMS_TO_ADD=COL + + [[ -z $VALUE ]] && error "Empty menu entry for $TAG" + [[ $TAG == EXIT ]] && TAG="@EXIT@xxxx00" + [[ $TAG == "<"* ]] && TAG="<xxxxxxxxx00" + + # Protect quotes in VALUE + # """"""""""""""""""""""" + FINAL_VALUE=$(echo "$VALUE" | sed -e 's/"/\\"/' -e "s/'/\\\\'/") + + while (( ITEMS_TO_ADD-- > 0 )); do + MENU+="'$TAG $FINAL_VALUE'"$'\n' + done + done <<< "$MENU_LINES" + + # Feed the cache + # """""""""""""" + MENU_ARRAY[$MENU_FILE]="$MENU" + COL_ARRAY[$MENU_FILE]=$COL + CENTERING_ARRAY[$MENU_FILE]=$CENTERING + TITLE_ARRAY[$MENU_FILE]=$TITLE + ERASE_ARRAY[$MENU_FILE]=$ERASE + else + # Read from the cache + # """"""""""""""""""" + MENU="${MENU_ARRAY[$MENU_FILE]}" + COL=${COL_ARRAY[$MENU_FILE]} + CENTERING=${CENTERING_ARRAY[$MENU_FILE]} + TITLE=${TITLE_ARRAY[$MENU_FILE]} + ERASE=${ERASE_ARRAY[$MENU_FILE]} + fi + + # Display the menu and get the selection + # """""""""""""""""""""""""""""""""""""" + SEL=$(echo "$MENU" | ../../smenu \ + $CENTERING \ + $ERASE \ + -N \ + -F -D o:10 i:0 n:2 \ + -n \ + -m "$TITLE" \ + -t $COL \ + -S'/@@@/ /' \ + -S'/^[^ ]+ //v' \ + -e @@@) + SEL=${SEL%% *} +} + +# Check for the presence of ../../smenu +# """"""""""""""""""""""""""""""""""""" +[[ -x ../../smenu ]] || error "smenu is not there, please build it." + +# Initialize the menu stack with the argument +# """"""""""""""""""""""""""""""""""""""""""" +MENU_STACK+=(${1%.mnu}.mnu) + +# And process the main menu +# """"""""""""""""""""""""" +process_menu ${1%.mnu}.mnu + +# According to the selection, navigate in the submenus or return +# the tag associated with the selected menu entry. +# """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +while true; do + if [[ $SEL == "<"* ]]; then + # Back to the previous menu + # ''''''''''''''''''''''''' + if (( ${#MENU_STACK[*]} == 1 )); then + process_menu ${MENU_STACK[-1]} + else + # Unstack the newly found submenu + # ''''''''''''''''''''''''''''''' + unset MENU_STACK[-1] + + # And generate the previous menu + # '''''''''''''''''''''''''''''' + process_menu ${MENU_STACK[-1]} + fi + + elif [[ $SEL == ">"* ]]; then + # Enter the selected submenu + # '''''''''''''''''''''''''' + SMENU_FILE=${SEL#>} + SMENU_FILE=${SMENU_FILE%.mnu}.mnu + [[ -f $SMENU_FILE ]] || error "The file $SMENU_FILE was not found/readable." + + # Stack the newly found submenu + # ''''''''''''''''''''''''''''' + MENU_STACK+=($SMENU_FILE) + + # And generate the submenu + # '''''''''''''''''''''''' + process_menu $SMENU_FILE + else + # An empty selection means than q or ^C has been hit + # '''''''''''''''''''''''''''''''''''''''''''''''''' + [[ -z $SEL ]] && exit 0 + + # Output the selected menu tag or exit the menu without outputting + # anything. + # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + [[ $SEL == @EXIT@* ]] && exit 0 + + # Lauch the user action which has the responsibility to act according + # to the tag passed as argument. + # ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + $USER_PROGRAM $SEL + + # And re-generate the current menu + # '''''''''''''''''''''''''''''''' + process_menu ${MENU_STACK[-1]} + fi +done diff --git a/examples/simple_menu/sub1.mnu b/examples/simple_menu/sub1.mnu new file mode 100644 index 0000000..5015142 --- /dev/null +++ b/examples/simple_menu/sub1.mnu @@ -0,0 +1,7 @@ +.columns 1 +.title First submenu +.eraseafter yes +SUB1ITEM1 First item of the first submenu +SUB1ITEM2 Second item of the first submenu +=== +< Previous Menu diff --git a/examples/simple_menu/sub2.mnu b/examples/simple_menu/sub2.mnu new file mode 100644 index 0000000..b34a932 --- /dev/null +++ b/examples/simple_menu/sub2.mnu @@ -0,0 +1,6 @@ +.columns 2 +.title Second submenu +SUB2ITEM1 First item of the second submenu +SUB2ITEM2 Second item of the second submenu +=== +< Previous Menu |