/home/alex/.bashrc (1)

From RaySoft
# ------------------------------------------------------------------------------
# .bashrc | .profile
# ==================
#
# Scope     Native
# Copyright (C) 2024 by RaySoft, Zurich, Switzerland
# License   GNU General Public License (GPL) 2.0
#           https://www.gnu.org/licenses/gpl2.txt
#
# ------------------------------------------------------------------------------
# Configuration
# -------------

MY_PROXY=''

# ------------------------------------------------------------------------------
# Test the environment
# --------------------

# If the shell is not running interactively, don't do anything!
if [[ "$-" != *i* ]]; then
  return 0
fi

# If the shell is not a Bash, return with an error.
if [[ -z "${BASH}" ]]; then
  echo
  echo "ERROR: This configuration file is not executed by a Bash!"
  echo

  return 1
fi

# If the Bash version is less than 4, print a warning.
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
  echo
  echo "WARNING: This is an old Bash version (${BASH_VERSINFO[0]}.x)!"
fi

# ------------------------------------------------------------------------------
# Get information about the operating system & current user
# ---------------------------------------------------------

# Store the host name
host_name="$(hostname)"

# Store the current user id.
user_id="$(id -r -u)"

# Store the current user name.
user_name="$(id -n -r -u)"

echo
echo "${host_name}"

if [[ "${host_name}" != 'raysoft.ch' ]]; then
  case "$(uname -s)" in
    'Darwin' )
      echo "macOS $(sw_vers -productVersion) ($(sw_vers -buildVersion))"
    ;;
    'Linux' )
      hostnamectl | awk --field-separator ': +' '/Operating System/ {print $2}'
    ;;
  esac
fi

uname -m -r -s

echo
echo "${user_name} (${user_id})"
echo
echo "Load '${BASH_SOURCE[0]}'..."

# ------------------------------------------------------------------------------
# Environment definition
# ----------------------

count=0

# Store the 8 command line colors.
# Normal font color (nr), bold font color (bl) and background color (bg)
for item in 'black' 'red' 'green' 'yellow' 'blue' 'purple' 'cyan' 'white'; do
  declare -x "${item}_nr=\e[0;3${count}m" "${item}_bl=\e[1;3${count}m" \
             "${item}_bg=\e[4${count}m"

  ((count+=1))
done

# Store the reset string.
declare -x none='\e[m'

# Store the proxy settings for FTP, HTTP, HTTPS and RSYNC if the variable
# MY_PROXY is set.
if [[ -n "${MY_PROXY}" ]]; then
  for item in 'ftp' 'http' 'https' 'rsync'; do
    declare -x "${item}_proxy=${MY_PROXY}"
  done
fi

# ----

EDITOR=''

for item in 'nvim' 'vim' 'vi'; do
  if EDITOR="$(type -P "${item}" 2>'/dev/null')"; then
    break
  fi
done

if [[ -z "${EDITOR}" ]]; then
  echo "Error finding 'Neovim', 'vim' or 'vi'!"
fi

# A colon-separated list of suffixes to ignore when performing filename
# completion. A filename whose suffix matches one of the entries in FIGNORE is
# excluded from the list of matched filenames.
FIGNORE=''

# A colon-separated list of values controlling how commands are saved on the
# history list.
# - A value of 'erasedups' causes all previous lines matching the current line
#   to be removed from the history list before that line is saved.
# - If the list of values includes 'ignorespace', lines which begin with a space
#   character are not saved in the history list.
HISTCONTROL='erasedups,ignorespace'

# The maximum number of lines contained in the history file. When this variable
# is assigned a value, the history file is truncated, if necessary, to contain
# no more than that number of lines by removing the oldest entries. The history
# file is also truncated to this size after writing it when a shell exits.
HISTFILESIZE=1000

# A colon-separated list of patterns used to decide which command lines should
# be saved on the history list.
HISTIGNORE='&:bg:exit:fg:l:ll:lr:ls:lt:pwd'

