From 5e8327b565b5ec2e7b959141c063ebcb3faf7d70 Mon Sep 17 00:00:00 2001 From: Tearran Date: Wed, 3 Apr 2024 19:24:59 -0700 Subject: [PATCH] organizing --- bin/armbian-configng | 222 ++ bin/armbian-interface | 119 + config.ng.functions.sh | 8 +- lib/armbian-configng/documents.sh | 388 +++ lib/armbian-configng/functions.sh | 145 + lib/armbian-configng/network/readme.md | 3 + lib/armbian-configng/network/set_wifi.sh | 19 + .../system/armbian_install.sh | 21 + lib/armbian-configng/system/hello_world.sh | 21 + lib/armbian-configng/system/readme.md | 2 + lib/armbian-configng/system/see_monitor.sh | 45 + lib/bash-utility-master/.editorconfig | 22 + lib/bash-utility-master/.gitignore | 3 + lib/bash-utility-master/.remarkrc | 8 + lib/bash-utility-master/CODE_OF_CONDUCT.md | 76 + lib/bash-utility-master/CONTRIBUTING.md | 129 + lib/bash-utility-master/LICENSE | 21 + lib/bash-utility-master/README.md | 3026 +++++++++++++++++ lib/bash-utility-master/bash_utility.sh | 20 + lib/bash-utility-master/bin/bashdoc.awk | 275 ++ .../bin/generate_readme.sh | 400 +++ .../image/bash-utility.png | Bin 0 -> 32396 bytes lib/bash-utility-master/image/logo.png | Bin 0 -> 23716 bytes lib/bash-utility-master/src/array.sh | 284 ++ lib/bash-utility-master/src/check.sh | 34 + lib/bash-utility-master/src/collection.sh | 287 ++ lib/bash-utility-master/src/date.sh | 744 ++++ lib/bash-utility-master/src/debug.sh | 49 + lib/bash-utility-master/src/file.sh | 222 ++ lib/bash-utility-master/src/format.sh | 183 + lib/bash-utility-master/src/interaction.sh | 96 + lib/bash-utility-master/src/json.sh | 25 + lib/bash-utility-master/src/misc.sh | 121 + lib/bash-utility-master/src/os.sh | 168 + lib/bash-utility-master/src/string.sh | 198 ++ lib/bash-utility-master/src/terminal.sh | 51 + lib/bash-utility-master/src/validation.sh | 244 ++ lib/bash-utility-master/src/variable.sh | 144 + .../doc/armbian-configng/armbian-configng.csv | 5 + .../armbian-configng/armbian-configng.html | 98 + .../armbian-configng/armbian-configng.json | 34 + share/doc/armbian-configng/armbianCPU.svg | 5 + share/doc/armbian-configng/index.html | 42 + 43 files changed, 8003 insertions(+), 4 deletions(-) create mode 100755 bin/armbian-configng create mode 100755 bin/armbian-interface create mode 100644 lib/armbian-configng/documents.sh create mode 100644 lib/armbian-configng/functions.sh create mode 100644 lib/armbian-configng/network/readme.md create mode 100644 lib/armbian-configng/network/set_wifi.sh create mode 100644 lib/armbian-configng/system/armbian_install.sh create mode 100644 lib/armbian-configng/system/hello_world.sh create mode 100644 lib/armbian-configng/system/readme.md create mode 100644 lib/armbian-configng/system/see_monitor.sh create mode 100644 lib/bash-utility-master/.editorconfig create mode 100644 lib/bash-utility-master/.gitignore create mode 100644 lib/bash-utility-master/.remarkrc create mode 100644 lib/bash-utility-master/CODE_OF_CONDUCT.md create mode 100644 lib/bash-utility-master/CONTRIBUTING.md create mode 100644 lib/bash-utility-master/LICENSE create mode 100644 lib/bash-utility-master/README.md create mode 100755 lib/bash-utility-master/bash_utility.sh create mode 100755 lib/bash-utility-master/bin/bashdoc.awk create mode 100755 lib/bash-utility-master/bin/generate_readme.sh create mode 100644 lib/bash-utility-master/image/bash-utility.png create mode 100644 lib/bash-utility-master/image/logo.png create mode 100755 lib/bash-utility-master/src/array.sh create mode 100755 lib/bash-utility-master/src/check.sh create mode 100755 lib/bash-utility-master/src/collection.sh create mode 100755 lib/bash-utility-master/src/date.sh create mode 100755 lib/bash-utility-master/src/debug.sh create mode 100755 lib/bash-utility-master/src/file.sh create mode 100755 lib/bash-utility-master/src/format.sh create mode 100755 lib/bash-utility-master/src/interaction.sh create mode 100755 lib/bash-utility-master/src/json.sh create mode 100755 lib/bash-utility-master/src/misc.sh create mode 100755 lib/bash-utility-master/src/os.sh create mode 100755 lib/bash-utility-master/src/string.sh create mode 100755 lib/bash-utility-master/src/terminal.sh create mode 100755 lib/bash-utility-master/src/validation.sh create mode 100755 lib/bash-utility-master/src/variable.sh create mode 100755 share/doc/armbian-configng/armbian-configng.csv create mode 100755 share/doc/armbian-configng/armbian-configng.html create mode 100755 share/doc/armbian-configng/armbian-configng.json create mode 100755 share/doc/armbian-configng/armbianCPU.svg create mode 100755 share/doc/armbian-configng/index.html diff --git a/bin/armbian-configng b/bin/armbian-configng new file mode 100755 index 000000000..2a6c89774 --- /dev/null +++ b/bin/armbian-configng @@ -0,0 +1,222 @@ +#!/bin/bash + +# This script provides a command-line interface for managing Armbian configuration. +# It loads libraries of functions from the lib directory and displays them in a menu. +# The user can select a function to run, or use a text-based user interface (TUI) to navigate the menus. +# The script also provides a help option and a debug option for developers. +# The script requires sudo privileges to run some functions. +# The script uses whiptail or dialog for the TUI, depending on which is available. + +#set -x +#set -e + +# +# Enable Dynamic directory root use home ~/ , /bin , /usr/sbin etc.. +bin="$(dirname "${BASH_SOURCE[0]}")" +directory="$(cd "$bin/../" && pwd )" +file_name="$(basename "${BASH_SOURCE[0]}")" +filename="${file_name%.*}" +libpath=$(cd "$directory/lib/$filename/" && pwd) +#sharepath=$(cd "$directory/share/${filename%-dev}/" && pwd) + + +# +# Consept Distribution Compatibility checks +check_distro() { + + [[ -f "/usr/bin/${filename%%-*}-config" ]] && distro_config="${filename%%-*}" + [[ -f "/etc/${filename%%-*}-release" ]] && distro_release="${filename%%-*}" + # if both true then we are good to go + [[ -z "$distro_config" ]] || [[ -z "$distro_release" ]] && echo "W: Costum build, Tech support links are missing." + [[ -n "$distro_config" ]] && [[ -n "$distro_release" ]] && echo "I: This build seems to be community supported" | ./armbian-interface -o + [[ -f "/usr/sbin/${filename%%-*}-config" ]] && distro_config="${filename%%-*}" + [[ -f "/etc/${filename%%-*}-release" ]] && distro_release="${filename%%-*}" + +} + +[[ "$1" == "--dev" ]] && dev=1 && shift 1 + +# +# Check if the script is dev version. +suffix="${file_name##*-}" + +if [[ "$suffix" == dev ]]; then + dev=1 + check_distro #| armbian-interface -o +fi + +if [[ "$(id -u)" != "0" ]] && [[ "$dev" == "1" ]] ; then + +cat << EOF #| ./armbian-interface -o +I: Running in UX development mode +W: Admin functions will not work as expected + +EOF +elif [[ "$(id -u)" == "0" ]] && [[ "$dev" == "1" ]] ; then +cat << EOF | ./armbian-interface -o +I: Running in UX development mode +W: Document files may need Admin privleges to edit/remove + +EOF + +fi + +# +# Check if the script is being run as root +# UX Development mode bypasses root check, many functions will not work as expected + +if [[ "$(id -u)" != "0" ]] && [[ "$dev" != "1" ]]; then + echo -e "E: This tool requires root privileges. Try: \"sudo $filename\"" >&2 + exit 1 +fi + +declare -A dialogue + +# +# Check if whiptail or dialog is installed and set the variable 'dialogue' accordingly. +# todo add a fallback TUI and GUI +if command -v whiptail &> /dev/null; then + dialogue="whiptail" +elif command -v dialog &> /dev/null; then + dialogue="dialog" +else + echo "TUI not found" + echo "Warning: Using fallback TUI" + sleep 1 + clear && generate_read +fi + +source "$libpath/functions.sh" +source "$libpath/documents.sh" +for file in "$libpath"/*/*.sh; do + source "$file" +done + +# +# mapfile -t categories < <(ls -d "$libpath"/* ) +mapfile -t categories < <(find "$libpath"/* -type d) +declare -A functions + +for category in "${categories[@]}"; do + category_name="${category##*/}" + + category_file="$category/readme.md" + if [[ -f "$category_file" ]]; then + category_description=$(grep -oP "(?<=# @description ).*" "$category_file") + fi + + for file in "$category"/*.sh; do + description="" + while IFS= read -r line; do + if [[ $line =~ ^#\ @description\ (.*)$ ]]; then + description="${BASH_REMATCH[1]}" + elif [[ $line =~ ^function\ (.*::.*)\(\)\{$ ]]; then + # END: be15d9bcejpp + function_name="${BASH_REMATCH[1]}" + key="$category_name:${file##*/}:${function_name}" + functions["$key,function_name"]=$(echo "$function_name" | sed 's/.*:://') + functions["$key,group_name"]=$(echo "$function_name" | sed 's/::.*//') + functions["$key,description"]=$description + elif [[ $line =~ ^#\ @options\ (.*)$ ]]; then + functions["$key,options"]="${BASH_REMATCH[1]}" + fi + done < "$file" + functions["$key,category"]=$category_name + functions["$key,category_description"]=$category_description + done +done + + +# +# WIP: Check arguments for no flag options +# armbian-config --help +# Change to BASH: /usr/sbin/armbian-config main=System selection=BASH +handle_no_flag(){ +if [[ "$1" == *"="* ]]; then + IFS='=' read -r key value <<< "$1" + function_name=$(parse_action "$key" "$value") + # Call the function using variable indirection + ${function_name} +elif [[ "$1" == "help"* ]]; then + generate_list_cli +fi +} + +# +# Check arguments for long flag options +# Help message related to the functions the back end +handle_long_flag(){ + if [[ "$1" == "--help" ]]; then + generate_list_run + exit 0 ; + elif [[ "$1" == "--doc" ]]; then + generate_doc + exit 0 ; + fi +# WIP: + if [ "$1" == "--run" ]; then + shift # Shifts the arguments to the left, excluding the first argument ("-r") + group_name="$1" # Takes the first argument as the group name + shift 1 # Shifts the arguments again to exclude the group name + + function_name=$(parse_action "$group_name" "$1") + if [ $? -eq 0 ]; then + # Call the function using variable indirection + ${function_name} + fi + elif [ "$1" == "--help" ]; then + generate_list_run + exit + elif [ "$1" == "--test" ]; then + check_distro | armbian-interface -o && $1 > /dev/null + fi + +} +# +# Check arguments for short flag options +# THe interface help message +handle_short_flag(){ +if [ "$1" == "-h" ]; then + generate_help + exit 0 ; +# Generate a text-based user interface +elif [ "$1" == "-t" ] ; then + generate_read ; exit 0 ; +# Generate all doc files +elif [ "$1" == "-d" ] ; then + generate_doc ; exit 0 ; +elif [ "$1" == "-j" ] ; then + generate_json ; exit 0 ; +fi + +} + +case "$1" in + *"="*) + # Handle the case where $1 contains "=" + handle_no_flag "$@" + ;; + *"--"*) + # Handle the case where $1 starts with "--" + handle_long_flag "$@" + ;; + *"-"*) + # Handle the case where $1 starts with "-" + handle_short_flag "$1" + ;; + *) + handle_no_flag "$@" + # Handle the case where $1 does not match any of the above patterns + # You can add your code here + ;; +esac + +if [[ -z "$1" ]] ; then + while true; do + generate_tui ; + if [[ "$?" == "0" ]]; then + exit 0 + fi + done + +fi \ No newline at end of file diff --git a/bin/armbian-interface b/bin/armbian-interface new file mode 100755 index 000000000..5f6e27c12 --- /dev/null +++ b/bin/armbian-interface @@ -0,0 +1,119 @@ +#!/bin/bash + +# +# Copyright (c) 2023 Joseph C Turner +# All rights reserved. +# +# This script. +# demonstrates the compatibility of multiple interfaces for displaying menus or messages. +# It uses an array to set the options for all three menus (bash, whiptail, and dialog). +# The script checks if whiptail or dialog are available on the system and uses them to display the menu in a more user-friendly way. +# If neither of these programs is available, it falls back to using bash. +# while both are installed falls back to whiptail to display the menu. +# The user can override the default program by passing an argument when running the script: +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +## DIRECTORY variable to the absolute path of the script's directory +# directory="$(dirname "$(readlink -f "$0")")" +filename=$(basename "${BASH_SOURCE[0]}") + +## DIALOG variable to the absolute path of the script's directory +DIALOG="bash" +[[ -x "$(command -v dialog)" ]] && DIALOG="dialog" +[[ -x "$(command -v whiptail)" ]] && DIALOG="whiptail" + +show_help(){ + + echo -e "\nUsage: [command] | ${filename%.*} [ -h | -m | -o ]" + echo "Options:" + echo " -h, Print this help." + echo "" + echo " -o, Opens an OK message Box" + echo "" + echo " -m, Opens an Menu select Box." + echo "" + echo " -p, Opens Popup message box. " + echo "" + exit 1; + } + +show_message(){ + + # Read the input from the pipe continuously until there is no more input + input="" + while read -r line; do + input+="$line\n" + done + + # Display the "OK" message box with the input data + [[ $DIALOG != "bash" ]] && $DIALOG --title "Message Box" --msgbox "$input" 0 0 + [[ $DIALOG == "bash" ]] && echo -e "$input" + [[ $DIALOG == "bash" ]] && read -p -r "Press [Enter] to continue..." ; echo "" ; exit 1 + + } + +show_popup(){ + + + input="" + while read -r line; do + input+="$line\n" + done + + [[ $DIALOG != "bash" ]] && $DIALOG --title "Popup Box" --infobox "$input" 0 0 + [[ $DIALOG == "bash" ]] && echo -e "$input" + + } + +show_menu(){ + + + # Get the input and convert it into an array of options + inpu_raw=$(cat) + # Remove the lines befor -h + input=$(echo "$inpu_raw" | sed 's/-\([a-zA-Z]\)/\1/' | grep '^ [a-zA-Z] ' | grep -v '\[') + options=() + while read -r line; do + package=$(echo "$line" | awk '{print $1}') + description=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ *//') + options+=("$package" "$description") + done <<< "$input" + + # Display the menu and get the user's choice + [[ $DIALOG != "bash" ]] && choice=$($DIALOG --title "Menu" --menu "Choose an option:" 0 0 9 "${options[@]}" 3>&1 1>&2 2>&3) + + # Check if the user made a choice + if [ $? -eq 0 ]; then + echo "$choice" + else + echo "You cancelled." + fi + } + +[[ $1 == "-m" ]] && show_menu ; +[[ $1 == "-o" ]] && show_message ; +[[ $1 == "-h" ]] && show_help ; +[[ $1 == "-p" ]] && show_popup ; +[[ -z "$*" ]] && show_help ; diff --git a/config.ng.functions.sh b/config.ng.functions.sh index c45aba3e9..003171806 100644 --- a/config.ng.functions.sh +++ b/config.ng.functions.sh @@ -314,7 +314,7 @@ function set_newt_colors() { 7) color="white" ;; 8) color="black" ;; 9) color="red" ;; - *) break ;; + *) return ;; esac export NEWT_COLORS="root=,$color" } @@ -491,7 +491,7 @@ function show_message() { $DIALOG --title "$BACKTITLE" --msgbox "$input" 0 0 else echo -e "$input" - read -p "Press [Enter] to continue..." + read -p -r "Press [Enter] to continue..." fi } @@ -528,7 +528,7 @@ function show_infobox() { else input="$1" - TERM=ansi $DIALOG --title "$BACKTITLE" --infobox "$( echo "$input" )" 6 80 + TERM=ansi $DIALOG --title "$BACKTITLE" --infobox "$input" 6 80 fi echo -ne '\033[3J' # clear the screen } @@ -644,7 +644,7 @@ function see_ping() { done if [[ $? -ne 0 ]]; then - read -n 1 -s -p "Warning: Configuration cannot work properly without a working internet connection. \ + read -n -r 1 -s -p "Warning: Configuration cannot work properly without a working internet connection. \ Press CTRL C to stop or any key to ignore and continue." fi diff --git a/lib/armbian-configng/documents.sh b/lib/armbian-configng/documents.sh new file mode 100644 index 000000000..8a319822c --- /dev/null +++ b/lib/armbian-configng/documents.sh @@ -0,0 +1,388 @@ +# This function is used to generate a simple JSON file containing all functions and their descriptions. +# pthon is more suited to complex arrays this should be handeled during build time +generate_json() { + json_objects=() + for key in "${!functions[@]}"; do + if [[ $key == *",function_name"* ]]; then + function_key="${key%,function_name}" + function_name="${functions[$key]}" + group_name="${functions["$function_key,group_name"]}" + description="${functions["$function_key,description"]}" + options="${functions["$function_key,options"]}" + category="${functions["$function_key,category"]}" + category_description="${functions["$function_key,category_description"]}" + json_objects+=("{ \"Function Name\": \"$function_name\", \"Group Name\": \"$group_name\", \"Description\": \"$description\", \"Options\": \"$options\", \"Category\": \"$category\", \"Category Description\": \"$category_description\" }") + fi + done + IFS=',' + echo "[${json_objects[*]}]" | jq +} + +# This function is used to generate a armbian CPU logo +generate_svg(){ + +cat << EOF + + + + + +EOF +} + +# This function is used to generate a CSV file containing all functions and their descriptions. +generate_csv() { + echo "Function Name,Group Name,Description,Options,Category,Category Description" + for key in "${!functions[@]}"; do + if [[ $key == *",function_name"* ]]; then + function_key="${key%,function_name}" + function_name="${functions[$key]}" + group_name="${functions["$function_key,group_name"]}" + description="${functions["$function_key,description"]}" + options="${functions["$function_key,options"]}" + category="${functions["$function_key,category"]}" + category_description="${functions["$function_key,category_description"]}" + echo "$function_name,$group_name,$description,$options,$category,$category_description" + fi + done +} + +generate_csv_test() { + for category in "${categories[@]}"; do + echo "Function Name,Group Name,Description,Options,Category,Category Description" > "$category.csv" + for key in "${!functions[@]}"; do + if [[ $key == *",function_name"* ]]; then + function_key="${key%,function_name}" + function_name="${functions[$key]}" + group_name="${functions["$function_key,group_name"]}" + description="${functions["$function_key,description"]}" + options="${functions["$function_key,options"]}" + category="${functions["$function_key,category"]}" + category_description="${functions["$function_key,category_description"]}" + if [[ $category == "$category" ]]; then + echo "$function_name,$group_name,$description,$options,$category,$category_description" >> "$category.csv" + fi + fi + done + done +} + +# This function is used to generate a Single page app. +generate_html5() { + +html5_content=' + + + + + Armbian '$(echo "$filename")' + + + +
+

'$(echo "$filename")'

+
+ +
+
+ +
+
+ + + + + +' + +echo "$html5_content" ; + +} + +# This function is used to generate tabe html file +# used to check proper array generation and output. +generate_html() { + html_content=' + + + + + + + + + + + + + + + + + ' + for key in "${!functions[@]}"; do + if [[ $key == *",function_name"* ]]; then + function_key="${key%,function_name}" + function_name="${functions[$key]}" + group_name="${functions["$function_key,group_name"]}" + description="${functions["$function_key,description"]}" + options="${functions["$function_key,options"]}" + category="${functions["$function_key,category"]}" + category_description="${functions["$function_key,category_description"]}" + html_content+="" + fi + done + html_content+=' + +
Function NameGroup NameDescriptionOptionsCategoryCategory Description
$function_name$group_name$description$options$category$category_description
+ + + ' + + echo "$html_content" +} + +# This function is used to generate the main readme.md file +generate_markdown() { +cat << EOF + +

+ Armbian logo +
+ Armbian ConfigNG +
+ CodeFactor +

