#!/bin/sh # Bare Metal Alchemist, 2022 ############################################# # Lead - ♄ # ############################################# # The most basic ingredient in an Alchemy recipe, lead is used in all # recipes in this repository to standardize some simple things that I # rely on to make scripts concise and pleasant to use # --------------- Escape Codes --------------- # These constants are used to add color and text formatting to the # terminal's output -- Further reading: ANSI Escape Codes RED="\e[0;31m" GREEN="\e[0;32m" BLUE="\e[0;34m" BOLD="\e[1m" ITALIC="\e[3m" ULINE="\e[4m" RESET="\e[0m" # TODO -- there are actually a lot of ANSI codes that can be used # 0 - Reset (normal attributes) # 1 - Bold or increased intensity # 2 - Faint (opposite of bold) # 3 - Italic # 4 - Underline # 5 - Blink slowly # 6 - Blink quickly # 7 - Swap foreground and background # 8 - Conceal # 9 - Cross-out # 10 - Default font # 11-19 - Alternate fonts # 20 - Fraktur font # 21 - Bold off or double underline # 22 - Bold and faint off # 23 - Italic and fraktur off # 24 - Underline off # 25 - Blink off # 27 - Undo foreground and background swapping # 28 - Conceal off # 29 - Cross-out off # 39 - Default text color # 49 - Default background color # 51 - Framed # 52 - Encircled # 53 - Overlined # 54 - Frame and encircle off # 55 - Overline off # TODO -- these should be implemented as part of the syntax for "say" # --------------- Traps --------------- # Traps, in the case of shell scripting, listen for certain signals # that are broadcast by the system and execute a command in response # We only want these to activate if we're running a recipe, so we # check to see if we're running a "shell inside of a shell" if [ $SHLVL -gt 1 ]; then # Make sure that ctrl+C consistently exits the script trap "exit" INT # Give informative error messages when we receive unguarded EXIT codes set -e handle_exit() { case $? in 0) exit ;; *) printf "${RED}Oops...${RESET} Something went wrong on line ${LINENO} of this script. Exit code was $?\n" ;; esac } trap handle_exit EXIT fi # --------------- Functions --------------- # Coding Moment: generally, whenever you see something with brackets # at the end of it, like this() or like(this), it's a function! # It takes inputs and gives (or does) something as an output # --------------- Input/Output --------------- # 'say' is a simple function that imitates "echo" # This needs to be built out way more! Replace echo for POSIX compatibility say() { printf "%b\n" "${1}" } ask_for() { if [ ${#} -eq 0 ]; then say "To use this command, you need to pass the variable you want," say "and then add as string of text to ask for it if you want. Example:\n" say "ask_for RESPONSE \"Could you give me a RESPONSE please?\"" say "" say "Afterwards, you'll be able to use \$RESPONSE as a variable," say "and ${ITALIC}say \$RESPONSE${RESET} will respond with your entry" return 0 fi if [ -n "${2}" ]; then printf "%b" "${2}" fi read ${1} } # --------------- Environment Setup --------------- # If there's an env file, export it's contents to the environment source_env() { if [ -f .env ]; then export $(grep -v '^#' .env | xargs) else if [ -z $ALCHEMY ]; then say "No .env file, this might cause some issues..." fi fi } # This is how we call a function that we have already defined. # This one takes no arguments (words after the function name source_env # --------------- Program Installation --------------- # Checks to see if we can use a command or not check_for() { command -v "$1" > /dev/null } if [ "$(id -u)" -eq 0 ]; then say "${RED}Woah there!${RESET} Seems you're running this script as a superuser." say "" say "That might cause some issues with permissions and whatnot. Run this script as your default user (without sudo) and I'll ask you when I need superuser permissions" say "" exit 1 fi # I like using source as a keyword! if ! check_for source; then source() { . ${1} } fi # This one installs utilities to your OS (If you need them!) install_if_needed() { for package in "$@" # $@ means "all the arguments you passed do case $DISTRO in "debian") # TODO Better installation detection than check_for if dpkg -l | awk '{ print $2 }' | grep -q "^${package}"; then say "$package already installed!" else say "installing $package" sudo apt install -y $package fi ;; "arch") if pacman -Qi $package &>/dev/null; then say "$package already installed!" else say "installing $package" sudo pacman -S $package --noconfirm --needed fi ;; "fedora") # TODO Better installation detection than "check_for" if [ -z $(check_for $package) ]; then say "installing $package" sudo dnf install -y $package else say "$package already installed!" fi ;; "mac") # TODO Better installation detection than "check_for" if [ -z $(check_for $package) ]; then say "installing $package" brew install $package else say "$package already installed!" fi ;; esac done } # --------------- Memory --------------- # These two functions might look like gibberish because we're using regex, # short for REGular EXpression. It's a form of matching text to a pattern. # It takes values and stores them away in the env for later reference forget() { DOTENV_ENTRY=$(cat .env | grep ^${1}\=) if [ -n "$DOTENV_ENTRY" ]; then unset ${1} sed -i "/^${1}.*$/d" .env else say "I already don't remember ${BLUE}${1}${RESET}..." fi # Once we've made changes, we need to make sure we're up to date source_env } remember() { # Optionally choose to output somewhere other than Alchemy if [ "${2}" = "to" ]; then ENV_LOCATION=${3} else ENV_LOCATION="$ALCHEMY/.env" fi KEY=$(say ${1} | cut -d'=' -f 1) VALUE=$(say ${1} | cut -d'=' -f 2) if say ${KEY} | grep -Eqv "^[A-Z_]+$"; then say "Keys must consist only of capital letters and underscores" return 1 fi VALID_CHARS='A-Za-z0-9/_.:=-' if say ${VALUE} | grep -Eqv "^[${VALID_CHARS}]+$"; then say "illegal VALUE: $VALUE" say "for key $KEY" say "Valid characters for env values: letters, numbers, \".\",\"/\",\"_\"",\":\", \"-\" fi # If we're setting a valid key/value pair if say ${1} | grep -Eq "^[A-Z_]+\=[$VALID_CHARS]+$"; then DOTENV_ENTRY=$(cat $ENV_LOCATION | grep "^${KEY}" ) # If something already exists and we're trying to set it to something new if [ -n "$DOTENV_ENTRY" ] && [ "$DOTENV_ENTRY" != "$1" ]; then say "I'm trying to remember ${BLUE}${1}${RESET}, but..." say "" say "${BLUE}${DOTENV_ENTRY}${RESET}" say "This has already been defined in the $ENV_LOCATION file!" say "" ask_for overwrite "would you like to overwrite it? ${BLUE}(y/n)${RESET} " case $overwrite in "y" | "Y") unset ${KEY} sed -i "/^${KEY}.*$/d" $ENV_LOCATION echo "${1}" >> $ENV_LOCATION export ${1} ;; esac elif [ -z "$DOTENV_ENTRY" ]; then unset ${KEY} echo "${1}" >> $ENV_LOCATION export ${1} fi fi # Once we've made changes to the Alchemy .env, # we need to make sure we're up to date if [ -z "$2" ]; then source_env fi } LEAD=1 # Confirm? Something to automate the y/n dialog