# The number of commands to remember in the command history.
HISTSIZE=1000

# If this variable is set and not null, its value is used as a format string
# for strftime to print the time stamp associated with each history entry
# displayed by the history builtin. If this variable is set, time stamps are
# written to the history file so they may be preserved across shell sessions.
HISTTIMEFORMAT='%F %H:%M  '

if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
  HISTTIMEFORMAT="$(echo -e "${cyan_nr}${HISTTIMEFORMAT}${none}")"
fi

# Used to determine the locale category for any category not specifically
# selected with a variable starting with LC_.
LANG='de_CH.UTF-8'

# This variable determines the collation order used when sorting the results of
# filename expansion, and determines the behavior of range expressions,
# equivalence classes, and collating sequences within filename expansion and
# pattern matching.
LC_COLLATE='C'

# This variable determines the interpretation of characters and the behavior of
# character classes within filename expansion and pattern matching.
LC_CTYPE='en_US.UTF-8'

# This variable determines the locale used to translate double-quoted strings
# preceded by a '$'.
LC_MESSAGES='en_US.UTF-8'

for item in '/usr/local/share/man' '/usr/share/man' \
            '/usr/local/opt/gnu-tar/libexec/gnuman'
do
  if [[ -d "${item}" ]]; then
    if [[ -n "${MANPATH}" && ! "${MANPATH}" =~ (^|:)${item}(:|$) ]]; then
      MANPATH="${item}:${MANPATH}"
    else
      MANPATH="${item}"
    fi
  fi
done

# A colon-separated list of directories in which the shell looks for commands.
for item in '/usr/sbin' '/usr/bin' '/sbin' '/bin' \
            '/usr/local/sbin' '/usr/local/bin' '/usr/local/opt/openssl/bin' \
            '/usr/local/opt/coreutils/libexec/gnubin' "${HOME}/bin"
do
  if [[ -d "${item}" ]]; then
    if [[ -n "${PATH}" && ! "${PATH}" =~ (^|:)${item}(:|$) ]]; then
      PATH="${item}:${PATH}"
    else
      PATH="${item}"
    fi
  fi
done

# If set, the value is executed as a command prior to issuing each primary
# prompt:
# - 'history -a' appends history lines from this session to the history file.
PROMPT_COMMAND='history -a'

# If set to a number greater than zero, the value is used as the number of
# trailing directory components to retain when expanding the '\w' and '\W'
# prompt string escapes.
PROMPT_DIRTRIM=5

# The primary prompt string.
PS1='\n\342\224\214\342\224\200 $(my::_exit_status "$?") '

if [[ "${user_id}" -eq 0 || "${user_name}" == 'root' ]]; then
  color="${red_bl}"

  PS1+=''
else
  color="${blue_bl}"

  PS1+="\u\[${color}\]@\[${none}\]"
fi

PS1+="\H\[${color}\]:\[${none}\]\w\n\342\224\224\342\224\200\342\224\200> "
PS1+="\[${color}\]"'#'"\[${none}\] "

# If TMOUT is set to a non-zero value 'time', then the shell prompt will time
# out after 'time' seconds. This will cause a logout.
if [[ "${user_id}" =~ ^0|1000$ || "${user_name}" =~ ^root|raysoft$ ]]; then
  TMOUT=600
else
  TMOUT=3600
fi

if [[ -n "${TMPDIR}" && -d "${TMPDIR}" ]]; then
  TMP="${TMPDIR}"
else
  TMP='/tmp'
fi

declare -x EDITOR HIST{CONTROL,FILESIZE,IGNORE,SIZE,TIMEFORMAT} LANG \
           LC_{COLLATE,CTYPE,MESSAGES} MANPATH PATH PROMPT_{COMMAND,DIRTRIM} PS1

declare -r -x FIGNORE TMOUT TMP

# ----

