From 320f29d373767a3c4256ad8db589454d85fde8e2 Mon Sep 17 00:00:00 2001 From: labbots Date: Sat, 6 Jun 2020 21:05:52 +0100 Subject: [PATCH] Initial commit --- .editorconfig | 22 +++++ .remarkrc | 7 ++ CODE_OF_CONDUCT.md | 76 ++++++++++++++++ CONTRIBUTING.md | 4 + bash_utilities.sh | 4 + bin/generate_readme.sh | 156 ++++++++++++++++++++++++++++++++ bin/readme-template.md | 21 +++++ bin/shdoc.awk | 196 +++++++++++++++++++++++++++++++++++++++++ src/array.sh | 136 ++++++++++++++++++++++++++++ src/string.sh | 196 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 818 insertions(+) create mode 100644 .editorconfig create mode 100644 .remarkrc create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 bash_utilities.sh create mode 100755 bin/generate_readme.sh create mode 100644 bin/readme-template.md create mode 100755 bin/shdoc.awk create mode 100644 src/array.sh create mode 100644 src/string.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f9b0758 --- /dev/null +++ b/.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/.remarkrc b/.remarkrc new file mode 100644 index 0000000..7f65681 --- /dev/null +++ b/.remarkrc @@ -0,0 +1,7 @@ +{ + "plugins": [ + "remark-preset-lint-markdown-style-guide", + ["remark-lint-list-item-spacing", false], + ["remark-lint-maximum-line-length", false] + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..461c926 --- /dev/null +++ b/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/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..58d3faf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# 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. diff --git a/bash_utilities.sh b/bash_utilities.sh new file mode 100644 index 0000000..78528db --- /dev/null +++ b/bash_utilities.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +source src/array.sh +source src/string.sh diff --git a/bin/generate_readme.sh b/bin/generate_readme.sh new file mode 100755 index 0000000..c143e33 --- /dev/null +++ b/bin/generate_readme.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +README="README.md" + +_setup() { + INVALID_CHARS="'[]/?!:\`.,()*\";{}+=<>~$|#@&–—" + MINLEVEL=1 + MAXLEVEL=2 + SCRIPT_FILE=$(basename $0) + SCRIPT_FILENAME="${SCRIPT_FILE%.*}" + rm README.md + cp readme-template.md "$README" +} + +_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)" + ./shdoc.awk < $file >> "$2" +} + +_insert_shdoc_to_file() { + declare shdoc_tmp_file="$1" + declare dest_file="$2" + + declare start_shdoc="" + declare info_shdoc="" + declare end_shdoc="" + + sed -i "1s/^/$info_shdoc\n/" "$shdoc_temp_file" + + if grep --color=always -Pzl "(?s)$start_shdoc.*\n.*$end_shdoc" $dest_file &> /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_temp_file" -e ":a; n; /$end_shdoc/ {p; b}; ba}; p" $dest_file + echo -e "\n Updated shdoc content to $dest_file\n" + + else + + echo -e "\n Created\n" + fi +} + +_process_sh_files() { + declare shdoc_temp_file=$(_setup_tempfile) + find ../src -name '*.sh' -print0 | sort -z | + while IFS= read -r -d '' line; do + _generate_shdoc "$line" "$shdoc_temp_file" + done + _insert_shdoc_to_file "$shdoc_temp_file" "$README" + rm "$shdoc_temp_file" + +} + +_generate_toc() { + + declare line level title anchor output counter temp_output + + while IFS='' read -r line || [[ -n "$line" ]]; do + level="$(echo "$line" | sed -E 's/(#+).*/\1/; s/#/ /g; s/^ //')" + title="$(echo "$line" | sed -E 's/^#+ //')" + # 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 + # copying doctoc behavior + 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 + output="$(echo "$output" | sed 's/--*/-/g')" + + echo "$output" + +} + +_insert_toc_to_file() { + + declare toc_text="$2" + + # inspired in doctoc lines + declare start_toc="" + declare info_toc="" + declare 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="$(echo "$toc_block" | sed "s,\&,$utext_ampersand,g")" + toc_block="$(echo "$toc_block" | sed "s,\/,$utext_slash,g")" + + # 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" $1 &> /dev/null; then + echo -e "\n Updated content of $appname block in $1 succesfully\n" + # 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" $1 + else + echo -e "\n Created $appname block in $1 succesfully\n" + sed -i 1i"$toc_block" "$1" + fi + + # undo symbol replacements + sed -i "s,$utext_ampersand,\&,g" $1 + sed -i "s,$utext_slash,\/,g" $1 + +} + +_process_toc() { + declare toc_temp_file=$(_setup_tempfile) + + sed '/```/,/```/d' "$README" > "$toc_temp_file" + + declare level=$MINLEVEL + while [[ $(grep -E "^#{$level} " "$toc_temp_file" | wc -l) -le 1 ]]; do + level=$(($level + 1)) + done + if [[ $MINLEVEL -ne $level ]]; then + echo -e "\nnote: detected all headers (maybe except 1) in level $level, switching to that level of headers to fill table of contents" + fi + MINLEVEL=$level + toc_text=$(_generate_toc "$toc_temp_file") + rm "$toc_temp_file" + + _insert_toc_to_file "$README" "$toc_text" +} + +main() { + _setup + _process_sh_files + _process_toc +} + +main "$@" diff --git a/bin/readme-template.md b/bin/readme-template.md new file mode 100644 index 0000000..6dd744a --- /dev/null +++ b/bin/readme-template.md @@ -0,0 +1,21 @@ +

Bash Utilites

+ +

+License + +

+Bash library which provides utility functions and helpers for functional programming in Bash. + + + + + + + + + + + +## License + +[MIT](https://github.com/labbots/google-drive-upload/blob/master/LICENSE) diff --git a/bin/shdoc.awk b/bin/shdoc.awk new file mode 100755 index 0000000..01bafc9 --- /dev/null +++ b/bin/shdoc.awk @@ -0,0 +1,196 @@ +#!/usr/bin/awk -f + +BEGIN { + 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["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" +} + +function render(type, text) { + return gensub( \ + styles[type, "from"], + styles[type, "to"], + "g", + text \ + ) +} + +function reset() { + has_example = 0 + has_args = 0 + has_exitcode = 0 + has_stdout = 0 +} + +/^[[:space:]]*# @internal/ { + is_internal = 1 +} + +/^[[:space:]]*# @file/ { + sub(/^[[:space:]]*# @file /, "") + filedoc = render("h1", $0) "\n" +} + +/^[[:space:]]*# @brief/ { + sub(/^[[:space:]]*# @brief /, "") + filedoc = filedoc "\n" $0 +} + +/^[[:space:]]*# @description/ { + in_description = 1 + in_example = 0 + + reset() + + docblock = "" +} + +in_description { + if (/^[^[[:space:]]*#]|^[[:space:]]*# @[^d]|^[[:space:]]*[^#]/) { + if (!match(docblock, /\n$/)) { + docblock = docblock "\n" + } + in_description = 0 + } else { + sub(/^[[:space:]]*# @description /, "") + sub(/^[[:space:]]*# /, "") + sub(/^[[:space:]]*#$/, "") + + docblock = docblock "\n" $0 + } +} + +in_example { + if (! /^[[:space:]]*#[ ]{3}/) { + in_example = 0 + + docblock = docblock "\n" render("/code") "\n" + } else { + sub(/^[[:space:]]*#[ ]{3}/, "") + + docblock = docblock "\n" $0 + } +} + +/^[[:space:]]*# @example/ { + in_example = 1 + + docblock = docblock "\n" render("h3", "Example") + docblock = docblock "\n\n" render("code", "bash") +} + +/^[[:space:]]*# @arg/ { + if (!has_args) { + has_args = 1 + + docblock = docblock "\n" render("h3", "Arguments") "\n\n" + } + + sub(/^[[:space:]]*# @arg /, "") + + $0 = render("argN", $0) + $0 = render("arg@", $0) + + docblock = docblock render("li", $0) "\n" +} + +/^[[:space:]]*# @noargs/ { + docblock = docblock "\n" render("i", "Function has no arguments.") "\n" +} + +/^[[:space:]]*# @exitcode/ { + if (!has_exitcode) { + has_exitcode = 1 + + docblock = docblock "\n" render("h3", "Exit codes") "\n\n" + } + + sub(/^[[:space:]]*# @exitcode /, "") + + $0 = render("exitcode", $0) + + docblock = docblock render("li", $0) "\n" +} + +/^[[:space:]]*# @see/ { + sub(/[[:space:]]*# @see /, "") + + $0 = render("anchor", $0) + $0 = render("li", $0) + + docblock = docblock "\n" render("h4", "See also") "\n\n" $0 "\n" +} + +/^[[:space:]]*# @stdout/ { + has_stdout = 1 + + sub(/^[[:space:]]*# @stdout /, "") + + docblock = docblock "\n" render("h3", "Output on stdout") + docblock = docblock "\n\n" render("li", $0) "\n" +} + +/^[ \t]*(function([ \t])+)?([a-zA-Z0-9_:-]+)([ \t]*)(\(([ \t]*)\))?[ \t]*\{/ && docblock != "" { + 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 + + url = func_name + # https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb#L44-L45 + url = tolower(url) + gsub(/[^[:alnum:] -]/, "", url) + gsub(/ /, "-", url) + + toc = toc "\n" "- [" func_name "](#" url ")" + } + + docblock = "" + reset() +} + +END { + if (filedoc != "") { + print filedoc + } + print toc + print "" + print doc +} diff --git a/src/array.sh b/src/array.sh new file mode 100644 index 0000000..9177e72 --- /dev/null +++ b/src/array.sh @@ -0,0 +1,136 @@ +#!/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 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 % $#]}" +} diff --git a/src/string.sh b/src/string.sh new file mode 100644 index 0000000..e244e13 --- /dev/null +++ b/src/string.sh @@ -0,0 +1,196 @@ +#!/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 The string that will 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 +# 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 + 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 "${@}\n" | tr '[A-Z]' '[a-z]' + 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 "${@}\n" | tr '[a-z]' '[A-Z]' + 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 + +}