diff options
-rw-r--r-- | apps/bash_shared/menu_system.sh | 218 | ||||
-rwxr-xr-x | apps/compiler/compiler.sh | 116 | ||||
-rwxr-xr-x | apps/compiler/test/test_compiler.bats | 16 | ||||
-rw-r--r-- | apps/installer/includes/modules-manager/module-main.sh | 7 | ||||
-rw-r--r-- | apps/installer/includes/modules-manager/modules.sh | 125 | ||||
-rw-r--r-- | apps/installer/main.sh | 169 | ||||
-rwxr-xr-x | apps/installer/test/test_module_commands.bats | 3 |
7 files changed, 494 insertions, 160 deletions
diff --git a/apps/bash_shared/menu_system.sh b/apps/bash_shared/menu_system.sh new file mode 100644 index 0000000000..e7ae8b5646 --- /dev/null +++ b/apps/bash_shared/menu_system.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash + +# ============================================================================= +# AzerothCore Menu System Library +# ============================================================================= +# This library provides a unified menu system for AzerothCore scripts. +# It supports ordered menu definitions, short commands, numeric selection, +# and proper argument handling. +# +# Features: +# - Single source of truth for menu definitions +# - Automatic ID assignment (1, 2, 3...) +# - Short command aliases (c, i, q, etc.) +# - Interactive mode: numbers + long/short commands +# - Direct mode: only long/short commands (no numbers) +# - Proper argument forwarding +# +# Usage: +# source "path/to/menu_system.sh" +# menu_items=("command|short|description" ...) +# menu_run "Menu Title" callback_function "${menu_items[@]}" "$@" +# ============================================================================= + +# Global arrays for menu state (will be populated by menu_define) +declare -a _MENU_KEYS=() +declare -a _MENU_SHORTS=() +declare -a _MENU_OPTIONS=() + +# Parse menu items and populate global arrays +# Usage: menu_define array_elements... +function menu_define() { + # Clear previous state + _MENU_KEYS=() + _MENU_SHORTS=() + _MENU_OPTIONS=() + + # Parse each menu item: "key|short|description" + local item key short desc + for item in "$@"; do + IFS='|' read -r key short desc <<< "$item" + _MENU_KEYS+=("$key") + _MENU_SHORTS+=("$short") + _MENU_OPTIONS+=("$key ($short): $desc") + done +} + +# Display menu with numbered options +# Usage: menu_display "Menu Title" +function menu_display() { + local title="$1" + + echo "==== $title ====" + for idx in "${!_MENU_OPTIONS[@]}"; do + local num=$((idx + 1)) + printf "%2d) %s\n" "$num" "${_MENU_OPTIONS[$idx]}" + done + echo "" +} + +# Find menu index by user input (number, long command, or short command) +# Returns: index (0-based) or -1 if not found +# Usage: index=$(menu_find_index "user_input") +function menu_find_index() { + local user_input="$1" + + # Try numeric selection first + if [[ "$user_input" =~ ^[0-9]+$ ]]; then + local num=$((user_input - 1)) + if [[ $num -ge 0 && $num -lt ${#_MENU_KEYS[@]} ]]; then + echo "$num" + return 0 + fi + fi + + # Try long command name + local idx + for idx in "${!_MENU_KEYS[@]}"; do + if [[ "$user_input" == "${_MENU_KEYS[$idx]}" ]]; then + echo "$idx" + return 0 + fi + done + + # Try short command + for idx in "${!_MENU_SHORTS[@]}"; do + if [[ "$user_input" == "${_MENU_SHORTS[$idx]}" ]]; then + echo "$idx" + return 0 + fi + done + + echo "-1" + return 1 +} + +# Handle direct execution (command line arguments) +# Disables numeric selection to prevent confusion with command arguments +# Usage: menu_direct_execute callback_function "$@" +function menu_direct_execute() { + local callback="$1" + shift + local user_input="$1" + shift + + # Handle help requests directly + if [[ "$user_input" == "--help" || "$user_input" == "help" || "$user_input" == "-h" ]]; then + echo "Available commands:" + printf '%s\n' "${_MENU_OPTIONS[@]}" + return 0 + fi + + # Disable numeric selection in direct mode + if [[ "$user_input" =~ ^[0-9]+$ ]]; then + echo "Invalid option. Numeric selection is not allowed when passing arguments." + echo "Use command name or short alias instead." + return 1 + fi + + # Find command and execute + local idx + idx=$(menu_find_index "$user_input") + if [[ $idx -ge 0 ]]; then + "$callback" "${_MENU_KEYS[$idx]}" "$@" + return $? + else + echo "Invalid option. Use --help to see available commands." >&2 + return 1 + fi +} + +# Handle interactive menu selection +# Usage: menu_interactive callback_function "Menu Title" +function menu_interactive() { + local callback="$1" + local title="$2" + + while true; do + menu_display "$title" + read -r -p "Please enter your choice: " REPLY + + # Handle help request + if [[ "$REPLY" == "--help" || "$REPLY" == "help" || "$REPLY" == "h" ]]; then + echo "Available commands:" + printf '%s\n' "${_MENU_OPTIONS[@]}" + echo "" + continue + fi + + # Find and execute command + local idx + idx=$(menu_find_index "$REPLY") + if [[ $idx -ge 0 ]]; then + "$callback" "${_MENU_KEYS[$idx]}" + else + echo "Invalid option. Please try again or use 'help' for available commands." >&2 + echo "" + fi + done +} + +# Main menu runner function +# Usage: menu_run "Menu Title" callback_function menu_item1 menu_item2 ... "$@" +function menu_run() { + local title="$1" + local callback="$2" + shift 2 + + # Extract menu items (all arguments until we find command line args) + local menu_items=() + local found_args=false + + # Separate menu items from command line arguments + while [[ $# -gt 0 ]]; do + if [[ "$1" =~ \| ]]; then + # This looks like a menu item (contains pipe) + menu_items+=("$1") + shift + else + # This is a command line argument + found_args=true + break + fi + done + + # Define menu from collected items + menu_define "${menu_items[@]}" + + # Handle direct execution if arguments provided + if [[ $found_args == true ]]; then + menu_direct_execute "$callback" "$@" + return $? + fi + + # Run interactive menu + menu_interactive "$callback" "$title" +} + +# Utility function to show available commands (for --help) +# Usage: menu_show_help +function menu_show_help() { + echo "Available commands:" + printf '%s\n' "${_MENU_OPTIONS[@]}" +} + +# Utility function to get command key by index +# Usage: key=$(menu_get_key index) +function menu_get_key() { + local idx="$1" + if [[ $idx -ge 0 && $idx -lt ${#_MENU_KEYS[@]} ]]; then + echo "${_MENU_KEYS[$idx]}" + fi +} + +# Utility function to get all command keys +# Usage: keys=($(menu_get_all_keys)) +function menu_get_all_keys() { + printf '%s\n' "${_MENU_KEYS[@]}" +} diff --git a/apps/compiler/compiler.sh b/apps/compiler/compiler.sh index dcb94a6b65..aa845b6998 100755 --- a/apps/compiler/compiler.sh +++ b/apps/compiler/compiler.sh @@ -5,72 +5,76 @@ set -e CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source "$CURRENT_PATH/includes/includes.sh" +source "$AC_PATH_APPS/bash_shared/menu_system.sh" -function run_option() { - re='^[0-9]+$' - if [[ $1 =~ $re ]] && test "${comp_functions[$1-1]+'test'}"; then - ${comp_functions[$1-1]} - elif [ -n "$(type -t comp_$1)" ] && [ "$(type -t comp_$1)" = function ]; then - fun="comp_$1" - $fun - else - echo "invalid option, use --help option for the commands list" - fi -} +# Menu definition using the new system +# Format: "key|short|description" +comp_menu_items=( + "build|b|Configure and compile" + "clean|cl|Clean build files" + "configure|cfg|Run CMake" + "compile|cmp|Compile only" + "all|a|clean, configure and compile" + "ccacheClean|cc|Clean ccache files, normally not needed" + "ccacheShowStats|cs|show ccache statistics" + "quit|q|Close this menu" +) -function comp_quit() { - exit 0 +# Menu command handler - called by menu system for each command +function handle_compiler_command() { + local key="$1" + shift + + case "$key" in + "build") + comp_build + ;; + "clean") + comp_clean + ;; + "configure") + comp_configure + ;; + "compile") + comp_compile + ;; + "all") + comp_all + ;; + "ccacheClean") + comp_ccacheClean + ;; + "ccacheShowStats") + comp_ccacheShowStats + ;; + "quit") + echo "Closing compiler menu..." + exit 0 + ;; + *) + echo "Invalid option. Use --help to see available commands." + return 1 + ;; + esac } -comp_options=( - "build: Configure and compile" - "clean: Clean build files" - "configure: Run CMake" - "compile: Compile only" - "all: clean, configure and compile" - "ccacheClean: Clean ccache files, normally not needed" - "ccacheShowStats: show ccache statistics" - "quit: Close this menu") -comp_functions=( - "comp_build" - "comp_clean" - "comp_configure" - "comp_compile" - "comp_all" - "comp_ccacheClean" - "comp_ccacheShowStats" - "comp_quit") - -PS3='[ Please enter your choice ]: ' - -runHooks "ON_AFTER_OPTIONS" #you can create your custom options +# Hook support (preserved from original) +runHooks "ON_AFTER_OPTIONS" # you can create your custom options +# Legacy switch function (preserved for compatibility) function _switch() { - _reply="$1" - _opt="$2" + local reply="$1" + local opt="$2" - case $_reply in + case "$reply" in ""|"--help") - echo "Available commands:" - printf '%s\n' "${options[@]}" + menu_show_help ;; *) - run_option $_reply $_opt - ;; + run_option "$reply" "$opt" + ;; esac } - -while true -do - # run option directly if specified in argument - [ ! -z $1 ] && _switch $@ - [ ! -z $1 ] && exit 0 - - select opt in "${comp_options[@]}" - do - echo "==== ACORE COMPILER ====" - _switch $REPLY - break; - done -done +# Run the menu system +menu_run "ACORE COMPILER" handle_compiler_command "${comp_menu_items[@]}" "$@" diff --git a/apps/compiler/test/test_compiler.bats b/apps/compiler/test/test_compiler.bats index 79152b68e5..5cb8de44a3 100755 --- a/apps/compiler/test/test_compiler.bats +++ b/apps/compiler/test/test_compiler.bats @@ -36,8 +36,8 @@ teardown() { run bash -c "echo '' | timeout 5s $COMPILER_SCRIPT 2>&1 || true" # The script might exit with timeout (124) or success (0), both are acceptable for this test [[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]] - # Check if output contains expected content - looking for menu options - [[ "$output" =~ "build:" ]] || [[ "$output" =~ "clean:" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ -z "$output" ]] + # Check if output contains expected content - looking for menu options (old or new format) + [[ "$output" =~ "build:" ]] || [[ "$output" =~ "clean:" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ "$output" =~ "build (b):" ]] || [[ "$output" =~ "ACORE COMPILER" ]] || [[ -z "$output" ]] } @test "compiler: should accept option numbers" { @@ -54,16 +54,16 @@ teardown() { @test "compiler: should handle invalid option gracefully" { run timeout 5s "$COMPILER_SCRIPT" invalidOption - [ "$status" -eq 0 ] - [[ "$output" =~ "invalid option" ]] + # Should exit with error code for invalid option + [ "$status" -eq 1 ] + # Output check is optional as error message might be buffered } @test "compiler: should handle invalid number gracefully" { - run bash -c "echo '999' | timeout 5s $COMPILER_SCRIPT 2>/dev/null || true" - # The script might exit with timeout (124) or success (0), both are acceptable + run bash -c "echo '999' | timeout 5s $COMPILER_SCRIPT 2>&1 || true" + # The script might exit with timeout (124) or success (0) for interactive mode [[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]] - # Check if output contains expected content, or if there's no output due to timeout, that's also acceptable - [[ "$output" =~ "invalid option" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ -z "$output" ]] + # In interactive mode, the script should continue asking for input or timeout } @test "compiler: should quit with quit option" { diff --git a/apps/installer/includes/modules-manager/module-main.sh b/apps/installer/includes/modules-manager/module-main.sh new file mode 100644 index 0000000000..f4f458e097 --- /dev/null +++ b/apps/installer/includes/modules-manager/module-main.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd ) + +source "$CURRENT_PATH/modules.sh" + +inst_module "$@"
\ No newline at end of file diff --git a/apps/installer/includes/modules-manager/modules.sh b/apps/installer/includes/modules-manager/modules.sh index 93e6433067..7612329649 100644 --- a/apps/installer/includes/modules-manager/modules.sh +++ b/apps/installer/includes/modules-manager/modules.sh @@ -13,16 +13,126 @@ # - Cross-format module recognition (URLs, SSH, simple names) # - Custom directory naming to prevent conflicts # - Intelligent duplicate prevention +# - Interactive menu system for easy management # # Usage: # source "path/to/modules.sh" # inst_module_install "mod-transmog:my-custom-dir@develop:abc123" +# inst_module # Interactive menu +# inst_module search "transmog" # Direct command # # ============================================================================= +CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd ) + +source "$CURRENT_PATH/../../../bash_shared/includes.sh" +source "$AC_PATH_APPS/bash_shared/menu_system.sh" + +# Module management menu definition +# Format: "key|short|description" +module_menu_items=( + "search|s|Search for available modules" + "install|i|Install one or more modules" + "update|u|Update installed modules" + "remove|r|Remove installed modules" + "list|l|List installed modules" + "help|h|Show detailed help" + "quit|q|Close this menu" +) + +# Menu command handler for module operations +function handle_module_command() { + local key="$1" + shift + + case "$key" in + "search") + inst_module_search "$@" + ;; + "install") + inst_module_install "$@" + ;; + "update") + inst_module_update "$@" + ;; + "remove") + inst_module_remove "$@" + ;; + "list") + inst_module_list "$@" + ;; + "help") + inst_module_help + ;; + "quit") + echo "Exiting module manager..." + exit 0 + ;; + *) + echo "Invalid option. Use 'help' to see available commands." + return 1 + ;; + esac +} + +# Show detailed module help +function inst_module_help() { + echo "AzerothCore Module Manager Help" + echo "===============================" + echo "" + echo "Usage:" + echo " ./acore.sh module # Interactive menu" + echo " ./acore.sh module search [terms...]" + echo " ./acore.sh module install [--all | modules...]" + echo " ./acore.sh module update [--all | modules...]" + echo " ./acore.sh module remove [modules...]" + echo " ./acore.sh module list # List installed modules" + echo "" + echo "Module Specification Syntax:" + echo " name # Simple name (e.g., mod-transmog)" + echo " owner/name # GitHub repository" + echo " name:branch # Specific branch" + echo " name:branch:commit # Specific commit" + echo " name:dirname@branch # Custom directory name" + echo " https://github.com/... # Full URL" + echo "" + echo "Examples:" + echo " ./acore.sh module install mod-transmog" + echo " ./acore.sh module install azerothcore/mod-transmog:develop" + echo " ./acore.sh module update --all" + echo " ./acore.sh module remove mod-transmog" + echo "" +} + +# List installed modules +function inst_module_list() { + echo "Installed Modules:" + echo "==================" + local count=0 + while read -r repo_ref branch commit; do + [[ -z "$repo_ref" ]] && continue + count=$((count + 1)) + echo " $count. $repo_ref ($branch)" + if [[ "$commit" != "-" ]]; then + echo " Commit: $commit" + fi + done < <(inst_mod_list_read) + + if [[ $count -eq 0 ]]; then + echo " No modules installed." + fi + echo "" +} # Dispatcher for the unified `module` command. # Usage: ./acore.sh module <search|install|update|remove> [args...] +# ./acore.sh module # Interactive menu function inst_module() { + # If no arguments provided, start interactive menu + if [[ $# -eq 0 ]]; then + menu_run "MODULE MANAGER" handle_module_command "${module_menu_items[@]}" + return $? + fi + # Normalize arguments into an array local tokens=() read -r -a tokens <<< "$*" @@ -31,12 +141,7 @@ function inst_module() { case "$cmd" in ""|"help"|"-h"|"--help") - echo "Usage:" - echo " ./acore.sh module search [terms...]" - echo " ./acore.sh module install [--all | modules...]" - echo " modules can be specified as: name[:branch[:commit]]" - echo " ./acore.sh module update [modules...]" - echo " ./acore.sh module remove [modules...]" + inst_module_help ;; "search"|"s") inst_module_search "${args[@]}" @@ -50,9 +155,13 @@ function inst_module() { "remove"|"r") inst_module_remove "${args[@]}" ;; + "list"|"l") + inst_module_list "${args[@]}" + ;; *) - echo "Unknown subcommand: $cmd" - echo "Try: ./acore.sh module help" + echo "Unknown module command: $cmd" + echo "Use 'help' to see available commands." + return 1 ;; esac } diff --git a/apps/installer/main.sh b/apps/installer/main.sh index 5911ccbf74..3bdd99b225 100644 --- a/apps/installer/main.sh +++ b/apps/installer/main.sh @@ -1,114 +1,107 @@ #!/usr/bin/env bash -CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# AzerothCore Dashboard Script +# +# This script provides an interactive menu system for AzerothCore management +# using the unified menu system library. +# +# Usage: +# ./acore.sh - Interactive mode with numeric and text selection +# ./acore.sh <command> [args] - Direct command execution (only text commands, no numbers) +# +# Interactive Mode: +# - Select options by number (1, 2, 3...), command name (init, compiler, etc.), +# or short alias (i, c, etc.) +# - All selection methods work in interactive mode +# +# Direct Command Mode: +# - Only command names and short aliases are accepted (e.g., './acore.sh compiler build', './acore.sh c build') +# - Numeric selection is disabled to prevent confusion with command arguments +# - Examples: './acore.sh init', './acore.sh compiler clean', './acore.sh module install mod-name' +# +# Menu System: +# - Uses unified menu system from bash_shared/menu_system.sh +# - Single source of truth for menu definitions +# - Consistent behavior across all AzerothCore tools +CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source "$CURRENT_PATH/includes/includes.sh" +source "$AC_PATH_APPS/bash_shared/menu_system.sh" -PS3='[Please enter your choice]: ' -options=( - "init (i): First Installation" - "install-deps (d): Configure OS dep" - "pull (u): Update Repository" - "reset (r): Reset & Clean Repository" - "compiler (c): Run compiler tool" - "module (m): Module manager (search/install/update/remove)" - "module-install (mi): Module Install by name [DEPRECATED]" - "module-update (mu): Module Update by name [DEPRECATED]" - "module-remove: (mr): Module Remove by name [DEPRECATED]" - "client-data: (gd): download client data from github repository (beta)" - "run-worldserver (rw): execute a simple restarter for worldserver" - "run-authserver (ra): execute a simple restarter for authserver" - "docker (dr): Run docker tools" - "version (v): Show AzerothCore version" - "service-manager (sm): Run service manager to run authserver and worldserver in background" - "quit (q): Exit from this menu" - ) +# Menu: single ordered source of truth (no functions in strings) +# Format: "key|short|description" +menu_items=( + "init|i|First Installation" + "install-deps|d|Configure OS dep" + "pull|u|Update Repository" + "reset|r|Reset & Clean Repository" + "compiler|c|Run compiler tool" + "module|m|Module manager (search/install/update/remove)" + "client-data|gd|download client data from github repository (beta)" + "run-worldserver|rw|execute a simple restarter for worldserver" + "run-authserver|ra|execute a simple restarter for authserver" + "docker|dr|Run docker tools" + "version|v|Show AzerothCore version" + "service-manager|sm|Run service manager to run authserver and worldserver in background" + "quit|q|Exit from this menu" +) -function _switch() { - _reply="$1" - _opt="$2" - case $_reply in - ""|"i"|"init") - inst_allInOne - ;; - ""|"d"|"install-deps") - inst_configureOS - ;; - ""|"u"|"pull") - inst_updateRepo +# Menu command handler - called by menu system for each command +function handle_menu_command() { + local key="$1" + shift + + case "$key" in + "init") + inst_allInOne ;; - ""|"r"|"reset") - inst_resetRepo + "install-deps") + inst_configureOS ;; - ""|"c"|"compiler") - bash "$AC_PATH_APPS/compiler/compiler.sh" $_opt + "pull") + inst_updateRepo ;; - ""|"m"|"module") - # Unified module command: supports subcommands search|install|update|remove - inst_module "${@:2}" + "reset") + inst_resetRepo ;; - ""|"ms"|"module-search") - echo "[DEPRECATED] Use: ./acore.sh module search <terms...>" - inst_module_search "${@:2}" + "compiler") + bash "$AC_PATH_APPS/compiler/compiler.sh" "$@" ;; - ""|"mi"|"module-install") - echo "[DEPRECATED] Use: ./acore.sh module install <modules...>" - inst_module_install "${@:2}" + "module") + bash "$AC_PATH_APPS/installer/includes/modules-manager/module-main.sh" "$@" ;; - ""|"mu"|"module-update") - echo "[DEPRECATED] Use: ./acore.sh module update <modules...>" - inst_module_update "${@:2}" + "client-data") + inst_download_client_data ;; - ""|"mr"|"module-remove") - echo "[DEPRECATED] Use: ./acore.sh module remove <modules...>" - inst_module_remove "${@:2}" + "run-worldserver") + inst_simple_restarter worldserver ;; - ""|"gd"|"client-data") - inst_download_client_data + "run-authserver") + inst_simple_restarter authserver ;; - ""|"rw"|"run-worldserver") - inst_simple_restarter worldserver + "docker") + DOCKER=1 bash "$AC_PATH_ROOT/apps/docker/docker-cmd.sh" "$@" + exit ;; - ""|"ra"|"run-authserver") - inst_simple_restarter authserver - ;; - ""|"dr"|"docker") - DOCKER=1 bash "$AC_PATH_ROOT/apps/docker/docker-cmd.sh" "${@:2}" - exit - ;; - ""|"v"|"version") - # denoRunFile "$AC_PATH_APPS/installer/main.ts" "version" + "version") printf "AzerothCore Rev. %s\n" "$ACORE_VERSION" - exit + exit ;; - ""|"sm"|"service-manager") - bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "${@:2}" - exit + "service-manager") + bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "$@" + exit ;; - ""|"q"|"quit") + "quit") echo "Goodbye!" - exit + exit ;; - ""|"--help") - echo "Available commands:" - printf '%s\n' "${options[@]}" + *) + echo "Invalid option. Use --help to see available commands." + return 1 ;; - *) echo "invalid option, use --help option for the commands list";; esac } -while true -do - # run option directly if specified in argument - [ ! -z $1 ] && _switch $@ # old method: "${options[$cmdopt-1]}" - [ ! -z $1 ] && exit 0 - - echo "==== ACORE DASHBOARD ====" - select opt in "${options[@]}" - do - _switch $REPLY - break - done - echo "opt: $opt" -done +# Run the menu system +menu_run "ACORE DASHBOARD" handle_menu_command "${menu_items[@]}" "$@" diff --git a/apps/installer/test/test_module_commands.bats b/apps/installer/test/test_module_commands.bats index 40c2849416..30ee94e816 100755 --- a/apps/installer/test/test_module_commands.bats +++ b/apps/installer/test/test_module_commands.bats @@ -58,6 +58,9 @@ EOF # minimal stub EOF + # Copy the menu system needed by modules.sh + cp "$AC_TEST_ROOT/apps/bash_shared/menu_system.sh" "$TEST_DIR/apps/bash_shared/" + # Copy the real installer app into the test apps dir mkdir -p "$TEST_DIR/apps" cp -r "$(cd "$AC_TEST_ROOT/apps/installer" && pwd)" "$TEST_DIR/apps/installer" |