# If set, Bash attempts to save all lines of a multiple-line command in the same
# history entry. This allows easy re-editing of multi-line commands.
shopt -s 'cmdhist'

# If set, the history list is appended to the file named by the value of the
# HISTFILE variable when the shell exits, rather than overwriting the file.
shopt -s 'histappend'

# If set, and Readline is being used, a user is given the opportunity to re-edit
# a failed history substitution.
shopt -s 'histreedit'

# If set, and Readline is being used, the results of history substitution are
# not immediately passed to the shell parser. Instead, the resulting line is
# loaded into the Readline editing buffer, allowing further modification.
shopt -s 'histverify'

# If set, and Readline is being used, Bash will not attempt to search the PATH
# for possible completions when completion is attempted on an empty line.
shopt -s 'no_empty_cmd_completion'

# If set, prompt strings undergo parameter expansion, command substitution,
# arithmetic expansion, and quote removal after being expanded.
shopt -s 'promptvars'

# If set, the source builtin uses the value of PATH to find the directory
# containing the file supplied as an argument.
shopt -u 'sourcepath'

# ----

# Forgets all remembered locations of commands.
hash -r

# Prevents the Bash from dumping core in the case of an unrecoverable error,
# since core dumps may contain sensitive data from memory.
ulimit -c 0 --

# Sets the user file-creation mask.
if [[ "${user_id}" =~ ^0|1000$ || "${user_name}" =~ ^root|raysoft$ ]]; then
  umask 0022
else
  umask 0077
fi

# ------------------------------------------------------------------------------
# Key bindings
# ------------

bind -r '\C-b'
bind '\C-b':'backward-word'

bind -r '\C-f'
bind '\C-f':'forward-word'

# ------------------------------------------------------------------------------
# Private functions
# -----------------

my::_cd() {
  # Provide a 'cd' command replacement using 'pushd'.
  #
  # Arguments:
  #   $1: Path (optional)
  #
  # Returns:
  #   0:  Success
  #   >0: Error

  if [[ "$#" -eq 0 ]]; then
    set -- "${HOME}"
  fi

  if [[ ! -d "$1" ]]; then
    echo "Error finding directory: $1!"
    return 1
  fi

  if ! pushd "$1" >'/dev/null' 2>&1; then
    echo "Error changing directory: $1!"
    return 1
  fi

  return 0
}

my::_exit_status() {
  # Verify the exit value of a executable and print a colorized output.
  #
  # Arguments:
  #   $1: Exit value
  #
  # Returns:
  #   String

  if [[ "$#" -ne 1 || ! "$1" =~ ^[0-9]+$ ]]; then
    echo 'Error calling function!'
    return 1
  fi

  if [[ "$1" -eq 0 ]]; then
    echo -e "[${green_bl}✔︎${none}]"
  else
    echo -e "[${red_bl}${none}]"
  fi

  return 0
}

my::_which() {
  # Provide a 'which' command replacement using 'type'.
  #
  # Arguments:
  #   $@: Shell commands
  #
  # Returns:
  #   String

  if [[ "$#" -eq 0 ]]; then
    echo 'Error calling function!'
    return 1
  fi

  local bin=''
  local name=''
  local prefix=''
  local var=''

  if [[ "$1" == '-p' ]]; then
    prefix="$2"; shift 2
  fi

  for name in "$@"; do
    if bin="$(type -P "${name}" 2>'/dev/null')"; then
      var="${name//-/_}"

      echo "${prefix^^}${var^^}=('${bin}')"
    else
      echo "Error finding binary: ${name}!"
      return 1
    fi
  done | sort

  return 0
}

# ------------------------------------------------------------------------------
# Aliases & Hooks
# ---------------

# Removes all existing aliases.
unalias -a

alias -- -='popd >"/dev/null" 2>&1'

if declare -F my::_cd >'/dev/null' 2>&1; then
  alias cd='my::_cd'
fi

if df -T >'/dev/null' 2>&1; then                      # GNU df version
  alias df='df -h -T'
