#!/usr/bin/env bash ## ## ## Visit for instructions and more information: ## https://github.com/andresgongora/synth-shell/ ## ## ## ##================================================================================================== ## CODE PARSERS ##================================================================================================== ##------------------------------------------------------------------------------ ## get8bitCode() { CODE=$1 case $CODE in default) echo 9 ;; none) echo 9 ;; black) echo 0 ;; red) echo 1 ;; green) echo 2 ;; yellow) echo 3 ;; blue) echo 4 ;; magenta|purple|pink) echo 5 ;; cyan) echo 6 ;; light-gray) echo 7 ;; dark-gray) echo 60 ;; light-red) echo 61 ;; light-green) echo 62 ;; light-yellow) echo 63 ;; light-blue) echo 64 ;; light-magenta|light-purple) echo 65 ;; light-cyan) echo 66 ;; white) echo 67 ;; *) echo 0 ;; esac } getColorCode() { COLOR=$1 if [ "$COLOR" -eq "$COLOR" ] 2>/dev/null; then if [ "$COLOR" -gt 0 ] && [ "$COLOR" -lt 256 ]; then echo "38;5;$COLOR" else echo 0 fi else BITCODE=$(get8bitCode "$COLOR") COLORCODE=$((BITCODE + 30)) echo "$COLORCODE" fi } getBackgroundCode() { COLOR=$1 if [ "$COLOR" -eq "$COLOR" ] 2>/dev/null; then if [ "$COLOR" -gt 0 ] && [ "$COLOR" -lt 256 ]; then echo "48;5;$COLOR" else echo 0 fi else BITCODE=$(get8bitCode "$COLOR") COLORCODE=$((BITCODE + 40)) echo "$COLORCODE" fi } getEffectCode() { EFFECT=$1 NONE=0 case $EFFECT in none|default) echo "$NONE" ;; bold|bright) echo 1 ;; dim) echo 2 ;; underline) echo 4 ;; blink) echo 5 ;; reverse) echo 7 ;; hidden) echo 8 ;; strikeout) echo 9 ;; *) echo "$NONE" ;; esac } getFormattingSequence() { START='\e[0;' MIDLE=$1 END='m' echo -n "$START$MIDLE$END" } ##================================================================================================== ## AUX ##================================================================================================== applyCodeToText() { local RESET RESET=$(getFormattingSequence "$(getEffectCode none)") TEXT=$1 CODE=$2 echo -n "$CODE$TEXT$RESET" } ##================================================================================================== ## MAIN FUNCTIONS ##================================================================================================== getFormatCode() { local RESET RESET=$(getFormattingSequence "$(getEffectCode none)") if [ "$#" -eq 0 ]; then echo -n "$RESET" elif [ "$#" -eq 1 ]; then TEXT_COLOR=$(getFormattingSequence "$(getColorCode "$1")") echo -n "$TEXT_COLOR" else FORMAT="" while [ "$1" != "" ]; do TYPE=$1 ARGUMENT=$2 case $TYPE in -c) CODE=$(getColorCode "$ARGUMENT") ;; -b) CODE=$(getBackgroundCode "$ARGUMENT") ;; -e) CODE=$(getEffectCode "$ARGUMENT") ;; *) CODE="" ;; esac if [ "$FORMAT" != "" ]; then FORMAT="$FORMAT;" fi FORMAT="$FORMAT$CODE" shift shift done FORMAT_CODE=$(getFormattingSequence "$FORMAT") echo -n "${FORMAT_CODE}" fi } formatText() { local RESET RESET=$(getFormattingSequence "$(getEffectCode none)") if [ "$#" -eq 0 ]; then echo -n "${RESET}" elif [ "$#" -eq 1 ]; then TEXT=$1 echo -n "${TEXT}${RESET}" else TEXT=$1 FORMAT_CODE=$(getFormatCode "${@:2}") applyCodeToText "$TEXT" "$FORMAT_CODE" fi } removeColorCodes() { printf "%s" "$1" | sed 's/\x1b\[[0-9;]*m//g' } ##============================================================================== ## MAIN FORMAT ##============================================================================== format="USER HOST PWD GIT PYENV TF KUBE DATE CLOCK" separator_char='\uE0B0' separator_padding_left='' separator_padding_right='' segment_padding=' ' enable_vertical_padding=true enable_command_on_new_line=true # Nerd Font icons. # Font hanya perlu tersedia pada terminal/client yang merender sesi SSH # (misalnya Termius). Tidak perlu install Nerd Font di setiap server Debian. icon_user=$'\uf007' icon_folder=$'\uf07b' icon_calendar=$'\uf073' icon_clock=$'\uf017' icon_debian=$'\ue77d' icon_linux=$'\uf17c' ##============================================================================== ## USER ##============================================================================== font_color_user="black" background_user="green" texteffect_user="bold" ##============================================================================== ## HOST ##============================================================================== font_color_host="white" background_host="light-blue" texteffect_host="bold" ##============================================================================== ## PWD (working dir) ##============================================================================== font_color_pwd="dark-gray" background_pwd="white" texteffect_pwd="bold" max_pwd_char="25" ##============================================================================== ## GIT ##============================================================================== font_color_git="light-gray" background_git="dark-gray" texteffect_git="bold" git_symbol_synced='' git_symbol_unpushed=' ▲' git_symbol_unpulled=' ▼' git_symbol_unpushedunpulled=' ◆' git_symbol_dirty=' ◔' git_symbol_dirty_unpushed=' ◔ △' git_symbol_dirty_unpulled=' ◔ ▽' git_symbol_dirty_unpushedunpulled=' ◔ ◇' git_symbol_stash='🗎' git_update_period_minutes=15 ##============================================================================== ## PYENV ##============================================================================== font_color_pyenv="white" background_pyenv="blue" texteffect_pyenv="bold" ##============================================================================== ## KUBERNETES ##============================================================================== font_color_kube="white" background_kube="purple" texteffect_kube="bold" ##============================================================================== ## TERRAFORM WORKSPACE ##============================================================================== font_color_tf="purple" background_tf="light-purple" texteffect_tf="bold" ##============================================================================== ## CLOCK ##============================================================================== font_color_clock="white" background_clock="black" texteffect_clock="bold" clock_format="%H:%M:%S" ##============================================================================== ## DATE ##============================================================================== font_color_date="white" background_date="light-purple" texteffect_date="bold" date_format="${hari}, %d ${bulan} %Y" ##============================================================================== ## INPUT (user typed command) ##============================================================================== font_color_input="45" background_input="none" texteffect_input="bold" ##============================================================================== ## EXTERNAL DEPENDENCIES ##============================================================================== [ "$(type -t include)" != 'function' ] && { include() { { [ -z "$_IR" ] && _IR="$PWD" && cd "$(dirname "${BASH_SOURCE[0]}")" && include "$1" && cd "$_IR" && unset _IR } || { local d="$PWD" && cd "$(dirname "$PWD/$1")" && . "$(basename "$1")" && cd "$d" } || { echo "Include failed $PWD->$1" && exit 1 } } } synth_shell_prompt() { ##============================================================================== ## FUNCTIONS ##============================================================================== getOSIcon() { if [ -r /etc/os-release ]; then local os_id os_id=$(. /etc/os-release; printf '%s' "${ID:-}") case "$os_id" in debian) printf '%s' "$SSP_ICON_DEBIAN" ;; *) printf '%s' "$SSP_ICON_LINUX" ;; esac else printf '%s' "$SSP_ICON_LINUX" fi } getGitBranch() { if ! command -v git >/dev/null 2>&1; then echo "" return fi local branch branch=$(git branch 2>/dev/null | sed -n '/^[^*]/d;s/*\s*\(.*\)/\1/p') if [[ -z "$branch" ]]; then echo "" return fi if [ "$SSP_GIT_UPDATE_PERIOD_MINUTES" -ge 0 ]; then local d="$PWD" local max_lvls=25 while [ ! -e "./.git" ] && [ "$max_lvls" -gt 0 ]; do cd .. max_lvls=$((max_lvls - 1)) done local dot_git="${PWD}/.git" cd "$d" || return if [ -f "$dot_git" ]; then dot_git=$(grep 'gitdir' "$dot_git" | sed 's/gitdir:\ //g') fi local git_last_update="" if [ -d "$dot_git" ] && [ -e "${dot_git}/FETCH_HEAD" ]; then git_last_update=$(stat -c "%Y" "${dot_git}/FETCH_HEAD") fi if [ -n "$git_last_update" ]; then local current_timestamp local elapsed_minutes current_timestamp=$(date +%s) elapsed_minutes=$(((current_timestamp-git_last_update)/60)) if [ "$elapsed_minutes" -ge "$SSP_GIT_UPDATE_PERIOD_MINUTES" ]; then git fetch --recurse-submodules >/dev/null 2>&1 & fi fi fi local is_dirty=false [[ -n "$(git status --porcelain 2>/dev/null)" ]] && is_dirty=true local is_ahead=false [[ "$(git status --porcelain -u no -b 2>/dev/null)" == *"ahead"* ]] && is_ahead=true local is_behind=false [[ "$(git status --porcelain -u no -b 2>/dev/null)" == *"behind"* ]] && is_behind=true local symbol if $is_dirty && $is_ahead && $is_behind; then symbol=$SSP_GIT_DIRTY_DIVERGED elif $is_dirty && $is_ahead; then symbol=$SSP_GIT_DIRTY_AHEAD elif $is_dirty && $is_behind; then symbol=$SSP_GIT_DIRTY_BEHIND elif $is_dirty; then symbol=$SSP_GIT_DIRTY elif $is_ahead && $is_behind; then symbol=$SSP_GIT_DIVERGED elif $is_ahead; then symbol=$SSP_GIT_AHEAD elif $is_behind; then symbol=$SSP_GIT_BEHIND else symbol=$SSP_GIT_SYNCED fi local tag="" if [[ -n "$(git tag --points-at HEAD 2>/dev/null)" ]]; then tag=" $(git tag --points-at HEAD 2>/dev/null)" fi local git_stash="" local stashed_elements stashed_elements=$(git stash list 2>/dev/null | wc -l) if [ "$stashed_elements" -gt 0 ]; then git_stash=" ${stashed_elements}${SSP_GIT_STASH}" fi echo "${branch}${symbol}${git_stash}${tag}" } getTerraform() { if ! command -v terraform >/dev/null 2>&1; then echo "" return fi if [ ! -d .terraform ]; then echo "" return fi local tf tf="$(terraform workspace show 2>/dev/null | tr -d '\n')" echo "$tf" } getPyenv() { if [ -n "${CONDA_DEFAULT_ENV:-}" ]; then echo "$CONDA_DEFAULT_ENV" elif [ -n "${VIRTUAL_ENV:-}" ]; then local regex='PS1=\"\((.*?)\)\s\$\{PS1' local pyenv pyenv=$(perl -n -e"/$regex/ && print \$1" "$VIRTUAL_ENV/bin/activate" 2>/dev/null) if [ -z "${pyenv}" ]; then pyenv=$(basename "$VIRTUAL_ENV") fi echo "$pyenv" fi } getKube() { type kubectl &>/dev/null && type yq &>/dev/null && echo -n "$(kubectl config view | yq '.contexts[].context.cluster |select(.contexts[].name == .current-context)' | head -n 1)" } printSegment() { local text=$1 local font_color=$2 local background_color=$3 local next_background_color=$4 local font_effect=$5 local no_color="\[$(getFormatCode -e reset)\]" local text_format="\[$(getFormatCode -c "$font_color" -b "$background_color" -e "$font_effect")\]" local separator_format="\[$(getFormatCode -c "$background_color" -b "$next_background_color")\]" printf "${text_format}${segment_padding}${text}${segment_padding}${separator_padding_left}${separator_format}${separator_char}${separator_padding_right}${no_color}" } get_colors_for_element() { case $1 in "USER") echo "${SSP_COLORS_USER[@]}" ;; "HOST") echo "${SSP_COLORS_HOST[@]}" ;; "PWD") echo "${SSP_COLORS_PWD[@]}" ;; "GIT") echo "${SSP_COLORS_GIT[@]}" ;; "PYENV") echo "${SSP_COLORS_PYENV[@]}" ;; "KUBE") echo "${SSP_COLORS_KUBE[@]}" ;; "TF") echo "${SSP_COLORS_TF[@]}" ;; "CLOCK") echo "${SSP_COLORS_CLOCK[@]}" ;; "DATE") echo "${SSP_COLORS_DATE[@]}" ;; "INPUT") echo "${SSP_COLORS_INPUT[@]}" ;; esac } combine_elements() { local first=$1 local second=$2 local colors_first=($(get_colors_for_element "$first")) local colors_second=($(get_colors_for_element "$second")) case $first in "USER") local text="$user" ;; "HOST") local text="$host" ;; "PWD") local text="$path" ;; "GIT") local text="$git_branch" ;; "PYENV") local text="$pyenv" ;; "KUBE") local text="$kube" ;; "TF") local text="$tf" ;; "CLOCK") local text="$clock" ;; "DATE") local text="$date" ;; "INPUT") local text="" ;; *) local text="" ;; esac local text_color=${colors_first[0]} local bg_color=${colors_first[1]} local next_bg_color=${colors_second[1]} local text_effect=${colors_first[2]} printSegment "$text" "$text_color" "$bg_color" "$next_bg_color" "$text_effect" } prompt_command_hook() { local elements=(${SSP_ELEMENTS[@]}) local user="${SSP_ICON_USER} ${USER}" local host="$(getOSIcon) ${HOSTNAME}" local path="${SSP_ICON_FOLDER} ${PWD}" local git_branch local pyenv local kube local tf local clock local date git_branch="$(getGitBranch)" pyenv="$(getPyenv)" kube="$(getKube)" tf="$(getTerraform)" clock="${SSP_ICON_CLOCK} $(date +"${SSP_CLOCK_FORMAT}")" date="${SSP_ICON_CALENDAR} $(date +"${SSP_DATE_FORMAT}")" if [ -z "$git_branch" ]; then elements=( ${elements[@]/"GIT"} ) fi if [ -z "$pyenv" ]; then elements=( ${elements[@]/"PYENV"} ) fi if [ -z "$tf" ]; then elements=( ${elements[@]/"TF"} ) fi if [ -z "$kube" ]; then elements=( ${elements[@]/"KUBE"} ) fi case $TERM in xterm*|rxvt*) SSP_PWD="$PWD" local titlebar="\[\033]0;\${USER}@\${HOSTNAME}: \${SSP_PWD}\007\]" ;; *) local titlebar="" ;; esac PS1="${titlebar}${SSP_VERTICAL_PADDING}${SSP_NEW_LINE_LINK_TOP}" while [ "${#elements[@]}" -gt 1 ]; do local current=${elements[0]} local next=${elements[1]} elements=("${elements[@]:1}") PS1="$PS1$(combine_elements "$current" "$next")" done local input_colors=($(get_colors_for_element "${elements[0]}")) local input_color=${input_colors[0]} local input_bg=${input_colors[1]} local input_effect=${input_colors[2]} local input_format="\[$(getFormatCode -c "$input_color" -b "$input_bg" -e "$input_effect")\]" local command_start_symbol="${input_format}${SSP_BASH_SYMBOL}" PS1="${PS1}${SSP_PROMPT_COMM_SEP}${SSP_NEW_LINE_LINK_BOTTOM}${command_start_symbol} $input_format" } ##============================================================================== ## MAIN ##============================================================================== if $enable_vertical_padding; then local vertical_padding="\n" else local vertical_padding="" fi if $enable_command_on_new_line; then local new_line_link_top="╭" local new_line_link_bottom="╰>" local prompt_command_separation="\n" local bash_symbol="\$" else local new_line_link_top="" local new_line_link_bottom="" local prompt_command_separation="" local bash_symbol="" fi SSP_ELEMENTS=($format "INPUT") SSP_COLORS_USER=($font_color_user $background_user $texteffect_user) SSP_COLORS_HOST=($font_color_host $background_host $texteffect_host) SSP_COLORS_PWD=($font_color_pwd $background_pwd $texteffect_pwd) SSP_COLORS_GIT=($font_color_git $background_git $texteffect_git) SSP_COLORS_PYENV=($font_color_pyenv $background_pyenv $texteffect_pyenv) SSP_COLORS_KUBE=($font_color_kube $background_kube $texteffect_kube) SSP_COLORS_TF=($font_color_tf $background_tf $texteffect_tf) SSP_COLORS_CLOCK=($font_color_clock $background_clock $texteffect_clock) SSP_COLORS_DATE=($font_color_date $background_date $texteffect_date) SSP_COLORS_INPUT=($font_color_input $background_input $texteffect_input) SSP_VERTICAL_PADDING=$vertical_padding SSP_NEW_LINE_LINK_TOP=$new_line_link_top SSP_NEW_LINE_LINK_BOTTOM=$new_line_link_bottom SSP_PROMPT_COMM_SEP=$prompt_command_separation SSP_BASH_SYMBOL=$bash_symbol SSP_MAX_PWD_CHAR=${max_pwd_char:-25} SSP_GIT_SYNCED=$git_symbol_synced SSP_GIT_AHEAD=$git_symbol_unpushed SSP_GIT_BEHIND=$git_symbol_unpulled SSP_GIT_DIVERGED=$git_symbol_unpushedunpulled SSP_GIT_DIRTY=$git_symbol_dirty SSP_GIT_DIRTY_AHEAD=$git_symbol_dirty_unpushed SSP_GIT_DIRTY_BEHIND=$git_symbol_dirty_unpulled SSP_GIT_DIRTY_DIVERGED=$git_symbol_dirty_unpushedunpulled SSP_GIT_STASH=$git_symbol_stash SSP_GIT_UPDATE_PERIOD_MINUTES=$git_update_period_minutes SSP_CLOCK_FORMAT=${clock_format:-"%H:%M:%S"} SSP_DATE_FORMAT=${date_format:-"%A, %d %B %Y"} SSP_ICON_USER=$icon_user SSP_ICON_FOLDER=$icon_folder SSP_ICON_CALENDAR=$icon_calendar SSP_ICON_CLOCK=$icon_clock SSP_ICON_DEBIAN=$icon_debian SSP_ICON_LINUX=$icon_linux none="$(tput sgr0)" trap 'echo -ne "${none}"' DEBUG PROMPT_COMMAND=prompt_command_hook } if [ "${BASH_SOURCE[0]}" == "${0}" ]; then echo -e "Do not run this script, it will do nothing.\nPlease source it instead by running:\n" echo -e "\t. ${BASH_SOURCE[0]}\n" exit elif [[ $- == *i* ]]; then if command -v tput >/dev/null 2>&1 && tput setaf 1 >&/dev/null; then synth_shell_prompt fi unset synth_shell_prompt unset include fi