From 97a99d0534d718bf88fc3cf85253633e8fe7deed Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:09:34 +0200 Subject: [PATCH 1/8] Feat: Introduce `bash-snippets.lib` library Move redundant function and variables into a shared bash script library file `bash-snippets.lib`. This allows the individual scripts to be more focused and maintainable. --- bash-snippets.lib | 268 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100755 bash-snippets.lib diff --git a/bash-snippets.lib b/bash-snippets.lib new file mode 100755 index 0000000..9f7d3e7 --- /dev/null +++ b/bash-snippets.lib @@ -0,0 +1,268 @@ +#!/usr/bin/env bash +# bash-snippets.lib -- Bash-Snippets library +# +# Author: Essam 'https://github.com/e55am' +# Repo: https://github.com/alexanderepstein/Bash-Snippets + + +### Globals variables +PROGRAM="${0##*/}" # bash version of `basename' +VERSION="$PROGRAM v1.23.0" +UPSTREAM="https://github.com/alexanderepstein/Bash-Snippets" +declare -i USAGE_LINE_COUNT=1 # number of lines from Help represent usage + + +### Function declaration +## library function prefixed with underscore '_' and in Camel_Case +## use two blank lines and a function separator between each function declaration +## each function declaration should have the following doc +# . brief description +# . Globals: global variables function relays on +# . Inputs: argument(s) passed, else nothing +# . Outputs: write to file, modifies global variable, else nothing +# . Returns: function return(s) value, else nothing +# each function should followed by `end of function FUNC_NAME' +# +# +# +# check if user have root permission +# +# Globals: nothing +# Inputs: nothing +# Outputs: nothing +# +# Returns: +# 0 (success) if root +# 1 (error) if non root +_Check_User_Perm() { + [[ $EUID -eq 0 ]] +} # end of function _Check_User_Perm + + +# check internet connection +# Globals: PROGRAM +# Inputs: nothing +# Outputs: nothing +# +# Returns: +# 0 (success) on active internet connection +# 1 (error) if no active internet connection and exit +_Check_Internet() { + + ping -c 1 "example.org" &> /dev/null || { + _Echo_Err "$PROGRAM: no active internet connection" + exit 1 + } + +} # end of function _Check_Internet + + +# check if dependencies installed, abort with error if any missing +# exit with error if any dependencies missing +# +# Globals: +# PROGRAM +# +# Inputs: +# dependency: array of string dependencies +# +# Outputs: nothing +# +# Returns: +# 0 (true): if all dependencies met +# 1 (false): on error and terminate the program +_Check_If_All_Dep() { + missing_dep="" + + for dependency in "$@"; do + command -v "$dependency" &> /dev/null || { + missing_dep="$missing_dep, $dependency" + } + done + + [[ -n "$missing_dep" ]] && { + _Echo_Err "$PROGRAM: unmet dependencies: '$missing_dep' not found" + _Echo_Err "Abort" + exit 1 + } + return 0 + +} # end of _Check_If_All_Dep + + +# check if any of the dependencies installed, return first one +# exit with error if any dependency found +# +# Globals: +# PROGRAM +# +# Inputs: +# dependency: array of string dependencies +# +# Outputs: nothing +# +# Returns: +# dependency: first installed dependency met +# 1 (error) if any dependency met and terminate the program +_Check_For_Any_Dep() { + + available_dep= + missing_dep= + + for dependency in "$@"; do + if command -v "$dependency" &> /dev/null; then + available_dep="$dependency" + break + else + missing_dep="$missing_dep, $dependency" + fi + done + + [[ -z "$available_dep" ]] && { + # remove first delimiter `, ' from missing_dep string + missing_dep="${missing_dep:2:${#missing_dep}}" # slice from second char to the end + _Echo_Err "$PROGRAM: could not find any of the following dependencies '$missing_dep'" + _Echo_Err "Abort" + exit 1 + } + echo "$available_dep" + +} # end of function _Check_For_Any_Dep + + +# set the default command that will be used to make HTTP GET request +# if no client found, exit with error +# +# Globals: +# PROGRAM +# +# Inputs: nothing +# +# Outputs: +# _HTTP_GET function +# +# Returns: +# 1 (false) if no client installed and terminate the program +_Config_HTTP_Client() { + + dependency=$(_Check_For_Any_Dep "curl" "wget" "http" "fetch") + case $dependency in + curl ) + _HTTP_GET() { curl --user-agent curl --silent "$*"; } + ;; + + wget ) + _HTTP_GET() { wget --quiet --output-document - "$*"; } + ;; + + http ) + _HTTP_GET() { http -b GET "$*"; } + ;; + + # TODO + # remove this case + * ) + _Echo_Err "$PROGRAM: missing dependency: curl, wget, httpie or fetch not found!" + exit 1 + ;; + esac + +} # end of function _Config_HTTP_Client + + +# display try help message, used after user error +# +# Globals: +# PROGRAM +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +_Try_Help() { + echo "Try '$PROGRAM -h' for more information" +} >&2 # end of function try_help + + +# print string to stderr +# +# Globals: nothing +# +# Inputs: +# string +# +# Outputs: nothing +# Returns: nothing +_Echo_Err() { + echo "$*" +} >&2 # end of function _Echo_Err + + +# extract version from VERSION variable +# +# Globals: +# VERSION +# +# Inputs: nothing +# Outputs: nothing +# +# Returns: +# version string +_Extract_Version() { + + echo "$VERSION" | grep --perl-regexp --only-matching '\d+.\d+.\d+' + +} # end of function _Extract_Version + + +# TODO +# implement +# update bash-snippets scripts/tools +_Update() { + + _Echo_Err "$PROGRAM: update functionality is not implement it yet" + exit 127 +} # end of function _Update + + +# access property from json (response) +# +# Globals: nothing +# +# Inputs: +# data: json data +# filter: filter string (read man 1 jq) for more information) +# +# Outputs: nothing +# +# Returns: +# empty string if filter not found in data, else matched data +_Access_Json_Element() { + + data="$1" + filter="$2" + + res=$(echo "$data" | jq --raw-output --exit-status "$filter") + [[ $? -eq 0 ]] && echo "$res" || echo + +} # end of function _Access_Json_Element + + +# Print first n lines from help message +# +# Globals: +# USAGE_LINE_COUNT +# +# Inputs: nothing +# +# Outputs: +# brief help message to stdout +# +# Returns: nothing +_Usage() { + USAGE_LINE_COUNT=${USAGE_LINE_COUNT:-1} + Help | head -n $USAGE_LINE_COUNT + +} # End of function _Usage + + +_Config_HTTP_Client From 68251beb6e698480732e608e402eb29bb25b46c6 Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:54:43 +0200 Subject: [PATCH 2/8] Refactor: Integrate `bash-snippets.lib` into `install.sh` - `bash-snippets.lib` is a dependency for the installer. - The installer now installs the shared library as part of its process. - Implemented command line option parsing in the installer. --- install.sh | 381 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 268 insertions(+), 113 deletions(-) diff --git a/install.sh b/install.sh index 7509e37..757097e 100755 --- a/install.sh +++ b/install.sh @@ -1,131 +1,286 @@ #!/usr/bin/env bash -# Author: Alexander Epstein https://github.com/alexanderepstein -currentVersion="1.23.0" +# Author: Alexander 'Epstein https://github.com/alexanderepstein' +# Refactor: e55am 'https://github.com/e55am' + +# TODO +# tools array should be populated with `find scripts -type -f -executable` + +# exit on error +set -e + +### Global vars +# DIRECTORY var will make sure wherever user CWD, installer will work +DIRECTORY="${0%/*}" # current repo directory +PROGRAM="${0##*/}" # bash version of basename +LIB="$DIRECTORY/bash-snippets.lib" +DETAIL=false + +prefix="/usr/local" +bin_path="$prefix/bin" +man_path="$prefix/share/man/man1" +interactive=false # prompt before each install +all=false # install all tools + +# TODO +# implement platform check on script/tool level will result cleaner install script +# (e.g. abort from program if platform incompatible) declare -a tools=(bak2dvd bash-snippets cheat cloudup crypt cryptocurrency currency geo gist lyrics meme movies newton pwned qrify short siteciphers stocks taste todo transfer weather ytview) declare -a extraLinuxTools=(maps) declare -a extraDarwinTools usedGithubInstallMethod="0" -prefix="/usr/local" -askInstall() -{ - read -p "Do you wish to install $1 [Y/n]: " answer - answer=${answer:-Y} - - if [[ "$answer" == [Yy] ]]; then - cd "$1" || return 1 - echo -n "Installing $1: " - chmod a+x "$1" - cp "$1" /usr/local/bin > /dev/null 2>&1 || { echo "Failure"; echo "Error copying file, try running install script as sudo"; exit 1; } - echo "Success" - cd .. || return 1 - fi -} -updateTool() -{ - if [[ -f /usr/local/bin/$1 ]]; then - usedGithubInstallMethod="1" - cd "$1" || return 1 - echo -n "Installing $1: " - chmod a+x "$1" - cp "$1" /usr/local/bin > /dev/null 2>&1 || { echo "Failure"; echo "Error copying file, try running install script as sudo"; exit 1; } - echo "Success" - cd .. || return 1 - fi +# source library, and exit on error +source "$LIB" || { + echo "$PROGRAM: failed to source '$LIB' library" >&2 + exit 3 } -extraUpdateTool() -{ - if [[ -f /usr/local/bin/$1 ]]; then - usedGithubInstallMethod="1" - cd extras || return 1 - cd "$2" || return 1 - cd "$1" || return 1 - echo -n "Installing $1: " - chmod a+x "$1" - cp "$1" /usr/local/bin > /dev/null 2>&1 || { echo "Failure"; echo "Error copying file, try running install script as sudo"; exit 1; } - echo "Success" - cd .. || return 1 - cd .. || return 1 - cd .. || return 1 - fi + +# print help message +# +# Globals: +# PROGRAM +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +Help() { + cat <<- EOF + $PROGRAM [-i|--interactive] [-p|--prefix DIRECTORY] [-h|-u|-U|-v|-V] {all,TOOL ...} + install all/some of bash-snippets tools + + Option + -h, --help display this help and exit + -i, --interactive prompt before install tool + -p DIR, --prefix DIR + install Bash-Snippets tool(s) under DIR (default is /usr/local) + -u, --usage display synopsis/brief usage and exit + -U, --update update Bash-Snippets + -V, --version print version and exit + EOF + +} # end of function Help + + +# conform user choice +# +# Globals: nothing +# Inputs: +# tool name +# Outputs: nothing +# +# Returns: +# 0 (true) if user choice was 'Y' or 'y' (default Y) +# non-zero (false) otherwise +Confirm_Install() { + + local tool="$1" + + echo "The following tool will be installed: $tool" + read -p "Do you want to continue? [Y/n] " answer + answer="${answer:-Y}" + + [[ "$answer" == [Yy] ]] + +} # end of function Confirm_Install + + +# copy tool to bin_path directory +# +# Globals: +# PROGRAM +# +# Inputs: +# tool: tool name +# +# Outputs: nothing +# Returns: nothing +Install_Tool() { + + local tool="$1" + + # TODO + # - replace cd `DIRECTORY/tool` with `DIRECTORY/scripts` + cd "$DIRECTORY/$tool" + echo "Installing $tool..." + chmod a+x "$tool" + cp "$tool" -t "$bin_path" &> /dev/null || { + _Echo_Err "$PROGRAM: failed to copy '$tool' to '$bin_path'" + } + echo "$tool installed" + cd .. # to repo directory + +} # end of function Install_Tool + + +# copy all tools to bin_path +# +# Globals: +# tools +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +Install_All_Tools() { + + for tool in "${tools[@]}"; do + if $interactive; then + (Confirm_Install "$tool") && Install_Tool "$tool" + else + Install_Tool "$tool" + fi + done + +} # end of function Install_All_Tools + + +# copy Man page to man_path +# +# Globals: +# PROGRAM +# man_path +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +Copy_Man_Page() { + + cp "$DIRECTORY/bash-snippets.1" -t "$man_path" || { + _Echo_Err "$PROGRAM: failed to copy man page" + } + +} # end of function Copy_Man_Page + + +# parse cli option +while getopts ':-:ahip:uUV' opts; do + case "$opts" in + a ) all=true ;; + h ) Help; exit 0 ;; + i ) interactive=true ;; + p ) + prefix="$OPTARG" + bin_path="$prefix/bin" + man_path="$prefix/share/man/man1" + ;; + + u ) _Usage; exit 0 ;; + U ) _Update ;; + V ) echo "$VERSION"; exit 0 ;; + + # parse long option-------------------------------------- + - ) + case "$OPTARG" in + all ) all=true ;; + help ) + Help + exit 0 + ;; + + interactive ) interactive=true ;; + prefix ) + shift + prefix="$1" + bin_path="$prefix/bin" + man_path="$prefix/share/man/man1" + + ## TODO + ## move the following block into function and test it + # + # # check if argument is path + # if [[ $prefix =~ ^(\.|\.\.)?/?.* ]]; then + # bin_path="$prefix/bin" + # man_path="$prefix/share/man/man1" + # else + # echo "$PROGRAM: invalid path" + # exit 1 + # fi + ;; + + update ) _Update ;; + usage ) _Usage; exit 0 ;; + version ) echo "$VERSION"; exit 0 ;; + * ) + _Echo_Err "$PROGRAM: Invalid option '--$OPTARG'" + exit 1 + ;; + esac + ;; + #-------------------------------------------------------- + + : ) + _Echo_Err "Option '-$OPTARG' requires an argument." + _Try_Help + exit 1 + ;; + + \?) + _Echo_Err "$PROGRAM: Invalid option '-$OPTARG'" + _Try_Help + exit 1 + ;; + esac +done +shift $(( OPTIND - 1 )) + + +## check user permission +[[ -d $prefix ]] || { + _Echo_Err "$PROGRAM: '$prefix' is not directory" + exit 1 } -singleInstall() -{ - cd "$1" || exit 1 - echo -n "Installing $1: " - chmod a+x "$1" - cp "$1" $prefix/bin > /dev/null 2>&1 || { echo "Failure"; echo "Error copying file, try running install script as sudo"; exit 1; } - echo "Success" - cd .. || exit 1 +[[ -w $prefix && -x $prefix ]] || { + _Echo_Err "$PROGRAM: cannot install/update: permission denied" + exit 2 } -copyManpage() -{ - manPath="$prefix/share/man/man1" - if [ -f "$prefix/man/man1/bash-snippets.1" ]; then rm -f "$prefix/man/man1/bash-snippets.1"; fi - cp bash-snippets.1 $manPath 2>&1 || { echo "Failure"; echo "Error copying file, try running install script as sudo"; exit 1; } +[[ $# == 0 ]] && { + (Confirm_Install "all ${#tools[@]} tools") && all=true } -response=$( echo "$@" | grep -Eo "\-\-prefix") - -if [[ $response == "--prefix" ]]; then - prefix=$(echo -n "$@" | sed -e 's/--prefix=\(.*\) .*/\1/' | cut -d " " -f 1) - mkdir -p "$prefix"/bin "$prefix"/share/man/man1 - if [[ $2 == "all" ]];then - for tool in "${tools[@]}"; do - singleInstall "$tool" || exit 1 - done - else - for tool in "${@:2}"; do - singleInstall "$tool" || exit 1 - done - fi - copyManpage || exit 1 -elif [[ $# == 0 ]]; then - for tool in "${tools[@]}"; do - askInstall "$tool" || exit 1 - done - copyManpage || exit 1 -elif [[ $1 == "update" ]]; then - echo "Updating scripts..." - for tool in "${tools[@]}"; do - updateTool "$tool" || exit 1 - done - if [[ $(uname -s) == "Linux" ]]; then - for tool in "${extraLinuxTools[@]}"; do - extraUpdateTool "$tool" Linux || exit 1 - done - fi - if [[ $(uname) == "Darwin" ]];then - for tool in "${extraDarwinTools[@]}"; do - extraUpdateTool "$tool" Darwin || exit 1 - done - fi - if [[ $usedGithubInstallMethod == "1" ]]; then - copyManpage || exit 1 - else - echo "It appears you have installed bash-snippets through a package manager, you must update it with the respective package manager." - exit 1 - fi -elif [[ $1 == "all" ]]; then - for tool in "${tools[@]}"; do - singleInstall "$tool" || exit 1 - done - copyManpage || exit 1 +for i in "$bin_path" "$man_path"; do + [[ -d "$i" ]] || mkdir -p "$i" +done + + +# TODO +# once we place all script in one directory call Install_Tool function +# library is necessary dependency +cp "$LIB" -t "$bin_path" +Copy_Man_Page + +if $all; then + Install_All_Tools else - singleInstall "$1" || exit 1 - copyManpage || exit 1 + for tool in "$@"; do + # check if tool name is valid + if [[ "${tools[*]}" == *$tool* ]]; then + Install_Tool "$tool" + else + _Echo_Err "E: cannot install $tool: no such tool" + _Echo_Err "skipping $tool" + fi + done fi -echo -n "( •_•)" -sleep .75 -echo -n -e "\r( •_•)>⌐■-■" -sleep .75 -echo -n -e "\r " -echo -e "\r(⌐■_■)" -sleep .5 -echo "Bash Snippets version $currentVersion" -echo "https://github.com/alexanderepstein/Bash-Snippets" + +# NOTE +# currently i have no idea how apt install this package from GitHub (e.g. apt internal work) +# feel free to remove/edit the following block +# if [[ $usedGithubInstallMethod == "1" ]]; then +# Copy_Man_Page +# else +# +# cat <<- EOF +# $PROGRAM: warning: It appears you have installed bash-snippets through a package manager, +# you must update it with the respective package manager. +# EOF +# exit 1 +# fi + +echo "Bash Snippets version $VERSION" +echo "https://github.com/alexanderepstein/Bash-Snippets" +exit 0 + From 053c0fc424f3c106ca3dd26da561d5df277a2030 Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Thu, 23 Jan 2025 21:09:59 +0200 Subject: [PATCH 3/8] Add style guide section to `CONTRIBUTING.md` Included guidelines for code style and formatting to ensure consistency across contributions. --- CONTRIBUTING.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b82d5a..9d1dacb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ If you've noticed a bug or have a question, search the [!NOTE] +> - By default variables in bash are global, except when you declare them otherwise with `local bar` or `declare bar` +> - Please use tabs instead of whitespace for indentation + From 0bdd6b69f50c5e375984388f6c9644bdbae6902d Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:04:58 +0200 Subject: [PATCH 4/8] Refactor: Integrate `bash-snippets.lib` into `movies` - Parse short and long command line options - API response processed with `jq` --- movies/movies | 470 +++++++++++++++++++++++++------------------------- 1 file changed, 232 insertions(+), 238 deletions(-) diff --git a/movies/movies b/movies/movies index be55fbf..3483ed0 100755 --- a/movies/movies +++ b/movies/movies @@ -1,252 +1,246 @@ #!/usr/bin/env bash -# Author: Alexander Epstein https://github.com/alexanderepstein - -currentVersion="1.23.0" -configuredClient="" -configuredPython="" -detail=false - -## This function determines which http get tool the system has installed and returns an error if there isnt one -getConfiguredClient() -{ - if command -v curl &>/dev/null; then - configuredClient="curl" - elif command -v wget &>/dev/null; then - configuredClient="wget" - elif command -v http &>/dev/null; then - configuredClient="httpie" - elif command -v fetch &>/dev/null; then - configuredClient="fetch" - else - echo "Error: This tool requires either curl, wget, httpie or fetch to be installed." >&2 - return 1 - fi -} +# Author: -## Allows to call the users configured client without if statements everywhere -httpGet() -{ - case "$configuredClient" in - curl) curl -A curl -s "$@" ;; - wget) wget -qO- "$@" ;; - httpie) http -b GET "$@" ;; - fetch) fetch -q "$@" ;; - esac -} +### Global vars +# Get the directory path where the current script is located +DIRECTORY="${0%/*}" +LIB="bash-snippets.lib" -getConfiguredPython() -{ - if command -v python3 &>/dev/null; then - configuredPython="python3" - elif command -v python2 &>/dev/null; then - configuredPython="python2" - elif command -v python &>/dev/null; then - configuredPython="python" - else - echo "Error: This tool requires python to be installed." - return 1 - fi -} +DETAIL=false # detailed output flag +ERR=0 # exit status +KEY_LEN=0 # longest key string length +VAL_LEN=0 +NCOL=80 # output line width -if [[ $(uname) != "Darwin" ]]; then - python() - { - case "$configuredPython" in - python3) python3 "$@" ;; - python2) python2 "$@" ;; - python) python "$@" ;; - esac - } -fi - -## Grabs an element from a a json string and then echoes it to stdout -## $1 = the JSON string -## $n+1 = the elements to be indexed -AccessJsonElement() { - json="$1" - shift - accessor="" - for element in "$@"; do - accessor="${accessor}['$element']" - done - echo "$json" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)${accessor})" 2> /dev/null - return "$?" -} -checkInternet() -{ - httpGet github.com > /dev/null 2>&1 || { echo "Error: no active internet connection" >&2; return 1; } # query github with a get request +# source library, and exit on error +source "$DIRECTORY/$LIB" || { + # don't use _Echo_Err; library not sourced! + echo "$PROGRAM: failed to source '$LIB' library" >&2 + exit 3 } -## This function grabs information about a movie and using python parses the -## JSON response to extrapolate the information for storage -getMovieInfo() -{ - apiKey=e3aeac39 # try not to abuse this it is a key that came from the ruby-scripts repo I link to. - movie=$( (echo "$@" | tr " " + ) | sed 's/-d+//g' ) ## format the inputs to use for the api. Added sed command to filter -d flag. - export PYTHONIOENCODING=utf8 #necessary for python in some cases - movieInfo=$(httpGet "http://www.omdbapi.com/?t=$movie&apikey=$apiKey") > /dev/null # query the server and get the JSON response - - ## check to see if the movie was found - checkResponse=$(echo "$movieInfo" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)['Response'])" 2> /dev/null) - if [[ $checkResponse == "False" ]]; then - echo "$movieInfo" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)['Error'])" 2> /dev/null - return 1 - fi - - # The rest of the code is just extrapolating the data with python from the JSON response - title="$(AccessJsonElement "$movieInfo" "Title")" - year="$(AccessJsonElement "$movieInfo" "Year")" - runtime="$(AccessJsonElement "$movieInfo" "Runtime")" - imdbScore=$(echo "$movieInfo" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)['Ratings'][0]['Value'])" 2> /dev/null) - tomatoScore=$(echo "$movieInfo" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)['Ratings'][1]['Value'])" 2> /dev/null) - rated="$(AccessJsonElement "$movieInfo" "Rated")" - genre="$(AccessJsonElement "$movieInfo" "Genre")" - director="$(AccessJsonElement "$movieInfo" "Director")" - actors="$(AccessJsonElement "$movieInfo" "Actors")" - plot="$(AccessJsonElement "$movieInfo" "Plot")" - - if $detail; then - awards="$(AccessJsonElement "$movieInfo" "Awards")" - boxOffice="$(AccessJsonElement "$movieInfo" "BoxOffice")" - metacriticScore=$(echo "$movieInfo" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)['Ratings'][2]['Value'])" 2> /dev/null) - production="$(AccessJsonElement "$movieInfo" "Production")" - fi -} -# Print key: value info -printKV() { - key=$1 - val=$2 - ROW="|%11s: %-40s|\n" - WIDTH=53 - - # If value too long (greater then 43 char), split it into several line - if [[ ${#val} -le $((WIDTH - 10)) ]]; then - printf "$ROW" "$key" "$val" - else - printf "$ROW" "$key" - printf "%s\n" "$val" | fmt -w 50 \ - | while IFS= read -r line; do printf "| %-50s |\n" "$line"; done - fi -} +# request movie info from OMDB API +# +# Globals: +# DETAIL +# ERR: set error to 1 on error (e.g title not found) +# +# Inputs: +# title +# +# Outputs: +# keys: array contain key string +# values: array contains values corresponded to keys +# +# Return: nothing +Get_Info() { + + # we used two arrays instead of associate array because associate array are unsorted + declare -g -a keys values + + title="$(echo "$*" | tr ' ' '+')" -# Prints the movie information out in a human readable format -printMovieInfo() -{ - echo - echo '+=====================================================+' - printKV "Title" "$title" - printKV "Year" "$year" - printKV "Runtime" "$runtime" - if [[ $imdbScore != "" ]]; then printKV "IMDB" "$imdbScore"; fi - if [[ $tomatoScore != "" ]]; then printKV "Tomato" "$tomatoScore"; fi - if $detail; then - if [[ $metacriticScore != "" ]]; then printKV "Metascore" "$metacriticScore"; fi - fi - if [[ $rated != "N/A" && $rated != "" ]]; then printKV "Rated" "$rated"; fi - printKV "Genre" "$genre" - printKV "Director" "$director" - printKV "Actors" "$actors" - if [[ $plot != "N/A" && $plot != "" ]]; then printKV "Plot" "$plot"; fi - if $detail; then - if [[ $boxOffice != "" ]]; then printKV "BoxOffice" "$boxOffice"; fi - if [[ $production != "" ]]; then printKV "Production" "$production"; fi - if [[ $awards != "" ]]; then printKV "Awards" "$awards"; fi - fi - echo '+=====================================================+' - echo -} + # please consider using your own API key + API_KEY="e3aeac39" + PARAMETER="t=$title&apikey=$API_KEY" + URL="http://www.omdbapi.com/?$PARAMETER" + + response="$(_HTTP_GET "$URL")" + + # check api response, if response was false, Print Error and exit + check_res=$(_Access_Json_Element "$response" ".Response") + if [[ "$check_res" == "False" ]]; then + _Echo_Err $(Print_KV "Error" "$(_Access_Json_Element "$response" ".Error")") + ERR=1 + return + fi + + keys+=('title') + values+=("$(_Access_Json_Element "$response" ".Title")") + + keys+=('year') + values+=("$(_Access_Json_Element "$response" ".Year")") + + keys+=('runtime') + values+=("$(_Access_Json_Element "$response" ".Runtime")") + + keys+=('imdb_rating') + values+=("$(_Access_Json_Element "$response" ".imdbRating")") + + keys+=('rotten_tomatoes_rating') + values+=("$(_Access_Json_Element "$response" ".Ratings[1].Value")") + + keys+=('rated') + values+=("$(_Access_Json_Element "$response" ".Rated")") + + keys+=('genre') + values+=("$(_Access_Json_Element "$response" ".Genre")") + + keys+=('director') + values+=("$(_Access_Json_Element "$response" ".Director")") + + keys+=('actor') + values+=("$(_Access_Json_Element "$response" ".Actors")") + + keys+=('plot') + values+=("$(_Access_Json_Element "$response" ".Plot")") + + if $DETAIL; then + keys+=('awards') + values+=("$(_Access_Json_Element "$response" ".Awards")") + + keys+=('box_office') + values+=("$(_Access_Json_Element "$response" ".BoxOffice")") + + keys+=('metacritic_rating') + values+=("$(_Access_Json_Element "$response" ".Ratings[2].Value")") + + keys+=('production') + values+=("$(_Access_Json_Element "$response" ".Production")") + fi + + Print_Info + +} # end of function Get_Info + + +# print 'key: value' pair information +# +# Globals: +# KEY_LEN +# VAL_LEN +# NCOL +# +# Inputs: +# key +# value +# +# Outputs: nothing +# Returns: nothing +Print_KV() { + + key="$1" + val="$2" + + line_format="%${KEY_LEN}s %-${NCOL}s\n" + + # API response may contain 'null' as string or 'N/A', skip it + [[ "${val,,}" == "null" || "${val^^}" == "N/A" || -z "$val" ]] && return + + if [[ ${#val} -le $VAL_LEN ]]; then + printf "$line_format" "$key:" "$val" + + else + printf "$line_format" "$key:" ' ' + while IFS= read -r line; do + printf "$line_format" ' ' "$line" + done < <(echo "$val" | fmt -w $VAL_LEN) + fi + +} # end of function Print_KV + + +# prints title information +# +# Global: +# keys +# values +# +# Input: nothing +# Output: +# KEY_LEN +# VAL_LEN +# +# Returns: nothing +Print_Info() { + + # get the length of longest key + for key in "${keys[@]}"; do + len=${#key} + (( len > KEY_LEN )) && KEY_LEN=$len + done + KEY_LEN=$(( $KEY_LEN + 2 )) + VAL_LEN=$(( NCOL - KEY_LEN )) + + for i in ${!keys[@]}; do + key=${keys[$i]} + key="${key^}" # convert first character to upper case + key="${key//_/ }" # replace all under score with space + Print_KV "$key" "${values[$i]}" + # paste -d ":\t" <(printf '%s\n' "${keys[@]}") <(printf '%s\n' "${values[@]}") + done + +} # end of function Print_Info + + +# print help message +# +# Globals: +# PROGRAM +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +Help() { + + cat <<- EOF + usage: $PROGRAM [OPTION] TITLE ... + Retrieve relevant information about a certain title. + + Option + -d, --detail show detailed information + -h, --help display help message and exit + -u, --usage display brief usage and exit + -V, --version display tool version and exit + EOF +} # end of function Help + + +### Parse cli option +while getopts ':-:dhuV' opt; do + case "$opt" in + d ) DETAIL=true ;; + h ) Help; exit 0 ;; + u ) _Usage; exit 0 ;; + V ) echo "$VERSION"; exit 0 ;; + + ### parse long option + - ) + case "$OPTARG" in + detail ) DETAIL=true ;; + help ) Help; exit 0 ;; + usage ) _Usage; exit 0 ;; + version ) echo "$VERSION"; exit 0 ;; + * ) + _Echo_Err "$PROGRAM: Invalid option '--$OPTARG'" + _Try_Help + exit 1 + ;; + esac + ;; + + * ) + _Echo_Err "$PROGRAM: Invalid option '-$OPTARG'" + _Try_Help + exit 1 + ;; + esac +done +shift $(( OPTIND - 1 )) -update() -{ - # Author: Alexander Epstein https://github.com/alexanderepstein - # Update utility version 2.2.0 - # To test the tool enter in the defualt values that are in the examples for each variable - repositoryName="Bash-Snippets" #Name of repostiory to be updated ex. Sandman-Lite - githubUserName="alexanderepstein" #username that hosts the repostiory ex. alexanderepstein - nameOfInstallFile="install.sh" # change this if the installer file has a different name be sure to include file extension if there is one - latestVersion=$(httpGet https://api.github.com/repos/$githubUserName/$repositoryName/tags | grep -Eo '"name":.*?[^\\]",'| head -1 | grep -Eo "[0-9.]+" ) #always grabs the tag without the v option - - if [[ $currentVersion == "" || $repositoryName == "" || $githubUserName == "" || $nameOfInstallFile == "" ]]; then - echo "Error: update utility has not been configured correctly." >&2 - exit 1 - elif [[ $latestVersion == "" ]]; then - echo "Error: no active internet connection" >&2 - exit 1 - else - if [[ "$latestVersion" != "$currentVersion" ]]; then - echo "Version $latestVersion available" - echo -n "Do you wish to update $repositoryName [Y/n]: " - read -r answer - if [[ "$answer" == [Yy] ]]; then - cd ~ || { echo 'Update Failed'; exit 1; } - if [[ -d ~/$repositoryName ]]; then rm -r -f $repositoryName || { echo "Permissions Error: try running the update as sudo"; exit 1; } ; fi - echo -n "Downloading latest version of: $repositoryName." - git clone -q "https://github.com/$githubUserName/$repositoryName" && touch .BSnippetsHiddenFile || { echo "Failure!"; exit 1; } & - while [ ! -f .BSnippetsHiddenFile ]; do { echo -n "."; sleep 2; };done - rm -f .BSnippetsHiddenFile - echo "Success!" - cd $repositoryName || { echo 'Update Failed'; exit 1; } - git checkout "v$latestVersion" 2> /dev/null || git checkout "$latestVersion" 2> /dev/null || echo "Couldn't git checkout to stable release, updating to latest commit." - chmod a+x install.sh #this might be necessary in your case but wasnt in mine. - ./$nameOfInstallFile "update" || exit 1 - cd .. - rm -r -f $repositoryName || { echo "Permissions Error: update succesfull but cannot delete temp files located at ~/$repositoryName delete this directory with sudo"; exit 1; } - else - exit 1 - fi - else - echo "$repositoryName is already the latest version" - fi - fi -} -usage() -{ - cat <&2 - exit 1 ;; - *) exit 1 ;; - esac -done +_Check_If_All_Dep "jq" "fmt" +_Check_Internet + +for title in "$@"; do Get_Info "$title"; done + +exit $ERR -if [[ $# == 0 ]]; then - usage -elif [[ $1 == "update" ]]; then - checkInternet || exit 1 # check if we have a valid internet connection if this isnt true the rest of the script will not work so stop here - update -elif [[ $1 == "help" ]]; then - usage -else - checkInternet || exit 1 # check if we have a valid internet connection if this isnt true the rest of the script will not work so stop here - getMovieInfo "$@" || exit 1 ## exit if we return 1 (chances are movie was not found) - printMovieInfo ## print out the data -fi From 5cfc38b330c31e9d2137f6a7a5183605ab1b12f3 Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Sat, 1 Feb 2025 13:38:03 +0200 Subject: [PATCH 5/8] Refactor: Integrate `bash-snippets.lib` into `lyrics` - Parse short and long command line options - Artist name and song name now are positional argument - Remove `-f` option; user can redirect output for desired output file - API response processed with `jq` --- lyrics/lyrics | 363 ++++++++++++++++++++------------------------------ 1 file changed, 145 insertions(+), 218 deletions(-) diff --git a/lyrics/lyrics b/lyrics/lyrics index 20b0b2a..75eda2c 100755 --- a/lyrics/lyrics +++ b/lyrics/lyrics @@ -1,236 +1,163 @@ #!/usr/bin/env bash # Author: Alexander Epstein https://github.com/alexanderepstein -currentVersion="1.23.0" -configuredClient="" -artist="false" -song="false" -filePath="" - -## This function determines which http get tool the system has installed and returns an error if there isnt one -getConfiguredClient() -{ - if command -v curl &>/dev/null; then - configuredClient="curl" - elif command -v wget &>/dev/null; then - configuredClient="wget" - elif command -v http &>/dev/null; then - configuredClient="httpie" - elif command -v fetch &>/dev/null; then - configuredClient="fetch" - else - echo "Error: This tool requires either curl, wget, httpie or fetch to be installed." >&2 - return 1 - fi -} -getConfiguredPython() -{ - if command -v python3 &>/dev/null; then - configuredPython="python3" - elif command -v python2 &>/dev/null; then - configuredPython="python2" - elif command -v python &>/dev/null; then - configuredPython="python" - else - echo "Error: This tool requires python to be installed." - return 1 - fi +### Global vars +# Get the directory path where the current script is located +DIRECTORY="${0%/*}" +LIB="bash-snippets.lib" + +ERR=0 # exit status + +# source library, and exit on error +source "$DIRECTORY/$LIB" || { + # don't use _Echo_Err; library not sourced! + echo "$PROGRAM: failed to source '$LIB' library" >&2 + exit 3 } -if [[ $(uname) != "Darwin" ]]; then - python() - { - case "$configuredPython" in - python3) python3 "$@" ;; - python2) python2 "$@" ;; - python) python "$@" ;; - esac - } -fi - -## Grabs an element from a a json string and then echoes it to stdout -## $1 = the JSON string -## $n+1 = the elements to be indexed -AccessJsonElement() { - json="$1" - shift - accessor="" - for element in "$@"; do - accessor="${accessor}['$element']" - done - echo "$json" | python -c "from __future__ import print_function; import sys, json; print(json.load(sys.stdin)${accessor})" 2> /dev/null - return "$?" + +### Function declaration +# +# get lyrics for song by artist +# +# Globals: +# artist: artist name +# song: song name +# +# Inputs: nothing +# +# Outputs: +# lyrics: lyrics text +# +# Returns: +# exit with error status if error occurred +Get_Lyrics() { + + # TODO + # better url encoding + # https://stackoverflow.com/questions/11876353/url-encoding-a-string-in-bash-script + # only wget support url encoding + + artist=$(echo "$artist" | sed -e 's/ /%20/g' -e 's/&/%26/g' -e 's/,/%2C/g' -e 's/-/%2D/g') + song=$( echo "$song" | sed -e 's/ /%20/g' -e 's/&/%26/g' -e 's/,/%2C/g' -e 's/-/%2D/g') + + response=$(_HTTP_GET "https://api.lyrics.ovh/v1/$artist/$song") + lyrics="$(_Access_Json_Element "$response" ".lyrics")" + + [[ $? -ne 0 ]] && { + _Echo_Err "$(_Access_Json_Element "$response" ".error")" + exit 1 + } + + Print_Lyrics + +} # end of function Get_Lyrics + + +# print lyrics +# +# Globals: +# lyrics +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +Print_Lyrics() { echo -e "$lyrics"; } + + +# print help message +# +# Globals: +# PROGRAM +# +# Inputs: nothing +# Outputs: nothing +# Returns: nothing +Help() { + cat <<- EOF + usage: $PROGRAM [-h|-u|-V] ARTIST SONG + Fetch lyrics for song title. + + Mandatory positional arguments + ARTIST: Artist name + SONG: Song title + + Options + -h display this help and exit + -u display synopsis/brief usage and exit + -V display tool version and exit + + Note: + arguments should be quoted + e.g. \`$PROGRAM 'artist' 'song name with space'\` + EOF } +### Parse cli option +while getopts ":-huV" opt; do + case "$opt" in + h ) Help; exit 0 ;; + u ) _Usage; exit 0 ;; + V ) echo "$VERSION"; exit 0 ;; + + ### parse long option + - ) + case "$OPTARG" in + help ) Help; exit 0 ;; + usage ) _Usage; exit 0 ;; + + * ) + _Echo_Err "Invalid option '--$OPTARG'" + _Try_Help + exit 1 + ;; + esac + ;; + + : ) + _Echo_Err "Option '-$OPTARG' requires an argument." + exit 1 + ;; + + * ) + _Echo_Err "$PROGRAM: Invalid option '-$OPTARG'" + _Try_Help + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) -## Allows to call the users configured client without if statements everywhere -httpGet() -{ - case "$configuredClient" in - curl) curl -A curl -s "$@" ;; - wget) wget -qO- "$@" ;; - httpie) http -b GET "$@" ;; - fetch) fetch -q "$@" ;; - esac -} -update() -{ - # Author: Alexander Epstein https://github.com/alexanderepstein - # Update utility version 1.2.0 - # To test the tool enter in the defualt values that are in the examples for each variable - repositoryName="Bash-Snippets" #Name of repostiory to be updated ex. Sandman-Lite - githubUserName="alexanderepstein" #username that hosts the repostiory ex. alexanderepstein - nameOfInstallFile="install.sh" # change this if the installer file has a different name be sure to include file extension if there is one - latestVersion=$(httpGet https://api.github.com/repos/$githubUserName/$repositoryName/tags | grep -Eo '"name":.*?[^\\]",'| head -1 | grep -Eo "[0-9.]+" ) #always grabs the tag without the v option - - if [[ $currentVersion == "" || $repositoryName == "" || $githubUserName == "" || $nameOfInstallFile == "" ]]; then - echo "Error: update utility has not been configured correctly." >&2 - exit 1 - elif [[ $latestVersion == "" ]]; then - echo "Error: no active internet connection" >&2 - exit 1 - else - if [[ "$latestVersion" != "$currentVersion" ]]; then - echo "Version $latestVersion available" - echo -n "Do you wish to update $repositoryName [Y/n]: " - read -r answer - if [[ "$answer" == [Yy] ]]; then - cd ~ || { echo 'Update Failed'; exit 1; } - if [[ -d ~/$repositoryName ]]; then rm -r -f $repositoryName || { echo "Permissions Error: try running the update as sudo"; exit 1; } ; fi - git clone "https://github.com/$githubUserName/$repositoryName" || { echo "Couldn't download latest version"; exit 1; } - cd $repositoryName || { echo 'Update Failed'; exit 1; } - git checkout "v$latestVersion" 2> /dev/null || git checkout "$latestVersion" 2> /dev/null || echo "Couldn't git checkout to stable release, updating to latest commit." - chmod a+x install.sh #this might be necessary in your case but wasnt in mine. - ./$nameOfInstallFile "update" || exit 1 - cd .. - rm -r -f $repositoryName || { echo "Permissions Error: update succesfull but cannot delete temp files located at ~/$repositoryName delete this directory with sudo"; exit 1; } - else - exit 1 - fi - else - echo "$repositoryName is already the latest version" - fi - fi -} +## check if missing mandatory command line arguments +case $# in + 0 ) + _Echo_Err "$PROGRAM: missing artist name and song title" + ERR=1 + ;; -checkInternet() -{ - httpGet github.com > /dev/null 2>&1 || { echo "Error: no active internet connection" >&2; return 1; } # query github with a get request -} + 1 ) + _Echo_Err "$PROGRAM: two few argument: missing artist name or title" + ERR=1 + ;; -getLyrics() -{ - encodedArtist=$(echo "$1" | sed s/" "/%20/g | sed s/"&"/%26/g | sed s/,/%2C/g | sed s/-/%2D/g) - encodedSong=$(echo "$2" | sed s/" "/%20/g | sed s/"&"/%26/g | sed s/,/%2C/g | sed s/-/%2D/g) - response=$(httpGet "https://api.lyrics.ovh/v1/$encodedArtist/$encodedSong") - lyrics="$(AccessJsonElement "$response" "lyrics" 2> /dev/null)" - if [[ $lyrics == "" ]];then { echo "Error: no lyrics found!"; return 1; }; fi -} + 2 ) artist="$1"; song="$2" ;; -printLyrics() -{ - if [[ $filePath == "" ]];then echo -e "$lyrics" - else - if [ -f "$filePath" ];then - echo -n "File already exists, do you want to overwrite it [Y/n]: " - read -r answer - if [[ "$answer" == [Yy] ]]; then - echo -e "$lyrics" > "$filePath"; - fi - else - echo -e "$lyrics" > "$filePath"; - fi - fi -} + * ) + _Echo_Err "$PROGRAM: two many arguments" + ERR=1 + ;; +esac -usage() -{ - cat <&2 - exit 1 - ;; - h) usage - exit 0 - ;; - v) echo "Version $currentVersion" - exit 0 - ;; - u) - getConfiguredClient || exit 1 - checkInternet || exit 1 - update - exit 0 - ;; - f) - filePath="$OPTARG" - ;; - a) - artist="true" - if [[ "$(echo "$@" | grep -Eo "\-s")" == "-s" ]];then song="true";fi # wont go through both options if arg spaced and not quoted this solves that issue (dont need this but once had bug on system where it was necessary) - if [[ "$(echo "$@" | grep -Eo "\-f")" == "-f" ]];then filePath=$(echo "$@" | grep -Eo "\-f [ a-z A-Z / 0-9 . \ ]*[ -]?" | sed s/-f//g | sed s/-//g | sed s/^" "//g);fi - ;; - s) - song="true" - if [[ "$(echo "$@" | grep -Eo "\-a")" == "-a" ]];then artist="true";fi # wont go through both options if arg spaced and not quoted this solves that issue (dont need this but once had bug on system where it was necessary) - if [[ "$(echo "$@" | grep -Eo "\-f")" == "-f" ]];then filePath=$(echo "$@" | grep -Eo "\-f [ a-z A-Z / 0-9 . \ ]*[ -]?" | sed s/-f//g | sed s/-//g | sed s/^" "//g);fi - ;; - :) echo "Option -$OPTARG requires an argument." >&2 - exit 1 - ;; - esac -done +exit $ERR -# special set of first arguments that have a specific behavior across tools -if [[ $# == "0" ]]; then - usage ## if calling the tool with no flags and args chances are you want to return usage - exit 0 -elif [[ $# == "1" ]]; then - if [[ $1 == "update" ]]; then - getConfiguredClient || exit 1 - checkInternet || exit 1 - update || exit 1 - exit 0 - elif [[ $1 == "help" ]]; then - usage - exit 0 - fi -fi - -if ($artist && ! $song) || ($song && ! $artist);then - echo "Error: the -a and the -s flag must be used to fetch lyrics." - exit 1 -elif $artist && $song;then - song=$(echo "$@" | grep -Eo "\-s [ a-z A-Z 0-9 . \ ]*[ -]?" | sed s/-s//g | sed s/-//g | sed s/^" "//g) - if [[ $song == "" ]];then { echo "Error: song could not be parsed from input."; exit 1; };fi - artist=$(echo "$@" | grep -Eo "\-a [ a-z A-Z 0-9 . \ ]*[ -]?" | sed s/-a//g | sed s/-//g | sed s/^" "//g) - if [[ $artist == "" ]];then { echo "Error: artist could not be parsed from input."; exit 1; };fi - getConfiguredClient || exit 1 - if [[ $(uname) != "Darwin" ]]; then getConfiguredPython || exit 1;fi - checkInternet || exit 1 - getLyrics "$artist" "$song" || exit 1 - printLyrics -else - { clear; echo "You shouldnt be here but maaaaaaybeee you slipped passed me, learn to use the tool!"; sleep 5; clear;} - usage - exit 1 -fi From 3f65464800b2bd262287f6dd60edf7b4026a6004 Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:59:20 +0200 Subject: [PATCH 6/8] Fix: movie info printed multiple times if multiple arguments passed When the function Get_Info finishes execution, the variable will not be unsetted; Variable keys and values are declared as global variables. --- movies/movies | 1 + 1 file changed, 1 insertion(+) diff --git a/movies/movies b/movies/movies index 3483ed0..d40bce8 100755 --- a/movies/movies +++ b/movies/movies @@ -102,6 +102,7 @@ Get_Info() { fi Print_Info + unset keys values } # end of function Get_Info From a1291b66307198d3c87a8324700a447634c8e523 Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:00:39 +0200 Subject: [PATCH 7/8] Feat: Implement dynamic output based on shell width for movies Previously, the program output was fixed to a specific number of columns per line (80 columns) This changed to dynamically adjust the number of columns based on the current user shell width --- movies/movies | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/movies/movies b/movies/movies index d40bce8..eceee19 100755 --- a/movies/movies +++ b/movies/movies @@ -10,7 +10,9 @@ DETAIL=false # detailed output flag ERR=0 # exit status KEY_LEN=0 # longest key string length VAL_LEN=0 -NCOL=80 # output line width +read _ NCOL < <(stty size) +(( NCOL > 80 )) && NCOL=80 + # source library, and exit on error @@ -125,7 +127,7 @@ Print_KV() { key="$1" val="$2" - line_format="%${KEY_LEN}s %-${NCOL}s\n" + line_format="% ${KEY_LEN}s %s\n" # API response may contain 'null' as string or 'N/A', skip it [[ "${val,,}" == "null" || "${val^^}" == "N/A" || -z "$val" ]] && return @@ -134,9 +136,9 @@ Print_KV() { printf "$line_format" "$key:" "$val" else - printf "$line_format" "$key:" ' ' + printf "$line_format" "$key:" '' while IFS= read -r line; do - printf "$line_format" ' ' "$line" + printf "$line_format" '' "$line" done < <(echo "$val" | fmt -w $VAL_LEN) fi @@ -162,7 +164,7 @@ Print_Info() { len=${#key} (( len > KEY_LEN )) && KEY_LEN=$len done - KEY_LEN=$(( $KEY_LEN + 2 )) + KEY_LEN=$(( KEY_LEN + 2 )) VAL_LEN=$(( NCOL - KEY_LEN )) for i in ${!keys[@]}; do From aa26f5d46574a2760f7d8a41c94cca42a2c03b38 Mon Sep 17 00:00:00 2001 From: e55am <112789498+e55am@users.noreply.github.com> Date: Mon, 12 May 2025 19:46:18 +0300 Subject: [PATCH 8/8] Fix: movies command output prefixed with spaces on multiple arguments input - Fix number of columns increased by 2 on each iteration - Add blank line separator if multiple argument supplied - Implement stand alone function to set output width --- movies/movies | 61 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/movies/movies b/movies/movies index eceee19..2acd2f4 100755 --- a/movies/movies +++ b/movies/movies @@ -8,11 +8,8 @@ LIB="bash-snippets.lib" DETAIL=false # detailed output flag ERR=0 # exit status -KEY_LEN=0 # longest key string length -VAL_LEN=0 -read _ NCOL < <(stty size) -(( NCOL > 80 )) && NCOL=80 - +KEY_LEN= +VAL_LEN= # source library, and exit on error @@ -23,7 +20,34 @@ source "$DIRECTORY/$LIB" || { } -# request movie info from OMDB API +# set output width +# +# Globals: +# KEY_LEN +# VAL_LEN +# +# Input: nothing +# Output: +# KEY_LEN +# VAL_LEN +# +# Returns: nothing +Set_Output_Width() { + + [[ -z "$KEY_LEN" || -z "$VAL_LEN" ]] || return + + read _ NCOL < <(stty size) + (( NCOL > 80 )) && NCOL=80 + + # longest key is rotten_tomatoes_rating + s="rotten_tomatoes_rating" + KEY_LEN=$(( ${#s} + 2 )) + VAL_LEN=$(( NCOL - KEY_LEN )) + +} # end of function Set_Output_Width + + +# fetch movie info from OMDB API # # Globals: # DETAIL @@ -41,7 +65,7 @@ Get_Info() { # we used two arrays instead of associate array because associate array are unsorted declare -g -a keys values - + title="$(echo "$*" | tr ' ' '+')" # please consider using your own API key @@ -50,11 +74,12 @@ Get_Info() { URL="http://www.omdbapi.com/?$PARAMETER" response="$(_HTTP_GET "$URL")" + Set_Output_Width # check api response, if response was false, Print Error and exit check_res=$(_Access_Json_Element "$response" ".Response") if [[ "$check_res" == "False" ]]; then - _Echo_Err $(Print_KV "Error" "$(_Access_Json_Element "$response" ".Error")") + Print_KV "Error" "$*: $(_Access_Json_Element "$response" ".Error")" ERR=1 return fi @@ -114,7 +139,6 @@ Get_Info() { # Globals: # KEY_LEN # VAL_LEN -# NCOL # # Inputs: # key @@ -152,21 +176,10 @@ Print_KV() { # values # # Input: nothing -# Output: -# KEY_LEN -# VAL_LEN -# +# Output: nothing # Returns: nothing Print_Info() { - # get the length of longest key - for key in "${keys[@]}"; do - len=${#key} - (( len > KEY_LEN )) && KEY_LEN=$len - done - KEY_LEN=$(( KEY_LEN + 2 )) - VAL_LEN=$(( NCOL - KEY_LEN )) - for i in ${!keys[@]}; do key=${keys[$i]} key="${key^}" # convert first character to upper case @@ -243,7 +256,11 @@ shift $(( OPTIND - 1 )) _Check_If_All_Dep "jq" "fmt" _Check_Internet -for title in "$@"; do Get_Info "$title"; done +for title in "$@"; do + Get_Info "$title" + # print blank line separator + [[ $# -gt 1 ]] && echo +done exit $ERR