else                                                  # other df versions
  alias df='df -h'
fi

alias du='du -h'

alias env='env | sort -f'

for item in 'grep' 'egrep' 'fgrep'; do
  if type -P "${item}" >'/dev/null' 2>&1 \
     && "${item}" --color='auto' 'foo' <<<'foo' >'/dev/null' 2>&1
  then
    alias "${item}=${item} --color='auto'"            # GNU *grep versions
  fi
done

if type -P 'eza' >'/dev/null' 2>&1; then              # eza
  alias ls="eza --classify --time-style='long-iso'"
  alias ll='ls --git --group --header --long'
  alias l='ll --all --all'
else
  if ls --context >'/dev/null' 2>&1; then             # GNU ls version
    alias ls="ls --classify --color --time-style='long-iso'"

    if type -P 'dircolors' >'/dev/null' 2>&1; then
      eval "$(dircolors -b)"
    fi
  else                                                # other ls versions
    alias ls="ls -D '%F %T' -F -G"
  fi

  alias ll='ls -h -l'
  alias l='ll -a'
fi

alias lr='l -R'
alias lt="l -r -s 'time'"

alias mkdir='mkdir -p'

alias tail='tail -n 30 -f'

if [[ -e "${EDITOR}" && "${EDITOR##*/}" =~ ^n?vim$ ]]; then
  alias vi="${EDITOR##*/}"
fi

if type -P 'hwatch' >'/dev/null' 2>&1; then
  alias watch='hwatch'
fi

if declare -F my::_which >'/dev/null' 2>&1; then
  alias which='my::_which'
fi

if type -P 'direnv' >'/dev/null' 2>&1; then
  eval "$(direnv hook 'bash')"
fi

# ------------------------------------------------------------------------------
# Load additional files
# ---------------------

for item in "${HOME}/.bashrc.local" "${HOME}/.profile.local" \
            '/usr/local/etc/profile.d/bash_completion.sh'
do
  if [[ -f "${item}" ]]; then
    echo "Load '${item}'..."

    source "${item}"
  fi
done

# ------------------------------------------------------------------------------
# Configure vim & Neovim
# ----------------------

if [[ -e "${EDITOR}" && "${EDITOR##*/}" =~ ^n?vim$ ]]; then
  if [[ ! -f "${HOME}/.vimrc" ]]; then
    curl --fail --location --silent \
      'https://www.raysoft.ch/wiki//home/alex/.vimrc_(1)?action=raw' \
    | sed '1,/<syntaxhighlight/d; /<\/syntaxhighlight/,$d' >"${HOME}/.vimrc"
  fi

  if [[ ! -d "${HOME}/.vim/autoload" ]]; then
    mkdir -p "${HOME}/.vim/autoload"
  fi

  if [[ "${EDITOR##*/}" == 'nvim' ]]; then
    if [[ ! -d "${HOME}/.config/nvim" ]]; then
      mkdir -p "${HOME}/.config/nvim"
    fi

    if [[ ! -L "${HOME}/.config/nvim/init.vim" ]]; then
      ln -f -s "${HOME}/.vimrc" "${HOME}/.config/nvim/init.vim"
    fi

    for item in 'autoload' 'plugged'; do
      if [[ ! -L "${HOME}/.config/nvim/${item}" ]]; then
        ln -f -s "${HOME}/.vim/${item}" "${HOME}/.config/nvim/${item}"
      fi
    done
  fi

  if [[ ! -f "${HOME}/.vim/autoload/plug.vim" ]]; then
    curl --create-dirs --fail --location --silent \
      --output "${HOME}/.vim/autoload/plug.vim" \
      'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'

    "${EDITOR}" -c 'PlugUpdate | quitall'
  fi
fi

# ------------------------------------------------------------------------------
# Clean up
# --------

unset -v color count item host_name user_{id,name}

# ------------------------------------------------------------------------------