From 796a2dfe9bb2796457f9a49b0b954835538669d7 Mon Sep 17 00:00:00 2001 From: deicidus Date: Fri, 13 Jan 2023 08:53:29 -0800 Subject: [PATCH] Refactored menu spell works! --- README.md | 18 +- spells/bind-summary | 19 ++ spells/bind-tome | 44 ++++ spells/{ => cantrips}/ask | 0 spells/cantrips/ask_Yn | 30 +++ spells/cantrips/assertions | 49 +++++ .../await-keypress} | 6 +- spells/cantrips/bg_log | 41 ++++ spells/cantrips/check-wizard | 15 ++ spells/cantrips/clear-area | 52 +++++ spells/cantrips/clear-input-lines | 45 ++++ spells/{ => cantrips}/colors | 24 ++- spells/cantrips/current-input-lines | 51 +++++ spells/{ => cantrips}/cursor | 2 + spells/{menu => cantrips}/cursor-blink | 0 spells/cantrips/fathom-cursor | 54 +++++ spells/cantrips/fathom-terminal | 70 +++++++ spells/cantrips/max-length | 42 ++++ spells/cantrips/menu | 97 +++++++++ spells/cantrips/move-cursor | 29 +++ spells/{ => cantrips}/say | 8 +- spells/{wizard_eyes => cantrips/wizard-eyes} | 15 +- spells/detect-distro | 4 +- spells/{rehashchant => hashchant} | 0 spells/hourly-bell | 57 +++++ spells/menu/fathom-cursor | 18 -- spells/menu/fathom-terminal | 22 -- spells/menu/max-length | 27 --- spells/rosetta-stone | 196 ++++++++++++++++++ 29 files changed, 946 insertions(+), 89 deletions(-) create mode 100755 spells/bind-summary create mode 100755 spells/bind-tome rename spells/{ => cantrips}/ask (100%) create mode 100755 spells/cantrips/ask_Yn create mode 100644 spells/cantrips/assertions rename spells/{menu/await_keypress => cantrips/await-keypress} (94%) create mode 100755 spells/cantrips/bg_log create mode 100755 spells/cantrips/check-wizard create mode 100755 spells/cantrips/clear-area create mode 100755 spells/cantrips/clear-input-lines rename spells/{ => cantrips}/colors (61%) create mode 100755 spells/cantrips/current-input-lines rename spells/{ => cantrips}/cursor (89%) rename spells/{menu => cantrips}/cursor-blink (100%) create mode 100755 spells/cantrips/fathom-cursor create mode 100755 spells/cantrips/fathom-terminal create mode 100755 spells/cantrips/max-length create mode 100755 spells/cantrips/menu create mode 100755 spells/cantrips/move-cursor rename spells/{ => cantrips}/say (65%) rename spells/{wizard_eyes => cantrips/wizard-eyes} (52%) mode change 100644 => 100755 rename spells/{rehashchant => hashchant} (100%) create mode 100755 spells/hourly-bell delete mode 100755 spells/menu/fathom-cursor delete mode 100755 spells/menu/fathom-terminal delete mode 100755 spells/menu/max-length create mode 100644 spells/rosetta-stone diff --git a/README.md b/README.md index 40890e1..615921c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,22 @@ On your AO adventure, there are three different tutorials which gradually unlock ao-mud contains unlockable menus and terminal shortcuts that make using the terminal easier and more fun. To skip ahead and turn some of these features on without unlocking them in tutorial mode, you can use a Save Code that you received from an earlier playthrough. To use a Save Code, open the AO main menu, select "Load Saved Game", enter your Save Code, and press Enter. +# Folder Structure + +The structure of this project right now is simple. There is a `spells` folder containing spells you can cast. This folder has a subfolder called `cantrips`, which contains tiny spells that are used to compose larger spells that aren't meant to be used on their own. + +# Conventions + +1. As a naming convention, spells that use hypens (-) in their name instead of spaces are set up to be run as scripts in the terminal (even if they are cantrips that will merely inform you not to do so). Spells that include a function that is not accessible by executing the script in the terminal are named with underscores (_) in their names. Open these (or any) spells in a text editor to see what they contain. + +2. Terminal commands that simply output a single value should output the bare value by default. A --verbose or -v flag should be included on all such functions to include a description field such as "Cursor X:". + +3. Cantrips should be so atomic so as to include a single value, if possible. This reduces parsing that must be done by those using the cantrip. + +# How to Cast a Spell + +To cast a spell, `cd` into the spells directory. Then, the first time you cast the spell, `chmod +x spellname`. Then `./spellname` to cast. + # How to Begin -To start your AO adventure, type ./ao in this directory. +A tutorial is coming soon. For now, explore the `./spells` directory. Try reading or running the `rosetta-stone` script or the `jump` script. \ No newline at end of file diff --git a/spells/bind-summary b/spells/bind-summary new file mode 100755 index 0000000..4fe7556 --- /dev/null +++ b/spells/bind-summary @@ -0,0 +1,19 @@ +#!/bin/bash + +# Check that a folder path was provided as an argument +if [ "$#" -ne 1 ]; then + echo "Error: Please provide a folder path as an argument." + exit 1 +fi + +# Create a text file with the same name as the folder +folder_name=$(basename "$1") +text_file="$folder_name.txt" + +# Read all the filenames in the folder and put them in the text file +# Todo: Remove the folder/ name from before the filename +for filename in "$1"/*; do + echo "$filename" >> "$text_file" +done + +echo "Text file created: $text_file" \ No newline at end of file diff --git a/spells/bind-tome b/spells/bind-tome new file mode 100755 index 0000000..6890985 --- /dev/null +++ b/spells/bind-tome @@ -0,0 +1,44 @@ +#!/bin/bash + +# The Tome Fusion Spell +# +# This powerful spell allows you to magically bind together the scattered pages of a tome, creating a single +# cohesive text file. Simply provide the path to the folder containing the sliced pages as an argument, and the +# spell will do the rest. If you want to destroy the original pages after fusing them together, use the -d flag. +# +# Usage: ./fuse_text_file_lines.sh path/to/folder [-d] +# todo: see if we can shorten or replace this script with `cat folder/* > file.txt` which simply cats a folder of scripts together (no separators tho) + +# Check that a folder path was provided as an argument +if [ "$#" -ne 1 ]; then + echo "Error: Please provide a folder path as an argument." + exit 1 +fi + +# Create a new text file with the same name as the folder +folder_name=$(basename "$1") +text_file="$folder_name.txt" +touch "$text_file" + +# Read all the files in the folder and append their content to the text file, with a special line marking the beginning and end of each file +for file in "$1"/*; do + filename=$(basename "$file") + echo "----- $filename -----" >> "$text_file" + cat "$file" >> "$text_file" + echo "----- End of $filename -----" >> "$text_file" +done + +echo "Text file created: $text_file" + +# Check if the delete flag (-d) was provided as an argument +while getopts "d" opt; do + case $opt in + d) + # If the delete flag was provided, delete all the original files + for file in "$1"/*; do + rm "$file" + done + echo "Original files deleted." + ;; + esac +done diff --git a/spells/ask b/spells/cantrips/ask similarity index 100% rename from spells/ask rename to spells/cantrips/ask diff --git a/spells/cantrips/ask_Yn b/spells/cantrips/ask_Yn new file mode 100755 index 0000000..2dd1d1b --- /dev/null +++ b/spells/cantrips/ask_Yn @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +# Define ask_Yn, which asks a yes or no question and saves the result in the given variable name (defaults to yes) +# Takes one arg, which is the variable name where the result will be saved +# No is 0 and Yes is 1 + +source say + +ask_Yn() { + if [ -n "$2" ]; then + say_inline "$2 (Y/n): " + fi + STTY_CONFIG=$(stty -g) # Save current stty config + stty raw -echo # Disable echo + ANSWER=$(head -c 1) # Scrape terminal input + stty $STTY_CONFIG # Restore stty config + if [ "$ANSWER" != "${ANSWER#[Nn]}" ]; then + say "N" + export "${1}=0" + else + say "Y" + export "${1}=1" + fi +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function + ask_Yn "$@" +fi diff --git a/spells/cantrips/assertions b/spells/cantrips/assertions new file mode 100644 index 0000000..3b5c2be --- /dev/null +++ b/spells/cantrips/assertions @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Functions unit tests, to test assertions. The point of an assertion is to crash if it isn't true. +# Usage: +# assert_equal $value1 $value2 +# assert_output "./scriptname" "expected output" +# assert_success $(command) +# assert_failure "./scriptname" + +# Helper function to compare two values +assert_equal() { + if [ "$1" != "$2" ]; then + echo "Assertion failed: $1 != $2" + exit 1 + fi +} + +# Helper function to compare the output of a given command to an expected value +assert_output() { + local command=$1 + local expected_output=$2 + echo com $command + echo exp $expected_output + echo out $($command) + if [ "$($command)" != "$expected_output" ]; then + echo "Test failed: $command" + exit 1 + fi +} + +# Helper function to run a command and check for a non-zero exit status +assert_success() { + local command=$1 + + if ! $command; then + echo "Test failed: $command" + exit 1 + fi +} + +# Helper function to run a command and check for a zero exit status +assert_failure() { + local command=$1 + + if $command; then + echo "Test failed: $command" + exit 1 + fi +} diff --git a/spells/menu/await_keypress b/spells/cantrips/await-keypress similarity index 94% rename from spells/menu/await_keypress rename to spells/cantrips/await-keypress index 7869ee1..0188735 100755 --- a/spells/menu/await_keypress +++ b/spells/cantrips/await-keypress @@ -5,12 +5,10 @@ # todo: Tab key not distinguished from Enter (\t case does not work) # Define the function to handle key presses -handle_key() { +await_keypress() { # Read a single character from the terminal read -s -n1 key - #echo $key > ./invisible_character # debug - # Check the value of the key case "$key" in # Enter key @@ -70,5 +68,5 @@ handle_key() { # Check if the script is being executed or sourced, if executed then call the function if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - handle_key + await_keypress fi diff --git a/spells/cantrips/bg_log b/spells/cantrips/bg_log new file mode 100755 index 0000000..0604287 --- /dev/null +++ b/spells/cantrips/bg_log @@ -0,0 +1,41 @@ +#!/bin/sh + +# bg_log: a script to output a message from a background process, while taking +# into account the user's current input and preserving it. +# +# Cast this spell to send a message from a background process to the terminal, +# without interrupting or overwriting the user's input. + +# todo: refactor this to use the cursor file + +bg_log() { + # Save the current cursor position + printf '\033[s' + + # Calculate the number of lines used by the user's input + lines=$(current_input_lines) + + # Move the cursor up the number of lines used by the user's input + printf '\033[%dA' "$lines" + + # Clear the current line + printf '\033[2K' + + # Output the message + printf '%s\n' "$@" + + # Clear the current line + printf '\033[2K' + + # Move the cursor down the number of lines used by the user's input + printf '\033[%dB' "$lines" + + # Restore the cursor position + printf '\033[u' +} + +# Check if the script is being executed or sourced +if [ "$0" = "$BASH_SOURCE" ]; then + # Script is being executed, call the bg_log function to output a message + bg_log $1 +fi diff --git a/spells/cantrips/check-wizard b/spells/cantrips/check-wizard new file mode 100755 index 0000000..d55c6d0 --- /dev/null +++ b/spells/cantrips/check-wizard @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# Asks the user if they are a wizard and exports an environment variable with the result + +# Todo next: Add remember/recall functions so that wizard status is remembered after first ask +check_wizard() { + ask_Yn WIZARD "First, let me ask, are you a wizard?" + export "WIZARD=$WIZARD" +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function + check_wizard "$@" +fi diff --git a/spells/cantrips/clear-area b/spells/cantrips/clear-area new file mode 100755 index 0000000..d219e77 --- /dev/null +++ b/spells/cantrips/clear-area @@ -0,0 +1,52 @@ +#!/usr/bin/env sh + +# todo: this script doesn't work at alll for some reason. clear_area is being called but the cursor isn't moving around correctly + +# This magical spell clears a specific area of the terminal window, leaving behind a clean and clear space. +# Usage: cast_clear_area x y width height +# The area to be cleared is specified by its top left corner (x, y) and its width and height. + +# Define the function to clear the specified area +clear_area() { + x=$1 + y=$2 + width=$3 + height=$4 + + # Move the cursor to the top left corner of the area to be cleared + move-cursor $x $y + #echo -en "\033[$y;$xH" + + # Clear the area by outputting a space and then moving the cursor back + i=0 + while [ $i -lt $height ]; do + j=0 + while [ $j -lt $width ]; do + echo -n " " + j=$((j + 1)) + done + # Move the cursor back to the start of the line + #echo -en "\033[$((y + i));$xH" + move-cursor $x $((y + i)) + i=$((i + 1)) + done +} + +# Unit test--this doesn't work yet, but I tseted clear_area manually and it works +test_clear_area() { + . assertions + initial_x=$(fathom-cursor -x) + initial_y=$(fathom-cursor -y) + move_cursor 5 5 + clear-area 3 3 + move_cursor 5 5 + assert_equal "$(tput cup 5 5; tput sc; tput cup $initial_y $initial_x)" "" + move_cursor $initial_x $initial_y + echo "All tests passed" +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function and pass it the command line arguments + clear_area "$@" +fi \ No newline at end of file diff --git a/spells/cantrips/clear-input-lines b/spells/cantrips/clear-input-lines new file mode 100755 index 0000000..f725bd1 --- /dev/null +++ b/spells/cantrips/clear-input-lines @@ -0,0 +1,45 @@ +#!/bin/sh + +# A cantrip to clear the user's current input lines in the terminal, including +# the terminal prompt. +# +# Cast this spell to erase the user's current input lines, leaving the terminal +# ready for a new command. + +# todo: modify this to use our cursor file + +clear_input_lines() { + # Save the current cursor position + printf '\033[s' + + # Move the cursor to the beginning of the line + printf '\033[1G' + + # Clear the current line + printf '\033[2K' + + # Calculate the number of lines used by the user's input + lines="$(current_input_lines)" + + # Clear the remaining lines + i=1 + while [ "$i" -lt "$lines" ]; do + # Move the cursor down one line + printf '\033[1B' + + # Clear the current line + printf '\033[2K' + + # Increment the counter + i=$(( i + 1 )) + done + + # Restore the cursor position + printf '\033[u' +} + +# Check if the script is being executed or sourced +if [ "$0" = "$BASH_SOURCE" ]; then + # Script is being executed, call the clear_input_lines function + clear_input_lines +fi diff --git a/spells/colors b/spells/cantrips/colors similarity index 61% rename from spells/colors rename to spells/cantrips/colors index cda3652..377e686 100644 --- a/spells/colors +++ b/spells/cantrips/colors @@ -2,7 +2,10 @@ # This magical knowledge serves as a colorful palette of hues and shades, # granting control over the appearance of the terminal. -# Use these variables to add a splash of color to your scripts. +# Use these variables to add color to your writing. +# Example: echo -en "${RED}Error:${RESET} An error message." +# This script contains all color codes in the POSIX standard. +# You must source this script with 'source ./cursor' or '. ./cursor' to use it. # Reset all attributes to their default values RESET="\033[0m" @@ -13,9 +16,18 @@ RED="\033[31m" GREEN="\033[32m" YELLOW="\033[33m" BLUE="\033[34m" -MAGENTA="\033[35m" +PURPLE="\033[35m" CYAN="\033[36m" -WHITE="\033[37m" +WHITE="\033[37m" # Or is this default/white? +GREY="\033[2m" + +# Formatting +BOLD="\033[1m" +ITALICS="\033[3m" #or 3? +UNDERLINED="\033[4m" +BLINK="\033[5m" +INVERT="\033[7m" +STRIKE="\033[9m" # Strikethrough # Bright colors BRIGHT_BLACK="\033[30;1m" @@ -23,7 +35,7 @@ BRIGHT_RED="\033[31;1m" BRIGHT_GREEN="\033[32;1m" BRIGHT_YELLOW="\033[33;1m" BRIGHT_BLUE="\033[34;1m" -BRIGHT_MAGENTA="\033[35;1m" +BRIGHT_PURPLE="\033[35;1m" BRIGHT_CYAN="\033[36;1m" BRIGHT_WHITE="\033[37;1m" @@ -33,7 +45,7 @@ BG_RED="\033[41m" BG_GREEN="\033[42m" BG_YELLOW="\033[43m" BG_BLUE="\033[44m" -BG_MAGENTA="\033[45m" +BG_PURPLE="\033[45m" BG_CYAN="\033[46m" BG_WHITE="\033[47m" @@ -43,6 +55,6 @@ BG_BRIGHT_RED="\033[41;1m" BG_BRIGHT_GREEN="\033[42;1m" BG_BRIGHT_YELLOW="\033[103m" BG_BRIGHT_BLUE="\033[104m" -BG_BRIGHT_MAGENTA="\033[105m" +BG_BRIGHT_PURPLE="\033[105m" BG_BRIGHT_CYAN="\033[106m" BG_BRIGHT_WHITE="\033[107m" diff --git a/spells/cantrips/current-input-lines b/spells/cantrips/current-input-lines new file mode 100755 index 0000000..b436f69 --- /dev/null +++ b/spells/cantrips/current-input-lines @@ -0,0 +1,51 @@ +#!/bin/sh + +# current_input_lines: a cantrip to count the number of lines used by the user's +# current input in the terminal, taking into account wrapped lines and the length +# of the terminal prompt. +# +# Cast this spell to measure the number of lines taken up vertically by the user's +# current input in the terminal, even if the input is wrapped to multiple lines or +# the terminal prompt is longer than usual. +# +# Usage: +# current_input_lines +# +# Returns: +# The number of lines used by the user's current input. + +#!/bin/sh + +current_input_lines() { + # Save the current cursor position + printf '\033[s' + + # Measure the width of the terminal + terminal_width=$(fathom-terminal -w) + + # Measure the length of the terminal prompt + prompt_length=$(echo -n "$PS1" | wc -c) + + # Calculate the number of columns available for the user's input + available_columns=$(( terminal_width - prompt_length )) + + # Calculate the number of lines used by the user's input + lines=$(( terminal_width / available_columns )) + + # If the input does not fit evenly into the available columns, add an extra line + if [ "$(( terminal_width % available_columns ))" -ne 0 ]; then + lines=$(( lines + 1 )) + fi + + # Restore the cursor position + printf '\033[u' + + echo "$lines" +} + +# Check if the script is being executed or sourced +if [ "$0" = "$BASH_SOURCE" ]; then + # Script is being executed + # Call the current_input_lines function + current_input_lines +fi \ No newline at end of file diff --git a/spells/cursor b/spells/cantrips/cursor similarity index 89% rename from spells/cursor rename to spells/cantrips/cursor index 11673d1..d25253a 100644 --- a/spells/cursor +++ b/spells/cantrips/cursor @@ -4,6 +4,8 @@ # allowing you to control the position and appearance of the terminal cursor. # Use these variables to move the cursor around the screen, # hide and show it, and save and restore its position. +# This script contains all cursor control codes in the POSIX standard. +# You must source this script with 'source ./cursor' or '. ./cursor' to use it. # # Example usage: # printf "$SAVE_CURSOR" diff --git a/spells/menu/cursor-blink b/spells/cantrips/cursor-blink similarity index 100% rename from spells/menu/cursor-blink rename to spells/cantrips/cursor-blink diff --git a/spells/cantrips/fathom-cursor b/spells/cantrips/fathom-cursor new file mode 100755 index 0000000..0eef41e --- /dev/null +++ b/spells/cantrips/fathom-cursor @@ -0,0 +1,54 @@ +#!/usr/bin/env sh + +# This spell reveals the x and y coordinates of the cursor in the terminal window. + +# Define the function to calculate the position of the cursor +fathom_cursor() { + local position + local x=false + local y=false + local verbose=false + while getopts 'vxy' flag; do + case "${flag}" in + --verbose|v) verbose=true ;; + x) x=true ;; + y) y=true ;; + esac + done + + # Get the position of the cursor + position=$(IFS=';' read -sdR -p $'\E[6n' ROW COL; printf "%s;%s" "${ROW#*[}" "$COL") + + if [ $x = true ] && [ $y = true ]; then + if [ $verbose = true ]; then + printf "X: %s\n" "${position##*;}" + printf "Y: %s\n" "${position%%;*}" + else + printf "%s\n" "${position##*;}" + printf "%s\n" "${position%%;*}" + fi + else + if [ $x = true ]; then + if [ $verbose = true ]; then + printf "X: " + fi + printf "%s\n" "${position##*;}" + elif [ $y = true ]; then + if [ $verbose = true ]; then + printf "Y: " + fi + printf "%s\n" "${position%%;*}" + else + if [ $verbose = true ]; then + printf "Position: " + fi + printf "%s\n" "$position" + fi + fi +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function + fathom_cursor "$@" +fi diff --git a/spells/cantrips/fathom-terminal b/spells/cantrips/fathom-terminal new file mode 100755 index 0000000..39309c0 --- /dev/null +++ b/spells/cantrips/fathom-terminal @@ -0,0 +1,70 @@ +#!/usr/bin/env sh + +# This spell reveals the width and height of the terminal window. + +# Define the function to calculate the size of the terminal window +fathom_terminal() { + width=false + height=false + verbose=false + while getopts "vwht" flag; do + case "${flag}" in + --verbose|v) verbose=true;; + --width|w) width=true;; + --height|h) height=true;; + --test|t) test_fathom_terminal; exit 0;; + esac + done + + # Get the size of the terminal window + window_width=$(tput cols) + window_height=$(tput lines) + size="${window_width};${window_height}" + if [ $width = true ] && [ $height = true ]; then + if [ $verbose = true ]; then + printf "Width: %s\n" "${window_width}" + printf "Height: %s\n" "${window_height}" + else + printf "%s\n" "${window_width}" + printf "%s\n" "${window_height}" + fi + else + if [ $width = true ]; then + if [ $verbose = true ]; then + printf "Width: " + fi + printf "%s\n" "${window_width}" + elif [ $height = true ]; then + if [ $verbose = true ]; then + printf "Height: " + fi + printf "%s\n" "${window_height}" + else + if [ $verbose = true ]; then + printf "Size: " + fi + printf "%s\n" "$size" + fi + fi +} + +# Unit test +test_fathom_terminal() { + . "./assertions" + assert_output "fathom_terminal -w" "$(tput cols)" + assert_output "fathom_terminal --width" "$(tput cols)" + assert_output "fathom_terminal -h" "$(tput lines)" + assert_output "fathom_terminal --height" "$(tput lines)" + assert_output "fathom_terminal -w -v" "Width: $(tput cols)" + assert_output "fathom_terminal --width --verbose" "Width: $(tput cols)" + assert_output "fathom_terminal -h -v" "Height: $(tput lines)" + assert_output "fathom_terminal --height --verbose" "Height: $(tput lines)" + assert_output "fathom_terminal -h -v -w" "Height: $(tput lines)\nWidth: $(tput cols)" + assert_output "fathom_terminal --height --verbose --width" "Height: $(tput lines)\nWidth: $(tput cols)" +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function + fathom_terminal "$@" +fi diff --git a/spells/cantrips/max-length b/spells/cantrips/max-length new file mode 100755 index 0000000..6c1a134 --- /dev/null +++ b/spells/cantrips/max-length @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +# This magical spell reveals the maximum length of a list of strings. +# Accepts either a list ("word1 word2 word3" or an array of strings +# Usage: cast_max_length string1 string2 ... + +# Define the function to calculate the maximum length of the given strings +max_length() { + if [ -z "$1" ]; then + echo "No arguments passed to max_length" + return 1 + fi + if [[ "$1" == *" "* ]]; then + # Argument is a string containing multiple words + longest=0 + for arg in $1; do + arg_length=${#arg} + if [ $arg_length -gt $longest ]; then + longest=$arg_length + fi + done + else + # Argument is an array + longest=0 + for arg in "${!1}"; do + arg_length=${#arg} + if [ $arg_length -gt $longest ]; then + longest=$arg_length + fi + done + fi + if [ "$2" == "-v" ]; then + echo "Maximum length: $longest" + fi + echo $longest +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function and pass it the command line arguments + max_length "$@" +fi \ No newline at end of file diff --git a/spells/cantrips/menu b/spells/cantrips/menu new file mode 100755 index 0000000..d69ce09 --- /dev/null +++ b/spells/cantrips/menu @@ -0,0 +1,97 @@ +#!/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: menu "Please choose" "item1 item2 item3" "ls cat command3" +# Use up/down arrow keys to select a menu item, then press Enter. Or cancel with ESC. + +. colors + +description=$1 +items=($2) +commands=($3) +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=${#items[@]} + +# Calculate the maximum length of the item names +max_name_length=$(max-length "${items[*]}") +max_name_length=$((max_name_length)) + +# 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 + for i in "${!items[@]}"; do + if [ $i -eq $selected ]; then + printf "${CYAN}> %-${max_name_length}s${RESET} ${GREY}%s${RESET}\n" "${items[$i]}" "${commands[$i]}" + else + # Print the menu item plus extra spaces to cover up its command, since it's not selected + printf " %-${max_name_length}s %*s\n" "${items[$i]}" "${#commands[$i]}" + fi + 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 +} + +cursor-blink off + +# Display the menu's description +echo $description + +# Menu loop +while true; do + display_menu + handle_key $key +done diff --git a/spells/cantrips/move-cursor b/spells/cantrips/move-cursor new file mode 100755 index 0000000..6fa0e7f --- /dev/null +++ b/spells/cantrips/move-cursor @@ -0,0 +1,29 @@ +#!/usr/bin/env sh + +# This magical spell moves the cursor to the specified x and y coordinates in the terminal window, as if guided by an unseen hand. + +# Define the function to move the cursor +move_cursor() { + x=$1 + y=$2 + + # Move the cursor to the specified position + printf "\033[${y};${x}H" +} + +# Unit test +test_move_cursor() { + . assertions + initial_x=$(fathom-cursor -x) + initial_y=$(fathom-cursor -y) + move_cursor 3 4 + final_position=$(fathom-cursor) + assert_equal $final_position "3;4" + move_cursor $initial_x $initial_y + echo "All tests passed" +} + +# Check if the script is being sourced, and run it if not +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + move_cursor "$@" +fi diff --git a/spells/say b/spells/cantrips/say similarity index 65% rename from spells/say rename to spells/cantrips/say index 3876a33..8af334a 100755 --- a/spells/say +++ b/spells/cantrips/say @@ -8,4 +8,10 @@ say() { say_inline() { printf "%b" "${1}" -} \ No newline at end of file +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function + say "$@" +fi diff --git a/spells/wizard_eyes b/spells/cantrips/wizard-eyes old mode 100644 new mode 100755 similarity index 52% rename from spells/wizard_eyes rename to spells/cantrips/wizard-eyes index 3edeaa0..7119e8b --- a/spells/wizard_eyes +++ b/spells/cantrips/wizard-eyes @@ -3,16 +3,15 @@ # A special version of say that prints the text in grey text, indented. # Use this to show commands before they are executed, to teach those on the path of wizardry. -source ./say -source ./ask_Yn +. colors wizard_eyes() { - if [ -z $WIZARD ]; then check_wizard; fi + if [ -z $WIZARD ]; then check-wizard; fi if [ -n "$WIZARD" ] && [ "$WIZARD" -eq 1 ]; then say " ${GREY}${1}${RESET}"; fi } -# Todo next: Add remember/recall functions so that wizard status is remembered after first ask -check_wizard() { - ask_Yn WIZARD "First, let me ask, are you a wizard?" - export "WIZARD=$WIZARD" -} \ No newline at end of file +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the function + wizard_eyes "$@" +fi diff --git a/spells/detect-distro b/spells/detect-distro index df9af3b..f0a2116 100755 --- a/spells/detect-distro +++ b/spells/detect-distro @@ -4,8 +4,8 @@ # Also saves the result in the $DISTRO environment variable. # -v for more verbose output -source ./colors -source ./say +source colors +source say # Read arguments while getopts v flag diff --git a/spells/rehashchant b/spells/hashchant similarity index 100% rename from spells/rehashchant rename to spells/hashchant diff --git a/spells/hourly-bell b/spells/hourly-bell new file mode 100755 index 0000000..5773a0c --- /dev/null +++ b/spells/hourly-bell @@ -0,0 +1,57 @@ +#!/bin/sh + +start_hourly_bell(){ + # Set up an array of message templates + messages=( + "In the silent depths of the night, a bell tolls and tells you it is %s o'clock." + "A lone bell tolls, a reminder of the hour %s." + "On the top of the hill, the ancient bell tolls to mark the hour, %s." + "Deep in the forest, the bell echoes announcing it's %s o'clock." + "You hear the chimes of a far-off bell. It's the hour of %s." + "From the cathedral tower, the bell tolls out the hour of %s." + "The bell of the clock tower tolls %s o'clock." + "A bell tolls in the distance, signaling that it is %s." + "On a still night, the bell tolls, %s o'clock." + "As you sit in your quiet study, a bell tolls, announcing the hour %s." + "A bell tolls, and the hour is %s" + "The bell tolls, and you realize it's %s o'clock." + "A bell echoes in the valley, announcing the hour %s." + "With a final toll, the bell signals the hour %s." + "A bell tolls softly, reminding you that it is %s o'clock." + ) + + while true; do + current_time=$(date +%T) # gets the current hour and minutes + current_hour=$(date +%H) # gets the current hour in 24 hour format + current_minutes=$(date +%M) # gets the current minute + # calculate how many seconds are left until the next hour + sleep_time=$((3600 - (current_minutes * 60) - $(date +%S))) + sleep $sleep_time + # Choose a random message template + message=${messages[$RANDOM % ${#messages[@]}]} + # Use the bg_log function to output the message + bg_log "$(printf "$message" "$current_hour")" + done +} + +install_hourly_bell(){ + if grep -q "source ${BASH_SOURCE[0]}" ~/.bashrc; then + echo "You have already memorized this spell" + else + echo "Do you wish to memorize this spell? (yes/no)" + read -r answer + if [ "$answer" == "yes" ]; then + echo "source ${BASH_SOURCE[0]}" >> ~/.bashrc + echo "Spell memorized in .bashrc, so the bell will always be active in the terminal." + else + echo "The bell will only ring in this shell window." + fi + fi +} + +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + install_hourly_bell + trap '' 2 # Traps the INT signal and does nothing with it + start_hourly_bell & # runs the function in background + echo "Hourly bell has started" # message when script is run +fi \ No newline at end of file diff --git a/spells/menu/fathom-cursor b/spells/menu/fathom-cursor deleted file mode 100755 index 899e0a3..0000000 --- a/spells/menu/fathom-cursor +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env sh - -# This spell reveals the x and y coordinates of the cursor in the terminal window. - -# Define the function to calculate the position of the cursor -fathom_cursor() { - # Get the position of the cursor - position=$(IFS=';' read -sdR -p $'\E[6n' ROW COL; printf "%s;%s" "${ROW#*[}" "$COL") - - # Output the position of the cursor - echo "Cursor position: $position" -} - -# Check if the script is being called from another script -if [ "${BASH_SOURCE[0]}" = "$0" ]; then - # If not, call the function - fathom_cursor -fi \ No newline at end of file diff --git a/spells/menu/fathom-terminal b/spells/menu/fathom-terminal deleted file mode 100755 index f7e3b52..0000000 --- a/spells/menu/fathom-terminal +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env sh - -# This magical spell reveals the dimensions of the terminal window, displaying the width and height as if by magic. - -# Define the function to calculate the dimensions of the terminal window -fathom_dimensions() { - # Get the width of the terminal window - width=$(tput cols) - - # Get the height of the terminal window - height=$(tput lines) - - # Output the dimensions of the terminal window - echo "Width: $width" - echo "Height: $height" -} - -# Check if the script is being called from another script -if [ "${BASH_SOURCE[0]}" = "$0" ]; then - # If not, call the function - fathom_dimensions -fi \ No newline at end of file diff --git a/spells/menu/max-length b/spells/menu/max-length deleted file mode 100755 index 4bc2c72..0000000 --- a/spells/menu/max-length +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env sh - -# This magical spell reveals the maximum length of a list of strings. -# Usage: cast_max_length string1 string2 ... - -# Define the function to calculate the maximum length of the given strings -max_length() { - # Initialize the maximum length to 0 - max_length=0 - - # Loop through the arguments and calculate the maximum length - for string in "$@"; do - length=${#string} - if [ $length -gt $max_length ]; then - max_length=$length - fi - done - - # Output the maximum length - echo "Maximum length: $max_length" -} - -# Check if the script is being called from another script -if [ "${BASH_SOURCE[0]}" = "$0" ]; then - # If not, call the function and pass it the command line arguments - max_length "$@" -fi \ No newline at end of file diff --git a/spells/rosetta-stone b/spells/rosetta-stone new file mode 100644 index 0000000..325cc41 --- /dev/null +++ b/spells/rosetta-stone @@ -0,0 +1,196 @@ +#!/bin/sh + +# The Rosetta Stone Spell +# +# This spell is an immutable, self-describing teacher-script, designed to teach +# basic Unix commands to wizards studying magic. + +# todo: this should allow colors +quest_text() { + foo=$1 + for (( i=0; i<${#foo}; i++ )); do + echo -en "${foo:$i:1}" + sleep 0.01 + if [ "${foo:$i:1}" = " " ]; then + # Add an extra pause between words + sleep "0.0$(echo $((RANDOM)))" + fi + done + echo -en "\n" + sleep 0.22 +} + +indent() { sed 's/^/ /'; } + +this_spell="${GREEN}this spell${RESET}" +This_spell="${GREEN}This spell${RESET}" + +# The rune for checking if a script file exists is -e +if [ -e "$0" ]; then + quest_text "This spell exists." +else + quest_text "The spell $0 does not exist." +fi + +# The rune for checking if a script file is a regular file is -f +if [ -f "$0" ]; then + quest_text "The spell is a regular file." +else + quest_text "The spell is not a regular file." +fi + +# The rune for checking if a script file is empty is -s +if [ -s "$0" ]; then + quest_text "The spell is not empty." +else + quest_text "The spell is empty." +fi + +# The rune for revealing the name of this script is $0 +quest_text "${This_spell} is named $0." + +# The rune for determining the number of arguments passed to this script +quest_text "${This_spell} was passed $# arguments." + +if [ $# -ge 1 ]; then + # The rune for revealing the first argument passed to this script + quest_text "The first argument passed to ${this_spell} is '$1'." + + # The rune for revealing all of the arguments passed to this script + quest_text "All arguments passed to ${this_spell} are '$@'." +fi + +# The rune for revealing the process ID of the current script +quest_text "The process ID of ${this_spell} is $$." + +# The rune for revealing the exit status of the last executed command +quest_text "The exit status of the command ran previously to ${this_spell} was $?." + +# todo: make sure -e is the right flag for checking that the string exists / is 1 or greater length +if [ -e "$!" ]; then + # The rune for revealing the process ID of the last backgrounded command + quest_text "The process ID of the last background command in this shell is $!." +fi + +# The rune for revealing the current line number in the script +quest_text "The current line number in ${this_spell} is $LINENO." + +# The spell for displaying the script's file type +quest_text "This spell is of type: " +quest_text "$(file $0 | indent)" + +# The spell for displaying the script's permissions +quest_text "This spell has permissions $(ls -l $0 | awk '{print $1}')." + +# The spell for displaying the script's owner and group +quest_text "This spell is owned by $(ls -l $0 | awk '{print $3}')." +quest_text "This spell belongs to group $(ls -l $0 | awk '{print $4}')." + +# The spell for displaying the last time the script was modified +quest_text "This spell was last modified on $(ls -l --time-style=+"%D %T" $0 | awk '{print $6, $7}')." + +# The spell for displaying the script's size +quest_text "This spell has a size of $(du -h $0 | awk '{print $1}')." + +# The spell for displaying the number of lines in this script +quest_text "This spell has $(wc -l $0 | awk '{print $1}') lines." + +# The spell for displaying the number of characters in this script +quest_text "This spell has $(wc -m $0 | awk '{print $1}') characters." + +# The spell for displaying the number of words in this script +quest_text "This spell has $(wc -w $0 | awk '{print $1}') words." + +# The spell for displaying the average line length of this script +quest_text "This script has an average line length of $(awk '{x += length; ++n} END {print x/n;}' $0)." + +# The spell for displaying the top 10 most common words used in this script +quest_text "The top 10 most common words used in this spell are: " +quest_text "$(tr -cs "[:alpha:]" "\n" < $0 | tr '[:upper:]' '[:lower:]' | sort | uniq -c | sort -n | tail -10 | indent)" + +# The rune for revealing the current working directory +quest_text "The current working directory you are running ${this_spell} in is $(pwd)." + +# The rune for revealing the current date and time +quest_text "The current date and time ${this_spell} is running is $(date)." + +# The rune for revealing the current user's home directory +quest_text "The home directory of the user running ${this_spell} is $HOME." + +# The rune for revealing the current shell +quest_text "The current shell is $SHELL." + +# The rune for revealing the hostname of the system +quest_text "The hostname of the system ${this_spell} is on is $(hostname)." + +# The rune for revealing the current user +quest_text "The current user is $(whoami)." + +# The rune for revealing the current working directory's permissions +# quest_text +#!/bin/sh + +# The rune for revealing the current PATH environment variable +quest_text "The current PATH env variable for the environment ${this_spell} is being run in is $PATH." + +# The rune for revealing all the environment variables +quest_text "All environment variables are $ENV." + +# The rune for revealing the number of seconds since the script was started +quest_text "Time since ${this_spell} started is $SECONDS." + +# The spell for generating a hash of the script for integrity verification +quest_text "The SHA-256 hash of this spell is $(sha256sum $0)" + +# The rune for revealing the system uptime +quest_text "Uptime since the system ${this_spell} is running on started is:" +quest_text "$(uptime | indent)" +printf "\n" + +# The rune for revealing memory usage is free +quest_text "RAM memory usage is:" +quest_text "$(free -h | indent)." +printf "\n" + +# The rune for revealing the total storage usage is df +quest_text "Hard disk storage usage is:" +quest_text "$(df -h | indent)" +printf "\n" + +# The spell for revealing the total number of running processes is pgrep +#quest_text "Total number of running processes: $(pgrep -c | indent)" +#printf "\n" + +# The spell for revealing all the running processes is ps +quest_text "All running processes on the shell ${this_spell} is running on are:" +quest_text "$(ps | indent)" +printf "\n" + +# The spell for searching this script for a specific pattern or keyword +quest_text "Searching this spell for the word 'wizard':" +quest_text "$(grep -n "wizard" $0 | indent)" +printf "\n" + +# The spell for performing text replacement within this script +#echo "Replacing all instances of 'spell' with 'incantation' in this script" +#sed -i "s/spell/incantation/g" $0 + +# The spell for extracting specific fields or information from this script +#echo "Extracting line numbers and line contents of all lines that contain the word 'spell':" +#awk '$0 ~ /spell/ {print NR,$0}' $0 + +# The spell for reading the content of the script file +quest_text "Complete contents of this spell are:" +cat $0 | indent + +# The spell for copying the script file to a new location +quest_text "Copying this spell to rosetta-stone-backup.sh" +cp $0 rosetta-stone-backup.sh + +# The spell for removing the script file +#quest_text "Deleting this script." +#rm $0 + +# The spell for making this script into an executable, self-destructing spell +echo "This script will now self-destruct." +chmod +x $0; rm $0