summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpgen <p.gen.progs@gmail.com>2019-03-11 00:33:05 +0100
committerpgen <p.gen.progs@gmail.com>2019-03-13 19:59:54 +0100
commitb30b7afbbb527392a1f1654fa9e0d52f3757392a (patch)
treed3b933086023068e364a322f3741aa2156fc93dd
parent2df7ebbd411946da92f8f4d1a7065bb8b318aec4 (diff)
Add an example of a hierarchical menu interpreter
-rw-r--r--examples/simple_menu/README39
-rwxr-xr-xexamples/simple_menu/actions.sh13
-rw-r--r--examples/simple_menu/main.mnu24
-rwxr-xr-xexamples/simple_menu/simple_menu.sh220
-rw-r--r--examples/simple_menu/sub1.mnu7
-rw-r--r--examples/simple_menu/sub2.mnu6
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