+ +# User guide +## Quick start +Run the following commands: + + sudo apt install git + cd ~/ + git clone https://github.com/armbian/configng.git + cd configng + ./bin/${file_name%.*} --dev + +If all goes well you should see the Text-Based User Inerface (TUI) + +### To see a list of all functions and their descriptions, run the following command: +~~~ +bash ~/configng/bin/armbian-configng -h +~~~ +## Coding Style +follow the following coding style: +~~~ +# @description A short description of the function. +# +# @exitcode 0 If successful. +# +# @options A description if there are options. +function group::string() {s + echo "hello world" + return 0 +} +~~~ +## Codestyle can be used to auto generate + - [Markdown](share/${file_name%.*}/readme.md) + - [JSON](share/${file_name%.*}/data/${file_name%.*}.json) + - [CSV](share/${file_name%.*}/data/${file_name%.*}.csv) + - [HTML](share/${file_name%.*}/${file_name%.*}-table.html) + - [github.io](//tearran/github.io/${file_name%.*}/index.html) +## Functions list as of $(date +%Y-%m-%d) +EOF + + for category in "${categories[@]}"; do + echo "## ${category##*/}" + echo "${functions["$key,category_description"]}" + echo + + for file in "$category"/*.sh; do + echo "### ${file##*/}" + echo + + mapfile -t functions_in_file < <(grep -oP '(?<=function\s)\w+::\w+' "$file") + + for function in "${functions_in_file[@]}"; do + key="${category##*/}:${file##*/}:${function}" + echo " - **Group Name:** ${functions["$key,group_name"]}" + echo " - **Action Name:** ${functions["$key,function_name"]}" + echo " - **Options:** ${functions["$key,options"]}" + echo " - **Description:** ${functions["$key,description"]}" + echo + done + done + done +cat << EOF + +# Inclueded projects +- [Bash Utility](https://labbots.github.io/bash-utility) +- [Armbian config](https://github.com/armbian/config.git) + +EOF +} + + +# This function is used to generate a extention to help meassage of all functions and their descriptions. +generate_list_run() { + echo "Usage: ${filename%.*} [--run] [option] [action]" + # Loop through each category + for category in "${categories[@]}"; do + # Initialize an empty array to store the group names that have been printed + declare -A printed_groups + + # Loop through each file in the category + for file in "$category"/*.sh; do + + # Extract functions from the file + mapfile -t functions_in_file < <(grep -oP '(?<=function\s)\w+::\w+' "$file") + + # Loop through each function in the file + for function in "${functions_in_file[@]}"; do + key="${category##*/}:${file##*/}:${function}" + group_name=${functions["$key,group_name"]} + + # If the group name has not been printed yet, print it and add it to the array + declare -A printed_groups + if [[ -z ${printed_groups["$group_name"]} ]]; then + echo " $group_name, [action]" + printed_groups["$group_name"]=1 + fi + + echo " ${functions["$key,function_name"]} - ${functions["$key,description"]}" + echo + done + done + done + +} + +# This function is used to generate a no flag options help message +generate_list_cli() { + + echo "Usage: ${filename%.*} [group]=[function]" + # Loop through each category + for category in "${categories[@]}"; do + # Initialize an empty array to store the group names that have been printed + declare -A printed_groups + + # Loop through each file in the category + for file in "$category"/*.sh; do + + # Extract functions from the file + mapfile -t functions_in_file < <(grep -oP '(?<=function\s)\w+::\w+' "$file") + + # Loop through each function in the file + for function in "${functions_in_file[@]}"; do + key="${category##*/}:${file##*/}:${function}" + group_name=${functions["$key,group_name"]} + printf "\t%-20s - \t %s \n" "$group_name=${functions["$key,function_name"]}" "${functions["$key,description"]}" + done + done + done +} + + +# This function is used to generate a help message. +generate_help(){ +cat << EOF +Usage: ${filename%.*} [flag][option] + flag options: + -h, Print this help. + -t, Show a TUI fallback read. + --help, Prints Help message of long flag interactive options (WIP)." + help, View advanced no-interface options (CURRENT FOCUS)." +EOF +} + +# THis function is used to make documents +generate_and_print() { + local generate_func=$1 + local filename=$2 + local file_extension=$3 + local output_message=$4 + + "$generate_func" > "$filename.$file_extension" + chmod 755 "$filename.$file_extension" + echo "$output_message - generated $filename.$file_extension" +} + +generate_doc() { + dir="$(dirname "$(dirname "$(realpath "$0")")")/share/${filename%-dev}" + if [[ ! -d "$dir" ]]; then + mkdir -p "$dir/data/" + fi + cd "$dir" || exit + generate_svg > "$filename.svg" + generate_and_print generate_markdown "../../readme" md "readme.md" + generate_and_print generate_html "../../index" html "index.html" + generate_and_print generate_html5 "index" html "HTML5" + generate_and_print generate_json "data/$filename" json "JSON" + generate_and_print generate_csv "data/${filename%-dev}" csv "CSV" + if [[ "$EUID" -eq 0 ]]; then + chown -R "$SUDO_USER":"$SUDO_USER" "$(dirname "$dir")" + cd ../../ + chown "$SUDO_USER":"$SUDO_USER" readme.md + fi + return 0 +} \ No newline at end of file diff --git a/lib/armbian-configng/functions.sh b/lib/armbian-configng/functions.sh new file mode 100644 index 000000000..3d598c61b --- /dev/null +++ b/lib/armbian-configng/functions.sh @@ -0,0 +1,145 @@ + + +# This function is used to generate a text-based user interface (TUI) for navigating the menus. +generate_tui() { + local options=() + local i=0 + declare -A categories_array + for category in "${categories[@]}"; do + local category_name="${category##*/}" + local category_description="" + local category_file="$category/readme.md" + + if [[ -f "$category_file" ]]; then + category_description=$(grep -oP "(?<=# @description ).*" "$category_file") + fi + + categories_array["$i"]="$category_name" + description_array["$i"]="$category_description" + options+=("$i" "$(printf '%-7s - %-8s' "${categories_array[$i]}" "${description_array[$i]}")") + #options+=("$i" "${categories_array[$i]} - ${description_array[$i]}") + ((++i)) + done + options+=("$i" "$(printf '%-7s - %-8s' "Legacy" "Run Legacy configuration")") + + #options+=("$i" "Legacy - Run Legacy configuration") + ((++i)) + options+=("$i" "$(printf '%-7s - %-8s' "Help" "Documentation, support, sources")") + #options+=("$i" "Help - Documentation, support, sources" ) + ((++i)) + + local choice + + choice=$($dialogue --menu "Select a category:" 0 0 9 "${options[@]}" 3>&1 1>&2 2>&3) + + if [[ -n $choice ]]; then + + if ((choice == "$i - 1")); then + generate_help | armbian-interface -o + exit ; + elif ((choice == "$i - 2")); then + armbian-config + exit ; + else + generate_sub_tui "${categories_array[$choice]}" + fi + fi +} + +# This function is used to generate a text-based user interface (TUI) for navigating the menus. +generate_sub_tui() { + local category="$1" + local options=() + local i=0 + declare -A functions_array + for file in "$libpath/$category"/*.sh; do + mapfile -t functions_in_file < <(grep -oP '(?<=function\s)\w+::\w+' "$file") + for function in "${functions_in_file[@]}"; do + key="${category##*/}:${file##*/}:${function}" + functions_array["$i"]="$function" + options+=("$i" "${functions["$key,function_name"]} - ${functions["$key,description"]}") + ((++i)) + done + done + + local choice + + choice=$($dialogue --menu "Select a function:" 0 0 9 "${options[@]}" 3>&1 1>&2 2>&3) + + if [[ -n $choice ]]; then + generate_action "${functions_array[$choice]}" + fi + +} + +# This function is used to generate a whiptail/dialog text-based user interface (TUI) for navigating the menus. +generate_action() { + local function_name="$1" + ${function_name} +} + +# This function is used to generate a bash text-based user interface (TUI) for navigating the menus. +generate_read() { + echo + echo "Please select an action:" + echo + # Initialize an empty array to store the function keys + declare -a function_keys + + # Loop through each key in the functions array + local i=1 + local current_category="" + for key in "${!functions[@]}"; do + if [[ $key == *",function_name" ]]; then + # Add the key to the function_keys array + function_keys[i]="${key%,function_name}" + + # Check if the category has changed and display it if so + local category="${functions["${function_keys[i]},category"]}" # < editor" + if [[ "$category" != "$current_category" ]]; then + echo "Category: $category" + current_category="$category" + fi + + # Display the function and its description as an option in the menu + echo " $i. ${functions["${function_keys[i]},group_name"]} ${functions[$key]} - ${functions["${function_keys[i]},description"]}" #" < for my editor + ((i++)) + fi + done + + echo + echo "$i. Show help" + ((i++)) + echo "$i. Exit" + + read -p "Enter your choice: " choice + + if ((choice == i-1)); then + generate_help + elif ((choice == i)); then + exit 0 + elif ((choice >= 1 && choice <= ${#function_keys[@]})); then + # Call the selected function using variable indirection + eval "${functions["${function_keys[choice]},group_name"]}::${functions["${function_keys[choice]},function_name"]}" #" < for my editor + else + echo "Invalid choice" + fi +} + +# This function is used to parse the action name and return the full function name. +parse_action() { + local group=$1 + local action=$2 + + # Construct the full function name + local function_name="${group}::${action}" + + # Check if the function exists + if declare -f "$function_name" > /dev/null; then + # Return the function name + echo "$function_name" + else + echo "Error: Unknown action '$action' for group '$group'" + return 1 + fi +} \ No newline at end of file diff --git a/lib/armbian-configng/network/readme.md b/lib/armbian-configng/network/readme.md new file mode 100644 index 000000000..646eff7e0 --- /dev/null +++ b/lib/armbian-configng/network/readme.md @@ -0,0 +1,3 @@ + +# @description Network Wired wireless Bluetooth access point + diff --git a/lib/armbian-configng/network/set_wifi.sh b/lib/armbian-configng/network/set_wifi.sh new file mode 100644 index 000000000..337a7d899 --- /dev/null +++ b/lib/armbian-configng/network/set_wifi.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright (c) Authors: http://www.armbian.com/authors, info@armbian.com +# +# This file is licensed under the terms of the GNU General Public +# License version 2. This program is licensed "as is" without any +# warranty of any kind, whether express or implied. + + + +# @description Network Manager. +# +# @exitcode 0 If successful. +# +# @options none +function network::NMTUI(){ + nmtui connect + return 0 +} diff --git a/lib/armbian-configng/system/armbian_install.sh b/lib/armbian-configng/system/armbian_install.sh new file mode 100644 index 000000000..688448636 --- /dev/null +++ b/lib/armbian-configng/system/armbian_install.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (c) Authors: http://www.armbian.com/authors, info@armbian.com +# +# This file is licensed under the terms of the GNU General Public +# License version 2. This program is licensed "as is" without any +# warranty of any kind, whether express or implied. + + +# @description Armbian installer +# +# @exitcode 0 If successful. +# +# @options none. +# +function system::Install(){ + + armbian-install + return 0 +} + diff --git a/lib/armbian-configng/system/hello_world.sh b/lib/armbian-configng/system/hello_world.sh new file mode 100644 index 000000000..c30f21520 --- /dev/null +++ b/lib/armbian-configng/system/hello_world.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (c) Authors: http://www.armbian.com/authors, info@armbian.com +# +# This file is licensed under the terms of the GNU General Public +# License version 2. This program is licensed "as is" without any +# warranty of any kind, whether express or implied. + + +# @description Hello System. +# +# @exitcode 0 If successful. +# +# @options none. +# +function system::Hello(){ + + echo "Hello Armbian" + return 0 +} + diff --git a/lib/armbian-configng/system/readme.md b/lib/armbian-configng/system/readme.md new file mode 100644 index 000000000..7adee2ea4 --- /dev/null +++ b/lib/armbian-configng/system/readme.md @@ -0,0 +1,2 @@ + +# @description System and Security diff --git a/lib/armbian-configng/system/see_monitor.sh b/lib/armbian-configng/system/see_monitor.sh new file mode 100644 index 000000000..5d7ed760a --- /dev/null +++ b/lib/armbian-configng/system/see_monitor.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Copyright (c) Authors: http://www.armbian.com/authors, info@armbian.com +# +# This file is licensed under the terms of the GNU General Public +# License version 2. This program is licensed "as is" without any +# warranty of any kind, whether express or implied. + + +# @description Armbian Monitor and Benchmarking. +# +# @exitcode 0 If successful. +# +# @options none +function monitor::Benchmarking(){ + see_menu #| armbian-interface -o + return 0 ; +} + +see_menu(){ + # Define the script + script="$(which armbianmonitor)" + + # Run the script with the -h option and save the output to a variable + help_message=$("$script" -h ) || exit 2 + + # Reformat the help message into an array line by line + readarray -t script_launcher < <(echo "$help_message" | sed 's/-\([a-zA-Z]\)/\1/' | grep '^ [a-zA-Z] ' | grep -v '\[') + + # Loop through each line in the array and create a menu string + menu_string="" + for line in "${script_launcher[@]}"; do + # Append the formatted line to the menu string + if [[ "$line" != " d "* ]] && [[ "$line" != " c "* ]]; then + menu_string+="$line\n" + fi + done + + # Use the get_help_msg function and pipe its output into configng-interface -m + selected_option=$(echo -e "$menu_string" | armbian-interface -m) + + # Run the armbian-monitor script with the selected option + [[ -n "$selected_option" ]] && "$script" -"$selected_option"; + } + diff --git a/lib/bash-utility-master/.editorconfig b/lib/bash-utility-master/.editorconfig new file mode 100644 index 000000000..f9b075876 --- /dev/null +++ b/lib/bash-utility-master/.editorconfig @@ -0,0 +1,22 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# for shfmt +[*.sh] +indent_style = space +indent_size = 4 +shell_variant = bash +switch_case_indent = true +space_redirects = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/lib/bash-utility-master/.gitignore b/lib/bash-utility-master/.gitignore new file mode 100644 index 000000000..7c7bf7091 --- /dev/null +++ b/lib/bash-utility-master/.gitignore @@ -0,0 +1,3 @@ +/tmp +gh-pages +hugo-docs diff --git a/lib/bash-utility-master/.remarkrc b/lib/bash-utility-master/.remarkrc new file mode 100644 index 000000000..a3ff1e10d --- /dev/null +++ b/lib/bash-utility-master/.remarkrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + "remark-preset-lint-markdown-style-guide", + ["remark-lint-list-item-spacing", false], + ["remark-lint-maximum-line-length", false], + ["remark-lint-no-duplicate-headings", false] + ] +} diff --git a/lib/bash-utility-master/CODE_OF_CONDUCT.md b/lib/bash-utility-master/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..461c926b9 --- /dev/null +++ b/lib/bash-utility-master/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at + +For answers to common questions about this code of conduct, see + + +[homepage]: https://www.contributor-covenant.org diff --git a/lib/bash-utility-master/CONTRIBUTING.md b/lib/bash-utility-master/CONTRIBUTING.md new file mode 100644 index 000000000..aa401df88 --- /dev/null +++ b/lib/bash-utility-master/CONTRIBUTING.md @@ -0,0 +1,129 @@ +# Contributing to Bash-Utility + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: +The following is a set of guidelines for contributing to this project on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +## Table of Contents +- [Code Contributions](#code-contributions) +- [Code Guidelines](#code-guidelines) + - [Styleguide](#styleguide) + - [Bashdoc guideline](#bashdoc-guideline) +- [Documentation](#documentation) +- [Commit Guidelines](#commit-guidelines) +- [Pull Request Guidelines](#pull-request-guidelines) +- [Contact](#contact) + +## Code Contributions + +Great, the more, the merrier. + +Sane code contributions are always welcome, whether to the code or documentation. + +Before making a pull request, make sure to follow below guidelines: + +### Code Guidelines + +#### Styleguide + +- Variable names must be meaningful and self-documenting. +- Long variable names must be structured by underscores to improve legibility. +- Global variables and constants must be ALL CAPS with underscores. (eg., INPUT_FILE) +- local variables used within functions must be all lower case with underscores ( only if required ). (eg., input_data) +- Variable names can be alphanumeric with underscores. No special characters in variable names. +- Variables name must not start with number. +- Variables within function must be declared. So the scope of variable is restricted to the function. +- Avoid accessing global variables within functions. +- Function names must be all lower case with underscores to seperate words (snake_case). +- Function name must start with section name followed by 2 colons and then the function name (eg., `array::contains()`) +- Try using bash builtins and string substitution as much as possible. +- Use printf everywhere instead of echo. +- Before adding a new logic, be sure to check the existing code. +- Make sure to add the function in appropriate section based on its operation. +- Use [shfmt](https://github.com/mvdan/sh) to format the script. Use below command: + + ```shell + shfmt upload.sh + ``` + + The repo already provides the .editorconfig file, which shfmt reads, so no need for extra flags. + You can also install shfmt for various editors, refer their repo for information. + Note: This is strictly necessary to maintain consistency, do not skip. + +- Script should pass all [shellcheck](https://www.shellcheck.net/) warnings, if not, then disable the warning and give a valid reason. + +#### Bashdoc guideline + +The documentation is generated based on the function documentation within the script file. So ensure to follow the style so the documentation is +properly generated by the generator. + +Follow the below bashdoc template to add function introductory comment. + +```bash +# @description Multiline description goes here and +# there +# +# @example +# sample::function a b c +# echo 123 +# +# @arg $1 string Some arg. +# @arg $2 any Rest of arguments. +# +# @noargs +# +# @exitcode 0 If successfull. +# @exitcode >0 On failure +# @exitcode 5 On some error. +# +# @stdout Path to something. +# +# @see sample::other_function(() +sample::function() { +} +``` + +- Each function must have a description detailing what the function does and a sample usage example to show how the function can be used. +- specify whether the function accepts args or no args by specifying @arg or @noargs tag in the comment. +- Make sure to document the exitcode emitted by the function. +- If the function is similar to other function add a reference to function using @see tag. + +### Documentation + +- Refrain from making unnecessary newlines or whitespace. +- Use pure markdown as much as possible, html is accepted but shouldn't be a priority. +- The markdown must pass RemarkLint checks. +- The function documentation and Table of Content is autogenerated using `generate_readme.sh`. So DO NOT edit them manually. Use the following command to update ToC and Bashdoc. + + ```bash + ./bin/generate_readme.sh -f README.md -s src/ + ``` + +### Commit Guidelines + +It is recommended to use small commits over one large commit. Small, focused commits make the review process easier and are more likely to be accepted. + +It is also important to summarise the changes made with brief commit messages. If the commit fixes a specific issue, it is also good to note that in the commit message. + +The commit message should start with a single line that briefly describes the changes. That should be followed by a blank line and then a more detailed explanation. + +Before committing check for unnecessary whitespace with `git diff --check`. + +### Pull Request Guidelines + +The following guidelines will increase the likelihood that your pull request will get accepted: + +- Follow the commit and code guidelines. +- Keep the patches on topic and focused. +- Try to avoid unnecessary formatting and clean-up where reasonable. + +A pull request should contain the following: + +- At least one commit (all of which should follow the Commit Guidelines). +- Title that summarises the issue/feature. +- Description that briefly summarises the changes. + +After submitting a pull request, you should get a response within 7 days. If you do not, don't hesitate to ping the thread. + +## Contact + +For further inquiries, you can contact the developer by opening an issue on the repository. diff --git a/lib/bash-utility-master/LICENSE b/lib/bash-utility-master/LICENSE new file mode 100644 index 000000000..99dd0836b --- /dev/null +++ b/lib/bash-utility-master/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 labbots + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/bash-utility-master/README.md b/lib/bash-utility-master/README.md new file mode 100644 index 000000000..9bc1f9484 --- /dev/null +++ b/lib/bash-utility-master/README.md @@ -0,0 +1,3026 @@ +

Bash Utility

+ +

+Stars +License + +

+

+Gh-pages Status +Website +

+

+Total number of Library functions +

+

+ +

