commit ccf33b459c3498f111b1b6cfbea95bc918ff4489 Author: deicidus Date: Mon Jan 9 07:03:17 2023 -0800 initial commit of ao-mud diff --git a/README.md b/README.md new file mode 100644 index 0000000..40890e1 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# ao-mud + +ao-mud is a project to make the terminal easier for novice users by narrativizing it as a Multi-User Dungeon, or M.U.D. + +# Principles: + +* story first - make the world make sense, and that will tell us what to code +* playtest-driven development - try to use it, fail fast and iterate; it has to be fun +* POSIX-compatible scripts - maximally compatible across platforms and weird nested environments +* immanence - the game is here, a meaningful virtual space in the terminal +* immediacy - the game is everywhere, not a separate mode +* pedagogy - collecting best practices and making them easy to use, learn, remember, and teach/share +* sharpen the saw - gradually improving the architecture of the tech stack +* smarter conventions - we can evolve the terminal and make it easier to teach by migrating to higher-order semantics (new words) + +# Architecture + +ao-mud is also ao-bash, an attempt to immanentize the AO to the terminal for a native un-AO experience that requires no database. + +Some architectural guidelines: + +* Make many small, atomic scripts that are almost as atomic as possible +* The smallest logical size is a semantic unit: (e.g., read-magic handles both reading all attributes or one named attribute, could abstract read-all-magic and read-all-magic but it's semantically parsimonious to combine them) +* Make each script work either sourced or executed by using the BASH_SOURCE idiom (will be included after conversion to POSIX) + +# The Beginning of Your AO Adventure... + +What is the Autonomous Organization, what does it mean? This mystery drives your quest. + +We all sense something is wrong with the world. We all want to ask the same question. + +The AO is the answer to this question. + +Along the way, you will learn to use the Unix terminal, and you will gain special terminal upgrades. Most people don't believe that UI upgrades can be game-changing, but with cybernetics and a little magic, anything is possible. + +# Three Paths of Knowledge + +On your AO adventure, there are three different tutorials which gradually unlock the powers of the terminal. Choose your class wisely: + +* Sorcerer: The Path of Sorcery is the path of casual power. Sorcerers learn how to wield powerful spells early on, but they don't know how they work. Sorcerers trade words and understanding and remember this knowledge in their memory. + +* Wizard: The Path of Wizardry is a path of careful learning. Wizards start by learning the true names of bash commands, and learn to assemble spells gradually. Wizards trade spells on pages (bash scripts) and save them in their spellbook (spells folder). + +* Alchemist: The Path of Art is an oral tradition. Find a bash alchemist who can teach you the elements and the ways of alchemy. + +# Loading Saved Games + +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. + +# How to Begin + +To start your AO adventure, type ./ao in this directory. diff --git a/spells/ask b/spells/ask new file mode 100755 index 0000000..ed64306 --- /dev/null +++ b/spells/ask @@ -0,0 +1,20 @@ +#!/usr/bin/env sh + +# This magical incantation allows the terminal to ask a question and receive an answer. + +ask() { + # Display the prompt + echo "${1}" + + # Read the user's input + read -r user_input + + # Return the user's input + echo "${user_input}" +} + +# Check if script is being sourced or executed +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # Script is being executed, call the function and pass the arguments + ask "$@" +fi diff --git a/spells/colors b/spells/colors new file mode 100644 index 0000000..cda3652 --- /dev/null +++ b/spells/colors @@ -0,0 +1,48 @@ +#!/usr/bin/env sh + +# 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. + +# Reset all attributes to their default values +RESET="\033[0m" + +# Colors +BLACK="\033[30m" +RED="\033[31m" +GREEN="\033[32m" +YELLOW="\033[33m" +BLUE="\033[34m" +MAGENTA="\033[35m" +CYAN="\033[36m" +WHITE="\033[37m" + +# Bright colors +BRIGHT_BLACK="\033[30;1m" +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_CYAN="\033[36;1m" +BRIGHT_WHITE="\033[37;1m" + +# Background colors +BG_BLACK="\033[40m" +BG_RED="\033[41m" +BG_GREEN="\033[42m" +BG_YELLOW="\033[43m" +BG_BLUE="\033[44m" +BG_MAGENTA="\033[45m" +BG_CYAN="\033[46m" +BG_WHITE="\033[47m" + +# Bright background colors +BG_BRIGHT_BLACK="\033[40;1m" +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_CYAN="\033[106m" +BG_BRIGHT_WHITE="\033[107m" diff --git a/spells/copy b/spells/copy new file mode 100755 index 0000000..94f4a39 --- /dev/null +++ b/spells/copy @@ -0,0 +1,29 @@ +#!/bin/sh + +# This spell copies the text file at the specified path to the clipboard. +# It takes one argument, the path to the text file. + +if [ ! -f "$1" ]; then + echo "That file does not exist." + exit 1 +fi + +# Check if the `pbcopy` command is available (only on macOS). If it is, use it to copy the contents of the file to the clipboard. +if command -v pbcopy >/dev/null 2>&1; then + pbcopy < "$1" + +# If `pbcopy` is not available, check if the `xsel` command is available (only on some Unix-based systems). If it is, use it to copy the contents of the file to the clipboard. +elif command -v xsel >/dev/null 2>&1; then + xsel --clipboard --input < "$1" + +# If neither `pbcopy` nor `xsel` is available, check if the `xclip` command is available (only on some Unix-based systems). If it is, use it to copy the contents of the file to the clipboard. +elif command -v xclip >/dev/null 2>&1; then + xclip -selection clipboard < "$1" + +# If none of the above commands are available, print an error message. +else + echo "Your spell fizzles. No clipboard utilities are available on this system. Please install xsel, xclip, or pbcopy (for mac)" >&2 + exit 1 +fi + +echo "Copied $1 to your clipboard." diff --git a/spells/cursor b/spells/cursor new file mode 100644 index 0000000..11673d1 --- /dev/null +++ b/spells/cursor @@ -0,0 +1,61 @@ +#!/usr/bin/env sh + +# This magical knowledge of textual cantrips allows for powerful cursor prestidigitation, +# 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. +# +# Example usage: +# printf "$SAVE_CURSOR" +# printf "$MOVE_UP" +# printf "$MOVE_LEFT" +# printf "Hello, world!" +# printf "$RESTORE_CURSOR" + +# Clearing +CLEAR_SCREEN="\033[2J" +CLEAR_LINE="\033[K" + +# Save cursor position +SAVE_CURSOR="\033[s" + +# Restore cursor position +RESTORE_CURSOR="\033[u" + +# Move cursor (one line/column) +MOVE_UP="\033[A" +MOVE_DOWN="\033[B" +MOVE_RIGHT="\033[C" +MOVE_LEFT="\033[D" + +# Move cursor to column n +# usage: move_to_column n +move_to_column() { + echo -en "\033[${1}G" +} + +# Move cursor to row n +# usage: move_to_row n +move_to_row() { + echo -en "\033[${1}H" +} + +# Move cursor to row n, column m +# usage: move_to_xy n m +move_to_xy() { + echo -en "\033[${1};${2}H" +} + +# Hide and show cursor +HIDE_CURSOR="\033[?25l" +SHOW_CURSOR="\033[?25h" + +# Scrolling +SCROLL_SCREEN="\033[S" +REVERSE_SCROLL_SCREEN="\033[T" + +# Set scrolling region +# usage: set_scroll_region top bottom +set_scroll_region() { + echo -en "\033[${1};${2}r" +} diff --git a/spells/detect-distro b/spells/detect-distro new file mode 100755 index 0000000..df9af3b --- /dev/null +++ b/spells/detect-distro @@ -0,0 +1,44 @@ +#!/usr/bin/env sh + +# Detects what OS you are running and outputs a short string to identify that OS. +# Also saves the result in the $DISTRO environment variable. +# -v for more verbose output + +source ./colors +source ./say + +# Read arguments +while getopts v flag +do + case "${flag}" in + v) VERBOSE=true + ;; + esac +done + +# Output welcome message +if [ $VERBOSE ]; then say "Detecting which Linux distribution or other operating system you are using..."; fi + +# Check for each known operating system one-by-one +if [ -f "/etc/debian_version" ]; then + DISTRO="debian" + if [ $VERBOSE ]; then say "${GREEN}Debian${RESET}, Ubuntu, or Raspbian OS detected."; fi +elif [ -f "/etc/arch-release" ]; then + DISTRO="arch" + if [ $VERBOSE ]; then say "${GREEN}Arch or Manjaro-based${RESET} OS detected."; fi +elif [ -f "/etc/fedora-release" ]; then + DISTRO="fedora" + if [ $VERBOSE ]; then say "${GREEN}Fedora${RESET} detected as the Operating System"; fi +elif [ $(uname | grep -c "Darwin") -eq 1 ]; then + DISTRO="mac" + if [ $VERBOSE ]; then say "${GREEN}MacOS${RESET} detected."; fi +else + # Failed to detect OS, so complain and exit + say unknown + exit 1 +fi + +# Output the short string for the detected OS +if [ ! $VERBOSE ]; then + say $DISTRO; +fi \ No newline at end of file diff --git a/spells/detect-magic b/spells/detect-magic new file mode 100755 index 0000000..583d7d3 --- /dev/null +++ b/spells/detect-magic @@ -0,0 +1,91 @@ +#!/bin/sh + +# This spell detects magic in the files in the current directory. It reveals any enchanted attributes hidden within the files and tells you how much magic it has detected. + +# Define colors +blue="\033[36m" +purple='\033[0;35m' +reset='\033[0m' + +# Define a function to print a message based on the total number of enchanted attributes +print_message() { + # Total number of enchanted attributes + count=$1 + + # High intensity message + if [ "$count" -gt 120 ]; then + # High intensity messages + messages=( + "Whoa, this room is seriously ${purple}enchanted${reset}!" + "I can feel the ${purple}magic${reset} emanating from these files!" + "This room is practically pulsating with ${purple}magic${reset}!" + "I've never sensed this much ${purple}magic${reset} in one place before!" + "The amount of ${purple}magic${reset} in this room is off the charts!" + "These files are practically glowing with ${purple}magic${reset}!" + "I can hardly contain all this ${purple}magic${reset}!" + ) + # Select a random message + message=${messages[$RANDOM % ${#messages[@]} ]} + # Medium intensity message + elif [ "$count" -gt 50 ]; then + # Medium intensity messages + messages=( + "There seems to be quite a bit of ${purple}magic${reset} in this room." + "This room is positively brimming with ${purple}magic${reset}." + "I can feel the ${purple}magic${reset} in the air." + "These files are infused with ${purple}magic${reset}." + "The ${purple}magic${reset} in this room is palpable." + "The ${purple}magic${reset} in these files is almost tangible." + "This room is filled with ${purple}magic${reset}." + ) + # Select a random message + message=${messages[$RANDOM % ${#messages[@]} ]} + # Low intensity message + elif [ "$count" -gt 15 ]; then + # Low intensity messages + messages=( + "I sense a bit of ${purple}magic${reset} in this room." + "There seems to be a hint of ${purple}magic${reset} in these files." + "I can feel a faint aura of ${purple}magic${reset} in this room." + "These files have a touch of ${purple}magic${reset}." + "I detect a faint trace of ${purple}magic${reset} in this room." + "There is a subtle essence of ${purple}magic${reset} in these files." + "This room has a faint glimmer of ${purple}magic${reset}." + ) + # Select a random message + message=${messages[$RANDOM % ${#messages[@]} ]} + fi + + # Print the message + echo -en "$message\n" +} + +# Print a header row +printf "${blue}File${reset}%-30s ${purple}Enchantments${reset}\n" + +# Iterate over all the files in the current directory +for file in *; do + # Use the read-magic spell to get a list of enchanted attributes for the file + attrs=$(./read-magic "$file" 2> /dev/null) + + # If the read-magic spell returns a non-empty list of attributes, + # print the file name and the number of attributes + if [ -n "$attrs" ]; then + # Count the number of lines in the attribute list + count=$(echo "$attrs" | wc -l) + + # Print the file name and the number of attributes in purple + printf "%-40s ${purple}%d${reset}\n" "$file" "$count" + + # Add the number of attributes to the total + total=$((total + count)) + fi +done + +# If the total number of attributes is greater than 0, print a message +if [ "$total" -gt 0 ]; then + print_message "$total" +fi + +# Reset the text color to the default +echo -en "$reset" diff --git a/spells/disenchant b/spells/disenchant new file mode 100755 index 0000000..f77d9aa --- /dev/null +++ b/spells/disenchant @@ -0,0 +1,89 @@ +#!/bin/sh + +# This "disenchant" script allows you to remove an extended attribute from a file. +# To use this script, pass the file and the key name as arguments. If no key name is specified, the script will provide +# a menu of the extended attribute keys on the file and allow you to select one with the arrow keys to delete. +# The menu is only displayed if there are at least two extended attributes (not counting the '# file:' line). +# If only one extended attribute is found, the script will delete that attribute without displaying the menu. + +# Disenchant a single attribute from a file +disenchant_one() { + key="$1" + file="$2" + if command -v xattr > /dev/null 2>&1; then + # xattr is available, use it + xattr -d "$key" "$file" + elif command -v attr > /dev/null 2>&1; then + # attr is available, use it + attr -r "$key" "$file" + elif command -v setfattr > /dev/null 2>&1; then + # setfattr is available, use it + setfattr -x "$key" "$file" + else + # xattr, attr, and setfattr are not available + echo "Error: xattr, attr, and setfattr are not available on this system." + exit 1 + fi + echo "Disenchanted $key attribute from $file." +} + +# Check if a file was given as an argument +if [ -z "$1" ]; then + # If no file was given, print an error message and exit + echo "Error: No file specified. Usage: disenchant file [key]" + exit 1 +fi + +# Read the extended attribute keys from the file +keys=$(./read-magic "$1") +# Extract the attribute keys from the output of read-magic +keys=$(echo "$keys" | awk -F: '{print $1}' | tr -d ' ') +echo $keys + +# Check if a key name was given as an argument +if [ -z "$2" ]; then + # If no key name was given, check if there are at least two extended attributes + if [ $(echo "$keys" | wc -l) -lt 2 ]; then + # If there is only one extended attribute, delete it + key=$(echo "$keys") + disenchant_one "$key" "$1" + else + # If there are at least two extended attributes, provide a menu of the keys and select one with the arrow keys + keys="$keys disenchant-all" + times=$(($(echo "$keys" | wc -l) + 1)) + desc="# disenchant this" + descriptions=() + for ((i = 1; i <= $times; i++)); do + descriptions+=("$desc") + done + echo "${descriptions[@]}" + menu "Choose which attribute to disenchant:" "$keys" $descriptions "menu_choice" # todo / problem: $descriptions needs to be a string but then it's only space-delimited so many commands cant work. solution is to refactor the menu script, splitting it up into many small and complete semantic scripts, and building a better more holistic and composed menu with lots of better building blocks. or using bmenu. + echo $menu_choice + key=${keys[$menu_choice]} + if [ -n "$key" ]; then + if [ "$key" = "disenchant all" ]; then + # Disenchant all extended attributes + for key in $keys; do + disenchant_one "$key" "$1" + done + echo "Disenchanted all attributes from $1." + break + else + # Disenchant the selected attribute + echo KEY $key + echo FILE $1 + disenchant_one "$key" "$1" + break + fi + else + # If no key was selected, print an error message and exit + echo "Error: No key selected." + exit 1 + fi + fi +else + # If a key name was given, use it as the key + key="$2" + # Disenchant the selected attribute + disenchant_one "$key" "$1" +fi \ No newline at end of file diff --git a/spells/enchant b/spells/enchant new file mode 100755 index 0000000..16b45f7 --- /dev/null +++ b/spells/enchant @@ -0,0 +1,45 @@ +#!/bin/sh + +# This spell enchants the specified file with the given attribute and value. +# If the attribute already exists, it is overwritten. +# If the file or attribute does not exist, it raises an error. + +# Check that the correct number of arguments was provided +if [ $# -lt 3 ] | [ $# -gt 4 ]; then + echo "Error: This spell requires three or four arguments: a file path, an attribute name, and a value." + exit 1 +fi + +# Set the file path, attribute name, and attribute value variables +file=$1 +attribute=$2 +value=$3 + +# Check that the file exists +if [ ! -f "$file" ]; then + echo "Error: The file does not exist." + exit 1 +fi + +# Define a function to set the attribute value using the available commands +set_attribute_value() { + # Try to set the attribute using the 'attr' command + attr -s "$1" -V "$2" "$3" 2>&1 >/dev/null || + + # If the 'attr' command is not available, try using the 'xattr' command + xattr -w "$1" "$2" "$3" 2>&1 > /dev/null || + + # If the 'xattr' command is not available, try using the 'setfattr' command + setfattr -n "$1" -v "$2" "$3" 2>&1 > /dev/null || + + # If none of the commands are available, raise an error + ( + echo "Error: This spell requires the 'attr', 'xattr', or 'setfattr' command to be installed." + exit 1 + ) +} + +# Set the attribute value using the set_attribute_value function +set_attribute_value "$attribute" "$value" "$file" + +echo "The file has been enchanted with the attribute '$attribute' and value '$value'." diff --git a/spells/forall b/spells/forall new file mode 100755 index 0000000..c055796 --- /dev/null +++ b/spells/forall @@ -0,0 +1,12 @@ +#!/bin/sh + +# This spell allows you to cast a magic spell on all the files in the current directory. Simply provide the spell as +# an argument, and it will be cast on each file in turn. +# +# Use this spell to perform powerful magic on all your files at once (or to simply perform a batch operation on +# multiple files). + +for file in ./*; do + echo "${file#./}" + "$@" "$file" | awk '{print " " $0}' +done diff --git a/spells/hash b/spells/hash new file mode 100755 index 0000000..861779d --- /dev/null +++ b/spells/hash @@ -0,0 +1,33 @@ +#!/bin/sh + +# The Hash Spell +# +# This powerful spell allows you to compute the CRC-32 hash of a text file, including its filename. Simply provide +# the path to the file as an argument, and the spell will do the rest. The resulting hash will be displayed on the +# command line. + +if [ "$#" != 1 ]; then + echo "Usage: hash file" + exit 1 +fi + +# Get the directory where the script is being called from +script_dir="$(cd "$(dirname "$0")" >/dev/null 2>&1 && pwd)" + +# Get the file name specified as an argument +file_name="$1" + +# Concatenate the script directory and the file name to get the full file path +file="$script_dir/$file_name" + +if [ ! -f "$file" ]; then + echo "Your spell fizzles. There is no file." + exit 1 +fi + +echo $file +# Compute the CRC-32 hash +crc=$(cksum "$file" | awk '{print $1}') + +# Convert the hash to hexadecimal and display it +printf "0x%x\n" "$crc" diff --git a/spells/jump-to-marker b/spells/jump-to-marker new file mode 100755 index 0000000..2298c66 --- /dev/null +++ b/spells/jump-to-marker @@ -0,0 +1,80 @@ +#!/bin/sh + +# This 'jump' spell teleports you to the location marked by the 'mark-location' spell. +# If no location has been marked, or if you are already at the marked location, the spell will fizzle. +# Because the script uses 'cd' to change your location, you must source it. This can be accomplished with "source ./jump-to-marker". +# Having to source the script explicitly can be avoided if the spell is 'memorized' (sourced in .bashrc). +# The spell will offer to do this, installing 'jump' as a systemwide keyword for you. + +# Check if the "jump" spell is already installed +if ! grep -q "jump" ~/.bashrc; then + printf "You have not yet memorized the 'jump-to-marker' spell, so you can't cast it anywhere. This spell is so powerful that it must be memorized (sourced in .bashrc) to use it as a normal command. Memorize the 'jump' spell now? (y/n) " + read -r REPLY + + if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then + # Install the "jump" spell in the user's bashrc file + SCRIPT_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") + printf "source $SCRIPT_DIR/jump-to-marker\n" >> ~/.bashrc + printf "The 'jump' spell has been memorized. Use the 'spellbook' command to forget memorized spells. You must open a new terminal or 'source ~/.bashrc' before using 'jump'.\n" + fi +fi + +# 'jump' spell function (will be loaded when this file is sourced by .bashrc) +jump() { + # Set the path to the marker file + marker_file="$HOME/.mud/portal_marker" + + # Check if the marker file exists + if [ -e "$marker_file" ]; then + # If the marker file exists, read the destination path from the file + destination="$(cat "$marker_file")" + + # Check if the current directory is already the destination + if [ "$(pwd)" == "$destination" ]; then + # If the current directory is already the destination, inform the user + echo "You are already at the marked location." + else + # If the current directory is not the destination, change the current directory to the destination + cd "$destination" + + # Print a random teleportation message + messages=("You feel a sudden warmth as you are enveloped in a glowing aura. When it fades, you find yourself at your destination." + "A strange sensation comes over you as you close your eyes. When you open them again, you are standing at your destination." + "You focus your mind on the marked location and feel yourself being pulled through the fabric of space. You open your eyes to find yourself at your destination." + "You feel a jolt as you are momentarily suspended in midair. When you land, you find yourself at your destination." + "You close your eyes and take a deep breath. When you open them, you are standing at your destination." + "A swirling vortex appears before you. You take a step forward and find yourself at your destination." + "You feel a tug on your stomach as you are suddenly pulled through a tunnel of light. When you emerge, you are at your destination." + "You hold out your hand and a bright portal appears. You step through and find yourself at your destination." + "You feel a rush of wind as you are teleported to your destination." + "You hear a faint ringing in your ears as you are transported to your destination." + "You close your eyes and visualize your destination. When you open them, you are standing there." + "You step into a glowing portal and are immediately transported to your destination." + "You feel a tug on your ankle as you are pulled through a wormhole. When you emerge, you are at your destination." + "You hear a faint hum as you are enveloped in a field of energy. When it dissipates, you are at your destination." + "You feel a sudden pressure on your chest as you are teleported to your destination." + "You feel a surge of magical energy as you are transported through the veil of reality." + "A glowing portal opens before you, and you step through it to your destination." + "A shimmering aura envelops you, and when it fades, you find yourself at the marked location." + "You close your eyes and focus, and when you open them again, you are standing at the marked location." + "A burst of light and sound surrounds you, and when it subsides, you are at the marked location." + "You feel a tug on your soul, and when it releases, you are standing at the marked location." + "You focus your magical energy and teleport to the marked location." + "A warp in the fabric of reality opens up, and you step through it to the marked location." + "You feel a sudden jolt, and when you regain your bearings, you are at the marked location." + "You close your eyes and visualize the marked location, and when you open them, you are there." + "You call upon the power of the elements to transport you to the marked location." + "You channel the energy of the celestial bodies to jump to the marked location." + "You recite an ancient incantation, and a magical portal appears, taking you to the marked location.") + echo "${messages[$RANDOM % ${#messages[@]}]}" + fi + else + # If the marker file does not exist, inform the user that no location has been marked + echo "No location has been marked. Use the 'mark-location' spell to mark a location before using the 'jump' spell." + fi +} + +# If this script is no being called from within another script, call the main function and forward it our args (if sourced, it loads the function without running it) +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + jump "$@" +fi diff --git a/spells/mark-location b/spells/mark-location new file mode 100755 index 0000000..ccf9ba4 --- /dev/null +++ b/spells/mark-location @@ -0,0 +1,27 @@ +#!/bin/sh + +# This "mark location" spell allows you to mark the current directory or file as the destination for a portal. +# To create a portal, navigate to the destination folder or file and cast this spell, then navigate to the location where +# you want to create the portal and cast the "create portal" spell. +# If an argument is given, it will be used as the path to the destination file or directory. +# If no argument is given, the current directory will be used as the destination. + +# Create ~/.mud direrctory if does not exist +mkdir -p ~/.mud + +# Set the path to the marker file +marker_file="$HOME/.mud/portal_marker" + +# Check if an argument was given +if [ -z "$1" ]; then + # If no argument was given, use the current directory as the destination + destination="$(pwd)" +else + # If an argument was given, use it as the destination + destination="$1" +fi + +# Save the destination path to the marker file +echo "$destination" > "$marker_file" + +echo "Location marked at $destination." diff --git a/spells/menu/await_keypress b/spells/menu/await_keypress new file mode 100755 index 0000000..7869ee1 --- /dev/null +++ b/spells/menu/await_keypress @@ -0,0 +1,74 @@ +#!/usr/bin/env sh + +# This magical spell captures and processes key presses. +# Use it to capture key presses from the terminal and determine which key was pressed. +# todo: Tab key not distinguished from Enter (\t case does not work) + +# Define the function to handle key presses +handle_key() { + # 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 + $'\t') + echo "tab" + ;; + $'\n') + echo "enter" + ;; + '') # also fires for Tab key + echo "enter" + ;; + '') + echo "backspace" + ;; + $'\b') + echo "backspace" + ;; + # Escape sequence, could be any number of keys depending on next character + $'\e') + read -rsn2 -t 0.1 key + case "$key" in + '') + echo 'escape' + ;; + # Up arrow key + $'[A') + echo "up" + ;; + # Down arrow key + $'[B') + echo "down" + ;; + # Right arrow key + $'[C') + echo "right" + ;; + # Left arrow key + $'[D') + echo "left" + ;; + $'[3') + echo "delete" + ;; + *) + echo "escaped key: $key" + ;; + esac + ;; + # Any other key + *) + echo "$key" + # od -c <<< $key # debug + ;; + esac +} + +# Check if the script is being executed or sourced, if executed then call the function +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + handle_key +fi diff --git a/spells/menu/cursor-blink b/spells/menu/cursor-blink new file mode 100755 index 0000000..7d3d462 --- /dev/null +++ b/spells/menu/cursor-blink @@ -0,0 +1,27 @@ +#!/usr/bin/env sh + +# This spell awakens or stills the cyclic illumination of the cursor. + +# Define the function to turn the cursor blink on +cursor_blink_on() { + # Turn the cursor blink on + printf "\033[?25h" +} + +# Define the function to turn the cursor blink off +cursor_blink_off() { + # Turn the cursor blink off + printf "\033[?25l" +} + +# Check if the script is being called from another script +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + # If not, call the appropriate function based on the command line argument + if [ "$1" = "on" ]; then + cursor_blink_on + elif [ "$1" = "off" ]; then + cursor_blink_off + else + echo "Usage: cast_cursor_blink on|off" + fi +fi \ No newline at end of file diff --git a/spells/menu/fathom-cursor b/spells/menu/fathom-cursor new file mode 100755 index 0000000..899e0a3 --- /dev/null +++ b/spells/menu/fathom-cursor @@ -0,0 +1,18 @@ +#!/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 new file mode 100755 index 0000000..f7e3b52 --- /dev/null +++ b/spells/menu/fathom-terminal @@ -0,0 +1,22 @@ +#!/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 new file mode 100755 index 0000000..4bc2c72 --- /dev/null +++ b/spells/menu/max-length @@ -0,0 +1,27 @@ +#!/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/path-wizard b/spells/path-wizard new file mode 100755 index 0000000..78d0042 --- /dev/null +++ b/spells/path-wizard @@ -0,0 +1,71 @@ +#!/bin/sh + +# This spell allows you to add or remove directories from your PATH environment variable permanently. +# It uses the 'grep' and 'sed' commands to search and edit the '.bashrc' file in your home directory. + +# Check if the correct number of arguments was provided +if [ "$#" -ne 1 ] && [ "$#" -ne 2 ]; then + echo "Error: This spell requires one or two arguments: 'add' or 'remove' and an optional directory path." + exit 1 +fi + +# Set the action and directory variables +action=$1 +directory=$2 + +# Check if the action is 'add' or 'remove' +if [ "$action" != "add" ] && [ "$action" != "remove" ]; then + echo "Error: The first argument must be 'add' or 'remove'." + exit 1 +fi + +# Check if the directory path was provided +if [ -z "$directory" ]; then + # If no directory path was provided, use the current directory + directory=$(pwd) +else + # If a directory path was provided, expand the '~' character if necessary + directory="${directory/#\~/$HOME}" +fi + +# Check if the directory exists +if [ ! -d "$directory" ]; then + echo "Error: The directory does not exist." + exit 1 +fi + +# Check if the '.bashrc' file exists in the home directory +if [ ! -f "$HOME/.bashrc" ]; then + echo "Error: The '.bashrc' file does not exist in your home directory." + exit 1 +fi + +# Check if the directory is already in the PATH variable +if grep -q "$directory" "$HOME/.bashrc"; then + # If the directory is already in the PATH variable, check if the action is 'add' + if [ "$action" = "add" ]; then + echo "The directory is already in your PATH." + exit 0 + fi + + # If the action is 'remove' and the directory is already in the PATH variable, remove the directory from the PATH variable using the 'sed' command + sed -i "\|$directory|d" "$HOME/.bashrc" + echo "The directory has been removed from your PATH." +else + # If the directory is not in the PATH variable, check if the action is 'add' + if [ "$action" = "add" ]; then + # If the action is 'add', append the directory to the PATH variable in the '.bashrc' file + echo "export PATH=$PATH:$directory" >> "$HOME/.bashrc" + echo "The directory has been added to your PATH." + else + # If the action is 'remove' and the directory is not in the PATH variable, display an error message + echo "Error: The directory is not in your PATH." + exit 1 + fi +fi + +if [ "$action" = "add" ]; then + echo "The changes to your PATH will not take effect until you open a new terminal or run the 'source ~/.bashrc' command in your current terminal." +else + echo "The changes to your PATH will not take effect until you open a new terminal." +fi \ No newline at end of file diff --git a/spells/read-magic b/spells/read-magic new file mode 100755 index 0000000..765799c --- /dev/null +++ b/spells/read-magic @@ -0,0 +1,82 @@ +#!/bin/sh + +# This spell reads the enchanted attributes from the specified file. +# If a field name is provided as the second argument, it reads the attribute with that name. +# If the file or attribute does not exist, it raises an error. + +# Check that the correct number of arguments was provided +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + echo "Error: This spell requires one or two arguments: a file path and an optional attribute name." + exit 1 +fi + +# Set the file path and attribute name variables +file=$1 +attribute=$2 + +# Check that the file exists +if [ ! -f "$file" ]; then + echo "Error: The file does not exist." + exit 1 +fi + +# Define a function to read the attribute value using the available commands +read_attribute_value() { + # Try to read the attribute using the 'attr' command + attribute_value=$(attr -g "$1" "$2" 2> /dev/null | cut -d ":" -f 2 | xargs) + + # If the 'attr' command is not available, try using the 'xattr' command + if [ -z "$attribute_value" ]; then + attribute_value=$(xattr -p "$1" "$2" 2> /dev/null | cut -d ":" -f 2 | xargs) + fi + + # If the 'xattr' command is not available, try using the 'getfattr' command + if [ -z "$attribute_value" ]; then + attribute_value=$(getfattr -n "$1" --only-values "$2" 2> /dev/null) + fi + + # Return the attribute value + echo "$attribute_value" +} + +# If an attribute name was not provided, print all attributes in YAML format +if [ -z "$attribute" ]; then + attributes_found=0 + + # Define a function to process each attribute + process_attribute() { + # Read the attribute value using the read_attribute_value function + value=$(read_attribute_value "$1" "$file") + + # Check if the attribute value was set + if [ -n "$value" ]; then + # Print the attribute and value in YAML format + printf "%s: %s\n" "$1" "$value" + attributes_found=$((attributes_found + 1)) + fi + } + + # Call the process_attribute function for each attribute + for attribute in $(getfattr -m ".*" -e hex "$file" | cut -d ":" -f 1 | cut -d "." -f 2 | tr -d ' '); do + process_attribute "$attribute" + done + + # Check if no extended attributes were found + if [ "$attributes_found" -eq 0 ]; then + echo "No enchanted attributes found." + fi + +# If an attribute name was provided, print the attribute value +else + # Read the attribute value using the read_attribute_value function + value=$(read_attribute_value "$attribute" "$file") + + # Check if the attribute value was set + if [ -z "$value" ]; then + echo "Error: The attribute does not exist." + exit 1 + fi + + # Print the attribute value + echo "$value" +fi diff --git a/spells/rehashchant b/spells/rehashchant new file mode 100755 index 0000000..0dd7900 --- /dev/null +++ b/spells/rehashchant @@ -0,0 +1,40 @@ +#!/bin/sh + +# This "enchant" spell imbues a file with the power of its own hash, binding it to its own unique identity. +# The spell requires a single argument: the path to the file to be enchanted. +# The resulting CRC-32 hash is saved as an extended attribute called "user.hash" in hexadecimal. + +# Check if the required argument was provided +if [ -z "$1" ]; then + # If no argument was given, display an error message and exit + echo "Error: No file specified." + exit 1 +fi + +# Set the path to the file +file="$1" + +# Check if the file exists +if [ ! -e "$file" ]; then + # If the file does not exist, display an error message and exit + echo "Error: File not found." + exit 1 +fi + +# Calculate the hash of the file +hash=$(echo -n "$file" | cksum | awk '{ print "0x"$1 }') + +# Save the hash as an extended attribute +if command -v attr > /dev/null; then + attr -s "hash" -V "$hash" "$file" 2>&1 >/dev/null +elif command -v xattr > /dev/null; then + xattr -w "user.hash" "$hash" "$file" 2>&1 >/dev/null +elif command -v setfattr > /dev/null; then + setfattr -n "user.hash" -v "$hash" "$file" 2>&1 > /dev/null +else + # If neither xattr or attr is available, display an error message and exit + echo "Error: xattr and attr commands not found. Cannot enchant file." + exit 1 +fi + +echo "File enchanted with hash: $hash" diff --git a/spells/say b/spells/say new file mode 100755 index 0000000..3876a33 --- /dev/null +++ b/spells/say @@ -0,0 +1,11 @@ +#!/usr/bin/env sh + +# 'say' is a more legible synonym for echo/printf +# Help! Why is this printing a newline??? printf isn't supposed to. Update: It is printing no newline at first then it starts printing one. ??? +say() { + printf "%b\n" "${1}" +} + +say_inline() { + printf "%b" "${1}" +} \ No newline at end of file diff --git a/spells/spellbook b/spells/spellbook new file mode 100755 index 0000000..049dc21 --- /dev/null +++ b/spells/spellbook @@ -0,0 +1,48 @@ +#!/bin/sh + +# 'Spellbook' spell +# This 'spellbook' spell allows you to view and manage your collection of memorized spells. +# To use this spell, simply cast it from any location. You will be presented with a list of spells that are currently memorized in your bashrc file, and a menu of options. + +# 'Spellbook' spell function + +spellbook() { + # Print a list of memorized spells + echo "Your current memorized spells are:" + spells=$(grep -o "source .*" ~/.bashrc | cut -d " " -f 2) + if [ -z "$spells" ]; then + echo "You have no memorized spells." + else + grep -o "source .*" ~/.bashrc | cut -d " " -f 2 | nl + fi + + # Print a menu of options + echo "What do you want to do? " + options="Forget Exit" + select opt in $options; do + case "$opt" in + "Forget") + # Print a menu of spells to forget + echo "Which spell do you want to forget?" + select spell in $spells; do + # Remove the selected spell from the bashrc file + sed -i "\\|$spell|d" ~/.bashrc + break + done + break + ;; + "Exit") + # Exit the 'spellbook' spell + break + ;; + *) + # Print an error message if the input is invalid + echo "Invalid option. Please try again." + ;; + esac + done +} + +if [ "$0" = "${BASH_SOURCE[0]}" ]; then + spellbook "$@" +fi diff --git a/spells/update-all b/spells/update-all new file mode 100755 index 0000000..1068525 --- /dev/null +++ b/spells/update-all @@ -0,0 +1,102 @@ +#!/usr/bin/env sh + +# Updates all system software. +# Requires that your OS is supported and detected by the detect-distro spell. +# On Arch-based distros, also offers to install pamac to automate rebuilding AUR packages with new changes to pull. +# -v for more verbose output + +# Requires say, ask_y, and wizard_eyes, also using colors, all currently located here: +source ./colors +source ./ask_Yn +source ./say +source ./wizard_eyes + +# Read arguments +while getopts v flag +do + case "${flag}" in + v) VERBOSE=true;; + esac +done + +# Helper function for Arch to check if pamac is already installed +check_pamac() { + wizard_eyes "pacman -Qi pamac # check if pamac is installed" + if pacman -Qi pamac &>/dev/null; then + PAMAC_INSTALLED=1 + else + PAMAC_INSTALLED=0 + fi +} + +# Helper function for Arch to install pamac +install_pamac() { + wizard_eyes "sudo pacman -S pamac" + sudo pacman -S pamac --noconfirm +} + +# Catch Ctrl-C and exit immediately it's pressed (otherwise it just kills current operation) +# Help! This doesn't always work, or works with a delay for sudo prompts. +quit() { + exit 1 +} +trap quit INT + +wizard_eyes "You're a wizard, so executed terminal commands and other tips will display like this." + +# Detect what OS we are using using the detect-distro spell, which puts the result in the $DISTRO environment variable +wizard_eyes "./detect-distro" +source ./detect-distro >/dev/null 2>&1 + +say "Updating system for ${GREEN}$DISTRO${RESET}." +if [ $VERBOSE ]; then say "You may be asked for your ${BLUE}sudo${RESET} password multiple times."; fi + +# Based on what distro was detected, update the system in the correct way +case $DISTRO in + "debian") + wizard_eyes "sudo apt update" + sudo apt update + wizard_eyes "sudo apt autoremove" + sudo apt autoremove + wizard_eyes "sudo apt upgrade" + sudo apt upgrade + ;; + "arch") + ask_Yn USE_PAMAC "Would you like to use ${BLUE}pamac${RESET} to automate rebuilding of updated AUR packages? (recommended)" + # If Yes, check for pamac install and offer to install it if it's not already installed + if [ "$USE_PAMAC" -eq 1 ]; then + check_pamac + if [ "$PAMAC_INSTALLED" -eq 0 ]; then + ask_Yn INSTALL_PAMAC "${BLUE}pamac${RESET} is not installed. Install it now?" + if [ "$INSTALL_PAMAC" -eq 1 ]; then + install_pamac + else + say "Ok, your AUR packages will not be automatically rebuilt, then." + fi + fi + fi + + wizard_eyes "pacman -Syu # update software packages" + sudo pacman -Syu --noconfirm + + # Use pamac to update repos + if [ "$USE_PAMAC" -eq 1 ] && [ "$PAMAC_INSTALLED" -eq 1 ]; then + say "Using pamac to rebuild AUR packages." + wizard_eyes "pamac update --aur --devel" + pamac update --aur --devel --no-confirm + fi + + ;; + "fedora") + wizard_eyes "sudo dnf update" + sudo dnf update + wizard_eyes "sudo dnf upgrade" + sudo dnf upgrade + ;; + "mac") + install + wizard_eyes "sudo brew update" + sudo brew update + ;; +esac + \ No newline at end of file diff --git a/spells/wizard_eyes b/spells/wizard_eyes new file mode 100644 index 0000000..3edeaa0 --- /dev/null +++ b/spells/wizard_eyes @@ -0,0 +1,18 @@ +#!/usr/bin/env sh + +# 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 + +wizard_eyes() { + 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