ao-mud is a spellbook of well-commented atomic bash scripts that each do one thing. we are building semantic building blocks for an autonomously-evolving digital spellcasting language.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
4.0 KiB

#!/usr/bin/env sh
# This script generates a menu from the given list of items. Choosing an item runs the command for that item.
# Usage:
# change_my_password="Change my password%passwd"
# list_users="List all users%cut -d: -f1 /etc/passwd"
# exit="kill -2 $$"
# menu "User management options:" "$change_my_password" "$list_users" "$exit"
# Use up/down arrow keys to select a menu item, then press Enter. Or cancel with ESC.
. colors
if [ ${#} -lt 2 ]; then
mud-menu
exit 0
fi
description=$1
shift
# Parse args. Each arg is "name%command"
items=""
commands=""
while [ ${#} -gt 0 ]; do
IFS='%' read -r item command <<EOF
$1
EOF
items="${items}:${item}"
commands="${commands}:${command}"
shift
done
selected=0
displayed=0
# Calculate the position of the menu
menu_x=$((0))
menu_y=$(fathom-cursor -y)
# Calculate the number of rows that the menu will occupy
num_rows=$(echo "$items" | tr -cd ':' | wc -c)
# Calculate the maximum length of the item names
# Todo: abstract this out into a max-length function in another script. I gave up on trying to find an elegant way to pass a list of strings with spaces. Even separating them with newlines didn't work.
max_name_length=0
# Find the longest line
set -f IFS=':'; set -- $items; set +f
for line; do
line_length=${#line}
if [ $line_length -gt $max_name_length ]; then
max_name_length=$line_length
fi
done
# Make sure the menu items don't wrap, multiline menu items are not supported
window_width=$(fathom-terminal -w)
if [ "${max_name_length:-0}" -gt "${window_width:-80}" ]; then
max_name_length=$window_width
fi
# Define the function to display the menu
display_menu() {
# Get the dimensions of the terminal window (again each time in case window is resized)
window_width=$(fathom-terminal -w)
window_height=$(fathom-terminal -h)
# Recalculate the position of the menu
menu_y=$(fathom-cursor -y)
# Move the cursor to the correct position
if [ $displayed -eq 0 ]; then
# Don't move the cursor or clear the screen the first time
displayed=1
else
if [ $((menu_y + num_rows)) -ge $window_height ]; then
menu_y=$((window_height))
fi
move-cursor $menu_x $((menu_y - num_rows))
fi
# Print the items and commands
i=0
set -f IFS=':'; set -- $items; set +f
for item; do
set -f IFS=':'; set -- $commands; set +f
for j in $(seq 1 $selected); do shift; done
command=$1
max_command_length=$(( $window_width - $max_name_length - 3 )) # -3 is for the > and the space between item and command
truncated_command=$(echo "${command}" | cut -c -$max_command_length)
if [ $i -eq $selected ]; then
printf "${CYAN}> %-${max_name_length}s${RESET} ${GREY}%s${RESET}\n" "${item}" "$truncated_command"
else
printf " %-${max_name_length}s %*s\n" "${item}" "${#truncated_command}"
fi
i=$((i + 1))
done
}
# Define the function to handle key presses
handle_key() {
key=$(await-keypress)
case "$key" in
up)
selected=$((selected - 1))
if [ $selected -lt 0 ]; then
selected=$((num_rows - 1))
fi
;;
down)
selected=$((selected + 1))
if [ $selected -ge $num_rows ]; then
selected=0
fi
;;
enter)
cursor-blink on
command="${commands[$selected]}"
printf "\n"
eval $command
exit 0
;;
escape)
cursor-blink on
echo ESC
exit
;;
esac
}
# Catch Ctrl-C and restore the cursor blink, so the user doesn't get stuck with an invisible cursor
handle_ctrl_c() {
trap 'cursor-blink on; exit' INT
}
handle_ctrl_c
test_menu() {
assert_equal "$(./menu "Please choose" "item1 item2 item3" "ls cat command3" -v)" "Please choose\n1) item1\n2) item2\n3) item3\n" "menu -v output differs"
assert_equal "$(./menu "Please choose" "item1 item2 item3" "ls cat command3")" "1) item1\n2) item2\n3) item3\n" "menu output differs"
}
cursor-blink off
# Display the menu's description
echo $description
# Menu loop
while true; do
display_menu
handle_key $key
done