diff --git a/ansi_colors.sh b/ansi_colors.sh new file mode 100644 index 0000000..21074ae --- /dev/null +++ b/ansi_colors.sh @@ -0,0 +1,34 @@ +# This script is meant to be sourced in other scripts to make colorizing easier. +# It should not be made executable and run stand-alone. + +# Set Select Graphic Rendition (SGR) +GROFF_NO_SGR=1 + +# Modifiers +RESET=$'\e[0m' +BOLD=$'\e[1m' # also used as bright +DIM=$'\e[2m' +UNDERLINE=$'\e[4m' +BLINK=$'\e[5m' # this may not work in all terminals +INVERT=$'\e[7m' # swaps background and foreground colors + +# Foreground colors +FG_BLACK=$'\e[0;30m' +FG_RED=$'\e[0;31m' +FG_GREEN=$'\e[0;32m' +FG_YELLOW=$'\e[0;33m' +FG_BLUE=$'\e[0;34m' +FG_MAGENTA=$'\e[0;35m' +FG_CYAN=$'\e[0;36m' +FG_WHITE=$'\e[0;37m' + +# Background colors +BG_BLACK=$'\e[40m' +BG_RED=$'\e[41m' +BG_GREEN=$'\e[42m' +BG_YELLOW=$'\e[43m' +BG_BLUE=$'\e[44m' +BG_MAGENTA=$'\e[45m' +BG_CYAN=$'\e[46m' +BG_WHITE=$'\e[47m' + diff --git a/colorize_manpages.sh b/colorize_manpages.sh new file mode 100644 index 0000000..2df391c --- /dev/null +++ b/colorize_manpages.sh @@ -0,0 +1,48 @@ +# Source file to set colorization in less and man +# To change between ansi escape and tput, change commenting as required +# +# Change the variable below to specify the colorization method to use +# Valid values are "ansi" and "tput" +COLORIZER="tput" + +# Set pagers and turn off select graphic rendition (SGR) +export GROFF_NO_SGR=1 +export MANPAGER='less' +export PAGER='less' + + +# Check the COLORIZER variable to use the requested method +if [[ "$COLORIER" == "ansi" ]]; then + # Set ansi escape codes to colorize less/man output + export LESS_TERMCAP_mb=$'\e[1;31m' # blinking text mode (if supported), set to bold/red + export LESS_TERMCAP_md=$'\e[1;31m' # bold text mode, set to bold/red + export LESS_TERMCAP_me=$'\e[0m' # ends all special formatting (like mb, md, etc.) + export LESS_TERMCAP_se=$'\e[0m' # ends standout mode + export LESS_TERMCAP_so=$'\e[1;33;44m' # begin standout mode, bold/yellow text w/ blue background + export LESS_TERMCAP_ue=$'\e[0m' # end underline mode + export LESS_TERMCAP_us=$'\e[4;1;32m' # begin underline mode, bold/green text w/ underline + export LESS_TERMCAP_mr=$'\e[7m' # begin reverse video mode + export LESS_TERMCAP_mh=$'\e[2m' # begin dim, half bright mode + export LESS_TERMCAP_ZN=$'\e[74m' # begin subscript mode, possibly not supported + export LESS_TERMCAP_ZV=$'\e[75m' # end subscript mode, possibly not supported + export LESS_TERMCAP_ZO=$'\e[73m' # begin superscript mode, possibly not supported + export LESS_TERMCAP_ZW=$'\e[75m' # end superscript mode, possible not supported +elif [[ "$COLORIZER" == "tput" ]]; then + # Set tput values to match the same colors above (see line comments above for descriptions) + export LESS_TERMCAP_md=$(tput bold; tput setaf 1) + export LESS_TERMCAP_me=$(tput sgr0) + export LESS_TERMCAP_se=$(tput sgr0) + export LESS_TERMCAP_so=$(tput bold; tput setaf 3; tput setab 4) + export LESS_TERMCAP_ue=$(tput sgr0) + export LESS_TERMCAP_us=$(tput smul; tput bold; tput setaf 2) + export LESS_TERMCAP_mr=$(tput rev) + export LESS_TERMCAP_mh=$(tput dim) + export LESS_TERMCAP_ZN=$(tput ssubm) + export LESS_TERMCAP_ZV=$(tput rsubm) + export LESS_TERMCAP_ZO=$(tput ssupm) + export LESS_TERMCAP_ZW=$(tput rsupm) +else + echo + echo ***** COLORIZER variable must be set to colorize man/less output ***** + echo +fi diff --git a/command_exists.sh b/command_exists.sh new file mode 100644 index 0000000..a2d5fba --- /dev/null +++ b/command_exists.sh @@ -0,0 +1,23 @@ +# To have scripts not write everything out to stdout so results can be captured +# redirect any output not wanted to /dev/null +# +# This is the original that outputs 1 or 0 with echo +command_exists_1 () { + if type "$1" >> /dev/null; then + echo "1" + else + echo "0" + fi +} + +# A better version is to use the return keyword +# This command can be used in an if statement, if needed +# The logic has to be reversed - the shell treats 0 as success +command_exists() { + if type "$1" >> /dev/null; then + return 0 + else + return 1 + fi +} + diff --git a/common.v1.sh b/common.v1.sh new file mode 100755 index 0000000..ae8f129 --- /dev/null +++ b/common.v1.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Inform the user of the output they can expect. + +echo "common commands:" + + +# Show the most commonly used commands +tail ~/.bash_history -n 1000 \ + | sort \ + | uniq -c \ + | sed 's/^ *//' \ + | sort -n -r \ + | head -n 10 \ No newline at end of file diff --git a/common.v2.sh b/common.v2.sh new file mode 100755 index 0000000..211a62e --- /dev/null +++ b/common.v2.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Inform the user of the output they can expect. + +echo "common commands:" + + +# The following variables control how the command runs. +history_lines=1000 # The number of lines of history to search through +command_count=10 # The number of common commands to show + +# Show the most commonly used commands +tail ~/.bash_history -n ${history_lines} \ + | sort \ + | uniq -c \ + | sed 's/^ *//' \ + | sort -n -r \ + | head -n ${command_count} \ No newline at end of file diff --git a/common.v3.sh b/common.v3.sh new file mode 100755 index 0000000..7ab9f53 --- /dev/null +++ b/common.v3.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Inform the user of the output they can expect. + +echo "common commands:" + + +# The following variables control how the command runs. +shell_binary="" # A place to store the shell binary value +history_file="" # A place to store the history file path +history_lines=1000 # The number of lines of history to search through +command_count=10 # The number of common commands to show + +# Check whether you can determine the name of the shell binary +shell_regex="([^/]+$)" +if [[ $SHELL =~ $shell_regex ]]; then + # Depending on the name of the shell binary, set the history filepath + shell_binary=${BASH_REMATCH[1]} + if [[ $shell_binary == "bash" ]]; then + history_file=~/.bash_history + elif [[ $shell_binary == "zsh" ]]; then + history_file=~/.zsh_history + fi +fi + +# If you are searching through the bash history, you can look at the history file +# to get the most common commands +if [[ $shell_binary == "bash" ]]; then + + # Show the most commonly used commands + tail "${history_file}" -n ${history_lines} \ + | sort \ + | uniq -c \ + | sed 's/^ *//' \ + | sort -n -r \ + | head -n ${command_count} + +elif [[ $shell_binary == "zsh" ]]; then + + # Z shell shitory lines look like this: + # : 1621135004:0;uname -a + # Run the same command as above, and extract everything _after_ the + # semicolon, which is the command text + tail "${history_file}" -n ${history_lines} \ + | cut -d';' -f2 \ + | sort \ + | uniq -c \ + | sed 's/^ *//' \ + | sort -n -r \ + | head -n ${command_count} +else + + # show a warning to the user that you don't know where the history file + # is for their shell + echo "Sorry, I don't know where to find the history file for '${SHELL}'" +fi diff --git a/common.v4.sh b/common.v4.sh new file mode 100755 index 0000000..fa89dd4 --- /dev/null +++ b/common.v4.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Exit if any command fails +set -e + +# Inform the user of the output they can expect. + +echo "common commands:" + +write_command_then_count() { + # Get the command and count, which currently looks like: + # '42 git commit' + # Then write th e command with the count afterward + local line="$1" + local count=$(echo "${line}" | cut -d' ' -f1) + local command=$(echo "${line}" | cut -d' ' -f2-) + echo "${command} (${count})" +} + + +# The following variables control how the command runs. +shell_binary="" # A place to store the shell binary value +history_file="" # A place to store the history file path +history_lines=1000 # The number of lines of history to search through +command_count=${1:-10} # The number of common commands to show + +# Check whether you can determine the name of the shell binary +shell_regex="([^/]+$)" +if [[ $SHELL =~ $shell_regex ]]; then + # Depending on the name of the shell binary, set the history filepath + shell_binary=${BASH_REMATCH[1]} + if [[ $shell_binary == "bash" ]]; then + history_file=~/.bash_history + elif [[ $shell_binary == "zsh" ]]; then + history_file=~/.zsh_history + fi +fi + +# If you are searching through the bash history, you can look at the history file +# to get the most common commands +if [[ $shell_binary == "bash" ]]; then + + # Show the most commonly used commands + commands=$(tail "${history_file}" -n ${history_lines} \ + | sort \ + | uniq -c \ + | sed 's/^ *//' \ + | sort -n -r \ + | head -n ${command_count}) + +elif [[ $shell_binary == "zsh" ]]; then + + # Z shell shitory lines look like this: + # : 1621135004:0;uname -a + # Run the same command as above, and extract everything _after_ the + # semicolon, which is the command text + commands=$(tail "${history_file}" -n ${history_lines} \ + | cut -d';' -f2 \ + | sort \ + | uniq -c \ + | sed 's/^ *//' \ + | sort -n -r \ + | head -n ${command_count}) +else + + # show a warning to the user that you don't know where the history file + # is for their shell + echo "Sorry, I don't know where to find the history file for '${SHELL}'" +fi + +# Print each command, showing its order in the list +# Commands are separated by newlines, so temporarily change IFS to loop over +# each line of the commands +counter=1 +old_ifs=$IFS +IFS=$'\n' +for command in $commands +do + use_count=$(echo "$command" | cut -d' ' -f 1) + use_command=$(echo "$command" | cut -d' ' -f 2) + printf "$(tput bold; tput setaf 3)%-5s $(tput sgr0)$(tput setaf 6)%-5s $(tput sgr0)%-5s\n" "$counter" "$use_count" "$use_command" + counter=$((counter + 1)) +done +IFS=$old_ifs + diff --git a/ifs_example.sh b/ifs_example.sh new file mode 100644 index 0000000..df2224d --- /dev/null +++ b/ifs_example.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# When files or symlinks may include a space in the name, +# this style of looping over them can come in handy. +# +# Save the current value of IFS, so you can restore it later. Split on newlines. +old_ifs=$IFS +IFS=$'\n' + +# Find all symlinks and print each one +links=$(find ~ -type l -maxdepth 1) + +for link in $links +do + echo "Found Link: $link" +done + +# Restore the original IFS value +IFS=$old_ifs + +# While this can be used to cover a specific edge case, if you change the IFS +# variable often, that is a sign that a programming or other scripting language +# would provide a better solution diff --git a/interrupt_example.sh b/interrupt_example.sh new file mode 100755 index 0000000..8341c1c --- /dev/null +++ b/interrupt_example.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# An example of how to ensure a user really wants to interrupt an operation +# Useful for scripts where interruption could be harmful + +interrupt_count=0 +on_interrupt() { + if [ $interrupt_count -lt 1 ]; then + echo "Canceling this operation can cause errors." + echo "Press Ctrl-C again if you are sure you want to cancel." + interrupt_count=$((interrupt_count + 1)) + else + # Convention is to use the status code 130 for interrupted scripts. + echo "Canceling long operation" + printf "$(tput cr)$(tput el)" + exit 130 + fi +} + +trap on_interrupt INT + +total_time=0 +while [ $total_time -le 600 ]; do + printf "$(tput cr)$(tput el)Long operation: ${total_time} seconds elapsed\r" + sleep 3 + total_time=$((total_time + 3)) +done + diff --git a/load_example_history.sh b/load_example_history.sh new file mode 100755 index 0000000..5063bb8 --- /dev/null +++ b/load_example_history.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Load the example history provided by the Effective Shell book + +# If the book examples aren't available, load them +if [ ! -d ~/effective-shell ]; then + echo -e "\e[0;36mCopying Effective Shell example files\e[0m" + curl effective.sh | sh +fi + +# Check if there is a backup of the history file already +if [ ! -f ~/.bash_history.backup ]; then + # if not, make a backup of the file + cp ~/.bash_history ~/.bash_history.backup + # copy the example history + cp ~/effective-shell/data/history.txt ~/.bash_history + # clear the history + history -c + # reload the history + history -r + echo -e "\e[1;31mThe history file has been backed up and test data staged\e[0m" +else + echo -e "\e[1;33mThere is already a history file backup\e[0m" +fi \ No newline at end of file diff --git a/looptest b/looptest new file mode 100755 index 0000000..9b16770 Binary files /dev/null and b/looptest differ diff --git a/looptest.cpp b/looptest.cpp new file mode 100644 index 0000000..3917ffc --- /dev/null +++ b/looptest.cpp @@ -0,0 +1,9 @@ +#include +using namespace std; + +int main() { + for(int i = 0; i < 1000000; i++) { + } + return 0; +} + diff --git a/looptest.py b/looptest.py new file mode 100755 index 0000000..8dca527 --- /dev/null +++ b/looptest.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3.14 + +for i in range(1_000_000): + pass + diff --git a/rainbow.sh b/rainbow.sh new file mode 100644 index 0000000..0f6a553 --- /dev/null +++ b/rainbow.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Print a message in each of the 8 ANSI terminal colors + +rainbow() { + local message="$1" + local reset='\e[0m' + for ((color=31; color<=37; color++)) + do + color_code="\\e[0;${color}m" + if [ -t 1 ]; then + printf "${color} - ${color_code}${message}${reset}\n" + else + printf "${color} - ${message}\n" + fi + done +} + diff --git a/rainbow.txt b/rainbow.txt new file mode 100644 index 0000000..4a3e124 --- /dev/null +++ b/rainbow.txt @@ -0,0 +1,7 @@ +31 - Good afternoon +32 - Good afternoon +33 - Good afternoon +34 - Good afternoon +35 - Good afternoon +36 - Good afternoon +37 - Good afternoon diff --git a/random_numbers.sh b/random_numbers.sh new file mode 100755 index 0000000..59177d5 --- /dev/null +++ b/random_numbers.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Create an empty array to store random numbers +random_numbers=() + +# As long as the length of the array is less than 5, continue to loop +while [ ${#random_numbers[@]} -lt 5 ] +do + # Get a random number; ask the user if they want to add it to the array + random_number=$RANDOM + read -p "Add $random_number to the list? (y/n): " choice + + # If the user chose "y", add the random number to the array + if [ "$choice" = "y" ]; then random_numbers+=($random_number); fi +done + +# Show the contents of the array +echo "Random Numbers: ${random_numbers[@]}" + diff --git a/restore_history.sh b/restore_history.sh new file mode 100755 index 0000000..1dcc323 --- /dev/null +++ b/restore_history.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Restore the real history file after running book examples + +# Check for a backup file +if [ -f ~/.bash_history.backup ]; then + # Rename the backukp file as the current file + mv ~/.bash_history.backup ~/.bash_history + # Clear the example history + history -c + # Reload the history from the restored file + history -r + echo -e "\e[1;36mThe original history has been restored\e[0m" +else + echo -e "\e[1;33mThere is no backup history to restore\e[0m" +fi \ No newline at end of file diff --git a/set_ps1.sh b/set_ps1.sh new file mode 100755 index 0000000..c31f15c --- /dev/null +++ b/set_ps1.sh @@ -0,0 +1,228 @@ +# Note: this script should be sourced into your shell. For this reason it does +# not specify a shebang. +# +# set-ps1: set the command prompt (PS1) variable to a named 'theme'. +# +# example: +# +# set-ps1 simple +# +# If no 'theme' is provided, the original PS1 value that was set in the shell +# startup files is used. + +# Keep track of the original PS1 value. +_original_ps1="${PS1}" + +set_ps1() { + # Foreground colours. + local fg_black=$(tput setaf 0) # \033[30m + local fg_red=$(tput setaf 1) # \033[31m + local fg_green=$(tput setaf 2) # \033[32m + local fg_yellow=$(tput setaf 3) # \033[33m + local fg_blue=$(tput setaf 4) # \033[34m + local fg_magenta=$(tput setaf 5) # \033[35m + local fg_cyan=$(tput setaf 6) # \033[36m + local fg_white=$(tput setaf 7) # \033[37m + + # Background colours. + local bg_black=$(tput setab 0) # \033[40m + local bg_red=$(tput setab 1) # \033[41m + local bg_green=$(tput setab 2) # \033[42m + local bg_yellow=$(tput setab 3) # \033[43m + local bg_blue=$(tput setab 4) # \033[44m + local bg_magenta=$(tput setab 5) # \033[45m + local bg_cyan=$(tput setab 6) # \033[46m + local bg_white=$(tput setab 7) # \033[47m + + # Text styles and reset. Note that on some terminals 'bold' will produce + # light colours for bright colours, on others it will actually show the text + # in bold. + local bold=$(tput bold) # \033[1m + local dim=$(tput dim) # \033[2m + local start_underline=$(tput smul) # \033[4m + local stop_underline=$(tput rmul) # \033[24m + local reset=$(tput sgr0) # \033[0m + + # If you need them, you can get other terminal capabilties such as the + # number of colours, lines, columns, etc. + local colors=$(tput colors) # Number of colours, e.g. '8' or '256' + local lines=$(tput lines) # Number of lines in the terminal + local cols=$(tput cols) # Number of columns in the terminal + + # Special characters for PS1 strings: + # \a an ASCII bell character (07) + # \d the date in "Weekday Month Date" format (e.g., "Tue May 26") + # \D{format} + # the format is passed to strftime(3) and the result is inserted into the prompt string; an empty format results in a locale-specific time representation. The + # braces are required + # \e an ASCII escape character (033) + # \h the hostname up to the first `.' + # \H the hostname + # \j the number of jobs currently managed by the shell + # \l the basename of the shell's terminal device name + # \n newline + # \r carriage return + # \s the name of the shell, the basename of $0 (the portion following the final slash) + # \t the current time in 24-hour HH:MM:SS format + # \T the current time in 12-hour HH:MM:SS format + # \@ the current time in 12-hour am/pm format + # \A the current time in 24-hour HH:MM format + # \u the username of the current user + # \v the version of bash (e.g., 2.00) + # \V the release of bash, version + patch level (e.g., 2.00.0) + # \w the current working directory, with $HOME abbreviated with a tilde (uses the value of the PROMPT_DIRTRIM variable) + # \W the basename of the current working directory, with $HOME abbreviated with a tilde + # \! the history number of this command + # \# the command number of this command + # \$ if the effective UID is 0, a #, otherwise a $ + # \nnn the character corresponding to the octal number nnn + # \\ a backslash + # \[ begin a sequence of non-printing characters, which could be used to embed a terminal control sequence into the prompt + # \] end a sequence of non-printing characters + + # Depending on the name of the theme provided, set the prompt. + case $1 in + basic) + # Debian/Ubuntu style: + # \u@\h - username@host + # \w - working directory + # \$ - prompt + PS1="\u@\h:\w\\$ " + ;; + + debian) + # Debian/Ubuntu style: + # \u@\h - username@host (bold/green) + # \w - working directory (bold/blue) + # \$ - prompt (# if root, otherwise $) (bold/white) + PS1="\[${bold}${fg_green}\]\u@\h:\[${fg_blue}\]\w\[${fg_white}\]\\$\[${reset}\] " + ;; + + datetime) + # A style that shows the date and time: + # \D{%Y-%m-%d} - the year/month/date (in white) + # \@ - the time (in green) + # \$ - prompt (# if root, otherwise $) (bold/white) + PS1="\[${fg_white}\]\D{%Y-%m-%d} \[${bold}${fg_green}\]\@\[${fg_white}\] \\$\[${reset}\] " + ;; + + git) + # A Debian style prompt with the git info above it. + PS1="\$(_git_info)"$'\n'"\\[${bold}${fg_green}\]\u@\h:\[${fg_blue}\]\w\[${fg_white}\]\\$\[${reset}\] " + ;; + + dwmkerr) + # The current folder, up to 3 items shown, the git info, then the prompt. + # Starts with a leading newline to space out commands. + PS1=$'\n'"\[${bold}${fg_blue}\$(_pwd_max_folders 3)${reset}\] \$(_git_info)"$'\n'"\[${bold}${fg_white}\]\\$\[${reset}\] " + ;; + + + # Add your own themes here! + + *) + # Restore PS1 to its original value. + PS1="${_original_ps1}" + ;; + + esac + + # If we are in Z-Shell convert the PS1 to use Z-Shell format. + [ -n "$ZSH_VERSION" ] && PS1=$(_to_zsh "$PS1") +} + +# Build a string that shows: +# - The branch (underlined if 'main') in green +# - A red exclamation if there are any local changes not committed +# - An indicator of the number of stashed items, if any. +_git_info() { + # Don't write anything if we're not in a folder tracked by git. + if ! [ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ] + then + return + fi + + # Local copies of the colours for this function. + local fg_red=$(tput setaf 1) + local fg_green=$(tput setaf 2) + local fg_yellow=$(tput setaf 3) + local start_underline=$(tput smul) + local stop_underline=$(tput rmul) + local reset=$(tput sgr0) + # Git details. + local git_branch_name="$(git branch --show-current)" + local git_any_local_changes="$(git status --porcelain=v1 2>/dev/null)" + local git_stash_count="$(git rev-list --walk-reflogs --count \ + refs/stash -- 2>/dev/null)" # Ignore error when no stashes + local git_info="" + if [ "${git_branch_name}" = "main" ]; then + git_info="${fg_green}${start_underline}${git_branch_name}${reset}" + else + git_info="${fg_green}${git_branch_name}${reset}" + fi + if ! [ -z "${git_any_local_changes}" ]; then + # Note that we have to be careful to put the exclamation mark + # in single quotes so that it is not expanded to the last command! + git_info="${git_info} ${fg_red}"'!'"${reset}" + fi + if [ "${git_stash_count:-0}" -gt 0 ]; then + git_info="${git_info} ${fg_yellow}${git_stash_count} in stash${reset}" + fi + printf "${git_info}" +} + +# Show the pwd, limited to a certain number of folders. +_pwd_max_folders() { + local max_folders="$1" + # Write the PWD, replace the home path with a tilde, then cut out the last + # of the directories, up to the max folders value. + echo "${PWD/#$HOME/~}" | rev | cut -d'/' -f1-${max_folders} | rev +} + +# Convert a Bash PS1 string to a ZSH PS1 string. +_to_zsh() { + # Note that the following sequences are not supported as they don't have + # an Z-Shell equivalent. Mappings have been worked out from this document: + # https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html#Prompt-Expansion + # + # \l the basename of the shell's terminal device name + # \s the name of the shell, the basename of $0 (the portion following the final slash) + # \T the current time in 12-hour HH:MM:SS format + # \v the version of bash (e.g., 2.00) + # \V the release of bash, version + patch level (e.g., 2.00.0) + # \W the basename of the current working directory, with $HOME abbreviated with a tilde + # \# the command number of this command + + # Replace Bash special characters with Z-Shell special characters. + # Before we echo out the PS1 value, we need to replace '\u' with '%n'. If we + # don't do this here with shell parameter expansion, then 'echo' thinks '\u' + # is the beginning of a unicode character. This took quite a while to work + # out. I have left the replacement of '\u' to '%n' in the 'sed' call below + # but it not needed as the shell expansion below handles it. + # Note that before we replace '\$' with '%#', we replace '\$(' temporarily + # to SHELL_EXPAND, otherwise things like '\$(_my_func)' would be transformed + # to '%#(_my_func)', i.e. we need to replace the prompt special character + # but not escaped shell functions. + local zsh_ps1="$(echo ${1/\\u/%n} | sed \ + -e 's/\\\[/%{/g' \ + -e 's/\\\]/%}/g' \ + -e 's/\\\d/%w/g' \ + -e 's/\\\D/%D/g' \ + -e 's/\\\u/%n/g' \ + -e 's/\\\h/%m/g' \ + -e 's/\\\H/%M/g' \ + -e 's/\\\j/%j/g' \ + -e 's/\\\t/%*/g' \ + -e 's/\\\A/%T/g' \ + -e 's/\\\@/%@/g' \ + -e 's/\\\w/%~/g' \ + -e 's/\\\!/%!/g' \ + -e 's/\\\$(/SHELL_EXPAND/g' \ + -e 's/\\\$/%#/g' \ + -e 's/SHELL_EXPAND/\\\$(/g' \ + )" + + # Print the sequence. Note that we need to escape the % symbol. + local zsh_ps1_escaped=$(echo "${zsh_ps1}" | sed 's/%/%%/g') + printf "${zsh_ps1_escaped}" +} diff --git a/show_top_values.sh b/show_top_values.sh new file mode 100644 index 0000000..410b83c --- /dev/null +++ b/show_top_values.sh @@ -0,0 +1,19 @@ +# Show the top "n" values of a set (first number is the "n" to get) +show_top() { + local n=$1 + local values=${@:2:n} + echo "Top ${n} values: ${values}" +} + + +# This version uses shift to get the same result +show_top2 () { + # Grab the number of values to show, then shift. + local n=$1 + shift + + # Get the set of vaules to show + local values=${@:1:n} + echo "Top ${n} values: ${values}" +} + diff --git a/sql/create_mytomes.sql b/sql/create_mytomes.sql new file mode 100644 index 0000000..0eb3167 --- /dev/null +++ b/sql/create_mytomes.sql @@ -0,0 +1,193 @@ +CREATE EXTENSION IF NOT EXISTS ltree; + +DO $$ BEGIN + CREATE TYPE "ProgressType" AS ENUM ( + 'PAGES', + 'PERCENTAGE', + 'SECONDS' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + CREATE TYPE "BookFormat" AS ENUM ( + 'HARDCOVER', + 'TRADE_PAPERBACK', + 'MASS_MARKET_PAPERBACK', + 'EBOOK', + 'AUDIOBOOK' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + CREATE TYPE "ReadingStatus" AS ENUM ( + 'WANT_TO_READ', + 'CURRENTLY_READING', + 'READ', + 'DNF', + 'PAUSED' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + CREATE TYPE "TierType" AS ENUM ( + 'FREE', + 'NOADS', + 'ANALYTICS_PLUS', + 'PREMIUM' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +DO $$ BEGIN + CREATE TYPE "UserRole" AS ENUM ( + 'USER', + 'AGENT', + 'AUTHOR', + 'MODERATOR', + 'EDITOR', + 'PUBLISHER', + 'ADMIN' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT UNIQUE NOT NULL, + username TEXT UNIQUE NOT NULL, + display_name TEXT, + role "UserRole" DEFAULT 'USER'::"UserRole", + tier "TierType" DEFAULT 'FREE'::"TierType", + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + is_enabled BOOLEAN DEFAULT TRUE +); + +CREATE TABLE IF NOT EXISTS publishers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + website TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS books ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL, + description TEXT, + isbn_13 VARCHAR(13) UNIQUE, + format "BookFormat" NOT NULL, + published_year INT, + cover_image_url TEXT, + total_pages INT, + total_audio_seconds INT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS authors ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID, + name TEXT NOT NULL, + bio TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS author_agents ( + agent_id UUID NOT NULL, + author_id UUID NOT NULL, + PRIMARY KEY (agent_id, author_id) +); + +CREATE TABLE IF NOT EXISTS book_authors ( + book_id UUID REFERENCES books(id) ON DELETE CASCADE, + author_id UUID REFERENCES authors(id) ON DELETE CASCADE, + author_order INT NOT NULL DEFAULT 0, + PRIMARY KEY (book_id, author_id) +); + +CREATE TABLE IF NOT EXISTS book_publishers ( + book_id UUID REFERENCES books(id) ON DELETE CASCADE, + publisher_id UUID REFERENCES publishers(id) ON DELETE CASCADE, + PRIMARY KEY (book_id, publisher_id) +); + +CREATE TABLE IF NOT EXISTS author_publishers ( + publisher_id UUID NOT NULL, + author_id UUID NOT NULL, + PRIMARY KEY (publisher_id, author_id) +); + +CREATE TABLE IF NOT EXISTS user_publishers ( + publisher_id UUID NOT NULL, + user_id UUID NOT NULL, + PRIMARY KEY (publisher_id, user_id) +); + +CREATE TABLE IF NOT EXISTS user_books ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, + book_id UUID REFERENCES books(id) ON DELETE CASCADE NOT NULL, + status "ReadingStatus" NOT NULL, + is_favorite BOOLEAN DEFAULT FALSE, + dnf_reason TEXT, -- Nullable, used only if status is 'DNF' + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE (user_id, book_id) +); + +CREATE TABLE IF NOT EXISTS reading_sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_book_id UUID REFERENCES user_books(id) ON DELETE CASCADE NOT NULL, + progress_type "ProgressType" NOT NULL, -- 'PAGES', 'PERCENTAGE', 'SECONDS' + session_start INT NOT NULL DEFAULT 0, + session_end INT NOT NULL, + total_progress INT NOT NULL, + notes TEXT, + logged_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS reviews ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, + book_id UUID REFERENCES books(id) ON DELETE CASCADE NOT NULL, + overall_rating NUMERIC(3,2) CHECK (overall_rating >= 0 AND overall_rating <= 5.00), -- Allows 4.25, 4.5 + pacing_rating NUMERIC(3,2) CHECK (pacing_rating >= 0 AND pacing_rating <= 5.00), -- Multi-dimensional + prose_rating NUMERIC(3,2) CHECK (prose_rating >= 0 AND prose_rating <= 5.00), + character_rating NUMERIC(3,2) CHECK (character_rating >= 0 AND character_rating <= 5.00), + review_text TEXT, + is_spoiler BOOLEAN DEFAULT FALSE, + spoiler_cutoff_progress INT, -- Hides review if reader hasn't reached this page/percentage + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE (user_id, book_id) +); + +CREATE TABLE IF NOT EXISTS shelves ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, + name TEXT NOT NULL, + path ltree NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS book_shelves ( + book_id UUID REFERENCES user_books(id), + shelf_id UUID NOT NULL REFERENCES shelves(id) ON DELETE CASCADE, + PRIMARY KEY (book_id, shelf_id) +); + +-- Database Performance Indexes +CREATE INDEX idx_user_books_user_status ON user_books(user_id, status); +CREATE INDEX idx_books_isbn ON books(isbn_13); +CREATE INDEX idx_reviews_book_rating ON reviews(book_id, overall_rating); +CREATE INDEX idx_shelves_path_gist ON shelves USING gist (path); +CREATE UNIQUE INDEX idx_user_shelf_path ON shelves (user_id, path); + +GRANT usage ON SCHEMA public TO tomes_user; +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO tomes_user; diff --git a/sql/drop_mytomes.sql b/sql/drop_mytomes.sql new file mode 100644 index 0000000..1ce52d0 --- /dev/null +++ b/sql/drop_mytomes.sql @@ -0,0 +1,5 @@ +DROP TABLE author_agents, author_publishers, book_authors, book_shelves, + shelves, authors, publishers, user_publishers, reading_sessions, + reviews, shelves, user_books, books, users; + +DROP TYPE "BookFormat", "ProgressType", "ReadingStatus", "TierType", "UserRole"; diff --git a/sql/inserts/authors.sql b/sql/inserts/authors.sql new file mode 100644 index 0000000..2491d09 --- /dev/null +++ b/sql/inserts/authors.sql @@ -0,0 +1,45 @@ +-- +-- PostgreSQL database dump +-- + +\restrict fXlKByDlYAidtT90ZdDHF56j3Ix7FrPcpjSCCvaSCa7HbxnN2SQaXsBjzcdek1q + +-- Dumped from database version 18.4 (Ubuntu 18.4-0ubuntu0.26.04.1) +-- Dumped by pg_dump version 18.4 (Ubuntu 18.4-1.pgdg24.04+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: authors; Type: TABLE DATA; Schema: public; Owner: chris +-- + +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('89b1198a-6ad8-4039-942e-970ddb61514c', NULL, 'James Patterson', NULL, '2026-08-02 18:15:11.519+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('de9700a3-bb7d-4616-92eb-2918bc74072e', NULL, 'Michael Connelly', NULL, '2026-08-02 18:15:11.522+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('352a696c-3dc6-4307-876e-327edf719119', NULL, 'Brian Sitts', NULL, '2026-08-02 18:15:11.523+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('5172620d-45dc-4c24-9829-512e2c900fd7', NULL, 'Tom Clancy', NULL, '2026-08-02 18:15:11.524+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('f4c56ec1-6ff4-483c-8eeb-44f391af2e57', NULL, 'M. P. Woodward', NULL, '2026-08-02 18:15:11.525+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('3411c148-3bb1-4cb0-9059-154ca6609ff2', NULL, 'Jason Myers', NULL, '2026-08-02 18:15:11.527+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('b48480b0-ea05-4223-b3c3-0d1d4101b3b4', NULL, 'Rick Copeland', NULL, '2026-08-02 18:15:11.528+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('4c89babf-b1a2-40f4-996a-9f3853438544', NULL, 'Stan Sakai', NULL, '2026-08-02 18:15:11.529+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('9e3f1e0c-87b9-45d3-ab0e-3130c5d7b1a9', NULL, 'Stephen King', NULL, '2026-08-02 18:15:11.53+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('53ff5686-3b85-4719-b4f0-edce9325cd04', NULL, 'Dean Koontz', NULL, '2026-08-02 18:15:11.531+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('e4e3c5f7-9eb8-4e3d-9f27-35650c48f04b', NULL, 'Mark Greaney', NULL, '2026-08-02 18:15:11.532+00'); +INSERT INTO public.authors (id, user_id, name, bio, created_at) VALUES ('053d7dca-2e77-47a7-a316-a34c4090e4e0', NULL, 'James Joyce', NULL, '2026-08-10 16:53:43.249504+00'); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict fXlKByDlYAidtT90ZdDHF56j3Ix7FrPcpjSCCvaSCa7HbxnN2SQaXsBjzcdek1q + diff --git a/sql/inserts/book_authors.sql b/sql/inserts/book_authors.sql new file mode 100644 index 0000000..c21557e --- /dev/null +++ b/sql/inserts/book_authors.sql @@ -0,0 +1,40 @@ +-- +-- PostgreSQL database dump +-- + +\restrict Ureq9Zr3sxHwBT50DepdrW70MZeA905r7amJN992A5Vy4Z7bxuljU23Ll8ow7rW + +-- Dumped from database version 18.4 (Ubuntu 18.4-0ubuntu0.26.04.1) +-- Dumped by pg_dump version 18.4 (Ubuntu 18.4-1.pgdg24.04+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: book_authors; Type: TABLE DATA; Schema: public; Owner: chris +-- + +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('e9a145ac-5167-4db6-a35e-fcc34010c844', '89b1198a-6ad8-4039-942e-970ddb61514c', 0); +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('5cce3730-38e2-41f8-921a-b4139b644e3b', '89b1198a-6ad8-4039-942e-970ddb61514c', 1); +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('5cce3730-38e2-41f8-921a-b4139b644e3b', '352a696c-3dc6-4307-876e-327edf719119', 2); +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('8959975c-4f19-46ca-818e-8841e7814637', 'de9700a3-bb7d-4616-92eb-2918bc74072e', 0); +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('674e5751-a7fe-4b0e-910c-fcb348b81ab7', '5172620d-45dc-4c24-9829-512e2c900fd7', 1); +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('674e5751-a7fe-4b0e-910c-fcb348b81ab7', 'f4c56ec1-6ff4-483c-8eeb-44f391af2e57', 2); +INSERT INTO public.book_authors (book_id, author_id, author_order) VALUES ('f1f8fe28-4217-4e35-a577-853ebc5d65b9', '053d7dca-2e77-47a7-a316-a34c4090e4e0', 0); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict Ureq9Zr3sxHwBT50DepdrW70MZeA905r7amJN992A5Vy4Z7bxuljU23Ll8ow7rW + diff --git a/sql/inserts/book_publishers.sql b/sql/inserts/book_publishers.sql new file mode 100644 index 0000000..c825939 --- /dev/null +++ b/sql/inserts/book_publishers.sql @@ -0,0 +1,5 @@ +INSERT INTO public.book_publishers (book_id, publisher_id) VALUES ('e9a145ac-5167-4db6-a35e-fcc34010c844', 'eaf00b0f-b12b-4bde-8ee4-d9f320001884'); +INSERT INTO public.book_publishers (book_id, publisher_id) VALUES ('5cce3730-38e2-41f8-921a-b4139b644e3b', '83109f47-8369-4ced-9f6e-761d8c3eceb4'); +INSERT INTO public.book_publishers (book_id, publisher_id) VALUES ('8959975c-4f19-46ca-818e-8841e7814637', '83109f47-8369-4ced-9f6e-761d8c3eceb4'); +INSERT INTO public.book_publishers (book_id, publisher_id) VALUES ('f1f8fe28-4217-4e35-a577-853ebc5d65b9', 'aa69017d-b8ef-43cd-98f9-e092923f56d2'); +INSERT INTO public.book_publishers (book_id, publisher_id) VALUES ('674e5751-a7fe-4b0e-910c-fcb348b81ab7', '68fe8a8b-4dbd-4cea-a20f-11d98d3e2406'); diff --git a/sql/inserts/books.sql b/sql/inserts/books.sql new file mode 100644 index 0000000..36187b7 --- /dev/null +++ b/sql/inserts/books.sql @@ -0,0 +1,38 @@ +-- +-- PostgreSQL database dump +-- + +\restrict WW5eMeJxZE5yJ8tp2dNtUPBiQcTN2c493qw0JRhxbL3tJVqqMEbYUyfOoVJG9Nd + +-- Dumped from database version 18.4 (Ubuntu 18.4-0ubuntu0.26.04.1) +-- Dumped by pg_dump version 18.4 (Ubuntu 18.4-1.pgdg24.04+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: books; Type: TABLE DATA; Schema: public; Owner: chris +-- + +INSERT INTO public.books (id, title, description, isbn_13, format, published_year, cover_image_url, total_pages, total_audio_seconds, created_at) VALUES ('e9a145ac-5167-4db6-a35e-fcc34010c844', 'Along Came A Spider', NULL, '9781455523566', 'MASS_MARKET_PAPERBACK', 2018, NULL, 459, NULL, '2026-08-10 16:50:19.991047+00'); +INSERT INTO public.books (id, title, description, isbn_13, format, published_year, cover_image_url, total_pages, total_audio_seconds, created_at) VALUES ('5cce3730-38e2-41f8-921a-b4139b644e3b', 'Holmes, Marple & Poe', NULL, '9781538710784', 'TRADE_PAPERBACK', 2024, NULL, 355, NULL, '2026-08-10 16:50:19.997354+00'); +INSERT INTO public.books (id, title, description, isbn_13, format, published_year, cover_image_url, total_pages, total_audio_seconds, created_at) VALUES ('8959975c-4f19-46ca-818e-8841e7814637', 'Nightshade', NULL, '9780316588485', 'HARDCOVER', 2025, NULL, 341, NULL, '2026-08-10 16:50:19.999223+00'); +INSERT INTO public.books (id, title, description, isbn_13, format, published_year, cover_image_url, total_pages, total_audio_seconds, created_at) VALUES ('f1f8fe28-4217-4e35-a577-853ebc5d65b9', 'Ulysses (Gabler Edition)', NULL, '9780394743127', 'TRADE_PAPERBACK', 1986, NULL, 688, NULL, '2026-08-10 16:50:20.001004+00'); +INSERT INTO public.books (id, title, description, isbn_13, format, published_year, cover_image_url, total_pages, total_audio_seconds, created_at) VALUES ('674e5751-a7fe-4b0e-910c-fcb348b81ab7', 'Tom Clancy Terminal Velocity', NULL, '9780593178032', 'HARDCOVER', 2025, NULL, 436, NULL, '2026-08-10 16:50:20.002142+00'); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict WW5eMeJxZE5yJ8tp2dNtUPBiQcTN2c493qw0JRhxbL3tJVqqMEbYUyfOoVJG9Nd + diff --git a/sql/inserts/publishers.sql b/sql/inserts/publishers.sql new file mode 100644 index 0000000..785d61c --- /dev/null +++ b/sql/inserts/publishers.sql @@ -0,0 +1,59 @@ +-- +-- PostgreSQL database dump +-- + +\restrict efVV0qggaXPBO9j0yQizZ69vZMTzIt5CSvL3S9b44aNGom4MWxUhA2LfPMrzJVD + +-- Dumped from database version 18.4 (Ubuntu 18.4-0ubuntu0.26.04.1) +-- Dumped by pg_dump version 18.4 (Ubuntu 18.4-1.pgdg24.04+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: publishers; Type: TABLE DATA; Schema: public; Owner: chris +-- + +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('d064858f-1320-41c7-89ed-0e614a6b5275', 'HarperCollins', 'https://www.harpercollins.com/', '2026-08-10 14:35:54.165062+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('60479d8b-abcc-470e-9365-863cfdf07942', 'Penguin Random House', 'https://www.penguinrandomhouse.com/', '2026-08-10 14:35:54.166369+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('e39f16fd-4079-4e3c-9072-f4ad93333425', 'Macmillan Publishers', 'https://us.macmillan.com/', '2026-08-10 14:35:54.167056+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('bd270e3b-9126-44b7-91d2-a0112e9a3ecf', 'Scholastic Corporation', 'https://www.scholastic.com/home', '2026-08-10 14:35:54.167979+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('701ceb17-df36-4b60-88c5-688afc054412', 'Simon & Schuster', 'https://www.simonandschuster.com/', '2026-08-10 14:35:54.168658+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('7c5c52ff-ad1c-4470-8d3a-6d2303f44c2a', 'Bantam Publishing', 'https://www.bantampublishing.com/', '2026-08-10 14:35:54.169314+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('4e5412a7-1364-4838-a5c8-47fbe173cd75', 'Houghton Mifflin Harcourt', 'https://www.hmhco.com/', '2026-08-10 14:35:54.16996+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('8ff8e9d6-d1c2-4e4b-9c9a-2545e55fd4ed', 'McGraw-Hill Education', 'https://www.mheducation.com/', '2026-08-10 14:35:54.170612+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('855108e3-593b-4b2c-8eb5-43ef6835f20e', 'Pearson Education', 'https://www.pearson.com', '2026-08-10 14:35:54.171286+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('1c200f80-1316-447f-a739-66025f97184f', 'W. W. Norton & Company', 'https://wwnorton.com/', '2026-08-10 14:35:54.172011+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('83109f47-8369-4ced-9f6e-761d8c3eceb4', 'Little, Brown & Company', 'https://www.hachettebookgroup.com/imprint/little-brown-and-company/', '2026-08-10 14:35:54.172704+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('11707922-8879-455f-95c2-54cb7625a4ff', 'Kindle Direct Publishing', 'https://kdp.amazon.com/', '2026-08-10 14:35:54.173363+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('022421c9-b005-4344-99f2-8b1d2905d985', 'Barnes & Noble Press', 'https://press.barnesandnoble.com/', '2026-08-10 14:35:54.174033+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('96536c7e-ffed-4c1c-90ff-e08c32c03db4', 'Apple Books', 'https://www.apple.com/apple-books/', '2026-08-10 14:35:54.174679+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('a3dea549-d83a-49a9-951c-c3c7b114864d', 'Kobo', 'https://www.kobo.com/us/', '2026-08-10 14:35:54.175326+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('61ae9190-18c9-4a20-b55b-ba199d7e7275', 'Candlewick Press', 'https://www.candlewick.com/', '2026-08-10 14:35:54.17594+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('34d9ad56-c90c-44b2-9fae-db53bca9ea20', 'Chronicle Books', 'https://www.chroniclebooks.com/', '2026-08-10 14:35:54.176581+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('eaf00b0f-b12b-4bde-8ee4-d9f320001884', 'Grand Central Publishing', 'https://www.hachettebookgroup.com/imprint/grand-central-publishing/', '2026-08-10 14:35:54.177231+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('aa69017d-b8ef-43cd-98f9-e092923f56d2', 'Vintage', 'https://knopfdoubleday.com/imprint/vintage/', '2026-08-10 14:35:54.177893+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('68fe8a8b-4dbd-4cea-a20f-11d98d3e2406', 'G. P. Putnam''s Sons', 'https://www.penguin.com/putnam/', '2026-08-10 14:35:54.178535+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('f19a0067-763a-487b-accd-34dd26113004', 'Lagardère SA', 'https://www.lagardere.com/en/home/', '2026-08-10 14:35:54.17917+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('a49a6faf-f0ca-4c5c-afa7-59ef8cb656ce', 'Hachette Livre', 'https://www.hachette.com/', '2026-08-10 14:35:54.179806+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('32827e5b-bcf5-4e35-a230-ae3477eabcf5', 'Le Livre de Poche', 'https://www.livredepoche.com/', '2026-08-10 14:35:54.180399+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('041ccaa3-0f8e-4d3f-be8a-9768cc019d95', 'O''Reilly Media', 'https://www.oreilly.com/', '2026-08-10 14:35:54.181053+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('d7ff0668-5ef3-4729-ae9e-4db180f61eb4', 'Knopf Doubleday', 'https://knopfdoubleday.com/', '2026-08-10 14:37:33.982413+00'); +INSERT INTO public.publishers (id, name, website, created_at) VALUES ('3acf87c5-ffb3-4dc0-95b1-15f78d977111', 'Hachette Book Group', 'https://www.hachettebookgroup.com/', '2026-08-10 14:37:33.983517+00'); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict efVV0qggaXPBO9j0yQizZ69vZMTzIt5CSvL3S9b44aNGom4MWxUhA2LfPMrzJVD + diff --git a/sql/inserts/users.sql b/sql/inserts/users.sql new file mode 100644 index 0000000..786b7bc --- /dev/null +++ b/sql/inserts/users.sql @@ -0,0 +1,36 @@ +-- +-- PostgreSQL database dump +-- + +\restrict qlbEiTLcs0n6OfMTYScKDUs7xlFe5bcBUt3DUt2BlNqvC9jTbGl08Uh5r9iJzrT + +-- Dumped from database version 18.4 (Ubuntu 18.4-1.pgdg24.04+1) +-- Dumped by pg_dump version 18.4 (Ubuntu 18.4-1.pgdg24.04+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: tomes_user +-- + +INSERT INTO public.users (id, email, username, display_name, role, tier, created_at, is_enabled) VALUES ('88c325e2-d1de-430f-b4a5-96c9c621845d', 'ole088@gmail.com', 'chris', 'Chris', 'ADMIN', 'PREMIUM', '2026-07-27 22:00:25.987', true); +INSERT INTO public.users (id, email, username, display_name, role, tier, created_at, is_enabled) VALUES ('12e90f24-7d06-470e-a2c7-39319b981bcc', 'natashastales@gmail.com', 'natasha', 'Natasha', 'AUTHOR', 'PREMIUM', '2026-07-27 22:00:26.033', true); +INSERT INTO public.users (id, email, username, display_name, role, tier, created_at, is_enabled) VALUES ('4a749c17-68cb-43e5-aad0-75cda6ca8734', 'chimaia6454@gmail.com', 'chiara', 'Chi Chi', 'USER', 'FREE', '2026-07-27 22:00:26.039', true); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict qlbEiTLcs0n6OfMTYScKDUs7xlFe5bcBUt3DUt2BlNqvC9jTbGl08Uh5r9iJzrT + diff --git a/sum.sh b/sum.sh new file mode 100644 index 0000000..0982e94 --- /dev/null +++ b/sum.sh @@ -0,0 +1,19 @@ +# Original sum function, only allows for two values +orig_sum() { + local value1=$1 + local value2=$2 + local result=$(( value1 + value2 )) + echo "The sum of ${value1} and ${value2} is ${result}" +} + +# Improved sum function that allows as many as required +sum() { + local total=0 + for value in $@; do + total=$(( total + value )) + done + + # Write the result + echo "Summed $# values for a total of: ${total}" +} + diff --git a/test_ansi.sh b/test_ansi.sh new file mode 100755 index 0000000..2590800 --- /dev/null +++ b/test_ansi.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Add color codes +source ./ansi_colors.sh + +message="This is a simple message" + +echo -e "Foreground Red, Background White: ${FG_RED}${BG_WHITE}${message}${RESET}" diff --git a/today.sh b/today.sh new file mode 100755 index 0000000..9ad2d0c --- /dev/null +++ b/today.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +# Exit if any command fails and enable tracing +set -ex + +# Get today's date in the format YYYY-MM-DD +today=$(date +"%Y-%m-%d") + +# Create a path to today's temp folder and then make sure the folder exists +temp_path="/tmp/${today}" +mkdir -p "${temp_path}" + +# Now make a symlink to the folder in your homedir +ln -sf "${temp_path}" "${HOME}/today" + +# Disable tracing +set +x + +# Write out the path you created +echo "${temp_path}" + diff --git a/winapps_installer.sh b/winapps_installer.sh new file mode 100755 index 0000000..b947aed --- /dev/null +++ b/winapps_installer.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# Run the online script to install WinApps in the current user profile +bash <(curl https://raw.githubusercontent.com/winapps-org/winapps/main/setup.sh) +