+Bash library which provides utility functions and helpers for functional programming in Bash. + +Detailed documentation is available at + + + +## Table of Contents + +- [Installation](#installation) + - [Method 1 - Git Submodules](#method-1---git-submodules) + - [Method 2 - Git Clone](#method-2---git-clone) + - [Method 3 - Direct Download](#method-3---direct-download) +- [Usage](#usage) +- [Array](#array) + - [array::contains()](#arraycontains) + - [array::dedupe()](#arraydedupe) + - [array::is_empty()](#arrayis_empty) + - [array::join()](#arrayjoin) + - [array::reverse()](#arrayreverse) + - [array::random_element()](#arrayrandom_element) + - [array::sort()](#arraysort) + - [array::rsort()](#arrayrsort) + - [array::bsort()](#arraybsort) + - [array::merge()](#arraymerge) +- [Check](#check) + - [check::command_exists()](#checkcommand_exists) + - [check::is_sudo()](#checkis_sudo) +- [Collection](#collection) + - [collection::each()](#collectioneach) + - [collection::every()](#collectionevery) + - [collection::filter()](#collectionfilter) + - [collection::find()](#collectionfind) + - [collection::invoke()](#collectioninvoke) + - [collection::map()](#collectionmap) + - [collection::reject()](#collectionreject) + - [collection::some()](#collectionsome) +- [Date](#date) + - [date::now()](#datenow) + - [date::epoc()](#dateepoc) + - [date::add_days_from()](#dateadd_days_from) + - [date::add_months_from()](#dateadd_months_from) + - [date::add_years_from()](#dateadd_years_from) + - [date::add_weeks_from()](#dateadd_weeks_from) + - [date::add_hours_from()](#dateadd_hours_from) + - [date::add_minutes_from()](#dateadd_minutes_from) + - [date::add_seconds_from()](#dateadd_seconds_from) + - [date::add_days()](#dateadd_days) + - [date::add_months()](#dateadd_months) + - [date::add_years()](#dateadd_years) + - [date::add_weeks()](#dateadd_weeks) + - [date::add_hours()](#dateadd_hours) + - [date::add_minutes()](#dateadd_minutes) + - [date::add_seconds()](#dateadd_seconds) + - [date::sub_days_from()](#datesub_days_from) + - [date::sub_months_from()](#datesub_months_from) + - [date::sub_years_from()](#datesub_years_from) + - [date::sub_weeks_from()](#datesub_weeks_from) + - [date::sub_hours_from()](#datesub_hours_from) + - [date::sub_minutes_from()](#datesub_minutes_from) + - [date::sub_seconds_from()](#datesub_seconds_from) + - [date::sub_days()](#datesub_days) + - [date::sub_months()](#datesub_months) + - [date::sub_years()](#datesub_years) + - [date::sub_weeks()](#datesub_weeks) + - [date::sub_hours()](#datesub_hours) + - [date::sub_minutes()](#datesub_minutes) + - [date::sub_seconds()](#datesub_seconds) + - [date::format()](#dateformat) +- [Debug](#debug) + - [debug::print_array()](#debugprint_array) + - [debug::print_ansi()](#debugprint_ansi) +- [File](#file) + - [file::make_temp_file()](#filemake_temp_file) + - [file::make_temp_dir()](#filemake_temp_dir) + - [file::name()](#filename) + - [file::basename()](#filebasename) + - [file::extension()](#fileextension) + - [file::dirname()](#filedirname) + - [file::full_path()](#filefull_path) + - [file::mime_type()](#filemime_type) + - [file::contains_text()](#filecontains_text) +- [Format](#format) + - [format::human_readable_seconds()](#formathuman_readable_seconds) + - [format::bytes_to_human()](#formatbytes_to_human) + - [format::strip_ansi()](#formatstrip_ansi) + - [format::text_center()](#formattext_center) + - [format::report()](#formatreport) + - [format::trim_text_to_term()](#formattrim_text_to_term) +- [Interaction](#interaction) + - [interaction::prompt_yes_no()](#interactionprompt_yes_no) + - [interaction::prompt_response()](#interactionprompt_response) +- [Json](#json) + - [json::get_value()](#jsonget_value) +- [Miscellaneous](#miscellaneous) + - [misc::check_internet_connection()](#misccheck_internet_connection) + - [misc::get_pid()](#miscget_pid) + - [misc::get_uid()](#miscget_uid) + - [misc::generate_uuid()](#miscgenerate_uuid) +- [Operating System](#operating-system) + - [os::detect_os()](#osdetect_os) + - [os::detect_linux_distro()](#osdetect_linux_distro) + - [os::detect_linux_version()](#osdetect_linux_version) + - [os::detect_mac_version()](#osdetect_mac_version) +- [String](#string) + - [string::trim()](#stringtrim) + - [string::split()](#stringsplit) + - [string::lstrip()](#stringlstrip) + - [string::rstrip()](#stringrstrip) + - [string::to_lower()](#stringto_lower) + - [string::to_upper()](#stringto_upper) + - [string::contains()](#stringcontains) + - [string::starts_with()](#stringstarts_with) + - [string::ends_with()](#stringends_with) + - [string::regex()](#stringregex) +- [Terminal](#terminal) + - [terminal::is_term()](#terminalis_term) + - [terminal::detect_profile()](#terminaldetect_profile) + - [terminal::clear_line()](#terminalclear_line) +- [Validation](#validation) + - [validation::email()](#validationemail) + - [validation::ipv4()](#validationipv4) + - [validation::ipv6()](#validationipv6) + - [validation::alpha()](#validationalpha) + - [validation::alpha_num()](#validationalpha_num) + - [validation::alpha_dash()](#validationalpha_dash) + - [validation::version_comparison()](#validationversion_comparison) +- [Variable](#variable) + - [variable::is_array()](#variableis_array) + - [variable::is_numeric()](#variableis_numeric) + - [variable::is_int()](#variableis_int) + - [variable::is_float()](#variableis_float) + - [variable::is_bool()](#variableis_bool) + - [variable::is_true()](#variableis_true) + - [variable::is_false()](#variableis_false) + - [variable::is_empty_or_null()](#variableis_empty_or_null) +- [Inspired By](#inspired-by) +- [License](#license) + + +## Installation +The script can be installed and sourced using following methods. + +### Method 1 - Git Submodules +If the library is used inside a git project then git submodules can be used to install the library to the project. +Following command will initialize git submodule and download the library to `./vendor/bash-utility` folder. + +```shell +git submodule init +git submodule add -b master https://github.com/labbots/bash-utility vendor/bash-utility +``` + +To Update submodules to latest code execute the following command. + +```shell +git submodule update --rebase --remote +``` +Once the submodule is added or updated, make sure to commit changes to your repository. + +```shell +git add . +git commit -m 'Added/updated bash-utility library.' +``` +**Note:** When cloning your repository, use `--recurse-submodules` flag to `git clone` command to install the git sub modules. + +### Method 2 - Git Clone +If you don't want to use git submodules, you can use `git clone` to download library and then move the files to desired location manually. + +The below command will clone the repository to `vendor/bash-utility` folder in current working directory. + +```shell +git clone https://github.com/labbots/bash-utility.git ./vendor/bash-utility +``` +### Method 3 - Direct Download +If you do not have git installed, you can download the archive of the latest version of the library. Extract the zip file to appropriate folder by following the below command. + +```shell +wget https://github.com/labbots/bash-utility/archive/master.zip +unzip -q master.zip -d tmp +mkdir -p vendor/bash-utility +mv tmp/bash-utility-master vendor/bash-utility +rm tmp +``` + +## Usage +Bash utility functions can be used by simply sourcing the library script file to your own script. +To access all the functions within the bash-utility library, you could import the main bash file as follows. + +```shell +source "vendor/bash-utility/bash-utility.sh" +``` + +You can also only use the necessary library functions by only importing the required function files. + +```shell +source "vendor/bash-utility/src/array.sh" +``` + + + +## Array + +Functions for array operations and manipulations. + +### array::contains() + +Check if item exists in the given array. + +#### Arguments + +- **$1** (mixed): Item to search (needle). +- **$2** (array): array to be searched (haystack). + +#### Exit codes + +- **0**: If successful. +- **1**: If no match found in the array. +- **2**: Function missing arguments. + +#### Example + +```bash +array=("a" "b" "c") +array::contains "c" ${array[@]} +#Output +0 +``` + +### array::dedupe() + +Remove duplicate items from the array. + +#### Arguments + +- **$1** (array): Array to be deduped. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Deduplicated array. + +#### Example + +```bash +array=("a" "b" "a" "c") +printf "%s" "$(array::dedupe ${array[@]})" +#Output +a +b +c +``` + +### array::is_empty() + +Check if a given array is empty. + +#### Arguments + +- **$1** (array): Array to be checked. + +#### Exit codes + +- **0**: If the given array is empty. +- **2**: If the given array is not empty. + +#### Example + +```bash +array=("a" "b" "c" "d") +array::is_empty "${array[@]}" +``` + +### array::join() + +Join array elements with a string. + +#### Arguments + +- **$1** (string): String to join the array elements (glue). +- **$2** (array): array to be joined with glue string. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- String containing a string representation of all the array elements in the same order,with the glue string between each element. + +#### Example + +```bash +array=("a" "b" "c" "d") +printf "%s" "$(array::join "," "${array[@]}")" +#Output +a,b,c,d +printf "%s" "$(array::join "" "${array[@]}")" +#Output +abcd +``` + +### array::reverse() + +Return an array with elements in reverse order. + +#### Arguments + +- **$1** (array): The input array. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- The reversed array. + +#### Example + +```bash +array=(1 2 3 4 5) +printf "%s" "$(array::reverse "${array[@]}")" +#Output +5 4 3 2 1 +``` + +### array::random_element() + +Returns a random item from the array. + +#### Arguments + +- **$1** (array): The input array. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Random item out of the array. + +#### Example + +```bash +array=("a" "b" "c" "d") +printf "%s\n" "$(array::random_element "${array[@]}")" +#Output +c +``` + +### array::sort() + +Sort an array from lowest to highest. + +#### Arguments + +- **$1** (array): The input array. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- sorted array. + +#### Example + +```bash +sarr=("a c" "a" "d" 2 1 "4 5") +array::array_sort "${sarr[@]}" +#Output +1 +2 +4 5 +a +a c +d +``` + +### array::rsort() + +Sort an array in reverse order (highest to lowest). + +#### Arguments + +- **$1** (array): The input array. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- reverse sorted array. + +#### Example + +```bash +sarr=("a c" "a" "d" 2 1 "4 5") +array::array_sort "${sarr[@]}" +#Output +d +a c +a +4 5 +2 +1 +``` + +### array::bsort() + +Bubble sort an integer array from lowest to highest. +This sort does not work on string array. + +#### Arguments + +- **$1** (array): The input array. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- bubble sorted array. + +#### Example + +```bash +iarr=(4 5 1 3) +array::bsort "${iarr[@]}" +#Output +1 +3 +4 +5 +``` + +### array::merge() + +Merge two arrays. +Pass the variable name of the array instead of value of the variable. + +#### Arguments + +- **$1** (string): variable name of first array. +- **$2** (string): variable name of second array. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Merged array. + +#### Example + +```bash +a=("a" "c") +b=("d" "c") +array::merge "a[@]" "b[@]" +#Output +a +c +d +c +``` + +## Check + +Helper functions. + +### check::command_exists() + +Check if the command exists in the system. + +#### Arguments + +- **$1** (string): Command name to be searched. + +#### Exit codes + +- **0**: If the command exists. +- **1**: If the command does not exist. +- **2**: Function missing arguments. + +#### Example + +```bash +check::command_exists "tput" +``` + +### check::is_sudo() + +Check if the script is executed with sudo privilege. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If the script is executed with root privilege. +- **1**: If the script is not executed with root privilege + +#### Example + +```bash +check::is_sudo +``` + +## Collection + +(Experimental) Functions to iterates over a list of elements, yielding each in turn to an iteratee function. + +### collection::each() + +Iterates over elements of collection and invokes iteratee for each element. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. +- other exitcode returned by iteratee. + +#### Output on stdout + +- Output of iteratee function. + +#### Example + +```bash +test_func(){ + printf "print value: %s\n" "$1" + return 0 + } +arr1=("a b" "c d" "a" "d") +printf "%s\n" "${arr1[@]}" | collection::each "test_func" +collection::each "test_func" < <(printf "%s\n" "${arr1[@]}") #alternative approach +#output + print value: a b + print value: c d + print value: a + print value: d +``` + +#### Example + +```bash +# If other function from this library is already used to process the array. +# Then following method could be used to pass the array to the function. +out=("$(array::dedupe "${arr1[@]}")") +collection::each "test_func" <<< "${out[@]}" +``` + +### collection::every() + +Checks if iteratee function returns truthy for all elements of collection. Iteration is stopped once predicate returns false. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **1**: If iteratee function fails. +- **2**: Function missing arguments. + +#### Example + +```bash +arri=("1" "2" "3" "4") +printf "%s\n" "${arri[@]}" | collection::every "variable::is_numeric" +``` + +### collection::filter() + +Iterates over elements of array, returning all elements where iteratee returns true. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- array values matching the iteratee function. + +#### Example + +```bash +arri=("1" "2" "3" "a") +printf "%s\n" "${arri[@]}" | collection::filter "variable::is_numeric" +#output +1 +2 +3 +``` + +### collection::find() + +Iterates over elements of collection, returning the first element where iteratee returns true. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **1**: If no match found. +- **2**: Function missing arguments. + +#### Output on stdout + +- first array value matching the iteratee function. + +#### Example + +```bash +arr=("1" "2" "3" "a") +check_a(){ + [[ "$1" = "a" ]] +} +printf "%s\n" "${arr[@]}" | collection::find "check_a" +#Output +a +``` + +### collection::invoke() + +Invokes the iteratee with each element passed as argument to the iteratee. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. +- other exitcode returned by iteratee. + +#### Output on stdout + +- Output from the iteratee function. + +#### Example + +```bash +opt=("-a" "-l") +printf "%s\n" "${opt[@]}" | collection::invoke "ls" +``` + +### collection::map() + +Creates an array of values by running each element in array through iteratee. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. +- other exitcode returned by iteratee. + +#### Output on stdout + +- Output result of iteratee on value. + +#### Example + +```bash +arri=("1" "2" "3") +add_one(){ + i=${1} + i=$(( i + 1 )) + printf "%s\n" "$i" +} +printf "%s\n" "${arri[@]}" | collection::map "add_one" +``` + +### collection::reject() + +The opposite of filter function; this method returns the elements of collection that iteratee does not return true. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- array values not matching the iteratee function. + +#### Example + +```bash +arri=("1" "2" "3" "a") +printf "%s\n" "${arri[@]}" | collection::reject "variable::is_numeric" +#Ouput +a +``` + +#### See also + +- [collection::filter](#collectionfilter) + +### collection::some() + +Checks if iteratee returns true for any element of the array. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): Iteratee function. + +#### Exit codes + +- **0**: If match successful. +- **1**: If no match found. +- **2**: Function missing arguments. + +#### Example + +```bash +arr=("a" "b" "3" "a") +printf "%s\n" "${arr[@]}" | collection::reject "variable::is_numeric" +``` + +## Date + +Functions for manipulating dates. + +### date::now() + +Get current time in unix timestamp. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- current timestamp. + +#### Example + +```bash +echo "$(date::now)" +#Output +1591554426 +``` + +### date::epoc() + +convert datetime string to unix timestamp. + +#### Arguments + +- **$1** (string): date time in any format. + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp for specified datetime. + +#### Example + +```bash +echo "$(date::epoc "2020-07-07 18:38")" +#Output +1594143480 +``` + +### date::add_days_from() + +Add number of days from specified timestamp. +If number of days not specified then it defaults to 1 day. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of days (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_days_from "1594143480")" +#Output +1594229880 +``` + +### date::add_months_from() + +Add number of months from specified timestamp. +If number of months not specified then it defaults to 1 month. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of months (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_months_from "1594143480")" +#Output +1596821880 +``` + +### date::add_years_from() + +Add number of years from specified timestamp. +If number of years not specified then it defaults to 1 year. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of years (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_years_from "1594143480")" +#Output +1625679480 +``` + +### date::add_weeks_from() + +Add number of weeks from specified timestamp. +If number of weeks not specified then it defaults to 1 week. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of weeks (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_weeks_from "1594143480")" +#Output +1594748280 +``` + +### date::add_hours_from() + +Add number of hours from specified timestamp. +If number of hours not specified then it defaults to 1 hour. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of hours (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_hours_from "1594143480")" +#Output +1594147080 +``` + +### date::add_minutes_from() + +Add number of minutes from specified timestamp. +If number of minutes not specified then it defaults to 1 minute. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of minutes (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_minutes_from "1594143480")" +#Output +1594143540 +``` + +### date::add_seconds_from() + +Add number of seconds from specified timestamp. +If number of seconds not specified then it defaults to 1 second. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of seconds (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_seconds_from "1594143480")" +#Output +1594143481 +``` + +### date::add_days() + +Add number of days from current day timestamp. +If number of days not specified then it defaults to 1 day. + +#### Arguments + +- **$1** (int): number of days (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_days "1")" +#Output +1591640826 +``` + +### date::add_months() + +Add number of months from current day timestamp. +If number of months not specified then it defaults to 1 month. + +#### Arguments + +- **$1** (int): number of months (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_months "1")" +#Output +1594146426 +``` + +### date::add_years() + +Add number of years from current day timestamp. +If number of years not specified then it defaults to 1 year. + +#### Arguments + +- **$1** (int): number of years (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_years "1")" +#Output +1623090426 +``` + +### date::add_weeks() + +Add number of weeks from current day timestamp. +If number of weeks not specified then it defaults to 1 year. + +#### Arguments + +- **$1** (int): number of weeks (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_weeks "1")" +#Output +1592159226 +``` + +### date::add_hours() + +Add number of hours from current day timestamp. +If number of hours not specified then it defaults to 1 hour. + +#### Arguments + +- **$1** (int): number of hours (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_hours "1")" +#Output +1591558026 +``` + +### date::add_minutes() + +Add number of minutes from current day timestamp. +If number of minutes not specified then it defaults to 1 minute. + +#### Arguments + +- **$2** (int): number of minutes (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_minutes "1")" +#Output +1591554486 +``` + +### date::add_seconds() + +Add number of seconds from current day timestamp. +If number of seconds not specified then it defaults to 1 second. + +#### Arguments + +- **$2** (int): number of seconds (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::add_seconds "1")" +#Output +1591554427 +``` + +### date::sub_days_from() + +Subtract number of days from specified timestamp. +If number of days not specified then it defaults to 1 day. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of days (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_days_from "1594143480")" +#Output +1594057080 +``` + +### date::sub_months_from() + +Subtract number of months from specified timestamp. +If number of months not specified then it defaults to 1 month. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of months (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_months_from "1594143480")" +#Output +1591551480 +``` + +### date::sub_years_from() + +Subtract number of years from specified timestamp. +If number of years not specified then it defaults to 1 year. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of years (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_years_from "1594143480")" +#Output +1562521080 +``` + +### date::sub_weeks_from() + +Subtract number of weeks from specified timestamp. +If number of weeks not specified then it defaults to 1 week. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of weeks (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_weeks_from "1594143480")" +#Output +1593538680 +``` + +### date::sub_hours_from() + +Subtract number of hours from specified timestamp. +If number of hours not specified then it defaults to 1 hour. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of hours (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_hours_from "1594143480")" +#Output +1594139880 +``` + +### date::sub_minutes_from() + +Subtract number of minutes from specified timestamp. +If number of minutes not specified then it defaults to 1 minute. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of minutes (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_minutes_from "1594143480")" +#Output +1594143420 +``` + +### date::sub_seconds_from() + +Subtract number of seconds from specified timestamp. +If number of seconds not specified then it defaults to 1 second. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (int): number of seconds (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. +- **2**: Function missing arguments. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_seconds_from "1594143480")" +#Output +1594143479 +``` + +### date::sub_days() + +Subtract number of days from current day timestamp. +If number of days not specified then it defaults to 1 day. + +#### Arguments + +- **$1** (int): number of days (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_days "1")" +#Output +1588876026 +``` + +### date::sub_months() + +Subtract number of months from current day timestamp. +If number of months not specified then it defaults to 1 month. + +#### Arguments + +- **$1** (int): number of months (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_months "1")" +#Output +1559932026 +``` + +### date::sub_years() + +Subtract number of years from current day timestamp. +If number of years not specified then it defaults to 1 year. + +#### Arguments + +- **$1** (int): number of years (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_years "1")" +#Output +1591468026 +``` + +### date::sub_weeks() + +Subtract number of weeks from current day timestamp. +If number of weeks not specified then it defaults to 1 week. + +#### Arguments + +- **$1** (int): number of weeks (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_weeks "1")" +#Output +1590949626 +``` + +### date::sub_hours() + +Subtract number of hours from current day timestamp. +If number of hours not specified then it defaults to 1 hour. + +#### Arguments + +- **$1** (int): number of hours (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_hours "1")" +#Output +1591550826 +``` + +### date::sub_minutes() + +Subtract number of minutes from current day timestamp. +If number of minutes not specified then it defaults to 1 minute. + +#### Arguments + +- **$1** (int): number of minutes (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_minutes "1")" +#Output +1591554366 +``` + +### date::sub_seconds() + +Subtract number of seconds from current day timestamp. +If number of seconds not specified then it defaults to 1 second. + +#### Arguments + +- **$1** (int): number of seconds (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate timestamp. + +#### Output on stdout + +- timestamp. + +#### Example + +```bash +echo "$(date::sub_seconds "1")" +#Output +1591554425 +``` + +### date::format() + +Format unix timestamp to human readable format. +If format string is not specified then it defaults to "yyyy-mm-dd hh:mm:ss" format. + +#### Arguments + +- **$1** (int): unix timestamp. +- **$2** (string): format control characters based on `date` command (optional). + +#### Exit codes + +- **0**: If successful. +- **1**: If unable to generate time string. +- **2**: Function missing arguments. + +#### Output on stdout + +- formatted time string. + +#### Example + +```bash +echo echo "$(date::format "1594143480")" +#Output +2020-07-07 18:38:00 +``` + +## Debug + +Functions to facilitate debugging scripts. + +### debug::print_array() + +Prints the content of array as key value pair for easier debugging. +Pass the variable name of the array instead of value of the variable. + +#### Arguments + +- **$1** (string): variable name of the array. + +#### Output on stdout + +- Formatted key value of array. + +#### Example + +```bash +array=(foo bar baz) +printf "Array\n" +printarr "array" +declare -A assoc_array +assoc_array=([foo]=bar [baz]=foobar) +printf "Assoc Array\n" +printarr "assoc_array" +#Output +Array +0 = foo +1 = bar +2 = baz +Assoc Array +baz = foobar +foo = bar +``` + +### debug::print_ansi() + +Function to print ansi escape sequence as is. +This function helps debug ansi escape sequence in text by displaying the escape codes. + +#### Arguments + +- **$1** (string): input with ansi escape sequence. + +#### Output on stdout + +- Ansi escape sequence printed in output as is. + +#### Example + +```bash +txt="$(tput bold)$(tput setaf 9)This is bold red text$(tput sgr0).$(tput setaf 10)This is green text$(tput sgr0)" +debug::print_ansi "$txt" +#Output +\e[1m\e[91mThis is bold red text\e(B\e[m.\e[92mThis is green text\e(B\e[m +``` + +## File + +Functions for handling files. + +### file::make_temp_file() + +Create temporary file. +Function creates temporary file with random name. The temporary file will be deleted when script finishes. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If successful. +- **1**: If failed to create temp file. + +#### Output on stdout + +- file name of temporary file created. + +#### Example + +```bash +echo "$(file::make_temp_file)" +#Output +tmp.vgftzy +``` + +### file::make_temp_dir() + +Create temporary directory. +Function creates temporary directory with random name. The temporary directory will be deleted when script finishes. + +#### Arguments + +- **$1** (string): Temporary directory prefix +- $2 string Flag to auto remove directory on exit trap (true) + +#### Exit codes + +- **0**: If successful. +- **1**: If failed to create temp directory. +- **2**: Missing arguments. + +#### Output on stdout + +- directory name of temporary directory created. + +#### Example + +```bash +echo "$(utility::make_temp_dir)" +#Output +tmp.rtfsxy +``` + +### file::name() + +Get only the filename from string path. + +#### Arguments + +- **$1** (string): path. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- name of the file with extension. + +#### Example + +```bash +echo "$(file::name "/path/to/test.md")" +#Output +test.md +``` + +### file::basename() + +Get the basename of file from file name. + +#### Arguments + +- **$1** (string): path. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- basename of the file. + +#### Example + +```bash +echo "$(file::basename "/path/to/test.md")" +#Output +test +``` + +### file::extension() + +Get the extension of file from file name. + +#### Arguments + +- **$1** (string): path. + +#### Exit codes + +- **0**: If successful. +- **1**: If no extension is found in the filename. +- **2**: Function missing arguments. + +#### Output on stdout + +- extension of the file. + +#### Example + +```bash +echo "$(file::extension "/path/to/test.md")" +#Output +md +``` + +### file::dirname() + +Get directory name from file path. + +#### Arguments + +- **$1** (string): path. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- directory path. + +#### Example + +```bash +echo "$(file::dirname "/path/to/test.md")" +#Output +/path/to +``` + +### file::full_path() + +Get absolute path of file or directory. + +#### Arguments + +- **$1** (string): relative or absolute path to file/direcotry. + +#### Exit codes + +- **0**: If successful. +- **1**: If file/directory does not exist. +- **2**: Function missing arguments. + +#### Output on stdout + +- Absolute path to file/directory. + +#### Example + +```bash +file::full_path "../path/to/file.md" +#Output +/home/labbots/docs/path/to/file.md +``` + +### file::mime_type() + +Get mime type of provided input. + +#### Arguments + +- **$1** (string): relative or absolute path to file/directory. + +#### Exit codes + +- **0**: If successful. +- **1**: If file/directory does not exist. +- **2**: Function missing arguments. +- **3**: If file or mimetype command not found in system. + +#### Output on stdout + +- mime type of file/directory. + +#### Example + +```bash +file::mime_type "../src/file.sh" +#Output +application/x-shellscript +``` + +### file::contains_text() + +Search if a given pattern is found in file. + +#### Arguments + +- **$1** (string): relative or absolute path to file/directory. +- **$2** (string): search key or regular expression. + +#### Exit codes + +- **0**: If given search parameter is found in file. +- **1**: If search paramter not found in file. +- **2**: Function missing arguments. + +#### Example + +```bash +file::contains_text "./file.sh" "^[ @[:alpha:]]*" +file::contains_text "./file.sh" "@file" +#Output +0 +``` + +## Format + +Functions to format provided input. + +### format::human_readable_seconds() + +Format seconds to human readable format. + +#### Arguments + +- **$1** (int): number of seconds. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- formatted time string. + +#### Example + +```bash +echo "$(format::human_readable_seconds "356786")" +#Output +4 days 3 hours 6 minute(s) and 26 seconds +``` + +### format::bytes_to_human() + +Format bytes to human readable format. + +#### Arguments + +- **$1** (int): size in bytes. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- formatted file size string. + +#### Example + +```bash +echo "$(format::bytes_to_human "2250")" +#Output +2.19 KB +``` + +### format::strip_ansi() + +Remove Ansi escape sequences from given text. + +#### Arguments + +- **$1** (string): Input text to be ansi stripped. + +#### Exit codes + +- **0**: If successful. + +#### Output on stdout + +- Ansi stripped text. + +#### Example + +```bash +format::strip_ansi "\e[1m\e[91mThis is bold red text\e(B\e[m.\e[92mThis is green text.\e(B\e[m" +#Output +This is bold red text.This is green text. +``` + +### format::text_center() + +Prints the given text to centre of terminal. + +#### Arguments + +- **$1** (string): Text to be printed. +- **$2** (string): Filler symbol to be added to prefix and suffix of the text (optional). Defaults to space as filler. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- formatted text. + +#### Example + +```bash +format::text_center "This text is in centre of the terminal." "-" +``` + +### format::report() + +Format String to print beautiful report. + +#### Arguments + +- **$1** (string): Text to be printed on the left. +- **$2** (string): Text to be printed within the square brackets. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- formatted text. + +#### Example + +```bash +format::report "Initialising mission state" "Success" +#Output +Initialising mission state ....................................................................[ Success ] +``` + +### format::trim_text_to_term() + +Trim given text to width of the terminal window. + +#### Arguments + +- **$1** (string): Text of first sentence. +- **$2** (string): Text of second sentence (optional). + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- trimmed text. + +#### Example + +```bash +format::trim_text_to_term "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." "This is part of second sentence." +#Output +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod..This is part of second sentence. +``` + +## Interaction + +Functions to enable interaction with the user. + +### interaction::prompt_yes_no() + +Prompt yes or no question to the user. + +#### Arguments + +- **$1** (string): The question to be prompted to the user. +- **$2** (string): default answer \[yes/no\] (optional). + +#### Exit codes + +- **0**: If user responds with yes. +- **1**: If user responds with no. +- **2**: Function missing arguments. + +#### Example + +```bash +interaction::prompt_yes_no "Are you sure to proceed" "yes" +#Output +Are you sure to proceed (y/n)? [y] +``` + +### interaction::prompt_response() + +Prompt question to the user. + +#### Arguments + +- **$1** (string): The question to be prompted to the user. +- **$2** (string): default answer (optional). + +#### Exit codes + +- **0**: If user responds with answer. +- **2**: Function missing arguments. + +#### Output on stdout + +- User entered answer to the question. + +#### Example + +```bash +interaction::prompt_response "Choose directory to install" "/home/path" +#Output +Choose directory to install? [/home/path] +``` + +## Json + +Simple json manipulation. These functions does not completely replace `jq` in any way. + +### json::get_value() + +Extract value from json based on key and position. +Input to the function can be a pipe output, here-string or file. + +#### Arguments + +- **$1** (string): id of the field to fetch. +- **$2** (int): position of value to extract.Defaults to 1.(optional) + +#### Exit codes + +- **0**: If match successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- string value of extracted key. + +#### Example + +```bash +json::get_value "id" "1" < json_file +json::get_value "id" <<< "${json_var}" +echo "{\"data\":{\"id\":\"123\",\"value\":\"name string\"}}" | json::get_value "id" +``` + +## Miscellaneous + +Set of miscellaneous helper functions. + +### misc::check_internet_connection() + +Check if internet connection is available. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If script can connect to internet. +- **1**: If script cannot access internet. + +#### Example + +```bash +misc::check_internet_connection +``` + +### misc::get_pid() + +Get list of process ids based on process name. + +#### Arguments + +- **$1** (Name): of the process to search. + +#### Exit codes + +- **0**: If match successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- list of process ids. + +#### Example + +```bash +misc::get_pid "chrome" +#Ouput +25951 +26043 +26528 +26561 +``` + +### misc::get_uid() + +Get user id based on username. + +#### Arguments + +- **$1** (username): to search. + +#### Exit codes + +- **0**: If match successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- string uid for the username. + +#### Example + +```bash +misc::get_uid "labbots" +#Ouput +1000 +``` + +### misc::generate_uuid() + +Generate random uuid. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If match successful. + +#### Output on stdout + +- random generated uuid. + +#### Example + +```bash +misc::generate_uuid +#Ouput +65bc64d1-d355-4ffc-a9d9-dc4f3954c34c +``` + +## Operating System + +Functions to detect Operating system and version. + +### os::detect_os() + +Identify the OS the function is run on. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If OS is successfully detected. +- **1**: If unable to detect OS. + +#### Output on stdout + +- Operating system name (linux, mac or windows). + +#### Example + +```bash +os::detect_os +#Output +linux +``` + +### os::detect_linux_distro() + +Identify the distribution flavour of linux. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If Linux distro is successfully detected. +- **1**: If unable to detect OS distro. + +#### Output on stdout + +- Linux OS distribution name (ubuntu, debian, suse, etc.,). + +#### Example + +```bash +os::detect_linux_distro +#Output +ubuntu +``` + +### os::detect_linux_version() + +Identify the Linux version. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If Linux version is successfully detected. +- **1**: If unable to detect Linux version. + +#### Output on stdout + +- Linux OS version number (18.04, 20.04, etc.,). + +#### Example + +```bash +os::detect_linux_version +#Output +20.04 +``` + +### os::detect_mac_version() + +Identify the MacOS version. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If MacOS version is successfully detected. +- **1**: If unable to detect MacOS version. + +#### Output on stdout + +- MacOS version number (10.15.6, etc.,) + +#### Example + +```bash +os::detect_linux_version +#Output +10.15.7 +``` + +## String + +Functions for string operations and manipulations. + +### string::trim() + +Strip whitespace from the beginning and end of a string. + +#### Arguments + +- **$1** (string): The string to be trimmed. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- The trimmed string. + +#### Example + +```bash +echo "$(string::trim " Hello World! ")" +#Output +Hello World! +``` + +### string::split() + +Split a string to array by a delimiter. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The delimiter string. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Returns an array of strings created by splitting the string parameter by the delimiter. + +#### Example + +```bash +array=( $(string::split "a,b,c" ",") ) +printf "%s" "$(string::split "Hello!World" "!")" +#Output +Hello +World +``` + +### string::lstrip() + +Strip characters from the beginning of a string. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The characters you want to strip. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Returns the modified string. + +#### Example + +```bash +echo "$(string::lstrip "Hello World!" "He")" +#Output +llo World! +``` + +### string::rstrip() + +Strip characters from the end of a string. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The characters you want to strip. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Returns the modified string. + +#### Example + +```bash +echo "$(string::rstrip "Hello World!" "d!")" +#Output +Hello Worl +``` + +### string::to_lower() + +Make a string lowercase. + +#### Arguments + +- **$1** (string): The input string. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Returns the lowercased string. + +#### Example + +```bash +echo "$(string::to_lower "HellO")" +#Output +hello +``` + +### string::to_upper() + +Make a string all uppercase. + +#### Arguments + +- **$1** (string): The input string. + +#### Exit codes + +- **0**: If successful. +- **2**: Function missing arguments. + +#### Output on stdout + +- Returns the uppercased string. + +#### Example + +```bash +echo "$(string::to_upper "HellO")" +#Output +HELLO +``` + +### string::contains() + +Check whether the search string exists within the input string. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The search key. + +#### Exit codes + +- **0**: If match found. +- **1**: If no match found. +- **2**: Function missing arguments. + +#### Example + +```bash +string::contains "Hello World!" "lo" +``` + +### string::starts_with() + +Check whether the input string starts with key string. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The search key. + +#### Exit codes + +- **0**: If match found. +- **1**: If no match found. +- **2**: Function missing arguments. + +#### Example + +```bash +string::starts_with "Hello World!" "He" +``` + +### string::ends_with() + +Check whether the input string ends with key string. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The search key. + +#### Exit codes + +- **0**: If match found. +- **1**: If no match found. +- **2**: Function missing arguments. + +#### Example + +```bash +string::ends_with "Hello World!" "d!" +``` + +### string::regex() + +Check whether the input string matches the given regex. + +#### Arguments + +- **$1** (string): The input string. +- **$2** (string): The search key. + +#### Exit codes + +- **0**: If match found. +- **1**: If no match found. +- **2**: Function missing arguments. + +#### Example + +```bash +string::regex "HELLO" "^[A-Z]*$" +``` + +## Terminal + +Set of useful terminal functions. + +### terminal::is_term() + +Check if script is run in terminal. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If script is run on terminal. +- **1**: If script is not run on terminal. + +### terminal::detect_profile() + +Detect profile rc file for zsh and bash of current script running user. + +*Function has no arguments.* + +#### Exit codes + +- **0**: If script is run on terminal. +- **1**: If script is not run on terminal. + +#### Output on stdout + +- path to the profile file. + +### terminal::clear_line() + +Clear the output in terminal on the specified line number. +This function clears line only on terminal. + +#### Arguments + +- **$1** (Line): number to clear. Defaults to 1. (optional) + +#### Exit codes + +- **0**: If script is run on terminal. + +#### Output on stdout + +- clear line ansi code. + +## Validation + +Functions to perform validation on given data. + +### validation::email() + +Validate whether a given input is a valid email address or not. + +#### Arguments + +- **$1** (string): input email address to validate. + +#### Exit codes + +- **0**: If provided input is an email address. +- **1**: If provided input is not an email address. +- **2**: Function missing arguments. + +#### Example + +```bash +test='test@gmail.com' +validation::email "${test}" +echo $? +#Output +0 +``` + +### validation::ipv4() + +Validate whether a given input is a valid IP V4 address. + +#### Arguments + +- **$1** (string): input IPv4 address. + +#### Exit codes + +- **0**: If provided input is a valid IPv4. +- **1**: If provided input is not a valid IPv4. +- **2**: Function missing arguments. + +#### Example + +```bash +ips=' + 4.2.2.2 + a.b.c.d + 192.168.1.1 + 0.0.0.0 + 255.255.255.255 + 255.255.255.256 + 192.168.0.1 + 192.168.0 + 1234.123.123.123 + 0.192.168.1 + ' +for ip in $ips; do + if validation::ipv4 $ip; then stat='good'; else stat='bad'; fi + printf "%-20s: %s\n" "$ip" "$stat" +done +#Output +4.2.2.2 : good +a.b.c.d : bad +192.168.1.1 : good +0.0.0.0 : good +255.255.255.255 : good +255.255.255.256 : bad +192.168.0.1 : good +192.168.0 : bad +1234.123.123.123 : bad +0.192.168.1 : good +``` + +### validation::ipv6() + +Validate whether a given input is a valid IP V6 address. + +#### Arguments + +- **$1** (string): input IPv6 address. + +#### Exit codes + +- **0**: If provided input is a valid IPv6. +- **1**: If provided input is not a valid IPv6. +- **2**: Function missing arguments. + +#### Example + +```bash +ips=' + 2001:db8:85a3:8d3:1319:8a2e:370:7348 + fe80::1ff:fe23:4567:890a + fe80::1ff:fe23:4567:890a%eth2 + 2001:0db8:85a3:0000:0000:8a2e:0370:7334:foo:bar + fezy::1ff:fe23:4567:890a + :: + 2001:db8:: + ' +for ip in $ips; do + if validation::ipv6 $ip; then stat='good'; else stat='bad'; fi + printf "%-50s= %s\n" "$ip" "$stat" +done +#Output +2001:db8:85a3:8d3:1319:8a2e:370:7348 = good +fe80::1ff:fe23:4567:890a = good +fe80::1ff:fe23:4567:890a%eth2 = good +2001:0db8:85a3:0000:0000:8a2e:0370:7334:foo:bar = bad +fezy::1ff:fe23:4567:890a = bad +:: = good +2001:db8:: = good +``` + +### validation::alpha() + +Validate if given variable is entirely alphabetic characters. + +#### Arguments + +- **$1** (string): Value of variable to validate. + +#### Exit codes + +- **0**: If input is only alpha characters. +- **1**: If input contains any non alpha characters. +- **2**: Function missing arguments. + +#### Example + +```bash +test='abcABC' +validation::alpha "${test}" +echo $? +#Output +0 +``` + +### validation::alpha_num() + +Check if given variable contains only alpha-numeric characters. + +#### Arguments + +- **$1** (string): Value of variable to validate. + +#### Exit codes + +- **0**: If input is an alpha-numeric. +- **1**: If input is not an alpha-numeric. +- **2**: Function missing arguments. + +#### Example + +```bash +test='abc123' +validation::alpha_num "${test}" +echo $? +#Output +0 +``` + +### validation::alpha_dash() + +Validate if given variable contains only alpha-numeric characters, as well as dashes and underscores. + +#### Arguments + +- **$1** (string): Value of variable to validate. + +#### Exit codes + +- **0**: If input is valid. +- **1**: If input the input is not valid. +- **2**: Function missing arguments. + +#### Example + +```bash +test='abc-ABC_cD' +validation::alpha_dash "${test}" +echo $? +#Output +0 +``` + +### validation::version_comparison() + +Compares version numbers and provides return based on whether the value in equal, less than or greater. + +#### Arguments + +- **$1** (string): Version number to check (eg: 1.0.1) + +#### Exit codes + +- **0**: version number is equal. +- **1**: $1 version number is greater than $2. +- **2**: $1 version number is less than $2. +- **3**: Function is missing required arguments. +- **4**: Provided input argument is in invalid format. + +#### Example + +```bash +test='abc-ABC_cD' +validation::version_comparison "12.0.1" "12.0.1" +echo $? +#Output +0 +``` + +## Variable + +Functions for handling variables. + +### variable::is_array() + +Check if given variable is array. +Pass the variable name instead of value of the variable. + +#### Arguments + +- **$1** (string): name of the variable to check. + +#### Exit codes + +- **0**: If input is array. +- **1**: If input is not an array. + +#### Example + +```bash +arr=("a" "b" "c") +variable::is_array "arr" +#Output +0 +``` + +### variable::is_numeric() + +Check if given variable is a number. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is number. +- **1**: If input is not a number. + +#### Example + +```bash +variable::is_numeric "1234" +#Output +0 +``` + +### variable::is_int() + +Check if given variable is an integer. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is an integer. +- **1**: If input is not an integer. + +#### Example + +```bash +variable::is_int "+1234" +#Output +0 +``` + +### variable::is_float() + +Check if given variable is a float. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is a float. +- **1**: If input is not a float. + +#### Example + +```bash +variable::is_float "+1234.0" +#Output +0 +``` + +### variable::is_bool() + +Check if given variable is a boolean. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is a boolean. +- **1**: If input is not a boolean. + +#### Example + +```bash +variable::is_bool "true" +#Output +0 +``` + +### variable::is_true() + +Check if given variable is a true. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is true. +- **1**: If input is not true. + +#### Example + +```bash +variable::is_true "true" +#Output +0 +``` + +### variable::is_false() + +Check if given variable is false. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is false. +- **1**: If input is not false. + +#### Example + +```bash +variable::is_false "false" +#Output +0 +``` + +### variable::is_empty_or_null() + +Check if given variable is empty or null. + +#### Arguments + +- **$1** (mixed): Value of variable to check. + +#### Exit codes + +- **0**: If input is empty or null. +- **1**: If input is not empty or null. + +#### Example + +```bash +test='' +variable::is_empty_or_null $test +#Output +0 +``` + + + +## Inspired By + +- [Bash Bible](https://github.com/dylanaraps/pure-bash-bible) - A collection of pure bash alternatives to external processes. + +## License + +[MIT](https://github.com/labbots/google-drive-upload/blob/master/LICENSE) diff --git a/lib/bash-utility-master/bash_utility.sh b/lib/bash-utility-master/bash_utility.sh new file mode 100755 index 000000000..65411add0 --- /dev/null +++ b/lib/bash-utility-master/bash_utility.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC1091 +source src/array.sh +source src/string.sh +source src/variable.sh +source src/file.sh +source src/misc.sh +source src/date.sh +source src/interaction.sh +source src/check.sh +source src/format.sh +source src/collection.sh +source src/json.sh +source src/terminal.sh +source src/validation.sh +source src/debug.sh +source src/os.sh + + diff --git a/lib/bash-utility-master/bin/bashdoc.awk b/lib/bash-utility-master/bin/bashdoc.awk new file mode 100755 index 000000000..13efdaf7b --- /dev/null +++ b/lib/bash-utility-master/bin/bashdoc.awk @@ -0,0 +1,275 @@ +#!/usr/bin/awk -f + +# Varibles +# style = readme or doc +# toc = true or false +BEGIN { + if (! style) { + style = "doc" + } + + if (! toc) { + toc = 0 + } + + styles["empty", "from"] = ".*" + styles["empty", "to"] = "" + + styles["h1", "from"] = ".*" + styles["h1", "to"] = "# &" + + styles["h2", "from"] = ".*" + styles["h2", "to"] = "## &" + + styles["h3", "from"] = ".*" + styles["h3", "to"] = "### &" + + styles["h4", "from"] = ".*" + styles["h4", "to"] = "#### &" + + styles["h5", "from"] = ".*" + styles["h5", "to"] = "##### &" + + styles["code", "from"] = ".*" + styles["code", "to"] = "```&" + + styles["/code", "to"] = "```" + + styles["argN", "from"] = "^(\\$[0-9]) (\\S+)" + styles["argN", "to"] = "**\\1** (\\2):" + + styles["arg@", "from"] = "^\\$@ (\\S+)" + styles["arg@", "to"] = "**...** (\\1):" + + styles["li", "from"] = ".*" + styles["li", "to"] = "- &" + + styles["i", "from"] = ".*" + styles["i", "to"] = "*&*" + + styles["anchor", "from"] = ".*" + styles["anchor", "to"] = "[&](#&)" + + styles["exitcode", "from"] = "([>!]?[0-9]{1,3}) (.*)" + styles["exitcode", "to"] = "**\\1**: \\2" + + styles["h_rule", "to"] = "---" + + styles["comment", "from"] = ".*" + styles["comment", "to"] = "" + + + output_format["readme", "h1"] = "h2" + output_format["readme", "h2"] = "h3" + output_format["readme", "h3"] = "h4" + output_format["readme", "h4"] = "h5" + + output_format["bashdoc", "h1"] = "h1" + output_format["bashdoc", "h2"] = "h2" + output_format["bashdoc", "h3"] = "h3" + output_format["bashdoc", "h4"] = "h4" + + output_format["webdoc", "h1"] = "empty" + output_format["webdoc", "h2"] = "h3" + output_format["webdoc", "h3"] = "h4" + output_format["webdoc", "h4"] = "h5" + +} + +function render(type, text) { + if((style,type) in output_format){ + type = output_format[style,type] + } + return gensub( \ + styles[type, "from"], + styles[type, "to"], + "g", + text \ + ) +} + +function render_list(item, anchor) { + return "- [" item "](#" anchor ")" +} + +function generate_anchor(text) { + # https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb#L44-L45 + text = tolower(text) + gsub(/[^[:alnum:]_ -]/, "", text) + gsub(/ /, "-", text) + return text +} + +function reset() { + has_example = 0 + has_args = 0 + has_exitcode = 0 + has_stdout = 0 + + content_desc = "" + content_example = "" + content_args = "" + content_exitcode = "" + content_seealso = "" + content_stdout = "" +} + +/^[[:space:]]*# @internal/ { + is_internal = 1 +} + +/^[[:space:]]*# @file/ { + sub(/^[[:space:]]*# @file /, "") + + filedoc = render("h1", $0) "\n" + if(style == "webdoc"){ + filedoc = filedoc render("comment", "file=" $0) "\n" + } + +} + +/^[[:space:]]*# @brief/ { + sub(/^[[:space:]]*# @brief /, "") + if(style == "webdoc"){ + filedoc = filedoc render("comment", "brief=" $0) "\n" + } + filedoc = filedoc "\n" $0 +} + +/^[[:space:]]*# @description/ { + in_description = 1 + in_example = 0 + + reset() + + docblock = "" +} + +in_description { + if (/^[^[[:space:]]*#]|^[[:space:]]*# @[^d]|^[[:space:]]*[^#]/) { + if (!match(content_desc, /\n$/)) { + content_desc = content_desc "\n" + } + in_description = 0 + } else { + sub(/^[[:space:]]*# @description /, "") + sub(/^[[:space:]]*# /, "") + sub(/^[[:space:]]*#$/, "") + + content_desc = content_desc "\n" $0 + } +} + +in_example { + + if (! /^[[:space:]]*#[ ]{3}/) { + + in_example = 0 + + content_example = content_example "\n" render("/code") "\n" + } else { + sub(/^[[:space:]]*#[ ]{3}/, "") + + content_example = content_example "\n" $0 + } +} + +/^[[:space:]]*# @example/ { + in_example = 1 + content_example = content_example "\n" render("h3", "Example") + content_example = content_example "\n\n" render("code", "bash") +} + +/^[[:space:]]*# @arg/ { + if (!has_args) { + has_args = 1 + + content_args = content_args "\n" render("h3", "Arguments") "\n\n" + } + + sub(/^[[:space:]]*# @arg /, "") + + $0 = render("argN", $0) + $0 = render("arg@", $0) + + content_args = content_args render("li", $0) "\n" +} + +/^[[:space:]]*# @noargs/ { + content_args = content_args "\n" render("i", "Function has no arguments.") "\n" +} + +/^[[:space:]]*# @exitcode/ { + if (!has_exitcode) { + has_exitcode = 1 + + content_exitcode = content_exitcode "\n" render("h3", "Exit codes") "\n\n" + } + + sub(/^[[:space:]]*# @exitcode /, "") + + $0 = render("exitcode", $0) + + content_exitcode = content_exitcode render("li", $0) "\n" +} + +/^[[:space:]]*# @see/ { + sub(/[[:space:]]*# @see /, "") + anchor = generate_anchor($0) + $0 = render_list($0, anchor) + + content_seealso = content_seealso "\n" render("h3", "See also") "\n\n" $0 "\n" +} + +/^[[:space:]]*# @stdout/ { + has_stdout = 1 + + sub(/^[[:space:]]*# @stdout /, "") + + content_stdout = content_stdout "\n" render("h3", "Output on stdout") + content_stdout = content_stdout "\n\n" render("li", $0) "\n" +} + +{ + docblock = content_desc content_args content_exitcode content_stdout content_example content_seealso + if(style == "webdoc"){ + docblock = docblock "\n" render("h_rule") "\n" + } +} + +/^[ \t]*(function([ \t])+)?([a-zA-Z0-9_:-]+)([ \t]*)(\(([ \t]*)\))?[ \t]*\{/ && docblock != "" && !in_example { + if (is_internal) { + is_internal = 0 + } else { + func_name = gensub(\ + /^[ \t]*(function([ \t])+)?([a-zA-Z0-9_:-]+)[ \t]*\(.*/, \ + "\\3()", \ + "g" \ + ) + doc = doc "\n" render("h2", func_name) "\n" docblock + if (toc) { + url = generate_anchor(func_name) + + content_idx = content_idx "\n" "- [" func_name "](#" url ")" + } + } + + docblock = "" + reset() +} + +END { + if (filedoc != "") { + print filedoc + } + + if (toc) { + print "" + print render("h2", "Table of Contents") + print content_idx + print "" + print render("h_rule") + } + + print doc +} diff --git a/lib/bash-utility-master/bin/generate_readme.sh b/lib/bash-utility-master/bin/generate_readme.sh new file mode 100755 index 000000000..858a4b8be --- /dev/null +++ b/lib/bash-utility-master/bin/generate_readme.sh @@ -0,0 +1,400 @@ +#!/usr/bin/env bash + +#https://github.com/bkrem/make-toc.sh/blob/master/make-toc.sh +#https://gitlab.com/pedrolab/doctoc.sh/-/blob/master/doctoc.sh +_usage() { + printf " +Script to autogenerate markdown based on bash source code.\n +The script generates table of contents and bashdoc and update the given markdown file.\n +Usage:\n %s [options.. ]\n +Options:\n + -f | --file - Relative or absolute path to the README.md file. + -s | --sh-dir - path to the bash script source folder to generate shdocs.\n + -l | --toc-level - Minimum level of header to print in Table of Contents.\n + -d | --toc-depth - Maximum depth of tree to print in Table of Contents.\n + -w | --webdoc - Flag to indicate generation of webdoc.\n + -p | --dest-dir - Path in which wedoc files must be generated.\n + -h | --help - Display usage instructions.\n" "${0##*/}" + exit 0 +} + +_setup_arguments() { + + unset MINLEVEL MAXLEVEL SCRIPT_FILE SOURCE_MARKDOWN SOURCE_SCRIPT_DIR SCRIPT_DIR WEBDOC WEBDOC_DEST_DIR + MINLEVEL=1 + MAXLEVEL=3 + SCRIPT_FILE="${0##*/}" + declare source="${BASH_SOURCE[0]}" + while [ -h "$source" ]; do # resolve $source until the file is no longer a symlink + SCRIPT_DIR="$(cd -P "$(dirname "$source")" > /dev/null 2>&1 && pwd)" + source="$(readlink "$source")" + [[ $source != /* ]] && source="$SCRIPT_DIR/$source" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located + done + SCRIPT_DIR="$(cd -P "$(dirname "$source")" > /dev/null 2>&1 && pwd)" + SOURCE_MARKDOWN="${SCRIPT_DIR}/../README.md" + SOURCE_SCRIPT_DIR="${SCRIPT_DIR}/../src" + WEBDOC_DEST_DIR="${SCRIPT_DIR}/../hugo-docs/content/functions" + + SHORTOPTS="whp:f:m:d:s:-:" + + while getopts "${SHORTOPTS}" OPTION; do + case "${OPTION}" in + -) + _check_longoptions() { { [[ -z "$1" ]] && printf '%s: --%s: option requires an argument\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1; } || :; } + case "${OPTARG}" in + help) + _usage + ;; + file) + _check_longoptions "${!OPTIND}" + SOURCE_MARKDOWN="${!OPTIND}" && OPTIND=$((OPTIND + 1)) + ;; + toc-level) + _check_longoptions "${!OPTIND}" + MINLEVEL="${!OPTIND}" && OPTIND=$((OPTIND + 1)) + ;; + toc-depth) + _check_longoptions "${!OPTIND}" + MAXLEVEL="${!OPTIND}" && OPTIND=$((OPTIND + 1)) + ;; + sh-dir) + _check_longoptions "${!OPTIND}" + SOURCE_SCRIPT_DIR="${!OPTIND}" && OPTIND=$((OPTIND + 1)) + ;; + webdoc) + WEBDOC=true + ;; + dest-dir) + WEBDOC_DEST_DIR="${OPTARG}" + ;; + '') + _usage + ;; + *) + printf '%s: --%s: Unknown option\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1 + ;; + esac + ;; + h) + _usage + ;; + f) + SOURCE_MARKDOWN="${OPTARG}" + ;; + m) + MINLEVEL="${OPTARG}" + ;; + d) + MAXLEVEL="${OPTARG}" + ;; + s) + SOURCE_SCRIPT_DIR="${OPTARG}" + ;; + w) + WEBDOC=true + ;; + p) + WEBDOC_DEST_DIR="${OPTARG}" + ;; + :) + printf '%s: -%s: option requires an argument\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1 + ;; + ?) + printf '%s: -%s: Unknown option\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1 + ;; + esac + done + shift "$((OPTIND - 1))" + + if [[ -w "${SOURCE_MARKDOWN}" ]]; then + declare src_file src_extension + src_file="${SOURCE_MARKDOWN##*/}" + src_extension="${src_file##*.}" + if [[ "${src_extension,,}" != "md" ]]; then + printf "Provided file %s is not a markdown.\n" "${src_file}" && exit 1 + fi + else + printf "Provided file %s does not exist or no enough permission to access it.\n" "${SOURCE_MARKDOWN}" && exit 1 + fi + + if [[ ! -d "${SOURCE_SCRIPT_DIR}" ]]; then + printf "Provided directory for bash script files %s does not exist.\n" "${SOURCE_SCRIPT_DIR}" && exit 1 + fi + + declare re='^[0-9]+$' + if ! [[ "${MINLEVEL}" =~ $re ]] || ! [[ "${MAXLEVEL}" =~ $re ]]; then + echo "error: Not a number" >&2 + exit 1 + fi + if [[ "${MINLEVEL}" -gt "${MAXLEVEL}" ]]; then + printf "Minimum level for TOC cannot be greater than the depth of TOC to be printed.\n" && exit 1 + fi + + [ -d "${WEBDOC_DEST_DIR}" ] || mkdir -p "${WEBDOC_DEST_DIR}" + +} + +_setup_tempfile() { + declare temp_file + type -p mktemp &> /dev/null && { temp_file="$(mktemp -u)" || temp_file="${PWD}/$((RANDOM * 2)).LOG"; } + trap 'rm -f "${temp_file}"' EXIT + printf "%s" "${temp_file}" +} + +_generate_shdoc() { + declare file + file="$(realpath "${1}")" + if [[ -s "${file}" ]]; then + awk -v style="readme" -v toc=0 -f "${SCRIPT_DIR}"/bashdoc.awk < "${file}" >> "$2" + #awk -v style="doc" -v toc=1 -f "${SCRIPT_DIR}"/bashdoc.awk < "${file}" >> "../docs/${file##*/}.md" + fi +} + +_insert_shdoc_to_file() { + declare shdoc_tmp_file source_markdown start_shdoc info_shdoc end_shdoc + shdoc_tmp_file="$1" + source_markdown="$2" + + start_shdoc="" + info_shdoc="" + end_shdoc="" + + sed -i "1s/^/${info_shdoc}\n/" "${shdoc_tmp_file}" + + if grep --color=always -Pzl "(?s)${start_shdoc}.*\n.*${end_shdoc}" "${source_markdown}" &> /dev/null; then + # src https://stackoverflow.com/questions/2699666/replace-delimited-block-of-text-in-file-with-the-contents-of-another-file + + sed -i -ne "/${start_shdoc}/ {p; r ${shdoc_tmp_file}" -e ":a; n; /${end_shdoc}/ {p; b}; ba}; p" "${source_markdown}" + echo -e "Updated bashdoc content to ${source_markdown} successfully\n" + + else + { + printf "%s\n" "${start_shdoc}" + cat "${shdoc_tmp_file}" + printf "%s\n" "${end_shdoc}" + } >> "${source_markdown}" + echo -e "Created bashdoc content to ${source_markdown} successfully\n" + fi +} + +_process_sh_files() { + declare shdoc_tmp_file source_script_dir source_markdown + source_markdown="${1}" + source_script_dir="${2}" + shdoc_tmp_file=$(_setup_tempfile) + find "${source_script_dir}" -name '*.sh' -print0 | sort -z | + while IFS= read -r -d '' line; do + _generate_shdoc "${line}" "${shdoc_tmp_file}" + done + _insert_shdoc_to_file "${shdoc_tmp_file}" "${source_markdown}" + rm "${shdoc_tmp_file}" + +} + +_generate_toc() { + + declare line level title anchor output counter temp_output invalid_chars + + invalid_chars="'[]/?!:\`.,()*\";{}+=<>~$|#@&–—" + while IFS='' read -r line || [[ -n "${line}" ]]; do + level="$(echo "${line}" | sed -E 's/(#+).*/\1/; s/#/ /g; s/^ //')" + title="$(echo "${line}" | sed -E 's/^#+ //')" + [[ "${title}" = "Table of Contents" ]] && continue + + # tr does not do OK the lowercase for non ascii chars, add sed to pipeline -> src https://stackoverflow.com/questions/13381746/tr-upper-lower-with-cyrillic-text + anchor="$(echo "${title}" | tr '[:upper:] ' '[:lower:]-' | sed 's/[[:upper:]]*/\L&/' | tr -d "${invalid_chars}")" + + # check new line introduced is not duplicated, if is duplicated, introduce a number at the end + temp_output=$output"$level- [$title](#$anchor)\n" + counter=1 + while true; do + nlines="$(echo -e "${temp_output}" | wc -l)" + duplines="$(echo -e "${temp_output}" | sort | uniq | wc -l)" + if [ "${nlines}" = "${duplines}" ]; then + break + fi + temp_output=$output"$level- [$title](#$anchor-$counter)\n" + counter=$((counter + 1)) + done + + output="$temp_output" + + # grep: filter header candidates to be included in toc + # sed: remove the ignored headers (case: minlevel greater than one) to avoid unnecessary spacing later in level variable assignment + done <<< "$(grep -E "^#{${MINLEVEL},${MAXLEVEL}} " "${1}" | tr -d '\r' | sed "s/^#\{$((MINLEVEL - 1))\}//g")" + + # when in toc we have two `--` quit one + echo "$output" + +} + +_insert_toc_to_file() { + + declare source_markdown toc_text start_toc info_toc end_toc toc_block utext_ampersand utext_slash + source_markdown="${1}" + toc_text="${2}" + start_toc="" + info_toc="" + end_toc="" + + toc_block="$start_toc\n$info_toc\n## Table of Contents\n\n$toc_text\n$end_toc" + # temporary replace of '/' (confused with separator of substitutions) and '&' (confused with match regex symbol) to run the special sed command + utext_ampersand="id8234923000230gzz" + utext_slash="id9992384923423gzz" + toc_block="${toc_block//\&/${utext_ampersand}}" + toc_block="${toc_block//\//${utext_slash}}" + + # search multiline toc block -> https://stackoverflow.com/questions/2686147/how-to-find-patterns-across-multiple-lines-using-grep/2686705 + # grep color for debugging -> https://superuser.com/questions/914856/grep-display-all-output-but-highlight-search-matches + if grep --color=always -Pzl "(?s)${start_toc}.*\n.*${end_toc}" "${source_markdown}" &> /dev/null; then + # src https://askubuntu.com/questions/533221/how-do-i-replace-multiple-lines-with-single-word-in-fileinplace-replace + sed -i ":a;N;\$!ba;s/$start_toc.*$end_toc/$toc_block/g" "${source_markdown}" + echo -e "Updated TOC content in ${source_markdown} succesfully\n" + + else + sed -i 1i"$toc_block" "${source_markdown}" + echo -e "Created TOC in ${source_markdown} succesfully\n" + + fi + + # undo symbol replacements + sed -i "s,${utext_ampersand},\&,g" "${source_markdown}" + sed -i "s,${utext_slash},\/,g" "${source_markdown}" + +} + +_process_toc() { + declare toc_temp_file source_markdown level toc_text + source_markdown="${1}" + + toc_temp_file=$(_setup_tempfile) + + sed '/```/,/```/d' "${source_markdown}" > "${toc_temp_file}" + + level=$MINLEVEL + while [[ $(grep -Ec "^#{$level} " "${toc_temp_file}") -le 1 ]]; do + level=$((level + 1)) + done + + MINLEVEL=${level} + toc_text=$(_generate_toc "${toc_temp_file}") + rm "${toc_temp_file}" + + _insert_toc_to_file "${source_markdown}" "${toc_text}" +} + +_generate_webdoc() { + declare dest_dir filename file_basename dest_file_path shdoc_tmp_file is_new_file + declare title description start_shdoc end_shdoc file_modified_date file_modified_date_epoc + declare webdoc_lastmod_date webdoc_lastmod_epoc + file="$(realpath "${1}")" + dest_dir="${2}" + + filename="${file##*/}" + file_basename="${filename%.*}" + dest_file_path="${dest_dir}/${file_basename}.md" + file_modified_date="$(date -r "${file}" +"%FT%T%:z")" + file_modified_date_epoc="$(date -r "${file}" +"%s")" + + start_shdoc="" + end_shdoc="" + if [[ ! -f "${dest_file_path}" ]]; then + + cat << EOF > "${dest_file_path}" +--- +title : +description : +date : ${file_modified_date} +lastmod : ${file_modified_date} +--- +${start_shdoc} +${end_shdoc} +EOF + is_new_file=true + else + is_new_file=false + webdoc_lastmod_date="$(sed -ne 's/-->//; s/^.*lastmod : //p' "${dest_file_path}")" + webdoc_lastmod_epoc="$(date -d "${webdoc_lastmod_date}" +"%s")" + fi + + if [[ "${is_new_file}" = true || "${file_modified_date_epoc}" -gt "${webdoc_lastmod_epoc}" ]]; then + + shdoc_tmp_file=$(_setup_tempfile) + if [[ -s "${file}" ]]; then + awk -v style="webdoc" -v toc=1 -f "${SCRIPT_DIR}"/bashdoc.awk < "${file}" >> "${shdoc_tmp_file}" + fi + + if grep --color=always -Pzl "(?s)${start_shdoc}.*\n.*${end_shdoc}" "${dest_file_path}" &> /dev/null; then + sed -i -ne "/${start_shdoc}/ {p; r ${shdoc_tmp_file}" -e ":a; n; /${end_shdoc}/ {p; b}; ba}; p" "${dest_file_path}" + fi + + # Extract title and description from webdoc + title="$(sed -ne 's/-->//; s/^.*//; s/^.*/${title}/g" "${dest_file_path}" + sed -i -e "s//${description}/g" "${dest_file_path}" + else + sed -i -e "s/title : .*/title : ${title}/g" "${dest_file_path}" + sed -i -e "s/description : .*/description : ${description}/g" "${dest_file_path}" + + fi + + # Update the last modified timestamp in front matter + sed -i -e "s/lastmod : .*/lastmod : ${file_modified_date}/g" "${dest_file_path}" + + echo -e "Updated bashdoc content to ${dest_file_path} successfully." + rm "${shdoc_tmp_file}" + + fi +} +_process_webdoc_files() { + declare source_script_dir dest_dir + + source_script_dir="${1}" + dest_dir="${2}" + + find "${source_script_dir}" -name '*.sh' -print0 | sort -z | + while IFS= read -r -d '' line; do + _generate_webdoc "${line}" "${dest_dir}" + done +} + +_count_library_functions() { + declare source_script_dir + source_script_dir="${1}" + + find "${source_script_dir}" -name '*.sh' -print0 | sort -z | + { + declare -i function_count=0 count=0 + while IFS= read -r -d '' line; do + count=0 + count=$(grep -c '^[[:alpha:]]*::[[:alnum:]_]*()' "${line}") + function_count=$((function_count + count)) + done + printf "Total library functions: %s \n" "${function_count}" + + } +} +main() { + # export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + # set -x + trap 'exit "$?"' INT TERM && trap 'exit "$?"' EXIT + set -o errexit -o noclobber -o pipefail + + _setup_arguments "${@}" + _process_sh_files "${SOURCE_MARKDOWN}" "${SOURCE_SCRIPT_DIR}" + _process_toc "${SOURCE_MARKDOWN}" + + if [[ -n ${WEBDOC} ]]; then + _process_webdoc_files "${SOURCE_SCRIPT_DIR}" "${WEBDOC_DEST_DIR}" + fi + _count_library_functions "${SOURCE_SCRIPT_DIR}" +} + +main "${@}" diff --git a/lib/bash-utility-master/image/bash-utility.png b/lib/bash-utility-master/image/bash-utility.png new file mode 100644 index 0000000000000000000000000000000000000000..c1bfb8b556809ce645cf41ab6d4b11547df68fc7 GIT binary patch literal 32396 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 literal 0 HcmV?d00001 diff --git a/lib/bash-utility-master/src/array.sh b/lib/bash-utility-master/src/array.sh new file mode 100755 index 000000000..42d5883b8 --- /dev/null +++ b/lib/bash-utility-master/src/array.sh @@ -0,0 +1,284 @@ +#!/usr/bin/env bash + +# @file Array +# @brief Functions for array operations and manipulations. + +# @description Check if item exists in the given array. +# +# @example +# array=("a" "b" "c") +# array::contains "c" ${array[@]} +# #Output +# 0 +# +# @arg $1 mixed Item to search (needle). +# @arg $2 array array to be searched (haystack). +# +# @exitcode 0 If successful. +# @exitcode 1 If no match found in the array. +# @exitcode 2 Function missing arguments. +array::contains() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare query="${1:-}" + shift + + for element in "${@}"; do + [[ "${element}" == "${query}" ]] && return 0 + done + + return 1 +} + +# @description Remove duplicate items from the array. +# +# @example +# array=("a" "b" "a" "c") +# printf "%s" "$(array::dedupe ${array[@]})" +# #Output +# a +# b +# c +# +# @arg $1 array Array to be deduped. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Deduplicated array. +array::dedupe() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -A arr_tmp + declare -a arr_unique + for i in "$@"; do + { [[ -z ${i} || ${arr_tmp[${i}]} ]]; } && continue + arr_unique+=("${i}") && arr_tmp[${i}]=x + done + printf '%s\n' "${arr_unique[@]}" +} + +# @description Check if a given array is empty. +# +# @example +# array=("a" "b" "c" "d") +# array::is_empty "${array[@]}" +# +# @arg $1 array Array to be checked. +# +# @exitcode 0 If the given array is empty. +# @exitcode 2 If the given array is not empty. +array::is_empty() { + declare -a array + local array=("$@") + if [ ${#array[@]} -eq 0 ]; then + return 0 + else + return 1 + fi +} +# @description Join array elements with a string. +# +# @example +# array=("a" "b" "c" "d") +# printf "%s" "$(array::join "," "${array[@]}")" +# #Output +# a,b,c,d +# printf "%s" "$(array::join "" "${array[@]}")" +# #Output +# abcd +# +# @arg $1 string String to join the array elements (glue). +# @arg $2 array array to be joined with glue string. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout String containing a string representation of all the array elements in the same order,with the glue string between each element. +array::join() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare delimiter="${1}" + shift + printf "%s" "${1}" + shift + printf "%s" "${@/#/${delimiter}}" +} + +# @description Return an array with elements in reverse order. +# +# @example +# array=(1 2 3 4 5) +# printf "%s" "$(array::reverse "${array[@]}")" +# #Output +# 5 4 3 2 1 +# +# @arg $1 array The input array. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout The reversed array. +array::reverse() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare min=0 + declare -a array + array=("$@") + declare max=$((${#array[@]} - 1)) + + while [[ $min -lt $max ]]; do + # Swap current first and last elements + x="${array[$min]}" + array[$min]="${array[$max]}" + array[$max]="$x" + + # Move closer + ((min++, max--)) + done + printf '%s\n' "${array[@]}" +} + +# @description Returns a random item from the array. +# +# @example +# array=("a" "b" "c" "d") +# printf "%s\n" "$(array::random_element "${array[@]}")" +# #Output +# c +# +# @arg $1 array The input array. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Random item out of the array. +array::random_element() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -a array + local array=("$@") + printf '%s\n' "${array[RANDOM % $#]}" +} + +# @description Sort an array from lowest to highest. +# +# @example +# sarr=("a c" "a" "d" 2 1 "4 5") +# array::array_sort "${sarr[@]}" +# #Output +# 1 +# 2 +# 4 5 +# a +# a c +# d +# +# @arg $1 array The input array. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout sorted array. +array::sort() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -a array=("$@") + declare -a sorted + declare noglobtate + noglobtate="$(shopt -po noglob)" + set -o noglob + declare IFS=$'\n' + sorted=($(sort <<< "${array[*]}")) + unset IFS + eval "${noglobtate}" + printf "%s\n" "${sorted[@]}" +} + +# @description Sort an array in reverse order (highest to lowest). +# +# @example +# sarr=("a c" "a" "d" 2 1 "4 5") +# array::array_sort "${sarr[@]}" +# #Output +# d +# a c +# a +# 4 5 +# 2 +# 1 +# +# @arg $1 array The input array. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout reverse sorted array. +array::rsort() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -a array=("$@") + declare -a sorted + declare noglobtate + noglobtate="$(shopt -po noglob)" + set -o noglob + declare IFS=$'\n' + sorted=($(sort -r<<< "${array[*]}")) + unset IFS + eval "${noglobtate}" + printf "%s\n" "${sorted[@]}" +} + +# @description Bubble sort an integer array from lowest to highest. +# This sort does not work on string array. +# @example +# iarr=(4 5 1 3) +# array::bsort "${iarr[@]}" +# #Output +# 1 +# 3 +# 4 +# 5 +# +# @arg $1 array The input array. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout bubble sorted array. +array::bsort() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare tmp + declare arr=("$@") + for ((i = 0; i <= $((${#arr[@]} - 2)); ++i)); do + for ((j = ((i + 1)); j <= ((${#arr[@]} - 1)); ++j)); do + if [[ ${arr[i]} -gt ${arr[j]} ]]; then + # echo $i $j ${arr[i]} ${arr[j]} + tmp=${arr[i]} + arr[i]=${arr[j]} + arr[j]=$tmp + fi + done + done + printf "%s\n" "${arr[@]}" +} + +# @description Merge two arrays. +# Pass the variable name of the array instead of value of the variable. +# @example +# a=("a" "c") +# b=("d" "c") +# array::merge "a[@]" "b[@]" +# #Output +# a +# c +# d +# c +# +# @arg $1 string variable name of first array. +# @arg $2 string variable name of second array. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Merged array. +array::merge() { + [[ $# -ne 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -a arr1=("${!1}") + declare -a arr2=("${!2}") + declare out=("${arr1[@]}" "${arr2[@]}") + printf "%s\n" "${out[@]}" +} diff --git a/lib/bash-utility-master/src/check.sh b/lib/bash-utility-master/src/check.sh new file mode 100755 index 000000000..2b7c1eb1d --- /dev/null +++ b/lib/bash-utility-master/src/check.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# @file Check +# @brief Helper functions. + +# @description Check if the command exists in the system. +# +# @example +# check::command_exists "tput" +# +# @arg $1 string Command name to be searched. +# +# @exitcode 0 If the command exists. +# @exitcode 1 If the command does not exist. +# @exitcode 2 Function missing arguments. +check::command_exists() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + hash "${1}" 2> /dev/null +} + +# @description Check if the script is executed with sudo privilege. +# +# @example +# check::is_sudo +# +# @noargs +# +# @exitcode 0 If the script is executed with root privilege. +# @exitcode 1 If the script is not executed with root privilege +check::is_sudo() { + if [[ $(id -u) -ne 0 ]]; then + return 1 + fi +} diff --git a/lib/bash-utility-master/src/collection.sh b/lib/bash-utility-master/src/collection.sh new file mode 100755 index 000000000..9f0e244a2 --- /dev/null +++ b/lib/bash-utility-master/src/collection.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash + +# @file Collection +# @brief (Experimental) Functions to iterates over a list of elements, yielding each in turn to an iteratee function. + +# @description Iterates over elements of collection and invokes iteratee for each element. +# Input to the function can be a pipe output, here-string or file. +# @example +# test_func(){ +# printf "print value: %s\n" "$1" +# return 0 +# } +# arr1=("a b" "c d" "a" "d") +# printf "%s\n" "${arr1[@]}" | collection::each "test_func" +# collection::each "test_func" < <(printf "%s\n" "${arr1[@]}") #alternative approach +# #output +# print value: a b +# print value: c d +# print value: a +# print value: d +# +# @example +# # If other function from this library is already used to process the array. +# # Then following method could be used to pass the array to the function. +# out=("$(array::dedupe "${arr1[@]}")") +# collection::each "test_func" <<< "${out[@]}" +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# @exitcode other exitcode returned by iteratee. +# +# @stdout Output of iteratee function. +collection::each() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + while read -r it; do + if [[ "${func}" == *"$"* ]]; then + eval "${func}" + else + eval "${func}" "'${it}'" + fi + declare -i ret="$?" + + if [[ $ret -ne 0 ]]; then + return $ret + fi + + done +} + +# @description Checks if iteratee function returns truthy for all elements of collection. Iteration is stopped once predicate returns false. +# Input to the function can be a pipe output, here-string or file. +# @example +# arri=("1" "2" "3" "4") +# printf "%s\n" "${arri[@]}" | collection::every "variable::is_numeric" +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 1 If iteratee function fails. +# @exitcode 2 Function missing arguments. +collection::every() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + while read -r it; do + if [[ "${func}" == *"$"* ]]; then + eval "${func}" + else + eval "${func}" "'${it}'" + fi + declare -i ret="$?" + + if [[ $ret -ne 0 ]]; then + return 1 + fi + + done +} + +# @description Iterates over elements of array, returning all elements where iteratee returns true. +# Input to the function can be a pipe output, here-string or file. +# @example +# arri=("1" "2" "3" "a") +# printf "%s\n" "${arri[@]}" | collection::filter "variable::is_numeric" +# #output +# 1 +# 2 +# 3 +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout array values matching the iteratee function. +collection::filter() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + while read -r it; do + if [[ "${func}" == *"$"* ]]; then + eval "${func}" + else + eval "${func}" "'${it}'" + fi + declare -i ret="$?" + if [[ $ret = 0 ]]; then + printf "%s\n" "${it}" + fi + done +} + +# @description Iterates over elements of collection, returning the first element where iteratee returns true. +# Input to the function can be a pipe output, here-string or file. +# @example +# arr=("1" "2" "3" "a") +# check_a(){ +# [[ "$1" = "a" ]] +# } +# printf "%s\n" "${arr[@]}" | collection::find "check_a" +# #Output +# a +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 1 If no match found. +# @exitcode 2 Function missing arguments. +# +# @stdout first array value matching the iteratee function. +collection::find() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + while read -r it; do + + if [[ "${func}" == *"$"* ]]; then + eval "${func}" + else + eval "${func}" "'${it}'" + fi + declare -i ret="$?" + if [[ $ret = 0 ]]; then + printf "%s" "${it}" + return 0 + fi + done + + return 1 +} + +# @description Invokes the iteratee with each element passed as argument to the iteratee. +# Input to the function can be a pipe output, here-string or file. +# @example +# opt=("-a" "-l") +# printf "%s\n" "${opt[@]}" | collection::invoke "ls" +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# @exitcode other exitcode returned by iteratee. +# +# @stdout Output from the iteratee function. +collection::invoke() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -a args=() + declare func="${1}" + while read -r it; do + args=("${args[@]}" "$it") + done + + eval "${func}" "${args[@]}" +} + +# @description Creates an array of values by running each element in array through iteratee. +# Input to the function can be a pipe output, here-string or file. +# @example +# arri=("1" "2" "3") +# add_one(){ +# i=${1} +# i=$(( i + 1 )) +# printf "%s\n" "$i" +# } +# printf "%s\n" "${arri[@]}" | collection::map "add_one" +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# @exitcode other exitcode returned by iteratee. +# +# @stdout Output result of iteratee on value. +collection::map() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + declare out + + while read -r it; do + + if [[ "${func}" == *"$"* ]]; then + out="$("${func}")" + else + out="$("${func}" "$it")" + fi + + declare -i ret=$? + + if [[ $ret -ne 0 ]]; then + return $ret + fi + + printf "%s\n" "${out}" + done +} + +# @description The opposite of filter function; this method returns the elements of collection that iteratee does not return true. +# Input to the function can be a pipe output, here-string or file. +# @example +# arri=("1" "2" "3" "a") +# printf "%s\n" "${arri[@]}" | collection::reject "variable::is_numeric" +# #Ouput +# a +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout array values not matching the iteratee function. +# @see collection::filter +collection::reject() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + while read -r it; do + + if [[ "${func}" == *"$"* ]]; then + eval "${func}" + else + eval "${func}" "'$it'" + fi + declare -i ret=$? + if [[ $ret -ne 0 ]]; then + echo "$it" + fi + + done +} + +# @description Checks if iteratee returns true for any element of the array. +# Input to the function can be a pipe output, here-string or file. +# @example +# arr=("a" "b" "3" "a") +# printf "%s\n" "${arr[@]}" | collection::reject "variable::is_numeric" +# +# @arg $1 string Iteratee function. +# +# @exitcode 0 If match successful. +# @exitcode 1 If no match found. +# @exitcode 2 Function missing arguments. +collection::some() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare func="${1}" + declare IFS=$'\n' + while read -r it; do + + if [[ "${func}" == *"$"* ]]; then + eval "${func}" + else + eval "${func}" "'$it'" + fi + + declare -i ret=$? + + if [[ $ret -eq 0 ]]; then + return 0 + fi + done + + return 1 +} diff --git a/lib/bash-utility-master/src/date.sh b/lib/bash-utility-master/src/date.sh new file mode 100755 index 000000000..41f071792 --- /dev/null +++ b/lib/bash-utility-master/src/date.sh @@ -0,0 +1,744 @@ +#!/usr/bin/env bash + +# @file Date +# @brief Functions for manipulating dates. + +# @description Get current time in unix timestamp. +# +# @example +# echo "$(date::now)" +# #Output +# 1591554426 +# +# @noargs +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout current timestamp. +date::now() { + declare now + now="$(date --universal +%s)" || return $? + printf "%s" "${now}" +} + +# @description convert datetime string to unix timestamp. +# +# @example +# echo "$(date::epoc "2020-07-07 18:38")" +# #Output +# 1594143480 +# +# @arg $1 string date time in any format. +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp for specified datetime. +date::epoc() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare date + date=$(date -d "${1}" +"%s") || return $? + printf "%s" "${date}" +} + +# @description Add number of days from specified timestamp. +# If number of days not specified then it defaults to 1 day. +# +# @example +# echo "$(date::add_days_from "1594143480")" +# #Output +# 1594229880 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of days (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_days_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp day + timestamp="${1}" + day=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${day} day" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of months from specified timestamp. +# If number of months not specified then it defaults to 1 month. +# +# @example +# echo "$(date::add_months_from "1594143480")" +# #Output +# 1596821880 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of months (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_months_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp month + timestamp="${1}" + month=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${month} month" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of years from specified timestamp. +# If number of years not specified then it defaults to 1 year. +# +# @example +# echo "$(date::add_years_from "1594143480")" +# #Output +# 1625679480 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of years (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_years_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp year + timestamp="${1}" + year=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${year} year" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of weeks from specified timestamp. +# If number of weeks not specified then it defaults to 1 week. +# +# @example +# echo "$(date::add_weeks_from "1594143480")" +# #Output +# 1594748280 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of weeks (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_weeks_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp week + timestamp="${1}" + week=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${week} week" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of hours from specified timestamp. +# If number of hours not specified then it defaults to 1 hour. +# +# @example +# echo "$(date::add_hours_from "1594143480")" +# #Output +# 1594147080 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of hours (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_hours_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp hour + timestamp="${1}" + hour=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${hour} hour" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of minutes from specified timestamp. +# If number of minutes not specified then it defaults to 1 minute. +# +# @example +# echo "$(date::add_minutes_from "1594143480")" +# #Output +# 1594143540 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of minutes (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_minutes_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp minute + timestamp="${1}" + minute=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${minute} minute" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of seconds from specified timestamp. +# If number of seconds not specified then it defaults to 1 second. +# +# @example +# echo "$(date::add_seconds_from "1594143480")" +# #Output +# 1594143481 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of seconds (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::add_seconds_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp minute + timestamp="${1}" + second=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T')+${second} second" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of days from current day timestamp. +# If number of days not specified then it defaults to 1 day. +# +# @example +# echo "$(date::add_days "1")" +# #Output +# 1591640826 +# +# @arg $1 int number of days (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_days() { + declare timestamp new_timestamp day + timestamp="$(date::now)" + day=${1:-1} + new_timestamp="$(date::add_days_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of months from current day timestamp. +# If number of months not specified then it defaults to 1 month. +# +# @example +# echo "$(date::add_months "1")" +# #Output +# 1594146426 +# +# @arg $1 int number of months (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_months() { + declare timestamp new_timestamp month + timestamp="$(date::now)" + month=${1:-1} + new_timestamp="$(date::add_months_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of years from current day timestamp. +# If number of years not specified then it defaults to 1 year. +# +# @example +# echo "$(date::add_years "1")" +# #Output +# 1623090426 +# +# @arg $1 int number of years (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_years() { + declare timestamp new_timestamp year + timestamp="$(date::now)" + year=${1:-1} + new_timestamp="$(date::add_years_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of weeks from current day timestamp. +# If number of weeks not specified then it defaults to 1 year. +# +# @example +# echo "$(date::add_weeks "1")" +# #Output +# 1592159226 +# +# @arg $1 int number of weeks (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_weeks() { + declare timestamp new_timestamp week + timestamp="$(date::now)" + week=${1:-1} + new_timestamp="$(date::add_weeks_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of hours from current day timestamp. +# If number of hours not specified then it defaults to 1 hour. +# +# @example +# echo "$(date::add_hours "1")" +# #Output +# 1591558026 +# +# @arg $1 int number of hours (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_hours() { + declare timestamp new_timestamp hour + timestamp="$(date::now)" + hour=${1:-1} + new_timestamp="$(date::add_hours_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of minutes from current day timestamp. +# If number of minutes not specified then it defaults to 1 minute. +# +# @example +# echo "$(date::add_minutes "1")" +# #Output +# 1591554486 +# +# @arg $2 int number of minutes (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_minutes() { + declare timestamp new_timestamp minute + timestamp="$(date::now)" + minute=${1:-1} + new_timestamp="$(date::add_minutes_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Add number of seconds from current day timestamp. +# If number of seconds not specified then it defaults to 1 second. +# +# @example +# echo "$(date::add_seconds "1")" +# #Output +# 1591554427 +# +# @arg $2 int number of seconds (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::add_seconds() { + declare timestamp new_timestamp minute + timestamp="$(date::now)" + second=${1:-1} + new_timestamp="$(date::add_seconds_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of days from specified timestamp. +# If number of days not specified then it defaults to 1 day. +# +# @example +# echo "$(date::sub_days_from "1594143480")" +# #Output +# 1594057080 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of days (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_days_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp day + timestamp="${1}" + day=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${day} days ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of months from specified timestamp. +# If number of months not specified then it defaults to 1 month. +# +# @example +# echo "$(date::sub_months_from "1594143480")" +# #Output +# 1591551480 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of months (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_months_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp month + timestamp="${1}" + month=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${month} months ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of years from specified timestamp. +# If number of years not specified then it defaults to 1 year. +# +# @example +# echo "$(date::sub_years_from "1594143480")" +# #Output +# 1562521080 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of years (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_years_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp year + timestamp="${1}" + year=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${year} years ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of weeks from specified timestamp. +# If number of weeks not specified then it defaults to 1 week. +# +# @example +# echo "$(date::sub_weeks_from "1594143480")" +# #Output +# 1593538680 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of weeks (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_weeks_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp week + timestamp="${1}" + week=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${week} weeks ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of hours from specified timestamp. +# If number of hours not specified then it defaults to 1 hour. +# +# @example +# echo "$(date::sub_hours_from "1594143480")" +# #Output +# 1594139880 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of hours (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_hours_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp hour + timestamp="${1}" + hour=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${hour} hours ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of minutes from specified timestamp. +# If number of minutes not specified then it defaults to 1 minute. +# +# @example +# echo "$(date::sub_minutes_from "1594143480")" +# #Output +# 1594143420 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of minutes (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_minutes_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp minute + timestamp="${1}" + minute=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${minute} minutes ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of seconds from specified timestamp. +# If number of seconds not specified then it defaults to 1 second. +# +# @example +# echo "$(date::sub_seconds_from "1594143480")" +# #Output +# 1594143479 +# +# @arg $1 int unix timestamp. +# @arg $2 int number of seconds (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# @exitcode 2 Function missing arguments. +# +# @stdout timestamp. +date::sub_seconds_from() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp new_timestamp minute + timestamp="${1}" + second=${2:-1} + new_timestamp="$(date -d "$(date -d "@${timestamp}" '+%F %T') ${second} seconds ago" +'%s')" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of days from current day timestamp. +# If number of days not specified then it defaults to 1 day. +# +# @example +# echo "$(date::sub_days "1")" +# #Output +# 1588876026 +# +# @arg $1 int number of days (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_days() { + declare timestamp new_timestamp day + timestamp="$(date::now)" + day=${1:-1} + new_timestamp="$(date::sub_days_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of months from current day timestamp. +# If number of months not specified then it defaults to 1 month. +# +# @example +# echo "$(date::sub_months "1")" +# #Output +# 1559932026 +# +# @arg $1 int number of months (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_months() { + declare timestamp new_timestamp month + timestamp="$(date::now)" + month=${1:-1} + new_timestamp="$(date::sub_months_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of years from current day timestamp. +# If number of years not specified then it defaults to 1 year. +# +# @example +# echo "$(date::sub_years "1")" +# #Output +# 1591468026 +# +# @arg $1 int number of years (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_years() { + declare timestamp new_timestamp year + timestamp="$(date::now)" + year=${1:-1} + new_timestamp="$(date::sub_years_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of weeks from current day timestamp. +# If number of weeks not specified then it defaults to 1 week. +# +# @example +# echo "$(date::sub_weeks "1")" +# #Output +# 1590949626 +# +# @arg $1 int number of weeks (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_weeks() { + declare timestamp new_timestamp week + timestamp="$(date::now)" + week=${1:-1} + new_timestamp="$(date::sub_weeks_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of hours from current day timestamp. +# If number of hours not specified then it defaults to 1 hour. +# +# @example +# echo "$(date::sub_hours "1")" +# #Output +# 1591550826 +# +# @arg $1 int number of hours (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_hours() { + declare timestamp new_timestamp hour + timestamp="$(date::now)" + hour=${1:-1} + new_timestamp="$(date::sub_hours_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of minutes from current day timestamp. +# If number of minutes not specified then it defaults to 1 minute. +# +# @example +# echo "$(date::sub_minutes "1")" +# #Output +# 1591554366 +# +# @arg $1 int number of minutes (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_minutes() { + declare timestamp new_timestamp minute + timestamp="$(date::now)" + minute=${1:-1} + new_timestamp="$(date::sub_minutes_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Subtract number of seconds from current day timestamp. +# If number of seconds not specified then it defaults to 1 second. +# +# @example +# echo "$(date::sub_seconds "1")" +# #Output +# 1591554425 +# +# @arg $1 int number of seconds (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate timestamp. +# +# @stdout timestamp. +date::sub_seconds() { + declare timestamp new_timestamp minute + timestamp="$(date::now)" + second=${1:-1} + new_timestamp="$(date::sub_seconds_from "${timestamp}" "${second}")" || return $? + printf "%s" "${new_timestamp}" +} + +# @description Format unix timestamp to human readable format. +# If format string is not specified then it defaults to "yyyy-mm-dd hh:mm:ss" format. +# +# @example +# echo echo "$(date::format "1594143480")" +# #Output +# 2020-07-07 18:38:00 +# +# @arg $1 int unix timestamp. +# @arg $2 string format control characters based on `date` command (optional). +# +# @exitcode 0 If successful. +# @exitcode 1 If unable to generate time string. +# @exitcode 2 Function missing arguments. +# +# @stdout formatted time string. +date::format() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare timestamp format out + timestamp="${1}" + format="${2:-"%F %T"}" + out="$(date -d "@${timestamp}" +"${format}")" || return $? + printf "%s" "${out}" + +} diff --git a/lib/bash-utility-master/src/debug.sh b/lib/bash-utility-master/src/debug.sh new file mode 100755 index 000000000..82828734f --- /dev/null +++ b/lib/bash-utility-master/src/debug.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# @file Debug +# @brief Functions to facilitate debugging scripts. + +# @description Prints the content of array as key value pair for easier debugging. +# Pass the variable name of the array instead of value of the variable. +# @example +# array=(foo bar baz) +# printf "Array\n" +# printarr "array" +# declare -A assoc_array +# assoc_array=([foo]=bar [baz]=foobar) +# printf "Assoc Array\n" +# printarr "assoc_array" +# #Output +# Array +# 0 = foo +# 1 = bar +# 2 = baz +# Assoc Array +# baz = foobar +# foo = bar +# +# @arg $1 string variable name of the array. +# +# @stdout Formatted key value of array. +debug::print_array() { + declare -n __arr="$1" + for k in "${!__arr[@]}"; do printf "%s = %s\n" "$k" "${__arr[$k]}"; done +} + +# @description Function to print ansi escape sequence as is. +# This function helps debug ansi escape sequence in text by displaying the escape codes. +# +# @example +# txt="$(tput bold)$(tput setaf 9)This is bold red text$(tput sgr0).$(tput setaf 10)This is green text$(tput sgr0)" +# debug::print_ansi "$txt" +# #Output +# \e[1m\e[91mThis is bold red text\e(B\e[m.\e[92mThis is green text\e(B\e[m +# +# @arg $1 string input with ansi escape sequence. +# +# @stdout Ansi escape sequence printed in output as is. +debug::print_ansi() { + #echo $(tr -dc '[:print:]'<<<$1) + printf "%s\n" "${1//$'\e'/\\e}" + +} diff --git a/lib/bash-utility-master/src/file.sh b/lib/bash-utility-master/src/file.sh new file mode 100755 index 000000000..71afd8476 --- /dev/null +++ b/lib/bash-utility-master/src/file.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env bash + +# @file File +# @brief Functions for handling files. + +# @description Create temporary file. +# Function creates temporary file with random name. The temporary file will be deleted when script finishes. +# +# @example +# echo "$(file::make_temp_file)" +# #Output +# tmp.vgftzy +# +# @noargs +# +# @exitcode 0 If successful. +# @exitcode 1 If failed to create temp file. +# +# @stdout file name of temporary file created. +file::make_temp_file() { + declare temp_file + type -p mktemp &> /dev/null && { temp_file="$(mktemp -u)" || temp_file="${PWD}/$((RANDOM * 2)).LOG"; } + trap 'rm -f "${temp_file}"' EXIT + printf "%s" "${temp_file}" +} + +# @description Create temporary directory. +# Function creates temporary directory with random name. The temporary directory will be deleted when script finishes. +# +# @example +# echo "$(utility::make_temp_dir)" +# #Output +# tmp.rtfsxy +# +# @arg $1 string Temporary directory prefix +# @arg $2 string Flag to auto remove directory on exit trap (true) +# +# @exitcode 0 If successful. +# @exitcode 1 If failed to create temp directory. +# @exitcode 2 Missing arguments. +# +# @stdout directory name of temporary directory created. +file::make_temp_dir() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare temp_dir prefix="${1}" trap_rm="${2}" + temp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t "${prefix}") + if [[ -n "${trap_rm}" ]]; then + trap 'rm -rf "${temp_dir}"' EXIT + fi + printf "%s" "${temp_dir}" +} + +# @description Get only the filename from string path. +# +# @example +# echo "$(file::name "/path/to/test.md")" +# #Output +# test.md +# +# @arg $1 string path. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout name of the file with extension. +file::name() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + printf "%s" "${1##*/}" +} + +# @description Get the basename of file from file name. +# +# @example +# echo "$(file::basename "/path/to/test.md")" +# #Output +# test +# +# @arg $1 string path. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout basename of the file. +file::basename() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare file basename + file="${1##*/}" + basename="${file%.*}" + + printf "%s" "${basename}" +} + +# @description Get the extension of file from file name. +# +# @example +# echo "$(file::extension "/path/to/test.md")" +# #Output +# md +# +# @arg $1 string path. +# +# @exitcode 0 If successful. +# @exitcode 1 If no extension is found in the filename. +# @exitcode 2 Function missing arguments. +# +# @stdout extension of the file. +file::extension() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare file extension + file="${1##*/}" + extension="${file##*.}" + [[ "${file}" = "${extension}" ]] && return 1 + + printf "%s" "${extension}" +} + +# @description Get directory name from file path. +# +# @example +# echo "$(file::dirname "/path/to/test.md")" +# #Output +# /path/to +# +# @arg $1 string path. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout directory path. +file::dirname() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare tmp=${1:-.} + + [[ ${tmp} != *[!/]* ]] && { printf '/\n' && return; } + tmp="${tmp%%"${tmp##*[!/]}"}" + + [[ ${tmp} != */* ]] && { printf '.\n' && return; } + tmp=${tmp%/*} && tmp="${tmp%%"${tmp##*[!/]}"}" + + printf '%s' "${tmp:-/}" +} + +# @description Get absolute path of file or directory. +# +# @example +# file::full_path "../path/to/file.md" +# #Output +# /home/labbots/docs/path/to/file.md +# +# @arg $1 string relative or absolute path to file/direcotry. +# +# @exitcode 0 If successful. +# @exitcode 1 If file/directory does not exist. +# @exitcode 2 Function missing arguments. +# +# @stdout Absolute path to file/directory. +file::full_path() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare input="${1}" + if [[ -f ${input} ]]; then + printf "%s/%s\n" "$(cd "$(file::dirname "${input}")" && pwd)" "${input##*/}" + elif [[ -d ${input} ]]; then + printf "%s\n" "$(cd "${input}" && pwd)" + else + return 1 + fi +} + +# @description Get mime type of provided input. +# +# @example +# file::mime_type "../src/file.sh" +# #Output +# application/x-shellscript +# +# @arg $1 string relative or absolute path to file/directory. +# +# @exitcode 0 If successful. +# @exitcode 1 If file/directory does not exist. +# @exitcode 2 Function missing arguments. +# @exitcode 3 If file or mimetype command not found in system. +# +# @stdout mime type of file/directory. +file::mime_type() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare mime_type + if [[ -f "${1}" ]] || [[ -d "${1}" ]]; then + if type -p mimetype &> /dev/null; then + mime_type=$(mimetype --output-format %m "${1}") + elif type -p file &> /dev/null; then + mime_type=$(file --brief --mime-type "${1}") + else + return 3 + fi + else + return 1 + fi + printf "%s" "${mime_type}" +} + +# @description Search if a given pattern is found in file. +# +# @example +# file::contains_text "./file.sh" "^[ @[:alpha:]]*" +# file::contains_text "./file.sh" "@file" +# #Output +# 0 +# +# @arg $1 string relative or absolute path to file/directory. +# @arg $2 string search key or regular expression. +# +# @exitcode 0 If given search parameter is found in file. +# @exitcode 1 If search paramter not found in file. +# @exitcode 2 Function missing arguments. +file::contains_text() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 + declare -r file="$1" + declare -r text="$2" + grep -q "$text" "$file" +} diff --git a/lib/bash-utility-master/src/format.sh b/lib/bash-utility-master/src/format.sh new file mode 100755 index 000000000..7dceb1d59 --- /dev/null +++ b/lib/bash-utility-master/src/format.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# @file Format +# @brief Functions to format provided input. + +# @internal +# @description Initialisation script when the code is sourced. +# +# @noargs +__init(){ +_check_terminal_window_size +} + +# @internal +# @description Checks the terminal window size, if necessary, updates the values of LINES and COLUMNS. +# +# @noargs +_check_terminal_window_size() { + shopt -s checkwinsize && (: && :) + trap 'shopt -s checkwinsize; (:;:)' SIGWINCH +} +# @description Format seconds to human readable format. +# +# @example +# echo "$(format::human_readable_seconds "356786")" +# #Output +# 4 days 3 hours 6 minute(s) and 26 seconds +# +# @arg $1 int number of seconds. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout formatted time string. +format::human_readable_seconds() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare T="${1}" + declare DAY="$((T / 60 / 60 / 24))" HR="$((T / 60 / 60 % 24))" MIN="$((T / 60 % 60))" SEC="$((T % 60))" + [[ ${DAY} -gt 0 ]] && printf '%d days ' "${DAY}" + [[ ${HR} -gt 0 ]] && printf '%d hours ' "${HR}" + [[ ${MIN} -gt 0 ]] && printf '%d minute(s) ' "${MIN}" + [[ ${DAY} -gt 0 || ${HR} -gt 0 || ${MIN} -gt 0 ]] && printf 'and ' + printf '%d seconds\n' "${SEC}" +} + +# @description Format bytes to human readable format. +# +# @example +# echo "$(format::bytes_to_human "2250")" +# #Output +# 2.19 KB +# +# @arg $1 int size in bytes. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout formatted file size string. +format::bytes_to_human() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare b=${1:-0} d='' s=0 S=(Bytes {K,M,G,T,P,E,Y,Z}B) + while ((b > 1024)); do + d="$(printf ".%02d" $((b % 1024 * 100 / 1024)))" + b=$((b / 1024)) && ((s++)) + done + printf "%s\n" "${b}${d} ${S[${s}]}" +} + +# @description Remove Ansi escape sequences from given text. +# +# @example +# format::strip_ansi "\e[1m\e[91mThis is bold red text\e(B\e[m.\e[92mThis is green text.\e(B\e[m" +# #Output +# This is bold red text.This is green text. +# +# @arg $1 string Input text to be ansi stripped. +# +# @exitcode 0 If successful. +# +# @stdout Ansi stripped text. +format::strip_ansi() { + declare tmp esc tpa re + tmp="${1}" + esc=$(printf "\x1b") + tpa=$(printf "\x28") + re="(.*)${esc}[\[${tpa}][0-9]*;*[mKB](.*)" + while [[ "${tmp}" =~ $re ]]; do + tmp="${BASH_REMATCH[1]}${BASH_REMATCH[2]}" + done + printf "%s" "${tmp}" +} + +# @description Prints the given text to centre of terminal. +# +# @example +# format::text_center "This text is in centre of the terminal." "-" +# +# @arg $1 string Text to be printed. +# @arg $2 string Filler symbol to be added to prefix and suffix of the text (optional). Defaults to space as filler. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout formatted text. +format::text_center() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 + + declare input="${1}" symbol="${2:- }" filler out no_ansi_out + no_ansi_out=$(format::strip_ansi "$input") + declare -i str_len=${#no_ansi_out} + declare -i filler_len="$(((COLUMNS - str_len) / 2))" + + [[ -n "${symbol}" ]] && symbol="${symbol:0:1}" + for ((i = 0; i < filler_len; i++)); do + filler+="${symbol}" + done + + out="${filler}${input}${filler}" + [[ $(((COLUMNS - str_len) % 2)) -ne 0 ]] && out+="${symbol}" + printf "%s" "${out}" +} + +# @description Format String to print beautiful report. +# +# @example +# format::report "Initialising mission state" "Success" +# #Output +# Initialising mission state ....................................................................[ Success ] +# +# @arg $1 string Text to be printed on the left. +# @arg $2 string Text to be printed within the square brackets. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout formatted text. +format::report() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 + + declare symbol="." to_print y hl hlout out + declare input1="${1} " input2="${2}" + input2="[ $input2 ]" + to_print="$((COLUMNS * 60 / 100))" + y=$(( to_print - ( ${#input1} + ${#input2} ) )) + hl="$(printf '%*s' $y '')" + hlout=${hl// /${symbol}} + out="${input1}${hlout}${input2}" + printf "%s\n" "${out}" +} + +# @description Trim given text to width of the terminal window. +# +# @example +# format::trim_text_to_term "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." "This is part of second sentence." +# #Output +# Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod..This is part of second sentence. +# +# @arg $1 string Text of first sentence. +# @arg $2 string Text of second sentence (optional). +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout trimmed text. +format::trim_text_to_term() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 + + declare to_print out input1="$1" input2="$2" + if [[ $# = 1 ]]; then + to_print="$((COLUMNS * 93 / 100))" + { [[ ${#input1} -gt ${to_print} ]] && out="${input1:0:to_print}.."; } || { out="$input1"; } + else + to_print="$((COLUMNS * 40 / 100))" + { [[ ${#input1} -gt ${to_print} ]] && out+=" ${input1:0:to_print}.."; } || { out+=" $input1"; } + to_print="$((COLUMNS * 53 / 100))" + { [[ ${#input2} -gt ${to_print} ]] && out+="${input2:0:to_print}.. "; } || { out+="$input2 "; } + fi + printf "%s" "$out" +} + +__init diff --git a/lib/bash-utility-master/src/interaction.sh b/lib/bash-utility-master/src/interaction.sh new file mode 100755 index 000000000..910b60e1c --- /dev/null +++ b/lib/bash-utility-master/src/interaction.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +# @file Interaction +# @brief Functions to enable interaction with the user. + +# @description Prompt yes or no question to the user. +# +# @example +# interaction::prompt_yes_no "Are you sure to proceed" "yes" +# #Output +# Are you sure to proceed (y/n)? [y] +# +# @arg $1 string The question to be prompted to the user. +# @arg $2 string default answer \[yes/no\] (optional). +# +# @exitcode 0 If user responds with yes. +# @exitcode 1 If user responds with no. +# @exitcode 2 Function missing arguments. +interaction::prompt_yes_no() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare def_arg response + def_arg="" + response="" + + case "${2}" in + [yY] | [yY][eE][sS]) + def_arg=y + ;; + [nN] | [nN][oO]) + def_arg=n + ;; + esac + + while :; do + printf "%s (y/n)? " "${1}" + [[ -n "${def_arg}" ]] && printf "[%s] " "${def_arg}" + + read -r response + [[ -z "${response}" ]] && response="${def_arg}" + + case "${response}" in + [yY] | [yY][eE][sS]) + response=y + break + ;; + [nN] | [nN][oO]) + response=n + break + ;; + *) + response="" + ;; + esac + done + + [[ "${response}" = 'y' ]] && return 0 || return 1 +} + +# @description Prompt question to the user. +# +# @example +# interaction::prompt_response "Choose directory to install" "/home/path" +# #Output +# Choose directory to install? [/home/path] +# +# @arg $1 string The question to be prompted to the user. +# @arg $2 string default answer (optional). +# +# @exitcode 0 If user responds with answer. +# @exitcode 2 Function missing arguments. +# +# @stdout User entered answer to the question. +interaction::prompt_response() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare def_arg response + response="" + def_arg="${2}" + + while :; do + printf "%s ? " "${1}" + [[ -n "${def_arg}" ]] && [[ "${def_arg}" != "-" ]] && printf "[%s] " "${def_arg}" + + read -r response + [[ -n "${response}" ]] && break + + if [[ -z "${response}" ]] && [[ -n "${def_arg}" ]]; then + response="${def_arg}" + break + fi + done + + [[ "${response}" = "-" ]] && response="" + + printf "%s" "${response}" +} diff --git a/lib/bash-utility-master/src/json.sh b/lib/bash-utility-master/src/json.sh new file mode 100755 index 000000000..73476618e --- /dev/null +++ b/lib/bash-utility-master/src/json.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# @file Json +# @brief Simple json manipulation. These functions does not completely replace `jq` in any way. + +# @description Extract value from json based on key and position. +# Input to the function can be a pipe output, here-string or file. +# @example +# json::get_value "id" "1" < json_file +# json::get_value "id" <<< "${json_var}" +# echo "{\"data\":{\"id\":\"123\",\"value\":\"name string\"}}" | json::get_value "id" +# +# @arg $1 string id of the field to fetch. +# @arg $2 int position of value to extract.Defaults to 1.(optional) +# +# @exitcode 0 If match successful. +# @exitcode 2 Function missing arguments. +# +# @stdout string value of extracted key. +json::get_value() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare LC_ALL=C num="${2:-1}" + grep -o "\"""${1}""\"\:.*" | sed -e "s/.*\"""${1}""\": //" -e 's/[",]*$//' -e 's/["]*$//' -e 's/[,]*$//' -e "s/\"//" -n -e "${num}"p +} diff --git a/lib/bash-utility-master/src/misc.sh b/lib/bash-utility-master/src/misc.sh new file mode 100755 index 000000000..fca9a75bf --- /dev/null +++ b/lib/bash-utility-master/src/misc.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +# @file Miscellaneous +# @brief Set of miscellaneous helper functions. + +# @internal +# @description Check if script is run in terminal. +# +# @noargs +# +# @exitcode 0 If script is run on terminal. +# @exitcode 1 If script is not run on terminal. +_is_terminal() { + [[ -t 1 || -z ${TERM} ]] && return 0 || return 1 +} + +# @description Check if internet connection is available. +# +# @example +# misc::check_internet_connection +# +# @noargs +# +# @exitcode 0 If script can connect to internet. +# @exitcode 1 If script cannot access internet. +misc::check_internet_connection() { + declare check_internet + if _is_terminal; then + check_internet="$(sh -ic 'exec 3>&1 2>/dev/null; { curl --compressed -Is google.com 1>&3; kill 0; } | { sleep 10; kill 0; }' || :)" + else + check_internet="$(curl --compressed -Is google.com -m 10)" + fi + if [[ -z ${check_internet} ]]; then + return 1 + fi +} + +# @description Get list of process ids based on process name. +# +# @example +# misc::get_pid "chrome" +# #Ouput +# 25951 +# 26043 +# 26528 +# 26561 +# +# @arg $1 Name of the process to search. +# +# @exitcode 0 If match successful. +# @exitcode 2 Function missing arguments. +# +# @stdout list of process ids. +misc::get_pid() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + pgrep "${1}" +} + +# @description Get user id based on username. +# +# @example +# misc::get_uid "labbots" +# #Ouput +# 1000 +# +# @arg $1 username to search. +# +# @exitcode 0 If match successful. +# @exitcode 2 Function missing arguments. +# +# @stdout string uid for the username. +misc::get_uid() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + user_id=$(id "${1}" 2> /dev/null) + declare -i ret=$? + if [[ $ret -ne 0 ]]; then + printf "No user found with username: %s" "${1}\n" + return 1 + fi + + printf "%s\n" "${user_id}" | sed -e 's/(.*$//' -e 's/^uid=//' + + unset user_id +} + +# @description Generate random uuid. +# +# @example +# misc::generate_uuid +# #Ouput +# 65bc64d1-d355-4ffc-a9d9-dc4f3954c34c +# +# @noargs +# +# @exitcode 0 If match successful. +# +# @stdout random generated uuid. +misc::generate_uuid() { + C="89ab" + + for ((N=0;N<16;++N)); do + B="$((RANDOM%256))" + + case "$N" in + 6) printf '4%x' "$((B%16))" ;; + 8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;; + + 3|5|7|9) + printf '%02x-' "$B" + ;; + + *) + printf '%02x' "$B" + ;; + esac + done + + printf '\n' +} diff --git a/lib/bash-utility-master/src/os.sh b/lib/bash-utility-master/src/os.sh new file mode 100755 index 000000000..78e0d36c5 --- /dev/null +++ b/lib/bash-utility-master/src/os.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +# @file Operating System +# @brief Functions to detect Operating system and version. + +# @description Identify the OS the function is run on. +# +# @noargs +# +# @example +# os::detect_os +# #Output +# linux +# +# @exitcode 0 If OS is successfully detected. +# @exitcode 1 If unable to detect OS. +# +# @stdout Operating system name (linux, mac or windows). +os::detect_os() { + declare uname os + uname=$(command -v uname) + + case $("${uname}" | tr '[:upper:]' '[:lower:]') in + linux*) + os="linux" + ;; + darwin*) + os="mac" + ;; + msys* | cygwin* | mingw* | nt | win*) + # or possible 'bash on windows' + os="windows" + ;; + *) + return 1 + ;; + esac + printf "%s" "${os}" +} + +# @description Identify the distribution flavour of linux. +# +# @noargs +# +# @example +# os::detect_linux_distro +# #Output +# ubuntu +# @exitcode 0 If Linux distro is successfully detected. +# @exitcode 1 If unable to detect OS distro. +# +# @stdout Linux OS distribution name (ubuntu, debian, suse, etc.,). +os::detect_linux_distro() { + declare distro + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . "/etc/os-release" + distro="${NAME}" + elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + distro=$(lsb_release -si) + elif [[ -f /etc/lsb-release ]]; then + # For some versions of Debian/Ubuntu without lsb_release command + # shellcheck disable=SC1091 + . /etc/lsb-release + distro="${DISTRIB_ID}" + elif [[ -f /etc/debian_version ]]; then + # Older Debian/Ubuntu/etc. + distro="debian" + elif [[ -f /etc/SuSe-release ]]; then + # Older SuSE/etc. + distro="suse" + elif [[ -f /etc/redhat-release ]]; then + # Older Red Hat, CentOS, etc. + distro="redhat" + else + return 1 + fi + printf "%s" "${distro}" | tr '[:upper:]' '[:lower:]' +} + +# @description Identify the Linux version. +# +# @noargs +# +# @example +# os::detect_linux_version +# #Output +# 20.04 +# +# @exitcode 0 If Linux version is successfully detected. +# @exitcode 1 If unable to detect Linux version. +# +# @stdout Linux OS version number (18.04, 20.04, etc.,). +os::detect_linux_version() { + declare distro_version + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . "/etc/os-release" + distro_version="${VERSION_ID}" + elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + distro_version=$(lsb_release -sr) + elif [[ -f /etc/lsb-release ]]; then + # For some versions of Debian/Ubuntu without lsb_release command + # shellcheck disable=SC1091 + . /etc/lsb-release + distro_version="${DISTRIB_RELEASE}" + else + return 1 + fi + printf "%s" "${distro_version}" +} + +# @description Identify the Linux codename. +# +# @noargs +# +# @example +# os::detect_linux_codename +# #Output +# jammy +# +# @exitcode 0 If Linux codename is successfully detected. +# @exitcode 1 If unable to detect Linux codename. +# +# @stdout Linux OS codename number (buster, jammy, etc.,). +os::detect_linux_codename() { + declare distro_codename + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . "/etc/os-release" + distro_codename="${VERSION_CODENAME}" + elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + distro_codename=$(lsb_release -cs) + elif [[ -f /etc/lsb-release ]]; then + # For some versions of Debian/Ubuntu without lsb_release command + # shellcheck disable=SC1091 + . /etc/lsb-release + distro_codename="${DISTRIB_CODENAME}" + else + return 1 + fi + printf "%s" "${distro_codename}" +} + +# @description Identify the MacOS version. +# +# @noargs +# +# @example +# os::detect_linux_version +# #Output +# 10.15.7 +# @exitcode 0 If MacOS version is successfully detected. +# @exitcode 1 If unable to detect MacOS version. +# +# @stdout MacOS version number (10.15.6, etc.,) +os::detect_mac_version() { + if [[ "$(os::detect_os)" = "mac" ]]; then + declare mac_version + mac_version="$(sw_vers -productVersion)" + printf "%s" "${mac_version}" + else + return 1 + fi +} diff --git a/lib/bash-utility-master/src/string.sh b/lib/bash-utility-master/src/string.sh new file mode 100755 index 000000000..aa522cb55 --- /dev/null +++ b/lib/bash-utility-master/src/string.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +# @file String +# @brief Functions for string operations and manipulations. + +# @description Strip whitespace from the beginning and end of a string. +# +# @example +# echo "$(string::trim " Hello World! ")" +# #Output +# Hello World! +# +# @arg $1 string The string to be trimmed. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout The trimmed string. +string::trim() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + : "${1#"${1%%[![:space:]]*}"}" + : "${_%"${_##*[![:space:]]}"}" + printf '%s\n' "$_" +} + +# @description Split a string to array by a delimiter. +# +# @example +# array=( $(string::split "a,b,c" ",") ) +# printf "%s" "$(string::split "Hello!World" "!")" +# #Output +# Hello +# World +# +# @arg $1 string The input string. +# @arg $2 string The delimiter string. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Returns an array of strings created by splitting the string parameter by the delimiter. +string::split() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare -a arr=() + IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}" + printf '%s\n' "${arr[@]}" +} + +# @description Strip characters from the beginning of a string. +# +# @example +# echo "$(string::lstrip "Hello World!" "He")" +# #Output +# llo World! +# +# @arg $1 string The input string. +# @arg $2 string The characters you want to strip. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Returns the modified string. +string::lstrip() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + printf '%s\n' "${1##$2}" +} + +# @description Strip characters from the end of a string. +# +# @example +# echo "$(string::rstrip "Hello World!" "d!")" +# #Output +# Hello Worl +# +# @arg $1 string The input string. +# @arg $2 string The characters you want to strip. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Returns the modified string. +string::rstrip() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + printf '%s\n' "${1%%$2}" +} + +# @description Make a string lowercase. +# +# @example +# echo "$(string::to_lower "HellO")" +# #Output +# hello +# +# @arg $1 string The input string. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Returns the lowercased string. +string::to_lower() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + if [[ ${BASH_VERSINFO:-0} -ge 4 ]]; then + printf '%s\n' "${1,,}" + else + printf "%s\n" "${@}" | tr '[:upper:]' '[:lower:]' + fi +} + +# @description Make a string all uppercase. +# +# @example +# echo "$(string::to_upper "HellO")" +# #Output +# HELLO +# +# @arg $1 string The input string. +# +# @exitcode 0 If successful. +# @exitcode 2 Function missing arguments. +# +# @stdout Returns the uppercased string. +string::to_upper() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + if [[ ${BASH_VERSINFO:-0} -ge 4 ]]; then + printf '%s\n' "${1^^}" + else + printf "%s\n" "${@}" | tr '[:lower:]' '[:upper:]' + fi +} + +# @description Check whether the search string exists within the input string. +# +# @example +# string::contains "Hello World!" "lo" +# +# @arg $1 string The input string. +# @arg $2 string The search key. +# @exitcode 0 If match found. +# @exitcode 1 If no match found. +# @exitcode 2 Function missing arguments. +string::contains() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + # Usage: string_contains hello he + [[ "${1}" == *${2}* ]] +} + +# @description Check whether the input string starts with key string. +# +# @example +# string::starts_with "Hello World!" "He" +# +# @arg $1 string The input string. +# @arg $2 string The search key. +# @exitcode 0 If match found. +# @exitcode 1 If no match found. +# @exitcode 2 Function missing arguments. +string::starts_with() { + # Usage: string_starts_with hello he + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + [[ "${1}" == ${2}* ]] +} + +# @description Check whether the input string ends with key string. +# +# @example +# string::ends_with "Hello World!" "d!" +# +# @arg $1 string The input string. +# @arg $2 string The search key. +# @exitcode 0 If match found. +# @exitcode 1 If no match found. +# @exitcode 2 Function missing arguments. +string::ends_with() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + # Usage: string_ends_wit hello lo + [[ "${1}" == *${2} ]] +} + +# @description Check whether the input string matches the given regex. +# +# @example +# string::regex "HELLO" "^[A-Z]*$" +# +# @arg $1 string The input string. +# @arg $2 string The search key. +# @exitcode 0 If match found. +# @exitcode 1 If no match found. +# @exitcode 2 Function missing arguments. +string::regex() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + if [[ ${1} =~ ${2} ]]; then + return 0 + else + return 1 + fi + +} diff --git a/lib/bash-utility-master/src/terminal.sh b/lib/bash-utility-master/src/terminal.sh new file mode 100755 index 000000000..d73331d7a --- /dev/null +++ b/lib/bash-utility-master/src/terminal.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# @file Terminal +# @brief Set of useful terminal functions. + +# @description Check if script is run in terminal. +# +# @noargs +# +# @exitcode 0 If script is run on terminal. +# @exitcode 1 If script is not run on terminal. +terminal::is_term() { + [[ -t 1 || -z ${TERM} ]] && return 0 || return 1 +} + +# @description Detect profile rc file for zsh and bash of current script running user. +# +# @noargs +# +# @exitcode 0 If script is run on terminal. +# @exitcode 1 If script is not run on terminal. +# +# @stdout path to the profile file. +terminal::detect_profile() { + declare CURRENT_SHELL="${SHELL##*/}" + case "${CURRENT_SHELL}" in + 'bash') DETECTED_PROFILE="${HOME}/.bashrc" ;; + 'zsh') DETECTED_PROFILE="${HOME}/.zshrc" ;; + *) if [[ -f "${HOME}/.profile" ]]; then + DETECTED_PROFILE="${HOME}/.profile" + else + printf "No compaitable shell file\n" && exit 1 + fi ;; + esac + printf "%s\n" "${DETECTED_PROFILE}" +} + +# @description Clear the output in terminal on the specified line number. +# This function clears line only on terminal. +# +# @arg $1 Line number to clear. Defaults to 1. (optional) +# +# @exitcode 0 If script is run on terminal. +# +# @stdout clear line ansi code. +terminal::clear_line() { + if terminal::is_term; then + declare line=${1:-1} + printf "\033[%sA\033[2K" "${line}" + fi +} diff --git a/lib/bash-utility-master/src/validation.sh b/lib/bash-utility-master/src/validation.sh new file mode 100755 index 000000000..37fde1cbc --- /dev/null +++ b/lib/bash-utility-master/src/validation.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash + +# @file Validation +# @brief Functions to perform validation on given data. + +# @description Validate whether a given input is a valid email address or not. +# +# @example +# test='test@gmail.com' +# validation::email "${test}" +# echo $? +# #Output +# 0 +# +# @arg $1 string input email address to validate. +# +# @exitcode 0 If provided input is an email address. +# @exitcode 1 If provided input is not an email address. +# @exitcode 2 Function missing arguments. +validation::email() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare email_re + email_re="^([A-Za-z]+[A-Za-z0-9]*\+?((\.|\-|\_)?[A-Za-z]+[A-Za-z0-9]*)*)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" + [[ "${1}" =~ ${email_re} ]] && return 0 || return 1 +} + +# @description Validate whether a given input is a valid IP V4 address. +# +# @example +# ips=' +# 4.2.2.2 +# a.b.c.d +# 192.168.1.1 +# 0.0.0.0 +# 255.255.255.255 +# 255.255.255.256 +# 192.168.0.1 +# 192.168.0 +# 1234.123.123.123 +# 0.192.168.1 +# ' +# for ip in $ips; do +# if validation::ipv4 $ip; then stat='good'; else stat='bad'; fi +# printf "%-20s: %s\n" "$ip" "$stat" +# done +# #Output +# 4.2.2.2 : good +# a.b.c.d : bad +# 192.168.1.1 : good +# 0.0.0.0 : good +# 255.255.255.255 : good +# 255.255.255.256 : bad +# 192.168.0.1 : good +# 192.168.0 : bad +# 1234.123.123.123 : bad +# 0.192.168.1 : good +# +# @arg $1 string input IPv4 address. +# +# @exitcode 0 If provided input is a valid IPv4. +# @exitcode 1 If provided input is not a valid IPv4. +# @exitcode 2 Function missing arguments. +validation::ipv4() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + declare ip="${1}" + declare IFS=. + # shellcheck disable=SC2206 + declare -a a=($ip) + [[ "${ip}" =~ ^[0-9]+(\.[0-9]+){3}$ ]] || return 1 + # Test values of quads + declare quad + for quad in {0..3}; do + [[ "${a[$quad]}" -gt 255 ]] && return 1 + done + return 0 +} + +# @description Validate whether a given input is a valid IP V6 address. +# +# @example +# ips=' +# 2001:db8:85a3:8d3:1319:8a2e:370:7348 +# fe80::1ff:fe23:4567:890a +# fe80::1ff:fe23:4567:890a%eth2 +# 2001:0db8:85a3:0000:0000:8a2e:0370:7334:foo:bar +# fezy::1ff:fe23:4567:890a +# :: +# 2001:db8:: +# ' +# for ip in $ips; do +# if validation::ipv6 $ip; then stat='good'; else stat='bad'; fi +# printf "%-50s= %s\n" "$ip" "$stat" +# done +# #Output +# 2001:db8:85a3:8d3:1319:8a2e:370:7348 = good +# fe80::1ff:fe23:4567:890a = good +# fe80::1ff:fe23:4567:890a%eth2 = good +# 2001:0db8:85a3:0000:0000:8a2e:0370:7334:foo:bar = bad +# fezy::1ff:fe23:4567:890a = bad +# :: = good +# 2001:db8:: = good +# +# @arg $1 string input IPv6 address. +# +# @exitcode 0 If provided input is a valid IPv6. +# @exitcode 1 If provided input is not a valid IPv6. +# @exitcode 2 Function missing arguments. +validation::ipv6() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare ip="${1}" + declare re="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|\ +([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|\ +([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|\ +([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|\ +:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|\ +::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|\ +(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|\ +(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" + + [[ "${ip}" =~ $re ]] && return 0 || return 1 +} + +# @description Validate if given variable is entirely alphabetic characters. +# +# @example +# test='abcABC' +# validation::alpha "${test}" +# echo $? +# #Output +# 0 +# +# @arg $1 string Value of variable to validate. +# +# @exitcode 0 If input is only alpha characters. +# @exitcode 1 If input contains any non alpha characters. +# @exitcode 2 Function missing arguments. +validation::alpha() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare re='^[[:alpha:]]+$' + if [[ "${1}" =~ $re ]]; then + return 0 + fi + return 1 +} + +# @description Check if given variable contains only alpha-numeric characters. +# +# @example +# test='abc123' +# validation::alpha_num "${test}" +# echo $? +# #Output +# 0 +# +# @arg $1 string Value of variable to validate. +# +# @exitcode 0 If input is an alpha-numeric. +# @exitcode 1 If input is not an alpha-numeric. +# @exitcode 2 Function missing arguments. +validation::alpha_num() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare re='^[[:alnum:]]+$' + if [[ "${1}" =~ $re ]]; then + return 0 + fi + return 1 +} + +# @description Validate if given variable contains only alpha-numeric characters, as well as dashes and underscores. +# +# @example +# test='abc-ABC_cD' +# validation::alpha_dash "${test}" +# echo $? +# #Output +# 0 +# +# @arg $1 string Value of variable to validate. +# +# @exitcode 0 If input is valid. +# @exitcode 1 If input the input is not valid. +# @exitcode 2 Function missing arguments. +validation::alpha_dash() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 2 + + declare re='^[[:alpha:]_-]+$' + if [[ "${1}" =~ $re ]]; then + return 0 + fi + return 1 +} + +# @description Compares version numbers and provides return based on whether the value in equal, less than or greater. +# +# @arg $1 string Version number to check (eg: 1.0.1) +# $arg $2 string Version number to check (eg: 1.0.1) +# +# @example +# test='abc-ABC_cD' +# validation::version_comparison "12.0.1" "12.0.1" +# echo $? +# #Output +# 0 +# +# @exitcode 0 version number is equal. +# @exitcode 1 $1 version number is greater than $2. +# @exitcode 2 $1 version number is less than $2. +# @exitcode 3 Function is missing required arguments. +# @exitcode 4 Provided input argument is in invalid format. +validation::version_comparison() { + [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 3 + + declare regex="^[.0-9]*$" + ! [[ $1 =~ $regex ]] && printf "Invalid argument: %s \n" "${1}" && return 4 + ! [[ $2 =~ $regex ]] && printf "Invalid argument invalid: %s \n" "${2}" && return 4 + + if [[ "$1" == "$2" ]]; then + return 0 + fi + declare IFS=. + declare -a ver1 ver2 + read -r -a ver1 <<<"${1}" + read -r -a ver2 <<<"${2}" + # fill empty fields in ver1 with zeros + for ((i = ${#ver1[@]}; i < ${#ver2[@]}; i++)); do + ver1[i]=0 + done + for ((i = 0; i < ${#ver1[@]}; i++)); do + if [[ -z ${ver2[i]} ]]; then + # fill empty fields in ver2 with zeros + ver2[i]=0 + fi + if ((10#${ver1[i]} > 10#${ver2[i]})); then + return 1 + fi + if ((10#${ver1[i]} < 10#${ver2[i]})); then + return 2 + fi + done + return 0 +} diff --git a/lib/bash-utility-master/src/variable.sh b/lib/bash-utility-master/src/variable.sh new file mode 100755 index 000000000..67e9fab55 --- /dev/null +++ b/lib/bash-utility-master/src/variable.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash + +# @file Variable +# @brief Functions for handling variables. + +# @description Check if given variable is array. +# Pass the variable name instead of value of the variable. +# +# @example +# arr=("a" "b" "c") +# variable::is_array "arr" +# #Output +# 0 +# +# @arg $1 string name of the variable to check. +# +# @exitcode 0 If input is array. +# @exitcode 1 If input is not an array. +variable::is_array() { + if [[ -z "${1}" ]]; then + return 1 + else + declare -p "${1}" 2> /dev/null | grep 'declare \-[aA]' > /dev/null && return 0 + fi + return 1 +} + +# @description Check if given variable is a number. +# +# @example +# variable::is_numeric "1234" +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is number. +# @exitcode 1 If input is not a number. +variable::is_numeric() { + declare re='^[0-9]+$' + if [[ ${1} =~ $re ]]; then + return 0 + fi + return 1 +} + +# @description Check if given variable is an integer. +# +# @example +# variable::is_int "+1234" +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is an integer. +# @exitcode 1 If input is not an integer. +variable::is_int() { + declare re='^[+-]?[0-9]+$' + if [[ ${1} =~ $re ]]; then + return 0 + fi + return 1 +} + +# @description Check if given variable is a float. +# +# @example +# variable::is_float "+1234.0" +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is a float. +# @exitcode 1 If input is not a float. +variable::is_float() { + declare re='^[+-]?[0-9]+.?[0-9]*$' + if [[ ${1} =~ $re ]]; then + return 0 + fi + return 1 +} + +# @description Check if given variable is a boolean. +# +# @example +# variable::is_bool "true" +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is a boolean. +# @exitcode 1 If input is not a boolean. +variable::is_bool() { + [[ "${1}" = true || "${1}" = false ]] && return 0 || return 1 +} + +# @description Check if given variable is a true. +# +# @example +# variable::is_true "true" +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is true. +# @exitcode 1 If input is not true. +variable::is_true() { + [[ "${1}" = true || "${1}" -eq 0 ]] && return 0 || return 1 +} + +# @description Check if given variable is false. +# +# @example +# variable::is_false "false" +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is false. +# @exitcode 1 If input is not false. +variable::is_false() { + [[ "${1}" = false || "${1}" -eq 1 ]] && return 0 || return 1 +} + +# @description Check if given variable is empty or null. +# +# @example +# test='' +# variable::is_empty_or_null $test +# #Output +# 0 +# +# @arg $1 mixed Value of variable to check. +# +# @exitcode 0 If input is empty or null. +# @exitcode 1 If input is not empty or null. +variable::is_empty_or_null() { + [[ -z "${1}" || "${1}" = "null" ]] && return 0 || return 1 +} diff --git a/share/doc/armbian-configng/armbian-configng.csv b/share/doc/armbian-configng/armbian-configng.csv new file mode 100755 index 000000000..40dc84ad9 --- /dev/null +++ b/share/doc/armbian-configng/armbian-configng.csv @@ -0,0 +1,5 @@ +Function Name,Group Name,Description,Options,Category,Category Description +NMTUI,network,Network Manager.,none.,network,Network Wired wireless Bluetooth access point +Hello,system,Hello System.,none,system,System and Security +Bencharking,monitor,Armbian Monitor and Benchmarking.,,system,System and Security +Install,system,Armbian installer,none.,system,System and Security diff --git a/share/doc/armbian-configng/armbian-configng.html b/share/doc/armbian-configng/armbian-configng.html new file mode 100755 index 000000000..40c3d7804 --- /dev/null +++ b/share/doc/armbian-configng/armbian-configng.html @@ -0,0 +1,98 @@ + + + + + + Armbian armbian-configng + + + +
+

