summary refs log tree commit diff
diff options
context:
space:
mode:
authorSingustromo <singustromo@disroot.org>2023-07-02 12:54:20 +0200
committerSingustromo <singustromo@disroot.org>2023-07-02 12:54:20 +0200
commitd0b41fb5266b188f6642db1af49b5ed8fe564e57 (patch)
treedfa29b1b2b525189fbe3c430f7506de2341ba27f
downloadbash-framework-d0b41fb5266b188f6642db1af49b5ed8fe564e57.tar.gz
bash-framework-d0b41fb5266b188f6642db1af49b5ed8fe564e57.zip
Initial commit
-rw-r--r--.gitattributes4
-rw-r--r--.gitignore24
-rw-r--r--lib/array/contains.sh7
-rwxr-xr-xlib/bootstrap.sh120
-rw-r--r--lib/string/GetSpaces.sh8
-rw-r--r--lib/string/IsNumber.sh6
-rw-r--r--lib/string/SanitizeForVariable.sh4
-rw-r--r--lib/string/SlashReplacement.sh17
-rw-r--r--lib/string/UUID.sh24
-rw-r--r--lib/util/bash4.sh1
-rw-r--r--lib/util/command.sh40
-rw-r--r--lib/util/exit_codes.sh24
-rw-r--r--vision20
-rwxr-xr-xyour-script.sh16
14 files changed, 315 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..f0b2165
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+# Auto detect text files and perform LF normalization
+* text eol=lf
+*.ico binary
+*.png binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5c76efe
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+## Directory-based project format:
+.infinity/
+.bashful/
+libX/
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
diff --git a/lib/array/contains.sh b/lib/array/contains.sh
new file mode 100644
index 0000000..ddfa89b
--- /dev/null
+++ b/lib/array/contains.sh
@@ -0,0 +1,7 @@
+Array::contains() {
+	local element
+	for element in "${@:2}"; do
+		[[ "$element" = "$1" ]] && return
+	done
+	false; return
+}
diff --git a/lib/bootstrap.sh b/lib/bootstrap.sh
new file mode 100755
index 0000000..10593c3
--- /dev/null
+++ b/lib/bootstrap.sh
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+
+###########################
+### BOOTSTRAP FUNCTIONS ###
+###########################
+
+# $1 : relative filename
+File::absolute_path() {
+	local file="$1"
+	if [ "${file::1}" == "/" ]; then
+		[ -r "$file" ] || return
+		printf "%s\n" "$file"
+		return
+	fi
+	local file_basename="${file##*/}"
+	local file_path="$(realpath "$file")"
+	local file_path="${file_path%/*}"
+
+	[ -r "$file_path/$file_basename" ] || return
+	printf "%s\n" "$file_path/$file_basename"
+}
+
+System::SourceFile() {
+	local libPath="${1:?no file provided}"; shift
+
+	libPath="$(File::absolute_path "$libPath")" || return
+
+	## already imported? -> let's return
+	# if declare -f "$libPath" &> /dev/null &&
+	if [ "${_VOID_ALLOW_FILERELOAD}" == 'false' ] \
+		&& [ -n "${_VOID_IMPORTS[*]}" ] \
+		&& Array::contains "$libPath" "${_VOID_IMPORTS[@]}"
+	then
+		true; return
+	fi
+	
+	# Check syntax before
+	/usr/bin/env bash -n "$libPath" \
+		&& builtin source "$libPath" "$@" \
+		&& _VOID_IMPORTS+=( "$libPath" )
+}
+
+System::SourcePath() {
+	local file libPath="${1:?no path specified}"
+
+	shift
+	if [ -d "$libPath" ]; then
+		for file in "$libPath"/*.sh; do 
+			System::SourceFile "$file" "$@" || return
+		done
+		return
+	fi
+	System::SourceFile "$libPath" "$@" \
+		|| System::SourceFile "${libPath}.sh" "$@"
+}
+
+System::ImportOne() {
+	local libPath="$1"
+	local requestedPath="$libPath"
+
+	shift
+	[[ "$requestedPath" == './'* ]] && requestedPath="${requestedPath:2}"
+	requestedPath="${_VOID_PATH}/${requestedPath}"
+
+	# try relative to parent script
+	# try with parent
+	# try without parent
+	# try global library
+	# try local library
+	{
+		local localPath="$( cd "${BASH_SOURCE[1]%/*}" && pwd )"
+		localPath="${localPath}/${libPath}"
+		System::SourcePath "${localPath}" "$@"
+	} && return
+
+	System::SourcePath "${requestedPath}" "$@" \
+		|| System::SourcePath "${libPath}" "$@" \
+		|| System::SourcePath "${_VOID_LIBPATH}/${libPath}" "$@" \
+		|| System::SourcePath "${_VOID_PATH}/${libPath}" "$@"
+}
+
+System::Import() {
+	local libPath
+	for libPath in "$@"; do
+		System::ImportOne "$libPath"
+	done
+}
+
+## note: aliases are visible inside functions only if
+## they were initialized AFTER they were created
+## this is the reason why we have to load files in a specific order
+System::Bootstrap() {
+	System::Import array/contains && return
+	printf "FATAL ERROR: Unable to bootstrap (missing lib directory?)\n" 1>&2
+	false; exit
+}
+
+######################
+## INITIALZE SYSTEM ##
+######################
+
+# From: http://wiki.bash-hackers.org/scripting/debuggingtips
+export PS4='+(${BASH_SOURCE##*/}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
+
+# return the highest exitcode in a chain of pipes.
+set -o pipefail
+
+_VOID_LIBPATH="$(realpath "${BASH_SOURCE[0]}")"
+declare -g -r _VOID_LIBPATH="${_VOID_LIBPATH%/*}"
+declare -g -r _VOID_PATH="${_VOID_LIBPATH%/*}"
+
+declare -g -a _VOID_IMPORTS
+
+: ${_VOID_ALLOW_FILERELOAD:=false}
+
+import() {
+	eval "System::Import $@"
+}
+
+System::Bootstrap && declare -g -r _VOID_BOOTSTRAPPED=true
diff --git a/lib/string/GetSpaces.sh b/lib/string/GetSpaces.sh
new file mode 100644
index 0000000..77bdf7c
--- /dev/null
+++ b/lib/string/GetSpaces.sh
@@ -0,0 +1,8 @@
+String::GetSpaces() {
+  local howMany="$1"
+
+  if [[ "$howMany" -gt 0 ]]
+  then
+    ( printf "%*s" "$howMany" )
+  fi
+}
diff --git a/lib/string/IsNumber.sh b/lib/string/IsNumber.sh
new file mode 100644
index 0000000..7537c71
--- /dev/null
+++ b/lib/string/IsNumber.sh
@@ -0,0 +1,6 @@
+String::IsNumber() {
+  local input="$1"
+  [ -n "$input" ] || return
+  local regex='^-?[0-9]+([.][0-9]+)?$'
+  [[ "$input" =~ $regex ]] || return
+}
diff --git a/lib/string/SanitizeForVariable.sh b/lib/string/SanitizeForVariable.sh
new file mode 100644
index 0000000..044b9d4
--- /dev/null
+++ b/lib/string/SanitizeForVariable.sh
@@ -0,0 +1,4 @@
+String::SanitizeForVariableName() {
+  local type="$1"
+  echo "${type//[^a-zA-Z0-9]/_}"
+}
diff --git a/lib/string/SlashReplacement.sh b/lib/string/SlashReplacement.sh
new file mode 100644
index 0000000..7922d7a
--- /dev/null
+++ b/lib/string/SlashReplacement.sh
@@ -0,0 +1,17 @@
+String::ReplaceSlashes() {
+  local stringToMark="$1"
+
+  # Workaround for a Bash bug that causes string replacement to fail when a \ is in the string
+  local slash="\\"
+  local slashReplacement='_%SLASH%_'
+  echo "${stringToMark/$slash$slash/$slashReplacement}"
+}
+
+String::RestoreSlashes() {
+  local stringToMark="$1"
+
+  # Workaround for a Bash bug that causes string replacement to fail when a \ is in the string
+  local slash="\\"
+  local slashReplacement='_%SLASH%_'
+  echo "${stringToMark/$slashReplacement/$slash}"
+}
diff --git a/lib/string/UUID.sh b/lib/string/UUID.sh
new file mode 100644
index 0000000..29cbee0
--- /dev/null
+++ b/lib/string/UUID.sh
@@ -0,0 +1,24 @@
+String::GenerateUUID() {
+  ## https://gist.github.com/markusfisch/6110640
+  local N B 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
+}
diff --git a/lib/util/bash4.sh b/lib/util/bash4.sh
new file mode 100644
index 0000000..75f33b7
--- /dev/null
+++ b/lib/util/bash4.sh
@@ -0,0 +1 @@
+[[ "${BASH_VERSINFO[0]}" -lt 4 ]] && echo "The module you are trying to load requires bash >= 4" && exit 1 || true
diff --git a/lib/util/command.sh b/lib/util/command.sh
new file mode 100644
index 0000000..e59afb8
--- /dev/null
+++ b/lib/util/command.sh
@@ -0,0 +1,40 @@
+# no dependencies
+
+Command::GetType() {
+  local name="$1"
+  local typeMatch=$(type -t "$name" 2> /dev/null || true)
+  echo "$typeMatch"
+}
+
+Command::Exists(){
+  local name="$1"
+  local typeMatch=$(Command::GetType "$name")
+  [[ "$typeMatch" == "alias" || "$typeMatch" == "function" || "$typeMatch" == "builtin" ]]
+}
+
+Alias::Exists(){
+  local name="$1"
+  local typeMatch=$(Command::GetType "$name")
+  [[ "$typeMatch" == "alias" ]]
+}
+
+Function::Exists(){
+  local name="$1"
+  declare -f "$name" &> /dev/null
+}
+
+Function::GetAllStartingWith() {
+  local startsWith="$1"
+  compgen -A 'function' "$startsWith" || true
+}
+
+Function::InjectCode() {
+  local functionName="$1"
+  local injectBefore="$2"
+  local injectAfter="$3"
+  local body=$(declare -f "$functionName")
+  body="${body#*{}" # trim start
+  body="${body%\}}" # trim end
+  local enter=$'\n'
+  eval "${functionName}() { ${enter}${injectBefore}${body}${injectAfter}${enter} }"
+}
diff --git a/lib/util/exit_codes.sh b/lib/util/exit_codes.sh
new file mode 100644
index 0000000..e9f322a
--- /dev/null
+++ b/lib/util/exit_codes.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+#
+# exits
+#
+# Those values are come from /usr/include/sysexits.h
+#
+
+# successful termination
+Util_ExitCode_OK=0
+Util_ExitCode_USAGE=64  # command line usage error
+Util_ExitCode_DATAERR=65  # data format error
+Util_ExitCode_NOINPUT=66  # cannot open input
+Util_ExitCode_NOUSER=67  # addressee unknown
+Util_ExitCode_NOHOST=68  # host name unknown
+Util_ExitCode_UNAVAILABLE=69  # service unavailable
+Util_ExitCode_SOFTWARE=70  # internal software error
+Util_ExitCode_OSERR=71  # system error (e.g., can't fork)
+Util_ExitCode_OSFILE=72  # critical OS file missing
+Util_ExitCode_CANTCREAT=73  # can't create (user) output file
+Util_ExitCode_IOERR=74  # input/output error
+Util_ExitCode_TEMPFAIL=75  # temp failure; user is invited to retry
+Util_ExitCode_PROTOCOL=76  # remote error in protocol
+Util_ExitCode_NOPERM=77  # permission denied
+Util_ExitCode_CONFIG=78  # configuration error
diff --git a/vision b/vision
new file mode 100644
index 0000000..127900b
--- /dev/null
+++ b/vision
@@ -0,0 +1,20 @@
+== VOID BASH FRAMEWORK ==
+* a bash standard library
+
+* similiar to infinity framework
+  - much simpler, though
+  - no object oriented code
+  - removed alias declarations
+
+* get some ideas from bashful
+* generalize code from previously made projects (e.g. radio-sh)
+
+* should be lightweight and as simple as possible
+
+* libraries should have a distinctive name and a version number
+
+* allow import of specific function in library ?
+  - import util/io.msg
+  - substring after dot is the specific function
+  - support rename of function?
+    import util/io.msg as IO.msg
diff --git a/your-script.sh b/your-script.sh
new file mode 100755
index 0000000..82c3ccd
--- /dev/null
+++ b/your-script.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+## BOOTSTRAP ##
+workdir="$(realpath "${BASH_SOURCE[0]}")"
+readonly workdir="${workdir%/*}"
+. "$workdir"/lib/bootstrap.sh
+
+## MAIN EXAMPLES ##
+import string/IsNumber
+# Files not in dir/ can also be imported:
+#  import my_file.sh
+#  import my_dir/my_file.sh
+
+printf "imported: %s\n" "${_VOID_IMPORTS[@]}"
+
+## YOUR CODE GOES HERE ##