From ba5af86f8579c31857dd034cc34605a25cf45133 Mon Sep 17 00:00:00 2001 From: Peritia Date: Tue, 16 Sep 2025 16:00:11 +0200 Subject: [PATCH] feat: use modular functions to ensure similar basic functionality --- nyx/bash/lib/git.sh | 407 ++++++++++++++++++++++++++++++++++++++++ nyx/bash/lib/logging.sh | 201 ++++++++++++++++++++ nyx/bash/lib/sudo.sh | 56 ++++++ 3 files changed, 664 insertions(+) create mode 100644 nyx/bash/lib/git.sh create mode 100644 nyx/bash/lib/logging.sh create mode 100644 nyx/bash/lib/sudo.sh diff --git a/nyx/bash/lib/git.sh b/nyx/bash/lib/git.sh new file mode 100644 index 0000000..1f62ff5 --- /dev/null +++ b/nyx/bash/lib/git.sh @@ -0,0 +1,407 @@ +######################################################################## +# Default Variables +######################################################################## +#echo "Debug - git.sh loaded" +setup_git_vars () { + # Enforce dependencies between flags + if [[ "$auto_commit" == "true" ]]; then + if [[ "$auto_stage" != "true" ]]; then + log_warn "autoStage is disabled" + log_debug_warn "auto_stage is $auto_stage" + auto_commit=false + log_warn "Disabling autoCommit" + log_debug_warn "Setting auto_commit to $auto_commit" + log_warn "Please enable autoStage if you want to use this feature" + fi + fi + if [[ "$auto_push" == "true" ]]; then + if [[ "$auto_commit" != "true" ]]; then + log_warn "autoCommit is disabled" + log_debug_warn "auto_stage is $auto_stage" + auto_push=false + log_warn "Disabling autoPush" + log_debug_warn "Setting autoPush to $auto_push" + log_warn "Please enable autoCommit if you want to use this feature" + fi + fi +} + + + +######################################################################## +# Git wrapper +######################################################################## +git_wrapped_git() { + # Local variable for arguments just in case + local args=("$@") + "$git_bin" "${args[@]}" + return $? +} + +######################################################################## +# Layer 1 - No logs or tell out +######################################################################## +git_add_raw() { + local file="$1" + git_wrapped_git add "$file" + return $? +} + +git_commit_raw() { + local message="$1" + git_wrapped_git commit -m "$message" + return $? +} + +git_push_raw() { + git_wrapped_git push + return $? +} + +git_pull_rebase_raw() { + git_wrapped_git pull --rebase + return $? +} +git_pull_raw() { + git_wrapped_git --rebase + return $? +} + +git_reset_raw() { + local mode="${1:-soft}" # default mode: soft + local target="${2:-HEAD}" # default reset target: HEAD + git_wrapped_git reset "--$mode" "$target" + return $? +} + + + +######################################################################## +# Layer 2 - Logs or Tell out - Mainly Debug Logs +######################################################################## + +git_check_autoStage_enabled () { + if [[ "$auto_stage" == "true" ]]; then + log_debug_info "Auto Stage is enabled will execute function further" + return 0 + else + log_debug_warn "Auto Stage is disabled will not execute function further" + return 1 + fi +} + + +git_check_if_file_stage_valid () { + local file="$1" + # note file can also be a folder it was just easier + if [[ -e "$file" ]]; then + log_debug_ok "found file $file" + # check if the file has changes + if git_wrapped_git diff -- "$file" | grep -q .; then + log_debug_ok "file $file has changes" + return 0 + else + log_debug_warn "file $file has no changes and will be skipped" + return 1 + fi + else + log_debug_error "Did not find file $file." + return 1 + fi +} + +git_check_autoCommit_enabled () { + if [[ "$auto_commit" == "true" ]]; then + log_debug_info "Auto Commit is enabled will execute function further" + return 0 + else + log_debug_warn "Auto Commit is disabled will not execute function further" + return 1 + fi +} + +git_check_has_staged_files() { + local staged_files + # git diff --cached --name-only lists staged files + # If no output, there are no staged files to commit + staged_files=$(git_wrapped_git diff --cached --name-only) + if [[ -n "$staged_files" ]]; then + log_debug_ok "Found staged files to commit" + log_debug_info "Staged files:" + log_debug_info "$staged_files" + return 0 + else + log_debug_warn "No staged files found" + return 1 + fi +} + +git_check_autoPush_enabled () { + if [[ "$auto_push" == "true" ]]; then + log_debug_info "Auto Push is enabled will execute function further" + return 0 + else + log_debug_warn "Auto Push is disabled will not execute function further" + return 1 + fi +} + + + +git_check_has_staged_files() { + local staged_files + # git diff --cached --name-only lists staged files + # If no output, there are no staged files to commit + staged_files=$(git_wrapped_git diff --cached --name-only) + if [[ -n "$staged_files" ]]; then + log_debug_warn "Found staged files to commit" + log_debug_info "Staged files:" + log_debug_info "$staged_files" + return 0 + else + log_debug_ok "No staged files found - Nothing uncommitted" + return 1 + fi +} + +# Returns 0 if there are unstaged +git_check_has_unstaged_files() { + local unstaged_files + # git diff --name-only lists unstaged files + staged_files=$(git_wrapped_git diff --name-only) + if [[ -n "$staged_files" ]]; then + log_debug_warn "Unstaged files detected" + log_debug_info "Unstaged files:" + log_debug_info "$unstaged_files" + return 0 + else + log_debug_warn "No unstaged files" + return 1 + fi +} + + +git_check_if_dir_is_in_repo() { + local file="${1:-.}" # defaults to current dir if no file passed + if [[ -e "$file" ]]; then + log_debug_ok "found file $file" + # note file can also be a folder it was just easier to name it like so + if git_wrapped_git -C "$file" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + log_debug_ok "file $file is in git repo" + return 0 + else + log_debug_error "file $file is not in a git repo" + return 1 + fi + else + log_debug_error "Did not find file $file." + return 1 + fi +} + +git_check_necessary_for_rebase() { + # Reuse: first check if we are behind upstream + if ! git_check_if_behind_upstream; then + log_debug_info "No rebase necessary: branch is not behind upstream" + return 1 + fi + + local branch ahead behind + branch=$(git_wrapped_git rev-parse --abbrev-ref HEAD 2>/dev/null) + ahead=$(git_wrapped_git rev-list --count "@{u}..${branch}") + behind=$(git_wrapped_git rev-list --count "${branch}..@{u}") + + if [[ $ahead -gt 0 && $behind -gt 0 ]]; then + log_debug_ok "Branch $branch has diverged (ahead: $ahead, behind: $behind), rebase required" + return 0 + fi + + log_debug_info "Branch $branch has no divergence (ahead: $ahead, behind: $behind)" + return 1 +} + +git_check_if_behind_upstream() { + # First make sure we are in a git repo + if ! git_check_if_dir_is_in_repo; then + log_debug_error "Not inside a git repository" + return 1 + fi + + # Make sure we have a tracking branch + if ! git_wrapped_git rev-parse --abbrev-ref "@{u}" >/dev/null 2>&1; then + log_debug_warn "Current branch has no upstream, cannot check if behind" + return 1 + fi + + git_wrapped_git fetch --quiet + + local behind_count + behind_count=$(git_wrapped_git rev-list --count "HEAD..@{u}") + + if [[ "$behind_count" -gt 0 ]]; then + log_debug_ok "Branch is behind upstream by $behind_count commits" + return 0 + else + log_debug_info "Branch is up to date with upstream" + return 1 + fi +} + + +######################################################################## +# Layer 3 - Logs or Tell out - Also used in Main Script directly +######################################################################## + +git_add () { + local file="$1" + # note file can also be a folder it was just easier + if git_check_autoStage_enabled; then + if git_check_if_file_stage_valid "$file"; then + if git_add_raw "$file"; then + log_ok "Added file: \"$file\"" + return 0 + else + log_error "Failed to add file: \"$file\"" + return 1 + fi + else + log_verbose_warn "Did not Stage: $file" + return 1 + fi + else + return 1 + fi +} + +git_commit () { + local message="$1" + if git_check_autoCommit_enabled; then + if git_check_has_staged_files; then + if git_commit_raw "$message"; then + log_ok "Committed with Message: \"$message\"" + return 0 + else + log_error "Commit failed with Message: \"$message\"" + return 1 + fi + else + log_verbose_warn "Nothing to commit. Would've committed with Message: \"$message\"" + return 1 + fi + else + return 1 + fi +} + +git_push () { + # Check if auto-push is enabled first + if git_check_autoPush_enabled; then + if git_push_raw; then + log_ok "Pushed to remote successfully" + return 0 + else + log_error "Push to remote failed" + return 1 + fi + else + log_verbose_warn "Auto Push disabled, skipping push" + return 1 + fi +} + +git_pull_rebase() { + + # check if current dir is in git repo + if ! git_wrapped_git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + log_error "Not inside a git repository, cannot pull" + return 1 + fi + + if git_check_necessary_for_rebase; then + # Rebase mode: we have local commits and are behind upstream + if git_wrapped_git pull --rebase; then + log_ok "Pulled from remote and rebased successfully" + return 0 + else + log_error "Pull with rebase failed" + return 1 + fi + elif git_check_if_behind_upstream; then + # Behind but no local commits → simple fast-forward pull + if git_wrapped_git pull; then + log_ok "Pulled from remote successfully (fast-forward)" + return 0 + else + log_error "Pull from remote failed" + return 1 + fi + else + log_info "Branch is already up to date, no pull required" + return 0 + fi +} + +git_reset() { + local mode="${1:-soft}" # default reset mode is soft + local target="${2:-HEAD}" # commit hash, branch, or tag + + log_info "Resetting to target: $target (mode: $mode)" + if git_reset_raw "$mode" "$target"; then + log_ok "Successfully reset to $target with --$mode" + return 0 + else + log_error "Failed to reset to $target" + return 1 + fi +} + + +git_store_starting_commit () { + # Get the current HEAD commit hash + local commit + commit=$(git_wrapped_git rev-parse HEAD 2>/dev/null) + + if [[ $? -eq 0 && -n "$commit" ]]; then + git_starting_commit="$commit" + log_debug_info "Stored starting commit: $git_starting_commit" + return 0 + else + log_debug_error "Unable to retrieve current commit hash" + return 1 + fi +} + +######################################################################## +# COMPILED FUNCTION SUMMARY +# +######################################################################## +# SETUP +######################################################################## +# setup_git_vars() +# Ensures auto_stage / auto_commit / auto_push flags are consistent. +# If dependencies are not satisfied, disables dependent flags and logs warnings. +# +######################################################################## +# LAYER 3: MAIN FUNCTIONS +######################################################################## +# git_add +# Stages file if auto_stage is enabled and file is valid. +# +# git_commit +# Commits staged files if auto_commit is enabled and there are staged files. +# +# git_push +# Pushes current branch if auto_push is enabled. +# +# git_pull_rebase +# Pulls latest changes. Uses rebase if local commits exist and branch has diverged. +# Falls back to fast-forward pull if only behind. Logs appropriately if up to date. +# +######################################################################## +# VARIABLES +######################################################################## +# auto_stage=true +# auto_commit=true +# auto_push=true +# +######################################################################## diff --git a/nyx/bash/lib/logging.sh b/nyx/bash/lib/logging.sh new file mode 100644 index 0000000..196de53 --- /dev/null +++ b/nyx/bash/lib/logging.sh @@ -0,0 +1,201 @@ +######################################################################## +# CONFIGURATION +######################################################################## +#echo "Debug - logging.sh loaded" + + +setup_logging_vars () { + local verbosity=${1:-3} # Default to max verbosity if not provided + NYX_VERBOSITY=$verbosity + log_to_file_disable +} +setup_logging_basic () { + + log_info "Log set to $NYX_VERBOSITY" + # Default log directory + log_info "Setting up LogDir in $log_subdir" + mkdir -p "$log_subdir" + + logPath="$log_subdir/log_$(date '+%Y-%m-%d_%H-%M-%S').log" + log_info "Full log is saved under $logPath" +} + + +######################################################################## +# Control whether logs are written to file +######################################################################## +log_to_file_enable () { + if [[ "$log_to_file" == "true" ]]; then + log_debug_info "log_to_file is already true" + log_debug_warn "log_to_file is $log_to_file" + else + log_to_file=true + log_verbose_warn "log_to_file is enabled" + log_debug_warn "log_to_file is $log_to_file" + fi +} + +log_to_file_disable () { + if [[ "$log_to_file" == "true" ]]; then + log_to_file=false + log_verbose_warn "log_to_file is disabled" + log_debug_warn "log_to_file is $log_to_file" + else + log_debug_info "log_to_file is already false" + log_debug_warn "log_to_file is $log_to_file" + fi +} + + +######################################################################## +# Write a log line to the file +######################################################################## +write_log() { + local line="$1" + echo "$line" >> "$logPath" +} + +######################################################################## +# Output with timestamp, level, color, and verbosity +######################################################################## +tell_out() { + local message="$1" + local level="${2:-INFO}" # Default level: INFO + local verbosity_level="${3:-1}" # Default verbosity level for message + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + # Append diagnostic info for errors + if [[ "$level" == "ERROR" ]]; then + tell_out "$message # $(what_messed_up)" "DEBUG" "3" + fi + + local log_line="[$timestamp][$level] $message" + local line="[$level] $message" + + # Only output messages if verbosity level is sufficient + if [[ $NYX_VERBOSITY -ge $verbosity_level ]]; then + case "$level" in + INFO) echo -e "\033[0;37m$line\033[0m" ;; # Gray + OK) echo -e "\033[0;32m$line\033[0m" ;; # Green + WARN) echo -e "\033[1;33m$line\033[0m" ;; # Yellow + ERROR) echo -e "\033[0;31m$line\033[0m" ;; # Red + CMD) echo -e "\033[0;36m$line\033[0m" ;; # Cyan + OTHER) echo -e "\033[0;35m$line\033[0m" ;; # Magenta + LINE) echo -e "\033[1;30m$line\033[0m" ;; # Dark Gray + *) echo -e "$line" ;; # Default no color + esac + fi + + # Always write to log file if enabled + if [[ "$log_to_file" == "true" ]]; then + write_log "$log_line" + fi +} + + +######################################################################## +# Separator line for logs +######################################################################## +log_separator() { + local verbosity="${1:-0}" + tell_out "===========================================" "LINE" $verbosity +} + +######################################################################## +# Execute a command with logging and error handling +######################################################################## +execute() { + local command="$1" + local cmd_verbosity="${2:-2}" + + tell_out "Executing: $command" "CMD" "$cmd_verbosity" + tell_out "### Log from $command start ###" "CMD" "$cmd_verbosity" + log_separator "$cmd_verbosity" + + # Use a subshell and a pipe to tee both stdout and stderr to the log + # while preserving the exit code + local status + { + # Redirect stderr to stdout so both are captured + eval "$command" 2>&1 | while IFS= read -r line; do + tell_out "$line" "CMD" "$cmd_verbosity" + done + } + status=${PIPESTATUS[0]} # Capture exit code of eval, not the while loop + + log_separator "$cmd_verbosity" + + tell_out "### Log from $command end ###" "CMD" "$cmd_verbosity" + + # Log success or failure + if (( status == 0 )); then + tell_out "Execution successful: $command" "OK" "$cmd_verbosity" + else + tell_out "Error executing command: $command (exit code $status)" "ERROR" 0 + fi + + return $status +} + +######################################################################## +# Call stack helper for debugging +######################################################################## +what_messed_up() { + local stack=("${FUNCNAME[@]}") + local call_chain="" + #unset 'stack[0]' # remove current function + #unset 'stack[1]' # remove direct caller (log_error etc.) + #unset 'stack[-1]' # remove "main" + + # Join the remaining stack elements with " -> " + for function in "${stack[@]}"; do + if [[ -z "$call_chain" ]]; then + call_chain="$function" + else + call_chain="$call_chain -> $function" + fi + done + + echo "$call_chain" +} + + + +######################################################################## +# Verbosity helper functions +######################################################################## +log_debug_info() { tell_out "$1" "INFO" 3; } +log_debug_warn() { tell_out "$1" "WARN" 3; } +log_debug_ok() { tell_out "$1" "OK" 3; } +log_debug_error() { tell_out "$1" "ERROR" 3; } + + +log_verbose_ok () { tell_out "$1" "OK" 2; } +log_verbose_info () { tell_out "$1" "INFO" 2; } +log_verbose_warn () { tell_out "$1" "WARN" 2; } +log_verbose_error () { tell_out "$1" "ERROR" 2; } +log_verbose_end () { tell_out "$1" "END" 2; } + +log_ok() { tell_out "$1" "OK" 1; } +log_info() { tell_out "$1" "INFO" 1; } +log_warn() { tell_out "$1" "WARN" 1; } +log_error() { tell_out "$1" "ERROR" 0; } +log_end() { tell_out "$1" "END" 0; } + +######################################################################## +# USAGE SUMMARY +# +######################################################################## +# LOGGING FUNCTIONS +######################################################################## +# log_debug_* "Message" # with variations +# log_verbose "Message" # Shown at verbosity 2+ +# log_info "Message" # Standard info message (verbosity 1+) +# log_warn "Message" # Warning message (verbosity 1+) +# log_error "Message" # Error message (always shown) +# +# log_to_file_enable # Enable writing all logs to $logPath +# log_to_file_disable # Disable log file writing +# +######################################################################## diff --git a/nyx/bash/lib/sudo.sh b/nyx/bash/lib/sudo.sh new file mode 100644 index 0000000..d16dcfc --- /dev/null +++ b/nyx/bash/lib/sudo.sh @@ -0,0 +1,56 @@ +#echo "Debug - sudo.sh loaded" + + +check_if_run_with_sudo() { + log_debug_info "Checking if the script is being run with sudo or as root..." + + # Check if running as root + if [[ "$EUID" -eq 0 ]]; then + log_error "This script must NOT be run as root or with sudo." + log_debug_error "Detected EUID=0 (root). Please run as a normal user." + exit 1 + fi + + # Check if running through sudo + if [[ -n "$SUDO_USER" ]]; then + log_error "This script must NOT be run with sudo." + log_debug_error "Detected SUDO_USER='$SUDO_USER'. Run without sudo." + exit 1 + fi + + log_verbose_ok "Sudo/root check passed. Running as a normal user: $USER" + return 0 +} + +get_sudo_ticket() { + log_verbose_info "Checking if sudo rights are already available..." + + # Check if sudo permissions are currently active + if sudo -n true 2>/dev/null; then + log_verbose_ok "Sudo rights are already active. No password required." + return 0 + fi + + log_info "Sudo permissions required. Prompting for password..." + + # Attempt to refresh or request sudo credentials + if sudo -v; then + log_ok "Sudo rights successfully acquired." + return 0 + else + log_error "Failed to acquire sudo permissions. Incorrect password or sudo not available." + return 1 + fi +} + +######################################################################## +# +# Sudo/Root Handling Functions: +# check_if_run_with_sudo +# - Exits if the script is run as root or with sudo. +# +# get_sudo_ticket +# - Checks if sudo permissions are cached. +# - If not, prompts for password to acquire them. +# +######################################################################## \ No newline at end of file