armbian-configng

+
+ +
+
+ +
+
+ + + + + + diff --git a/share/doc/armbian-configng/armbian-configng.json b/share/doc/armbian-configng/armbian-configng.json new file mode 100755 index 000000000..806c67fa9 --- /dev/null +++ b/share/doc/armbian-configng/armbian-configng.json @@ -0,0 +1,34 @@ +[ + { + "Function Name": "NMTUI", + "Group Name": "network", + "Description": "Network Manager.", + "Options": "none.", + "Category": "network", + "Category Description": "Network Wired wireless Bluetooth access point" + }, + { + "Function Name": "Hello", + "Group Name": "system", + "Description": "Hello System.", + "Options": "none", + "Category": "system", + "Category Description": "System and Security" + }, + { + "Function Name": "Benchmarking", + "Group Name": "monitor", + "Description": "Armbian Monitor and Benchmarking.", + "Options": "", + "Category": "system", + "Category Description": "System and Security" + }, + { + "Function Name": "Install", + "Group Name": "system", + "Description": "Armbian installer", + "Options": "none.", + "Category": "system", + "Category Description": "System and Security" + } +] diff --git a/share/doc/armbian-configng/armbianCPU.svg b/share/doc/armbian-configng/armbianCPU.svg new file mode 100755 index 000000000..4d19784d6 --- /dev/null +++ b/share/doc/armbian-configng/armbianCPU.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/share/doc/armbian-configng/index.html b/share/doc/armbian-configng/index.html new file mode 100755 index 000000000..dc65e1110 --- /dev/null +++ b/share/doc/armbian-configng/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + +
Function NameGroup NameDescriptionOptionsCategoryCategory Description
NMTUInetworkNetwork Manager.none.networkNetwork Wired wireless Bluetooth access point
HellosystemHello System.nonesystemSystem and Security
BenchmarkingmonitorArmbian Monitor and Benchmarking.systemSystem and Security
InstallsystemArmbian installernone.systemSystem and Security
+ + +